From 1b5fc8529badfa2f0d51b68341e9560642b7f19c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Sun, 8 Feb 2026 13:03:15 +0100 Subject: [PATCH 01/25] Added F1 2025 packets --- GamesDat/GamesDat.Core.csproj | 6 + .../Telemetry/Sources/Formula1/EventCodes.cs | 33 +++++ .../Sources/Formula1/F12025/CarDamageData.cs | 39 ++++++ .../Sources/Formula1/F12025/CarMotionData.cs | 32 +++++ .../Sources/Formula1/F12025/CarSetup.cs | 32 +++++ .../Sources/Formula1/F12025/CarSetupData.cs | 15 ++ .../Sources/Formula1/F12025/CarStatusData.cs | 45 ++++++ .../Sources/Formula1/F12025/CarTelemetry.cs | 36 +++++ .../Formula1/F12025/CarTelemetryData.cs | 22 +++ .../Sources/Formula1/F12025/EventData.cs | 43 ++++++ .../Formula1/F12025/EventDataDetails.cs | 131 ++++++++++++++++++ .../F12025/FinalClassificationData.cs | 28 ++++ .../Sources/Formula1/F12025/LapData.cs | 48 +++++++ .../Sources/Formula1/F12025/LapHistoryData.cs | 18 +++ .../Sources/Formula1/F12025/LobbyInfoData.cs | 27 ++++ .../Sources/Formula1/F12025/MarshalZone.cs | 11 ++ .../Sources/Formula1/F12025/MotionData.cs | 16 +++ .../Formula1/F12025/PacketCarDamageData.cs | 14 ++ .../Formula1/F12025/PacketCarStatusData.cs | 18 +++ .../F12025/PacketFinalClassificationData.cs | 15 ++ .../Sources/Formula1/F12025/PacketHeader.cs | 21 +++ .../Sources/Formula1/F12025/PacketLapData.cs | 18 +++ .../Formula1/F12025/PacketLapPositionsData.cs | 18 +++ .../Formula1/F12025/PacketLobbyInfoData.cs | 20 +++ .../Formula1/F12025/PacketMotionExData.cs | 63 +++++++++ .../F12025/PacketSessionHistoryData.cs | 26 ++++ .../Formula1/F12025/PacketTimeTrialData.cs | 14 ++ .../Formula1/F12025/PacketTyreSetsData.cs | 18 +++ .../Sources/Formula1/F12025/Participant.cs | 30 ++++ .../Formula1/F12025/ParticipantData.cs | 15 ++ .../Sources/Formula1/F12025/SessionData.cs | 101 ++++++++++++++ .../Formula1/F12025/TimeTrialDataSet.cs | 21 +++ .../Sources/Formula1/F12025/TyreSetData.cs | 18 +++ .../Formula1/F12025/TyreStintHistoryData.cs | 12 ++ .../Formula1/F12025/WeatherForecastSample.cs | 32 +++++ .../Formula1/F1RealtimeTelemetrySource.cs | 20 +++ .../Sources/Formula1/F1TelemetryFrame.cs | 12 ++ .../Sources/Formula1/LiveryColour.cs | 12 ++ .../Telemetry/Sources/Formula1/PacketId.cs | 23 +++ GamesDat/Telemetry/Sources/UdpSourceBase.cs | 54 ++++++++ .../Telemetry/Sources/UdpSourceOptions.cs | 21 +++ 41 files changed, 1198 insertions(+) create mode 100644 GamesDat/Telemetry/Sources/Formula1/EventCodes.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/CarDamageData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/CarMotionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/CarSetup.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/CarSetupData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/CarStatusData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetry.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/FinalClassificationData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/LapData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/LapHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/LobbyInfoData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/MarshalZone.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/MotionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketCarDamageData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketCarStatusData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketFinalClassificationData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketHeader.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketLapData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketLapPositionsData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketLobbyInfoData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketMotionExData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketSessionHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketTimeTrialData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketTyreSetsData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/Participant.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/ParticipantData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/SessionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/TimeTrialDataSet.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/TyreSetData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/TyreStintHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/WeatherForecastSample.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/LiveryColour.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/PacketId.cs create mode 100644 GamesDat/Telemetry/Sources/UdpSourceBase.cs create mode 100644 GamesDat/Telemetry/Sources/UdpSourceOptions.cs diff --git a/GamesDat/GamesDat.Core.csproj b/GamesDat/GamesDat.Core.csproj index eb279c4..347f089 100644 --- a/GamesDat/GamesDat.Core.csproj +++ b/GamesDat/GamesDat.Core.csproj @@ -14,4 +14,10 @@ + + + + + + diff --git a/GamesDat/Telemetry/Sources/Formula1/EventCodes.cs b/GamesDat/Telemetry/Sources/Formula1/EventCodes.cs new file mode 100644 index 0000000..28dfbd0 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/EventCodes.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + public static class EventCodes + { + public const string SessionStarted = "SSTA"; + public const string SessionEnded = "SEND"; + public const string FastestLap = "FTLP"; + public const string Retirement = "RTMT"; + public const string DrsEnabled = "DRSE"; + public const string DrsDisabled = "DRSD"; + public const string TeamMateInPits = "TMPT"; + public const string ChequeredFlag = "CHQF"; + public const string RaceWinner = "RCWN"; + public const string Penalty = "PENA"; + public const string SpeedTrap = "SPTP"; + public const string StartLights = "STLG"; + public const string LightsOut = "LGOT"; + public const string DriveThroughServed = "DTSV"; + public const string StopGoServed = "SGSV"; + public const string Flashback = "FLBK"; + public const string ButtonStatus = "BUTN"; + public const string RedFlag = "RDFL"; + public const string Overtake = "OVTK"; + public const string SafetyCar = "SCAR"; + public const string Collision = "COLL"; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarDamageData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarDamageData.cs new file mode 100644 index 0000000..81dcc11 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarDamageData.cs @@ -0,0 +1,39 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarDamageData + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresWear; // Tyre wear (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresDamage; // Tyre damage (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_brakesDamage; // Brakes damage (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyreBlisters; // Tyre blisters value (percentage) + + public byte m_frontLeftWingDamage; // Front left wing damage (percentage) + public byte m_frontRightWingDamage; // Front right wing damage (percentage) + public byte m_rearWingDamage; // Rear wing damage (percentage) + public byte m_floorDamage; // Floor damage (percentage) + public byte m_diffuserDamage; // Diffuser damage (percentage) + public byte m_sidepodDamage; // Sidepod damage (percentage) + public byte m_drsFault; // Indicator for DRS fault, 0 = OK, 1 = fault + public byte m_ersFault; // Indicator for ERS fault, 0 = OK, 1 = fault + public byte m_gearBoxDamage; // Gear box damage (percentage) + public byte m_engineDamage; // Engine damage (percentage) + public byte m_engineMGUHWear; // Engine wear MGU-H (percentage) + public byte m_engineESWear; // Engine wear ES (percentage) + public byte m_engineCEWear; // Engine wear CE (percentage) + public byte m_engineICEWear; // Engine wear ICE (percentage) + public byte m_engineMGUKWear; // Engine wear MGU-K (percentage) + public byte m_engineTCWear; // Engine wear TC (percentage) + public byte m_engineBlown; // Engine blown, 0 = OK, 1 = fault + public byte m_engineSeized; // Engine seized, 0 = OK, 1 = fault + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarMotionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarMotionData.cs new file mode 100644 index 0000000..f402c84 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarMotionData.cs @@ -0,0 +1,32 @@ + +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + + /// + /// Car motion data for F1 2025 telemetry packets. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarMotionData + { + public float m_worldPositionX; // World space X position - metres + public float m_worldPositionY; // World space Y position + public float m_worldPositionZ; // World space Z position + public float m_worldVelocityX; // Velocity in world space X - metres/s + public float m_worldVelocityY; // Velocity in world space Y + public float m_worldVelocityZ; // World space velocity in Z + public short m_worldForwardDirX; // World space forward X direction (normalised) + public short m_worldForwardDirY; // World space forward Y direction (normalised) + public short m_worldForwardDirZ; // World space forward Z direction (normalised) + public short m_worldRightDirX; // World space right X direction (normalised) + public short m_worldRightDirY; // World space right Y direction (normalised) + public short m_worldRightDirZ; // World space right Z direction (normalised) + public float m_gForceLateral; // Lateral G-Force component + public float m_gForceLongitudinal; // Longitudinal G-Force component + public float m_gForceVertical; // Vertical G-Force component + public float m_yaw; // Yaw angle in radians + public float m_pitch; // Pitch angle in radians + public float m_roll; // Roll angle in radians + } +} \ No newline at end of file diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetup.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetup.cs new file mode 100644 index 0000000..3e89582 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetup.cs @@ -0,0 +1,32 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarSetup + { + public byte m_frontWing; // Front wing aero + public byte m_rearWing; // Rear wing aero + public byte m_onThrottle; // Differential adjustment on throttle (percentage) + public byte m_offThrottle; // Differential adjustment off throttle (percentage) + public float m_frontCamber; // Front camber angle (suspension geometry) + public float m_rearCamber; // Rear camber angle (suspension geometry) + public float m_frontToe; // Front toe angle (suspension geometry) + public float m_rearToe; // Rear toe angle (suspension geometry) + public byte m_frontSuspension; // Front suspension + public byte m_rearSuspension; // Rear suspension + public byte m_frontAntiRollBar; // Front anti-roll bar + public byte m_rearAntiRollBar; // Front anti-roll bar + public byte m_frontSuspensionHeight; // Front ride height + public byte m_rearSuspensionHeight; // Rear ride height + public byte m_brakePressure; // Brake pressure (percentage) + public byte m_brakeBias; // Brake bias (percentage) + public byte m_engineBraking; // Engine braking (percentage) + public float m_rearLeftTyrePressure; // Rear left tyre pressure (PSI) + public float m_rearRightTyrePressure; // Rear right tyre pressure (PSI) + public float m_frontLeftTyrePressure; // Front left tyre pressure (PSI) + public float m_frontRightTyrePressure; // Front right tyre pressure (PSI) + public byte m_ballast; // Ballast + public float m_fuelLoad; // Fuel load + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetupData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetupData.cs new file mode 100644 index 0000000..f6a327f --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetupData.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarSetupData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarSetupData[] m_carSetupData; + + public float m_nextFrontWingValue; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarStatusData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarStatusData.cs new file mode 100644 index 0000000..3f7e063 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarStatusData.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarStatusData + { + public byte m_tractionControl; // Traction control - 0 = off, 1 = medium, 2 = full + public byte m_antiLockBrakes; // 0 (off) - 1 (on) + public byte m_fuelMix; // Fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max + public byte m_frontBrakeBias; // Front brake bias (percentage) + public byte m_pitLimiterStatus; // Pit limiter status - 0 = off, 1 = on + public float m_fuelInTank; // Current fuel mass + public float m_fuelCapacity; // Fuel capacity + public float m_fuelRemainingLaps; // Fuel remaining in terms of laps (value on MFD) + public ushort m_maxRPM; // Cars max RPM, point of rev limiter + public ushort m_idleRPM; // Cars idle RPM + public byte m_maxGears; // Maximum number of gears + public byte m_drsAllowed; // 0 = not allowed, 1 = allowed + public ushort m_drsActivationDistance; // 0 = DRS not available, non-zero - DRS will be available in [X] metres + public byte m_actualTyreCompound; // F1 Modern - 16 = C5, 17 = C4, 18 = C3, 19 = C2, 20 = C1, 21 = C0, 7 = inter, 8 = wet + // F1 Classic - 9 = dry, 10 = wet + // F2 – 11 = super soft, 12 = soft, 13 = medium, 14 = hard, 15 = wet + public byte m_visualTyreCompound; // F1 visual (can be different from actual compound) + // 16 = soft, 17 = medium, 18 = hard, 7 = inter, 8 = wet + // F1 Classic – same as above + // F2 '20, 15 = wet, 19 – super soft, 20 = soft, 21 = medium, 22 = hard + public byte m_tyresAgeLaps; // Age in laps of the current set of tyres + public sbyte m_vehicleFIAFlags; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow + public float m_enginePowerICE; // Engine power output of ICE (W) + public float m_enginePowerMGUK; // Engine power output of MGU-K (W) + public float m_ersStoreEnergy; // ERS energy store in Joules + public byte m_ersDeployMode; // ERS deployment mode, 0 = none, 1 = medium, 2 = hotlap, 3 = overtake + public float m_ersHarvestedThisLapMGUK; // ERS energy harvested this lap by MGU-K + public float m_ersHarvestedThisLapMGUH; // ERS energy harvested this lap by MGU-H + public float m_ersDeployedThisLap; // ERS energy deployed this lap + public byte m_networkPaused; // Whether the car is paused in a network game + + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetry.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetry.cs new file mode 100644 index 0000000..7fbbad5 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetry.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarTelemetry + { + public ushort m_speed; // Speed of car in kilometres per hour + public float m_throttle; // Amount of throttle applied (0.0 to 1.0) + public float m_steer; // Steering (-1.0 (full lock left) to 1.0 (full lock right)) + public float m_brake; // Amount of brake applied (0.0 to 1.0) + public byte m_clutch; // Amount of clutch applied (0 to 100) + public sbyte m_gear; // Gear selected (1-8, N=0, R=-1) + public ushort m_engineRPM; // Engine RPM + public byte m_drs; // 0 = off, 1 = on + public byte m_revLightsPercent; // Rev lights indicator (percentage) + public ushort m_revLightsBitValue; // Rev lights (bit 0 = leftmost LED, bit 14 = rightmost LED) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public ushort[] m_brakesTemperature; // Brakes temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresSurfaceTemperature; // Tyres surface temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresInnerTemperature; // Tyres inner temperature (celsius) + + public ushort m_engineTemperature; // Engine temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresPressure; // Tyre pressure (PSI) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_surfaceType; // Driving surface, see appendices + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetryData.cs new file mode 100644 index 0000000..f58b715 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetryData.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarTelemetryData + { + public PacketHeader m_header; // Header + + // Packet specific data + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarTelemetryData[] m_carTelemetryData; + + public byte m_mfdPanelIndex; // Index of MFD panel open - 255 = MFD closed + // Single player, race – 0 = Car setup, 1 = Pits + // 2 = Damage, 3 = Engine, 4 = Temperatures + // May vary depending on game mode + public byte m_mfdPanelIndexSecondaryPlayer; // See above + public byte m_suggestedGear; // Suggested gear for the player (1-8), 0 if no gear suggested + + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs new file mode 100644 index 0000000..f7d3523 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs @@ -0,0 +1,43 @@ +using System.Runtime.InteropServices; +using System.Text; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EventData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] EventStringCode; + + public EventDataDetails m_eventDetails; + + // Helper property to get event code as string + public string EventCode => Encoding.ASCII.GetString(EventStringCode); + + public T GetEventDetails() where T : struct + { + return EventCode switch + { + EventCodes.FastestLap when typeof(T) == typeof(FastestLapData) => (T)(object)m_eventDetails.FastestLap, + EventCodes.Retirement when typeof(T) == typeof(RetirementData) => (T)(object)m_eventDetails.Retirement, + EventCodes.DrsDisabled when typeof(T) == typeof(DRSDisabledData) => (T)(object)m_eventDetails.DRSDisabled, + EventCodes.TeamMateInPits when typeof(T) == typeof(TeamMateInPitsData) => (T)(object)m_eventDetails.TeamMateInPits, + EventCodes.RaceWinner when typeof(T) == typeof(RaceWinnerData) => (T)(object)m_eventDetails.RaceWinner, + EventCodes.Penalty when typeof(T) == typeof(PenaltyData) => (T)(object)m_eventDetails.Penalty, + EventCodes.SpeedTrap when typeof(T) == typeof(SpeedTrapData) => (T)(object)m_eventDetails.SpeedTrap, + EventCodes.StartLights when typeof(T) == typeof(StartLightsData) => (T)(object)m_eventDetails.StartLights, + EventCodes.DriveThroughServed when typeof(T) == typeof(DriveThroughPenaltyServedData) => (T)(object)m_eventDetails.DriveThroughPenaltyServed, + EventCodes.StopGoServed when typeof(T) == typeof(StopGoPenaltyServedData) => (T)(object)m_eventDetails.StopGoPenaltyServed, + EventCodes.Flashback when typeof(T) == typeof(FlashbackData) => (T)(object)m_eventDetails.Flashback, + EventCodes.ButtonStatus when typeof(T) == typeof(ButtonsData) => (T)(object)m_eventDetails.Buttons, + EventCodes.Overtake when typeof(T) == typeof(OvertakeData) => (T)(object)m_eventDetails.Overtake, + EventCodes.SafetyCar when typeof(T) == typeof(SafetyCarData) => (T)(object)m_eventDetails.SafetyCar, + EventCodes.Collision when typeof(T) == typeof(CollisionData) => (T)(object)m_eventDetails.Collision, + // fallback for unsupported event types or mismatched type requests + _ => throw new InvalidOperationException($"Cannot get {typeof(T).Name} for event {EventCode}") + }; + } + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs new file mode 100644 index 0000000..16eef6f --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs @@ -0,0 +1,131 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Explicit)] + public struct EventDataDetails + { + [FieldOffset(0)] public FastestLapData FastestLap; + [FieldOffset(0)] public RetirementData Retirement; + [FieldOffset(0)] public DRSDisabledData DRSDisabled; + [FieldOffset(0)] public TeamMateInPitsData TeamMateInPits; + [FieldOffset(0)] public RaceWinnerData RaceWinner; + [FieldOffset(0)] public PenaltyData Penalty; + [FieldOffset(0)] public SpeedTrapData SpeedTrap; + [FieldOffset(0)] public StartLightsData StartLights; + [FieldOffset(0)] public DriveThroughPenaltyServedData DriveThroughPenaltyServed; + [FieldOffset(0)] public StopGoPenaltyServedData StopGoPenaltyServed; + [FieldOffset(0)] public FlashbackData Flashback; + [FieldOffset(0)] public ButtonsData Buttons; + [FieldOffset(0)] public OvertakeData Overtake; + [FieldOffset(0)] public SafetyCarData SafetyCar; + [FieldOffset(0)] public CollisionData Collision; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FastestLapData + { + public byte VehicleIdx; + public float LapTime; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RetirementData + { + public byte VehicleIdx; + public byte Reason; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TeamMateInPitsData + { + public byte VehicleIdx; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DRSDisabledData + { + public byte VehicleIdx; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RaceWinnerData + { + public byte VehicleIdx; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PenaltyData + { + public byte PenaltyType; + public byte InfringementType; + public byte VehicleIdx; + public byte OtherVehicleIdx; + public byte Time; + public byte LapNum; + public byte PlacesGained; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SpeedTrapData + { + public byte VehicleIdx; + public float Speed; + public byte OverallFastestInSession; + public byte DriverFastestInSession; + public byte FastestVehicleIdxInSession; + public float FastestSpeedInSession; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct StartLightsData + { + public byte NumLights; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DriveThroughPenaltyServedData + { + public byte VehicleIdx; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct StopGoPenaltyServedData + { + public byte VehicleIdx; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FlashbackData + { + public byte FlashbackFrameIdentifier; + public float FlashbackSessionTime; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ButtonsData + { + public uint ButtonStatus; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct OvertakeData + { + public byte OvertakingVehicleIdx; + public byte OvertakenVehicleIdx; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SafetyCarData + { + public byte SafetyCarType; // 0 = No Safety Car, 1 = Full Safety Car, 2 = Virtual Safety Car, 3 = Formation Lap Safety Car + public byte EventType; // 0 = Deployed, 1 = Returning, 2 = Returned, 3 = Resume Race + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CollisionData + { + public byte VehicleIdx; + public byte CollidingVehicleIdx; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/FinalClassificationData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/FinalClassificationData.cs new file mode 100644 index 0000000..f0c96a0 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/FinalClassificationData.cs @@ -0,0 +1,28 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FinalClassificationData + { + public byte m_position; // Finishing position + public byte m_numLaps; // Number of laps completed + public byte m_gridPosition; // Grid position of the car + public byte m_points; // Number of points scored + public byte m_numPitStops; // Number of pit stops made + public byte m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active, 3 = finished, 4 = didnotfinish, 5 = disqualified, 6 = not classified, 7 = retired + public byte m_resultReason; // Result reason - 0 = invalid, 1 = retired, 2 = finished, 3 = terminal damage, 4 = inactive, 5 = not enough laps completed, 6 = black flagged + // 7 = red flagged, 8 = mechanical failure, 9 = session skipped, 10 = session simulated + public uint m_bestLapTimeInMS; // Best lap time of the session in milliseconds + public double m_totalRaceTime; // Total race time in seconds without penalties + public byte m_penaltiesTime; // Total penalties accumulated in seconds + public byte m_numPenalties; // Number of penalties applied to this driver + public byte m_numTyreStints; // Number of tyres stints up to maximum + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsActual; // Actual tyres used by this driver + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsVisual; // Visual tyres used by this driver + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsEndLaps; // The lap number stints end on + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/LapData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/LapData.cs new file mode 100644 index 0000000..8051e10 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/LapData.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LapData + { + public uint m_lastLapTimeInMS; // Last lap time in milliseconds + public uint m_currentLapTimeInMS; // Current time around the lap in milliseconds + public ushort m_sector1TimeMSPart; // Sector 1 time milliseconds part + public byte m_sector1TimeMinutesPart; // Sector 1 whole minute part + public ushort m_sector2TimeMSPart; // Sector 2 time milliseconds part + public byte m_sector2TimeMinutesPart; // Sector 2 whole minute part + public ushort m_deltaToCarInFrontMSPart; // Time delta to car in front milliseconds part + public byte m_deltaToCarInFrontMinutesPart; // Time delta to car in front whole minute part + public ushort m_deltaToRaceLeaderMSPart; // Time delta to race leader milliseconds part + public byte m_deltaToRaceLeaderMinutesPart; // Time delta to race leader whole minute part + public float m_lapDistance; // Distance vehicle is around current lap in metres – could be negative if line hasn't been crossed yet + public float m_totalDistance; // Total distance travelled in session in metres – could be negative if line hasn't been crossed yet + public float m_safetyCarDelta; // Delta in seconds for safety car + public byte m_carPosition; // Car race position + public byte m_currentLapNum; // Current lap number + public byte m_pitStatus; // 0 = none, 1 = pitting, 2 = in pit area + public byte m_numPitStops; // Number of pit stops taken in this race + public byte m_sector; // 0 = sector1, 1 = sector2, 2 = sector3 + public byte m_currentLapInvalid; // Current lap invalid - 0 = valid, 1 = invalid + public byte m_penalties; // Accumulated time penalties in seconds to be added + public byte m_totalWarnings; // Accumulated number of warnings issued + public byte m_cornerCuttingWarnings; // Accumulated number of corner cutting warnings issued + public byte m_numUnservedDriveThroughPens; // Num drive through pens left to serve + public byte m_numUnservedStopGoPens; // Num stop go pens left to serve + public byte m_gridPosition; // Grid position the vehicle started the race in + public byte m_driverStatus; // Status of driver - 0 = in garage, 1 = flying lap, 2 = in lap, 3 = out lap, 4 = on track + public byte m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active, 3 = finished, 4 = didnotfinish, 5 = disqualified, 6 = not classified, 7 = retired + public byte m_pitLaneTimerActive; // Pit lane timing, 0 = inactive, 1 = active + public ushort m_pitLaneTimeInLaneInMS; // If active, the current time spent in the pit lane in ms + public ushort m_pitStopTimerInMS; // Time of the actual pit stop in ms + public byte m_pitStopShouldServePen; // Whether the car should serve a penalty at this stop + public float m_speedTrapFastestSpeed; // Fastest speed through speed trap for this car in kmph + public byte m_speedTrapFastestLap; // Lap no the fastest speed was achieved, 255 = not set + + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/LapHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/LapHistoryData.cs new file mode 100644 index 0000000..e3a833f --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/LapHistoryData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LapHistoryData + { + public uint m_lapTimeInMS; // Lap time in milliseconds + public ushort m_sector1TimeMSPart; // Sector 1 milliseconds part + public byte m_sector1TimeMinutesPart; // Sector 1 whole minute part + public ushort m_sector2TimeMSPart; // Sector 2 time milliseconds part + public byte m_sector2TimeMinutesPart; // Sector 2 whole minute part + public ushort m_sector3TimeMSPart; // Sector 3 time milliseconds part + public byte m_sector3TimeMinutesPart; // Sector 3 whole minute part + public byte m_lapValidBitFlags; // 0x01 bit set-lap valid, 0x02 bit set-sector 1 valid + // 0x04 bit set-sector 2 valid, 0x08 bit set-sector 3 valid + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/LobbyInfoData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/LobbyInfoData.cs new file mode 100644 index 0000000..c11e455 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/LobbyInfoData.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LobbyInfoData + { + public byte m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + public byte m_teamId; // Team id - see appendix (255 if no team currently selected) + public byte m_nationality; // Nationality of the driver + public byte m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string m_name; // Name of participant in UTF-8 format – null terminated + // Will be truncated with ... (U+2026) if too long + public byte m_carNumber; // Car number of the player + public byte m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + public byte m_showOnlineNames; // The player's show online names setting, 0 = off, 1 = on + public ushort m_techLevel; // F1 World tech level + public byte m_readyStatus; // 0 = not ready, 1 = ready, 2 = spectating + + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/MarshalZone.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/MarshalZone.cs new file mode 100644 index 0000000..03d4d43 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/MarshalZone.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MarshalZone + { + public float m_zoneStart; // Fraction (0..1) of way through the lap the marshal zone starts + public byte m_zoneFlag; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow, 4 = red + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/MotionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/MotionData.cs new file mode 100644 index 0000000..ad43c01 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/MotionData.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + /// + /// Motion data packet for F1 2025 telemetry. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MotionData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarMotionData[] m_carMotionData; + } +} \ No newline at end of file diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketCarDamageData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketCarDamageData.cs new file mode 100644 index 0000000..fed4bd4 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketCarDamageData.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarDamageData + { + public PacketHeader m_header; // Header + + // Packet specific data + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarDamageData[] m_carDamageData; // data for all cars on track + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketCarStatusData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketCarStatusData.cs new file mode 100644 index 0000000..c760e4e --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketCarStatusData.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarStatusData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarStatusData[] m_carStatusData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketFinalClassificationData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketFinalClassificationData.cs new file mode 100644 index 0000000..e03770d --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketFinalClassificationData.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketFinalClassificationData + { + public PacketHeader m_header; + + public byte m_numCars; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public FinalClassificationData[] m_classificationData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketHeader.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketHeader.cs new file mode 100644 index 0000000..47ccdb5 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketHeader.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketHeader + { + public ushort m_packetFormat; // 2025 + public byte m_gameYear; // Game year - last two digits e.g. 25 + public byte m_gameMajorVersion; // Game major version - "X.00" + public byte m_gameMinorVersion; // Game minor version - "1.XX" + public byte m_packetVersion; // Version of this packet type + public byte m_packetId; // Identifier for the packet type + public ulong m_sessionUID; // Unique identifier for the session + public float m_sessionTime; // Session timestamp + public uint m_frameIdentifier; // Identifier for the frame the data was retrieved on + public uint m_overallFrameIdentifier; // Overall identifier for the frame the data was retrieved on, doesn't go back after flashbacks + public byte m_playerCarIndex; // Index of player's car in the array + public byte m_secondaryPlayerCarIndex; // Index of secondary player's car in the array (splitscreen) - 255 if no second player + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLapData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLapData.cs new file mode 100644 index 0000000..78d7626 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLapData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLapData + { + public PacketHeader m_header; // Header + + // Packet specific data + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public LapData[] m_lapData; // Lap data for all cars on track + + public byte m_timeTrialPBCarIdx; // Index of Personal Best car in time trial (255 if invalid) + public byte m_timeTrialRivalCarIdx; // Index of Rival car in time trial (255 if invalid) + + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLapPositionsData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLapPositionsData.cs new file mode 100644 index 0000000..535cd50 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLapPositionsData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLapPositionsData + { + public PacketHeader m_header; // Header + + // Packet specific data + public byte m_numLaps; // Number of laps in the data + public byte m_lapStart; // Index of the lap where the data starts, 0 indexed + + // Array holding the position of the car in a given lap, 0 if no record + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50 * 22)] + public byte[] m_positionForVehicleIdx; // [50 laps][22 cars] + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLobbyInfoData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLobbyInfoData.cs new file mode 100644 index 0000000..c69ebb8 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLobbyInfoData.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLobbyInfoData + { + public PacketHeader m_header; + + public byte m_numPlayers; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public LobbyInfoData[] m_lobbyPlayers; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketMotionExData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketMotionExData.cs new file mode 100644 index 0000000..784a30f --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketMotionExData.cs @@ -0,0 +1,63 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketMotionExData + { + public PacketHeader m_header; // Header + + // Extra player car ONLY data + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionPosition; // Note: All wheel arrays have the following order: + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionVelocity; // RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionAcceleration; // RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSpeed; // Speed of each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSlipRatio; // Slip ratio for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSlipAngle; // Slip angles for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelLatForce; // Lateral forces for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelLongForce; // Longitudinal forces for each wheel + + public float m_heightOfCOGAboveGround; // Height of centre of gravity above ground + public float m_localVelocityX; // Velocity in local space X - metres/s + public float m_localVelocityY; // Velocity in local space Y + public float m_localVelocityZ; // Velocity in local space Z + public float m_angularVelocityX; // Angular velocity x-component - radians/s + public float m_angularVelocityY; // Angular velocity y-component + public float m_angularVelocityZ; // Angular velocity z-component + public float m_angularAccelerationX; // Angular acceleration x-component - radians/s/s + public float m_angularAccelerationY; // Angular acceleration y-component + public float m_angularAccelerationZ; // Angular acceleration z-component + public float m_frontWheelsAngle; // Current front wheels angle in radians + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelVertForce; // Vertical forces for each wheel + + public float m_frontAeroHeight; // Front plank edge height above road surface + public float m_rearAeroHeight; // Rear plank edge height above road surface + public float m_frontRollAngle; // Roll angle of the front suspension + public float m_rearRollAngle; // Roll angle of the rear suspension + public float m_chassisYaw; // Yaw angle of the chassis relative to the direction of motion - radians + public float m_chassisPitch; // Pitch angle of the chassis relative to the direction of motion - radians + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelCamber; // Camber of each wheel in radians + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelCamberGain; // Camber gain for each wheel in radians, difference between active camber and dynamic camber + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketSessionHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketSessionHistoryData.cs new file mode 100644 index 0000000..cef2d1c --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketSessionHistoryData.cs @@ -0,0 +1,26 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketSessionHistoryData + { + public PacketHeader m_header; // Header + + // Packet specific data + public byte m_carIdx; // Index of the car this lap data relates to + public byte m_numLaps; // Num laps in the data (including current partial lap) + public byte m_numTyreStints; // Number of tyre stints in the data + + public byte m_bestLapTimeLapNum; // Lap the best lap time was achieved on + public byte m_bestSector1LapNum; // Lap the best Sector 1 time was achieved on + public byte m_bestSector2LapNum; // Lap the best Sector 2 time was achieved on + public byte m_bestSector3LapNum; // Lap the best Sector 3 time was achieved on + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)] + public LapHistoryData[] m_lapHistoryData; // 100 laps of data max + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public TyreStintHistoryData[] m_tyreStintsHistoryData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketTimeTrialData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketTimeTrialData.cs new file mode 100644 index 0000000..09cb080 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketTimeTrialData.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketTimeTrialData + { + public PacketHeader m_header; // Header + + public TimeTrialDataSet m_playerSessionBestDataSet; // Player session best data set + public TimeTrialDataSet m_personalBestDataSet; // Personal best data set + public TimeTrialDataSet m_rivalDataSet; // Rival data set + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketTyreSetsData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketTyreSetsData.cs new file mode 100644 index 0000000..293f996 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketTyreSetsData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketTyreSetsData + { + public PacketHeader m_header; // Header + + public byte m_carIdx; // Index of the car this data relates to + + // Packet specific data + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public TyreSetData[] m_tyreSetData; // 13 (dry) + 7 (wet) + + public byte m_fittedIdx; // Index into array of fitted tyre + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/Participant.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/Participant.cs new file mode 100644 index 0000000..441d97d --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/Participant.cs @@ -0,0 +1,30 @@ +using System; +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct Participant + { + public byte m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + public byte m_driverId; // Driver id - see appendix, 255 if network human + public byte m_networkId; // Network id - unique identifier for network players + public byte m_teamId; // Team id - see appendix + public byte m_myTeam; // My team flag - 1 = My Team, 0 = otherwise + public byte m_raceNumber; // Race number of the car + public byte m_nationality; // Nationality of the driver + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string m_name; // Name of participant in UTF-8 format – null terminated + // Will be truncated with ... (U+2026) if too long + public byte m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + public byte m_showOnlineNames; // The player's show online names setting, 0 = off, 1 = on + public ushort m_techLevel; // F1 World tech level + public byte m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + public byte m_numColours; // Number of colours valid for this car + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public LiveryColour[] m_liveryColours; // Colours for the car + + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/ParticipantData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/ParticipantData.cs new file mode 100644 index 0000000..7bd4c17 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/ParticipantData.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ParticipantData + { + public PacketHeader m_header; + + public byte m_numActiveCars; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ParticipantData[] m_participants; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/SessionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/SessionData.cs new file mode 100644 index 0000000..3ad04de --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/SessionData.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SessionData + { + public PacketHeader m_header; + public byte m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast, 3 = light rain, 4 = heavy rain, 5 = storm + public sbyte m_trackTemperature; // Track temp. in degrees celsius + public sbyte m_airTemperature; // Air temp. in degrees celsius + public byte m_totalLaps; // Total number of laps in this race + public ushort m_trackLength; // Track length in metres + public byte m_sessionType; // 0 = unknown, see appendix + public sbyte m_trackId; // -1 for unknown, see appendix + public byte m_formula; // Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2, 3 = F1 Generic, 4 = Beta, 6 = Esports, 8 = F1 World, 9 = F1 Elimination + public ushort m_sessionTimeLeft; // Time left in session in seconds + public ushort m_sessionDuration; // Session duration in seconds + public byte m_pitSpeedLimit; // Pit speed limit in kilometres per hour + public byte m_gamePaused; // Whether the game is paused - network game only + public byte m_isSpectating; // Whether the player is spectating + public byte m_spectatorCarIndex; // Index of the car being spectated + public byte m_sliProNativeSupport; // SLI Pro support, 0 = inactive, 1 = active + public byte m_numMarshalZones; // Number of marshal zones to follow + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 21)] + public MarshalZone[] m_marshalZones; // List of marshal zones - max 21 + + public byte m_safetyCarStatus; // 0 = no safety car, 1 = full, 2 = virtual, 3 = formation lap + public byte m_networkGame; // 0 = offline, 1 = online + public byte m_numWeatherForecastSamples; // Number of weather samples to follow + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] + public WeatherForecastSample[] m_weatherForecastSamples; // Array of weather forecast samples + + public byte m_forecastAccuracy; // 0 = Perfect, 1 = Approximate + public byte m_aiDifficulty; // AI difficulty - 0-110 + public uint m_seasonLinkIdentifier; // Identifier for season - persists across saves + public uint m_weekendLinkIdentifier; // Identifier for weekend - persists across saves + public uint m_sessionLinkIdentifier; // Identifier for session - persists across saves + public byte m_pitStopWindowIdealLap; // Ideal lap to pit on for current strategy (player) + public byte m_pitStopWindowLatestLap; // Latest lap to pit on for current strategy (player) + public byte m_pitStopRejoinPosition; // Predicted position to rejoin at (player) + public byte m_steeringAssist; // 0 = off, 1 = on + public byte m_brakingAssist; // 0 = off, 1 = low, 2 = medium, 3 = high + public byte m_gearboxAssist; // 1 = manual, 2 = manual & suggested gear, 3 = auto + public byte m_pitAssist; // 0 = off, 1 = on + public byte m_pitReleaseAssist; // 0 = off, 1 = on + public byte m_ERSAssist; // 0 = off, 1 = on + public byte m_DRSAssist; // 0 = off, 1 = on + public byte m_dynamicRacingLine; // 0 = off, 1 = corners only, 2 = full + public byte m_dynamicRacingLineType; // 0 = 2D, 1 = 3D + public byte m_gameMode; // Game mode id - see appendix + public byte m_ruleSet; // Ruleset - see appendix + public uint m_timeOfDay; // Local time of day - minutes since midnight + public byte m_sessionLength; // 0 = None, 2 = Very Short, 3 = Short, 4 = Medium, 5 = Medium Long, 6 = Long, 7 = Full + public byte m_speedUnitsLeadPlayer; // 0 = MPH, 1 = KPH + public byte m_temperatureUnitsLeadPlayer; // 0 = Celsius, 1 = Fahrenheit + public byte m_speedUnitsSecondaryPlayer; // 0 = MPH, 1 = KPH + public byte m_temperatureUnitsSecondaryPlayer; // 0 = Celsius, 1 = Fahrenheit + public byte m_numSafetyCarPeriods; // Number of safety cars called during session + public byte m_numVirtualSafetyCarPeriods; // Number of virtual safety cars called during session + public byte m_numRedFlagPeriods; // Number of red flags called during session + public byte m_equalCarPerformance; // 0 = Off, 1 = On + public byte m_recoveryMode; // 0 = None, 1 = Flashbacks, 2 = Auto-recovery + public byte m_flashbackLimit; // 0 = Low, 1 = Medium, 2 = High, 3 = Unlimited + public byte m_surfaceType; // 0 = Simplified, 1 = Realistic + public byte m_lowFuelMode; // 0 = Easy, 1 = Hard + public byte m_raceStarts; // 0 = Manual, 1 = Assisted + public byte m_tyreTemperature; // 0 = Surface only, 1 = Surface & Carcass + public byte m_pitLaneTyreSim; // 0 = On, 1 = Off + public byte m_carDamage; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Simulation + public byte m_carDamageRate; // 0 = Reduced, 1 = Standard, 2 = Simulation + public byte m_collisions; // 0 = Off, 1 = Player-to-Player Off, 2 = On + public byte m_collisionsOffForFirstLapOnly; // 0 = Disabled, 1 = Enabled + public byte m_mpUnsafePitRelease; // 0 = On, 1 = Off (Multiplayer) + public byte m_mpOffForGriefing; // 0 = Disabled, 1 = Enabled (Multiplayer) + public byte m_cornerCuttingStringency; // 0 = Regular, 1 = Strict + public byte m_parcFermeRules; // 0 = Off, 1 = On + public byte m_pitStopExperience; // 0 = Automatic, 1 = Broadcast, 2 = Immersive + public byte m_safetyCar; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Increased + public byte m_safetyCarExperience; // 0 = Broadcast, 1 = Immersive + public byte m_formationLap; // 0 = Off, 1 = On + public byte m_formationLapExperience; // 0 = Broadcast, 1 = Immersive + public byte m_redFlags; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Increased + public byte m_affectsLicenceLevelSolo; // 0 = Off, 1 = On + public byte m_affectsLicenceLevelMP; // 0 = Off, 1 = On + public byte m_numSessionsInWeekend; // Number of session in following array + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)] + public byte[] m_weekendStructure; // List of session types to show weekend structure - see appendix for types + + public float m_sector2LapDistanceStart; // Distance in m around track where sector 2 starts + public float m_sector3LapDistanceStart; // Distance in m around track where sector 3 starts + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/TimeTrialDataSet.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/TimeTrialDataSet.cs new file mode 100644 index 0000000..6cf9ea4 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/TimeTrialDataSet.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TimeTrialDataSet + { + public byte m_carIdx; // Index of the car this data relates to + public byte m_teamId; // Team id - see appendix + public uint m_lapTimeInMS; // Lap time in milliseconds + public uint m_sector1TimeInMS; // Sector 1 time in milliseconds + public uint m_sector2TimeInMS; // Sector 2 time in milliseconds + public uint m_sector3TimeInMS; // Sector 3 time in milliseconds + public byte m_tractionControl; // 0 = assist off, 1 = assist on + public byte m_gearboxAssist; // 0 = assist off, 1 = assist on + public byte m_antiLockBrakes; // 0 = assist off, 1 = assist on + public byte m_equalCarPerformance; // 0 = Realistic, 1 = Equal + public byte m_customSetup; // 0 = No, 1 = Yes + public byte m_valid; // 0 = invalid, 1 = valid + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/TyreSetData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/TyreSetData.cs new file mode 100644 index 0000000..09e587c --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/TyreSetData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TyreSetData + { + public byte m_actualTyreCompound; // Actual tyre compound used + public byte m_visualTyreCompound; // Visual tyre compound used + public byte m_wear; // Tyre wear (percentage) + public byte m_available; // Whether this set is currently available + public byte m_recommendedSession; // Recommended session for tyre set, see appendix + public byte m_lifeSpan; // Laps left in this tyre set + public byte m_usableLife; // Max number of laps recommended for this compound + public short m_lapDeltaTime; // Lap delta time in milliseconds compared to fitted set + public byte m_fitted; // Whether the set is fitted or not + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/TyreStintHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/TyreStintHistoryData.cs new file mode 100644 index 0000000..8c72d9f --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/TyreStintHistoryData.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TyreStintHistoryData + { + public byte m_endLap; // Lap the tyre usage ends on (255 if current tyre) + public byte m_tyreActualCompound; // Actual tyres used by this driver + public byte m_tyreVisualCompound; // Visual tyres used by this driver + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/WeatherForecastSample.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/WeatherForecastSample.cs new file mode 100644 index 0000000..0a90e4f --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/WeatherForecastSample.cs @@ -0,0 +1,32 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct WeatherForecastSample + { + // 0 = unknown, see appendix + public byte m_sessionType; // uint8 + + // Time in minutes the forecast is for + public byte m_timeOffset; // uint8 + + // Weather - 0 = clear, 1 = light cloud, 2 = overcast, 3 = light rain, 4 = heavy rain, 5 = storm + public byte m_weather; // uint8 + + // Track temp. in degrees celsius + public sbyte m_trackTemperature; // int8 + + // Track temp. change - 0 = up, 1 = down, 2 = no change + public sbyte m_trackTemperatureChange; // int8 + + // Air temp. in degrees celsius + public sbyte m_airTemperature; // int8 + + // Air temp. change - 0 = up, 1 = down, 2 = no change + public sbyte m_airTemperatureChange; // int8 + + // Rain percentage (0-100) + public byte m_rainPercentage; // uint8 + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs b/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs new file mode 100644 index 0000000..1905657 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + internal class F1RealtimeTelemetrySource : UdpSourceBase + { + public F1RealtimeTelemetrySource(UdpSourceOptions options) : base(options) + { + } + + protected override IEnumerable ProcessData(byte[] data) + { + yield return new F1TelemetryFrame(); + } + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs new file mode 100644 index 0000000..d6398bb --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + public struct F1TelemetryFrame + { + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/LiveryColour.cs b/GamesDat/Telemetry/Sources/Formula1/LiveryColour.cs new file mode 100644 index 0000000..3d5b372 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/LiveryColour.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LiveryColour + { + byte red; + byte green; + byte blue; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/PacketId.cs b/GamesDat/Telemetry/Sources/Formula1/PacketId.cs new file mode 100644 index 0000000..39dff1e --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/PacketId.cs @@ -0,0 +1,23 @@ +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + public enum PacketId : byte + { + Motion = 0, // Contains all motion data for player’s car – only sent while player is in control + Session = 1, // Data about the session – track, time left + LapData = 2, // Data about all the lap times of cars in the session + Event = 3, // Various notable events that happen during a session + Participants = 4, // List of participants in the session, mostly relevant for multiplayer + CarSetups = 5, // Packet detailing car setups for cars in the race + CarTelemetry = 6, // Telemetry data for all cars + CarStatus = 7, // Status data for all cars + FinalClassification = 8, // Final classification confirmation at the end of a race + LobbyInfo = 9, // Information about players in a multiplayer lobby + CarDamage = 10, // Damage status for all cars + SessionHistory = 11, // Lap and tyre data for session + TyreSets = 12, // Extended tyre set data + MotionEx = 13, // Extended motion data for player car + TimeTrial = 14, // Time Trial specific data + LapPositions = 15, // Lap positions on each lap so a chart can be constructed + Max + } +} diff --git a/GamesDat/Telemetry/Sources/UdpSourceBase.cs b/GamesDat/Telemetry/Sources/UdpSourceBase.cs new file mode 100644 index 0000000..b032040 --- /dev/null +++ b/GamesDat/Telemetry/Sources/UdpSourceBase.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; + +namespace GamesDat.Core.Telemetry.Sources +{ + public abstract class UdpSourceBase : TelemetrySourceBase + { + + protected UdpClient _listener; + protected IPEndPoint _endpoint; + protected bool _isListening; + + protected int Port { get; set; } + protected int BufferSize { get; set; } + + public UdpSourceBase(UdpSourceOptions options) : base() + { + Port = options.Port; + BufferSize = options.BufferSize; + + _endpoint = new IPEndPoint(IPAddress.Any, Port); + _listener = new UdpClient(_endpoint); + } + + public override async IAsyncEnumerable ReadContinuousAsync([EnumeratorCancellation] CancellationToken ct = default) + { + _isListening = true; + try + { + while (!ct.IsCancellationRequested) + { + var result = await _listener.ReceiveAsync(ct); + var data = result.Buffer; + // Process the received data and yield telemetry objects + foreach (var item in ProcessData(data)) + { + yield return item; + } + } + } + finally + { + _isListening = false; + _listener.Dispose(); + } + } + + abstract protected IEnumerable ProcessData(byte[] data); + } +} diff --git a/GamesDat/Telemetry/Sources/UdpSourceOptions.cs b/GamesDat/Telemetry/Sources/UdpSourceOptions.cs new file mode 100644 index 0000000..cdac687 --- /dev/null +++ b/GamesDat/Telemetry/Sources/UdpSourceOptions.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources +{ + public class UdpSourceOptions + { + /// + /// Local port to listen for UDP packets + /// + public int Port { get; set; } = 0; + + /// + /// The maximum size of the buffer used to receive UDP packets. Default is 8192 bytes. + /// + public int BufferSize { get; set; } = 8192; + } +} From 05c82b9123771fccd54519b27e79aadbe0af2ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Mon, 9 Feb 2026 11:59:02 +0100 Subject: [PATCH 02/25] Added F1 2024 types --- GamesDat/.claude/settings.local.json | 4 +- .../Sources/Formula1/F12024/CarDamageData.cs | 39 +++ .../Sources/Formula1/F12024/CarMotionData.cs | 30 ++ .../Sources/Formula1/F12024/CarSetupData.cs | 35 ++ .../Sources/Formula1/F12024/CarStatusData.cs | 42 +++ .../Formula1/F12024/CarTelemetryData.cs | 39 +++ .../Formula1/F12024/EventDataDetails.cs | 123 +++++++ .../F12024/FinalClassificationData.cs | 32 ++ .../Sources/Formula1/F12024/LapData.cs | 45 +++ .../Sources/Formula1/F12024/LapHistoryData.cs | 18 + .../Sources/Formula1/F12024/LobbyInfoData.cs | 26 ++ .../Sources/Formula1/F12024/MarshalZone.cs | 11 + .../Formula1/F12024/PacketCarDamageData.cs | 13 + .../Formula1/F12024/PacketCarSetupData.cs | 15 + .../Formula1/F12024/PacketCarStatusData.cs | 13 + .../Formula1/F12024/PacketCarTelemetryData.cs | 20 ++ .../Formula1/F12024/PacketEventData.cs | 41 +++ .../F12024/PacketFinalClassificationData.cs | 15 + .../Sources/Formula1/F12024/PacketHeader.cs | 21 ++ .../Sources/Formula1/F12024/PacketLapData.cs | 16 + .../Formula1/F12024/PacketLobbyInfoData.cs | 15 + .../Formula1/F12024/PacketMotionData.cs | 16 + .../Formula1/F12024/PacketMotionExData.cs | 58 ++++ .../Formula1/F12024/PacketParticipantsData.cs | 15 + .../Formula1/F12024/PacketSessionData.cs | 97 ++++++ .../F12024/PacketSessionHistoryData.cs | 25 ++ .../Formula1/F12024/PacketTimeTrialData.cs | 14 + .../Formula1/F12024/PacketTyreSetsData.cs | 17 + .../Formula1/F12024/ParticipantData.cs | 28 ++ .../Formula1/F12024/TimeTrialDataSet.cs | 21 ++ .../Sources/Formula1/F12024/TyreSetData.cs | 21 ++ .../Formula1/F12024/TyreStintHistoryData.cs | 12 + .../Formula1/F12024/WeatherForecastSample.cs | 17 + .../Sources/Formula1/F1PacketTypeMapper.cs | 52 +++ .../Formula1/F1RealtimeTelemetrySource.cs | 43 ++- .../Sources/Formula1/F1TelemetryFrame.cs | 11 + .../Formula1/F1_IMPLEMENTATION_WORKFLOW.md | 322 ++++++++++++++++++ 37 files changed, 1377 insertions(+), 5 deletions(-) create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/CarDamageData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/CarMotionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/CarSetupData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/CarStatusData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/CarTelemetryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/EventDataDetails.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/FinalClassificationData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/LapData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/LapHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/LobbyInfoData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/MarshalZone.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarDamageData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarSetupData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarStatusData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarTelemetryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketEventData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketFinalClassificationData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketHeader.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketLapData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketLobbyInfoData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketMotionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketMotionExData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketParticipantsData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketSessionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketSessionHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketTimeTrialData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketTyreSetsData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/ParticipantData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/TimeTrialDataSet.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/TyreSetData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/TyreStintHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/WeatherForecastSample.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md diff --git a/GamesDat/.claude/settings.local.json b/GamesDat/.claude/settings.local.json index e1c7f21..562a20f 100644 --- a/GamesDat/.claude/settings.local.json +++ b/GamesDat/.claude/settings.local.json @@ -9,7 +9,9 @@ "Bash(tree:*)", "Bash(dir /s /b \"..\\\\GamesDate.Demo.Wpf\\\\ViewModels\\\\*.cs\")", "Bash(dir /s /b \"..\\\\GamesDate.Demo.Wpf\\\\Views\\\\*.xaml\")", - "WebFetch(domain:wiki.trackmania.io)" + "WebFetch(domain:wiki.trackmania.io)", + "Bash(xargs:*)", + "Bash(grep:*)" ] } } diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/CarDamageData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/CarDamageData.cs new file mode 100644 index 0000000..0445d53 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/CarDamageData.cs @@ -0,0 +1,39 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Car damage data for one car in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarDamageData + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresWear; // Tyre wear (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresDamage; // Tyre damage (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_brakesDamage; // Brakes damage (percentage) + + public byte m_frontLeftWingDamage; // Front left wing damage (percentage) + public byte m_frontRightWingDamage; // Front right wing damage (percentage) + public byte m_rearWingDamage; // Rear wing damage (percentage) + public byte m_floorDamage; // Floor damage (percentage) + public byte m_diffuserDamage; // Diffuser damage (percentage) + public byte m_sidepodDamage; // Sidepod damage (percentage) + public byte m_drsFault; // Indicator for DRS fault, 0 = OK, 1 = fault + public byte m_ersFault; // Indicator for ERS fault, 0 = OK, 1 = fault + public byte m_gearBoxDamage; // Gear box damage (percentage) + public byte m_engineDamage; // Engine damage (percentage) + public byte m_engineMGUHWear; // Engine wear MGU-H (percentage) + public byte m_engineESWear; // Engine wear ES (percentage) + public byte m_engineCEWear; // Engine wear CE (percentage) + public byte m_engineICEWear; // Engine wear ICE (percentage) + public byte m_engineMGUKWear; // Engine wear MGU-K (percentage) + public byte m_engineTCWear; // Engine wear TC (percentage) + public byte m_engineBlown; // Engine blown, 0 = OK, 1 = fault + public byte m_engineSeized; // Engine seized, 0 = OK, 1 = fault + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/CarMotionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/CarMotionData.cs new file mode 100644 index 0000000..d548990 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/CarMotionData.cs @@ -0,0 +1,30 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Motion data for one car in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarMotionData + { + public float m_worldPositionX; // World space X position - metres + public float m_worldPositionY; // World space Y position + public float m_worldPositionZ; // World space Z position + public float m_worldVelocityX; // Velocity in world space X - metres/s + public float m_worldVelocityY; // Velocity in world space Y + public float m_worldVelocityZ; // Velocity in world space Z + public short m_worldForwardDirX; // World space forward X direction (normalised) + public short m_worldForwardDirY; // World space forward Y direction (normalised) + public short m_worldForwardDirZ; // World space forward Z direction (normalised) + public short m_worldRightDirX; // World space right X direction (normalised) + public short m_worldRightDirY; // World space right Y direction (normalised) + public short m_worldRightDirZ; // World space right Z direction (normalised) + public float m_gForceLateral; // Lateral G-Force component + public float m_gForceLongitudinal; // Longitudinal G-Force component + public float m_gForceVertical; // Vertical G-Force component + public float m_yaw; // Yaw angle in radians + public float m_pitch; // Pitch angle in radians + public float m_roll; // Roll angle in radians + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/CarSetupData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/CarSetupData.cs new file mode 100644 index 0000000..5d2ff56 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/CarSetupData.cs @@ -0,0 +1,35 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Data about one car setup in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarSetupData + { + public byte m_frontWing; // Front wing aero + public byte m_rearWing; // Rear wing aero + public byte m_onThrottle; // Differential adjustment on throttle (percentage) + public byte m_offThrottle; // Differential adjustment off throttle (percentage) + public float m_frontCamber; // Front camber angle (suspension geometry) + public float m_rearCamber; // Rear camber angle (suspension geometry) + public float m_frontToe; // Front toe angle (suspension geometry) + public float m_rearToe; // Rear toe angle (suspension geometry) + public byte m_frontSuspension; // Front suspension + public byte m_rearSuspension; // Rear suspension + public byte m_frontAntiRollBar; // Front anti-roll bar + public byte m_rearAntiRollBar; // Rear anti-roll bar + public byte m_frontSuspensionHeight; // Front ride height + public byte m_rearSuspensionHeight; // Rear ride height + public byte m_brakePressure; // Brake pressure (percentage) + public byte m_brakeBias; // Brake bias (percentage) + public byte m_engineBraking; // Engine braking (percentage) + public float m_rearLeftTyrePressure; // Rear left tyre pressure (PSI) + public float m_rearRightTyrePressure; // Rear right tyre pressure (PSI) + public float m_frontLeftTyrePressure; // Front left tyre pressure (PSI) + public float m_frontRightTyrePressure; // Front right tyre pressure (PSI) + public byte m_ballast; // Ballast + public float m_fuelLoad; // Fuel load + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/CarStatusData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/CarStatusData.cs new file mode 100644 index 0000000..1fbd8cb --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/CarStatusData.cs @@ -0,0 +1,42 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Car status data for one car in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarStatusData + { + public byte m_tractionControl; // Traction control - 0 = off, 1 = medium, 2 = full + public byte m_antiLockBrakes; // 0 (off) - 1 (on) + public byte m_fuelMix; // Fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max + public byte m_frontBrakeBias; // Front brake bias (percentage) + public byte m_pitLimiterStatus; // Pit limiter status - 0 = off, 1 = on + public float m_fuelInTank; // Current fuel mass + public float m_fuelCapacity; // Fuel capacity + public float m_fuelRemainingLaps; // Fuel remaining in terms of laps (value on MFD) + public ushort m_maxRPM; // Cars max RPM, point of rev limiter + public ushort m_idleRPM; // Cars idle RPM + public byte m_maxGears; // Maximum number of gears + public byte m_drsAllowed; // 0 = not allowed, 1 = allowed + public ushort m_drsActivationDistance; // 0 = DRS not available, non-zero - DRS will be available in [X] metres + public byte m_actualTyreCompound; // F1 Modern - 16 = C5, 17 = C4, 18 = C3, 19 = C2, 20 = C1, 21 = C0, 7 = inter, 8 = wet + // F1 Classic - 9 = dry, 10 = wet + // F2 – 11 = super soft, 12 = soft, 13 = medium, 14 = hard, 15 = wet + public byte m_visualTyreCompound; // F1 visual (can be different from actual compound) + // 16 = soft, 17 = medium, 18 = hard, 7 = inter, 8 = wet + // F1 Classic – same as above + // F2 '20, 15 = wet, 19 – super soft, 20 = soft, 21 = medium, 22 = hard + public byte m_tyresAgeLaps; // Age in laps of the current set of tyres + public sbyte m_vehicleFIAFlags; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow + public float m_enginePowerICE; // Engine power output of ICE (W) + public float m_enginePowerMGUK; // Engine power output of MGU-K (W) + public float m_ersStoreEnergy; // ERS energy store in Joules + public byte m_ersDeployMode; // ERS deployment mode, 0 = none, 1 = medium, 2 = hotlap, 3 = overtake + public float m_ersHarvestedThisLapMGUK; // ERS energy harvested this lap by MGU-K + public float m_ersHarvestedThisLapMGUH; // ERS energy harvested this lap by MGU-H + public float m_ersDeployedThisLap; // ERS energy deployed this lap + public byte m_networkPaused; // Whether the car is paused in a network game + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/CarTelemetryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/CarTelemetryData.cs new file mode 100644 index 0000000..060ba3c --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/CarTelemetryData.cs @@ -0,0 +1,39 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Telemetry data for one car in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarTelemetryData + { + public ushort m_speed; // Speed of car in kilometres per hour + public float m_throttle; // Amount of throttle applied (0.0 to 1.0) + public float m_steer; // Steering (-1.0 (full lock left) to 1.0 (full lock right)) + public float m_brake; // Amount of brake applied (0.0 to 1.0) + public byte m_clutch; // Amount of clutch applied (0 to 100) + public sbyte m_gear; // Gear selected (1-8, N=0, R=-1) + public ushort m_engineRPM; // Engine RPM + public byte m_drs; // 0 = off, 1 = on + public byte m_revLightsPercent; // Rev lights indicator (percentage) + public ushort m_revLightsBitValue; // Rev lights (bit 0 = leftmost LED, bit 14 = rightmost LED) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public ushort[] m_brakesTemperature; // Brakes temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresSurfaceTemperature; // Tyres surface temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresInnerTemperature; // Tyres inner temperature (celsius) + + public ushort m_engineTemperature; // Engine temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresPressure; // Tyre pressure (PSI) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_surfaceType; // Driving surface, see appendices + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/EventDataDetails.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/EventDataDetails.cs new file mode 100644 index 0000000..9385f4c --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/EventDataDetails.cs @@ -0,0 +1,123 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Explicit)] + public struct EventDataDetails + { + [FieldOffset(0)] public FastestLapData FastestLap; + [FieldOffset(0)] public RetirementData Retirement; + [FieldOffset(0)] public TeamMateInPitsData TeamMateInPits; + [FieldOffset(0)] public RaceWinnerData RaceWinner; + [FieldOffset(0)] public PenaltyData Penalty; + [FieldOffset(0)] public SpeedTrapData SpeedTrap; + [FieldOffset(0)] public StartLightsData StartLights; + [FieldOffset(0)] public DriveThroughPenaltyServedData DriveThroughPenaltyServed; + [FieldOffset(0)] public StopGoPenaltyServedData StopGoPenaltyServed; + [FieldOffset(0)] public FlashbackData Flashback; + [FieldOffset(0)] public ButtonsData Buttons; + [FieldOffset(0)] public OvertakeData Overtake; + [FieldOffset(0)] public SafetyCarData SafetyCar; + [FieldOffset(0)] public CollisionData Collision; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FastestLapData + { + public byte vehicleIdx; // Vehicle index of car achieving fastest lap + public float lapTime; // Lap time is in seconds + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RetirementData + { + public byte vehicleIdx; // Vehicle index of car retiring + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TeamMateInPitsData + { + public byte vehicleIdx; // Vehicle index of team mate + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RaceWinnerData + { + public byte vehicleIdx; // Vehicle index of the race winner + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PenaltyData + { + public byte penaltyType; // Penalty type – see Appendices + public byte infringementType; // Infringement type – see Appendices + public byte vehicleIdx; // Vehicle index of the car the penalty is applied to + public byte otherVehicleIdx; // Vehicle index of the other car involved + public byte time; // Time gained, or time spent doing action in seconds + public byte lapNum; // Lap the penalty occurred on + public byte placesGained; // Number of places gained by this + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SpeedTrapData + { + public byte vehicleIdx; // Vehicle index of the vehicle triggering speed trap + public float speed; // Top speed achieved in kilometres per hour + public byte isOverallFastestInSession; // Overall fastest speed in session = 1, otherwise 0 + public byte isDriverFastestInSession; // Fastest speed for driver in session = 1, otherwise 0 + public byte fastestVehicleIdxInSession; // Vehicle index of the vehicle that is the fastest in this session + public float fastestSpeedInSession; // Speed of the vehicle that is the fastest in this session + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct StartLightsData + { + public byte numLights; // Number of lights showing + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DriveThroughPenaltyServedData + { + public byte vehicleIdx; // Vehicle index of the vehicle serving drive through + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct StopGoPenaltyServedData + { + public byte vehicleIdx; // Vehicle index of the vehicle serving stop go + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FlashbackData + { + public uint flashbackFrameIdentifier; // Frame identifier flashed back to + public float flashbackSessionTime; // Session time flashed back to + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ButtonsData + { + public uint buttonStatus; // Bit flags specifying which buttons are being pressed currently - see appendices + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct OvertakeData + { + public byte overtakingVehicleIdx; // Vehicle index of the vehicle overtaking + public byte beingOvertakenVehicleIdx; // Vehicle index of the vehicle being overtaken + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SafetyCarData + { + public byte safetyCarType; // 0 = No Safety Car, 1 = Full Safety Car, 2 = Virtual Safety Car, 3 = Formation Lap Safety Car + public byte eventType; // 0 = Deployed, 1 = Returning, 2 = Returned, 3 = Resume Race + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CollisionData + { + public byte vehicle1Idx; // Vehicle index of the first vehicle involved in the collision + public byte vehicle2Idx; // Vehicle index of the second vehicle involved in the collision + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/FinalClassificationData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/FinalClassificationData.cs new file mode 100644 index 0000000..1b10fb2 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/FinalClassificationData.cs @@ -0,0 +1,32 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Data about one participant's final results in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FinalClassificationData + { + public byte m_position; // Finishing position + public byte m_numLaps; // Number of laps completed + public byte m_gridPosition; // Grid position of the car + public byte m_points; // Number of points scored + public byte m_numPitStops; // Number of pit stops made + public byte m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active, 3 = finished, 4 = didnotfinish, 5 = disqualified, 6 = not classified, 7 = retired + public uint m_bestLapTimeInMS; // Best lap time of the session in milliseconds + public double m_totalRaceTime; // Total race time in seconds without penalties + public byte m_penaltiesTime; // Total penalties accumulated in seconds + public byte m_numPenalties; // Number of penalties applied to this driver + public byte m_numTyreStints; // Number of tyres stints up to maximum + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsActual; // Actual tyres used by this driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsVisual; // Visual tyres used by this driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsEndLaps; // The lap number stints end on + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/LapData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/LapData.cs new file mode 100644 index 0000000..3b7587b --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/LapData.cs @@ -0,0 +1,45 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Lap data about one car in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LapData + { + public uint m_lastLapTimeInMS; // Last lap time in milliseconds + public uint m_currentLapTimeInMS; // Current time around the lap in milliseconds + public ushort m_sector1TimeMSPart; // Sector 1 time milliseconds part + public byte m_sector1TimeMinutesPart; // Sector 1 whole minute part + public ushort m_sector2TimeMSPart; // Sector 2 time milliseconds part + public byte m_sector2TimeMinutesPart; // Sector 2 whole minute part + public ushort m_deltaToCarInFrontMSPart; // Time delta to car in front milliseconds part + public byte m_deltaToCarInFrontMinutesPart; // Time delta to car in front whole minute part + public ushort m_deltaToRaceLeaderMSPart; // Time delta to race leader milliseconds part + public byte m_deltaToRaceLeaderMinutesPart; // Time delta to race leader whole minute part + public float m_lapDistance; // Distance vehicle is around current lap in metres – could be negative if line hasn't been crossed yet + public float m_totalDistance; // Total distance travelled in session in metres – could be negative if line hasn't been crossed yet + public float m_safetyCarDelta; // Delta in seconds for safety car + public byte m_carPosition; // Car race position + public byte m_currentLapNum; // Current lap number + public byte m_pitStatus; // 0 = none, 1 = pitting, 2 = in pit area + public byte m_numPitStops; // Number of pit stops taken in this race + public byte m_sector; // 0 = sector1, 1 = sector2, 2 = sector3 + public byte m_currentLapInvalid; // Current lap invalid - 0 = valid, 1 = invalid + public byte m_penalties; // Accumulated time penalties in seconds to be added + public byte m_totalWarnings; // Accumulated number of warnings issued + public byte m_cornerCuttingWarnings; // Accumulated number of corner cutting warnings issued + public byte m_numUnservedDriveThroughPens; // Num drive through pens left to serve + public byte m_numUnservedStopGoPens; // Num stop go pens left to serve + public byte m_gridPosition; // Grid position the vehicle started the race in + public byte m_driverStatus; // Status of driver - 0 = in garage, 1 = flying lap, 2 = in lap, 3 = out lap, 4 = on track + public byte m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active, 3 = finished, 4 = didnotfinish, 5 = disqualified, 6 = not classified, 7 = retired + public byte m_pitLaneTimerActive; // Pit lane timing, 0 = inactive, 1 = active + public ushort m_pitLaneTimeInLaneInMS; // If active, the current time spent in the pit lane in ms + public ushort m_pitStopTimerInMS; // Time of the actual pit stop in ms + public byte m_pitStopShouldServePen; // Whether the car should serve a penalty at this stop + public float m_speedTrapFastestSpeed; // Fastest speed through speed trap for this car in kmph + public byte m_speedTrapFastestLap; // Lap no the fastest speed was achieved, 255 = not set + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/LapHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/LapHistoryData.cs new file mode 100644 index 0000000..4609dbe --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/LapHistoryData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LapHistoryData + { + public uint m_lapTimeInMS; // Lap time in milliseconds + public ushort m_sector1TimeMSPart; // Sector 1 milliseconds part + public byte m_sector1TimeMinutesPart; // Sector 1 whole minute part + public ushort m_sector2TimeMSPart; // Sector 2 time milliseconds part + public byte m_sector2TimeMinutesPart; // Sector 2 whole minute part + public ushort m_sector3TimeMSPart; // Sector 3 time milliseconds part + public byte m_sector3TimeMinutesPart; // Sector 3 whole minute part + public byte m_lapValidBitFlags; // 0x01 bit set-lap valid, 0x02 bit set-sector 1 valid + // 0x04 bit set-sector 2 valid, 0x08 bit set-sector 3 valid + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/LobbyInfoData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/LobbyInfoData.cs new file mode 100644 index 0000000..a5b8e2d --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/LobbyInfoData.cs @@ -0,0 +1,26 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Data about one participant in the lobby in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LobbyInfoData + { + public byte m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + public byte m_teamId; // Team id - see appendix (255 if no team currently selected) + public byte m_nationality; // Nationality of the driver + public byte m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] + public byte[] m_name; // Name of participant in UTF-8 format – null terminated + // Will be truncated with ... (U+2026) if too long + + public byte m_carNumber; // Car number of the player + public byte m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + public byte m_showOnlineNames; // The player's show online names setting, 0 = off, 1 = on + public ushort m_techLevel; // F1 World tech level + public byte m_readyStatus; // 0 = not ready, 1 = ready, 2 = spectating + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/MarshalZone.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/MarshalZone.cs new file mode 100644 index 0000000..8590b16 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/MarshalZone.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MarshalZone + { + public float m_zoneStart; // Fraction (0..1) of way through the lap the marshal zone starts + public sbyte m_zoneFlag; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarDamageData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarDamageData.cs new file mode 100644 index 0000000..b60a793 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarDamageData.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarDamageData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarDamageData[] m_carDamageData; // data for all cars on track + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarSetupData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarSetupData.cs new file mode 100644 index 0000000..4063234 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarSetupData.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarSetupData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarSetupData[] m_carSetupData; + + public float m_nextFrontWingValue; // Value of front wing after next pit stop - player only + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarStatusData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarStatusData.cs new file mode 100644 index 0000000..75159ff --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarStatusData.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarStatusData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarStatusData[] m_carStatusData; // data for all cars on track + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarTelemetryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarTelemetryData.cs new file mode 100644 index 0000000..f26a62c --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarTelemetryData.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarTelemetryData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarTelemetryData[] m_carTelemetryData; // data for all cars on track + + public byte m_mfdPanelIndex; // Index of MFD panel open - 255 = MFD closed + // Single player, race – 0 = Car setup, 1 = Pits + // 2 = Damage, 3 = Engine, 4 = Temperatures + // May vary depending on game mode + public byte m_mfdPanelIndexSecondaryPlayer; // See above + public sbyte m_suggestedGear; // Suggested gear for the player (1-8), 0 if no gear suggested + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketEventData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketEventData.cs new file mode 100644 index 0000000..adb5979 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketEventData.cs @@ -0,0 +1,41 @@ +using System.Runtime.InteropServices; +using System.Text; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketEventData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_eventStringCode; // Event string code + + public EventDataDetails m_eventDetails; // Event details - should be interpreted differently for each type + + // Helper property to get event code as string + public string EventCode => Encoding.ASCII.GetString(m_eventStringCode); + + public T GetEventDetails() where T : struct + { + return EventCode switch + { + EventCodes.FastestLap when typeof(T) == typeof(FastestLapData) => (T)(object)m_eventDetails.FastestLap, + EventCodes.Retirement when typeof(T) == typeof(RetirementData) => (T)(object)m_eventDetails.Retirement, + EventCodes.TeamMateInPits when typeof(T) == typeof(TeamMateInPitsData) => (T)(object)m_eventDetails.TeamMateInPits, + EventCodes.RaceWinner when typeof(T) == typeof(RaceWinnerData) => (T)(object)m_eventDetails.RaceWinner, + EventCodes.Penalty when typeof(T) == typeof(PenaltyData) => (T)(object)m_eventDetails.Penalty, + EventCodes.SpeedTrap when typeof(T) == typeof(SpeedTrapData) => (T)(object)m_eventDetails.SpeedTrap, + EventCodes.StartLights when typeof(T) == typeof(StartLightsData) => (T)(object)m_eventDetails.StartLights, + EventCodes.DriveThroughServed when typeof(T) == typeof(DriveThroughPenaltyServedData) => (T)(object)m_eventDetails.DriveThroughPenaltyServed, + EventCodes.StopGoServed when typeof(T) == typeof(StopGoPenaltyServedData) => (T)(object)m_eventDetails.StopGoPenaltyServed, + EventCodes.Flashback when typeof(T) == typeof(FlashbackData) => (T)(object)m_eventDetails.Flashback, + EventCodes.ButtonStatus when typeof(T) == typeof(ButtonsData) => (T)(object)m_eventDetails.Buttons, + EventCodes.Overtake when typeof(T) == typeof(OvertakeData) => (T)(object)m_eventDetails.Overtake, + EventCodes.SafetyCar when typeof(T) == typeof(SafetyCarData) => (T)(object)m_eventDetails.SafetyCar, + EventCodes.Collision when typeof(T) == typeof(CollisionData) => (T)(object)m_eventDetails.Collision, + _ => throw new InvalidOperationException($"Cannot get {typeof(T).Name} for event {EventCode}") + }; + } + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketFinalClassificationData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketFinalClassificationData.cs new file mode 100644 index 0000000..9868f20 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketFinalClassificationData.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketFinalClassificationData + { + public PacketHeader m_header; + + public byte m_numCars; // Number of cars in the final classification + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public FinalClassificationData[] m_classificationData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketHeader.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketHeader.cs new file mode 100644 index 0000000..86390d4 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketHeader.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketHeader + { + public ushort m_packetFormat; // 2024 + public byte m_gameYear; // Game year - last two digits e.g. 24 + public byte m_gameMajorVersion; // Game major version - "X.00" + public byte m_gameMinorVersion; // Game minor version - "1.XX" + public byte m_packetVersion; // Version of this packet type + public byte m_packetId; // Identifier for the packet type + public ulong m_sessionUID; // Unique identifier for the session + public float m_sessionTime; // Session timestamp + public uint m_frameIdentifier; // Identifier for the frame the data was retrieved on + public uint m_overallFrameIdentifier; // Overall identifier for the frame the data was retrieved on, doesn't go back after flashbacks + public byte m_playerCarIndex; // Index of player's car in the array + public byte m_secondaryPlayerCarIndex; // Index of secondary player's car in the array (splitscreen) - 255 if no second player + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketLapData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketLapData.cs new file mode 100644 index 0000000..d6977fb --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketLapData.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLapData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public LapData[] m_lapData; // Lap data for all cars on track + + public byte m_timeTrialPBCarIdx; // Index of Personal Best car in time trial (255 if invalid) + public byte m_timeTrialRivalCarIdx; // Index of Rival car in time trial (255 if invalid) + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketLobbyInfoData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketLobbyInfoData.cs new file mode 100644 index 0000000..61ec950 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketLobbyInfoData.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLobbyInfoData + { + public PacketHeader m_header; + + public byte m_numPlayers; // Number of players in the lobby data + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public LobbyInfoData[] m_lobbyPlayers; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketMotionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketMotionData.cs new file mode 100644 index 0000000..4292f08 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketMotionData.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Motion data packet for F1 2024 telemetry. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketMotionData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarMotionData[] m_carMotionData; // Data for all cars on track + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketMotionExData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketMotionExData.cs new file mode 100644 index 0000000..b193250 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketMotionExData.cs @@ -0,0 +1,58 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketMotionExData + { + public PacketHeader m_header; + + // Extra player car ONLY data + // Note: All wheel arrays have the following order: RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionPosition; // RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionVelocity; // RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionAcceleration; // RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSpeed; // Speed of each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSlipRatio; // Slip ratio for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSlipAngle; // Slip angles for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelLatForce; // Lateral forces for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelLongForce; // Longitudinal forces for each wheel + + public float m_heightOfCOGAboveGround; // Height of centre of gravity above ground + public float m_localVelocityX; // Velocity in local space X - metres/s + public float m_localVelocityY; // Velocity in local space Y + public float m_localVelocityZ; // Velocity in local space Z + public float m_angularVelocityX; // Angular velocity x-component - radians/s + public float m_angularVelocityY; // Angular velocity y-component + public float m_angularVelocityZ; // Angular velocity z-component + public float m_angularAccelerationX; // Angular acceleration x-component - radians/s/s + public float m_angularAccelerationY; // Angular acceleration y-component + public float m_angularAccelerationZ; // Angular acceleration z-component + public float m_frontWheelsAngle; // Current front wheels angle in radians + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelVertForce; // Vertical forces for each wheel + + public float m_frontAeroHeight; // Front plank edge height above road surface + public float m_rearAeroHeight; // Rear plank edge height above road surface + public float m_frontRollAngle; // Roll angle of the front suspension + public float m_rearRollAngle; // Roll angle of the rear suspension + public float m_chassisYaw; // Yaw angle of the chassis relative to the direction of motion - radians + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketParticipantsData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketParticipantsData.cs new file mode 100644 index 0000000..f99fe39 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketParticipantsData.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketParticipantsData + { + public PacketHeader m_header; + + public byte m_numActiveCars; // Number of active cars in the data - should match number of cars on HUD + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ParticipantData[] m_participants; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketSessionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketSessionData.cs new file mode 100644 index 0000000..2ffdcf0 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketSessionData.cs @@ -0,0 +1,97 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketSessionData + { + public PacketHeader m_header; + + public byte m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast, 3 = light rain, 4 = heavy rain, 5 = storm + public sbyte m_trackTemperature; // Track temp. in degrees celsius + public sbyte m_airTemperature; // Air temp. in degrees celsius + public byte m_totalLaps; // Total number of laps in this race + public ushort m_trackLength; // Track length in metres + public byte m_sessionType; // 0 = unknown, see appendix + public sbyte m_trackId; // -1 for unknown, see appendix + public byte m_formula; // Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2, 3 = F1 Generic, 4 = Beta, 6 = Esports, 8 = F1 World, 9 = F1 Elimination + public ushort m_sessionTimeLeft; // Time left in session in seconds + public ushort m_sessionDuration; // Session duration in seconds + public byte m_pitSpeedLimit; // Pit speed limit in kilometres per hour + public byte m_gamePaused; // Whether the game is paused - network game only + public byte m_isSpectating; // Whether the player is spectating + public byte m_spectatorCarIndex; // Index of the car being spectated + public byte m_sliProNativeSupport; // SLI Pro support, 0 = inactive, 1 = active + public byte m_numMarshalZones; // Number of marshal zones to follow + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 21)] + public MarshalZone[] m_marshalZones; // List of marshal zones - max 21 + + public byte m_safetyCarStatus; // 0 = no safety car, 1 = full, 2 = virtual, 3 = formation lap + public byte m_networkGame; // 0 = offline, 1 = online + public byte m_numWeatherForecastSamples; // Number of weather samples to follow + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] + public WeatherForecastSample[] m_weatherForecastSamples; // Array of weather forecast samples + + public byte m_forecastAccuracy; // 0 = Perfect, 1 = Approximate + public byte m_aiDifficulty; // AI difficulty - 0-110 + public uint m_seasonLinkIdentifier; // Identifier for season - persists across saves + public uint m_weekendLinkIdentifier; // Identifier for weekend - persists across saves + public uint m_sessionLinkIdentifier; // Identifier for session - persists across saves + public byte m_pitStopWindowIdealLap; // Ideal lap to pit on for current strategy (player) + public byte m_pitStopWindowLatestLap; // Latest lap to pit on for current strategy (player) + public byte m_pitStopRejoinPosition; // Predicted position to rejoin at (player) + public byte m_steeringAssist; // 0 = off, 1 = on + public byte m_brakingAssist; // 0 = off, 1 = low, 2 = medium, 3 = high + public byte m_gearboxAssist; // 1 = manual, 2 = manual & suggested gear, 3 = auto + public byte m_pitAssist; // 0 = off, 1 = on + public byte m_pitReleaseAssist; // 0 = off, 1 = on + public byte m_ERSAssist; // 0 = off, 1 = on + public byte m_DRSAssist; // 0 = off, 1 = on + public byte m_dynamicRacingLine; // 0 = off, 1 = corners only, 2 = full + public byte m_dynamicRacingLineType; // 0 = 2D, 1 = 3D + public byte m_gameMode; // Game mode id - see appendix + public byte m_ruleSet; // Ruleset - see appendix + public uint m_timeOfDay; // Local time of day - minutes since midnight + public byte m_sessionLength; // 0 = None, 2 = Very Short, 3 = Short, 4 = Medium, 5 = Medium Long, 6 = Long, 7 = Full + public byte m_speedUnitsLeadPlayer; // 0 = MPH, 1 = KPH + public byte m_temperatureUnitsLeadPlayer; // 0 = Celsius, 1 = Fahrenheit + public byte m_speedUnitsSecondaryPlayer; // 0 = MPH, 1 = KPH + public byte m_temperatureUnitsSecondaryPlayer; // 0 = Celsius, 1 = Fahrenheit + public byte m_numSafetyCarPeriods; // Number of safety cars called during session + public byte m_numVirtualSafetyCarPeriods; // Number of virtual safety cars called during session + public byte m_numRedFlagPeriods; // Number of red flags called during session + public byte m_equalCarPerformance; // 0 = Off, 1 = On + public byte m_recoveryMode; // 0 = None, 1 = Flashbacks, 2 = Auto-recovery + public byte m_flashbackLimit; // 0 = Low, 1 = Medium, 2 = High, 3 = Unlimited + public byte m_surfaceType; // 0 = Simplified, 1 = Realistic + public byte m_lowFuelMode; // 0 = Easy, 1 = Hard + public byte m_raceStarts; // 0 = Manual, 1 = Assisted + public byte m_tyreTemperature; // 0 = Surface only, 1 = Surface & Carcass + public byte m_pitLaneTyreSim; // 0 = On, 1 = Off + public byte m_carDamage; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Simulation + public byte m_carDamageRate; // 0 = Reduced, 1 = Standard, 2 = Simulation + public byte m_collisions; // 0 = Off, 1 = Player-to-Player Off, 2 = On + public byte m_collisionsOffForFirstLapOnly; // 0 = Disabled, 1 = Enabled + public byte m_mpUnsafePitRelease; // 0 = On, 1 = Off (Multiplayer) + public byte m_mpOffForGriefing; // 0 = Disabled, 1 = Enabled (Multiplayer) + public byte m_cornerCuttingStringency; // 0 = Regular, 1 = Strict + public byte m_parcFermeRules; // 0 = Off, 1 = On + public byte m_pitStopExperience; // 0 = Automatic, 1 = Broadcast, 2 = Immersive + public byte m_safetyCar; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Increased + public byte m_safetyCarExperience; // 0 = Broadcast, 1 = Immersive + public byte m_formationLap; // 0 = Off, 1 = On + public byte m_formationLapExperience; // 0 = Broadcast, 1 = Immersive + public byte m_redFlags; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Increased + public byte m_affectsLicenceLevelSolo; // 0 = Off, 1 = On + public byte m_affectsLicenceLevelMP; // 0 = Off, 1 = On + public byte m_numSessionsInWeekend; // Number of session in following array + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)] + public byte[] m_weekendStructure; // List of session types to show weekend structure - see appendix for types + + public float m_sector2LapDistanceStart; // Distance in m around track where sector 2 starts + public float m_sector3LapDistanceStart; // Distance in m around track where sector 3 starts + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketSessionHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketSessionHistoryData.cs new file mode 100644 index 0000000..17d2078 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketSessionHistoryData.cs @@ -0,0 +1,25 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketSessionHistoryData + { + public PacketHeader m_header; + + public byte m_carIdx; // Index of the car this lap data relates to + public byte m_numLaps; // Num laps in the data (including current partial lap) + public byte m_numTyreStints; // Number of tyre stints in the data + + public byte m_bestLapTimeLapNum; // Lap the best lap time was achieved on + public byte m_bestSector1LapNum; // Lap the best Sector 1 time was achieved on + public byte m_bestSector2LapNum; // Lap the best Sector 2 time was achieved on + public byte m_bestSector3LapNum; // Lap the best Sector 3 time was achieved on + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)] + public LapHistoryData[] m_lapHistoryData; // 100 laps of data max + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public TyreStintHistoryData[] m_tyreStintsHistoryData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketTimeTrialData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketTimeTrialData.cs new file mode 100644 index 0000000..90b6bdf --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketTimeTrialData.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketTimeTrialData + { + public PacketHeader m_header; + + public TimeTrialDataSet m_playerSessionBestDataSet; // Player session best data set + public TimeTrialDataSet m_personalBestDataSet; // Personal best data set + public TimeTrialDataSet m_rivalDataSet; // Rival data set + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketTyreSetsData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketTyreSetsData.cs new file mode 100644 index 0000000..b4fa500 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketTyreSetsData.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketTyreSetsData + { + public PacketHeader m_header; + + public byte m_carIdx; // Index of the car this data relates to + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public TyreSetData[] m_tyreSetData; // 13 (dry) + 7 (wet) + + public byte m_fittedIdx; // Index into array of fitted tyre + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/ParticipantData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/ParticipantData.cs new file mode 100644 index 0000000..9668f72 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/ParticipantData.cs @@ -0,0 +1,28 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Data about one participant in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ParticipantData + { + public byte m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + public byte m_driverId; // Driver id - see appendix, 255 if network human + public byte m_networkId; // Network id - unique identifier for network players + public byte m_teamId; // Team id - see appendix + public byte m_myTeam; // My team flag - 1 = My Team, 0 = otherwise + public byte m_raceNumber; // Race number of the car + public byte m_nationality; // Nationality of the driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] + public byte[] m_name; // Name of participant in UTF-8 format – null terminated + // Will be truncated with ... (U+2026) if too long + + public byte m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + public byte m_showOnlineNames; // The player's show online names setting, 0 = off, 1 = on + public ushort m_techLevel; // F1 World tech level + public byte m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/TimeTrialDataSet.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/TimeTrialDataSet.cs new file mode 100644 index 0000000..9f3dc33 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/TimeTrialDataSet.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TimeTrialDataSet + { + public byte m_carIdx; // Index of the car this data relates to + public byte m_teamId; // Team id - see appendix + public uint m_lapTimeInMS; // Lap time in milliseconds + public uint m_sector1TimeInMS; // Sector 1 time in milliseconds + public uint m_sector2TimeInMS; // Sector 2 time in milliseconds + public uint m_sector3TimeInMS; // Sector 3 time in milliseconds + public byte m_tractionControl; // 0 = assist off, 1 = assist on + public byte m_gearboxAssist; // 0 = assist off, 1 = assist on + public byte m_antiLockBrakes; // 0 = assist off, 1 = assist on + public byte m_equalCarPerformance; // 0 = Realistic, 1 = Equal + public byte m_customSetup; // 0 = No, 1 = Yes + public byte m_valid; // 0 = invalid, 1 = valid + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/TyreSetData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/TyreSetData.cs new file mode 100644 index 0000000..c78945c --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/TyreSetData.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Data about one tyre set in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TyreSetData + { + public byte m_actualTyreCompound; // Actual tyre compound used + public byte m_visualTyreCompound; // Visual tyre compound used + public byte m_wear; // Tyre wear (percentage) + public byte m_available; // Whether this set is currently available + public byte m_recommendedSession; // Recommended session for tyre set, see appendix + public byte m_lifeSpan; // Laps left in this tyre set + public byte m_usableLife; // Max number of laps recommended for this compound + public short m_lapDeltaTime; // Lap delta time in milliseconds compared to fitted set + public byte m_fitted; // Whether the set is fitted or not + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/TyreStintHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/TyreStintHistoryData.cs new file mode 100644 index 0000000..f1497a3 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/TyreStintHistoryData.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TyreStintHistoryData + { + public byte m_endLap; // Lap the tyre usage ends on (255 if current tyre) + public byte m_tyreActualCompound; // Actual tyres used by this driver + public byte m_tyreVisualCompound; // Visual tyres used by this driver + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/WeatherForecastSample.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/WeatherForecastSample.cs new file mode 100644 index 0000000..b6a80ab --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/WeatherForecastSample.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct WeatherForecastSample + { + public byte m_sessionType; // 0 = unknown, see appendix + public byte m_timeOffset; // Time in minutes the forecast is for + public byte m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast, 3 = light rain, 4 = heavy rain, 5 = storm + public sbyte m_trackTemperature; // Track temp. in degrees celsius + public sbyte m_trackTemperatureChange; // Track temp. change - 0 = up, 1 = down, 2 = no change + public sbyte m_airTemperature; // Air temp. in degrees celsius + public sbyte m_airTemperatureChange; // Air temp. change - 0 = up, 1 = down, 2 = no change + public byte m_rainPercentage; // Rain percentage (0-100) + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs b/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs new file mode 100644 index 0000000..5962cb5 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs @@ -0,0 +1,52 @@ +using GamesDat.Core.Telemetry.Sources.Formula1.F12024; +using GamesDat.Core.Telemetry.Sources.Formula1.F12025; + +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + internal static class F1PacketTypeMapper + { + private static readonly Dictionary<(ushort format, byte id), Type> _packetTypeMap = new() + { + // F1 2024 + [(2024, (byte)PacketId.Motion)] = typeof(F12024.PacketMotionData), + [(2024, (byte)PacketId.Session)] = typeof(F12024.PacketSessionData), + [(2024, (byte)PacketId.LapData)] = typeof(F12024.PacketLapData), + [(2024, (byte)PacketId.Event)] = typeof(F12024.PacketEventData), + [(2024, (byte)PacketId.Participants)] = typeof(F12024.PacketParticipantsData), + [(2024, (byte)PacketId.CarSetups)] = typeof(F12024.PacketCarSetupData), + [(2024, (byte)PacketId.CarTelemetry)] = typeof(F12024.PacketCarTelemetryData), + [(2024, (byte)PacketId.CarStatus)] = typeof(F12024.PacketCarStatusData), + [(2024, (byte)PacketId.FinalClassification)] = typeof(F12024.PacketFinalClassificationData), + [(2024, (byte)PacketId.LobbyInfo)] = typeof(F12024.PacketLobbyInfoData), + [(2024, (byte)PacketId.CarDamage)] = typeof(F12024.PacketCarDamageData), + [(2024, (byte)PacketId.SessionHistory)] = typeof(F12024.PacketSessionHistoryData), + [(2024, (byte)PacketId.TyreSets)] = typeof(F12024.PacketTyreSetsData), + [(2024, (byte)PacketId.MotionEx)] = typeof(F12024.PacketMotionExData), + [(2024, (byte)PacketId.TimeTrial)] = typeof(F12024.PacketTimeTrialData), + + // F1 2025 + [(2025, (byte)PacketId.Motion)] = typeof(F12025.MotionData), + [(2025, (byte)PacketId.Session)] = typeof(F12025.SessionData), + [(2025, (byte)PacketId.LapData)] = typeof(F12025.PacketLapData), + [(2025, (byte)PacketId.Event)] = typeof(F12025.EventData), + [(2025, (byte)PacketId.Participants)] = typeof(F12025.ParticipantData), + [(2025, (byte)PacketId.CarSetups)] = typeof(F12025.CarSetupData), + [(2025, (byte)PacketId.CarTelemetry)] = typeof(F12025.CarTelemetryData), + [(2025, (byte)PacketId.CarStatus)] = typeof(F12025.PacketCarStatusData), + [(2025, (byte)PacketId.FinalClassification)] = typeof(F12025.PacketFinalClassificationData), + [(2025, (byte)PacketId.LobbyInfo)] = typeof(F12025.PacketLobbyInfoData), + [(2025, (byte)PacketId.CarDamage)] = typeof(F12025.PacketCarDamageData), + [(2025, (byte)PacketId.SessionHistory)] = typeof(F12025.PacketSessionHistoryData), + [(2025, (byte)PacketId.TyreSets)] = typeof(F12025.PacketTyreSetsData), + [(2025, (byte)PacketId.MotionEx)] = typeof(F12025.PacketMotionExData), + [(2025, (byte)PacketId.TimeTrial)] = typeof(F12025.PacketTimeTrialData), + [(2025, (byte)PacketId.LapPositions)] = typeof(F12025.PacketLapPositionsData) + }; + + public static Type? GetPacketType(ushort packetFormat, byte packetId) + { + _packetTypeMap.TryGetValue((packetFormat, packetId), out var type); + return type; + } + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs b/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs index 1905657..394042e 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs @@ -1,20 +1,55 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.InteropServices; namespace GamesDat.Core.Telemetry.Sources.Formula1 { internal class F1RealtimeTelemetrySource : UdpSourceBase { + private const int MinRoutingHeaderSize = 7; + public F1RealtimeTelemetrySource(UdpSourceOptions options) : base(options) { } protected override IEnumerable ProcessData(byte[] data) { - yield return new F1TelemetryFrame(); + if (data.Length < MinRoutingHeaderSize) + yield break; + + var packetFormat = BitConverter.ToUInt16(data, 0); + var packetId = data[6]; + + var packetType = F1PacketTypeMapper.GetPacketType(packetFormat, packetId); + if (packetType == null) + yield break; + + var expectedSize = Marshal.SizeOf(packetType); + if (data.Length < expectedSize) + yield break; + + var packet = BytesToStruct(data, packetType); + + yield return new F1TelemetryFrame + { + Packet = packet, + PacketFormat = packetFormat, + PacketId = packetId, + PacketType = packetType + }; + } + + private static object BytesToStruct(byte[] bytes, Type type) + { + var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + try + { + return Marshal.PtrToStructure(handle.AddrOfPinnedObject(), type)!; + } + finally + { + handle.Free(); + } } } } diff --git a/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs index d6398bb..901664c 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs @@ -8,5 +8,16 @@ namespace GamesDat.Core.Telemetry.Sources.Formula1 { public struct F1TelemetryFrame { + public object Packet { get; init; } + public ushort PacketFormat { get; init; } + public byte PacketId { get; init; } + public Type PacketType { get; init; } + + public T GetPacket() where T : struct + { + if (Packet is T typed) + return typed; + throw new InvalidCastException($"Packet is {Packet?.GetType().Name ?? "null"}, not {typeof(T).Name}"); + } } } diff --git a/GamesDat/Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md b/GamesDat/Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md new file mode 100644 index 0000000..0fabbea --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md @@ -0,0 +1,322 @@ +# F1 UDP Telemetry Implementation Workflow + +This document describes the workflow for implementing F1 game UDP telemetry packet structures from C++ specifications into C# structs for the GamesDat project. + +## Overview + +The F1 games send UDP telemetry data using a specific binary format defined in C++ header files. This workflow guides the process of converting those C++ specifications into C# structs that can deserialize the UDP packets. + +## Prerequisites + +- C++ specification file (typically provided by EA/Codemasters) +- Understanding of C struct layout and C# interop +- Existing F1 implementation to use as reference (e.g., F12025) + +## Workflow Steps + +### 1. Analyze the C++ Specification + +Review the C++ header file to understand: +- **Packet format version** (e.g., 2024, 2025) +- **Packet sizes** (documented in comments) +- **Constants** (e.g., `cs_maxNumCarsInUDPData = 22`) +- **Struct hierarchy** (which structs contain others) +- **Array sizes** (fixed-size arrays in structs) + +### 2. Create the Namespace Folder + +Create a new folder under `Telemetry/Sources/Formula1/` with the naming pattern `F1YYYY` (e.g., `F12024`, `F12025`). + +### 3. Implement the PacketHeader + +Start with the `PacketHeader` struct as it's used by all packet types: + +```csharp +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F1YYYY +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketHeader + { + public ushort m_packetFormat; // YYYY + public byte m_gameYear; // Last two digits e.g. 24 + // ... other fields from spec + } +} +``` + +**Key points:** +- Always use `[StructLayout(LayoutKind.Sequential, Pack = 1)]` +- Keep field names exactly as in C++ spec (helps with debugging) +- Add inline comments from the C++ spec + +### 4. Implement Support Structures + +Create the smaller, reusable structs before the main packet types: + +Examples: `MarshalZone`, `WeatherForecastSample`, `CarMotionData`, etc. + +**Pattern:** +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct StructName +{ + public type m_fieldName; // comment from spec + // ... more fields +} +``` + +### 5. Implement Main Packet Structures + +For each packet type from the spec, create: + +1. **Component struct** (e.g., `LapData`) - data for one car/item +2. **Packet struct** (e.g., `PacketLapData`) - complete packet with header and array + +**Pattern:** +```csharp +// Component for one car +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct ComponentData +{ + public type m_field1; + public type m_field2; +} + +// Full packet +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct PacketComponentData +{ + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ComponentData[] m_dataArray; + + // ... any additional single fields +} +``` + +### 6. Handle Special Cases + +#### Fixed-Size Arrays +```csharp +[MarshalAs(UnmanagedType.ByValArray, SizeConst = SIZE)] +public Type[] m_arrayField; +``` + +#### Strings/Character Arrays +```csharp +[MarshalAs(UnmanagedType.ByValArray, SizeConst = LENGTH)] +public byte[] m_name; // UTF-8 null-terminated string +``` + +#### Unions (EventDataDetails) +Use `StructLayout(LayoutKind.Explicit)` with `FieldOffset`: + +```csharp +[StructLayout(LayoutKind.Explicit)] +public struct EventDataDetails +{ + [FieldOffset(0)] public FastestLapData FastestLap; + [FieldOffset(0)] public RetirementData Retirement; + // ... all union members at offset 0 +} +``` + +Then create a wrapper with helper methods: +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct PacketEventData +{ + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_eventStringCode; + + public EventDataDetails m_eventDetails; + + // Helper to get event code as string + public string EventCode => Encoding.ASCII.GetString(m_eventStringCode); + + // Helper to safely extract typed event details + public T GetEventDetails() where T : struct { ... } +} +``` + +### 7. Type Mapping Reference + +| C++ Type | C# Type | Notes | +|----------|---------|-------| +| `uint8` | `byte` | Unsigned 8-bit | +| `int8` | `sbyte` | Signed 8-bit | +| `uint16` | `ushort` | Unsigned 16-bit | +| `int16` | `short` | Signed 16-bit | +| `uint32` / `uint` | `uint` | Unsigned 32-bit | +| `int32` | `int` | Signed 32-bit | +| `uint64` | `ulong` | Unsigned 64-bit | +| `float` | `float` | 32-bit float | +| `double` | `double` | 64-bit float | +| `char[]` | `byte[]` | UTF-8 strings | + +### 8. Update F1PacketTypeMapper + +Add mappings for all packet types to `F1PacketTypeMapper.cs`: + +```csharp +private static readonly Dictionary<(ushort format, byte id), Type> _packetTypeMap = new() +{ + // F1 YYYY + [(YYYY, (byte)PacketId.Motion)] = typeof(F1YYYY.PacketMotionData), + [(YYYY, (byte)PacketId.Session)] = typeof(F1YYYY.PacketSessionData), + // ... all packet types + + // F1 previous years + // ... +}; +``` + +**Important:** Use fully qualified type names (e.g., `F12024.PacketMotionData`) to avoid conflicts between years. + +### 9. Packet Types Checklist + +Ensure you implement all standard packet types: + +- [ ] Motion (`PacketMotionData`) +- [ ] Session (`PacketSessionData`) +- [ ] Lap Data (`PacketLapData`) +- [ ] Event (`PacketEventData`) +- [ ] Participants (`PacketParticipantsData`) +- [ ] Car Setups (`PacketCarSetupData`) +- [ ] Car Telemetry (`PacketCarTelemetryData`) +- [ ] Car Status (`PacketCarStatusData`) +- [ ] Final Classification (`PacketFinalClassificationData`) +- [ ] Lobby Info (`PacketLobbyInfoData`) +- [ ] Car Damage (`PacketCarDamageData`) +- [ ] Session History (`PacketSessionHistoryData`) +- [ ] Tyre Sets (`PacketTyreSetsData`) +- [ ] Motion Ex (`PacketMotionExData`) +- [ ] Time Trial (`PacketTimeTrialData`) + +### 10. Validation + +After implementation: + +1. **Build the project** - ensure no compilation errors +2. **Check struct sizes** - if possible, verify binary size matches spec comments +3. **Test with real data** - use actual game UDP packets to validate deserialization +4. **Compare with previous year** - look for differences and ensure they're intentional + +## Common Patterns + +### Pattern 1: Simple Data Struct +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct SimpleData +{ + public byte m_field1; + public float m_field2; +} +``` + +### Pattern 2: Packet with Array +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct PacketWithArray +{ + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ItemData[] m_items; +} +``` + +### Pattern 3: Nested Arrays +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct DataWithArrays +{ + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresPressure; // 4 tyres +} +``` + +### Pattern 4: Data + Extra Fields +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct PacketWithExtras +{ + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ItemData[] m_items; + + public byte m_extraField1; + public byte m_extraField2; +} +``` + +## Best Practices + +1. **Maintain exact field order** - C# struct layout must match C++ exactly +2. **Use Pack = 1** - prevents automatic padding/alignment +3. **Keep original naming** - makes comparison with spec easier +4. **Add XML comments** - for complex structs, add summary docs +5. **Preserve spec comments** - inline comments help understand field meaning +6. **Test incrementally** - validate each packet type as you implement it +7. **Reference existing implementations** - use previous years as templates +8. **Watch for year-specific changes** - array sizes, new fields, removed fields + +## File Organization + +Each year's implementation should be self-contained: + +``` +Formula1/ +├── F12024/ +│ ├── PacketHeader.cs +│ ├── PacketMotionData.cs +│ ├── CarMotionData.cs +│ ├── PacketSessionData.cs +│ ├── MarshalZone.cs +│ ├── WeatherForecastSample.cs +│ └── ... (all other packet types) +├── F12025/ +│ └── ... (same structure) +├── F1PacketTypeMapper.cs +├── PacketId.cs (shared enum) +└── EventCodes.cs (shared constants) +``` + +## Troubleshooting + +### Issue: Struct size doesn't match spec +- Check for missing `Pack = 1` +- Verify all arrays have correct `SizeConst` +- Ensure no fields were skipped +- Check type mappings (e.g., `int8` vs `uint8`) + +### Issue: Deserialization fails +- Verify packet header format number matches +- Check PacketTypeMapper has correct entries +- Ensure union types use `LayoutKind.Explicit` +- Validate field order exactly matches spec + +### Issue: Data seems corrupted +- Check endianness (should be little-endian) +- Verify `Pack = 1` is set +- Ensure no extra padding between fields +- Check array sizes match spec constants + +## Example: Complete Implementation + +See `F12024/` folder for a complete reference implementation following this workflow. + +## Version History + +- **2024-02-09**: Created workflow documentation based on F12024 implementation +- **Future**: Update as new patterns or edge cases are discovered + +--- + +*This workflow is designed for GamesDat project contributors and AI agents implementing F1 telemetry support.* From 06cbfb848e412e48dabbbc7d4a68d63e22a909cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Mon, 9 Feb 2026 14:30:33 +0100 Subject: [PATCH 03/25] Added F1 2023 structs --- .claude/skills/f1-add-game/SKILL.md | 409 +++++ GamesDat/.claude/settings.local.json | 3 +- .../Sources/Formula1/F12023/CarDamageData.cs | 36 + .../Sources/Formula1/F12023/CarMotionData.cs | 30 + .../Sources/Formula1/F12023/CarSetupData.cs | 31 + .../Sources/Formula1/F12023/CarStatusData.cs | 40 + .../Formula1/F12023/CarTelemetryData.cs | 36 + .../Formula1/F12023/EventDataDetails.cs | 110 ++ .../F12023/FinalClassificationData.cs | 31 + .../Sources/Formula1/F12023/LapData.cs | 43 + .../Sources/Formula1/F12023/LapHistoryData.cs | 18 + .../Sources/Formula1/F12023/LobbyInfoData.cs | 20 + .../Sources/Formula1/F12023/MarshalZone.cs | 11 + .../Formula1/F12023/PacketCarDamageData.cs | 19 + .../Formula1/F12023/PacketCarSetupData.cs | 19 + .../Formula1/F12023/PacketCarStatusData.cs | 19 + .../Formula1/F12023/PacketCarTelemetryData.cs | 27 + .../Formula1/F12023/PacketEventData.cs | 21 + .../F12023/PacketFinalClassificationData.cs | 21 + .../Sources/Formula1/F12023/PacketHeader.cs | 21 + .../Sources/Formula1/F12023/PacketLapData.cs | 22 + .../Formula1/F12023/PacketLobbyInfoData.cs | 21 + .../Formula1/F12023/PacketMotionData.cs | 19 + .../Formula1/F12023/PacketMotionExData.cs | 58 + .../Formula1/F12023/PacketParticipantsData.cs | 21 + .../Formula1/F12023/PacketSessionData.cs | 79 + .../F12023/PacketSessionHistoryData.cs | 31 + .../Formula1/F12023/PacketTyreSetsData.cs | 23 + .../Formula1/F12023/ParticipantData.cs | 24 + .../Sources/Formula1/F12023/TyreSetData.cs | 18 + .../Formula1/F12023/TyreStintHistoryData.cs | 12 + .../Formula1/F12023/WeatherForecastSample.cs | 20 + .../Sources/Formula1/F1PacketTypeMapper.cs | 17 + .../Formula1/Specifications/F1_2023_spec.h | 1342 +++++++++++++++++ .../Sources/Formula1/Specifications/README.md | 31 + 35 files changed, 2702 insertions(+), 1 deletion(-) create mode 100644 .claude/skills/f1-add-game/SKILL.md create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/CarDamageData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/CarMotionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/CarSetupData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/CarStatusData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/CarTelemetryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/EventDataDetails.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/FinalClassificationData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/LapData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/LapHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/LobbyInfoData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/MarshalZone.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarDamageData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarSetupData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarStatusData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarTelemetryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketEventData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketFinalClassificationData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketHeader.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketLapData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketLobbyInfoData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketMotionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketMotionExData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketParticipantsData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketSessionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketSessionHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketTyreSetsData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/ParticipantData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/TyreSetData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/TyreStintHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/WeatherForecastSample.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2023_spec.h create mode 100644 GamesDat/Telemetry/Sources/Formula1/Specifications/README.md diff --git a/.claude/skills/f1-add-game/SKILL.md b/.claude/skills/f1-add-game/SKILL.md new file mode 100644 index 0000000..45a731b --- /dev/null +++ b/.claude/skills/f1-add-game/SKILL.md @@ -0,0 +1,409 @@ +--- +name: f1-add-game +description: Adds the required telemetry structs for a specific game year of the F1 game series made by EA/Codemasters, based on their C++ specification files. +--- + +# F1 UDP Telemetry Implementation Skill + +You are an expert F1 UDP telemetry implementation specialist for the GamesDat project. Your role is to convert C++ specification files from EA/Codemasters into C# structs that can deserialize UDP packets. + +## Invocation + +When invoked with `/f1-implementation ` (e.g., `/f1-implementation 2026`), follow this workflow to implement F1 telemetry packet structures. + +## Step 0: Locate the C++ Specification + +**Before starting implementation, you MUST locate the C++ specification file using these strategies:** + +### Strategy 1: Check Convention-Based Location +Look for specification files in these locations: +- `Telemetry/Sources/Formula1/Specifications/F1__spec.h` +- `Telemetry/Sources/Formula1/Specifications/F1_spec.h` +- `Telemetry/Sources/Formula1/Specifications/_udp_spec.h` +- `Telemetry/Sources/Formula1/Docs/` directory + +Use Glob tool to search: +``` +pattern: "Telemetry/Sources/Formula1/**/*.h" +pattern: "Telemetry/Sources/Formula1/**/*.cpp" +pattern: "Telemetry/Sources/Formula1/**/*spec*" +pattern: "Telemetry/Sources/Formula1/**/**" +``` + +### Strategy 2: Search Repository-Wide +If not found in Formula1 directory, search the entire repository: +``` +pattern: "**/*F1**.h" +pattern: "**/*udp**.h" +``` + +### Strategy 3: Ask User for Specification +If specification is not found, ask the user: + +**Use AskUserQuestion tool with these options:** +- **Question**: "I couldn't find the C++ specification file for F1 . How would you like to provide it?" +- **Header**: "Spec Location" +- **Options**: + 1. **Label**: "File path on disk", **Description**: "I'll provide the path to the .h or .cpp file" + 2. **Label**: "Paste content", **Description**: "I'll paste the C++ specification content directly" + 3. **Label**: "Download from URL", **Description**: "I'll provide a URL to download the spec" + 4. **Label**: "Use previous year as template", **Description**: "Copy from F1 and I'll modify manually" + +### Strategy 4: Handle User Response +- **If file path provided**: Read the file using Read tool +- **If content pasted**: Proceed with the pasted content +- **If URL provided**: Use WebFetch or Bash (curl) to download +- **If template requested**: Copy previous year's implementation and note differences for manual review + +**IMPORTANT**: Do NOT proceed with implementation until you have the specification content in hand. + +--- + +## Implementation Workflow + +Once you have the C++ specification, follow these steps: + +### 1. Analyze the C++ Specification + +Review the spec to understand: +- **Packet format version** (e.g., 2024, 2025) +- **Packet sizes** (documented in comments) +- **Constants** (e.g., `cs_maxNumCarsInUDPData = 22`) +- **Struct hierarchy** (which structs contain others) +- **Array sizes** (fixed-size arrays in structs) + +### 2. Create the Namespace Folder + +Create: `Telemetry/Sources/Formula1/F1/` + +Example: `Telemetry/Sources/Formula1/F12026/` + +### 3. Implement the PacketHeader + +Start with the `PacketHeader` struct as it's used by all packet types: + +```csharp +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F1 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketHeader + { + public ushort m_packetFormat; // + public byte m_gameYear; // Last two digits e.g. 26 + public byte m_gameMajorVersion; // Game major version + public byte m_gameMinorVersion; // Game minor version + public byte m_packetVersion; // Version of this packet type + public byte m_packetId; // Identifier for the packet type + public ulong m_sessionUID; // Unique identifier for the session + public float m_sessionTime; // Session timestamp + public uint m_frameIdentifier; // Identifier for the frame + public uint m_overallFrameIdentifier; // Overall identifier + public byte m_playerCarIndex; // Index of player's car + public byte m_secondaryPlayerCarIndex; // Index of secondary player's car + } +} +``` + +**Key points:** +- Always use `[StructLayout(LayoutKind.Sequential, Pack = 1)]` +- Keep field names exactly as in C++ spec (helps with debugging) +- Add inline comments from the C++ spec + +### 4. Implement Support Structures + +Create the smaller, reusable structs before the main packet types. + +Examples: `MarshalZone`, `WeatherForecastSample`, `CarMotionData`, etc. + +**Pattern:** +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct StructName +{ + public type m_fieldName; // comment from spec + // ... more fields +} +``` + +### 5. Implement Main Packet Structures + +For each packet type from the spec, create: + +1. **Component struct** (e.g., `LapData`) - data for one car/item +2. **Packet struct** (e.g., `PacketLapData`) - complete packet with header and array + +**Pattern:** +```csharp +// Component for one car +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct ComponentData +{ + public type m_field1; + public type m_field2; +} + +// Full packet +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct PacketComponentData +{ + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ComponentData[] m_dataArray; + + // ... any additional single fields +} +``` + +### 6. Handle Special Cases + +#### Fixed-Size Arrays +```csharp +[MarshalAs(UnmanagedType.ByValArray, SizeConst = SIZE)] +public Type[] m_arrayField; +``` + +#### Strings/Character Arrays +```csharp +[MarshalAs(UnmanagedType.ByValArray, SizeConst = LENGTH)] +public byte[] m_name; // UTF-8 null-terminated string +``` + +#### Unions (EventDataDetails) +Use `StructLayout(LayoutKind.Explicit)` with `FieldOffset`: + +```csharp +[StructLayout(LayoutKind.Explicit)] +public struct EventDataDetails +{ + [FieldOffset(0)] public FastestLapData FastestLap; + [FieldOffset(0)] public RetirementData Retirement; + // ... all union members at offset 0 +} +``` + +Then create a wrapper with helper methods: +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct PacketEventData +{ + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_eventStringCode; + + public EventDataDetails m_eventDetails; + + // Helper to get event code as string + public string EventCode => System.Text.Encoding.ASCII.GetString(m_eventStringCode); + + // Helper to safely extract typed event details + public T GetEventDetails() where T : struct + { + // Implementation for safe union access + } +} +``` + +### 7. Type Mapping Reference + +| C++ Type | C# Type | Notes | +|----------|---------|-------| +| `uint8` | `byte` | Unsigned 8-bit | +| `int8` | `sbyte` | Signed 8-bit | +| `uint16` | `ushort` | Unsigned 16-bit | +| `int16` | `short` | Signed 16-bit | +| `uint32` / `uint` | `uint` | Unsigned 32-bit | +| `int32` | `int` | Signed 32-bit | +| `uint64` | `ulong` | Unsigned 64-bit | +| `float` | `float` | 32-bit float | +| `double` | `double` | 64-bit float | +| `char[]` | `byte[]` | UTF-8 strings | + +### 8. Update F1PacketTypeMapper + +Add mappings for all packet types to `Telemetry/Sources/Formula1/F1PacketTypeMapper.cs`: + +```csharp +private static readonly Dictionary<(ushort format, byte id), Type> _packetTypeMap = new() +{ + // F1 + [(, (byte)PacketId.Motion)] = typeof(F1.PacketMotionData), + [(, (byte)PacketId.Session)] = typeof(F1.PacketSessionData), + [(, (byte)PacketId.LapData)] = typeof(F1.PacketLapData), + [(, (byte)PacketId.Event)] = typeof(F1.PacketEventData), + [(, (byte)PacketId.Participants)] = typeof(F1.PacketParticipantsData), + [(, (byte)PacketId.CarSetups)] = typeof(F1.PacketCarSetupData), + [(, (byte)PacketId.CarTelemetry)] = typeof(F1.PacketCarTelemetryData), + [(, (byte)PacketId.CarStatus)] = typeof(F1.PacketCarStatusData), + [(, (byte)PacketId.FinalClassification)] = typeof(F1.PacketFinalClassificationData), + [(, (byte)PacketId.LobbyInfo)] = typeof(F1.PacketLobbyInfoData), + [(, (byte)PacketId.CarDamage)] = typeof(F1.PacketCarDamageData), + [(, (byte)PacketId.SessionHistory)] = typeof(F1.PacketSessionHistoryData), + [(, (byte)PacketId.TyreSets)] = typeof(F1.PacketTyreSetsData), + [(, (byte)PacketId.MotionEx)] = typeof(F1.PacketMotionExData), + [(, (byte)PacketId.TimeTrial)] = typeof(F1.PacketTimeTrialData), + // ... existing previous years ... +}; +``` + +**Important:** Use fully qualified type names (e.g., `F12024.PacketMotionData`) to avoid conflicts between years. + +### 9. Packet Types Checklist + +Ensure you implement all standard packet types: + +- [ ] Motion (`PacketMotionData`) +- [ ] Session (`PacketSessionData`) +- [ ] Lap Data (`PacketLapData`) +- [ ] Event (`PacketEventData`) +- [ ] Participants (`PacketParticipantsData`) +- [ ] Car Setups (`PacketCarSetupData`) +- [ ] Car Telemetry (`PacketCarTelemetryData`) +- [ ] Car Status (`PacketCarStatusData`) +- [ ] Final Classification (`PacketFinalClassificationData`) +- [ ] Lobby Info (`PacketLobbyInfoData`) +- [ ] Car Damage (`PacketCarDamageData`) +- [ ] Session History (`PacketSessionHistoryData`) +- [ ] Tyre Sets (`PacketTyreSetsData`) +- [ ] Motion Ex (`PacketMotionExData`) +- [ ] Time Trial (`PacketTimeTrialData`) + +**Note:** Some years may have additional packet types or missing ones. Follow the specification exactly. + +### 10. Validation + +After implementation: + +1. **Build the project** - `dotnet build` to ensure no compilation errors +2. **Check struct sizes** - if possible, verify binary size matches spec comments +3. **Compare with previous year** - look for differences and ensure they're intentional +4. **Create summary** - list all implemented packet types and any notable changes from previous year + +--- + +## Best Practices + +1. **Maintain exact field order** - C# struct layout must match C++ exactly +2. **Use Pack = 1** - prevents automatic padding/alignment +3. **Keep original naming** - makes comparison with spec easier +4. **Add XML comments** - for complex structs, add summary docs +5. **Preserve spec comments** - inline comments help understand field meaning +6. **Reference existing implementations** - use previous years as templates (F12024, F12025) +7. **Watch for year-specific changes** - array sizes, new fields, removed fields +8. **One file per packet type** - keeps code organized and maintainable + +## File Organization Pattern + +``` +Formula1/ +├── F1/ +│ ├── PacketHeader.cs +│ ├── CarMotionData.cs +│ ├── PacketMotionData.cs +│ ├── MarshalZone.cs +│ ├── WeatherForecastSample.cs +│ ├── PacketSessionData.cs +│ ├── LapData.cs +│ ├── PacketLapData.cs +│ └── ... (all other packet types) +├── F1PacketTypeMapper.cs (update this) +├── PacketId.cs (shared enum) +└── EventCodes.cs (shared constants) +``` + +## Common Patterns Reference + +### Pattern 1: Simple Data Struct +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct SimpleData +{ + public byte m_field1; + public float m_field2; +} +``` + +### Pattern 2: Packet with Array +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct PacketWithArray +{ + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ItemData[] m_items; +} +``` + +### Pattern 3: Nested Arrays +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct DataWithArrays +{ + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresPressure; // 4 tyres +} +``` + +### Pattern 4: Data + Extra Fields +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct PacketWithExtras +{ + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ItemData[] m_items; + + public byte m_extraField1; + public byte m_extraField2; +} +``` + +## Troubleshooting + +### Issue: Struct size doesn't match spec +- Check for missing `Pack = 1` +- Verify all arrays have correct `SizeConst` +- Ensure no fields were skipped +- Check type mappings (e.g., `int8` vs `uint8`) + +### Issue: Deserialization fails +- Verify packet header format number matches +- Check PacketTypeMapper has correct entries +- Ensure union types use `LayoutKind.Explicit` +- Validate field order exactly matches spec + +### Issue: Data seems corrupted +- Check endianness (should be little-endian) +- Verify `Pack = 1` is set +- Ensure no extra padding between fields +- Check array sizes match spec constants + +--- + +## Execution Approach + +When this skill is invoked: + +1. **Extract the year** from the invocation (e.g., "2026" from `/f1-implementation 2026`) +2. **Locate the specification** using the strategies in Step 0 +3. **Read existing implementations** (F12024, F12025) to understand patterns +4. **Implement all packet types** following the workflow above +5. **Update F1PacketTypeMapper** with new mappings +6. **Build and validate** the implementation +7. **Provide summary** of what was implemented + +## Reference Implementations + +Use these existing implementations as templates: +- `Telemetry/Sources/Formula1/F12024/` - Complete F1 2024 implementation +- `Telemetry/Sources/Formula1/F12025/` - Complete F1 2025 implementation +- `Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md` - Original workflow documentation + +--- + +*This skill automates F1 UDP telemetry implementation for GamesDat.* diff --git a/GamesDat/.claude/settings.local.json b/GamesDat/.claude/settings.local.json index 562a20f..43a375f 100644 --- a/GamesDat/.claude/settings.local.json +++ b/GamesDat/.claude/settings.local.json @@ -11,7 +11,8 @@ "Bash(dir /s /b \"..\\\\GamesDate.Demo.Wpf\\\\Views\\\\*.xaml\")", "WebFetch(domain:wiki.trackmania.io)", "Bash(xargs:*)", - "Bash(grep:*)" + "Bash(grep:*)", + "Bash(dir:*)" ] } } diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/CarDamageData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/CarDamageData.cs new file mode 100644 index 0000000..b5036e8 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/CarDamageData.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarDamageData + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresWear; // Tyre wear (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresDamage; // Tyre damage (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_brakesDamage; // Brakes damage (percentage) + + public byte m_frontLeftWingDamage; // Front left wing damage (percentage) + public byte m_frontRightWingDamage; // Front right wing damage (percentage) + public byte m_rearWingDamage; // Rear wing damage (percentage) + public byte m_floorDamage; // Floor damage (percentage) + public byte m_diffuserDamage; // Diffuser damage (percentage) + public byte m_sidepodDamage; // Sidepod damage (percentage) + public byte m_drsFault; // Indicator for DRS fault, 0 = OK, 1 = fault + public byte m_ersFault; // Indicator for ERS fault, 0 = OK, 1 = fault + public byte m_gearBoxDamage; // Gear box damage (percentage) + public byte m_engineDamage; // Engine damage (percentage) + public byte m_engineMGUHWear; // Engine wear MGU-H (percentage) + public byte m_engineESWear; // Engine wear ES (percentage) + public byte m_engineCEWear; // Engine wear CE (percentage) + public byte m_engineICEWear; // Engine wear ICE (percentage) + public byte m_engineMGUKWear; // Engine wear MGU-K (percentage) + public byte m_engineTCWear; // Engine wear TC (percentage) + public byte m_engineBlown; // Engine blown, 0 = OK, 1 = fault + public byte m_engineSeized; // Engine seized, 0 = OK, 1 = fault + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/CarMotionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/CarMotionData.cs new file mode 100644 index 0000000..a10f9cd --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/CarMotionData.cs @@ -0,0 +1,30 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Motion data for one car in F1 2023. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarMotionData + { + public float m_worldPositionX; // World space X position - metres + public float m_worldPositionY; // World space Y position + public float m_worldPositionZ; // World space Z position + public float m_worldVelocityX; // Velocity in world space X - metres/s + public float m_worldVelocityY; // Velocity in world space Y + public float m_worldVelocityZ; // Velocity in world space Z + public short m_worldForwardDirX; // World space forward X direction (normalised) + public short m_worldForwardDirY; // World space forward Y direction (normalised) + public short m_worldForwardDirZ; // World space forward Z direction (normalised) + public short m_worldRightDirX; // World space right X direction (normalised) + public short m_worldRightDirY; // World space right Y direction (normalised) + public short m_worldRightDirZ; // World space right Z direction (normalised) + public float m_gForceLateral; // Lateral G-Force component + public float m_gForceLongitudinal; // Longitudinal G-Force component + public float m_gForceVertical; // Vertical G-Force component + public float m_yaw; // Yaw angle in radians + public float m_pitch; // Pitch angle in radians + public float m_roll; // Roll angle in radians + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/CarSetupData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/CarSetupData.cs new file mode 100644 index 0000000..dd927d3 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/CarSetupData.cs @@ -0,0 +1,31 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarSetupData + { + public byte m_frontWing; // Front wing aero + public byte m_rearWing; // Rear wing aero + public byte m_onThrottle; // Differential adjustment on throttle (percentage) + public byte m_offThrottle; // Differential adjustment off throttle (percentage) + public float m_frontCamber; // Front camber angle (suspension geometry) + public float m_rearCamber; // Rear camber angle (suspension geometry) + public float m_frontToe; // Front toe angle (suspension geometry) + public float m_rearToe; // Rear toe angle (suspension geometry) + public byte m_frontSuspension; // Front suspension + public byte m_rearSuspension; // Rear suspension + public byte m_frontAntiRollBar; // Front anti-roll bar + public byte m_rearAntiRollBar; // Rear anti-roll bar + public byte m_frontSuspensionHeight; // Front ride height + public byte m_rearSuspensionHeight; // Rear ride height + public byte m_brakePressure; // Brake pressure (percentage) + public byte m_brakeBias; // Brake bias (percentage) + public float m_rearLeftTyrePressure; // Rear left tyre pressure (PSI) + public float m_rearRightTyrePressure; // Rear right tyre pressure (PSI) + public float m_frontLeftTyrePressure; // Front left tyre pressure (PSI) + public float m_frontRightTyrePressure; // Front right tyre pressure (PSI) + public byte m_ballast; // Ballast + public float m_fuelLoad; // Fuel load + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/CarStatusData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/CarStatusData.cs new file mode 100644 index 0000000..ed99732 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/CarStatusData.cs @@ -0,0 +1,40 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarStatusData + { + public byte m_tractionControl; // Traction control - 0 = off, 1 = medium, 2 = full + public byte m_antiLockBrakes; // 0 (off) - 1 (on) + public byte m_fuelMix; // Fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max + public byte m_frontBrakeBias; // Front brake bias (percentage) + public byte m_pitLimiterStatus; // Pit limiter status - 0 = off, 1 = on + public float m_fuelInTank; // Current fuel mass + public float m_fuelCapacity; // Fuel capacity + public float m_fuelRemainingLaps; // Fuel remaining in terms of laps (value on MFD) + public ushort m_maxRPM; // Cars max RPM, point of rev limiter + public ushort m_idleRPM; // Cars idle RPM + public byte m_maxGears; // Maximum number of gears + public byte m_drsAllowed; // 0 = not allowed, 1 = allowed + public ushort m_drsActivationDistance; // 0 = DRS not available, non-zero - DRS will be available in [X] metres + public byte m_actualTyreCompound; // F1 Modern - 16 = C5, 17 = C4, 18 = C3, 19 = C2, 20 = C1 + // 21 = C0, 7 = inter, 8 = wet + // F1 Classic - 9 = dry, 10 = wet + // F2 - 11 = super soft, 12 = soft, 13 = medium, 14 = hard, 15 = wet + public byte m_visualTyreCompound; // F1 visual (can be different from actual compound) + // 16 = soft, 17 = medium, 18 = hard, 7 = inter, 8 = wet + // F1 Classic - same as above + // F2 - 19, 15 = wet, 19 - super soft, 20 = soft, 21 = medium, 22 = hard + public byte m_tyresAgeLaps; // Age in laps of the current set of tyres + public sbyte m_vehicleFiaFlags; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow + public float m_enginePowerICE; // Engine power output of ICE (W) + public float m_enginePowerMGUK; // Engine power output of MGU-K (W) + public float m_ersStoreEnergy; // ERS energy store in Joules + public byte m_ersDeployMode; // ERS deployment mode, 0 = none, 1 = medium, 2 = hotlap, 3 = overtake + public float m_ersHarvestedThisLapMGUK; // ERS energy harvested this lap by MGU-K + public float m_ersHarvestedThisLapMGUH; // ERS energy harvested this lap by MGU-H + public float m_ersDeployedThisLap; // ERS energy deployed this lap + public byte m_networkPaused; // Whether the car is paused in a network game + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/CarTelemetryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/CarTelemetryData.cs new file mode 100644 index 0000000..2636e92 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/CarTelemetryData.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarTelemetryData + { + public ushort m_speed; // Speed of car in kilometres per hour + public float m_throttle; // Amount of throttle applied (0.0 to 1.0) + public float m_steer; // Steering (-1.0 (full lock left) to 1.0 (full lock right)) + public float m_brake; // Amount of brake applied (0.0 to 1.0) + public byte m_clutch; // Amount of clutch applied (0 to 100) + public sbyte m_gear; // Gear selected (1-8, N=0, R=-1) + public ushort m_engineRPM; // Engine RPM + public byte m_drs; // 0 = off, 1 = on + public byte m_revLightsPercent; // Rev lights indicator (percentage) + public ushort m_revLightsBitValue; // Rev lights (bit 0 = leftmost LED, bit 14 = rightmost LED) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public ushort[] m_brakesTemperature; // Brakes temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresSurfaceTemperature; // Tyres surface temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresInnerTemperature; // Tyres inner temperature (celsius) + + public ushort m_engineTemperature; // Engine temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresPressure; // Tyres pressure (PSI) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_surfaceType; // Driving surface, see appendices + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/EventDataDetails.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/EventDataDetails.cs new file mode 100644 index 0000000..d0027bb --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/EventDataDetails.cs @@ -0,0 +1,110 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Union of all event data types. Use the event code to determine which struct to access. + /// + [StructLayout(LayoutKind.Explicit)] + public struct EventDataDetails + { + [FieldOffset(0)] public FastestLapData FastestLap; + [FieldOffset(0)] public RetirementData Retirement; + [FieldOffset(0)] public TeamMateInPitsData TeamMateInPits; + [FieldOffset(0)] public RaceWinnerData RaceWinner; + [FieldOffset(0)] public PenaltyData Penalty; + [FieldOffset(0)] public SpeedTrapData SpeedTrap; + [FieldOffset(0)] public StartLightsData StartLights; + [FieldOffset(0)] public DriveThroughPenaltyServedData DriveThroughPenaltyServed; + [FieldOffset(0)] public StopGoPenaltyServedData StopGoPenaltyServed; + [FieldOffset(0)] public FlashbackData Flashback; + [FieldOffset(0)] public ButtonsData Buttons; + [FieldOffset(0)] public OvertakeData Overtake; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FastestLapData + { + public byte vehicleIdx; // Vehicle index of car achieving fastest lap + public float lapTime; // Lap time is in seconds + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RetirementData + { + public byte vehicleIdx; // Vehicle index of car retiring + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TeamMateInPitsData + { + public byte vehicleIdx; // Vehicle index of team mate + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RaceWinnerData + { + public byte vehicleIdx; // Vehicle index of the race winner + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PenaltyData + { + public byte penaltyType; // Penalty type - see Appendices + public byte infringementType; // Infringement type - see Appendices + public byte vehicleIdx; // Vehicle index of the car the penalty is applied to + public byte otherVehicleIdx; // Vehicle index of the other car involved + public byte time; // Time gained, or time spent doing action in seconds + public byte lapNum; // Lap the penalty occurred on + public byte placesGained; // Number of places gained by this + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SpeedTrapData + { + public byte vehicleIdx; // Vehicle index of the vehicle triggering speed trap + public float speed; // Top speed achieved in kilometres per hour + public byte isOverallFastestInSession; // Overall fastest speed in session = 1, otherwise 0 + public byte isDriverFastestInSession; // Fastest speed for driver in session = 1, otherwise 0 + public byte fastestVehicleIdxInSession; // Vehicle index of the vehicle that is the fastest in this session + public float fastestSpeedInSession; // Speed of the vehicle that is the fastest in this session + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct StartLightsData + { + public byte numLights; // Number of lights showing + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DriveThroughPenaltyServedData + { + public byte vehicleIdx; // Vehicle index of the vehicle serving drive through + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct StopGoPenaltyServedData + { + public byte vehicleIdx; // Vehicle index of the vehicle serving stop go + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FlashbackData + { + public uint flashbackFrameIdentifier; // Frame identifier flashed back to + public float flashbackSessionTime; // Session time flashed back to + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ButtonsData + { + public uint buttonStatus; // Bit flags specifying which buttons are being pressed currently - see appendices + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct OvertakeData + { + public byte overtakingVehicleIdx; // Vehicle index of the vehicle overtaking + public byte beingOvertakenVehicleIdx; // Vehicle index of the vehicle being overtaken + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/FinalClassificationData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/FinalClassificationData.cs new file mode 100644 index 0000000..3ef3f5a --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/FinalClassificationData.cs @@ -0,0 +1,31 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FinalClassificationData + { + public byte m_position; // Finishing position + public byte m_numLaps; // Number of laps completed + public byte m_gridPosition; // Grid position of the car + public byte m_points; // Number of points scored + public byte m_numPitStops; // Number of pit stops made + public byte m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active + // 3 = finished, 4 = didnotfinish, 5 = disqualified + // 6 = not classified, 7 = retired + public uint m_bestLapTimeInMS; // Best lap time of the session in milliseconds + public double m_totalRaceTime; // Total race time in seconds without penalties + public byte m_penaltiesTime; // Total penalties accumulated in seconds + public byte m_numPenalties; // Number of penalties applied to this driver + public byte m_numTyreStints; // Number of tyres stints up to maximum + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsActual; // Actual tyres used by this driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsVisual; // Visual tyres used by this driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsEndLaps; // The lap number stints end on + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/LapData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/LapData.cs new file mode 100644 index 0000000..885d379 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/LapData.cs @@ -0,0 +1,43 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LapData + { + public uint m_lastLapTimeInMS; // Last lap time in milliseconds + public uint m_currentLapTimeInMS; // Current time around the lap in milliseconds + public ushort m_sector1TimeInMS; // Sector 1 time in milliseconds + public byte m_sector1TimeMinutes; // Sector 1 whole minute part + public ushort m_sector2TimeInMS; // Sector 2 time in milliseconds + public byte m_sector2TimeMinutes; // Sector 2 whole minute part + public ushort m_deltaToCarInFrontInMS; // Time delta to car in front in milliseconds + public ushort m_deltaToRaceLeaderInMS; // Time delta to race leader in milliseconds + public float m_lapDistance; // Distance vehicle is around current lap in metres - could + // be negative if line hasn't been crossed yet + public float m_totalDistance; // Total distance travelled in session in metres - could + // be negative if line hasn't been crossed yet + public float m_safetyCarDelta; // Delta in seconds for safety car + public byte m_carPosition; // Car race position + public byte m_currentLapNum; // Current lap number + public byte m_pitStatus; // 0 = none, 1 = pitting, 2 = in pit area + public byte m_numPitStops; // Number of pit stops taken in this race + public byte m_sector; // 0 = sector1, 1 = sector2, 2 = sector3 + public byte m_currentLapInvalid; // Current lap invalid - 0 = valid, 1 = invalid + public byte m_penalties; // Accumulated time penalties in seconds to be added + public byte m_totalWarnings; // Accumulated number of warnings issued + public byte m_cornerCuttingWarnings; // Accumulated number of corner cutting warnings issued + public byte m_numUnservedDriveThroughPens; // Num drive through pens left to serve + public byte m_numUnservedStopGoPens; // Num stop go pens left to serve + public byte m_gridPosition; // Grid position the vehicle started the race in + public byte m_driverStatus; // Status of driver - 0 = in garage, 1 = flying lap + // 2 = in lap, 3 = out lap, 4 = on track + public byte m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active + // 3 = finished, 4 = didnotfinish, 5 = disqualified + // 6 = not classified, 7 = retired + public byte m_pitLaneTimerActive; // Pit lane timing, 0 = inactive, 1 = active + public ushort m_pitLaneTimeInLaneInMS; // If active, the current time spent in the pit lane in ms + public ushort m_pitStopTimerInMS; // Time of the actual pit stop in ms + public byte m_pitStopShouldServePen; // Whether the car should serve a penalty at this stop + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/LapHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/LapHistoryData.cs new file mode 100644 index 0000000..e3afb9d --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/LapHistoryData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LapHistoryData + { + public uint m_lapTimeInMS; // Lap time in milliseconds + public ushort m_sector1TimeInMS; // Sector 1 time in milliseconds + public byte m_sector1TimeMinutes; // Sector 1 whole minute part + public ushort m_sector2TimeInMS; // Sector 2 time in milliseconds + public byte m_sector2TimeMinutes; // Sector 2 whole minute part (note: spec has typo, says sector1TimeMinutes) + public ushort m_sector3TimeInMS; // Sector 3 time in milliseconds + public byte m_sector3TimeMinutes; // Sector 3 whole minute part + public byte m_lapValidBitFlags; // 0x01 bit set-lap valid, 0x02 bit set-sector 1 valid + // 0x04 bit set-sector 2 valid, 0x08 bit set-sector 3 valid + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/LobbyInfoData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/LobbyInfoData.cs new file mode 100644 index 0000000..c0beacf --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/LobbyInfoData.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LobbyInfoData + { + public byte m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + public byte m_teamId; // Team id - see appendix (255 if no team currently selected) + public byte m_nationality; // Nationality of the driver + public byte m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] + public byte[] m_name; // Name of participant in UTF-8 format - null terminated + // Will be truncated with ... (U+2026) if too long + + public byte m_carNumber; // Car number of the player + public byte m_readyStatus; // 0 = not ready, 1 = ready, 2 = spectating + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/MarshalZone.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/MarshalZone.cs new file mode 100644 index 0000000..7c491c0 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/MarshalZone.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MarshalZone + { + public float m_zoneStart; // Fraction (0..1) of way through the lap the marshal zone starts + public sbyte m_zoneFlag; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarDamageData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarDamageData.cs new file mode 100644 index 0000000..205adc0 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarDamageData.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Car damage packet for F1 2023. Damage parameters for all cars. + /// Frequency: 10 per second + /// Size: 953 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarDamageData + { + public PacketHeader m_header; // Header + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarDamageData[] m_carDamageData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarSetupData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarSetupData.cs new file mode 100644 index 0000000..a620267 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarSetupData.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Car setups packet for F1 2023. Details the car setups for each vehicle. + /// Frequency: 2 per second + /// Size: 1107 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarSetupData + { + public PacketHeader m_header; // Header + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarSetupData[] m_carSetups; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarStatusData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarStatusData.cs new file mode 100644 index 0000000..c3b48d3 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarStatusData.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Car status packet for F1 2023. Status data for all cars. + /// Frequency: Rate as specified in menus + /// Size: 1239 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarStatusData + { + public PacketHeader m_header; // Header + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarStatusData[] m_carStatusData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarTelemetryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarTelemetryData.cs new file mode 100644 index 0000000..283edd8 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarTelemetryData.cs @@ -0,0 +1,27 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Car telemetry packet for F1 2023. Telemetry data for all cars. + /// Frequency: Rate as specified in menus + /// Size: 1352 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarTelemetryData + { + public PacketHeader m_header; // Header + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarTelemetryData[] m_carTelemetryData; + + public byte m_mfdPanelIndex; // Index of MFD panel open - 255 = MFD closed + // Single player, race - 0 = Car setup, 1 = Pits + // 2 = Damage, 3 = Engine, 4 = Temperatures + // May vary depending on game mode + public byte m_mfdPanelIndexSecondaryPlayer; // See above + public sbyte m_suggestedGear; // Suggested gear for the player (1-8) + // 0 if no gear suggested + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketEventData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketEventData.cs new file mode 100644 index 0000000..89ddf12 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketEventData.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Event packet for F1 2023. Details of events that happen during a session. + /// Frequency: When the event occurs + /// Size: 45 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketEventData + { + public PacketHeader m_header; // Header + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_eventStringCode; // Event string code, see below + + public EventDataDetails m_eventDetails; // Event details - should be interpreted differently for each type + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketFinalClassificationData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketFinalClassificationData.cs new file mode 100644 index 0000000..67d08d5 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketFinalClassificationData.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Final classification packet for F1 2023. Final classification at the end of the race. + /// Frequency: Once at the end of a race + /// Size: 1020 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketFinalClassificationData + { + public PacketHeader m_header; // Header + + public byte m_numCars; // Number of cars in the final classification + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public FinalClassificationData[] m_classificationData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketHeader.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketHeader.cs new file mode 100644 index 0000000..49faf1b --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketHeader.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketHeader + { + public ushort m_packetFormat; // 2023 + public byte m_gameYear; // Game year - last two digits e.g. 23 + public byte m_gameMajorVersion; // Game major version - "X.00" + public byte m_gameMinorVersion; // Game minor version - "1.XX" + public byte m_packetVersion; // Version of this packet type, all start from 1 + public byte m_packetId; // Identifier for the packet type, see below + public ulong m_sessionUID; // Unique identifier for the session + public float m_sessionTime; // Session timestamp + public uint m_frameIdentifier; // Identifier for the frame the data was retrieved on + public uint m_overallFrameIdentifier; // Overall identifier for the frame the data was retrieved on, doesn't go back after flashbacks + public byte m_playerCarIndex; // Index of player's car in the array + public byte m_secondaryPlayerCarIndex; // Index of secondary player's car in the array (splitscreen) - 255 if no second player + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketLapData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketLapData.cs new file mode 100644 index 0000000..a92b591 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketLapData.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Lap data packet for F1 2023. Details of all cars in the session. + /// Frequency: Rate as specified in menus + /// Size: 1131 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLapData + { + public PacketHeader m_header; // Header + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public LapData[] m_lapData; // Lap data for all cars on track + + public byte m_timeTrialPBCarIdx; // Index of Personal Best car in time trial (255 if invalid) + public byte m_timeTrialRivalCarIdx; // Index of Rival car in time trial (255 if invalid) + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketLobbyInfoData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketLobbyInfoData.cs new file mode 100644 index 0000000..75a13c9 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketLobbyInfoData.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Lobby info packet for F1 2023. Players currently in a multiplayer lobby. + /// Frequency: Two every second when in the lobby + /// Size: 1218 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLobbyInfoData + { + public PacketHeader m_header; // Header + + public byte m_numPlayers; // Number of players in the lobby data + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public LobbyInfoData[] m_lobbyPlayers; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketMotionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketMotionData.cs new file mode 100644 index 0000000..a0e5583 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketMotionData.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Motion packet for F1 2023. Contains physics data for all cars. + /// Frequency: Rate as specified in menus + /// Size: 1349 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketMotionData + { + public PacketHeader m_header; // Header + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarMotionData[] m_carMotionData; // Data for all cars on track + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketMotionExData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketMotionExData.cs new file mode 100644 index 0000000..08ad8b7 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketMotionExData.cs @@ -0,0 +1,58 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Motion Ex packet for F1 2023. Extended motion data for the car being driven. + /// Frequency: Rate as specified in menus + /// Size: 217 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketMotionExData + { + public PacketHeader m_header; // Header + + // Extra player car ONLY data + // Note: All wheel arrays have the following order: RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionPosition; // RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionVelocity; // RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionAcceleration; // RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSpeed; // Speed of each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSlipRatio; // Slip ratio for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSlipAngle; // Slip angles for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelLatForce; // Lateral forces for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelLongForce; // Longitudinal forces for each wheel + + public float m_heightOfCOGAboveGround; // Height of centre of gravity above ground + public float m_localVelocityX; // Velocity in local space - metres/s + public float m_localVelocityY; // Velocity in local space + public float m_localVelocityZ; // Velocity in local space + public float m_angularVelocityX; // Angular velocity x-component - radians/s + public float m_angularVelocityY; // Angular velocity y-component + public float m_angularVelocityZ; // Angular velocity z-component + public float m_angularAccelerationX; // Angular acceleration x-component - radians/s/s + public float m_angularAccelerationY; // Angular acceleration y-component + public float m_angularAccelerationZ; // Angular acceleration z-component + public float m_frontWheelsAngle; // Current front wheels angle in radians + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelVertForce; // Vertical forces for each wheel + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketParticipantsData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketParticipantsData.cs new file mode 100644 index 0000000..a4ad388 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketParticipantsData.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Participants packet for F1 2023. List of participants in the race. + /// Frequency: Every 5 seconds + /// Size: 1306 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketParticipantsData + { + public PacketHeader m_header; // Header + + public byte m_numActiveCars; // Number of active cars in the data - should match number of cars on HUD + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ParticipantData[] m_participants; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketSessionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketSessionData.cs new file mode 100644 index 0000000..eb316b5 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketSessionData.cs @@ -0,0 +1,79 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Session packet for F1 2023. Details about the current session. + /// Frequency: 2 per second + /// Size: 644 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketSessionData + { + public PacketHeader m_header; // Header + + public byte m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast + // 3 = light rain, 4 = heavy rain, 5 = storm + public sbyte m_trackTemperature; // Track temp. in degrees celsius + public sbyte m_airTemperature; // Air temp. in degrees celsius + public byte m_totalLaps; // Total number of laps in this race + public ushort m_trackLength; // Track length in metres + public byte m_sessionType; // 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P + // 5 = Q1, 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ + // 10 = R, 11 = R2, 12 = R3, 13 = Time Trial + public sbyte m_trackId; // -1 for unknown, see appendix + public byte m_formula; // Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2, + // 3 = F1 Generic, 4 = Beta, 5 = Supercars + // 6 = Esports, 7 = F2 2021 + public ushort m_sessionTimeLeft; // Time left in session in seconds + public ushort m_sessionDuration; // Session duration in seconds + public byte m_pitSpeedLimit; // Pit speed limit in kilometres per hour + public byte m_gamePaused; // Whether the game is paused - network game only + public byte m_isSpectating; // Whether the player is spectating + public byte m_spectatorCarIndex; // Index of the car being spectated + public byte m_sliProNativeSupport; // SLI Pro support, 0 = inactive, 1 = active + public byte m_numMarshalZones; // Number of marshal zones to follow + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 21)] + public MarshalZone[] m_marshalZones; // List of marshal zones - max 21 + + public byte m_safetyCarStatus; // 0 = no safety car, 1 = full + // 2 = virtual, 3 = formation lap + public byte m_networkGame; // 0 = offline, 1 = online + public byte m_numWeatherForecastSamples; // Number of weather samples to follow + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 56)] + public WeatherForecastSample[] m_weatherForecastSamples; // Array of weather forecast samples + + public byte m_forecastAccuracy; // 0 = Perfect, 1 = Approximate + public byte m_aiDifficulty; // AI Difficulty rating - 0-110 + public uint m_seasonLinkIdentifier; // Identifier for season - persists across saves + public uint m_weekendLinkIdentifier; // Identifier for weekend - persists across saves + public uint m_sessionLinkIdentifier; // Identifier for session - persists across saves + public byte m_pitStopWindowIdealLap; // Ideal lap to pit on for current strategy (player) + public byte m_pitStopWindowLatestLap; // Latest lap to pit on for current strategy (player) + public byte m_pitStopRejoinPosition; // Predicted position to rejoin at (player) + public byte m_steeringAssist; // 0 = off, 1 = on + public byte m_brakingAssist; // 0 = off, 1 = low, 2 = medium, 3 = high + public byte m_gearboxAssist; // 1 = manual, 2 = manual & suggested gear, 3 = auto + public byte m_pitAssist; // 0 = off, 1 = on + public byte m_pitReleaseAssist; // 0 = off, 1 = on + public byte m_ERSAssist; // 0 = off, 1 = on + public byte m_DRSAssist; // 0 = off, 1 = on + public byte m_dynamicRacingLine; // 0 = off, 1 = corners only, 2 = full + public byte m_dynamicRacingLineType; // 0 = 2D, 1 = 3D + public byte m_gameMode; // Game mode id - see appendix + public byte m_ruleSet; // Ruleset - see appendix + public uint m_timeOfDay; // Local time of day - minutes since midnight + public byte m_sessionLength; // 0 = None, 2 = Very Short, 3 = Short, 4 = Medium + // 5 = Medium Long, 6 = Long, 7 = Full + public byte m_speedUnitsLeadPlayer; // 0 = MPH, 1 = KPH + public byte m_temperatureUnitsLeadPlayer; // 0 = Celsius, 1 = Fahrenheit + public byte m_speedUnitsSecondaryPlayer; // 0 = MPH, 1 = KPH + public byte m_temperatureUnitsSecondaryPlayer; // 0 = Celsius, 1 = Fahrenheit + public byte m_numSafetyCarPeriods; // Number of safety cars called during session + public byte m_numVirtualSafetyCarPeriods; // Number of virtual safety cars called + public byte m_numRedFlagPeriods; // Number of red flags called during session + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketSessionHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketSessionHistoryData.cs new file mode 100644 index 0000000..6e819bb --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketSessionHistoryData.cs @@ -0,0 +1,31 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Session history packet for F1 2023. Lap times and tyre usage for the session. + /// Frequency: 20 per second but cycling through cars + /// Size: 1460 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketSessionHistoryData + { + public PacketHeader m_header; // Header + + public byte m_carIdx; // Index of the car this lap data relates to + public byte m_numLaps; // Num laps in the data (including current partial lap) + public byte m_numTyreStints; // Number of tyre stints in the data + + public byte m_bestLapTimeLapNum; // Lap the best lap time was achieved on + public byte m_bestSector1LapNum; // Lap the best Sector 1 time was achieved on + public byte m_bestSector2LapNum; // Lap the best Sector 2 time was achieved on + public byte m_bestSector3LapNum; // Lap the best Sector 3 time was achieved on + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)] + public LapHistoryData[] m_lapHistoryData; // 100 laps of data max + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public TyreStintHistoryData[] m_tyreStintsHistoryData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketTyreSetsData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketTyreSetsData.cs new file mode 100644 index 0000000..3cbb21f --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketTyreSetsData.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Tyre sets packet for F1 2023. In-depth details about tyre sets assigned to a vehicle. + /// Frequency: 20 per second but cycling through cars + /// Size: 231 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketTyreSetsData + { + public PacketHeader m_header; // Header + + public byte m_carIdx; // Index of the car this data relates to + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public TyreSetData[] m_tyreSetData; // 13 (dry) + 7 (wet) + + public byte m_fittedIdx; // Index into array of fitted tyre + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/ParticipantData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/ParticipantData.cs new file mode 100644 index 0000000..14aad8c --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/ParticipantData.cs @@ -0,0 +1,24 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ParticipantData + { + public byte m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + public byte m_driverId; // Driver id - see appendix, 255 if network human + public byte m_networkId; // Network id - unique identifier for network players + public byte m_teamId; // Team id - see appendix + public byte m_myTeam; // My team flag - 1 = My Team, 0 = otherwise + public byte m_raceNumber; // Race number of the car + public byte m_nationality; // Nationality of the driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] + public byte[] m_name; // Name of participant in UTF-8 format - null terminated + // Will be truncated with … (U+2026) if too long + + public byte m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + public byte m_showOnlineNames; // The player's show online names setting, 0 = off, 1 = on + public byte m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/TyreSetData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/TyreSetData.cs new file mode 100644 index 0000000..35e2aa3 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/TyreSetData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TyreSetData + { + public byte m_actualTyreCompound; // Actual tyre compound used + public byte m_visualTyreCompound; // Visual tyre compound used + public byte m_wear; // Tyre wear (percentage) + public byte m_available; // Whether this set is currently available + public byte m_recommendedSession; // Recommended session for tyre set + public byte m_lifeSpan; // Laps left in this tyre set + public byte m_usableLife; // Max number of laps recommended for this compound + public short m_lapDeltaTime; // Lap delta time in milliseconds compared to fitted set + public byte m_fitted; // Whether the set is fitted or not + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/TyreStintHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/TyreStintHistoryData.cs new file mode 100644 index 0000000..6ed79d2 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/TyreStintHistoryData.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TyreStintHistoryData + { + public byte m_endLap; // Lap the tyre usage ends on (255 of current tyre) + public byte m_tyreActualCompound; // Actual tyres used by this driver + public byte m_tyreVisualCompound; // Visual tyres used by this driver + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/WeatherForecastSample.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/WeatherForecastSample.cs new file mode 100644 index 0000000..751c3ec --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/WeatherForecastSample.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct WeatherForecastSample + { + public byte m_sessionType; // 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P, 5 = Q1 + // 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ, 10 = R, 11 = R2 + // 12 = R3, 13 = Time Trial + public byte m_timeOffset; // Time in minutes the forecast is for + public byte m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast + // 3 = light rain, 4 = heavy rain, 5 = storm + public sbyte m_trackTemperature; // Track temp. in degrees Celsius + public sbyte m_trackTemperatureChange; // Track temp. change - 0 = up, 1 = down, 2 = no change + public sbyte m_airTemperature; // Air temp. in degrees celsius + public sbyte m_airTemperatureChange; // Air temp. change - 0 = up, 1 = down, 2 = no change + public byte m_rainPercentage; // Rain percentage (0-100) + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs b/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs index 5962cb5..e5ffbb2 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs @@ -1,3 +1,4 @@ +using GamesDat.Core.Telemetry.Sources.Formula1.F12023; using GamesDat.Core.Telemetry.Sources.Formula1.F12024; using GamesDat.Core.Telemetry.Sources.Formula1.F12025; @@ -7,6 +8,22 @@ internal static class F1PacketTypeMapper { private static readonly Dictionary<(ushort format, byte id), Type> _packetTypeMap = new() { + // F1 2023 + [(2023, (byte)PacketId.Motion)] = typeof(F12023.PacketMotionData), + [(2023, (byte)PacketId.Session)] = typeof(F12023.PacketSessionData), + [(2023, (byte)PacketId.LapData)] = typeof(F12023.PacketLapData), + [(2023, (byte)PacketId.Event)] = typeof(F12023.PacketEventData), + [(2023, (byte)PacketId.Participants)] = typeof(F12023.PacketParticipantsData), + [(2023, (byte)PacketId.CarSetups)] = typeof(F12023.PacketCarSetupData), + [(2023, (byte)PacketId.CarTelemetry)] = typeof(F12023.PacketCarTelemetryData), + [(2023, (byte)PacketId.CarStatus)] = typeof(F12023.PacketCarStatusData), + [(2023, (byte)PacketId.FinalClassification)] = typeof(F12023.PacketFinalClassificationData), + [(2023, (byte)PacketId.LobbyInfo)] = typeof(F12023.PacketLobbyInfoData), + [(2023, (byte)PacketId.CarDamage)] = typeof(F12023.PacketCarDamageData), + [(2023, (byte)PacketId.SessionHistory)] = typeof(F12023.PacketSessionHistoryData), + [(2023, (byte)PacketId.TyreSets)] = typeof(F12023.PacketTyreSetsData), + [(2023, (byte)PacketId.MotionEx)] = typeof(F12023.PacketMotionExData), + // F1 2024 [(2024, (byte)PacketId.Motion)] = typeof(F12024.PacketMotionData), [(2024, (byte)PacketId.Session)] = typeof(F12024.PacketSessionData), diff --git a/GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2023_spec.h b/GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2023_spec.h new file mode 100644 index 0000000..5dca29d --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2023_spec.h @@ -0,0 +1,1342 @@ +Packet Information + +Packet Types + +Each packet carries different types of data rather than having one packet which contains everything.The header in each packet describes the packet type and versioning info so it will be easier for applications to check they are interpreting the incoming data in the correct way.Please note that all values are encoded using Little Endian format.All data is packed. + +The following data types are used in the structures : + +Type Description +uint8 Unsigned 8 - bit integer +int8 Signed 8 - bit integer +uint16 Unsigned 16 - bit integer +int16 Signed 16 - bit integer +uint32 Unsigned 32 - bit integer +float Floating point(32 - bit) +Double Double - precision floating point(64 - bit) +uint64 Unsigned 64 - bit integer +char Character + + +Packet Header + +Each packet has the following header : + +struct PacketHeader +{ + uint16 m_packetFormat; // 2023 + uint8 m_gameYear; // Game year - last two digits e.g. 23 + uint8 m_gameMajorVersion; // Game major version - "X.00" + uint8 m_gameMinorVersion; // Game minor version - "1.XX" + uint8 m_packetVersion; // Version of this packet type, all start from 1 + uint8 m_packetId; // Identifier for the packet type, see below + uint64 m_sessionUID; // Unique identifier for the session + float m_sessionTime; // Session timestamp + uint32 m_frameIdentifier; // Identifier for the frame the data was retrieved on + uint32 m_overallFrameIdentifier; // Overall identifier for the frame the data was retrieved + // on, doesn't go back after flashbacks + uint8 m_playerCarIndex; // Index of player's car in the array + uint8 m_secondaryPlayerCarIndex; // Index of secondary player's car in the array (splitscreen) + // 255 if no second player +}; + + +Packet IDs + +The packets IDs are as follows : + +Packet Name Value Description +Motion 0 Contains all motion data for player�s car � only sent while player is in control +Session 1 Data about the session � track, time left +Lap Data 2 Data about all the lap times of cars in the session +Event 3 Various notable events that happen during a session +Participants 4 List of participants in the session, mostly relevant for multiplayer +Car Setups 5 Packet detailing car setups for cars in the race +Car Telemetry 6 Telemetry data for all cars +Car Status 7 Status data for all cars +Final Classification 8 Final classification confirmation at the end of a race +Lobby Info 9 Information about players in a multiplayer lobby +Car Damage 10 Damage status for all cars +Session History 11 Lap and tyre data for session +Tyre Sets 12 Extended tyre set data +Motion Ex 13 Extended motion data for player car + + +Motion Packet + +The motion packet gives physics data for all the cars being driven. +N.B.For the normalised vectors below, to convert to float values divide by 32767.0f � 16 - bit signed values are used to pack the data and on the assumption that direction values are always between - 1.0f and 1.0f. + +Frequency: Rate as specified in menus +Size : 1349 bytes +Version : 1 + +struct CarMotionData +{ + float m_worldPositionX; // World space X position - metres + float m_worldPositionY; // World space Y position + float m_worldPositionZ; // World space Z position + float m_worldVelocityX; // Velocity in world space X � metres/s + float m_worldVelocityY; // Velocity in world space Y + float m_worldVelocityZ; // Velocity in world space Z + int16 m_worldForwardDirX; // World space forward X direction (normalised) + int16 m_worldForwardDirY; // World space forward Y direction (normalised) + int16 m_worldForwardDirZ; // World space forward Z direction (normalised) + int16 m_worldRightDirX; // World space right X direction (normalised) + int16 m_worldRightDirY; // World space right Y direction (normalised) + int16 m_worldRightDirZ; // World space right Z direction (normalised) + float m_gForceLateral; // Lateral G-Force component + float m_gForceLongitudinal; // Longitudinal G-Force component + float m_gForceVertical; // Vertical G-Force component + float m_yaw; // Yaw angle in radians + float m_pitch; // Pitch angle in radians + float m_roll; // Roll angle in radians +}; + +struct PacketMotionData +{ + PacketHeader m_header; // Header + + CarMotionData m_carMotionData[22]; // Data for all cars on track +}; + + +Session Packet + +The session packet includes details about the current session in progress. + +Frequency: 2 per second +Size : 644 bytes +Version : 1 + +struct MarshalZone +{ + float m_zoneStart; // Fraction (0..1) of way through the lap the marshal zone starts + int8 m_zoneFlag; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow +}; + +struct WeatherForecastSample +{ + uint8 m_sessionType; // 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P, 5 = Q1 + // 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ, 10 = R, 11 = R2 + // 12 = R3, 13 = Time Trial + uint8 m_timeOffset; // Time in minutes the forecast is for + uint8 m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast + // 3 = light rain, 4 = heavy rain, 5 = storm + int8 m_trackTemperature; // Track temp. in degrees Celsius + int8 m_trackTemperatureChange; // Track temp. change � 0 = up, 1 = down, 2 = no change + int8 m_airTemperature; // Air temp. in degrees celsius + int8 m_airTemperatureChange; // Air temp. change � 0 = up, 1 = down, 2 = no change + uint8 m_rainPercentage; // Rain percentage (0-100) +}; + +struct PacketSessionData +{ + PacketHeader m_header; // Header + + uint8 m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast + // 3 = light rain, 4 = heavy rain, 5 = storm + int8 m_trackTemperature; // Track temp. in degrees celsius + int8 m_airTemperature; // Air temp. in degrees celsius + uint8 m_totalLaps; // Total number of laps in this race + uint16 m_trackLength; // Track length in metres + uint8 m_sessionType; // 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P + // 5 = Q1, 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ + // 10 = R, 11 = R2, 12 = R3, 13 = Time Trial + int8 m_trackId; // -1 for unknown, see appendix + uint8 m_formula; // Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2, + // 3 = F1 Generic, 4 = Beta, 5 = Supercars +// 6 = Esports, 7 = F2 2021 + uint16 m_sessionTimeLeft; // Time left in session in seconds + uint16 m_sessionDuration; // Session duration in seconds + uint8 m_pitSpeedLimit; // Pit speed limit in kilometres per hour + uint8 m_gamePaused; // Whether the game is paused � network game only + uint8 m_isSpectating; // Whether the player is spectating + uint8 m_spectatorCarIndex; // Index of the car being spectated + uint8 m_sliProNativeSupport; // SLI Pro support, 0 = inactive, 1 = active + uint8 m_numMarshalZones; // Number of marshal zones to follow + MarshalZone m_marshalZones[21]; // List of marshal zones � max 21 + uint8 m_safetyCarStatus; // 0 = no safety car, 1 = full + // 2 = virtual, 3 = formation lap + uint8 m_networkGame; // 0 = offline, 1 = online + uint8 m_numWeatherForecastSamples; // Number of weather samples to follow + WeatherForecastSample m_weatherForecastSamples[56]; // Array of weather forecast samples + uint8 m_forecastAccuracy; // 0 = Perfect, 1 = Approximate + uint8 m_aiDifficulty; // AI Difficulty rating � 0-110 + uint32 m_seasonLinkIdentifier; // Identifier for season - persists across saves + uint32 m_weekendLinkIdentifier; // Identifier for weekend - persists across saves + uint32 m_sessionLinkIdentifier; // Identifier for session - persists across saves + uint8 m_pitStopWindowIdealLap; // Ideal lap to pit on for current strategy (player) + uint8 m_pitStopWindowLatestLap; // Latest lap to pit on for current strategy (player) + uint8 m_pitStopRejoinPosition; // Predicted position to rejoin at (player) + uint8 m_steeringAssist; // 0 = off, 1 = on + uint8 m_brakingAssist; // 0 = off, 1 = low, 2 = medium, 3 = high + uint8 m_gearboxAssist; // 1 = manual, 2 = manual & suggested gear, 3 = auto + uint8 m_pitAssist; // 0 = off, 1 = on + uint8 m_pitReleaseAssist; // 0 = off, 1 = on + uint8 m_ERSAssist; // 0 = off, 1 = on + uint8 m_DRSAssist; // 0 = off, 1 = on + uint8 m_dynamicRacingLine; // 0 = off, 1 = corners only, 2 = full + uint8 m_dynamicRacingLineType; // 0 = 2D, 1 = 3D + uint8 m_gameMode; // Game mode id - see appendix + uint8 m_ruleSet; // Ruleset - see appendix + uint32 m_timeOfDay; // Local time of day - minutes since midnight + uint8 m_sessionLength; // 0 = None, 2 = Very Short, 3 = Short, 4 = Medium + // 5 = Medium Long, 6 = Long, 7 = Full + uint8 m_speedUnitsLeadPlayer; // 0 = MPH, 1 = KPH + uint8 m_temperatureUnitsLeadPlayer; // 0 = Celsius, 1 = Fahrenheit + uint8 m_speedUnitsSecondaryPlayer; // 0 = MPH, 1 = KPH + uint8 m_temperatureUnitsSecondaryPlayer; // 0 = Celsius, 1 = Fahrenheit + uint8 m_numSafetyCarPeriods; // Number of safety cars called during session + uint8 m_numVirtualSafetyCarPeriods; // Number of virtual safety cars called + uint8 m_numRedFlagPeriods; // Number of red flags called during session +}; + + +Lap Data Packet + +The lap data packet gives details of all the cars in the session. + +Frequency: Rate as specified in menus +Size : 1131 bytes +Version : 1 + +struct LapData +{ + uint32 m_lastLapTimeInMS; // Last lap time in milliseconds + uint32 m_currentLapTimeInMS; // Current time around the lap in milliseconds + uint16 m_sector1TimeInMS; // Sector 1 time in milliseconds + uint8 m_sector1TimeMinutes; // Sector 1 whole minute part + uint16 m_sector2TimeInMS; // Sector 2 time in milliseconds + uint8 m_sector2TimeMinutes; // Sector 2 whole minute part + uint16 m_deltaToCarInFrontInMS; // Time delta to car in front in milliseconds + uint16 m_deltaToRaceLeaderInMS; // Time delta to race leader in milliseconds + float m_lapDistance; // Distance vehicle is around current lap in metres � could + // be negative if line hasn�t been crossed yet + float m_totalDistance; // Total distance travelled in session in metres � could + // be negative if line hasn�t been crossed yet + float m_safetyCarDelta; // Delta in seconds for safety car + uint8 m_carPosition; // Car race position + uint8 m_currentLapNum; // Current lap number + uint8 m_pitStatus; // 0 = none, 1 = pitting, 2 = in pit area + uint8 m_numPitStops; // Number of pit stops taken in this race + uint8 m_sector; // 0 = sector1, 1 = sector2, 2 = sector3 + uint8 m_currentLapInvalid; // Current lap invalid - 0 = valid, 1 = invalid + uint8 m_penalties; // Accumulated time penalties in seconds to be added + uint8 m_totalWarnings; // Accumulated number of warnings issued + uint8 m_cornerCuttingWarnings; // Accumulated number of corner cutting warnings issued + uint8 m_numUnservedDriveThroughPens; // Num drive through pens left to serve + uint8 m_numUnservedStopGoPens; // Num stop go pens left to serve + uint8 m_gridPosition; // Grid position the vehicle started the race in + uint8 m_driverStatus; // Status of driver - 0 = in garage, 1 = flying lap + // 2 = in lap, 3 = out lap, 4 = on track + uint8 m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active + // 3 = finished, 4 = didnotfinish, 5 = disqualified + // 6 = not classified, 7 = retired + uint8 m_pitLaneTimerActive; // Pit lane timing, 0 = inactive, 1 = active + uint16 m_pitLaneTimeInLaneInMS; // If active, the current time spent in the pit lane in ms + uint16 m_pitStopTimerInMS; // Time of the actual pit stop in ms + uint8 m_pitStopShouldServePen; // Whether the car should serve a penalty at this stop +}; + + +struct PacketLapData +{ + PacketHeader m_header; // Header + + LapData m_lapData[22]; // Lap data for all cars on track + + uint8 m_timeTrialPBCarIdx; // Index of Personal Best car in time trial (255 if invalid) + uint8 m_timeTrialRivalCarIdx; // Index of Rival car in time trial (255 if invalid) +}; + + +Event Packet + +This packet gives details of events that happen during the course of a session. + +Frequency: When the event occurs +Size : 45 bytes +Version : 1 + +// The event details packet is different for each type of event. +// Make sure only the correct type is interpreted. +union EventDataDetails +{ + struct + { + uint8 vehicleIdx; // Vehicle index of car achieving fastest lap + float lapTime; // Lap time is in seconds + } FastestLap; + + struct + { + uint8 vehicleIdx; // Vehicle index of car retiring + } Retirement; + + struct + { + uint8 vehicleIdx; // Vehicle index of team mate + } TeamMateInPits; + + struct + { + uint8 vehicleIdx; // Vehicle index of the race winner + } RaceWinner; + + struct + { + uint8 penaltyType; // Penalty type � see Appendices + uint8 infringementType; // Infringement type � see Appendices + uint8 vehicleIdx; // Vehicle index of the car the penalty is applied to + uint8 otherVehicleIdx; // Vehicle index of the other car involved + uint8 time; // Time gained, or time spent doing action in seconds + uint8 lapNum; // Lap the penalty occurred on + uint8 placesGained; // Number of places gained by this + } Penalty; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle triggering speed trap + float speed; // Top speed achieved in kilometres per hour + uint8 isOverallFastestInSession; // Overall fastest speed in session = 1, otherwise 0 + uint8 isDriverFastestInSession; // Fastest speed for driver in session = 1, otherwise 0 + uint8 fastestVehicleIdxInSession;// Vehicle index of the vehicle that is the fastest + // in this session + float fastestSpeedInSession; // Speed of the vehicle that is the fastest + // in this session + } SpeedTrap; + + struct + { + uint8 numLights; // Number of lights showing + } StartLIghts; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle serving drive through + } DriveThroughPenaltyServed; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle serving stop go + } StopGoPenaltyServed; + + struct + { + uint32 flashbackFrameIdentifier; // Frame identifier flashed back to + float flashbackSessionTime; // Session time flashed back to + } Flashback; + + struct + { + uint32 buttonStatus; // Bit flags specifying which buttons are being pressed + // currently - see appendices + } Buttons; + + struct + { + uint8 overtakingVehicleIdx; // Vehicle index of the vehicle overtaking + uint8 beingOvertakenVehicleIdx; // Vehicle index of the vehicle being overtaken + } Overtake; +}; + +struct PacketEventData +{ + PacketHeader m_header; // Header + + uint8 m_eventStringCode[4]; // Event string code, see below + EventDataDetails m_eventDetails; // Event details - should be interpreted differently + // for each type +}; + + +Event String Codes + +Event Code Description +Session Started �SSTA� Sent when the session starts +Session Ended �SEND� Sent when the session ends +Fastest Lap �FTLP� When a driver achieves the fastest lap +Retirement �RTMT� When a driver retires +DRS enabled �DRSE� Race control have enabled DRS +DRS disabled �DRSD� Race control have disabled DRS +Team mate in pits �TMPT� Your team mate has entered the pits +Chequered flag �CHQF� The chequered flag has been waved +Race Winner �RCWN� The race winner is announced +Penalty Issued �PENA� A penalty has been issued � details in event +Speed Trap Triggered �SPTP� Speed trap has been triggered by fastest speed +Start lights �STLG� Start lights � number shown +Lights out �LGOT� Lights out +Drive through served �DTSV� Drive through penalty served +Stop go served �SGSV� Stop go penalty served +Flashback �FLBK� Flashback activated +Button status �BUTN� Button status changed +Red Flag �RDFL� Red flag shown +Overtake �OVTK� Overtake occurred + + +Participants Packet + +This is a list of participants in the race.If the vehicle is controlled by AI, then the name will be the driver name.If this is a multiplayer game, the names will be the Steam Id on PC, or the LAN name if appropriate. + +N.B.on Xbox One, the names will always be the driver name, on PS4 the name will be the LAN name if playing a LAN game, otherwise it will be the driver name. + +The array should be indexed by vehicle index. + +Frequency: Every 5 seconds +Size : 1306 bytes +Version : 1 + +struct ParticipantData +{ + uint8 m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + uint8 m_driverId; // Driver id - see appendix, 255 if network human + uint8 m_networkId; // Network id � unique identifier for network players + uint8 m_teamId; // Team id - see appendix + uint8 m_myTeam; // My team flag � 1 = My Team, 0 = otherwise + uint8 m_raceNumber; // Race number of the car + uint8 m_nationality; // Nationality of the driver + char m_name[48]; // Name of participant in UTF-8 format � null terminated + // Will be truncated with � (U+2026) if too long + uint8 m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + uint8 m_showOnlineNames; // The player's show online names setting, 0 = off, 1 = on + uint8 m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown +}; + +struct PacketParticipantsData +{ + PacketHeader m_header; // Header + + uint8 m_numActiveCars; // Number of active cars in the data � should match number of + // cars on HUD + ParticipantData m_participants[22]; +}; + + +Car Setups Packet + +This packet details the car setups for each vehicle in the session.Note that in multiplayer games, other player cars will appear as blank, you will only be able to see your own car setup, regardless of the �Your Telemetry� setting.Spectators will also not be able to see any car setups. + +Frequency: 2 per second +Size : 1107 bytes +Version : 1 + +struct CarSetupData +{ + uint8 m_frontWing; // Front wing aero + uint8 m_rearWing; // Rear wing aero + uint8 m_onThrottle; // Differential adjustment on throttle (percentage) + uint8 m_offThrottle; // Differential adjustment off throttle (percentage) + float m_frontCamber; // Front camber angle (suspension geometry) + float m_rearCamber; // Rear camber angle (suspension geometry) + float m_frontToe; // Front toe angle (suspension geometry) + float m_rearToe; // Rear toe angle (suspension geometry) + uint8 m_frontSuspension; // Front suspension + uint8 m_rearSuspension; // Rear suspension + uint8 m_frontAntiRollBar; // Front anti-roll bar + uint8 m_rearAntiRollBar; // Front anti-roll bar + uint8 m_frontSuspensionHeight; // Front ride height + uint8 m_rearSuspensionHeight; // Rear ride height + uint8 m_brakePressure; // Brake pressure (percentage) + uint8 m_brakeBias; // Brake bias (percentage) + float m_rearLeftTyrePressure; // Rear left tyre pressure (PSI) + float m_rearRightTyrePressure; // Rear right tyre pressure (PSI) + float m_frontLeftTyrePressure; // Front left tyre pressure (PSI) + float m_frontRightTyrePressure; // Front right tyre pressure (PSI) + uint8 m_ballast; // Ballast + float m_fuelLoad; // Fuel load +}; + +struct PacketCarSetupData +{ + PacketHeader m_header; // Header + + CarSetupData m_carSetups[22]; +}; + + +Car Telemetry Packet + +This packet details telemetry for all the cars in the race.It details various values that would be recorded on the car such as speed, throttle application, DRS etc.Note that the rev light configurations are presented separately as well and will mimic real life driver preferences. + +Frequency: Rate as specified in menus +Size : 1352 bytes +Version : 1 + +struct CarTelemetryData +{ + uint16 m_speed; // Speed of car in kilometres per hour + float m_throttle; // Amount of throttle applied (0.0 to 1.0) + float m_steer; // Steering (-1.0 (full lock left) to 1.0 (full lock right)) + float m_brake; // Amount of brake applied (0.0 to 1.0) + uint8 m_clutch; // Amount of clutch applied (0 to 100) + int8 m_gear; // Gear selected (1-8, N=0, R=-1) + uint16 m_engineRPM; // Engine RPM + uint8 m_drs; // 0 = off, 1 = on + uint8 m_revLightsPercent; // Rev lights indicator (percentage) + uint16 m_revLightsBitValue; // Rev lights (bit 0 = leftmost LED, bit 14 = rightmost LED) + uint16 m_brakesTemperature[4]; // Brakes temperature (celsius) + uint8 m_tyresSurfaceTemperature[4]; // Tyres surface temperature (celsius) + uint8 m_tyresInnerTemperature[4]; // Tyres inner temperature (celsius) + uint16 m_engineTemperature; // Engine temperature (celsius) + float m_tyresPressure[4]; // Tyres pressure (PSI) + uint8 m_surfaceType[4]; // Driving surface, see appendices +}; + +struct PacketCarTelemetryData +{ + PacketHeader m_header; // Header + + CarTelemetryData m_carTelemetryData[22]; + + uint8 m_mfdPanelIndex; // Index of MFD panel open - 255 = MFD closed + // Single player, race � 0 = Car setup, 1 = Pits + // 2 = Damage, 3 = Engine, 4 = Temperatures + // May vary depending on game mode + uint8 m_mfdPanelIndexSecondaryPlayer; // See above + int8 m_suggestedGear; // Suggested gear for the player (1-8) + // 0 if no gear suggested +}; + + +Car Status Packet + +This packet details car statuses for all the cars in the race. + +Frequency: Rate as specified in menus +Size : 1239 bytes +Version : 1 + +struct CarStatusData +{ + uint8 m_tractionControl; // Traction control - 0 = off, 1 = medium, 2 = full + uint8 m_antiLockBrakes; // 0 (off) - 1 (on) + uint8 m_fuelMix; // Fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max + uint8 m_frontBrakeBias; // Front brake bias (percentage) + uint8 m_pitLimiterStatus; // Pit limiter status - 0 = off, 1 = on + float m_fuelInTank; // Current fuel mass + float m_fuelCapacity; // Fuel capacity + float m_fuelRemainingLaps; // Fuel remaining in terms of laps (value on MFD) + uint16 m_maxRPM; // Cars max RPM, point of rev limiter + uint16 m_idleRPM; // Cars idle RPM + uint8 m_maxGears; // Maximum number of gears + uint8 m_drsAllowed; // 0 = not allowed, 1 = allowed + uint16 m_drsActivationDistance; // 0 = DRS not available, non-zero - DRS will be available + // in [X] metres + uint8 m_actualTyreCompound; // F1 Modern - 16 = C5, 17 = C4, 18 = C3, 19 = C2, 20 = C1 + // 21 = C0, 7 = inter, 8 = wet + // F1 Classic - 9 = dry, 10 = wet + // F2 � 11 = super soft, 12 = soft, 13 = medium, 14 = hard + // 15 = wet + uint8 m_visualTyreCompound; // F1 visual (can be different from actual compound) + // 16 = soft, 17 = medium, 18 = hard, 7 = inter, 8 = wet + // F1 Classic � same as above + // F2 �19, 15 = wet, 19 � super soft, 20 = soft + // 21 = medium , 22 = hard + uint8 m_tyresAgeLaps; // Age in laps of the current set of tyres + int8 m_vehicleFiaFlags; // -1 = invalid/unknown, 0 = none, 1 = green + // 2 = blue, 3 = yellow + float m_enginePowerICE; // Engine power output of ICE (W) + float m_enginePowerMGUK; // Engine power output of MGU-K (W) + float m_ersStoreEnergy; // ERS energy store in Joules + uint8 m_ersDeployMode; // ERS deployment mode, 0 = none, 1 = medium + // 2 = hotlap, 3 = overtake + float m_ersHarvestedThisLapMGUK; // ERS energy harvested this lap by MGU-K + float m_ersHarvestedThisLapMGUH; // ERS energy harvested this lap by MGU-H + float m_ersDeployedThisLap; // ERS energy deployed this lap + uint8 m_networkPaused; // Whether the car is paused in a network game +}; + +struct PacketCarStatusData +{ + PacketHeader m_header; // Header + + CarStatusData m_carStatusData[22]; +}; + +Final Classification Packet + +This packet details the final classification at the end of the race, and the data will match with the post race results screen.This is especially useful for multiplayer games where it is not always possible to send lap times on the final frame because of network delay. + +Frequency: Once at the end of a race +Size : 1020 bytes +Version : 1 + +struct FinalClassificationData +{ + uint8 m_position; // Finishing position + uint8 m_numLaps; // Number of laps completed + uint8 m_gridPosition; // Grid position of the car + uint8 m_points; // Number of points scored + uint8 m_numPitStops; // Number of pit stops made + uint8 m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active + // 3 = finished, 4 = didnotfinish, 5 = disqualified + // 6 = not classified, 7 = retired + uint32 m_bestLapTimeInMS; // Best lap time of the session in milliseconds + double m_totalRaceTime; // Total race time in seconds without penalties + uint8 m_penaltiesTime; // Total penalties accumulated in seconds + uint8 m_numPenalties; // Number of penalties applied to this driver + uint8 m_numTyreStints; // Number of tyres stints up to maximum + uint8 m_tyreStintsActual[8]; // Actual tyres used by this driver + uint8 m_tyreStintsVisual[8]; // Visual tyres used by this driver + uint8 m_tyreStintsEndLaps[8]; // The lap number stints end on +}; + +struct PacketFinalClassificationData +{ + PacketHeader m_header; // Header + + uint8 m_numCars; // Number of cars in the final classification + FinalClassificationData m_classificationData[22]; +}; + +Lobby Info Packet + +This packet details the players currently in a multiplayer lobby.It details each player�s selected car, any AI involved in the game and also the ready status of each of the participants. + +Frequency: Two every second when in the lobby +Size : 1218 bytes +Version : 1 + +struct LobbyInfoData +{ + uint8 m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + uint8 m_teamId; // Team id - see appendix (255 if no team currently selected) + uint8 m_nationality; // Nationality of the driver + uint8 m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + char m_name[48]; // Name of participant in UTF-8 format � null terminated + // Will be truncated with ... (U+2026) if too long + uint8 m_carNumber; // Car number of the player + uint8 m_readyStatus; // 0 = not ready, 1 = ready, 2 = spectating +}; + +struct PacketLobbyInfoData +{ + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_numPlayers; // Number of players in the lobby data + LobbyInfoData m_lobbyPlayers[22]; +}; + +Car Damage Packet + +This packet details car damage parameters for all the cars in the race. + +Frequency: 10 per second +Size : 953 bytes +Version : 1 + +struct CarDamageData +{ + float m_tyresWear[4]; // Tyre wear (percentage) + uint8 m_tyresDamage[4]; // Tyre damage (percentage) + uint8 m_brakesDamage[4]; // Brakes damage (percentage) + uint8 m_frontLeftWingDamage; // Front left wing damage (percentage) + uint8 m_frontRightWingDamage; // Front right wing damage (percentage) + uint8 m_rearWingDamage; // Rear wing damage (percentage) + uint8 m_floorDamage; // Floor damage (percentage) + uint8 m_diffuserDamage; // Diffuser damage (percentage) + uint8 m_sidepodDamage; // Sidepod damage (percentage) + uint8 m_drsFault; // Indicator for DRS fault, 0 = OK, 1 = fault + uint8 m_ersFault; // Indicator for ERS fault, 0 = OK, 1 = fault + uint8 m_gearBoxDamage; // Gear box damage (percentage) + uint8 m_engineDamage; // Engine damage (percentage) + uint8 m_engineMGUHWear; // Engine wear MGU-H (percentage) + uint8 m_engineESWear; // Engine wear ES (percentage) + uint8 m_engineCEWear; // Engine wear CE (percentage) + uint8 m_engineICEWear; // Engine wear ICE (percentage) + uint8 m_engineMGUKWear; // Engine wear MGU-K (percentage) + uint8 m_engineTCWear; // Engine wear TC (percentage) + uint8 m_engineBlown; // Engine blown, 0 = OK, 1 = fault + uint8 m_engineSeized; // Engine seized, 0 = OK, 1 = fault +} + +struct PacketCarDamageData +{ + PacketHeader m_header; // Header + + CarDamageData m_carDamageData[22]; +}; + +Session History Packet + +This packet contains lap times and tyre usage for the session.This packet works slightly differently to other packets.To reduce CPU and bandwidth, each packet relates to a specific vehicle and is sent every 1 / 20 s, and the vehicle being sent is cycled through.Therefore in a 20 car race you should receive an update for each vehicle at least once per second. + +Note that at the end of the race, after the final classification packet has been sent, a final bulk update of all the session histories for the vehicles in that session will be sent. + +Frequency: 20 per second but cycling through cars +Size : 1460 bytes +Version : 1 + +struct LapHistoryData +{ + uint32 m_lapTimeInMS; // Lap time in milliseconds + uint16 m_sector1TimeInMS; // Sector 1 time in milliseconds + uint8 m_sector1TimeMinutes; // Sector 1 whole minute part + uint16 m_sector2TimeInMS; // Sector 2 time in milliseconds + uint8 m_sector1TimeMinutes; // Sector 2 whole minute part + uint16 m_sector3TimeInMS; // Sector 3 time in milliseconds + uint8 m_sector3TimeMinutes; // Sector 3 whole minute part + uint8 m_lapValidBitFlags; // 0x01 bit set-lap valid, 0x02 bit set-sector 1 valid + // 0x04 bit set-sector 2 valid, 0x08 bit set-sector 3 valid +}; + +struct TyreStintHistoryData +{ + uint8 m_endLap; // Lap the tyre usage ends on (255 of current tyre) + uint8 m_tyreActualCompound; // Actual tyres used by this driver + uint8 m_tyreVisualCompound; // Visual tyres used by this driver +}; + +struct PacketSessionHistoryData +{ + PacketHeader m_header; // Header + + uint8 m_carIdx; // Index of the car this lap data relates to + uint8 m_numLaps; // Num laps in the data (including current partial lap) + uint8 m_numTyreStints; // Number of tyre stints in the data + + uint8 m_bestLapTimeLapNum; // Lap the best lap time was achieved on + uint8 m_bestSector1LapNum; // Lap the best Sector 1 time was achieved on + uint8 m_bestSector2LapNum; // Lap the best Sector 2 time was achieved on + uint8 m_bestSector3LapNum; // Lap the best Sector 3 time was achieved on + + LapHistoryData m_lapHistoryData[100]; // 100 laps of data max + TyreStintHistoryData m_tyreStintsHistoryData[8]; +}; + +Tyre Sets Packet + +This packets gives a more in - depth details about tyre sets assigned to a vehicle during the session. + +Frequency: 20 per second but cycling through cars +Size : 231 bytes +Version : 1 + +struct TyreSetData +{ + uint8 m_actualTyreCompound; // Actual tyre compound used + uint8 m_visualTyreCompound; // Visual tyre compound used + uint8 m_wear; // Tyre wear (percentage) + uint8 m_available; // Whether this set is currently available + uint8 m_recommendedSession; // Recommended session for tyre set + uint8 m_lifeSpan; // Laps left in this tyre set + uint8 m_usableLife; // Max number of laps recommended for this compound + int16 m_lapDeltaTime; // Lap delta time in milliseconds compared to fitted set + uint8 m_fitted; // Whether the set is fitted or not +}; + +struct PacketTyreSetsData +{ + PacketHeader m_header; // Header + + uint8 m_carIdx; // Index of the car this data relates to + + TyreSetData m_tyreSetData[20]; // 13 (dry) + 7 (wet) + + uint8 m_fittedIdx; // Index into array of fitted tyre +}; +Motion Ex Packet + +The motion packet gives extended data for the car being driven with the goal of being able to drive a motion platform setup. + +Frequency: Rate as specified in menus +Size : 217 bytes +Version : 1 + +struct PacketMotionExData +{ + PacketHeader m_header; // Header + + // Extra player car ONLY data + float m_suspensionPosition[4]; // Note: All wheel arrays have the following order: + float m_suspensionVelocity[4]; // RL, RR, FL, FR + float m_suspensionAcceleration[4]; // RL, RR, FL, FR + float m_wheelSpeed[4]; // Speed of each wheel + float m_wheelSlipRatio[4]; // Slip ratio for each wheel + float m_wheelSlipAngle[4]; // Slip angles for each wheel + float m_wheelLatForce[4]; // Lateral forces for each wheel + float m_wheelLongForce[4]; // Longitudinal forces for each wheel + float m_heightOfCOGAboveGround; // Height of centre of gravity above ground + float m_localVelocityX; // Velocity in local space � metres/s + float m_localVelocityY; // Velocity in local space + float m_localVelocityZ; // Velocity in local space + float m_angularVelocityX; // Angular velocity x-component � radians/s + float m_angularVelocityY; // Angular velocity y-component + float m_angularVelocityZ; // Angular velocity z-component + float m_angularAccelerationX; // Angular acceleration x-component � radians/s/s + float m_angularAccelerationY; // Angular acceleration y-component + float m_angularAccelerationZ; // Angular acceleration z-component + float m_frontWheelsAngle; // Current front wheels angle in radians + float m_wheelVertForce[4]; // Vertical forces for each wheel +}; + + +Restricted data(Your Telemetry setting) + +There is some data in the UDP that you may not want other players seeing if you are in a multiplayer game.This is controlled by the �Your Telemetry� setting in the Telemetry options.The options are : + +� Restricted(Default) � other players viewing the UDP data will not see values for your car +� Public � all other players can see all the data for your car +� Show online ID � this additional option allows other players to view your online ID / gamertag in their UDP output. + +Note : You can always see the data for the car you are driving regardless of the setting. + +The following data items are set to zero if the player driving the car in question has their �Your Telemetry� set to �Restricted� : + +Car status packet + +� m_fuelInTank +� m_fuelCapacity +� m_fuelMix +� m_fuelRemainingLaps +� m_frontBrakeBias +� m_ersDeployMode +� m_ersStoreEnergy +� m_ersDeployedThisLap +� m_ersHarvestedThisLapMGUK +� m_ersHarvestedThisLapMGUH +� m_enginePowerICE +� m_enginePowerMGUK + +Car damage packet + +� m_frontLeftWingDamage +� m_frontRightWingDamage +� m_rearWingDamage +� m_floorDamage +� m_diffuserDamage +� m_sidepodDamage +� m_engineDamage +� m_gearBoxDamage +� m_tyresWear(All four wheels) +� m_tyresDamage(All four wheels) +� m_brakesDamage(All four wheels) +� m_drsFault +� m_engineMGUHWear +� m_engineESWear +� m_engineCEWear +� m_engineICEWear +� m_engineMGUKWear +� m_engineTCWear + +Tyre set packet +� All data within this packet for player car + +To allow other players to view your online ID in their UDP output during an online session, you must enable the �Show online ID / gamertags� option.Selecting this will bring up a confirmation box that must be confirmed before this option is enabled. + +Please note that all options can be changed during a game session and will take immediate effect. + + +FAQS + +How do I enable the UDP Telemetry Output ? +In F1 23, UDP telemetry output is controlled via the in - game menus.To enable this, enter the options menu from the main menu(triangle / Y), then enter the settings menu - the UDP option will be at the bottom of the list.From there you will be able to enable / disable the UDP output, configure the IP address and port for the receiving application, toggle broadcast mode and set the send rate.Broadcast mode transmits the data across the network subnet to allow multiple devices on the same subnet to be able to receive this information.When using broadcast mode it is not necessary to set a target IP address, just a target port for applications to listen on. + +Advanced PC Users : You can additionally edit the game�s configuration XML file to configure UDP output.The file is located here(after an initial boot of the game) : + + ...\Documents\My Games\\hardwaresettings\hardware_settings_config.xml + + You should see the tag : + + +... + +... +< / motion> + +Here you can set the values manually.Note that any changes made within the game when it is running will overwrite any changes made manually.Note the enabled flag is now a state. + + +What has changed since last year ? +F1� 23 sees the following changes to the UDP specification : + +� Added game year to packet header � apps can identify which F1 game data is coming from +� Temperature and speed units choice for players sent in session packet +� Platform of players added to lobby info and participants packets +� Added flag to say whether a player has their �Show online names� flag set in participants packet +� Added whole minute part to sector times in lap data and session history packets +� Damage packet now updates at 10 / s +� Separated corner cutting warnings in the lap data packet +� Added new tyre sets packet to give more detail about tyre sets for each car +� Added time deltas for cars in the lap data packet +� Added overall frame identifier to packet header to help deal with flashbacks +� Red flag event added +� Added Safety car, VSC and Red Flag counts to session data +� Added more physics data in the motion packet +� Added Overtake event +� Added power outputs readings for the engine +� Added C0 tyre type +� Added a new Motion Ex packet and moved player car settings from Motion packet to stop it getting too large, added vertical wheel forces + + +What is the order of the wheel arrays ? +All wheel arrays are in the following order : + +0 � Rear Left(RL) +1 � Rear Right(RR) +2 � Front Left(FL) +3 � Front Right(FR) + + +Do the vehicle indices change ? +During a session, each car is assigned a vehicle index.This will not change throughout the session and all the arrays that are sent use this vehicle index to dereference the correct piece of data. + +What are the co - ordinate systems used ? + +Here is a visual representation of the co - ordinate system used with the F1 telemetry data. + + + +What encoding format is used ? +All values are encoded using Little Endian format. + + +Are the data structures packed ? +Yes, all data is packed, there is no padding used. + + +How many cars are in the data structures ? +The maximum number of cars in the data structures is 22, to allow for certain game modes, although the data is not always filled in. + +You should always check the data item called m_numActiveCars in the participants packet which tells you how many cars are active in the race.However, you should check the individual result status of each car in the lap data to see if that car is actively providing data.If it is not �Invalid� or �Inactive� then the corresponding vehicle index has valid data. + + +How often are updated packets sent ? +For the packets which get updated at �Rate as specified in the menus� you can be guaranteed that on the frame that these get sent they will all get sent together and will never be separated across frames.This of course relies on the reliability of your network as to whether they are received correctly as everything is sent via UDP.Other packets that get sent at specific rates can arrive on any frame. +If you are connected to the game when it starts transmitting the first frame will contain the following information to help initialise data structures on the receiving application : +Packets sent on Frame 1 : (All packets sent on this frame have �Session timestamp� 0.000) +� Session +� Participants +� Car Setups +� Lap Data +� Motion Data +� Car Telemetry +� Car Status +� Car Damage +� Motion Ex Data +As an example, assuming that you are running at 60Hz with 60Hz update rate selected in the menus then you would expect to see the following packets and timestamps: +Packets sent on Frame 2 : (All packets sent on this frame have �Session timestamp� 0.016) +� Lap Data +� Motion Data +� Car Telemetry +� Car Status +� Motion Ex Data +� +Packets sent on Frame 31: (All packets sent on this frame have �Session timestamp� 0.5) +� Session(since 2 updates per second) +� Car Setups(since 2 updates per second) +� Lap Data +� Motion Data +� Car Telemetry +� Car Status +� Car Damage(since 2 updates per second) +� Motion Ex Data + +Will my old app still work with F1 23 ? +Please note that from F1 23 the game will only support the previous 2 UDP formats. + +F1 23 uses a new format for the UDP data.However, some earlier formats of the data are still supported so that most older apps implemented using the previous data formats should work with little or no change from the developer.To use the old formats, please enter the UDP options menu and set �UDP Format� to either �2022� or �2021�. +Specifications for the older formats can be seen here : + +� F1 2021 - https ://forums.codemasters.com/topic/80231-f1-2021-udp-specification + � F1 22 - https ://answers.ea.com/t5/General-Discussion/F1-22-UDP-Specification/td-p/11551274 + + How do I enable D - BOX output ? + D - BOX output is currently supported on the PC platform.In F1 23, the D - BOX activation can be controlled via the menus.Navigate to Game Options->Settings->UDP Telemetry Settings->D - BOX to activate this on your system. + + Advanced PC Users : It is possible to control D - BOX by editing the games� configuration XML file.The file is located here(after an initial boot of the game) : + + ...\Documents\My Games\\hardwaresettings\hardware_settings_config.xml + + You should see the tag : + + + +... +< / motion> + +Set the �enabled� value to �true� to allow the game to output to your D - BOX motion platform.Note that any changes made within the game when it is running will overwrite any changes made manually. + + +How can I disable in - game support for LED device ? +The F1 game has native support for some of the basic features supported by some external LED devices, such as the Leo Bodnar SLI Pro and the Fanatec steering wheels.To avoid conflicts between the game�s implementation and any third - party device managers on the PC platform it may be necessary to disable the native support.This is done using the following led_display flags in the hardware_settings_config.xml.The file is located here(after an initial boot of the game) : + + ...\Documents\My Games\\hardwaresettings\hardware_settings_config.xml + + The flags to enabled / disable LED output are : + + + +The sliProNativeSupport flag controls the output to SLI Pro devices.The fanatecNativeSupport flag controls the output to Fanatec(and some related) steering wheel LEDs.Set the values for any of these to �false� to disable them and avoid conflicts with your own device manager. + +Please note there is an additional flag to manually control the LED brightness on the SLI Pro : + + + +This option(using value in the range 0 - 255) will be ignored when setting the sliProNativeSupport flag to �false�. + +Also note it is now possible to edit these values on the fly via the Game Options->Settings->UDP Telemetry Settings menu. + + +Can I configure the UDP output using an XML File ? +PC users can edit the game�s configuration XML file to configure UDP output.The file is located here(after an initial boot of the game) : + + ...\Documents\My Games\\hardwaresettings\hardware_settings_config.xml + + You should see the tag : + + +... + +... +< / motion> + +Here you can set the values manually.Note that any changes made within the game when it is running will overwrite any changes made manually. + +Appendices + +Here are the values used for some of the parameters in the UDP data output. + +Team IDs + +ID Team ID Team ID Team +0 Mercedes 106 Prema �21 136 Campos �22 +1 Ferrari 107 Uni - Virtuosi �21 137 Van Amersfoort Racing �22 +2 Red Bull Racing 108 Carlin �21 138 Trident �22 +3 Williams 109 Hitech �21 139 Hitech �22 +4 Aston Martin 110 Art GP �21 140 Art GP �22 +5 Alpine 111 MP Motorsport �21 +6 Alpha Tauri 112 Charouz �21 +7 Haas 113 Dams �21 +8 McLaren 114 Campos �21 +9 Alfa Romeo 115 BWT �21 +85 Mercedes 2020 116 Trident �21 +86 Ferrari 2020 117 Mercedes AMG GT Black Series +87 Red Bull 2020 118 Mercedes �22 +88 Williams 2020 119 Ferrari �22 +89 Racing Point 2020 120 Red Bull Racing �22 +90 Renault 2020 121 Williams �22 +91 Alpha Tauri 2020 122 Aston Martin �22 +92 Haas 2020 123 Alpine �22 +93 McLaren 2020 124 Alpha Tauri �22 +94 Alfa Romeo 2020 125 Haas �22 +95 Aston Martin DB11 V12 126 McLaren �22 +96 Aston Martin Vantage F1 Edition 127 Alfa Romeo �22 +97 Aston Martin Vantage Safety Car 128 Konnersport �22 +98 Ferrari F8 Tributo 129 Konnersport +99 Ferrari Roma 130 Prema �22 +100 McLaren 720S 131 Virtuosi �22 +101 McLaren Artura 132 Carlin �22 +102 Mercedes AMG GT Black Series Safety Car 133 MP Motorsport �22 +103 Mercedes AMG GTR Pro 134 Charouz �22 +104 F1 Custom Team 135 Dams �22 + + +Driver IDs + +ID Driver ID Driver ID Driver +0 Carlos Sainz 56 Louis Del�traz 115 Theo Pourchaire +1 Daniil Kvyat 57 Antonio Fuoco 116 Richard Verschoor +2 Daniel Ricciardo 58 Charles Leclerc 117 Lirim Zendeli +3 Fernando Alonso 59 Pierre Gasly 118 David Beckmann +4 Felipe Massa 62 Alexander Albon 121 Alessio Deledda +6 Kimi R�ikk�nen 63 Nicholas Latifi 122 Bent Viscaal +7 Lewis Hamilton 64 Dorian Boccolacci 123 Enzo Fittipaldi +9 Max Verstappen 65 Niko Kari 125 Mark Webber +10 Nico Hulkenburg 66 Roberto Merhi 126 Jacques Villeneuve +11 Kevin Magnussen 67 Arjun Maini 127 Callie Mayer +12 Romain Grosjean 68 Alessio Lorandi 128 Noah Bell +13 Sebastian Vettel 69 Ruben Meijer 129 Jake Hughes +14 Sergio Perez 70 Rashid Nair 130 Frederik Vesti +15 Valtteri Bottas 71 Jack Tremblay 131 Olli Caldwell +17 Esteban Ocon 72 Devon Butler 132 Logan Sargeant +19 Lance Stroll 73 Lukas Weber 133 Cem Bolukbasi +20 Arron Barnes 74 Antonio Giovinazzi 134 Ayumu Iwasa +21 Martin Giles 75 Robert Kubica 135 Clement Novalak +22 Alex Murray 76 Alain Prost 136 Jack Doohan +23 Lucas Roth 77 Ayrton Senna 137 Amaury Cordeel +24 Igor Correia 78 Nobuharu Matsushita 138 Dennis Hauger +25 Sophie Levasseur 79 Nikita Mazepin 139 Calan Williams +26 Jonas Schiffer 80 Guanya Zhou 140 Jamie Chadwick +27 Alain Forest 81 Mick Schumacher 141 Kamui Kobayashi +28 Jay Letourneau 82 Callum Ilott 142 Pastor Maldonado +29 Esto Saari 83 Juan Manuel Correa 143 Mika Hakkinen +30 Yasar Atiyeh 84 Jordan King 144 Nigel Mansell +31 Callisto Calabresi 85 Mahaveer Raghunathan +32 Naota Izum 86 Tatiana Calderon +33 Howard Clarke 87 Anthoine Hubert +34 Wilheim Kaufmann 88 Guiliano Alesi +35 Marie Laursen 89 Ralph Boschung +36 Flavio Nieves 90 Michael Schumacher +37 Peter Belousov 91 Dan Ticktum +38 Klimek Michalski 92 Marcus Armstrong +39 Santiago Moreno 93 Christian Lundgaard +40 Benjamin Coppens 94 Yuki Tsunoda +41 Noah Visser 95 Jehan Daruvala +42 Gert Waldmuller 96 Gulherme Samaia +43 Julian Quesada 97 Pedro Piquet +44 Daniel Jones 98 Felipe Drugovich +45 Artem Markelov 99 Robert Schwartzman +46 Tadasuke Makino 100 Roy Nissany +47 Sean Gelael 101 Marino Sato +48 Nyck De Vries 102 Aidan Jackson +49 Jack Aitken 103 Casper Akkerman +50 George Russell 109 Jenson Button +51 Maximilian G�nther 110 David Coulthard +52 Nirei Fukuzumi 111 Nico Rosberg +53 Luca Ghiotto 112 Oscar Piastri +54 Lando Norris 113 Liam Lawson +55 S�rgio Sette C�mara 114 Juri Vips + + +Track IDs + +ID Track +0 Melbourne +1 Paul Ricard +2 Shanghai +3 Sakhir(Bahrain) +4 Catalunya +5 Monaco +6 Montreal +7 Silverstone +8 Hockenheim +9 Hungaroring +10 Spa +11 Monza +12 Singapore +13 Suzuka +14 Abu Dhabi +15 Texas +16 Brazil +17 Austria +18 Sochi +19 Mexico +20 Baku(Azerbaijan) +21 Sakhir Short +22 Silverstone Short +23 Texas Short +24 Suzuka Short +25 Hanoi +26 Zandvoort +27 Imola +28 Portim�o +29 Jeddah +30 Miami +31 Las Vegas +32 Losail + + + +Nationality IDs + +ID Nationality ID Nationality ID Nationality +1 American 31 Greek 61 Paraguayan +2 Argentinean 32 Guatemalan 62 Peruvian +3 Australian 33 Honduran 63 Polish +4 Austrian 34 Hong Konger 64 Portuguese +5 Azerbaijani 35 Hungarian 65 Qatari +6 Bahraini 36 Icelander 66 Romanian +7 Belgian 37 Indian 67 Russian +8 Bolivian 38 Indonesian 68 Salvadoran +9 Brazilian 39 Irish 69 Saudi +10 British 40 Israeli 70 Scottish +11 Bulgarian 41 Italian 71 Serbian +12 Cameroonian 42 Jamaican 72 Singaporean +13 Canadian 43 Japanese 73 Slovakian +14 Chilean 44 Jordanian 74 Slovenian +15 Chinese 45 Kuwaiti 75 South Korean +16 Colombian 46 Latvian 76 South African +17 Costa Rican 47 Lebanese 77 Spanish +18 Croatian 48 Lithuanian 78 Swedish +19 Cypriot 49 Luxembourger 79 Swiss +20 Czech 50 Malaysian 80 Thai +21 Danish 51 Maltese 81 Turkish +22 Dutch 52 Mexican 82 Uruguayan +23 Ecuadorian 53 Monegasque 83 Ukrainian +24 English 54 New Zealander 84 Venezuelan +25 Emirian 55 Nicaraguan 85 Barbadian +26 Estonian 56 Northern Irish 86 Welsh +27 Finnish 57 Norwegian 87 Vietnamese +28 French 58 Omani +29 German 59 Pakistani +30 Ghanaian 60 Panamanian + + + + +Game Mode IDs + +ID Mode +0 Event Mode +3 Grand Prix +4 Grand Prix �23 +5 Time Trial +6 Splitscreen +7 Online Custom +8 Online League +11 Career Invitational +12 Championship Invitational +13 Championship +14 Online Championship +15 Online Weekly Event +17 Story Mode +19 Career �22 +20 Career �22 Online +21 Career �23 +22 Career �23 Online +127 Benchmark + + +Ruleset IDs + +ID Ruleset +0 Practice & Qualifying +1 Race +2 Time Trial +4 Time Attack +6 Checkpoint Challenge +8 Autocross +9 Drift +10 Average Speed Zone +11 Rival Duel + +Surface types + +These types are from physics data and show what type of contact each wheel is experiencing. + +ID Surface +0 Tarmac +1 Rumble strip +2 Concrete +3 Rock +4 Gravel +5 Mud +6 Sand +7 Grass +8 Water +9 Cobblestone +10 Metal +11 Ridged + +Button flags + +These flags are used in the telemetry packet to determine if any buttons are being held on the controlling device.If the value below logical ANDed with the button status is set then the corresponding button is being held. + +Bit Flag Button +0x00000001 Cross or A +0x00000002 Triangle or Y +0x00000004 Circle or B +0x00000008 Square or X +0x00000010 D - pad Left +0x00000020 D - pad Right +0x00000040 D - pad Up +0x00000080 D - pad Down +0x00000100 Options or Menu +0x00000200 L1 or LB +0x00000400 R1 or RB +0x00000800 L2 or LT +0x00001000 R2 or RT +0x00002000 Left Stick Click +0x00004000 Right Stick Click +0x00008000 Right Stick Left +0x00010000 Right Stick Right +0x00020000 Right Stick Up +0x00040000 Right Stick Down +0x00080000 Special +0x00100000 UDP Action 1 +0x00200000 UDP Action 2 +0x00400000 UDP Action 3 +0x00800000 UDP Action 4 +0x01000000 UDP Action 5 +0x02000000 UDP Action 6 +0x04000000 UDP Action 7 +0x08000000 UDP Action 8 +0x10000000 UDP Action 9 +0x20000000 UDP Action 10 +0x40000000 UDP Action 11 +0x80000000 UDP Action 12 + +Penalty types + +ID Penalty meaning +0 Drive through +1 Stop Go +2 Grid penalty +3 Penalty reminder +4 Time penalty +5 Warning +6 Disqualified +7 Removed from formation lap +8 Parked too long timer +9 Tyre regulations +10 This lap invalidated +11 This and next lap invalidated +12 This lap invalidated without reason +13 This and next lap invalidated without reason +14 This and previous lap invalidated +15 This and previous lap invalidated without reason +16 Retired +17 Black flag timer + + +Infringement types + +ID Infringement meaning +0 Blocking by slow driving +1 Blocking by wrong way driving +2 Reversing off the start line +3 Big Collision +4 Small Collision +5 Collision failed to hand back position single +6 Collision failed to hand back position multiple +7 Corner cutting gained time +8 Corner cutting overtake single +9 Corner cutting overtake multiple +10 Crossed pit exit lane +11 Ignoring blue flags +12 Ignoring yellow flags +13 Ignoring drive through +14 Too many drive throughs +15 Drive through reminder serve within n laps +16 Drive through reminder serve this lap +17 Pit lane speeding +18 Parked for too long +19 Ignoring tyre regulations +20 Too many penalties +21 Multiple warnings +22 Approaching disqualification +23 Tyre regulations select single +24 Tyre regulations select multiple +25 Lap invalidated corner cutting +26 Lap invalidated running wide +27 Corner cutting ran wide gained time minor +28 Corner cutting ran wide gained time significant +29 Corner cutting ran wide gained time extreme +30 Lap invalidated wall riding +31 Lap invalidated flashback used +32 Lap invalidated reset to track +33 Blocking the pitlane +34 Jump start +35 Safety car to car collision +36 Safety car illegal overtake +37 Safety car exceeding allowed pace +38 Virtual safety car exceeding allowed pace +39 Formation lap below allowed speed +40 Formation lap parking +41 Retired mechanical failure +42 Retired terminally damaged +43 Safety car falling too far back +44 Black flag timer +45 Unserved stop go penalty +46 Unserved drive through penalty +47 Engine component change +48 Gearbox change +49 Parc Ferm� change +50 League grid penalty +51 Retry penalty +52 Illegal time gain +53 Mandatory pitstop +54 Attribute assigned diff --git a/GamesDat/Telemetry/Sources/Formula1/Specifications/README.md b/GamesDat/Telemetry/Sources/Formula1/Specifications/README.md new file mode 100644 index 0000000..c348109 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/Specifications/README.md @@ -0,0 +1,31 @@ +# F1 UDP Telemetry Specifications + +This directory stores the official C++ specification files from EA/Codemasters for F1 game UDP telemetry. + +## Naming Convention + +Place specification files here using this naming pattern: +- `F1__spec.h` (e.g., `F1_2026_spec.h`) +- `F1_spec.h` (e.g., `F12026_spec.h`) +- `_udp_spec.h` (e.g., `2026_udp_spec.h`) + +## Usage + +The `/f1-implementation` skill automatically looks for specifications in this directory first. If your spec file follows the naming convention above, it will be found automatically. + +## Obtaining Specifications + +Official F1 game UDP specifications are typically: +- Provided by EA/Codemasters in game documentation +- Available in the game installation directory +- Published on official forums or GitHub repositories +- Included in game modding documentation + +## Example + +``` +Specifications/ +├── F1_2024_spec.h # F1 2024 specification +├── F1_2025_spec.h # F1 2025 specification +└── F1_2026_spec.h # F1 2026 specification (future) +``` From d267c6375f79e921a656388dbe0abbd32249b7f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Tue, 10 Feb 2026 15:20:49 +0100 Subject: [PATCH 04/25] Added F1 packets and fixtures --- .../F1-Telemetry-Fixture-Capture/capture.js | 24 + .../fixtures/.gitkeep | 0 .../fixtures/2023/packet-0.bin | Bin 0 -> 1349 bytes .../fixtures/2023/packet-1.bin | Bin 0 -> 644 bytes .../fixtures/2023/packet-10.bin | Bin 0 -> 953 bytes .../fixtures/2023/packet-11.bin | Bin 0 -> 1460 bytes .../fixtures/2023/packet-12.bin | Bin 0 -> 231 bytes .../fixtures/2023/packet-13.bin | Bin 0 -> 217 bytes .../fixtures/2023/packet-2.bin | Bin 0 -> 1131 bytes .../fixtures/2023/packet-3.bin | Bin 0 -> 45 bytes .../fixtures/2023/packet-4.bin | Bin 0 -> 1306 bytes .../fixtures/2023/packet-5.bin | Bin 0 -> 1107 bytes .../fixtures/2023/packet-6.bin | Bin 0 -> 1352 bytes .../fixtures/2023/packet-7.bin | Bin 0 -> 1239 bytes .../fixtures/2023/packet-8.bin | Bin 0 -> 1020 bytes .../fixtures/2024/packet-0.bin | Bin 0 -> 1349 bytes .../fixtures/2024/packet-1.bin | Bin 0 -> 753 bytes .../fixtures/2024/packet-10.bin | Bin 0 -> 953 bytes .../fixtures/2024/packet-11.bin | Bin 0 -> 1460 bytes .../fixtures/2024/packet-12.bin | Bin 0 -> 231 bytes .../fixtures/2024/packet-13.bin | Bin 0 -> 237 bytes .../fixtures/2024/packet-14.bin | Bin 0 -> 101 bytes .../fixtures/2024/packet-2.bin | Bin 0 -> 1285 bytes .../fixtures/2024/packet-3.bin | Bin 0 -> 45 bytes .../fixtures/2024/packet-4.bin | Bin 0 -> 1350 bytes .../fixtures/2024/packet-5.bin | Bin 0 -> 1133 bytes .../fixtures/2024/packet-6.bin | Bin 0 -> 1352 bytes .../fixtures/2024/packet-7.bin | Bin 0 -> 1239 bytes .../fixtures/2025/packet-0.bin | Bin 0 -> 1349 bytes .../fixtures/2025/packet-1.bin | Bin 0 -> 753 bytes .../fixtures/2025/packet-10.bin | Bin 0 -> 1041 bytes .../fixtures/2025/packet-11.bin | Bin 0 -> 1460 bytes .../fixtures/2025/packet-12.bin | Bin 0 -> 231 bytes .../fixtures/2025/packet-13.bin | Bin 0 -> 273 bytes .../fixtures/2025/packet-14.bin | Bin 0 -> 101 bytes .../fixtures/2025/packet-15.bin | Bin 0 -> 1131 bytes .../fixtures/2025/packet-2.bin | Bin 0 -> 1285 bytes .../fixtures/2025/packet-3-BUTN.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-COLL.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-FLBK.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-FTLP.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-LGOT.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-OVTK.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-PENA.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-RCWN.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-RTMT.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-SEND.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-SPTP.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-SSTA.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-STLG.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-TMPT.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-4.bin | Bin 0 -> 1284 bytes .../fixtures/2025/packet-5.bin | Bin 0 -> 1133 bytes .../fixtures/2025/packet-6.bin | Bin 0 -> 1352 bytes .../fixtures/2025/packet-7.bin | Bin 0 -> 1239 bytes .../fixtures/2025/packet-8.bin | Bin 0 -> 1042 bytes .../F1-Telemetry-Fixture-Capture/package.json | 14 + GamesDat.Demo.Wpf/GamesDat.Demo.Wpf.csproj | 2 +- .../Properties/launchSettings.json | 5 +- GamesDat.Demo.Wpf/Resources/Styles.xaml | 2 + .../ViewModels/F1RealtimeSourceViewModel.cs | 166 +++ .../ViewModels/IRealtimeSource.cs | 14 + .../ViewModels/RealtimeSourceViewModel.cs | 8 +- .../ViewModels/RealtimeTabViewModel.cs | 8 +- GamesDat.Demo.Wpf/Views/RealtimeTabView.xaml | 156 ++- .../Sources/Formula1/Docs/F1_2022_spec.md | 1226 +++++++++++++++++ .../F1_2023_spec.h => Docs/F1_2023_spec.md} | 0 .../Sources/Formula1/F12022/CarDamageData.cs | 36 + .../Sources/Formula1/F12022/CarMotionData.cs | 30 + .../Sources/Formula1/F12022/CarSetupData.cs | 31 + .../Sources/Formula1/F12022/CarStatusData.cs | 42 + .../Formula1/F12022/CarTelemetryData.cs | 36 + .../Formula1/F12022/EventDataDetails.cs | 115 ++ .../F12022/FinalClassificationData.cs | 31 + .../Sources/Formula1/F12022/LapData.cs | 38 + .../Sources/Formula1/F12022/LapHistoryData.cs | 15 + .../Sources/Formula1/F12022/LobbyInfoData.cs | 19 + .../Sources/Formula1/F12022/MarshalZone.cs | 11 + .../Formula1/F12022/PacketCarDamageData.cs | 18 + .../Formula1/F12022/PacketCarSetupData.cs | 18 + .../Formula1/F12022/PacketCarStatusData.cs | 18 + .../Formula1/F12022/PacketCarTelemetryData.cs | 26 + .../Formula1/F12022/PacketEventData.cs | 26 + .../F12022/PacketFinalClassificationData.cs | 20 + .../Sources/Formula1/F12022/PacketHeader.cs | 19 + .../Sources/Formula1/F12022/PacketLapData.cs | 21 + .../Formula1/F12022/PacketLobbyInfoData.cs | 20 + .../Formula1/F12022/PacketMotionData.cs | 40 + .../Formula1/F12022/PacketParticipantsData.cs | 20 + .../Formula1/F12022/PacketSessionData.cs | 71 + .../F12022/PacketSessionHistoryData.cs | 30 + .../Formula1/F12022/ParticipantData.cs | 22 + .../Formula1/F12022/TyreStintHistoryData.cs | 12 + .../Formula1/F12022/WeatherForecastSample.cs | 20 + .../Sources/Formula1/F12025/CarSetupData.cs | 2 +- .../Formula1/F12025/CarTelemetryData.cs | 2 +- .../Sources/Formula1/F12025/EventData.cs | 2 +- .../Formula1/F12025/EventDataDetails.cs | 9 +- .../Formula1/F12025/ParticipantData.cs | 2 +- .../Sources/Formula1/F1PacketTypeMapper.cs | 15 + .../Formula1/F1RealtimeTelemetrySource.cs | 21 +- .../Sources/Formula1/F1TelemetryFrame.cs | 35 +- .../Formula1/F1TelemetryFrameExtensions.cs | 84 ++ .../Sources/Formula1/Specifications/F1_2025.h | 860 ++++++++++++ 105 files changed, 3394 insertions(+), 68 deletions(-) create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/capture.js create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/.gitkeep create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-0.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-1.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-10.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-11.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-12.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-13.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-2.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-3.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-4.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-5.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-6.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-7.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-8.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-0.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-1.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-10.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-11.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-12.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-13.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-14.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-2.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-3.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-4.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-5.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-6.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-7.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-0.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-1.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-10.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-11.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-12.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-13.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-14.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-15.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-2.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-BUTN.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-COLL.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FLBK.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FTLP.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-LGOT.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-OVTK.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-PENA.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-RCWN.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-RTMT.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SEND.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SPTP.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SSTA.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-STLG.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-TMPT.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-4.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-5.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-6.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-7.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-8.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/package.json create mode 100644 GamesDat.Demo.Wpf/ViewModels/F1RealtimeSourceViewModel.cs create mode 100644 GamesDat.Demo.Wpf/ViewModels/IRealtimeSource.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/Docs/F1_2022_spec.md rename GamesDat/Telemetry/Sources/Formula1/{Specifications/F1_2023_spec.h => Docs/F1_2023_spec.md} (100%) create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/CarDamageData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/CarMotionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/CarSetupData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/CarStatusData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/CarTelemetryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/EventDataDetails.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/FinalClassificationData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/LapData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/LapHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/LobbyInfoData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/MarshalZone.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarDamageData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarSetupData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarStatusData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarTelemetryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketEventData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketFinalClassificationData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketHeader.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketLapData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketLobbyInfoData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketMotionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketParticipantsData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketSessionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketSessionHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/ParticipantData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/TyreStintHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/WeatherForecastSample.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrameExtensions.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2025.h diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/capture.js b/DebugTooling/F1-Telemetry-Fixture-Capture/capture.js new file mode 100644 index 0000000..5f98e58 --- /dev/null +++ b/DebugTooling/F1-Telemetry-Fixture-Capture/capture.js @@ -0,0 +1,24 @@ +import dgram from "dgram"; +import { writeFileSync, mkdirSync } from "fs"; + +const socket = dgram.createSocket("udp4"); +socket.bind(20777); + +socket.on("message", (msg) => { + // PacketHeader: packetFormat(2) + gameYear(1) + gameMajorVersion(1) + gameMinorVersion(1) + packetVersion(1) + packetId(1) + const packetFormat = msg.readUInt16LE(0); // e.g., 2025, 2024 + const packetId = msg.readUInt8(6); + + const dir = `./fixtures/${packetFormat}`; + mkdirSync(dir, { recursive: true }); + + let filename = `packet-${packetId}.bin`; + + // Event packets (packetId 3) have a 4-character event code at offset 29 (after 29-byte header) + if (packetId === 3) { + const eventCode = msg.toString("ascii", 29, 33); // Read 4-byte event string (SSTA, FTLP, etc.) + filename = `packet-3-${eventCode}.bin`; + } + + writeFileSync(`${dir}/${filename}`, msg); +}); diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/.gitkeep b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-0.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-0.bin new file mode 100644 index 0000000000000000000000000000000000000000..2e5a7281acde5cba08b01d8ea030ed382cb3a1cd GIT binary patch literal 1349 zcmaFP&cG0fLPOfzVAHq+O2dM2rFZ+$N3 zP?|Rz>6r}a-WDe`?bsq(_szAsYIAj;f&)V~^B&h@!Mo+ozd4-L70`4#8#*xs>D%u( zkbQ{F_W;A0nWkyd|5<#I_1@kk@4#^Q!0xr@o?7pdI)1okp6!8I&YfuX#xh6&?LC2J zuLH>5dse#-aLU*;yPi3`P}uIkoa`l zz3?B^-pwGr4}vYto*uF)tiEt~X^r23Uslu5^gaaYeUdKmZx4!_--7fyR9U%LmDm)m zyn0wn#pl4|^yz4N`@#0EL9_Q7Nbj^H>xt4D*6F%e4=;KfazK9P92C8Wy(*bhW;{wi z`)3P^y^9oqdQ0c+S#nzybd^CH{gY3O<-bzt$qP6h7 zn}>JJh&f97CnM6vfFSnn6hpLIuV&T`&A9Mm6n;9cAjG`&}WdY`73 ZqQ%iyklq99tytc=+90R5QTE^r0RU5UBvb$Z literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-1.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-1.bin new file mode 100644 index 0000000000000000000000000000000000000000..ad8ac56c0be8525ba1afbd469f941a946ca27d23 GIT binary patch literal 644 zcmaFP&cG2d!^rTTfp3PLfi1() zecW~oi+>u~F(e5F*)g0v-eJeE=jJ>+hRvdT>==69J+Na)lw+`G$XO_1&!FgUXU~vc q8f4FK=3s_B!*ka%d#L%N)QAfKMn*;k21Z6!rYlT9zcT_M5C8y!-6EF& literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-10.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-10.bin new file mode 100644 index 0000000000000000000000000000000000000000..6dd3d7aca06d2f98c3f7479a32d86f780a8b93c7 GIT binary patch literal 953 scmaFP&cGARtLtr!nMnix`A@E;N H0ISIWCQ%4u literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-12.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-12.bin new file mode 100644 index 0000000000000000000000000000000000000000..53ef9c9255cf01d5e66bb332361091e1b9b50fd3 GIT binary patch literal 231 lcmaFP&cGDFCcI2ipJu literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-13.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-13.bin new file mode 100644 index 0000000000000000000000000000000000000000..a9b1eebeb9098f717591937f022bb76aab97a280 GIT binary patch literal 217 zcmV;~04D$E2LJ&P0S$;767?ICfBHX{EkqHX0000000030Qzb=0kLN`~Z;od|P0?vW zwovH6jLF}@Ex#nc^IjjoOk|D2ru~A%=)Sc>iK+lYZGDnL@OhF#PPmdnthJItgS#)f zpk_+CTu$ORB<9dKFwS{9KW5;&^(}Y1yYLx0e6gp*qLlSS4#k*415d<5am*&e=dwb> zX8z_uQY_vwM^!^ z69|KKzzk+!fa{nYi|!A5?@dl14Aud45|9no0c4}uaad`)69|KKV6g*Jhf2V5ClDrH zhqS_UClDrHhk{wV69^NpBP<}#351E);TG%X1j5AY2y@VL0%78Hs3-F|fiUqp*rwib f1YzQJ6z`bh2*UVu0ObgS|AdvGsTkG4z{COoO~hhH literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-3.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-3.bin new file mode 100644 index 0000000000000000000000000000000000000000..1f6f1006b22c2428da41237bee97c9b2724be92b GIT binary patch literal 45 icmaFP&cGWs5n82k%5u% zKO<1*|A5q@;{3eC90jM;;u3=T7=X6@XZ+7ZwoYaapq;@br3IA?q_`GnCo{-S@^mth zt&@Rloh)SQWF=cCJJ~wf$kxeBwoXp6b!L#OlZCvz@}J@V|NjiYtVRYH)k!GO$POU@ D^yz<5 literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-5.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-5.bin new file mode 100644 index 0000000000000000000000000000000000000000..429edc2aa8833646c927bde792601ada275e2cf1 GIT binary patch literal 1107 zcmaFP&cGGcY6^0O~&g)L=8=&{tbkZhme_{ghD+ppY63 Qr9qT5AgO$mq;d!V05TH(3;+NC literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-6.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-6.bin new file mode 100644 index 0000000000000000000000000000000000000000..dcfb0f74c9aad41f7fa89eb42b91c6e6aa661695 GIT binary patch literal 1352 zcmaFP&cGl{IN#hasG3{rg>sJFr1Y>kO6STBdRF^HCgLN_SLV@OL|u#u;m4$&>Ce9(h&Inp8)`tQ>B~$ literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-7.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-7.bin new file mode 100644 index 0000000000000000000000000000000000000000..060942068b5672e9e7973d7a23b91e64fcdd697c GIT binary patch literal 1239 zcmaFP&cG^i_It{V)6Jx-NqA7$D$30|TQKkg4Fvz;MTDXVVG?ZQ}*}oInvl z0Tu?0SxlZ_bt)xZOb`ul5~nsshMDP~JYd=w;Tj+eJldRoA#5XB8w10t?>pSdu(%B61z003rPghT)U literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-8.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-8.bin new file mode 100644 index 0000000000000000000000000000000000000000..660a2dd3ed425c4f7ad491dbf36e280c2d0d0939 GIT binary patch literal 1020 zcmaFP&cG_HIbBYY@|LVDRy^(TR}UEqDIS;iSf>1DA5P zIP>^TblQ2Wai8h^i-%TvZv7uIeQAc^zZIT9JK$jZc|`{XJ8|pYjpa5ekJmY`@q6I# zOmO9)`U-2uMg3d%mLAhRu&xld$wmEI$Y;x zazOQDlQXiK{a#iKk~5yAANse~3t2o<4QTI-7rSnz%G;F5taq;HecSvoL$sIX=V-m`n3)bYbT^K1{ya_&U4HY(yMT5*K&OKbcN{IZ&cruQLG?~`G&f<<-MtDn17ur%y-I+Yh#P4Vt~zKzgSoSx=PKuuj*#dU(;>kOT5N=b-34>{ZF6 zGUHMD**{xQ>|LY?)LS}l&!U@WY~&(u9q#K7I8gIwE{fiLUT4Af=A+qr9%S!@^Hz$2 z6Rm~s-8{T&M$7>V)5U0dKLYjMPk;SqCyKof!Fs<~{;WG{bC&b|;h_Go1MlLNpy|B| e)cZ8O6fKUvg7h9(Z^iP~)do4ejj{)42mk;y9WE{a literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-1.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-1.bin new file mode 100644 index 0000000000000000000000000000000000000000..f3ca10a69e2ddcb3c729677a4290f24ad3f2e8c9 GIT binary patch literal 753 zcmaFCF3Bj!$k-t*^i_It{V&@z4VQ?S3=AO5@Sj0Vl9fqZh#km|V6p)T@XfF@uw^*9 zkK2x6@lPW=h9totJBE|TJM0+t+?;2}uvv7E9YfE%2X+jJat!tiISVE185I5P>>1Ka zgX|g39L%t1cWHSjD`RiA;8GU$iTqJ$jWqu38Mr|dFff2H!+!=wo;XGZmwW~W(|!hqN%2521_nk3 dMkXBMER3^b85z#xF)$eQF)++cfT(9=1OU{u6aN4J literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-2.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-2.bin new file mode 100644 index 0000000000000000000000000000000000000000..9e5bc9deaf3ac46862956f4ae54c45ec1615ab07 GIT binary patch literal 1285 zcmaFCF3Bj!$kZV%^i_It{jZ2d4VQwM3=AO5@c&ReBZIg%kc9*SJiA=}>^$HG6l!2( zMMy9*LD)?j7CJNW#6dLbFfcH}l*lJ;cLiavMxbJ-`u}hRK#Bh_jWA6h+URGW3kU<5 z4KQ1PY*dXPh3HOQCUe~hguxmS)}U&f9gFG5iP{dVRU=RU!^Xo(+nqod ztPz)u*fgpHEO!E7k~K;zOm_ldk~J!rwL5_@$r{4~@|-}JWQ}gIeoi1vvc@n6Jtq(* zS)+O~pA!g^tdVW%9Y+u*S!3~zIgTJqP$N(QG2lNjim<5})rc)rpkkrq5vaM z6(i$+MxfsR0jWjB`FV*s3QnoTB}C{3>HW|6pNRs!%p5>_gG)*aDj7&~GtgdUkiF#U zWuibY0|k0nDA3DFfnIhB^s-T)mze^+oD}HIAYU&Fx%ucn!~g&Pfmwoxtb;|#s9r3A Kfd7XSbN literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-6.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-6.bin new file mode 100644 index 0000000000000000000000000000000000000000..f2319d15bc963201d84cb3675ce45067ed4d9ee7 GIT binary patch literal 1352 zcmaFCF3Bj!$krh(^i_It{V&OS4VS!`3=AO5@IM1cFfbgbblMAMFwIs5(LPMxO!-Wy zOtDaq$8gGIog)aZcyknt8Q2-vPct$wG}xQ1F|h^f<JhuF(!yZXF9X9K nM+m+Eq(K-7G(gz6>>brR8dkW{2DZF_6k4OqK^g-8|1$sp%XO#| literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-7.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-7.bin new file mode 100644 index 0000000000000000000000000000000000000000..3ae9b9054c85b8fa615cf4a4cb4ab76840387ef7 GIT binary patch literal 1239 zcmaFCF3Bj!$lf6>^i_It{V&OS4VS!`3=AO5@SlN!(F#Z_I5IHYaoX9m!a>`30Y4|h zWd=b376!|kS3Q7|3=AqIUQA#Hg21Yc0jQ0UVP^U#xHd+F4loO=wsl}_`6g_hU^PJ7 sh|&hKje%j+_Z{veXuiEEey30le?<-@`NroFl+~K zvryFJQDbgSd?+)-nA`4lcZWks5P1+|iTM10k_4g@aO0(&-uHX;J>Qq#^Etn#ZUKy` z3{ZBHKGt1u>=)PQ6ZrEX02Yn{;73mgJ0(zOM#KJ`aGcDzW(!?h%RQi&0yFlcg7-b{ zy;3oxd!`i)VZ}1JB`T_eP`uEOhgsjnr)&8-!K2CpEe} z&}}*F%1vMnpk@45K}8UG0-eRruf596ghS!f zaDIv9Ffos6*NfN|Q~)wYD?wx)f=Cr|p>Vyf^0VNG96y6RiO3=iGW&XolC;Qj?Et zfCY6fILOVU4s5ToQrIB=>pwPx<2H=+zir%OjN52)aNwXfCqH#MzOz&cn#iz$}+1eCB_`DkhNTf+$t}*Y-Xy*UAF&3Wc z4a5Bql*$UqMPtP%CD-o;1^O@7&tfX|Jr_@N*QKFY&%*?6MZ@%QlQ|}HNQ1d;RXb-4=$krZ?`IwgZdRO<1acWXIa7l+KU z1b6&Le=+<+xDLN=mr_fVOlxzdh__dnI<_pJ`4MAmuhJlmCA~-e$pyaNX!O~itb1O? zBTujAA(wn_2!^~uVWeg`zSOnO_OY#n5U7)>;FcPCrMgB|$p~)gV>Mcf7sg<#UN>>m zR8b^o4dm1|t}@>}o=kb?q`>6txO!Q40YAGx4ZV9%$2%>L2lb)oE$i4R`PCSIGJ?8@ zI(_@iV^taq{J=6@=wu-+AhKQ9I%;>2`R$uMIfY8QS9biCY+Hkz<+dP;po{A^!Xyj$4J1p*TT7FPS1|6ZeQ!sNR iAVgB@W*$gu=T>!o#8o}2K!LoBb6Y!%y!}u9&-@QS*)=Wz literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-1.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-1.bin new file mode 100644 index 0000000000000000000000000000000000000000..5e2bac603a722f33b74e1af72e35f60d68f5fefa GIT binary patch literal 753 zcmaFKF3Bj!$T;KL|GD>C=Wb&)UgGlR8v}#kUj_!@{|wT?B6CFe85sVFDaZscGW=%{ zx&H8x6~kOLJ6ne2y%%j6?|4vSVm3&9Gw-e$`>epgVPz9mCp=Rdx((^iSI{ zWEH)%V{mxSXwTqroY$Vg$VtYYq0dy+o}qNJnLPu4yrVtC%C7#hE|>z{tb|OsY&kE-(SX!TN=_T~@Aia|Hlj_CDwU literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-10.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-10.bin new file mode 100644 index 0000000000000000000000000000000000000000..4689b6c75625effb132cb8d2d6ce64345b358f4c GIT binary patch literal 1041 zcmaFKF3Bj!$Tj2I|GD>C=Wb&)UgGlR8v}#kUj_!@|3Q)89m0IiI)p#p=wR&;@4(K+ z%FKWh%)20K*Jr?I_vJvb?V$sXwpbOWFr)QysOk?HxC(OFRBex#+;h&B=yU z2UxI}$I|g!kc{KopG=Pa|5i9~b8@iaR^9hS&ynlAfMZX{BZt}>(;T=s*;#O_w!0ssk@xcQ46icFgU3;qXCxm&1(U z76%S?Ry;1L`lso5>MOV73e}GemHAg3xH#DHgh_c)u;T(oW5=ZvSsYg{*a&n9zA*Xn z>9fO?hAR#m?Ke7n5--9Um*A)Y1{5QMcZZJSuVsRc!j)`}S2k<{h9Ew(k9tNpcG#Oc zHswn@ew=ZUK$v9K#5$_A*g0-A6?J5aA*A~19y`Y?o75dOqS+i9|L$?%=ECQaO%h6u zHb=M|`82OO+zprx3_&(L;r*gl+fntrh~v(cHy!d?LFoja>S=Ym>_1)Vx33mzviA-Z zv}a^w#GR-Z{9WuF>+)nBc|Pzs{%qMoAjQfrc6SJLmUFO}G}D2V+uwnii3Oh1_@K5h OGBYtSFf#%PS^@yfD3yZ% literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-11.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-11.bin new file mode 100644 index 0000000000000000000000000000000000000000..183a7098924f18fc4913d5deb7a94f3162aa8b59 GIT binary patch literal 1460 zcmaFKF3Bj!$UWoQ|GD>C=Wb&)UgGlR8v}#kUj_!@|LpusZ2auJpXwPI-cM!t;=#bb zkb(bDC?mu68U{vB2F?i#{9S&G48G|GD>C=Wb&)UgGlR8v}#kUj_!@|7?PSB8>bzg7u6HLP89T{Gy`% z{}|ZWfh<|sXaNQe4zSpC2?hZH1_nkRo~b{~SXgVQ5tt$#a6A7O{txFC=%>QCTF)xEd%Q+H5FlDSlQemt@gz%|Cf9^5&zXh!~`$HcAHT{mHJvmIAvQ! z7NpC<<6X$YAR~)D(A$>0;2sP-BLAsEB|u8NpZt!yW+%8i?|M|etjgoPu5gDw0001h zI=lo%yG8{@`NhUXgp9^T;IY0tJ7wxSO*@u0`K#Wy08nPR01yZ|1c@-cWPC8a(?{ zsZn`+IUQdgj?en;>Hhr#ID7)*RMB?AYg3}D!REw$C`mx-%4!+eWWOuHwJK;_)wZ%6 zOvK3E)gq|R>{1c_7P7?FVt>E;hq}HY7{;GDyY9AncGq<^4YG?ETNqtPu@cLgo!qUYAkvD!>`O&i;@Sl&62&Dl zi|)F+$|e%ghl(zCrB4MFk`XAOV$z2qFC`V0#b&c-b~yb74ToVkGrxJBdEWPvc?xk5 zrLlXWxxcQtJ#JB(dw2|37y+CUFNz49GYC3-c;v-2BMK@pFn)&h?Pw|lP_5cQ6u#iY zVT>(ij|FI4e?5U?yFqNl=CqN^tmx+f4RoqRg2^xw?L?$5Kusgw5yJ{X;@AUI!B;SbKyk{wOBcl4bih8mxD zFps6rx~PE;4#rx?Cc$%mg4BA2H+gbC!4n@ky$D%QSc1`wLDn1*Z=9sR(JH~fNQkNr zc&g8M_e3gj!HbnC(QFe}wz1NaBGot@OpxG#8V-KpbzGJ}@O(d9t6^0!R*!QiA79w*dlCA?CRjlQ+sH0uf(ZoR_6;qbCQL9k;pYz1TUqQ0n Ang9R* literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-BUTN.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-BUTN.bin new file mode 100644 index 0000000000000000000000000000000000000000..6b98e9a8be7c78fbca5873e45f62d0af0aab3a45 GIT binary patch literal 45 xcmaFKF3Bj!$UNiP|GD>C=Wb&)UgGlR8v}#kUj_!@|4yMHehfemC9WF|!T^pS4@Cd~ literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-COLL.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-COLL.bin new file mode 100644 index 0000000000000000000000000000000000000000..9e725c18198617a4ccda8ac67265e06289a744dd GIT binary patch literal 45 zcmaFKF3Bj!$UNiP|GD>C=Wff5Pj_M2z`y{)!vCH9eSA2Dzp14&F))01GF=M*ec%u= literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FLBK.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FLBK.bin new file mode 100644 index 0000000000000000000000000000000000000000..d61bbd1c2d91f3d767736259ca965352dcd39bd6 GIT binary patch literal 45 zcmaFKF3Bj!$UNiP|GD>C=WaW6Yr4zIrwj}SUobET|9A6o@~(Nr!0=RKnhOIE007FF B5(EGM literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FTLP.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FTLP.bin new file mode 100644 index 0000000000000000000000000000000000000000..4ef9c8d86a52e2717c5d415ef4f6044dba87a2b3 GIT binary patch literal 45 xcmaFKF3Bj!$UNiP|GD>C=WhEb>Fts*i-7@zh5x&S_yhC=Wb&F0vkpM7XI(!?jHgYsSv!t5267v$O{Sp literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-OVTK.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-OVTK.bin new file mode 100644 index 0000000000000000000000000000000000000000..d9532f70bf0608c0f35ae7a3305369d72941f2f4 GIT binary patch literal 45 zcmV+|0Mh^I2N?ko0Rx!U|DE22owi+nphQ{m005r%000yJPgYb*1rh)NzI@0$mk*gg DrezVR literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-PENA.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-PENA.bin new file mode 100644 index 0000000000000000000000000000000000000000..97604be392595e936e42398c5077b6dfb3d8b117 GIT binary patch literal 45 zcmV+|0Mh^I2N?ko0Rx!U|DE22owjwOdqkSP006SV000yJP(@Bb5Geuw{|x`~yQ2lg Dwp|nz literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-RCWN.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-RCWN.bin new file mode 100644 index 0000000000000000000000000000000000000000..dd0b88db6fa4818f15d091c9c33c41164a7a66e0 GIT binary patch literal 45 ucmaFKF3Bj!$UHxMBFkBU2!)oMbC=Wb&)UgE+41;YP>UHx1*=190QLPY>ZUJE_| literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SPTP.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SPTP.bin new file mode 100644 index 0000000000000000000000000000000000000000..05093ae0379f7c8c1fa999d888ae610d15ce4152 GIT binary patch literal 45 zcmaFKF3Bj!$UNiP|GD>C=Wd(0dy&h^FANOtelaiz{|^ob31C{uJ<}P;5w&093;@AH B5zqht literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SSTA.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SSTA.bin new file mode 100644 index 0000000000000000000000000000000000000000..1efd014a84b485eaa07c87738deda4b2ba6f6271 GIT binary patch literal 45 ocmaFKF3Bj!$UNiP|GD>C=Wb&_0>b};gF_sdfg%N)lq5kk04G8UkN^Mx literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-STLG.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-STLG.bin new file mode 100644 index 0000000000000000000000000000000000000000..214628b6813a14739f9e392051a8504b15fc3da4 GIT binary patch literal 45 scmaFKF3Bj!$UNiP|GD>C=Wb&Ff)7wE{69Fv$DI`<#Kgdml%Jmi08YUSzW@LL literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-TMPT.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-TMPT.bin new file mode 100644 index 0000000000000000000000000000000000000000..57b12d5bfdd391789c71e349ec6f921ca5b8646e GIT binary patch literal 45 xcmaFKF3Bj!$UNiP|GD>C=WgpPYjxp0#lWC?mVrU|e~52D2sg)H7e)pM004(M4jljh literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3.bin new file mode 100644 index 0000000000000000000000000000000000000000..ccafd97d7bbb9d3a2234fd713de56fb5c8cd1b18 GIT binary patch literal 45 ucmaFKF3Bj!$eerTW!qG*U3Pa$*E=~1F))Cz@PDV!5I+VWh!WQg2Vnq9`wYAQ literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-4.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-4.bin new file mode 100644 index 0000000000000000000000000000000000000000..075e39997b8b6fc180c84507b2f377c62db26515 GIT binary patch literal 1284 zcmbW%&ubGw6bJA(-J~&#Rog%{By9*eNlKtCX~9Buk_^qZlbv;DW6?uz1&{X7|3FW@ z6h-jTgQ6GD;?@7bgFh~UCyDf=Ab4nB-?E!FflQ0eac4h#XWo0ey-c1>veb)Dc<}AX z!Ta>$>(=2PfcYbU98R%kn1M~VA-L{vA8d=!20F*fW$Z6DNw8E@O=VYvM!+nNL3#R~ z&?e+TAS^tF)zz)vbLFG2K(TBU9k?EF**G^chBqF5fte45!gd6=C~>+Pny#-}+;+v} zs6&ZMcnMOjueA1z)g=&jt4gU_trjJ&U>e-w8aML3-6as7{=&CEy1rOLZAm_Y_}Zku;JNvE~Z7=ABZ#lt&E;tnOwE=j&=>}+IW4pT5S zVM48>wB_~T%KqM8e4NkcqvALOuXMOnzES@KU(oL zhf0cz;9xrzXaH`zx@!#e-;&qArD#f_o98An VXRwda$4tKys~Fm#cTd-U!xKYfA4UKG literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-5.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-5.bin new file mode 100644 index 0000000000000000000000000000000000000000..44f71f380e8018686ad1ee5e488c84f60b153ae4 GIT binary patch literal 1133 zcmaFKF3Bj!$U5WN|GD>C=Wb&)UgGlR8v}#kUj_!@|Dxhi<_ru82N)O_4sc&zY%}4| zS6fwXer`$q6f2{&v{jBEJZt7bFlJ+5P;ea8F&b8*sbpaDhq$<$`K+0d2aJtB?+4~u c+c$w0cB;bsoRUU~SaYq66EMxv0qk%D02OUmC=Wb&)UgGlR8v}#kUj_!@|1k_8@Xg`HJ}`r+ULHgnF`F=J zGaE36LO~uwTG}c{5S}&jAQ&@(l|zXJWR5Tc1S$c6CJg8KinT zSj$CaQ9Ce$rAHY=8!;O)t1~Mx{$n!V0A7 zCJ;B+d+cYh0r41EZ|N|){;#)UvSEs6O2Xq`Mg~TP7=}7FDSP$36QFT4T>)$cGZQl# zGZz7S)xcpT!L!E-tcOEYlTnF*VJYJ>#!HM>8S(m;A)UeGy0AUd^&Y6b7ga!Ja53{R zb2AIz4=b=`V4?xRWMB}1LJ26q9={@BH=D0lv<2&BpRLVc@qlq6<0QrvjB6MJpa8pG zBe356V$yrTdO0R&G3X~S90$VB3?CWr>-_}O(qM1!gJm~ZFI$x^gU=-R(KVpn2K&`XHIT4kU8l?7_nsepjq-waVRD(ovJZ-$2R84U(m1_o;; zOQv8ZKc)ch055MZFHh{@oWqb|@zJg#wh@vSm`GCMGPGP@E;lZN210y!M) zagKFb3KftG&VWZ$`$5U#n9B<4$=(v2ren+tCfg=7h002jX BQrrLl literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-7.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-7.bin new file mode 100644 index 0000000000000000000000000000000000000000..0d1242b4e6a01b535001befcc7dd00578eafa0f3 GIT binary patch literal 1239 zcmcK3>q`_-7zXh7I6F!zy1TB}l}wG)O1zaIl3Ll!d-`A>%B;*XTg^-|Q^HuGA}`%6 ziy$&Xa*a|;3NJ)hcC&kQ(^jeltw19wYYMGon>jnJ+aJ)E;moqXeV%jPvx9Dvh>p0O z=&{vUOKaNrDD&b^KL8@e0DNpfNGyb&&PxP%voJVzWy0*JTJH$}pwqcPmsl^#OMsw4 zN-lM@<2|z{9h}>Q;G!_s;TFcM`)iyS1NKLdgivg$75i_P#23$F1Yl{+?e;WJ$aOHi z-W6VbA*eWW(RxbOF0T`9{l1tGyIZcHq=|+SmiBV#E(g=;IPeM9U_t6fVaf(y%Jmv0 zJZbQz}Lig+lDg4>KTDz^v$bk$$RT^85=3d zb}%LF_o6n&3XO4;obE87&HJ_Jp8QKKX>}`Ota8>72UF5+-sdr=`>?RHB9oH75DB-| zdQqwRx?0jT(9U|sDphui4d99PGD{158$wAzwuJMK@zfB0S7p{C_L4!5Qk~K?P(4qB zE$(Ka*Gt};zO)H*lKklLf<~2F&-LHKd`jOnaPy+&y?`sdoS9T)T563 z^D(c5$}O=3A97;c_`Ky?GXBC=$^bko?Q?_ldE|FF1vUj7EaiXr(VzhB#rmMRJJ^zAA;8*d+XbkyH*1L*Y{$hob-rlbSH z(TkfX5$~Kw&eMa=`glQQ`KNZVv=MisBTcIX2sLupSX3+|rew)~oCa@Pc3>k8XGg27 zb=1Vzl7aCf(djhsICqqdyUQ9*iQm9<9DLd)W?K4`59K2f?yq93(B%J5+R8qj&Fx*u e6(q^G(hvtH(kyE$+c29tdwfc7?i_#n534_7zBkeU literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-8.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-8.bin new file mode 100644 index 0000000000000000000000000000000000000000..bbfb71868becfaefe90eed93947fffaff3bd91fe GIT binary patch literal 1042 zcmcK3Jxjwt7zgmXT%IIN)8)uo%04h~%u1lOwI;@~KPgR`9+EL6ln zp$NgQ`U!Lp#Gy`#APz!(?#1=l%yQ)T@#lZfB_p@SWA2@<9NQ1kYJFz*rpe5cjx!HR zHWSt$;%e`BLn`{v3ZXnWvJ&9rMpjGq_#jLsCO$$@6Ua7sB8fcqHbyZd=`?~ z5hSw7>xAz8_D<>)@Fc?8$aE8r?@&!DP?dT*Rl0sKAhR8?Jk%mqI^SxMI)PYb7n8cl z@Q2hKcn$TknaSNfIYAdZ2lX&i*+s8SW@nJeCQUuT_M$`1qy&M5dQ*QV`Dl;K25@}T zOI6jRyWSvm6)zt3p>9%~Ka#o(zSLLxcJlrusRJlW{cY~DaZPF;aMHp$GTA3RQrqAL bs4w&>+0+85L$GC&Ppyl!7gC!a?q8N)1hrT( literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/package.json b/DebugTooling/F1-Telemetry-Fixture-Capture/package.json new file mode 100644 index 0000000..ded401d --- /dev/null +++ b/DebugTooling/F1-Telemetry-Fixture-Capture/package.json @@ -0,0 +1,14 @@ +{ + "name": "F1-Telemetry-Fixture-Capture", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "capture": "node capture.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.11.0", + "type": "module" +} diff --git a/GamesDat.Demo.Wpf/GamesDat.Demo.Wpf.csproj b/GamesDat.Demo.Wpf/GamesDat.Demo.Wpf.csproj index 566fa83..f4ca665 100644 --- a/GamesDat.Demo.Wpf/GamesDat.Demo.Wpf.csproj +++ b/GamesDat.Demo.Wpf/GamesDat.Demo.Wpf.csproj @@ -1,7 +1,7 @@  - WinExe + Exe net9.0-windows enable enable diff --git a/GamesDat.Demo.Wpf/Properties/launchSettings.json b/GamesDat.Demo.Wpf/Properties/launchSettings.json index 153c610..f796aca 100644 --- a/GamesDat.Demo.Wpf/Properties/launchSettings.json +++ b/GamesDat.Demo.Wpf/Properties/launchSettings.json @@ -1,7 +1,10 @@ { "profiles": { "GamesDat.Demo.Wpf": { - "commandName": "Project" + "commandName": "Project", + "environmentVariables": { + "DEBUG": "1" + } } } } \ No newline at end of file diff --git a/GamesDat.Demo.Wpf/Resources/Styles.xaml b/GamesDat.Demo.Wpf/Resources/Styles.xaml index d9d73a7..41796c9 100644 --- a/GamesDat.Demo.Wpf/Resources/Styles.xaml +++ b/GamesDat.Demo.Wpf/Resources/Styles.xaml @@ -3,6 +3,8 @@ xmlns:converters="clr-namespace:GamesDat.Demo.Wpf.Converters"> + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GamesDat/Telemetry/Sources/Formula1/Docs/F1_2022_spec.md b/GamesDat/Telemetry/Sources/Formula1/Docs/F1_2022_spec.md new file mode 100644 index 0000000..94c90ee --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/Docs/F1_2022_spec.md @@ -0,0 +1,1226 @@ +Packet Information + +Packet Types + +Each packet carries different types of data rather than having one packet which contains everything. The header in each packet describes the packet type and versioning info so it will be easier for applications to check they are interpreting the incoming data in the correct way. Please note that all values are encoded using Little Endian format. All data is packed. + +The following data types are used in the structures: + +Type Description +uint8 Unsigned 8-bit integer +int8 Signed 8-bit integer +uint16 Unsigned 16-bit integer +int16 Signed 16-bit integer +uint32 Unsigned 32-bit integer +float Floating point (32-bit) +uint64 Unsigned 64-bit integer + + +Packet Header + +Each packet has the following header: + +struct PacketHeader +{ + uint16 m_packetFormat; // 2022 + uint8 m_gameMajorVersion; // Game major version - "X.00" + uint8 m_gameMinorVersion; // Game minor version - "1.XX" + uint8 m_packetVersion; // Version of this packet type, all start from 1 + uint8 m_packetId; // Identifier for the packet type, see below + uint64 m_sessionUID; // Unique identifier for the session + float m_sessionTime; // Session timestamp + uint32 m_frameIdentifier; // Identifier for the frame the data was retrieved on + uint8 m_playerCarIndex; // Index of player's car in the array + uint8 m_secondaryPlayerCarIndex; // Index of secondary player's car in the array (splitscreen) + // 255 if no second player +}; + + +Packet IDs + +The packets IDs are as follows: + +Packet Name Value Description +Motion 0 Contains all motion data for player’s car – only sent while player is in control +Session 1 Data about the session – track, time left +Lap Data 2 Data about all the lap times of cars in the session +Event 3 Various notable events that happen during a session +Participants 4 List of participants in the session, mostly relevant for multiplayer +Car Setups 5 Packet detailing car setups for cars in the race +Car Telemetry 6 Telemetry data for all cars +Car Status 7 Status data for all cars +Final Classification 8 Final classification confirmation at the end of a race +Lobby Info 9 Information about players in a multiplayer lobby +Car Damage 10 Damage status for all cars +Session History 11 Lap and tyre data for session + + +Motion Packet + +The motion packet gives physics data for all the cars being driven. There is additional data for the car being driven with the goal of being able to drive a motion platform setup. +N.B. For the normalised vectors below, to convert to float values divide by 32767.0f – 16-bit signed values are used to pack the data and on the assumption that direction values are always between -1.0f and 1.0f. + +Frequency: Rate as specified in menus +Size: 1464 bytes +Version: 1 + +struct CarMotionData +{ + float m_worldPositionX; // World space X position + float m_worldPositionY; // World space Y position + float m_worldPositionZ; // World space Z position + float m_worldVelocityX; // Velocity in world space X + float m_worldVelocityY; // Velocity in world space Y + float m_worldVelocityZ; // Velocity in world space Z + int16 m_worldForwardDirX; // World space forward X direction (normalised) + int16 m_worldForwardDirY; // World space forward Y direction (normalised) + int16 m_worldForwardDirZ; // World space forward Z direction (normalised) + int16 m_worldRightDirX; // World space right X direction (normalised) + int16 m_worldRightDirY; // World space right Y direction (normalised) + int16 m_worldRightDirZ; // World space right Z direction (normalised) + float m_gForceLateral; // Lateral G-Force component + float m_gForceLongitudinal; // Longitudinal G-Force component + float m_gForceVertical; // Vertical G-Force component + float m_yaw; // Yaw angle in radians + float m_pitch; // Pitch angle in radians + float m_roll; // Roll angle in radians +}; + +struct PacketMotionData +{ + PacketHeader m_header; // Header + + CarMotionData m_carMotionData[22]; // Data for all cars on track + + // Extra player car ONLY data + float m_suspensionPosition[4]; // Note: All wheel arrays have the following order: + float m_suspensionVelocity[4]; // RL, RR, FL, FR + float m_suspensionAcceleration[4]; // RL, RR, FL, FR + float m_wheelSpeed[4]; // Speed of each wheel + float m_wheelSlip[4]; // Slip ratio for each wheel + float m_localVelocityX; // Velocity in local space + float m_localVelocityY; // Velocity in local space + float m_localVelocityZ; // Velocity in local space + float m_angularVelocityX; // Angular velocity x-component + float m_angularVelocityY; // Angular velocity y-component + float m_angularVelocityZ; // Angular velocity z-component + float m_angularAccelerationX; // Angular velocity x-component + float m_angularAccelerationY; // Angular velocity y-component + float m_angularAccelerationZ; // Angular velocity z-component + float m_frontWheelsAngle; // Current front wheels angle in radians +}; + + +Session Packet + +The session packet includes details about the current session in progress. + +Frequency: 2 per second +Size: 632 bytes +Version: 1 + +struct MarshalZone +{ + float m_zoneStart; // Fraction (0..1) of way through the lap the marshal zone starts + int8 m_zoneFlag; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow, 4 = red +}; + +struct WeatherForecastSample +{ + uint8 m_sessionType; // 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P, 5 = Q1 + // 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ, 10 = R, 11 = R2 + // 12 = R3, 13 = Time Trial + uint8 m_timeOffset; // Time in minutes the forecast is for + uint8 m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast + // 3 = light rain, 4 = heavy rain, 5 = storm + int8 m_trackTemperature; // Track temp. in degrees Celsius + int8 m_trackTemperatureChange; // Track temp. change – 0 = up, 1 = down, 2 = no change + int8 m_airTemperature; // Air temp. in degrees celsius + int8 m_airTemperatureChange; // Air temp. change – 0 = up, 1 = down, 2 = no change + uint8 m_rainPercentage; // Rain percentage (0-100) +}; + +struct PacketSessionData +{ + PacketHeader m_header; // Header + + uint8 m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast + // 3 = light rain, 4 = heavy rain, 5 = storm + int8 m_trackTemperature; // Track temp. in degrees celsius + int8 m_airTemperature; // Air temp. in degrees celsius + uint8 m_totalLaps; // Total number of laps in this race + uint16 m_trackLength; // Track length in metres + uint8 m_sessionType; // 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P + // 5 = Q1, 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ + // 10 = R, 11 = R2, 12 = R3, 13 = Time Trial + int8 m_trackId; // -1 for unknown, see appendix + uint8 m_formula; // Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2, + // 3 = F1 Generic, 4 = Beta, 5 = Supercars +// 6 = Esports, 7 = F2 2021 + uint16 m_sessionTimeLeft; // Time left in session in seconds + uint16 m_sessionDuration; // Session duration in seconds + uint8 m_pitSpeedLimit; // Pit speed limit in kilometres per hour + uint8 m_gamePaused; // Whether the game is paused – network game only + uint8 m_isSpectating; // Whether the player is spectating + uint8 m_spectatorCarIndex; // Index of the car being spectated + uint8 m_sliProNativeSupport; // SLI Pro support, 0 = inactive, 1 = active + uint8 m_numMarshalZones; // Number of marshal zones to follow + MarshalZone m_marshalZones[21]; // List of marshal zones – max 21 + uint8 m_safetyCarStatus; // 0 = no safety car, 1 = full + // 2 = virtual, 3 = formation lap + uint8 m_networkGame; // 0 = offline, 1 = online + uint8 m_numWeatherForecastSamples; // Number of weather samples to follow + WeatherForecastSample m_weatherForecastSamples[56]; // Array of weather forecast samples + uint8 m_forecastAccuracy; // 0 = Perfect, 1 = Approximate + uint8 m_aiDifficulty; // AI Difficulty rating – 0-110 + uint32 m_seasonLinkIdentifier; // Identifier for season - persists across saves + uint32 m_weekendLinkIdentifier; // Identifier for weekend - persists across saves + uint32 m_sessionLinkIdentifier; // Identifier for session - persists across saves + uint8 m_pitStopWindowIdealLap; // Ideal lap to pit on for current strategy (player) + uint8 m_pitStopWindowLatestLap; // Latest lap to pit on for current strategy (player) + uint8 m_pitStopRejoinPosition; // Predicted position to rejoin at (player) + uint8 m_steeringAssist; // 0 = off, 1 = on + uint8 m_brakingAssist; // 0 = off, 1 = low, 2 = medium, 3 = high + uint8 m_gearboxAssist; // 1 = manual, 2 = manual & suggested gear, 3 = auto + uint8 m_pitAssist; // 0 = off, 1 = on + uint8 m_pitReleaseAssist; // 0 = off, 1 = on + uint8 m_ERSAssist; // 0 = off, 1 = on + uint8 m_DRSAssist; // 0 = off, 1 = on + uint8 m_dynamicRacingLine; // 0 = off, 1 = corners only, 2 = full + uint8 m_dynamicRacingLineType; // 0 = 2D, 1 = 3D + uint8 m_gameMode; // Game mode id - see appendix + uint8 m_ruleSet; // Ruleset - see appendix + uint32 m_timeOfDay; // Local time of day - minutes since midnight + uint8 m_sessionLength; // 0 = None, 2 = Very Short, 3 = Short, 4 = Medium +// 5 = Medium Long, 6 = Long, 7 = Full +}; + + +Lap Data Packet + +The lap data packet gives details of all the cars in the session. + +Frequency: Rate as specified in menus +Size: 972 bytes +Version: 1 + +struct LapData +{ + uint32 m_lastLapTimeInMS; // Last lap time in milliseconds + uint32 m_currentLapTimeInMS; // Current time around the lap in milliseconds + uint16 m_sector1TimeInMS; // Sector 1 time in milliseconds + uint16 m_sector2TimeInMS; // Sector 2 time in milliseconds + float m_lapDistance; // Distance vehicle is around current lap in metres – could + // be negative if line hasn’t been crossed yet + float m_totalDistance; // Total distance travelled in session in metres – could + // be negative if line hasn’t been crossed yet + float m_safetyCarDelta; // Delta in seconds for safety car + uint8 m_carPosition; // Car race position + uint8 m_currentLapNum; // Current lap number + uint8 m_pitStatus; // 0 = none, 1 = pitting, 2 = in pit area + uint8 m_numPitStops; // Number of pit stops taken in this race + uint8 m_sector; // 0 = sector1, 1 = sector2, 2 = sector3 + uint8 m_currentLapInvalid; // Current lap invalid - 0 = valid, 1 = invalid + uint8 m_penalties; // Accumulated time penalties in seconds to be added + uint8 m_warnings; // Accumulated number of warnings issued + uint8 m_numUnservedDriveThroughPens; // Num drive through pens left to serve + uint8 m_numUnservedStopGoPens; // Num stop go pens left to serve + uint8 m_gridPosition; // Grid position the vehicle started the race in + uint8 m_driverStatus; // Status of driver - 0 = in garage, 1 = flying lap + // 2 = in lap, 3 = out lap, 4 = on track + uint8 m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active + // 3 = finished, 4 = didnotfinish, 5 = disqualified + // 6 = not classified, 7 = retired + uint8 m_pitLaneTimerActive; // Pit lane timing, 0 = inactive, 1 = active + uint16 m_pitLaneTimeInLaneInMS; // If active, the current time spent in the pit lane in ms + uint16 m_pitStopTimerInMS; // Time of the actual pit stop in ms + uint8 m_pitStopShouldServePen; // Whether the car should serve a penalty at this stop +}; + + +struct PacketLapData +{ + PacketHeader m_header; // Header + + LapData m_lapData[22]; // Lap data for all cars on track + + uint8 m_timeTrialPBCarIdx; // Index of Personal Best car in time trial (255 if invalid) + uint8 m_timeTrialRivalCarIdx; // Index of Rival car in time trial (255 if invalid) +}; + + +Event Packet + +This packet gives details of events that happen during the course of a session. + +Frequency: When the event occurs +Size: 40 bytes +Version: 1 + +// The event details packet is different for each type of event. +// Make sure only the correct type is interpreted. +union EventDataDetails +{ + struct + { + uint8 vehicleIdx; // Vehicle index of car achieving fastest lap + float lapTime; // Lap time is in seconds + } FastestLap; + + struct + { + uint8 vehicleIdx; // Vehicle index of car retiring + } Retirement; + + struct + { + uint8 vehicleIdx; // Vehicle index of team mate + } TeamMateInPits; + + struct + { + uint8 vehicleIdx; // Vehicle index of the race winner + } RaceWinner; + + struct + { + uint8 penaltyType; // Penalty type – see Appendices + uint8 infringementType; // Infringement type – see Appendices + uint8 vehicleIdx; // Vehicle index of the car the penalty is applied to + uint8 otherVehicleIdx; // Vehicle index of the other car involved + uint8 time; // Time gained, or time spent doing action in seconds + uint8 lapNum; // Lap the penalty occurred on + uint8 placesGained; // Number of places gained by this + } Penalty; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle triggering speed trap + float speed; // Top speed achieved in kilometres per hour + uint8 isOverallFastestInSession; // Overall fastest speed in session = 1, otherwise 0 + uint8 isDriverFastestInSession; // Fastest speed for driver in session = 1, otherwise 0 + uint8 fastestVehicleIdxInSession;// Vehicle index of the vehicle that is the fastest +// in this session + float fastestSpeedInSession; // Speed of the vehicle that is the fastest + // in this session + } SpeedTrap; + + struct + { + uint8 numLights; // Number of lights showing + } StartLIghts; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle serving drive through + } DriveThroughPenaltyServed; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle serving stop go + } StopGoPenaltyServed; + + struct + { + uint32 flashbackFrameIdentifier; // Frame identifier flashed back to + float flashbackSessionTime; // Session time flashed back to + } Flashback; + + struct + { + uint32 m_buttonStatus; // Bit flags specifying which buttons are being pressed + // currently - see appendices + } Buttons; +}; + +struct PacketEventData +{ + PacketHeader m_header; // Header + + uint8 m_eventStringCode[4]; // Event string code, see below + EventDataDetails m_eventDetails; // Event details - should be interpreted differently + // for each type +}; + + +Event String Codes + +Event Code Description +Session Started “SSTA” Sent when the session starts +Session Ended “SEND” Sent when the session ends +Fastest Lap “FTLP” When a driver achieves the fastest lap +Retirement “RTMT” When a driver retires +DRS enabled “DRSE” Race control have enabled DRS +DRS disabled “DRSD” Race control have disabled DRS +Team mate in pits “TMPT” Your team mate has entered the pits +Chequered flag “CHQF” The chequered flag has been waved +Race Winner “RCWN” The race winner is announced +Penalty Issued “PENA” A penalty has been issued – details in event +Speed Trap Triggered “SPTP” Speed trap has been triggered by fastest speed +Start lights “STLG” Start lights – number shown +Lights out “LGOT” Lights out +Drive through served “DTSV” Drive through penalty served +Stop go served “SGSV” Stop go penalty served +Flashback “FLBK” Flashback activated +Button status “BUTN” Button status changed + + +Participants Packet + +This is a list of participants in the race. If the vehicle is controlled by AI, then the name will be the driver name. If this is a multiplayer game, the names will be the Steam Id on PC, or the LAN name if appropriate. + +N.B. on Xbox One, the names will always be the driver name, on PS4 the name will be the LAN name if playing a LAN game, otherwise it will be the driver name. + +The array should be indexed by vehicle index. + +Frequency: Every 5 seconds +Size: 1257 bytes +Version: 1 + +struct ParticipantData +{ + uint8 m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + uint8 m_driverId; // Driver id - see appendix, 255 if network human + uint8 m_networkId; // Network id – unique identifier for network players + uint8 m_teamId; // Team id - see appendix + uint8 m_myTeam; // My team flag – 1 = My Team, 0 = otherwise + uint8 m_raceNumber; // Race number of the car + uint8 m_nationality; // Nationality of the driver + char m_name[48]; // Name of participant in UTF-8 format – null terminated + // Will be truncated with … (U+2026) if too long + uint8 m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public +}; + +struct PacketParticipantsData +{ + PacketHeader m_header; // Header + + uint8 m_numActiveCars; // Number of active cars in the data – should match number of + // cars on HUD + ParticipantData m_participants[22]; +}; + + +Car Setups Packet + +This packet details the car setups for each vehicle in the session. Note that in multiplayer games, other player cars will appear as blank, you will only be able to see your car setup and AI cars. + +Frequency: 2 per second +Size: 1102 bytes +Version: 1 + +struct CarSetupData +{ + uint8 m_frontWing; // Front wing aero + uint8 m_rearWing; // Rear wing aero + uint8 m_onThrottle; // Differential adjustment on throttle (percentage) + uint8 m_offThrottle; // Differential adjustment off throttle (percentage) + float m_frontCamber; // Front camber angle (suspension geometry) + float m_rearCamber; // Rear camber angle (suspension geometry) + float m_frontToe; // Front toe angle (suspension geometry) + float m_rearToe; // Rear toe angle (suspension geometry) + uint8 m_frontSuspension; // Front suspension + uint8 m_rearSuspension; // Rear suspension + uint8 m_frontAntiRollBar; // Front anti-roll bar + uint8 m_rearAntiRollBar; // Front anti-roll bar + uint8 m_frontSuspensionHeight; // Front ride height + uint8 m_rearSuspensionHeight; // Rear ride height + uint8 m_brakePressure; // Brake pressure (percentage) + uint8 m_brakeBias; // Brake bias (percentage) + float m_rearLeftTyrePressure; // Rear left tyre pressure (PSI) + float m_rearRightTyrePressure; // Rear right tyre pressure (PSI) + float m_frontLeftTyrePressure; // Front left tyre pressure (PSI) + float m_frontRightTyrePressure; // Front right tyre pressure (PSI) + uint8 m_ballast; // Ballast + float m_fuelLoad; // Fuel load +}; + +struct PacketCarSetupData +{ + PacketHeader m_header; // Header + + CarSetupData m_carSetups[22]; +}; + + +Car Telemetry Packet + +This packet details telemetry for all the cars in the race. It details various values that would be recorded on the car such as speed, throttle application, DRS etc. Note that the rev light configurations are presented separately as well and will mimic real life driver preferences. + +Frequency: Rate as specified in menus +Size: 1347 bytes +Version: 1 + +struct CarTelemetryData +{ + uint16 m_speed; // Speed of car in kilometres per hour + float m_throttle; // Amount of throttle applied (0.0 to 1.0) + float m_steer; // Steering (-1.0 (full lock left) to 1.0 (full lock right)) + float m_brake; // Amount of brake applied (0.0 to 1.0) + uint8 m_clutch; // Amount of clutch applied (0 to 100) + int8 m_gear; // Gear selected (1-8, N=0, R=-1) + uint16 m_engineRPM; // Engine RPM + uint8 m_drs; // 0 = off, 1 = on + uint8 m_revLightsPercent; // Rev lights indicator (percentage) + uint16 m_revLightsBitValue; // Rev lights (bit 0 = leftmost LED, bit 14 = rightmost LED) + uint16 m_brakesTemperature[4]; // Brakes temperature (celsius) + uint8 m_tyresSurfaceTemperature[4]; // Tyres surface temperature (celsius) + uint8 m_tyresInnerTemperature[4]; // Tyres inner temperature (celsius) + uint16 m_engineTemperature; // Engine temperature (celsius) + float m_tyresPressure[4]; // Tyres pressure (PSI) + uint8 m_surfaceType[4]; // Driving surface, see appendices +}; + +struct PacketCarTelemetryData +{ + PacketHeader m_header; // Header + + CarTelemetryData m_carTelemetryData[22]; + + uint8 m_mfdPanelIndex; // Index of MFD panel open - 255 = MFD closed + // Single player, race – 0 = Car setup, 1 = Pits + // 2 = Damage, 3 = Engine, 4 = Temperatures + // May vary depending on game mode + uint8 m_mfdPanelIndexSecondaryPlayer; // See above + int8 m_suggestedGear; // Suggested gear for the player (1-8) + // 0 if no gear suggested +}; + + +Car Status Packet + +This packet details car statuses for all the cars in the race. + +Frequency: Rate as specified in menus +Size: 1058 bytes +Version: 1 + +struct CarStatusData +{ + uint8 m_tractionControl; // Traction control - 0 = off, 1 = medium, 2 = full + uint8 m_antiLockBrakes; // 0 (off) - 1 (on) + uint8 m_fuelMix; // Fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max + uint8 m_frontBrakeBias; // Front brake bias (percentage) + uint8 m_pitLimiterStatus; // Pit limiter status - 0 = off, 1 = on + float m_fuelInTank; // Current fuel mass + float m_fuelCapacity; // Fuel capacity + float m_fuelRemainingLaps; // Fuel remaining in terms of laps (value on MFD) + uint16 m_maxRPM; // Cars max RPM, point of rev limiter + uint16 m_idleRPM; // Cars idle RPM + uint8 m_maxGears; // Maximum number of gears + uint8 m_drsAllowed; // 0 = not allowed, 1 = allowed + uint16 m_drsActivationDistance; // 0 = DRS not available, non-zero - DRS will be available + // in [X] metres + uint8 m_actualTyreCompound; // F1 Modern - 16 = C5, 17 = C4, 18 = C3, 19 = C2, 20 = C1 + // 7 = inter, 8 = wet + // F1 Classic - 9 = dry, 10 = wet + // F2 – 11 = super soft, 12 = soft, 13 = medium, 14 = hard + // 15 = wet + uint8 m_visualTyreCompound; // F1 visual (can be different from actual compound) + // 16 = soft, 17 = medium, 18 = hard, 7 = inter, 8 = wet + // F1 Classic – same as above + // F2 ‘19, 15 = wet, 19 – super soft, 20 = soft + // 21 = medium , 22 = hard + uint8 m_tyresAgeLaps; // Age in laps of the current set of tyres + int8 m_vehicleFiaFlags; // -1 = invalid/unknown, 0 = none, 1 = green + // 2 = blue, 3 = yellow, 4 = red + float m_ersStoreEnergy; // ERS energy store in Joules + uint8 m_ersDeployMode; // ERS deployment mode, 0 = none, 1 = medium + // 2 = hotlap, 3 = overtake + float m_ersHarvestedThisLapMGUK; // ERS energy harvested this lap by MGU-K + float m_ersHarvestedThisLapMGUH; // ERS energy harvested this lap by MGU-H + float m_ersDeployedThisLap; // ERS energy deployed this lap + uint8 m_networkPaused; // Whether the car is paused in a network game +}; + +struct PacketCarStatusData +{ + PacketHeader m_header; // Header + + CarStatusData m_carStatusData[22]; +}; + +Final Classification Packet + +This packet details the final classification at the end of the race, and the data will match with the post race results screen. This is especially useful for multiplayer games where it is not always possible to send lap times on the final frame because of network delay. + +Frequency: Once at the end of a race +Size: 1015 bytes +Version: 1 + +struct FinalClassificationData +{ + uint8 m_position; // Finishing position + uint8 m_numLaps; // Number of laps completed + uint8 m_gridPosition; // Grid position of the car + uint8 m_points; // Number of points scored + uint8 m_numPitStops; // Number of pit stops made + uint8 m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active + // 3 = finished, 4 = didnotfinish, 5 = disqualified + // 6 = not classified, 7 = retired + uint32 m_bestLapTimeInMS; // Best lap time of the session in milliseconds + double m_totalRaceTime; // Total race time in seconds without penalties + uint8 m_penaltiesTime; // Total penalties accumulated in seconds + uint8 m_numPenalties; // Number of penalties applied to this driver + uint8 m_numTyreStints; // Number of tyres stints up to maximum + uint8 m_tyreStintsActual[8]; // Actual tyres used by this driver + uint8 m_tyreStintsVisual[8]; // Visual tyres used by this driver + uint8 m_tyreStintsEndLaps[8]; // The lap number stints end on +}; + +struct PacketFinalClassificationData +{ + PacketHeader m_header; // Header + + uint8 m_numCars; // Number of cars in the final classification + FinalClassificationData m_classificationData[22]; +}; + +Lobby Info Packet + +This packet details the players currently in a multiplayer lobby. It details each player’s selected car, any AI involved in the game and also the ready status of each of the participants. + +Frequency: Two every second when in the lobby +Size: 1191 bytes +Version: 1 + +struct LobbyInfoData +{ + uint8 m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + uint8 m_teamId; // Team id - see appendix (255 if no team currently selected) + uint8 m_nationality; // Nationality of the driver + char m_name[48]; // Name of participant in UTF-8 format – null terminated + // Will be truncated with ... (U+2026) if too long + uint8 m_carNumber; // Car number of the player + uint8 m_readyStatus; // 0 = not ready, 1 = ready, 2 = spectating +}; + +struct PacketLobbyInfoData +{ + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_numPlayers; // Number of players in the lobby data + LobbyInfoData m_lobbyPlayers[22]; +}; + +Car Damage Packet + +This packet details car damage parameters for all the cars in the race. + +Frequency: 2 per second +Size: 948 bytes +Version: 1 + +struct CarDamageData +{ + float m_tyresWear[4]; // Tyre wear (percentage) + uint8 m_tyresDamage[4]; // Tyre damage (percentage) + uint8 m_brakesDamage[4]; // Brakes damage (percentage) + uint8 m_frontLeftWingDamage; // Front left wing damage (percentage) + uint8 m_frontRightWingDamage; // Front right wing damage (percentage) + uint8 m_rearWingDamage; // Rear wing damage (percentage) + uint8 m_floorDamage; // Floor damage (percentage) + uint8 m_diffuserDamage; // Diffuser damage (percentage) + uint8 m_sidepodDamage; // Sidepod damage (percentage) + uint8 m_drsFault; // Indicator for DRS fault, 0 = OK, 1 = fault + uint8 m_ersFault; // Indicator for ERS fault, 0 = OK, 1 = fault + uint8 m_gearBoxDamage; // Gear box damage (percentage) + uint8 m_engineDamage; // Engine damage (percentage) + uint8 m_engineMGUHWear; // Engine wear MGU-H (percentage) + uint8 m_engineESWear; // Engine wear ES (percentage) + uint8 m_engineCEWear; // Engine wear CE (percentage) + uint8 m_engineICEWear; // Engine wear ICE (percentage) + uint8 m_engineMGUKWear; // Engine wear MGU-K (percentage) + uint8 m_engineTCWear; // Engine wear TC (percentage) + uint8 m_engineBlown; // Engine blown, 0 = OK, 1 = fault + uint8 m_engineSeized; // Engine seized, 0 = OK, 1 = fault +} + +struct PacketCarDamageData +{ + PacketHeader m_header; // Header + + CarDamageData m_carDamageData[22]; +}; + +Session History Packet + +This packet contains lap times and tyre usage for the session. This packet works slightly differently to other packets. To reduce CPU and bandwidth, each packet relates to a specific vehicle and is sent every 1/20 s, and the vehicle being sent is cycled through. Therefore in a 20 car race you should receive an update for each vehicle at least once per second. + +Note that at the end of the race, after the final classification packet has been sent, a final bulk update of all the session histories for the vehicles in that session will be sent. + +Frequency: 20 per second but cycling through cars +Size: 1155 bytes +Version: 1 + +struct LapHistoryData +{ + uint32 m_lapTimeInMS; // Lap time in milliseconds + uint16 m_sector1TimeInMS; // Sector 1 time in milliseconds + uint16 m_sector2TimeInMS; // Sector 2 time in milliseconds + uint16 m_sector3TimeInMS; // Sector 3 time in milliseconds + uint8 m_lapValidBitFlags; // 0x01 bit set-lap valid, 0x02 bit set-sector 1 valid + // 0x04 bit set-sector 2 valid, 0x08 bit set-sector 3 valid +}; + +struct TyreStintHistoryData +{ + uint8 m_endLap; // Lap the tyre usage ends on (255 of current tyre) + uint8 m_tyreActualCompound; // Actual tyres used by this driver + uint8 m_tyreVisualCompound; // Visual tyres used by this driver +}; + +struct PacketSessionHistoryData +{ + PacketHeader m_header; // Header + + uint8 m_carIdx; // Index of the car this lap data relates to + uint8 m_numLaps; // Num laps in the data (including current partial lap) + uint8 m_numTyreStints; // Number of tyre stints in the data + + uint8 m_bestLapTimeLapNum; // Lap the best lap time was achieved on + uint8 m_bestSector1LapNum; // Lap the best Sector 1 time was achieved on + uint8 m_bestSector2LapNum; // Lap the best Sector 2 time was achieved on + uint8 m_bestSector3LapNum; // Lap the best Sector 3 time was achieved on + + LapHistoryData m_lapHistoryData[100]; // 100 laps of data max + TyreStintHistoryData m_tyreStintsHistoryData[8]; +}; +  +Restricted data (Your Telemetry setting) + +There is some data in the UDP that you may not want other players seeing if you are in a multiplayer game. This is controlled by the “Your Telemetry” setting in the Telemetry options. The options are: + +• Restricted (Default) – other players viewing the UDP data will not see values for your car +• Public – all other players can see all the data for your car +• Show online ID – this additional option allows other players to view your online ID / gamertag in their UDP output. + +Note: You can always see the data for the car you are driving regardless of the setting. + +The following data items are set to zero if the player driving the car in question has their “Your Telemetry” set to “Restricted”: + +Car status packet + +• m_fuelInTank +• m_fuelCapacity +• m_fuelMix +• m_fuelRemainingLaps +• m_frontBrakeBias +• m_ersDeployMode +• m_ersStoreEnergy +• m_ersDeployedThisLap +• m_ersHarvestedThisLapMGUK +• m_ersHarvestedThisLapMGUH + +Car damage packet + +• m_frontLeftWingDamage +• m_frontRightWingDamage +• m_rearWingDamage +• m_floorDamage +• m_diffuserDamage +• m_sidepodDamage +• m_engineDamage +• m_gearBoxDamage +• m_tyresWear (All four wheels) +• m_tyresDamage (All four wheels) +• m_brakesDamage (All four wheels) +• m_drsFault +• m_engineMGUHWear +• m_engineESWear +• m_engineCEWear +• m_engineICEWear +• m_engineMGUKWear +• m_engineTCWear + +To allow other players to view your online ID in their UDP output during an online session, you must enable the “Show online ID / gamertags” option. Selecting this will bring up a confirmation box that must be confirmed before this option is enabled. + +Please note that all options can be changed during a game session and will take immediate effect. + + +FAQS + +How do I enable the UDP Telemetry Output? +In F1 22, UDP telemetry output is controlled via the in-game menus. To enable this, enter the options menu from the main menu (triangle / Y), then enter the settings menu - the UDP option will be at the bottom of the list. From there you will be able to enable / disable the UDP output, configure the IP address and port for the receiving application, toggle broadcast mode and set the send rate. Broadcast mode transmits the data across the network subnet to allow multiple devices on the same subnet to be able to receive this information. When using broadcast mode it is not necessary to set a target IP address, just a target port for applications to listen on. + +Advanced PC Users: You can additionally edit the game’s configuration XML file to configure UDP output. The file is located here (after an initial boot of the game): + +...\Documents\My Games\\hardwaresettings\hardware_settings_config.xml + +You should see the tag: + + + ... + + ... + + +Here you can set the values manually. Note that any changes made within the game when it is running will overwrite any changes made manually. Note the enabled flag is now a state. + + +What has changed since last year? +F1 22 sees the following changes to the UDP specification: + +• Custom UDP actions have been added to the button array so you can assign up to 12 custom controller button to come through UDP +• Personal best and rival car indices added to lap data for time trial +• Added game mode id to the session packet – see appendix for list +• Added ERS and engine damage states to damage packet +• End lap added to tyre stint data in final classification packet +• Added fastest driver and speed to speed trap event, also fixing a bug with fastest speed +• Player’s online name is now displayed in the Participant packet when enabled +• Added ruleset, time of day and session length to the session packet + +What is the order of the wheel arrays? +All wheel arrays are in the following order: + + 0 – Rear Left (RL) + 1 – Rear Right (RR) + 2 – Front Left (FL) + 3 – Front Right (FR) + + +Do the vehicle indices change? +During a session, each car is assigned a vehicle index. This will not change throughout the session and all the arrays that are sent use this vehicle index to dereference the correct piece of data. + + +What encoding format is used? +All values are encoded using Little Endian format. + + +Are the data structures packed? +Yes, all data is packed, there is no padding used. + + +Will there always be 20 cars in the data structures? +No, certain game modes or car classes allow 22 cars to be present on the grid. This means that all previous places where 20 cars were used, 22 is now the maximum. Note that if your UDP format is 2019, 2018 or legacy and you are in “My Team” career mode, no UDP output will be produced because of this limitation. + +There is still the data item called m_numActiveCars in the participants packet which tells you how many cars are active in the race. However, you should check the individual result status of each car in the lap data to see if that car is actively providing data. If it is not “Invalid” or “Inactive” then the corresponding vehicle index has valid data. + + +How often are updated packets sent? +For the packets which get updated at “Rate as specified in the menus” you can be guaranteed that on the frame that these get sent they will all get sent together and will never be separated across frames. This of course relies on the reliability of your network as to whether they are received correctly as everything is sent via UDP. Other packets that get sent at specific rates can arrive on any frame. +If you are connected to the game when it starts transmitting the first frame will contain the following information to help initialise data structures on the receiving application: +Packets sent on Frame 1: (All packets sent on this frame have “Session timestamp” 0.000) +• Session +• Participants +• Car Setups +• Lap Data +• Motion Data +• Car Telemetry +• Car Status +• Car Damage +As an example, assuming that you are running at 60Hz with 60Hz update rate selected in the menus then you would expect to see the following packets and timestamps: +Packets sent on Frame 2: (All packets sent on this frame have “Session timestamp” 0.016) +• Lap Data +• Motion Data +• Car Telemetry +• Car Status +… +Packets sent on Frame 31: (All packets sent on this frame have “Session timestamp” 0.5) +• Session (since 2 updates per second) +• Car Setups (since 2 updates per second) +• Lap Data +• Motion Data +• Car Telemetry +• Car Status +• Car Damage (since 2 updates per second) + +Will my old app still work with F1 22? +F1 22 uses a new format for the UDP data. However, earlier formats of the data are still supported so that most older apps implemented using the previous data formats should work with little or no change from the developer. To use the old formats, please enter the UDP options menu and set “UDP Format” to either “2021”, “2020”, “2019”, “2018” or “Legacy” (for F1 2017 and earlier). +Specifications for the olders formats can be seen here: + +• Legacy (2017 and earlier) - http://forums.codemasters.com/discussion/53139/f1-2017-d-box-and-udp-output-specification/p1. +• F1 2018 - https://forums.codemasters.com/topic/30601-f1-2018-udp-specification/ +• F1 2019 - https://forums.codemasters.com/topic/44592-f1-2019-udp-specification/ +• F1 2020 - https://forums.codemasters.com/topic/54423-f1%C2%AE-2020-udp-specification/ +• F1 2021 - https://forums.codemasters.com/topic/80231-f1-2021-udp-specification + +How do I enable D-BOX output? +D-BOX output is currently supported on the PC platform. In F1 22, the D-BOX activation can be controlled via the menus. Navigate to Game Options->Settings->UDP Telemetry Settings->D-BOX to activate this on your system. + +Advanced PC Users: It is possible to control D-BOX by editing the games’ configuration XML file. The file is located here (after an initial boot of the game): + +...\Documents\My Games\\hardwaresettings\hardware_settings_config.xml + +You should see the tag: + + + + ... + + +Set the “enabled” value to “true” to allow the game to output to your D-BOX motion platform. Note that any changes made within the game when it is running will overwrite any changes made manually. + + +How can I disable in-game support for LED device? +The F1 game has native support for some of the basic features supported by some external LED devices, such as the Leo Bodnar SLI Pro and the Fanatec steering wheels. To avoid conflicts between the game’s implementation and any third-party device managers on the PC platform it may be necessary to disable the native support. This is done using the following led_display flags in the hardware_settings_config.xml. The file is located here (after an initial boot of the game): + +...\Documents\My Games\\hardwaresettings\hardware_settings_config.xml + +The flags to enabled/disable LED output are: + + + +The sliProNativeSupport flag controls the output to SLI Pro devices. The fanatecNativeSupport flag controls the output to Fanatec (and some related) steering wheel LEDs. Set the values for any of these to “false” to disable them and avoid conflicts with your own device manager. + +Please note there is an additional flag to manually control the LED brightness on the SLI Pro: + + + +This option (using value in the range 0-255) will be ignored when setting the sliProNativeSupport flag to “false”. + +Also note it is now possible to edit these values on the fly via the Game Options->Settings->UDP Telemetry Settings menu. + + +Can I configure the UDP output using an XML File? +PC users can edit the game’s configuration XML file to configure UDP output. The file is located here (after an initial boot of the game): + + ...\Documents\My Games\\hardwaresettings\hardware_settings_config.xml + +You should see the tag: + + + ... + + ... + + +Here you can set the values manually. Note that any changes made within the game when it is running will overwrite any changes made manually. +  +Appendices + +Here are the values used for some of the parameters in the UDP data output. + +Team IDs + +ID Team ID Team +0 Mercedes 106 Prema ‘21 +1 Ferrari 107 Uni-Virtuosi ‘21 +2 Red Bull Racing 108 Carlin ‘21 +3 Williams 109 Hitech ‘21 +4 Aston Martin 110 Art GP ‘21 +5 Alpine 111 MP Motorsport ‘21 +6 Alpha Tauri 112 Charouz ‘21 +7 Haas 113 Dams ‘21 +8 McLaren 114 Campos ‘21 +9 Alfa Romeo 115 BWT ‘21 +85 Mercedes 2020 116 Trident ‘21 +86 Ferrari 2020 117 Mercedes AMG GT Black Series +87 Red Bull 2020 118 Prema ‘22 +88 Williams 2020 119 Virtuosi ‘22 +89 Racing Point 2020 120 Carlin ‘22 +90 Renault 2020 121 Hitech ‘22 +91 Alpha Tauri 2020 122 Art GP ‘22 +92 Haas 2020 123 MP Motorsport ‘22 +93 McLaren 2020 124 Charouz ‘22 +94 Alfa Romeo 2020 125 Dams ‘22 +95 Aston Martin DB11 V12 126 Campos ‘22 +96 Aston Martin Vantage F1 Edition 127 Van Amersfoort Racing ‘22 +97 Aston Martin Vantage Safety Car 128 Trident ‘22 +98 Ferrari F8 Tributo +99 Ferrari Roma +100 McLaren 720S +101 McLaren Artura +102 Mercedes AMG GT Black Series Safety Car +103 Mercedes AMG GTR Pro +104 F1 Custom Team +  +Driver IDs + +ID Driver ID Driver ID Driver +0 Carlos Sainz 56 Louis Delétraz 115 Theo Pourchaire +1 Daniil Kvyat 57 Antonio Fuoco 116 Richard Verschoor +2 Daniel Ricciardo 58 Charles Leclerc 117 Lirim Zendeli +3 Fernando Alonso 59 Pierre Gasly 118 David Beckmann +4 Felipe Massa 62 Alexander Albon 121 Alessio Deledda +6 Kimi Räikkönen 63 Nicholas Latifi 122 Bent Viscaal +7 Lewis Hamilton 64 Dorian Boccolacci 123 Enzo Fittipaldi +9 Max Verstappen 65 Niko Kari 125 Mark Webber +10 Nico Hulkenburg 66 Roberto Merhi 126 Jacques Villeneuve +11 Kevin Magnussen 67 Arjun Maini 127 Jake Hughes +12 Romain Grosjean 68 Alessio Lorandi 128 Frederik Vesti +13 Sebastian Vettel 69 Ruben Meijer 129 Olli Caldwell +14 Sergio Perez 70 Rashid Nair 130 Logan Sargeant +15 Valtteri Bottas 71 Jack Tremblay 131 Cem Bolukbasi +17 Esteban Ocon 72 Devon Butler 132 Ayuma Iwasa +19 Lance Stroll 73 Lukas Weber 133 Clement Novolak +20 Arron Barnes 74 Antonio Giovinazzi 134 Dennis Hauger +21 Martin Giles 75 Robert Kubica 135 Calan Williams +22 Alex Murray 76 Alain Prost 136 Jack Doohan +23 Lucas Roth 77 Ayrton Senna 137 Amaury Cordeel +24 Igor Correia 78 Nobuharu Matsushita 138 Mika Hakkinen +25 Sophie Levasseur 79 Nikita Mazepin +26 Jonas Schiffer 80 Guanya Zhou +27 Alain Forest 81 Mick Schumacher +28 Jay Letourneau 82 Callum Ilott +29 Esto Saari 83 Juan Manuel Correa +30 Yasar Atiyeh 84 Jordan King +31 Callisto Calabresi 85 Mahaveer Raghunathan +32 Naota Izum 86 Tatiana Calderon +33 Howard Clarke 87 Anthoine Hubert +34 Wilheim Kaufmann 88 Guiliano Alesi +35 Marie Laursen 89 Ralph Boschung +36 Flavio Nieves 90 Michael Schumacher +37 Peter Belousov 91 Dan Ticktum +38 Klimek Michalski 92 Marcus Armstrong +39 Santiago Moreno 93 Christian Lundgaard +40 Benjamin Coppens 94 Yuki Tsunoda +41 Noah Visser 95 Jehan Daruvala +42 Gert Waldmuller 96 Gulherme Samaia +43 Julian Quesada 97 Pedro Piquet +44 Daniel Jones 98 Felipe Drugovich +45 Artem Markelov 99 Robert Schwartzman +46 Tadasuke Makino 100 Roy Nissany +47 Sean Gelael 101 Marino Sato +48 Nyck De Vries 102 Aidan Jackson +49 Jack Aitken 103 Casper Akkerman +50 George Russell 109 Jenson Button +51 Maximilian Günther 110 David Coulthard +52 Nirei Fukuzumi 111 Nico Rosberg +53 Luca Ghiotto 112 Oscar Piastri +54 Lando Norris 113 Liam Lawson +55 Sérgio Sette Câmara 114 Juri Vips + + +Track IDs + +ID Track +0 Melbourne +1 Paul Ricard +2 Shanghai +3 Sakhir (Bahrain) +4 Catalunya +5 Monaco +6 Montreal +7 Silverstone +8 Hockenheim +9 Hungaroring +10 Spa +11 Monza +12 Singapore +13 Suzuka +14 Abu Dhabi +15 Texas +16 Brazil +17 Austria +18 Sochi +19 Mexico +20 Baku (Azerbaijan) +21 Sakhir Short +22 Silverstone Short +23 Texas Short +24 Suzuka Short +25 Hanoi +26 Zandvoort +27 Imola +28 Portimão +29 Jeddah +30 Miami + + +  +Nationality IDs + +ID Nationality ID Nationality ID Nationality +1 American 31 Greek 61 Paraguayan +2 Argentinean 32 Guatemalan 62 Peruvian +3 Australian 33 Honduran 63 Polish +4 Austrian 34 Hong Konger 64 Portuguese +5 Azerbaijani 35 Hungarian 65 Qatari +6 Bahraini 36 Icelander 66 Romanian +7 Belgian 37 Indian 67 Russian +8 Bolivian 38 Indonesian 68 Salvadoran +9 Brazilian 39 Irish 69 Saudi +10 British 40 Israeli 70 Scottish +11 Bulgarian 41 Italian 71 Serbian +12 Cameroonian 42 Jamaican 72 Singaporean +13 Canadian 43 Japanese 73 Slovakian +14 Chilean 44 Jordanian 74 Slovenian +15 Chinese 45 Kuwaiti 75 South Korean +16 Colombian 46 Latvian 76 South African +17 Costa Rican 47 Lebanese 77 Spanish +18 Croatian 48 Lithuanian 78 Swedish +19 Cypriot 49 Luxembourger 79 Swiss +20 Czech 50 Malaysian 80 Thai +21 Danish 51 Maltese 81 Turkish +22 Dutch 52 Mexican 82 Uruguayan +23 Ecuadorian 53 Monegasque 83 Ukrainian +24 English 54 New Zealander 84 Venezuelan +25 Emirian 55 Nicaraguan 85 Barbadian +26 Estonian 56 Northern Irish 86 Welsh +27 Finnish 57 Norwegian 87 Vietnamese +28 French 58 Omani +29 German 59 Pakistani +30 Ghanaian 60 Panamanian + + + +Game Mode IDs + +ID Team +0 Event Mode +3 Grand Prix +5 Time Trial +6 Splitscreen +7 Online Custom +8 Online League +11 Career Invitational +12 Championship Invitational +13 Championship +14 Online Championship +15 Online Weekly Event +19 Career ‘22 +20 Career ’22 Online +127 Benchmark + +Ruleset IDs + +ID Team +0 Practice & Qualifying +1 Race +2 Time Trial +4 Time Attack +6 Checkpoint Challenge +8 Autocross +9 Drift +10 Average Speed Zone +11 Rival Duel + +Surface types + +These types are from physics data and show what type of contact each wheel is experiencing. + +ID Surface +0 Tarmac +1 Rumble strip +2 Concrete +3 Rock +4 Gravel +5 Mud +6 Sand +7 Grass +8 Water +9 Cobblestone +10 Metal +11 Ridged + +Button flags + +These flags are used in the telemetry packet to determine if any buttons are being held on the controlling device. If the value below logical ANDed with the button status is set then the corresponding button is being held. + +Bit Flag Button +0x00000001 Cross or A +0x00000002 Triangle or Y +0x00000004 Circle or B +0x00000008 Square or X +0x00000010 D-pad Left +0x00000020 D-pad Right +0x00000040 D-pad Up +0x00000080 D-pad Down +0x00000100 Options or Menu +0x00000200 L1 or LB +0x00000400 R1 or RB +0x00000800 L2 or LT +0x00001000 R2 or RT +0x00002000 Left Stick Click +0x00004000 Right Stick Click +0x00008000 Right Stick Left +0x00010000 Right Stick Right +0x00020000 Right Stick Up +0x00040000 Right Stick Down +0x00080000 Special +0x00100000 UDP Action 1 +0x00200000 UDP Action 2 +0x00400000 UDP Action 3 +0x00800000 UDP Action 4 +0x01000000 UDP Action 5 +0x02000000 UDP Action 6 +0x04000000 UDP Action 7 +0x08000000 UDP Action 8 +0x10000000 UDP Action 9 +0x20000000 UDP Action 10 +0x40000000 UDP Action 11 +0x80000000 UDP Action 12 + +Penalty types + +ID Penalty meaning +0 Drive through +1 Stop Go +2 Grid penalty +3 Penalty reminder +4 Time penalty +5 Warning +6 Disqualified +7 Removed from formation lap +8 Parked too long timer +9 Tyre regulations +10 This lap invalidated +11 This and next lap invalidated +12 This lap invalidated without reason +13 This and next lap invalidated without reason +14 This and previous lap invalidated +15 This and previous lap invalidated without reason +16 Retired +17 Black flag timer + + +Infringement types + +ID Infringement meaning +0 Blocking by slow driving +1 Blocking by wrong way driving +2 Reversing off the start line +3 Big Collision +4 Small Collision +5 Collision failed to hand back position single +6 Collision failed to hand back position multiple +7 Corner cutting gained time +8 Corner cutting overtake single +9 Corner cutting overtake multiple +10 Crossed pit exit lane +11 Ignoring blue flags +12 Ignoring yellow flags +13 Ignoring drive through +14 Too many drive throughs +15 Drive through reminder serve within n laps +16 Drive through reminder serve this lap +17 Pit lane speeding +18 Parked for too long +19 Ignoring tyre regulations +20 Too many penalties +21 Multiple warnings +22 Approaching disqualification +23 Tyre regulations select single +24 Tyre regulations select multiple +25 Lap invalidated corner cutting +26 Lap invalidated running wide +27 Corner cutting ran wide gained time minor +28 Corner cutting ran wide gained time significant +29 Corner cutting ran wide gained time extreme +30 Lap invalidated wall riding +31 Lap invalidated flashback used +32 Lap invalidated reset to track +33 Blocking the pitlane +34 Jump start +35 Safety car to car collision +36 Safety car illegal overtake +37 Safety car exceeding allowed pace +38 Virtual safety car exceeding allowed pace +39 Formation lap below allowed speed +40 Formation lap parking +41 Retired mechanical failure +42 Retired terminally damaged +43 Safety car falling too far back +44 Black flag timer +45 Unserved stop go penalty +46 Unserved drive through penalty +47 Engine component change +48 Gearbox change +49 Parc Fermé change +50 League grid penalty +51 Retry penalty +52 Illegal time gain +53 Mandatory pitstop +54 Attribute assigned diff --git a/GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2023_spec.h b/GamesDat/Telemetry/Sources/Formula1/Docs/F1_2023_spec.md similarity index 100% rename from GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2023_spec.h rename to GamesDat/Telemetry/Sources/Formula1/Docs/F1_2023_spec.md diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/CarDamageData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/CarDamageData.cs new file mode 100644 index 0000000..f966865 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/CarDamageData.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarDamageData + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresWear; // Tyre wear (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresDamage; // Tyre damage (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_brakesDamage; // Brakes damage (percentage) + + public byte m_frontLeftWingDamage; // Front left wing damage (percentage) + public byte m_frontRightWingDamage; // Front right wing damage (percentage) + public byte m_rearWingDamage; // Rear wing damage (percentage) + public byte m_floorDamage; // Floor damage (percentage) + public byte m_diffuserDamage; // Diffuser damage (percentage) + public byte m_sidepodDamage; // Sidepod damage (percentage) + public byte m_drsFault; // Indicator for DRS fault, 0 = OK, 1 = fault + public byte m_ersFault; // Indicator for ERS fault, 0 = OK, 1 = fault + public byte m_gearBoxDamage; // Gear box damage (percentage) + public byte m_engineDamage; // Engine damage (percentage) + public byte m_engineMGUHWear; // Engine wear MGU-H (percentage) + public byte m_engineESWear; // Engine wear ES (percentage) + public byte m_engineCEWear; // Engine wear CE (percentage) + public byte m_engineICEWear; // Engine wear ICE (percentage) + public byte m_engineMGUKWear; // Engine wear MGU-K (percentage) + public byte m_engineTCWear; // Engine wear TC (percentage) + public byte m_engineBlown; // Engine blown, 0 = OK, 1 = fault + public byte m_engineSeized; // Engine seized, 0 = OK, 1 = fault + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/CarMotionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/CarMotionData.cs new file mode 100644 index 0000000..635b2bb --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/CarMotionData.cs @@ -0,0 +1,30 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Motion data for one car in F1 2022. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarMotionData + { + public float m_worldPositionX; // World space X position + public float m_worldPositionY; // World space Y position + public float m_worldPositionZ; // World space Z position + public float m_worldVelocityX; // Velocity in world space X + public float m_worldVelocityY; // Velocity in world space Y + public float m_worldVelocityZ; // Velocity in world space Z + public short m_worldForwardDirX; // World space forward X direction (normalised) + public short m_worldForwardDirY; // World space forward Y direction (normalised) + public short m_worldForwardDirZ; // World space forward Z direction (normalised) + public short m_worldRightDirX; // World space right X direction (normalised) + public short m_worldRightDirY; // World space right Y direction (normalised) + public short m_worldRightDirZ; // World space right Z direction (normalised) + public float m_gForceLateral; // Lateral G-Force component + public float m_gForceLongitudinal; // Longitudinal G-Force component + public float m_gForceVertical; // Vertical G-Force component + public float m_yaw; // Yaw angle in radians + public float m_pitch; // Pitch angle in radians + public float m_roll; // Roll angle in radians + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/CarSetupData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/CarSetupData.cs new file mode 100644 index 0000000..3a9fd90 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/CarSetupData.cs @@ -0,0 +1,31 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarSetupData + { + public byte m_frontWing; // Front wing aero + public byte m_rearWing; // Rear wing aero + public byte m_onThrottle; // Differential adjustment on throttle (percentage) + public byte m_offThrottle; // Differential adjustment off throttle (percentage) + public float m_frontCamber; // Front camber angle (suspension geometry) + public float m_rearCamber; // Rear camber angle (suspension geometry) + public float m_frontToe; // Front toe angle (suspension geometry) + public float m_rearToe; // Rear toe angle (suspension geometry) + public byte m_frontSuspension; // Front suspension + public byte m_rearSuspension; // Rear suspension + public byte m_frontAntiRollBar; // Front anti-roll bar + public byte m_rearAntiRollBar; // Rear anti-roll bar + public byte m_frontSuspensionHeight; // Front ride height + public byte m_rearSuspensionHeight; // Rear ride height + public byte m_brakePressure; // Brake pressure (percentage) + public byte m_brakeBias; // Brake bias (percentage) + public float m_rearLeftTyrePressure; // Rear left tyre pressure (PSI) + public float m_rearRightTyrePressure; // Rear right tyre pressure (PSI) + public float m_frontLeftTyrePressure; // Front left tyre pressure (PSI) + public float m_frontRightTyrePressure; // Front right tyre pressure (PSI) + public byte m_ballast; // Ballast + public float m_fuelLoad; // Fuel load + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/CarStatusData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/CarStatusData.cs new file mode 100644 index 0000000..78b8bf5 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/CarStatusData.cs @@ -0,0 +1,42 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarStatusData + { + public byte m_tractionControl; // Traction control - 0 = off, 1 = medium, 2 = full + public byte m_antiLockBrakes; // 0 (off) - 1 (on) + public byte m_fuelMix; // Fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max + public byte m_frontBrakeBias; // Front brake bias (percentage) + public byte m_pitLimiterStatus; // Pit limiter status - 0 = off, 1 = on + public float m_fuelInTank; // Current fuel mass + public float m_fuelCapacity; // Fuel capacity + public float m_fuelRemainingLaps; // Fuel remaining in terms of laps (value on MFD) + public ushort m_maxRPM; // Cars max RPM, point of rev limiter + public ushort m_idleRPM; // Cars idle RPM + public byte m_maxGears; // Maximum number of gears + public byte m_drsAllowed; // 0 = not allowed, 1 = allowed + public ushort m_drsActivationDistance; // 0 = DRS not available, non-zero - DRS will be available in [X] metres + public byte m_actualTyreCompound; // F1 Modern - 16 = C5, 17 = C4, 18 = C3, 19 = C2, 20 = C1 + // 7 = inter, 8 = wet + // F1 Classic - 9 = dry, 10 = wet + // F2 – 11 = super soft, 12 = soft, 13 = medium, 14 = hard + // 15 = wet + public byte m_visualTyreCompound; // F1 visual (can be different from actual compound) + // 16 = soft, 17 = medium, 18 = hard, 7 = inter, 8 = wet + // F1 Classic – same as above + // F2 '19, 15 = wet, 19 – super soft, 20 = soft + // 21 = medium , 22 = hard + public byte m_tyresAgeLaps; // Age in laps of the current set of tyres + public sbyte m_vehicleFiaFlags; // -1 = invalid/unknown, 0 = none, 1 = green + // 2 = blue, 3 = yellow, 4 = red + public float m_ersStoreEnergy; // ERS energy store in Joules + public byte m_ersDeployMode; // ERS deployment mode, 0 = none, 1 = medium + // 2 = hotlap, 3 = overtake + public float m_ersHarvestedThisLapMGUK; // ERS energy harvested this lap by MGU-K + public float m_ersHarvestedThisLapMGUH; // ERS energy harvested this lap by MGU-H + public float m_ersDeployedThisLap; // ERS energy deployed this lap + public byte m_networkPaused; // Whether the car is paused in a network game + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/CarTelemetryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/CarTelemetryData.cs new file mode 100644 index 0000000..f2621f4 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/CarTelemetryData.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarTelemetryData + { + public ushort m_speed; // Speed of car in kilometres per hour + public float m_throttle; // Amount of throttle applied (0.0 to 1.0) + public float m_steer; // Steering (-1.0 (full lock left) to 1.0 (full lock right)) + public float m_brake; // Amount of brake applied (0.0 to 1.0) + public byte m_clutch; // Amount of clutch applied (0 to 100) + public sbyte m_gear; // Gear selected (1-8, N=0, R=-1) + public ushort m_engineRPM; // Engine RPM + public byte m_drs; // 0 = off, 1 = on + public byte m_revLightsPercent; // Rev lights indicator (percentage) + public ushort m_revLightsBitValue; // Rev lights (bit 0 = leftmost LED, bit 14 = rightmost LED) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public ushort[] m_brakesTemperature; // Brakes temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresSurfaceTemperature; // Tyres surface temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresInnerTemperature; // Tyres inner temperature (celsius) + + public ushort m_engineTemperature; // Engine temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresPressure; // Tyres pressure (PSI) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_surfaceType; // Driving surface, see appendices + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/EventDataDetails.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/EventDataDetails.cs new file mode 100644 index 0000000..14cc9b2 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/EventDataDetails.cs @@ -0,0 +1,115 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + // Fastest Lap event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FastestLapData + { + public byte vehicleIdx; // Vehicle index of car achieving fastest lap + public float lapTime; // Lap time is in seconds + } + + // Retirement event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RetirementData + { + public byte vehicleIdx; // Vehicle index of car retiring + } + + // Team mate in pits event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TeamMateInPitsData + { + public byte vehicleIdx; // Vehicle index of team mate + } + + // Race winner event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RaceWinnerData + { + public byte vehicleIdx; // Vehicle index of the race winner + } + + // Penalty event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PenaltyData + { + public byte penaltyType; // Penalty type – see Appendices + public byte infringementType; // Infringement type – see Appendices + public byte vehicleIdx; // Vehicle index of the car the penalty is applied to + public byte otherVehicleIdx; // Vehicle index of the other car involved + public byte time; // Time gained, or time spent doing action in seconds + public byte lapNum; // Lap the penalty occurred on + public byte placesGained; // Number of places gained by this + } + + // Speed trap event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SpeedTrapData + { + public byte vehicleIdx; // Vehicle index of the vehicle triggering speed trap + public float speed; // Top speed achieved in kilometres per hour + public byte isOverallFastestInSession; // Overall fastest speed in session = 1, otherwise 0 + public byte isDriverFastestInSession; // Fastest speed for driver in session = 1, otherwise 0 + public byte fastestVehicleIdxInSession; // Vehicle index of the vehicle that is the fastest in this session + public float fastestSpeedInSession; // Speed of the vehicle that is the fastest in this session + } + + // Start lights event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct StartLightsData + { + public byte numLights; // Number of lights showing + } + + // Drive through penalty served event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DriveThroughPenaltyServedData + { + public byte vehicleIdx; // Vehicle index of the vehicle serving drive through + } + + // Stop go penalty served event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct StopGoPenaltyServedData + { + public byte vehicleIdx; // Vehicle index of the vehicle serving stop go + } + + // Flashback event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FlashbackData + { + public uint flashbackFrameIdentifier; // Frame identifier flashed back to + public float flashbackSessionTime; // Session time flashed back to + } + + // Buttons event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ButtonsData + { + public uint m_buttonStatus; // Bit flags specifying which buttons are being pressed currently - see appendices + } + + /// + /// Union of all possible event data details. + /// The event details packet is different for each type of event. + /// Make sure only the correct type is interpreted based on m_eventStringCode. + /// + [StructLayout(LayoutKind.Explicit)] + public struct EventDataDetails + { + [FieldOffset(0)] public FastestLapData FastestLap; + [FieldOffset(0)] public RetirementData Retirement; + [FieldOffset(0)] public TeamMateInPitsData TeamMateInPits; + [FieldOffset(0)] public RaceWinnerData RaceWinner; + [FieldOffset(0)] public PenaltyData Penalty; + [FieldOffset(0)] public SpeedTrapData SpeedTrap; + [FieldOffset(0)] public StartLightsData StartLights; + [FieldOffset(0)] public DriveThroughPenaltyServedData DriveThroughPenaltyServed; + [FieldOffset(0)] public StopGoPenaltyServedData StopGoPenaltyServed; + [FieldOffset(0)] public FlashbackData Flashback; + [FieldOffset(0)] public ButtonsData Buttons; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/FinalClassificationData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/FinalClassificationData.cs new file mode 100644 index 0000000..1fbec34 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/FinalClassificationData.cs @@ -0,0 +1,31 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FinalClassificationData + { + public byte m_position; // Finishing position + public byte m_numLaps; // Number of laps completed + public byte m_gridPosition; // Grid position of the car + public byte m_points; // Number of points scored + public byte m_numPitStops; // Number of pit stops made + public byte m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active + // 3 = finished, 4 = didnotfinish, 5 = disqualified + // 6 = not classified, 7 = retired + public uint m_bestLapTimeInMS; // Best lap time of the session in milliseconds + public double m_totalRaceTime; // Total race time in seconds without penalties + public byte m_penaltiesTime; // Total penalties accumulated in seconds + public byte m_numPenalties; // Number of penalties applied to this driver + public byte m_numTyreStints; // Number of tyres stints up to maximum + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsActual; // Actual tyres used by this driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsVisual; // Visual tyres used by this driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsEndLaps; // The lap number stints end on + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/LapData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/LapData.cs new file mode 100644 index 0000000..3a1d3b3 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/LapData.cs @@ -0,0 +1,38 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LapData + { + public uint m_lastLapTimeInMS; // Last lap time in milliseconds + public uint m_currentLapTimeInMS; // Current time around the lap in milliseconds + public ushort m_sector1TimeInMS; // Sector 1 time in milliseconds + public ushort m_sector2TimeInMS; // Sector 2 time in milliseconds + public float m_lapDistance; // Distance vehicle is around current lap in metres – could + // be negative if line hasn't been crossed yet + public float m_totalDistance; // Total distance travelled in session in metres – could + // be negative if line hasn't been crossed yet + public float m_safetyCarDelta; // Delta in seconds for safety car + public byte m_carPosition; // Car race position + public byte m_currentLapNum; // Current lap number + public byte m_pitStatus; // 0 = none, 1 = pitting, 2 = in pit area + public byte m_numPitStops; // Number of pit stops taken in this race + public byte m_sector; // 0 = sector1, 1 = sector2, 2 = sector3 + public byte m_currentLapInvalid; // Current lap invalid - 0 = valid, 1 = invalid + public byte m_penalties; // Accumulated time penalties in seconds to be added + public byte m_warnings; // Accumulated number of warnings issued + public byte m_numUnservedDriveThroughPens; // Num drive through pens left to serve + public byte m_numUnservedStopGoPens; // Num stop go pens left to serve + public byte m_gridPosition; // Grid position the vehicle started the race in + public byte m_driverStatus; // Status of driver - 0 = in garage, 1 = flying lap + // 2 = in lap, 3 = out lap, 4 = on track + public byte m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active + // 3 = finished, 4 = didnotfinish, 5 = disqualified + // 6 = not classified, 7 = retired + public byte m_pitLaneTimerActive; // Pit lane timing, 0 = inactive, 1 = active + public ushort m_pitLaneTimeInLaneInMS; // If active, the current time spent in the pit lane in ms + public ushort m_pitStopTimerInMS; // Time of the actual pit stop in ms + public byte m_pitStopShouldServePen; // Whether the car should serve a penalty at this stop + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/LapHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/LapHistoryData.cs new file mode 100644 index 0000000..7b3645e --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/LapHistoryData.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LapHistoryData + { + public uint m_lapTimeInMS; // Lap time in milliseconds + public ushort m_sector1TimeInMS; // Sector 1 time in milliseconds + public ushort m_sector2TimeInMS; // Sector 2 time in milliseconds + public ushort m_sector3TimeInMS; // Sector 3 time in milliseconds + public byte m_lapValidBitFlags; // 0x01 bit set-lap valid, 0x02 bit set-sector 1 valid + // 0x04 bit set-sector 2 valid, 0x08 bit set-sector 3 valid + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/LobbyInfoData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/LobbyInfoData.cs new file mode 100644 index 0000000..ae30820 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/LobbyInfoData.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LobbyInfoData + { + public byte m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + public byte m_teamId; // Team id - see appendix (255 if no team currently selected) + public byte m_nationality; // Nationality of the driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] + public byte[] m_name; // Name of participant in UTF-8 format – null terminated + // Will be truncated with ... (U+2026) if too long + + public byte m_carNumber; // Car number of the player + public byte m_readyStatus; // 0 = not ready, 1 = ready, 2 = spectating + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/MarshalZone.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/MarshalZone.cs new file mode 100644 index 0000000..e94aad7 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/MarshalZone.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MarshalZone + { + public float m_zoneStart; // Fraction (0..1) of way through the lap the marshal zone starts + public sbyte m_zoneFlag; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow, 4 = red + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarDamageData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarDamageData.cs new file mode 100644 index 0000000..d692547 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarDamageData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Car damage packet for F1 2022 telemetry. + /// Frequency: 2 per second + /// Size: 948 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarDamageData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarDamageData[] m_carDamageData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarSetupData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarSetupData.cs new file mode 100644 index 0000000..f18750b --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarSetupData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Car setups packet for F1 2022 telemetry. + /// Frequency: 2 per second + /// Size: 1102 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarSetupData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarSetupData[] m_carSetups; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarStatusData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarStatusData.cs new file mode 100644 index 0000000..8e6ec89 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarStatusData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Car status packet for F1 2022 telemetry. + /// Frequency: Rate as specified in menus + /// Size: 1058 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarStatusData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarStatusData[] m_carStatusData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarTelemetryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarTelemetryData.cs new file mode 100644 index 0000000..7b0c8f8 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarTelemetryData.cs @@ -0,0 +1,26 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Car telemetry packet for F1 2022 telemetry. + /// Frequency: Rate as specified in menus + /// Size: 1347 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarTelemetryData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarTelemetryData[] m_carTelemetryData; + + public byte m_mfdPanelIndex; // Index of MFD panel open - 255 = MFD closed + // Single player, race – 0 = Car setup, 1 = Pits + // 2 = Damage, 3 = Engine, 4 = Temperatures + // May vary depending on game mode + public byte m_mfdPanelIndexSecondaryPlayer; // See above + public sbyte m_suggestedGear; // Suggested gear for the player (1-8) + // 0 if no gear suggested + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketEventData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketEventData.cs new file mode 100644 index 0000000..a55b4bb --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketEventData.cs @@ -0,0 +1,26 @@ +using System.Runtime.InteropServices; +using System.Text; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Event packet for F1 2022 telemetry. + /// Frequency: When the event occurs + /// Size: 40 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketEventData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_eventStringCode; // Event string code, see below + + public EventDataDetails m_eventDetails; // Event details - should be interpreted differently for each type + + /// + /// Helper to get event code as string + /// + public readonly string EventCode => Encoding.ASCII.GetString(m_eventStringCode); + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketFinalClassificationData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketFinalClassificationData.cs new file mode 100644 index 0000000..6615046 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketFinalClassificationData.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Final classification packet for F1 2022 telemetry. + /// Frequency: Once at the end of a race + /// Size: 1015 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketFinalClassificationData + { + public PacketHeader m_header; + + public byte m_numCars; // Number of cars in the final classification + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public FinalClassificationData[] m_classificationData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketHeader.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketHeader.cs new file mode 100644 index 0000000..b43548a --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketHeader.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketHeader + { + public ushort m_packetFormat; // 2022 + public byte m_gameMajorVersion; // Game major version - "X.00" + public byte m_gameMinorVersion; // Game minor version - "1.XX" + public byte m_packetVersion; // Version of this packet type, all start from 1 + public byte m_packetId; // Identifier for the packet type, see below + public ulong m_sessionUID; // Unique identifier for the session + public float m_sessionTime; // Session timestamp + public uint m_frameIdentifier; // Identifier for the frame the data was retrieved on + public byte m_playerCarIndex; // Index of player's car in the array + public byte m_secondaryPlayerCarIndex; // Index of secondary player's car in the array (splitscreen) - 255 if no second player + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketLapData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketLapData.cs new file mode 100644 index 0000000..5a82874 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketLapData.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Lap data packet for F1 2022 telemetry. + /// Frequency: Rate as specified in menus + /// Size: 972 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLapData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public LapData[] m_lapData; // Lap data for all cars on track + + public byte m_timeTrialPBCarIdx; // Index of Personal Best car in time trial (255 if invalid) + public byte m_timeTrialRivalCarIdx; // Index of Rival car in time trial (255 if invalid) + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketLobbyInfoData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketLobbyInfoData.cs new file mode 100644 index 0000000..6bc40e6 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketLobbyInfoData.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Lobby info packet for F1 2022 telemetry. + /// Frequency: Two every second when in the lobby + /// Size: 1191 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLobbyInfoData + { + public PacketHeader m_header; + + public byte m_numPlayers; // Number of players in the lobby data + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public LobbyInfoData[] m_lobbyPlayers; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketMotionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketMotionData.cs new file mode 100644 index 0000000..1fc7b6f --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketMotionData.cs @@ -0,0 +1,40 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Motion data packet for F1 2022 telemetry. + /// Frequency: Rate as specified in menus + /// Size: 1464 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketMotionData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarMotionData[] m_carMotionData; // Data for all cars on track + + // Extra player car ONLY data + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionPosition; // Note: All wheel arrays have the following order: RL, RR, FL, FR + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionVelocity; // RL, RR, FL, FR + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionAcceleration; // RL, RR, FL, FR + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSpeed; // Speed of each wheel + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSlip; // Slip ratio for each wheel + public float m_localVelocityX; // Velocity in local space + public float m_localVelocityY; // Velocity in local space + public float m_localVelocityZ; // Velocity in local space + public float m_angularVelocityX; // Angular velocity x-component + public float m_angularVelocityY; // Angular velocity y-component + public float m_angularVelocityZ; // Angular velocity z-component + public float m_angularAccelerationX; // Angular acceleration x-component + public float m_angularAccelerationY; // Angular acceleration y-component + public float m_angularAccelerationZ; // Angular acceleration z-component + public float m_frontWheelsAngle; // Current front wheels angle in radians + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketParticipantsData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketParticipantsData.cs new file mode 100644 index 0000000..b9c71cd --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketParticipantsData.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Participants packet for F1 2022 telemetry. + /// Frequency: Every 5 seconds + /// Size: 1257 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketParticipantsData + { + public PacketHeader m_header; + + public byte m_numActiveCars; // Number of active cars in the data – should match number of cars on HUD + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ParticipantData[] m_participants; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketSessionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketSessionData.cs new file mode 100644 index 0000000..4e9dc67 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketSessionData.cs @@ -0,0 +1,71 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Session data packet for F1 2022 telemetry. + /// Frequency: 2 per second + /// Size: 632 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketSessionData + { + public PacketHeader m_header; + + public byte m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast + // 3 = light rain, 4 = heavy rain, 5 = storm + public sbyte m_trackTemperature; // Track temp. in degrees celsius + public sbyte m_airTemperature; // Air temp. in degrees celsius + public byte m_totalLaps; // Total number of laps in this race + public ushort m_trackLength; // Track length in metres + public byte m_sessionType; // 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P + // 5 = Q1, 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ + // 10 = R, 11 = R2, 12 = R3, 13 = Time Trial + public sbyte m_trackId; // -1 for unknown, see appendix + public byte m_formula; // Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2, + // 3 = F1 Generic, 4 = Beta, 5 = Supercars + // 6 = Esports, 7 = F2 2021 + public ushort m_sessionTimeLeft; // Time left in session in seconds + public ushort m_sessionDuration; // Session duration in seconds + public byte m_pitSpeedLimit; // Pit speed limit in kilometres per hour + public byte m_gamePaused; // Whether the game is paused – network game only + public byte m_isSpectating; // Whether the player is spectating + public byte m_spectatorCarIndex; // Index of the car being spectated + public byte m_sliProNativeSupport; // SLI Pro support, 0 = inactive, 1 = active + public byte m_numMarshalZones; // Number of marshal zones to follow + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 21)] + public MarshalZone[] m_marshalZones; // List of marshal zones – max 21 + + public byte m_safetyCarStatus; // 0 = no safety car, 1 = full + // 2 = virtual, 3 = formation lap + public byte m_networkGame; // 0 = offline, 1 = online + public byte m_numWeatherForecastSamples; // Number of weather samples to follow + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 56)] + public WeatherForecastSample[] m_weatherForecastSamples; // Array of weather forecast samples + + public byte m_forecastAccuracy; // 0 = Perfect, 1 = Approximate + public byte m_aiDifficulty; // AI Difficulty rating – 0-110 + public uint m_seasonLinkIdentifier; // Identifier for season - persists across saves + public uint m_weekendLinkIdentifier; // Identifier for weekend - persists across saves + public uint m_sessionLinkIdentifier; // Identifier for session - persists across saves + public byte m_pitStopWindowIdealLap; // Ideal lap to pit on for current strategy (player) + public byte m_pitStopWindowLatestLap; // Latest lap to pit on for current strategy (player) + public byte m_pitStopRejoinPosition; // Predicted position to rejoin at (player) + public byte m_steeringAssist; // 0 = off, 1 = on + public byte m_brakingAssist; // 0 = off, 1 = low, 2 = medium, 3 = high + public byte m_gearboxAssist; // 1 = manual, 2 = manual & suggested gear, 3 = auto + public byte m_pitAssist; // 0 = off, 1 = on + public byte m_pitReleaseAssist; // 0 = off, 1 = on + public byte m_ERSAssist; // 0 = off, 1 = on + public byte m_DRSAssist; // 0 = off, 1 = on + public byte m_dynamicRacingLine; // 0 = off, 1 = corners only, 2 = full + public byte m_dynamicRacingLineType; // 0 = 2D, 1 = 3D + public byte m_gameMode; // Game mode id - see appendix + public byte m_ruleSet; // Ruleset - see appendix + public uint m_timeOfDay; // Local time of day - minutes since midnight + public byte m_sessionLength; // 0 = None, 2 = Very Short, 3 = Short, 4 = Medium + // 5 = Medium Long, 6 = Long, 7 = Full + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketSessionHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketSessionHistoryData.cs new file mode 100644 index 0000000..df91afa --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketSessionHistoryData.cs @@ -0,0 +1,30 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Session history packet for F1 2022 telemetry. + /// Frequency: 20 per second but cycling through cars + /// Size: 1155 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketSessionHistoryData + { + public PacketHeader m_header; + + public byte m_carIdx; // Index of the car this lap data relates to + public byte m_numLaps; // Num laps in the data (including current partial lap) + public byte m_numTyreStints; // Number of tyre stints in the data + + public byte m_bestLapTimeLapNum; // Lap the best lap time was achieved on + public byte m_bestSector1LapNum; // Lap the best Sector 1 time was achieved on + public byte m_bestSector2LapNum; // Lap the best Sector 2 time was achieved on + public byte m_bestSector3LapNum; // Lap the best Sector 3 time was achieved on + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)] + public LapHistoryData[] m_lapHistoryData; // 100 laps of data max + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public TyreStintHistoryData[] m_tyreStintsHistoryData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/ParticipantData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/ParticipantData.cs new file mode 100644 index 0000000..34feb1b --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/ParticipantData.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ParticipantData + { + public byte m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + public byte m_driverId; // Driver id - see appendix, 255 if network human + public byte m_networkId; // Network id – unique identifier for network players + public byte m_teamId; // Team id - see appendix + public byte m_myTeam; // My team flag – 1 = My Team, 0 = otherwise + public byte m_raceNumber; // Race number of the car + public byte m_nationality; // Nationality of the driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] + public byte[] m_name; // Name of participant in UTF-8 format – null terminated + // Will be truncated with … (U+2026) if too long + + public byte m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/TyreStintHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/TyreStintHistoryData.cs new file mode 100644 index 0000000..6a546ce --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/TyreStintHistoryData.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TyreStintHistoryData + { + public byte m_endLap; // Lap the tyre usage ends on (255 of current tyre) + public byte m_tyreActualCompound; // Actual tyres used by this driver + public byte m_tyreVisualCompound; // Visual tyres used by this driver + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/WeatherForecastSample.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/WeatherForecastSample.cs new file mode 100644 index 0000000..d409121 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/WeatherForecastSample.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct WeatherForecastSample + { + public byte m_sessionType; // 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P, 5 = Q1 + // 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ, 10 = R, 11 = R2 + // 12 = R3, 13 = Time Trial + public byte m_timeOffset; // Time in minutes the forecast is for + public byte m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast + // 3 = light rain, 4 = heavy rain, 5 = storm + public sbyte m_trackTemperature; // Track temp. in degrees Celsius + public sbyte m_trackTemperatureChange; // Track temp. change – 0 = up, 1 = down, 2 = no change + public sbyte m_airTemperature; // Air temp. in degrees celsius + public sbyte m_airTemperatureChange; // Air temp. change – 0 = up, 1 = down, 2 = no change + public byte m_rainPercentage; // Rain percentage (0-100) + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetupData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetupData.cs index f6a327f..95fc780 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetupData.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetupData.cs @@ -8,7 +8,7 @@ public struct CarSetupData public PacketHeader m_header; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] - public CarSetupData[] m_carSetupData; + public CarSetup[] m_carSetupData; public float m_nextFrontWingValue; } diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetryData.cs index f58b715..9278b79 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetryData.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetryData.cs @@ -9,7 +9,7 @@ public struct CarTelemetryData // Packet specific data [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] - public CarTelemetryData[] m_carTelemetryData; + public CarTelemetry[] m_carTelemetryData; public byte m_mfdPanelIndex; // Index of MFD panel open - 255 = MFD closed // Single player, race – 0 = Car setup, 1 = Pits diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs index f7d3523..303b22a 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs @@ -14,7 +14,7 @@ public struct EventData public EventDataDetails m_eventDetails; // Helper property to get event code as string - public string EventCode => Encoding.ASCII.GetString(EventStringCode); + public readonly string EventCode => EventStringCode != null ? Encoding.ASCII.GetString(EventStringCode) : string.Empty; public T GetEventDetails() where T : struct { diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs index 16eef6f..993187f 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs @@ -45,7 +45,7 @@ public struct TeamMateInPitsData [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct DRSDisabledData { - public byte VehicleIdx; + public byte Reason; // 0 = Wet track, 1 = Safety car, 2 = Red flag, 3 = Min lap not reached } [StructLayout(LayoutKind.Sequential, Pack = 1)] @@ -93,12 +93,13 @@ public struct DriveThroughPenaltyServedData public struct StopGoPenaltyServedData { public byte VehicleIdx; + public float StopTime; // Time spent serving stop-go in seconds } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct FlashbackData { - public byte FlashbackFrameIdentifier; + public uint FlashbackFrameIdentifier; // Changed from byte to uint public float FlashbackSessionTime; } @@ -125,7 +126,7 @@ public struct SafetyCarData [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct CollisionData { - public byte VehicleIdx; - public byte CollidingVehicleIdx; + public byte Vehicle1Idx; // Changed from VehicleIdx + public byte Vehicle2Idx; // Changed from CollidingVehicleIdx } } diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/ParticipantData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/ParticipantData.cs index 7bd4c17..82c36bd 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F12025/ParticipantData.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/ParticipantData.cs @@ -10,6 +10,6 @@ public struct ParticipantData public byte m_numActiveCars; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] - public ParticipantData[] m_participants; + public Participant[] m_participants; } } diff --git a/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs b/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs index e5ffbb2..da63667 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs @@ -1,3 +1,4 @@ +using GamesDat.Core.Telemetry.Sources.Formula1.F12022; using GamesDat.Core.Telemetry.Sources.Formula1.F12023; using GamesDat.Core.Telemetry.Sources.Formula1.F12024; using GamesDat.Core.Telemetry.Sources.Formula1.F12025; @@ -8,6 +9,20 @@ internal static class F1PacketTypeMapper { private static readonly Dictionary<(ushort format, byte id), Type> _packetTypeMap = new() { + // F1 2022 + [(2022, (byte)PacketId.Motion)] = typeof(F12022.PacketMotionData), + [(2022, (byte)PacketId.Session)] = typeof(F12022.PacketSessionData), + [(2022, (byte)PacketId.LapData)] = typeof(F12022.PacketLapData), + [(2022, (byte)PacketId.Event)] = typeof(F12022.PacketEventData), + [(2022, (byte)PacketId.Participants)] = typeof(F12022.PacketParticipantsData), + [(2022, (byte)PacketId.CarSetups)] = typeof(F12022.PacketCarSetupData), + [(2022, (byte)PacketId.CarTelemetry)] = typeof(F12022.PacketCarTelemetryData), + [(2022, (byte)PacketId.CarStatus)] = typeof(F12022.PacketCarStatusData), + [(2022, (byte)PacketId.FinalClassification)] = typeof(F12022.PacketFinalClassificationData), + [(2022, (byte)PacketId.LobbyInfo)] = typeof(F12022.PacketLobbyInfoData), + [(2022, (byte)PacketId.CarDamage)] = typeof(F12022.PacketCarDamageData), + [(2022, (byte)PacketId.SessionHistory)] = typeof(F12022.PacketSessionHistoryData), + // F1 2023 [(2023, (byte)PacketId.Motion)] = typeof(F12023.PacketMotionData), [(2023, (byte)PacketId.Session)] = typeof(F12023.PacketSessionData), diff --git a/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs b/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs index 394042e..dd42af8 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs @@ -4,7 +4,7 @@ namespace GamesDat.Core.Telemetry.Sources.Formula1 { - internal class F1RealtimeTelemetrySource : UdpSourceBase + public class F1RealtimeTelemetrySource : UdpSourceBase { private const int MinRoutingHeaderSize = 7; @@ -15,28 +15,31 @@ public F1RealtimeTelemetrySource(UdpSourceOptions options) : base(options) protected override IEnumerable ProcessData(byte[] data) { if (data.Length < MinRoutingHeaderSize) + { + System.Diagnostics.Debug.WriteLine($"[F1] Packet too small: {data.Length} bytes (min {MinRoutingHeaderSize})"); yield break; + } var packetFormat = BitConverter.ToUInt16(data, 0); var packetId = data[6]; var packetType = F1PacketTypeMapper.GetPacketType(packetFormat, packetId); if (packetType == null) + { + System.Diagnostics.Debug.WriteLine($"[F1] Unknown packet: Format={packetFormat}, PacketId={packetId}"); yield break; + } var expectedSize = Marshal.SizeOf(packetType); if (data.Length < expectedSize) + { + System.Diagnostics.Debug.WriteLine($"[F1] Packet size mismatch: PacketId={packetId}, Expected={expectedSize}, Actual={data.Length}"); yield break; + } - var packet = BytesToStruct(data, packetType); + System.Diagnostics.Debug.WriteLine($"[F1] Packet received: Format={packetFormat}, PacketId={packetId}, Size={data.Length}"); - yield return new F1TelemetryFrame - { - Packet = packet, - PacketFormat = packetFormat, - PacketId = packetId, - PacketType = packetType - }; + yield return new F1TelemetryFrame(packetFormat, packetId, data); } private static object BytesToStruct(byte[] bytes, Type type) diff --git a/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs index 901664c..6c9e7f2 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs @@ -1,23 +1,32 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.InteropServices; +using GamesDat.Core.Attributes; namespace GamesDat.Core.Telemetry.Sources.Formula1 { - public struct F1TelemetryFrame + /// + /// Unmanaged struct containing raw F1 telemetry packet data. + /// Use F1TelemetryFrameExtensions for easy deserialization. + /// + [GameId("F1")] + [DataVersion(1, 0, 0)] + public unsafe struct F1TelemetryFrame { - public object Packet { get; init; } - public ushort PacketFormat { get; init; } - public byte PacketId { get; init; } - public Type PacketType { get; init; } + public ushort PacketFormat; + public byte PacketId; + public fixed byte RawData[2048]; // Max F1 packet size + public int DataLength; - public T GetPacket() where T : struct + public F1TelemetryFrame(ushort packetFormat, byte packetId, byte[] data) { - if (Packet is T typed) - return typed; - throw new InvalidCastException($"Packet is {Packet?.GetType().Name ?? "null"}, not {typeof(T).Name}"); + PacketFormat = packetFormat; + PacketId = packetId; + DataLength = Math.Min(data.Length, 2048); + + fixed (byte* ptr = RawData) + { + Marshal.Copy(data, 0, (IntPtr)ptr, DataLength); + } } } } diff --git a/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrameExtensions.cs b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrameExtensions.cs new file mode 100644 index 0000000..f20f62b --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrameExtensions.cs @@ -0,0 +1,84 @@ +using System; +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + /// + /// Helper methods for working with F1TelemetryFrame. + /// Provides friendly API for deserializing packet data. + /// + public static class F1TelemetryFrameExtensions + { + /// + /// Gets the Type of the packet based on format and ID + /// + public static Type? GetPacketType(this F1TelemetryFrame frame) + { + return F1PacketTypeMapper.GetPacketType(frame.PacketFormat, frame.PacketId); + } + + /// + /// Deserializes the packet to the specified type + /// + public static unsafe T GetPacket(this F1TelemetryFrame frame) where T : struct + { + var packetType = typeof(T); + var expectedSize = Marshal.SizeOf(); + + if (frame.DataLength < expectedSize) + { + throw new InvalidOperationException( + $"Packet data too small. Expected {expectedSize} bytes for {packetType.Name}, got {frame.DataLength}"); + } + + return Marshal.PtrToStructure((IntPtr)frame.RawData); + } + + /// + /// Deserializes the packet to its actual type (determined by PacketFormat and PacketId) + /// + public static unsafe object? DeserializePacket(this F1TelemetryFrame frame) + { + var packetType = frame.GetPacketType(); + if (packetType == null) + return null; + + var expectedSize = Marshal.SizeOf(packetType); + + var msg = $"[F1 Deserialize] Type={packetType.Name}, Expected={expectedSize}, Actual={frame.DataLength}, PacketId={frame.PacketId}"; + System.Diagnostics.Debug.WriteLine(msg); + Console.WriteLine(msg); + + if (frame.DataLength < expectedSize) + { + var errorMsg = $"Packet data too small. Expected {expectedSize} bytes for {packetType.Name}, got {frame.DataLength}"; + Console.WriteLine($"[F1 ERROR] {errorMsg}"); + throw new InvalidOperationException(errorMsg); + } + + try + { + var result = Marshal.PtrToStructure((IntPtr)frame.RawData, packetType); + Console.WriteLine($"[F1 Deserialize] SUCCESS for PacketId={frame.PacketId}"); + return result; + } + catch (Exception ex) + { + var errorMsg = $"Marshal.PtrToStructure failed: {ex.GetType().Name} - {ex.Message}"; + Console.WriteLine($"[F1 ERROR] {errorMsg}"); + System.Diagnostics.Debug.WriteLine($"[F1 ERROR] {errorMsg}"); + throw; + } + } + + /// + /// Gets the raw packet data as a byte array + /// + public static unsafe byte[] GetRawData(this F1TelemetryFrame frame) + { + var data = new byte[frame.DataLength]; + Marshal.Copy((IntPtr)frame.RawData, data, 0, frame.DataLength); + return data; + } + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2025.h b/GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2025.h new file mode 100644 index 0000000..82315c5 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2025.h @@ -0,0 +1,860 @@ +//------------------------------------------------------------------------------ +// (c) 2025 Electronic Arts Inc. All rights reserved. +//------------------------------------------------------------------------------ + +// C++ structures for F1 25 UDP specification +// Allows easy comparison with previous years to see what has changed + +//----------------------------------------------------------------------------- +// Header - 29 bytes +//----------------------------------------------------------------------------- +static const uint32 cs_maxNumCarsInUDPData = 22; +static const uint32 cs_maxParticipantNameLen = 32; +static const uint32 cs_maxTyreStints = 8; +static const uint32 cs_maxNumTyreSets = 13 + 7; // 13 slick and 7 wet weather + +// Different packet types +enum PacketId +{ + ePacketIdMotion = 0, // Contains all motion data for player�s car � only sent while player is in control + ePacketIdSession = 1, // Data about the session � track, time left + ePacketIdLapData = 2, // Data about all the lap times of cars in the session + ePacketIdEvent = 3, // Various notable events that happen during a session + ePacketIdParticipants = 4, // List of participants in the session, mostly relevant for multiplayer + ePacketIdCarSetups = 5, // Packet detailing car setups for cars in the race + ePacketIdCarTelemetry = 6, // Telemetry data for all cars + ePacketIdCarStatus = 7, // Status data for all cars + ePacketIdFinalClassification = 8, // Final classification confirmation at the end of a race + ePacketIdLobbyInfo = 9, // Information about players in a multiplayer lobby + ePacketIdCarDamage = 10, // Damage status for all cars + ePacketIdSessionHistory = 11, // Lap and tyre data for session + ePacketIdTyreSets = 12, // Extended tyre set data + ePacketIdMotionEx = 13, // Extended motion data for player car + ePacketIdTimeTrial = 14, // Time Trial specific data + ePacketIdLapPositions = 15, // Lap positions on each lap so a chart can be constructed + ePacketIdMax +}; + +struct PacketHeader +{ + uint16 m_packetFormat; // 2025 + uint8 m_gameYear; // Game year - last two digits e.g. 25 + uint8 m_gameMajorVersion; // Game major version - "X.00" + uint8 m_gameMinorVersion; // Game minor version - "1.XX" + uint8 m_packetVersion; // Version of this packet type + uint8 m_packetId; // Identifier for the packet type + uint64 m_sessionUID; // Unique identifier for the session + float m_sessionTime; // Session timestamp + uint32 m_frameIdentifier; // Identifier for the frame the data was retrieved on + uint32 m_overallFrameIdentifier; // Overall identifier for the frame the data was retrieved on, doesn't go back after flashbacks + uint8 m_playerCarIndex; // Index of player's car in the array + uint8 m_secondaryPlayerCarIndex; // Index of secondary player's car in the array (splitscreen) - 255 if no second player +}; + + +//----------------------------------------------------------------------------- +// Motion - 1349 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Motion data for one car +//----------------------------------------------------------------------------- +struct CarMotionData +{ + float m_worldPositionX; // World space X position - metres + float m_worldPositionY; // World space Y position + float m_worldPositionZ; // World space Z position + float m_worldVelocityX; // Velocity in world space X - metres/s + float m_worldVelocityY; // Velocity in world space Y + float m_worldVelocityZ; // Velocity in world space Z + int16 m_worldForwardDirX; // World space forward X direction (normalised) + int16 m_worldForwardDirY; // World space forward Y direction (normalised) + int16 m_worldForwardDirZ; // World space forward Z direction (normalised) + int16 m_worldRightDirX; // World space right X direction (normalised) + int16 m_worldRightDirY; // World space right Y direction (normalised) + int16 m_worldRightDirZ; // World space right Z direction (normalised) + float m_gForceLateral; // Lateral G-Force component + float m_gForceLongitudinal; // Longitudinal G-Force component + float m_gForceVertical; // Vertical G-Force component + float m_yaw; // Yaw angle in radians + float m_pitch; // Pitch angle in radians + float m_roll; // Roll angle in radians +}; + +struct PacketMotionData +{ + PacketHeader m_header; // Header + + // Packet specific data + CarMotionData m_carMotionData[cs_maxNumCarsInUDPData]; // Data for all cars on track +}; + + +//----------------------------------------------------------------------------- +// Session - 753 bytes +//----------------------------------------------------------------------------- +static const uint32 cs_maxMarshalsZonePerLap = 21; +static const uint32 cs_maxWeatherForecastSamples = 64; +static const uint32 cs_maxSessionsInWeekend = 12; + +struct MarshalZone +{ + float m_zoneStart; // Fraction (0..1) of way through the lap the marshal zone starts + int8 m_zoneFlag; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow +}; + +struct WeatherForecastSample +{ + uint8 m_sessionType; // 0 = unknown, see appendix + uint8 m_timeOffset; // Time in minutes the forecast is for + uint8 m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast, 3 = light rain, 4 = heavy rain, 5 = storm + int8 m_trackTemperature; // Track temp. in degrees celsius + int8 m_trackTemperatureChange; // Track temp. change - 0 = up, 1 = down, 2 = no change + int8 m_airTemperature; // Air temp. in degrees celsius + int8 m_airTemperatureChange; // Air temp. change - 0 = up, 1 = down, 2 = no change + uint8 m_rainPercentage; // Rain percentage (0-100) +}; + +struct PacketSessionData +{ + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast, 3 = light rain, 4 = heavy rain, 5 = storm + int8 m_trackTemperature; // Track temp. in degrees celsius + int8 m_airTemperature; // Air temp. in degrees celsius + uint8 m_totalLaps; // Total number of laps in this race + uint16 m_trackLength; // Track length in metres + uint8 m_sessionType; // 0 = unknown, see appendix + int8 m_trackId; // -1 for unknown, see appendix + uint8 m_formula; // Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2, 3 = F1 Generic, 4 = Beta, 6 = Esports, 8 = F1 World, 9 = F1 Elimination + uint16 m_sessionTimeLeft; // Time left in session in seconds + uint16 m_sessionDuration; // Session duration in seconds + uint8 m_pitSpeedLimit; // Pit speed limit in kilometres per hour + uint8 m_gamePaused; // Whether the game is paused - network game only + uint8 m_isSpectating; // Whether the player is spectating + uint8 m_spectatorCarIndex; // Index of the car being spectated + uint8 m_sliProNativeSupport; // SLI Pro support, 0 = inactive, 1 = active + uint8 m_numMarshalZones; // Number of marshal zones to follow + MarshalZone m_marshalZones[cs_maxMarshalsZonePerLap]; // List of marshal zones - max 21 + uint8 m_safetyCarStatus; // 0 = no safety car, 1 = full, 2 = virtual, 3 = formation lap + uint8 m_networkGame; // 0 = offline, 1 = online + uint8 m_numWeatherForecastSamples; // Number of weather samples to follow + WeatherForecastSample m_weatherForecastSamples[cs_maxWeatherForecastSamples]; // Array of weather forecast samples + uint8 m_forecastAccuracy; // 0 = Perfect, 1 = Approximate + uint8 m_aiDifficulty; // AI difficulty - 0-110 + uint32 m_seasonLinkIdentifier; // Identifier for season - persists across saves + uint32 m_weekendLinkIdentifier; // Identifier for weekend - persists across saves + uint32 m_sessionLinkIdentifier; // Identifier for session - persists across saves + uint8 m_pitStopWindowIdealLap; // Ideal lap to pit on for current strategy (player) + uint8 m_pitStopWindowLatestLap; // Latest lap to pit on for current strategy (player) + uint8 m_pitStopRejoinPosition; // Predicted position to rejoin at (player) + uint8 m_steeringAssist; // 0 = off, 1 = on + uint8 m_brakingAssist; // 0 = off, 1 = low, 2 = medium, 3 = high + uint8 m_gearboxAssist; // 1 = manual, 2 = manual & suggested gear, 3 = auto + uint8 m_pitAssist; // 0 = off, 1 = on + uint8 m_pitReleaseAssist; // 0 = off, 1 = on + uint8 m_ERSAssist; // 0 = off, 1 = on + uint8 m_DRSAssist; // 0 = off, 1 = on + uint8 m_dynamicRacingLine; // 0 = off, 1 = corners only, 2 = full + uint8 m_dynamicRacingLineType; // 0 = 2D, 1 = 3D + uint8 m_gameMode; // Game mode id - see appendix + uint8 m_ruleSet; // Ruleset - see appendix + uint32 m_timeOfDay; // Local time of day - minutes since midnight + uint8 m_sessionLength; // 0 = None, 2 = Very Short, 3 = Short, 4 = Medium, 5 = Medium Long, 6 = Long, 7 = Full + uint8 m_speedUnitsLeadPlayer; // 0 = MPH, 1 = KPH + uint8 m_temperatureUnitsLeadPlayer; // 0 = Celsius, 1 = Fahrenheit + uint8 m_speedUnitsSecondaryPlayer; // 0 = MPH, 1 = KPH + uint8 m_temperatureUnitsSecondaryPlayer; // 0 = Celsius, 1 = Fahrenheit + uint8 m_numSafetyCarPeriods; // Number of safety cars called during session + uint8 m_numVirtualSafetyCarPeriods; // Number of virtual safety cars called during session + uint8 m_numRedFlagPeriods; // Number of red flags called during session + uint8 m_equalCarPerformance; // 0 = Off, 1 = On + uint8 m_recoveryMode; // 0 = None, 1 = Flashbacks, 2 = Auto-recovery + uint8 m_flashbackLimit; // 0 = Low, 1 = Medium, 2 = High, 3 = Unlimited + uint8 m_surfaceType; // 0 = Simplified, 1 = Realistic + uint8 m_lowFuelMode; // 0 = Easy, 1 = Hard + uint8 m_raceStarts; // 0 = Manual, 1 = Assisted + uint8 m_tyreTemperature; // 0 = Surface only, 1 = Surface & Carcass + uint8 m_pitLaneTyreSim; // 0 = On, 1 = Off + uint8 m_carDamage; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Simulation + uint8 m_carDamageRate; // 0 = Reduced, 1 = Standard, 2 = Simulation + uint8 m_collisions; // 0 = Off, 1 = Player-to-Player Off, 2 = On + uint8 m_collisionsOffForFirstLapOnly; // 0 = Disabled, 1 = Enabled + uint8 m_mpUnsafePitRelease; // 0 = On, 1 = Off (Multiplayer) + uint8 m_mpOffForGriefing; // 0 = Disabled, 1 = Enabled (Multiplayer) + uint8 m_cornerCuttingStringency; // 0 = Regular, 1 = Strict + uint8 m_parcFermeRules; // 0 = Off, 1 = On + uint8 m_pitStopExperience; // 0 = Automatic, 1 = Broadcast, 2 = Immersive + uint8 m_safetyCar; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Increased + uint8 m_safetyCarExperience; // 0 = Broadcast, 1 = Immersive + uint8 m_formationLap; // 0 = Off, 1 = On + uint8 m_formationLapExperience; // 0 = Broadcast, 1 = Immersive + uint8 m_redFlags; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Increased + uint8 m_affectsLicenceLevelSolo; // 0 = Off, 1 = On + uint8 m_affectsLicenceLevelMP; // 0 = Off, 1 = On + uint8 m_numSessionsInWeekend; // Number of session in following array + uint8 m_weekendStructure[cs_maxSessionsInWeekend]; // List of session types to show weekend structure - see appendix for types + float m_sector2LapDistanceStart; // Distance in m around track where sector 2 starts + float m_sector3LapDistanceStart; // Distance in m around track where sector 3 starts +}; + + +//----------------------------------------------------------------------------- +// Lap - 1285 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Lap data about one car +//----------------------------------------------------------------------------- +struct LapData +{ + uint32 m_lastLapTimeInMS; // Last lap time in milliseconds + uint32 m_currentLapTimeInMS; // Current time around the lap in milliseconds + uint16 m_sector1TimeMSPart; // Sector 1 time milliseconds part + uint8 m_sector1TimeMinutesPart; // Sector 1 whole minute part + uint16 m_sector2TimeMSPart; // Sector 2 time milliseconds part + uint8 m_sector2TimeMinutesPart; // Sector 2 whole minute part + uint16 m_deltaToCarInFrontMSPart; // Time delta to car in front milliseconds part + uint8 m_deltaToCarInFrontMinutesPart; // Time delta to car in front whole minute part + uint16 m_deltaToRaceLeaderMSPart; // Time delta to race leader milliseconds part + uint8 m_deltaToRaceLeaderMinutesPart; // Time delta to race leader whole minute part + float m_lapDistance; // Distance vehicle is around current lap in metres � could be negative if line hasn�t been crossed yet + float m_totalDistance; // Total distance travelled in session in metres � could be negative if line hasn�t been crossed yet + float m_safetyCarDelta; // Delta in seconds for safety car + uint8 m_carPosition; // Car race position + uint8 m_currentLapNum; // Current lap number + uint8 m_pitStatus; // 0 = none, 1 = pitting, 2 = in pit area + uint8 m_numPitStops; // Number of pit stops taken in this race + uint8 m_sector; // 0 = sector1, 1 = sector2, 2 = sector3 + uint8 m_currentLapInvalid; // Current lap invalid - 0 = valid, 1 = invalid + uint8 m_penalties; // Accumulated time penalties in seconds to be added + uint8 m_totalWarnings; // Accumulated number of warnings issued + uint8 m_cornerCuttingWarnings; // Accumulated number of corner cutting warnings issued + uint8 m_numUnservedDriveThroughPens; // Num drive through pens left to serve + uint8 m_numUnservedStopGoPens; // Num stop go pens left to serve + uint8 m_gridPosition; // Grid position the vehicle started the race in + uint8 m_driverStatus; // Status of driver - 0 = in garage, 1 = flying lap, 2 = in lap, 3 = out lap, 4 = on track + uint8 m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active, 3 = finished, 4 = didnotfinish, 5 = disqualified, 6 = not classified, 7 = retired + uint8 m_pitLaneTimerActive; // Pit lane timing, 0 = inactive, 1 = active + uint16 m_pitLaneTimeInLaneInMS; // If active, the current time spent in the pit lane in ms + uint16 m_pitStopTimerInMS; // Time of the actual pit stop in ms + uint8 m_pitStopShouldServePen; // Whether the car should serve a penalty at this stop + float m_speedTrapFastestSpeed; // Fastest speed through speed trap for this car in kmph + uint8 m_speedTrapFastestLap; // Lap no the fastest speed was achieved, 255 = not set +}; + +struct PacketLapData +{ + PacketHeader m_header; // Header + + // Packet specific data + LapData m_lapData[cs_maxNumCarsInUDPData]; // Lap data for all cars on track + + uint8 m_timeTrialPBCarIdx; // Index of Personal Best car in time trial (255 if invalid) + uint8 m_timeTrialRivalCarIdx; // Index of Rival car in time trial (255 if invalid) +}; + + +//----------------------------------------------------------------------------- +// Event - 45 bytes +//----------------------------------------------------------------------------- + +static const uint cs_eventStringCodeLen = 4; + +// The event details packet is different for each type of event. +// Make sure only the correct type is interpreted. +union EventDataDetails +{ + struct + { + uint8 vehicleIdx; // Vehicle index of car achieving fastest lap + float lapTime; // Lap time is in seconds + } FastestLap; + + struct + { + uint8 vehicleIdx; // Vehicle index of car retiring + uint8 reason; // Result reason - 0 = invalid, 1 = retired, 2 = finished, 3 = terminal damage, 4 = inactive, 5 = not enough laps completed, 6 = black flagged + // 7 = red flagged, 8 = mechanical failure, 9 = session skipped, 10 = session simulated + } Retirement; + + struct + { + NEuint8 reason; // 0 = Wet track, 1 = Safety car deployed, 2 = Red flag, 3 = Min lap not reached + } DRSDisabled; + + struct + { + uint8 vehicleIdx; // Vehicle index of team mate + } TeamMateInPits; + + struct + { + uint8 vehicleIdx; // Vehicle index of the race winner + } RaceWinner; + + struct + { + uint8 penaltyType; // Penalty type � see Appendices + uint8 infringementType; // Infringement type � see Appendices + uint8 vehicleIdx; // Vehicle index of the car the penalty is applied to + uint8 otherVehicleIdx; // Vehicle index of the other car involved + uint8 time; // Time gained, or time spent doing action in seconds + uint8 lapNum; // Lap the penalty occurred on + uint8 placesGained; // Number of places gained by this + } Penalty; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle triggering speed trap + float speed; // Top speed achieved in kilometres per hour + uint8 isOverallFastestInSession; // Overall fastest speed in session = 1, otherwise 0 + uint8 isDriverFastestInSession; // Fastest speed for driver in session = 1, otherwise 0 + uint8 fastestVehicleIdxInSession; // Vehicle index of the vehicle that is the fastest in this session + float fastestSpeedInSession; // Speed of the vehicle that is the fastest in this session + } SpeedTrap; + + struct + { + uint8 numLights; // Number of lights showing + } StartLights; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle serving drive through + } DriveThroughPenaltyServed; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle serving stop go + float stopTime; // Time spent serving stop go in seconds + } StopGoPenaltyServed; + + struct + { + uint32 flashbackFrameIdentifier; // Frame identifier flashed back to + float flashbackSessionTime; // Session time flashed back to + } Flashback; + + struct + { + uint32 buttonStatus; // Bit flags specifying which buttons are being pressed currently - see appendices + } Buttons; + + struct + { + uint8 overtakingVehicleIdx; // Vehicle index of the vehicle overtaking + uint8 beingOvertakenVehicleIdx; // Vehicle index of the vehicle being overtaken + } Overtake; + + struct + { + uint8 safetyCarType; // 0 = No Safety Car, 1 = Full Safety Car, 2 = Virtual Safety Car, 3 = Formation Lap Safety Car + uint8 eventType; // 0 = Deployed, 1 = Returning, 2 = Returned, 3 = Resume Race + } SafetyCar; + + struct + { + uint8 vehicle1Idx; // Vehicle index of the first vehicle involved in the collision + uint8 vehicle2Idx; // Vehicle index of the second vehicle involved in the collision + } Collision; +}; + +struct PacketEventData +{ + // Valid event strings + static const NEchar* cs_sessionStartedEventCode; // "SSTA" + static const NEchar* cs_sessionEndedEventCode; // "SEND" + static const NEchar* cs_fastestLapEventCode; // "FTLP" + static const NEchar* cs_retirementEventCode; // "RTMT" + static const NEchar* cs_drsEnabledEventCode; // "DRSE" + static const NEchar* cs_drsDisabledEventCode; // "DRSD" + static const NEchar* cs_teamMateInPitsEventCode; // "TMPT" + static const NEchar* cs_chequeredFlagEventCode; // "CHQF" + static const NEchar* cs_raceWinnerEventCode; // "RCWN" + static const NEchar* cs_penaltyEventCode; // "PENA" + static const NEchar* cs_speedTrapEventCode; // "SPTP" + static const NEchar* cs_startLightsEventCode; // "STLG" + static const NEchar* cs_lightsOutEventCode; // "LGOT" + static const NEchar* cs_driveThroughServedEventCode; // "DTSV" + static const NEchar* cs_stopGoServedEventCode; // "SGSV" + static const NEchar* cs_flashbackEventCode; // "FLBK" + static const NEchar* cs_buttonStatusEventCode; // "BUTN" + static const NEchar* cs_redFlagEventCode; // "RDFL" + static const NEchar* cs_overtakeEventCode; // "OVTK" + static const NEchar* cs_safetyCarEventCode; // "SCAR" + static const NEchar* cs_collisionEventCode; // "COLL" + + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_eventStringCode[cs_eventStringCodeLen]; // Event string code + EventDataDetails m_eventDetails; // Event details - should be interpreted differently for each type +}; + + +//----------------------------------------------------------------------------- +// Participants - 1284 bytes +//----------------------------------------------------------------------------- + +// RGB value of a colour +struct LiveryColour +{ + uint8 red; + uint8 green; + uint8 blue; +}; + +//----------------------------------------------------------------------------- +// Data about one participant +//----------------------------------------------------------------------------- +struct ParticipantData +{ + uint8 m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + uint8 m_driverId; // Driver id - see appendix, 255 if network human + uint8 m_networkId; // Network id - unique identifier for network players + uint8 m_teamId; // Team id - see appendix + uint8 m_myTeam; // My team flag - 1 = My Team, 0 = otherwise + uint8 m_raceNumber; // Race number of the car + uint8 m_nationality; // Nationality of the driver + char m_name[cs_maxParticipantNameLen]; // Name of participant in UTF-8 format � null terminated + // Will be truncated with ... (U+2026) if too long + uint8 m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + uint8 m_showOnlineNames; // The player's show online names setting, 0 = off, 1 = on + uint16 m_techLevel; // F1 World tech level + uint8 m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + uint8 m_numColours; // Number of colours valid for this car + LiveryColour m_liveryColours[4]; // Colours for the car +}; + +struct PacketParticipantsData +{ + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_numActiveCars; // Number of active cars in the data - should match number of cars on HUD + ParticipantData m_participants[cs_maxNumCarsInUDPData]; +}; + + +//----------------------------------------------------------------------------- +// Car Setups - 1133 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Data about one car setup +//----------------------------------------------------------------------------- +struct CarSetupData +{ + uint8 m_frontWing; // Front wing aero + uint8 m_rearWing; // Rear wing aero + uint8 m_onThrottle; // Differential adjustment on throttle (percentage) + uint8 m_offThrottle; // Differential adjustment off throttle (percentage) + float m_frontCamber; // Front camber angle (suspension geometry) + float m_rearCamber; // Rear camber angle (suspension geometry) + float m_frontToe; // Front toe angle (suspension geometry) + float m_rearToe; // Rear toe angle (suspension geometry) + uint8 m_frontSuspension; // Front suspension + uint8 m_rearSuspension; // Rear suspension + uint8 m_frontAntiRollBar; // Front anti-roll bar + uint8 m_rearAntiRollBar; // Front anti-roll bar + uint8 m_frontSuspensionHeight; // Front ride height + uint8 m_rearSuspensionHeight; // Rear ride height + uint8 m_brakePressure; // Brake pressure (percentage) + uint8 m_brakeBias; // Brake bias (percentage) + uint8 m_engineBraking; // Engine braking (percentage) + float m_rearLeftTyrePressure; // Rear left tyre pressure (PSI) + float m_rearRightTyrePressure; // Rear right tyre pressure (PSI) + float m_frontLeftTyrePressure; // Front left tyre pressure (PSI) + float m_frontRightTyrePressure; // Front right tyre pressure (PSI) + uint8 m_ballast; // Ballast + float m_fuelLoad; // Fuel load +}; + +struct PacketCarSetupData +{ + PacketHeader m_header; // Header + + // Packet specific data + CarSetupData m_carSetupData[cs_maxNumCarsInUDPData]; + + float m_nextFrontWingValue; // Value of front wing after next pit stop - player only +}; + + +//----------------------------------------------------------------------------- +// Car Telemetry - 1352 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Telemetry data for one car +//----------------------------------------------------------------------------- +struct CarTelemetryData +{ + uint16 m_speed; // Speed of car in kilometres per hour + float m_throttle; // Amount of throttle applied (0.0 to 1.0) + float m_steer; // Steering (-1.0 (full lock left) to 1.0 (full lock right)) + float m_brake; // Amount of brake applied (0.0 to 1.0) + uint8 m_clutch; // Amount of clutch applied (0 to 100) + int8 m_gear; // Gear selected (1-8, N=0, R=-1) + uint16 m_engineRPM; // Engine RPM + uint8 m_drs; // 0 = off, 1 = on + uint8 m_revLightsPercent; // Rev lights indicator (percentage) + uint16 m_revLightsBitValue; // Rev lights (bit 0 = leftmost LED, bit 14 = rightmost LED) + uint16 m_brakesTemperature[4]; // Brakes temperature (celsius) + uint8 m_tyresSurfaceTemperature[4]; // Tyres surface temperature (celsius) + uint8 m_tyresInnerTemperature[4]; // Tyres inner temperature (celsius) + uint16 m_engineTemperature; // Engine temperature (celsius) + float m_tyresPressure[4]; // Tyre pressure (PSI) + uint8 m_surfaceType[4]; // Driving surface, see appendices +}; + +struct PacketCarTelemetryData +{ + PacketHeader m_header; // Header + + // Packet specific data + CarTelemetryData m_carTelemetryData[cs_maxNumCarsInUDPData]; // data for all cars on track + + uint8 m_mfdPanelIndex; // Index of MFD panel open - 255 = MFD closed + // Single player, race � 0 = Car setup, 1 = Pits + // 2 = Damage, 3 = Engine, 4 = Temperatures + // May vary depending on game mode + uint8 m_mfdPanelIndexSecondaryPlayer; // See above + int8 m_suggestedGear; // Suggested gear for the player (1-8), 0 if no gear suggested +}; + + +//----------------------------------------------------------------------------- +// Car Status - 1239 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Car status data for one car +//----------------------------------------------------------------------------- +struct CarStatusData +{ + uint8 m_tractionControl; // Traction control - 0 = off, 1 = medium, 2 = full + uint8 m_antiLockBrakes; // 0 (off) - 1 (on) + uint8 m_fuelMix; // Fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max + uint8 m_frontBrakeBias; // Front brake bias (percentage) + uint8 m_pitLimiterStatus; // Pit limiter status - 0 = off, 1 = on + float m_fuelInTank; // Current fuel mass + float m_fuelCapacity; // Fuel capacity + float m_fuelRemainingLaps; // Fuel remaining in terms of laps (value on MFD) + uint16 m_maxRPM; // Cars max RPM, point of rev limiter + uint16 m_idleRPM; // Cars idle RPM + uint8 m_maxGears; // Maximum number of gears + uint8 m_drsAllowed; // 0 = not allowed, 1 = allowed + uint16 m_drsActivationDistance; // 0 = DRS not available, non-zero - DRS will be available in [X] metres + uint8 m_actualTyreCompound; // F1 Modern - 16 = C5, 17 = C4, 18 = C3, 19 = C2, 20 = C1, 21 = C0, 7 = inter, 8 = wet + // F1 Classic - 9 = dry, 10 = wet + // F2 � 11 = super soft, 12 = soft, 13 = medium, 14 = hard, 15 = wet + uint8 m_visualTyreCompound; // F1 visual (can be different from actual compound) + // 16 = soft, 17 = medium, 18 = hard, 7 = inter, 8 = wet + // F1 Classic � same as above + // F2 �20, 15 = wet, 19 � super soft, 20 = soft, 21 = medium, 22 = hard + uint8 m_tyresAgeLaps; // Age in laps of the current set of tyres + int8 m_vehicleFIAFlags; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow + float m_enginePowerICE; // Engine power output of ICE (W) + float m_enginePowerMGUK; // Engine power output of MGU-K (W) + float m_ersStoreEnergy; // ERS energy store in Joules + uint8 m_ersDeployMode; // ERS deployment mode, 0 = none, 1 = medium, 2 = hotlap, 3 = overtake + float m_ersHarvestedThisLapMGUK; // ERS energy harvested this lap by MGU-K + float m_ersHarvestedThisLapMGUH; // ERS energy harvested this lap by MGU-H + float m_ersDeployedThisLap; // ERS energy deployed this lap + uint8 m_networkPaused; // Whether the car is paused in a network game +}; + +struct PacketCarStatusData +{ + PacketHeader m_header; // Header + + // Packet specific data + CarStatusData m_carStatusData[cs_maxNumCarsInUDPData]; // data for all cars on track +}; + + +//----------------------------------------------------------------------------- +// Final Classification - 1042 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Data about one participant's final results +//----------------------------------------------------------------------------- +struct FinalClassificationData +{ + uint8 m_position; // Finishing position + uint8 m_numLaps; // Number of laps completed + uint8 m_gridPosition; // Grid position of the car + uint8 m_points; // Number of points scored + uint8 m_numPitStops; // Number of pit stops made + uint8 m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active, 3 = finished, 4 = didnotfinish, 5 = disqualified, 6 = not classified, 7 = retired + uint8 m_resultReason; // Result reason - 0 = invalid, 1 = retired, 2 = finished, 3 = terminal damage, 4 = inactive, 5 = not enough laps completed, 6 = black flagged + // 7 = red flagged, 8 = mechanical failure, 9 = session skipped, 10 = session simulated + uint32 m_bestLapTimeInMS; // Best lap time of the session in milliseconds + double m_totalRaceTime; // Total race time in seconds without penalties + uint8 m_penaltiesTime; // Total penalties accumulated in seconds + uint8 m_numPenalties; // Number of penalties applied to this driver + uint8 m_numTyreStints; // Number of tyres stints up to maximum + uint8 m_tyreStintsActual[cs_maxTyreStints]; // Actual tyres used by this driver + uint8 m_tyreStintsVisual[cs_maxTyreStints]; // Visual tyres used by this driver + uint8 m_tyreStintsEndLaps[cs_maxTyreStints]; // The lap number stints end on +}; + +struct PacketFinalClassificationData +{ + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_numCars; // Number of cars in the final classification + FinalClassificationData m_classificationData[cs_maxNumCarsInUDPData]; +}; + + +//----------------------------------------------------------------------------- +// Lobby Info - 954 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Data about one participant +//----------------------------------------------------------------------------- +struct LobbyInfoData +{ + uint8 m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + uint8 m_teamId; // Team id - see appendix (255 if no team currently selected) + uint8 m_nationality; // Nationality of the driver + uint8 m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + char m_name[cs_maxParticipantNameLen]; // Name of participant in UTF-8 format � null terminated + // Will be truncated with ... (U+2026) if too long + uint8 m_carNumber; // Car number of the player + uint8 m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + uint8 m_showOnlineNames; // The player's show online names setting, 0 = off, 1 = on + uint16 m_techLevel; // F1 World tech level + uint8 m_readyStatus; // 0 = not ready, 1 = ready, 2 = spectating +}; + +struct PacketLobbyInfoData +{ + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_numPlayers; // Number of players in the lobby data + LobbyInfoData m_lobbyPlayers[cs_maxNumCarsInUDPData]; +}; + + +//----------------------------------------------------------------------------- +// Car Damage - 1041 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Car damage data for one car +//----------------------------------------------------------------------------- +struct CarDamageData +{ + float m_tyresWear[4]; // Tyre wear (percentage) + uint8 m_tyresDamage[4]; // Tyre damage (percentage) + uint8 m_brakesDamage[4]; // Brakes damage (percentage) + uint8 m_tyreBlisters[4]; // Tyre blisters value (percentage) + uint8 m_frontLeftWingDamage; // Front left wing damage (percentage) + uint8 m_frontRightWingDamage; // Front right wing damage (percentage) + uint8 m_rearWingDamage; // Rear wing damage (percentage) + uint8 m_floorDamage; // Floor damage (percentage) + uint8 m_diffuserDamage; // Diffuser damage (percentage) + uint8 m_sidepodDamage; // Sidepod damage (percentage) + uint8 m_drsFault; // Indicator for DRS fault, 0 = OK, 1 = fault + uint8 m_ersFault; // Indicator for ERS fault, 0 = OK, 1 = fault + uint8 m_gearBoxDamage; // Gear box damage (percentage) + uint8 m_engineDamage; // Engine damage (percentage) + uint8 m_engineMGUHWear; // Engine wear MGU-H (percentage) + uint8 m_engineESWear; // Engine wear ES (percentage) + uint8 m_engineCEWear; // Engine wear CE (percentage) + uint8 m_engineICEWear; // Engine wear ICE (percentage) + uint8 m_engineMGUKWear; // Engine wear MGU-K (percentage) + uint8 m_engineTCWear; // Engine wear TC (percentage) + uint8 m_engineBlown; // Engine blown, 0 = OK, 1 = fault + uint8 m_engineSeized; // Engine seized, 0 = OK, 1 = fault +}; + +struct PacketCarDamageData +{ + PacketHeader m_header; // Header + + // Packet specific data + CarDamageData m_carDamageData[cs_maxNumCarsInUDPData]; // data for all cars on track +}; + + +//----------------------------------------------------------------------------- +// Session History - 1460 bytes +//----------------------------------------------------------------------------- + +static const uint cs_maxNumLapsInHistory = 100; + +struct LapHistoryData +{ + uint32 m_lapTimeInMS; // Lap time in milliseconds + uint16 m_sector1TimeMSPart; // Sector 1 milliseconds part + uint8 m_sector1TimeMinutesPart; // Sector 1 whole minute part + uint16 m_sector2TimeMSPart; // Sector 2 time milliseconds part + uint8 m_sector2TimeMinutesPart; // Sector 2 whole minute part + uint16 m_sector3TimeMSPart; // Sector 3 time milliseconds part + uint8 m_sector3TimeMinutesPart; // Sector 3 whole minute part + uint8 m_lapValidBitFlags; // 0x01 bit set-lap valid, 0x02 bit set-sector 1 valid + // 0x04 bit set-sector 2 valid, 0x08 bit set-sector 3 valid +}; + +struct TyreStintHistoryData +{ + uint8 m_endLap; // Lap the tyre usage ends on (255 if current tyre) + uint8 m_tyreActualCompound; // Actual tyres used by this driver + uint8 m_tyreVisualCompound; // Visual tyres used by this driver +}; + +struct PacketSessionHistoryData +{ + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_carIdx; // Index of the car this lap data relates to + uint8 m_numLaps; // Num laps in the data (including current partial lap) + uint8 m_numTyreStints; // Number of tyre stints in the data + + uint8 m_bestLapTimeLapNum; // Lap the best lap time was achieved on + uint8 m_bestSector1LapNum; // Lap the best Sector 1 time was achieved on + uint8 m_bestSector2LapNum; // Lap the best Sector 2 time was achieved on + uint8 m_bestSector3LapNum; // Lap the best Sector 3 time was achieved on + + LapHistoryData m_lapHistoryData[cs_maxNumLapsInHistory]; // 100 laps of data max + TyreStintHistoryData m_tyreStintsHistoryData[cs_maxTyreStints]; +}; + + +//----------------------------------------------------------------------------- +// Tyre Sets - 231 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Data about one tyre set +//----------------------------------------------------------------------------- +struct TyreSetData +{ + uint8 m_actualTyreCompound; // Actual tyre compound used + uint8 m_visualTyreCompound; // Visual tyre compound used + uint8 m_wear; // Tyre wear (percentage) + uint8 m_available; // Whether this set is currently available + uint8 m_recommendedSession; // Recommended session for tyre set, see appendix + uint8 m_lifeSpan; // Laps left in this tyre set + uint8 m_usableLife; // Max number of laps recommended for this compound + int16 m_lapDeltaTime; // Lap delta time in milliseconds compared to fitted set + uint8 m_fitted; // Whether the set is fitted or not +}; + +struct PacketTyreSetsData +{ + PacketHeader m_header; // Header + + uint8 m_carIdx; // Index of the car this data relates to + + // Packet specific data + TyreSetData m_tyreSetData[cs_maxNumTyreSets]; // 13 (dry) + 7 (wet) + + uint8 m_fittedIdx; // Index into array of fitted tyre +}; + + +//----------------------------------------------------------------------------- +// Motion Ex - 273 bytes +//----------------------------------------------------------------------------- + +struct PacketMotionExData +{ + PacketHeader m_header; // Header + + // Extra player car ONLY data + float m_suspensionPosition[4]; // Note: All wheel arrays have the following order: + float m_suspensionVelocity[4]; // RL, RR, FL, FR + float m_suspensionAcceleration[4]; // RL, RR, FL, FR + float m_wheelSpeed[4]; // Speed of each wheel + float m_wheelSlipRatio[4]; // Slip ratio for each wheel + float m_wheelSlipAngle[4]; // Slip angles for each wheel + float m_wheelLatForce[4]; // Lateral forces for each wheel + float m_wheelLongForce[4]; // Longitudinal forces for each wheel + float m_heightOfCOGAboveGround; // Height of centre of gravity above ground + float m_localVelocityX; // Velocity in local space X - metres/s + float m_localVelocityY; // Velocity in local space Y + float m_localVelocityZ; // Velocity in local space Z + float m_angularVelocityX; // Angular velocity x-component - radians/s + float m_angularVelocityY; // Angular velocity y-component + float m_angularVelocityZ; // Angular velocity z-component + float m_angularAccelerationX; // Angular acceleration x-component - radians/s/s + float m_angularAccelerationY; // Angular acceleration y-component + float m_angularAccelerationZ; // Angular acceleration z-component + float m_frontWheelsAngle; // Current front wheels angle in radians + float m_wheelVertForce[4]; // Vertical forces for each wheel + float m_frontAeroHeight; // Front plank edge height above road surface + float m_rearAeroHeight; // Rear plank edge height above road surface + float m_frontRollAngle; // Roll angle of the front suspension + float m_rearRollAngle; // Roll angle of the rear suspension + float m_chassisYaw; // Yaw angle of the chassis relative to the direction of motion - radians + float m_chassisPitch; // Pitch angle of the chassis relative to the direction of motion - radians + float m_wheelCamber[4]; // Camber of each wheel in radians + float m_wheelCamberGain[4]; // Camber gain for each wheel in radians, difference between active camber and dynamic camber +}; + + +//----------------------------------------------------------------------------- +// Time Trial - 101 bytes +//----------------------------------------------------------------------------- + +struct TimeTrialDataSet +{ + uint8 m_carIdx; // Index of the car this data relates to + uint8 m_teamId; // Team id - see appendix + uint m_lapTimeInMS; // Lap time in milliseconds + uint m_sector1TimeInMS; // Sector 1 time in milliseconds + uint m_sector2TimeInMS; // Sector 2 time in milliseconds + uint m_sector3TimeInMS; // Sector 3 time in milliseconds + uint8 m_tractionControl; // 0 = assist off, 1 = assist on + uint8 m_gearboxAssist; // 0 = assist off, 1 = assist on + uint8 m_antiLockBrakes; // 0 = assist off, 1 = assist on + uint8 m_equalCarPerformance; // 0 = Realistic, 1 = Equal + uint8 m_customSetup; // 0 = No, 1 = Yes + uint8 m_valid; // 0 = invalid, 1 = valid +}; + +struct PacketTimeTrialData +{ + PacketHeader m_header; // Header + + TimeTrialDataSet m_playerSessionBestDataSet; // Player session best data set + TimeTrialDataSet m_personalBestDataSet; // Personal best data set + TimeTrialDataSet m_rivalDataSet; // Rival data set +}; + + +//----------------------------------------------------------------------------- +// Lap Positions - 1131 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Packet to send UDP data about the lap positions in a session. It details +// the positions of all the drivers at the start of each lap +//----------------------------------------------------------------------------- +static const NEuint cs_maxNumLapsInLapPositionsHistoryPacket = 50; + +struct PacketLapPositionsData +{ + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_numLaps; // Number of laps in the data + uint8 m_lapStart; // Index of the lap where the data starts, 0 indexed + + // Array holding the position of the car in a given lap, 0 if no record + uint8 m_positionForVehicleIdx[cs_maxNumLapsInLapPositionsHistoryPacket][cs_maxNumCarsInUDPData]; +}; + +//////////////////////////////// END OF FILE //////////////////////////////// From 7e59998af188fc1bc2c4efee47992013bd6ea0e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Sun, 8 Feb 2026 13:03:15 +0100 Subject: [PATCH 05/25] Added F1 2025 packets --- GamesDat/GamesDat.Core.csproj | 6 + .../Telemetry/Sources/Formula1/EventCodes.cs | 33 +++++ .../Sources/Formula1/F12025/CarDamageData.cs | 39 ++++++ .../Sources/Formula1/F12025/CarMotionData.cs | 32 +++++ .../Sources/Formula1/F12025/CarSetup.cs | 32 +++++ .../Sources/Formula1/F12025/CarSetupData.cs | 15 ++ .../Sources/Formula1/F12025/CarStatusData.cs | 45 ++++++ .../Sources/Formula1/F12025/CarTelemetry.cs | 36 +++++ .../Formula1/F12025/CarTelemetryData.cs | 22 +++ .../Sources/Formula1/F12025/EventData.cs | 43 ++++++ .../Formula1/F12025/EventDataDetails.cs | 131 ++++++++++++++++++ .../F12025/FinalClassificationData.cs | 28 ++++ .../Sources/Formula1/F12025/LapData.cs | 48 +++++++ .../Sources/Formula1/F12025/LapHistoryData.cs | 18 +++ .../Sources/Formula1/F12025/LobbyInfoData.cs | 27 ++++ .../Sources/Formula1/F12025/MarshalZone.cs | 11 ++ .../Sources/Formula1/F12025/MotionData.cs | 16 +++ .../Formula1/F12025/PacketCarDamageData.cs | 14 ++ .../Formula1/F12025/PacketCarStatusData.cs | 18 +++ .../F12025/PacketFinalClassificationData.cs | 15 ++ .../Sources/Formula1/F12025/PacketHeader.cs | 21 +++ .../Sources/Formula1/F12025/PacketLapData.cs | 18 +++ .../Formula1/F12025/PacketLapPositionsData.cs | 18 +++ .../Formula1/F12025/PacketLobbyInfoData.cs | 20 +++ .../Formula1/F12025/PacketMotionExData.cs | 63 +++++++++ .../F12025/PacketSessionHistoryData.cs | 26 ++++ .../Formula1/F12025/PacketTimeTrialData.cs | 14 ++ .../Formula1/F12025/PacketTyreSetsData.cs | 18 +++ .../Sources/Formula1/F12025/Participant.cs | 30 ++++ .../Formula1/F12025/ParticipantData.cs | 15 ++ .../Sources/Formula1/F12025/SessionData.cs | 101 ++++++++++++++ .../Formula1/F12025/TimeTrialDataSet.cs | 21 +++ .../Sources/Formula1/F12025/TyreSetData.cs | 18 +++ .../Formula1/F12025/TyreStintHistoryData.cs | 12 ++ .../Formula1/F12025/WeatherForecastSample.cs | 32 +++++ .../Formula1/F1RealtimeTelemetrySource.cs | 20 +++ .../Sources/Formula1/F1TelemetryFrame.cs | 12 ++ .../Sources/Formula1/LiveryColour.cs | 12 ++ .../Telemetry/Sources/Formula1/PacketId.cs | 23 +++ GamesDat/Telemetry/Sources/UdpSourceBase.cs | 54 ++++++++ .../Telemetry/Sources/UdpSourceOptions.cs | 21 +++ 41 files changed, 1198 insertions(+) create mode 100644 GamesDat/Telemetry/Sources/Formula1/EventCodes.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/CarDamageData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/CarMotionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/CarSetup.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/CarSetupData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/CarStatusData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetry.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/FinalClassificationData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/LapData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/LapHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/LobbyInfoData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/MarshalZone.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/MotionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketCarDamageData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketCarStatusData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketFinalClassificationData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketHeader.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketLapData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketLapPositionsData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketLobbyInfoData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketMotionExData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketSessionHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketTimeTrialData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/PacketTyreSetsData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/Participant.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/ParticipantData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/SessionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/TimeTrialDataSet.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/TyreSetData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/TyreStintHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12025/WeatherForecastSample.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/LiveryColour.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/PacketId.cs create mode 100644 GamesDat/Telemetry/Sources/UdpSourceBase.cs create mode 100644 GamesDat/Telemetry/Sources/UdpSourceOptions.cs diff --git a/GamesDat/GamesDat.Core.csproj b/GamesDat/GamesDat.Core.csproj index eb279c4..347f089 100644 --- a/GamesDat/GamesDat.Core.csproj +++ b/GamesDat/GamesDat.Core.csproj @@ -14,4 +14,10 @@ + + + + + + diff --git a/GamesDat/Telemetry/Sources/Formula1/EventCodes.cs b/GamesDat/Telemetry/Sources/Formula1/EventCodes.cs new file mode 100644 index 0000000..28dfbd0 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/EventCodes.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + public static class EventCodes + { + public const string SessionStarted = "SSTA"; + public const string SessionEnded = "SEND"; + public const string FastestLap = "FTLP"; + public const string Retirement = "RTMT"; + public const string DrsEnabled = "DRSE"; + public const string DrsDisabled = "DRSD"; + public const string TeamMateInPits = "TMPT"; + public const string ChequeredFlag = "CHQF"; + public const string RaceWinner = "RCWN"; + public const string Penalty = "PENA"; + public const string SpeedTrap = "SPTP"; + public const string StartLights = "STLG"; + public const string LightsOut = "LGOT"; + public const string DriveThroughServed = "DTSV"; + public const string StopGoServed = "SGSV"; + public const string Flashback = "FLBK"; + public const string ButtonStatus = "BUTN"; + public const string RedFlag = "RDFL"; + public const string Overtake = "OVTK"; + public const string SafetyCar = "SCAR"; + public const string Collision = "COLL"; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarDamageData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarDamageData.cs new file mode 100644 index 0000000..81dcc11 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarDamageData.cs @@ -0,0 +1,39 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarDamageData + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresWear; // Tyre wear (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresDamage; // Tyre damage (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_brakesDamage; // Brakes damage (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyreBlisters; // Tyre blisters value (percentage) + + public byte m_frontLeftWingDamage; // Front left wing damage (percentage) + public byte m_frontRightWingDamage; // Front right wing damage (percentage) + public byte m_rearWingDamage; // Rear wing damage (percentage) + public byte m_floorDamage; // Floor damage (percentage) + public byte m_diffuserDamage; // Diffuser damage (percentage) + public byte m_sidepodDamage; // Sidepod damage (percentage) + public byte m_drsFault; // Indicator for DRS fault, 0 = OK, 1 = fault + public byte m_ersFault; // Indicator for ERS fault, 0 = OK, 1 = fault + public byte m_gearBoxDamage; // Gear box damage (percentage) + public byte m_engineDamage; // Engine damage (percentage) + public byte m_engineMGUHWear; // Engine wear MGU-H (percentage) + public byte m_engineESWear; // Engine wear ES (percentage) + public byte m_engineCEWear; // Engine wear CE (percentage) + public byte m_engineICEWear; // Engine wear ICE (percentage) + public byte m_engineMGUKWear; // Engine wear MGU-K (percentage) + public byte m_engineTCWear; // Engine wear TC (percentage) + public byte m_engineBlown; // Engine blown, 0 = OK, 1 = fault + public byte m_engineSeized; // Engine seized, 0 = OK, 1 = fault + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarMotionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarMotionData.cs new file mode 100644 index 0000000..f402c84 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarMotionData.cs @@ -0,0 +1,32 @@ + +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + + /// + /// Car motion data for F1 2025 telemetry packets. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarMotionData + { + public float m_worldPositionX; // World space X position - metres + public float m_worldPositionY; // World space Y position + public float m_worldPositionZ; // World space Z position + public float m_worldVelocityX; // Velocity in world space X - metres/s + public float m_worldVelocityY; // Velocity in world space Y + public float m_worldVelocityZ; // World space velocity in Z + public short m_worldForwardDirX; // World space forward X direction (normalised) + public short m_worldForwardDirY; // World space forward Y direction (normalised) + public short m_worldForwardDirZ; // World space forward Z direction (normalised) + public short m_worldRightDirX; // World space right X direction (normalised) + public short m_worldRightDirY; // World space right Y direction (normalised) + public short m_worldRightDirZ; // World space right Z direction (normalised) + public float m_gForceLateral; // Lateral G-Force component + public float m_gForceLongitudinal; // Longitudinal G-Force component + public float m_gForceVertical; // Vertical G-Force component + public float m_yaw; // Yaw angle in radians + public float m_pitch; // Pitch angle in radians + public float m_roll; // Roll angle in radians + } +} \ No newline at end of file diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetup.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetup.cs new file mode 100644 index 0000000..3e89582 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetup.cs @@ -0,0 +1,32 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarSetup + { + public byte m_frontWing; // Front wing aero + public byte m_rearWing; // Rear wing aero + public byte m_onThrottle; // Differential adjustment on throttle (percentage) + public byte m_offThrottle; // Differential adjustment off throttle (percentage) + public float m_frontCamber; // Front camber angle (suspension geometry) + public float m_rearCamber; // Rear camber angle (suspension geometry) + public float m_frontToe; // Front toe angle (suspension geometry) + public float m_rearToe; // Rear toe angle (suspension geometry) + public byte m_frontSuspension; // Front suspension + public byte m_rearSuspension; // Rear suspension + public byte m_frontAntiRollBar; // Front anti-roll bar + public byte m_rearAntiRollBar; // Front anti-roll bar + public byte m_frontSuspensionHeight; // Front ride height + public byte m_rearSuspensionHeight; // Rear ride height + public byte m_brakePressure; // Brake pressure (percentage) + public byte m_brakeBias; // Brake bias (percentage) + public byte m_engineBraking; // Engine braking (percentage) + public float m_rearLeftTyrePressure; // Rear left tyre pressure (PSI) + public float m_rearRightTyrePressure; // Rear right tyre pressure (PSI) + public float m_frontLeftTyrePressure; // Front left tyre pressure (PSI) + public float m_frontRightTyrePressure; // Front right tyre pressure (PSI) + public byte m_ballast; // Ballast + public float m_fuelLoad; // Fuel load + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetupData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetupData.cs new file mode 100644 index 0000000..f6a327f --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetupData.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarSetupData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarSetupData[] m_carSetupData; + + public float m_nextFrontWingValue; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarStatusData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarStatusData.cs new file mode 100644 index 0000000..3f7e063 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarStatusData.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarStatusData + { + public byte m_tractionControl; // Traction control - 0 = off, 1 = medium, 2 = full + public byte m_antiLockBrakes; // 0 (off) - 1 (on) + public byte m_fuelMix; // Fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max + public byte m_frontBrakeBias; // Front brake bias (percentage) + public byte m_pitLimiterStatus; // Pit limiter status - 0 = off, 1 = on + public float m_fuelInTank; // Current fuel mass + public float m_fuelCapacity; // Fuel capacity + public float m_fuelRemainingLaps; // Fuel remaining in terms of laps (value on MFD) + public ushort m_maxRPM; // Cars max RPM, point of rev limiter + public ushort m_idleRPM; // Cars idle RPM + public byte m_maxGears; // Maximum number of gears + public byte m_drsAllowed; // 0 = not allowed, 1 = allowed + public ushort m_drsActivationDistance; // 0 = DRS not available, non-zero - DRS will be available in [X] metres + public byte m_actualTyreCompound; // F1 Modern - 16 = C5, 17 = C4, 18 = C3, 19 = C2, 20 = C1, 21 = C0, 7 = inter, 8 = wet + // F1 Classic - 9 = dry, 10 = wet + // F2 – 11 = super soft, 12 = soft, 13 = medium, 14 = hard, 15 = wet + public byte m_visualTyreCompound; // F1 visual (can be different from actual compound) + // 16 = soft, 17 = medium, 18 = hard, 7 = inter, 8 = wet + // F1 Classic – same as above + // F2 '20, 15 = wet, 19 – super soft, 20 = soft, 21 = medium, 22 = hard + public byte m_tyresAgeLaps; // Age in laps of the current set of tyres + public sbyte m_vehicleFIAFlags; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow + public float m_enginePowerICE; // Engine power output of ICE (W) + public float m_enginePowerMGUK; // Engine power output of MGU-K (W) + public float m_ersStoreEnergy; // ERS energy store in Joules + public byte m_ersDeployMode; // ERS deployment mode, 0 = none, 1 = medium, 2 = hotlap, 3 = overtake + public float m_ersHarvestedThisLapMGUK; // ERS energy harvested this lap by MGU-K + public float m_ersHarvestedThisLapMGUH; // ERS energy harvested this lap by MGU-H + public float m_ersDeployedThisLap; // ERS energy deployed this lap + public byte m_networkPaused; // Whether the car is paused in a network game + + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetry.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetry.cs new file mode 100644 index 0000000..7fbbad5 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetry.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarTelemetry + { + public ushort m_speed; // Speed of car in kilometres per hour + public float m_throttle; // Amount of throttle applied (0.0 to 1.0) + public float m_steer; // Steering (-1.0 (full lock left) to 1.0 (full lock right)) + public float m_brake; // Amount of brake applied (0.0 to 1.0) + public byte m_clutch; // Amount of clutch applied (0 to 100) + public sbyte m_gear; // Gear selected (1-8, N=0, R=-1) + public ushort m_engineRPM; // Engine RPM + public byte m_drs; // 0 = off, 1 = on + public byte m_revLightsPercent; // Rev lights indicator (percentage) + public ushort m_revLightsBitValue; // Rev lights (bit 0 = leftmost LED, bit 14 = rightmost LED) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public ushort[] m_brakesTemperature; // Brakes temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresSurfaceTemperature; // Tyres surface temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresInnerTemperature; // Tyres inner temperature (celsius) + + public ushort m_engineTemperature; // Engine temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresPressure; // Tyre pressure (PSI) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_surfaceType; // Driving surface, see appendices + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetryData.cs new file mode 100644 index 0000000..f58b715 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetryData.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarTelemetryData + { + public PacketHeader m_header; // Header + + // Packet specific data + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarTelemetryData[] m_carTelemetryData; + + public byte m_mfdPanelIndex; // Index of MFD panel open - 255 = MFD closed + // Single player, race – 0 = Car setup, 1 = Pits + // 2 = Damage, 3 = Engine, 4 = Temperatures + // May vary depending on game mode + public byte m_mfdPanelIndexSecondaryPlayer; // See above + public byte m_suggestedGear; // Suggested gear for the player (1-8), 0 if no gear suggested + + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs new file mode 100644 index 0000000..f7d3523 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs @@ -0,0 +1,43 @@ +using System.Runtime.InteropServices; +using System.Text; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EventData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] EventStringCode; + + public EventDataDetails m_eventDetails; + + // Helper property to get event code as string + public string EventCode => Encoding.ASCII.GetString(EventStringCode); + + public T GetEventDetails() where T : struct + { + return EventCode switch + { + EventCodes.FastestLap when typeof(T) == typeof(FastestLapData) => (T)(object)m_eventDetails.FastestLap, + EventCodes.Retirement when typeof(T) == typeof(RetirementData) => (T)(object)m_eventDetails.Retirement, + EventCodes.DrsDisabled when typeof(T) == typeof(DRSDisabledData) => (T)(object)m_eventDetails.DRSDisabled, + EventCodes.TeamMateInPits when typeof(T) == typeof(TeamMateInPitsData) => (T)(object)m_eventDetails.TeamMateInPits, + EventCodes.RaceWinner when typeof(T) == typeof(RaceWinnerData) => (T)(object)m_eventDetails.RaceWinner, + EventCodes.Penalty when typeof(T) == typeof(PenaltyData) => (T)(object)m_eventDetails.Penalty, + EventCodes.SpeedTrap when typeof(T) == typeof(SpeedTrapData) => (T)(object)m_eventDetails.SpeedTrap, + EventCodes.StartLights when typeof(T) == typeof(StartLightsData) => (T)(object)m_eventDetails.StartLights, + EventCodes.DriveThroughServed when typeof(T) == typeof(DriveThroughPenaltyServedData) => (T)(object)m_eventDetails.DriveThroughPenaltyServed, + EventCodes.StopGoServed when typeof(T) == typeof(StopGoPenaltyServedData) => (T)(object)m_eventDetails.StopGoPenaltyServed, + EventCodes.Flashback when typeof(T) == typeof(FlashbackData) => (T)(object)m_eventDetails.Flashback, + EventCodes.ButtonStatus when typeof(T) == typeof(ButtonsData) => (T)(object)m_eventDetails.Buttons, + EventCodes.Overtake when typeof(T) == typeof(OvertakeData) => (T)(object)m_eventDetails.Overtake, + EventCodes.SafetyCar when typeof(T) == typeof(SafetyCarData) => (T)(object)m_eventDetails.SafetyCar, + EventCodes.Collision when typeof(T) == typeof(CollisionData) => (T)(object)m_eventDetails.Collision, + // fallback for unsupported event types or mismatched type requests + _ => throw new InvalidOperationException($"Cannot get {typeof(T).Name} for event {EventCode}") + }; + } + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs new file mode 100644 index 0000000..16eef6f --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs @@ -0,0 +1,131 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Explicit)] + public struct EventDataDetails + { + [FieldOffset(0)] public FastestLapData FastestLap; + [FieldOffset(0)] public RetirementData Retirement; + [FieldOffset(0)] public DRSDisabledData DRSDisabled; + [FieldOffset(0)] public TeamMateInPitsData TeamMateInPits; + [FieldOffset(0)] public RaceWinnerData RaceWinner; + [FieldOffset(0)] public PenaltyData Penalty; + [FieldOffset(0)] public SpeedTrapData SpeedTrap; + [FieldOffset(0)] public StartLightsData StartLights; + [FieldOffset(0)] public DriveThroughPenaltyServedData DriveThroughPenaltyServed; + [FieldOffset(0)] public StopGoPenaltyServedData StopGoPenaltyServed; + [FieldOffset(0)] public FlashbackData Flashback; + [FieldOffset(0)] public ButtonsData Buttons; + [FieldOffset(0)] public OvertakeData Overtake; + [FieldOffset(0)] public SafetyCarData SafetyCar; + [FieldOffset(0)] public CollisionData Collision; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FastestLapData + { + public byte VehicleIdx; + public float LapTime; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RetirementData + { + public byte VehicleIdx; + public byte Reason; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TeamMateInPitsData + { + public byte VehicleIdx; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DRSDisabledData + { + public byte VehicleIdx; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RaceWinnerData + { + public byte VehicleIdx; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PenaltyData + { + public byte PenaltyType; + public byte InfringementType; + public byte VehicleIdx; + public byte OtherVehicleIdx; + public byte Time; + public byte LapNum; + public byte PlacesGained; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SpeedTrapData + { + public byte VehicleIdx; + public float Speed; + public byte OverallFastestInSession; + public byte DriverFastestInSession; + public byte FastestVehicleIdxInSession; + public float FastestSpeedInSession; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct StartLightsData + { + public byte NumLights; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DriveThroughPenaltyServedData + { + public byte VehicleIdx; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct StopGoPenaltyServedData + { + public byte VehicleIdx; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FlashbackData + { + public byte FlashbackFrameIdentifier; + public float FlashbackSessionTime; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ButtonsData + { + public uint ButtonStatus; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct OvertakeData + { + public byte OvertakingVehicleIdx; + public byte OvertakenVehicleIdx; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SafetyCarData + { + public byte SafetyCarType; // 0 = No Safety Car, 1 = Full Safety Car, 2 = Virtual Safety Car, 3 = Formation Lap Safety Car + public byte EventType; // 0 = Deployed, 1 = Returning, 2 = Returned, 3 = Resume Race + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CollisionData + { + public byte VehicleIdx; + public byte CollidingVehicleIdx; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/FinalClassificationData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/FinalClassificationData.cs new file mode 100644 index 0000000..f0c96a0 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/FinalClassificationData.cs @@ -0,0 +1,28 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FinalClassificationData + { + public byte m_position; // Finishing position + public byte m_numLaps; // Number of laps completed + public byte m_gridPosition; // Grid position of the car + public byte m_points; // Number of points scored + public byte m_numPitStops; // Number of pit stops made + public byte m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active, 3 = finished, 4 = didnotfinish, 5 = disqualified, 6 = not classified, 7 = retired + public byte m_resultReason; // Result reason - 0 = invalid, 1 = retired, 2 = finished, 3 = terminal damage, 4 = inactive, 5 = not enough laps completed, 6 = black flagged + // 7 = red flagged, 8 = mechanical failure, 9 = session skipped, 10 = session simulated + public uint m_bestLapTimeInMS; // Best lap time of the session in milliseconds + public double m_totalRaceTime; // Total race time in seconds without penalties + public byte m_penaltiesTime; // Total penalties accumulated in seconds + public byte m_numPenalties; // Number of penalties applied to this driver + public byte m_numTyreStints; // Number of tyres stints up to maximum + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsActual; // Actual tyres used by this driver + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsVisual; // Visual tyres used by this driver + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsEndLaps; // The lap number stints end on + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/LapData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/LapData.cs new file mode 100644 index 0000000..8051e10 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/LapData.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LapData + { + public uint m_lastLapTimeInMS; // Last lap time in milliseconds + public uint m_currentLapTimeInMS; // Current time around the lap in milliseconds + public ushort m_sector1TimeMSPart; // Sector 1 time milliseconds part + public byte m_sector1TimeMinutesPart; // Sector 1 whole minute part + public ushort m_sector2TimeMSPart; // Sector 2 time milliseconds part + public byte m_sector2TimeMinutesPart; // Sector 2 whole minute part + public ushort m_deltaToCarInFrontMSPart; // Time delta to car in front milliseconds part + public byte m_deltaToCarInFrontMinutesPart; // Time delta to car in front whole minute part + public ushort m_deltaToRaceLeaderMSPart; // Time delta to race leader milliseconds part + public byte m_deltaToRaceLeaderMinutesPart; // Time delta to race leader whole minute part + public float m_lapDistance; // Distance vehicle is around current lap in metres – could be negative if line hasn't been crossed yet + public float m_totalDistance; // Total distance travelled in session in metres – could be negative if line hasn't been crossed yet + public float m_safetyCarDelta; // Delta in seconds for safety car + public byte m_carPosition; // Car race position + public byte m_currentLapNum; // Current lap number + public byte m_pitStatus; // 0 = none, 1 = pitting, 2 = in pit area + public byte m_numPitStops; // Number of pit stops taken in this race + public byte m_sector; // 0 = sector1, 1 = sector2, 2 = sector3 + public byte m_currentLapInvalid; // Current lap invalid - 0 = valid, 1 = invalid + public byte m_penalties; // Accumulated time penalties in seconds to be added + public byte m_totalWarnings; // Accumulated number of warnings issued + public byte m_cornerCuttingWarnings; // Accumulated number of corner cutting warnings issued + public byte m_numUnservedDriveThroughPens; // Num drive through pens left to serve + public byte m_numUnservedStopGoPens; // Num stop go pens left to serve + public byte m_gridPosition; // Grid position the vehicle started the race in + public byte m_driverStatus; // Status of driver - 0 = in garage, 1 = flying lap, 2 = in lap, 3 = out lap, 4 = on track + public byte m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active, 3 = finished, 4 = didnotfinish, 5 = disqualified, 6 = not classified, 7 = retired + public byte m_pitLaneTimerActive; // Pit lane timing, 0 = inactive, 1 = active + public ushort m_pitLaneTimeInLaneInMS; // If active, the current time spent in the pit lane in ms + public ushort m_pitStopTimerInMS; // Time of the actual pit stop in ms + public byte m_pitStopShouldServePen; // Whether the car should serve a penalty at this stop + public float m_speedTrapFastestSpeed; // Fastest speed through speed trap for this car in kmph + public byte m_speedTrapFastestLap; // Lap no the fastest speed was achieved, 255 = not set + + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/LapHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/LapHistoryData.cs new file mode 100644 index 0000000..e3a833f --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/LapHistoryData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LapHistoryData + { + public uint m_lapTimeInMS; // Lap time in milliseconds + public ushort m_sector1TimeMSPart; // Sector 1 milliseconds part + public byte m_sector1TimeMinutesPart; // Sector 1 whole minute part + public ushort m_sector2TimeMSPart; // Sector 2 time milliseconds part + public byte m_sector2TimeMinutesPart; // Sector 2 whole minute part + public ushort m_sector3TimeMSPart; // Sector 3 time milliseconds part + public byte m_sector3TimeMinutesPart; // Sector 3 whole minute part + public byte m_lapValidBitFlags; // 0x01 bit set-lap valid, 0x02 bit set-sector 1 valid + // 0x04 bit set-sector 2 valid, 0x08 bit set-sector 3 valid + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/LobbyInfoData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/LobbyInfoData.cs new file mode 100644 index 0000000..c11e455 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/LobbyInfoData.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LobbyInfoData + { + public byte m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + public byte m_teamId; // Team id - see appendix (255 if no team currently selected) + public byte m_nationality; // Nationality of the driver + public byte m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string m_name; // Name of participant in UTF-8 format – null terminated + // Will be truncated with ... (U+2026) if too long + public byte m_carNumber; // Car number of the player + public byte m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + public byte m_showOnlineNames; // The player's show online names setting, 0 = off, 1 = on + public ushort m_techLevel; // F1 World tech level + public byte m_readyStatus; // 0 = not ready, 1 = ready, 2 = spectating + + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/MarshalZone.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/MarshalZone.cs new file mode 100644 index 0000000..03d4d43 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/MarshalZone.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MarshalZone + { + public float m_zoneStart; // Fraction (0..1) of way through the lap the marshal zone starts + public byte m_zoneFlag; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow, 4 = red + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/MotionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/MotionData.cs new file mode 100644 index 0000000..ad43c01 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/MotionData.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + /// + /// Motion data packet for F1 2025 telemetry. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MotionData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarMotionData[] m_carMotionData; + } +} \ No newline at end of file diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketCarDamageData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketCarDamageData.cs new file mode 100644 index 0000000..fed4bd4 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketCarDamageData.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarDamageData + { + public PacketHeader m_header; // Header + + // Packet specific data + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarDamageData[] m_carDamageData; // data for all cars on track + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketCarStatusData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketCarStatusData.cs new file mode 100644 index 0000000..c760e4e --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketCarStatusData.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarStatusData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarStatusData[] m_carStatusData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketFinalClassificationData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketFinalClassificationData.cs new file mode 100644 index 0000000..e03770d --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketFinalClassificationData.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketFinalClassificationData + { + public PacketHeader m_header; + + public byte m_numCars; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public FinalClassificationData[] m_classificationData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketHeader.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketHeader.cs new file mode 100644 index 0000000..47ccdb5 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketHeader.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketHeader + { + public ushort m_packetFormat; // 2025 + public byte m_gameYear; // Game year - last two digits e.g. 25 + public byte m_gameMajorVersion; // Game major version - "X.00" + public byte m_gameMinorVersion; // Game minor version - "1.XX" + public byte m_packetVersion; // Version of this packet type + public byte m_packetId; // Identifier for the packet type + public ulong m_sessionUID; // Unique identifier for the session + public float m_sessionTime; // Session timestamp + public uint m_frameIdentifier; // Identifier for the frame the data was retrieved on + public uint m_overallFrameIdentifier; // Overall identifier for the frame the data was retrieved on, doesn't go back after flashbacks + public byte m_playerCarIndex; // Index of player's car in the array + public byte m_secondaryPlayerCarIndex; // Index of secondary player's car in the array (splitscreen) - 255 if no second player + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLapData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLapData.cs new file mode 100644 index 0000000..78d7626 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLapData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLapData + { + public PacketHeader m_header; // Header + + // Packet specific data + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public LapData[] m_lapData; // Lap data for all cars on track + + public byte m_timeTrialPBCarIdx; // Index of Personal Best car in time trial (255 if invalid) + public byte m_timeTrialRivalCarIdx; // Index of Rival car in time trial (255 if invalid) + + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLapPositionsData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLapPositionsData.cs new file mode 100644 index 0000000..535cd50 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLapPositionsData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLapPositionsData + { + public PacketHeader m_header; // Header + + // Packet specific data + public byte m_numLaps; // Number of laps in the data + public byte m_lapStart; // Index of the lap where the data starts, 0 indexed + + // Array holding the position of the car in a given lap, 0 if no record + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50 * 22)] + public byte[] m_positionForVehicleIdx; // [50 laps][22 cars] + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLobbyInfoData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLobbyInfoData.cs new file mode 100644 index 0000000..c69ebb8 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketLobbyInfoData.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLobbyInfoData + { + public PacketHeader m_header; + + public byte m_numPlayers; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public LobbyInfoData[] m_lobbyPlayers; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketMotionExData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketMotionExData.cs new file mode 100644 index 0000000..784a30f --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketMotionExData.cs @@ -0,0 +1,63 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketMotionExData + { + public PacketHeader m_header; // Header + + // Extra player car ONLY data + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionPosition; // Note: All wheel arrays have the following order: + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionVelocity; // RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionAcceleration; // RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSpeed; // Speed of each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSlipRatio; // Slip ratio for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSlipAngle; // Slip angles for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelLatForce; // Lateral forces for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelLongForce; // Longitudinal forces for each wheel + + public float m_heightOfCOGAboveGround; // Height of centre of gravity above ground + public float m_localVelocityX; // Velocity in local space X - metres/s + public float m_localVelocityY; // Velocity in local space Y + public float m_localVelocityZ; // Velocity in local space Z + public float m_angularVelocityX; // Angular velocity x-component - radians/s + public float m_angularVelocityY; // Angular velocity y-component + public float m_angularVelocityZ; // Angular velocity z-component + public float m_angularAccelerationX; // Angular acceleration x-component - radians/s/s + public float m_angularAccelerationY; // Angular acceleration y-component + public float m_angularAccelerationZ; // Angular acceleration z-component + public float m_frontWheelsAngle; // Current front wheels angle in radians + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelVertForce; // Vertical forces for each wheel + + public float m_frontAeroHeight; // Front plank edge height above road surface + public float m_rearAeroHeight; // Rear plank edge height above road surface + public float m_frontRollAngle; // Roll angle of the front suspension + public float m_rearRollAngle; // Roll angle of the rear suspension + public float m_chassisYaw; // Yaw angle of the chassis relative to the direction of motion - radians + public float m_chassisPitch; // Pitch angle of the chassis relative to the direction of motion - radians + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelCamber; // Camber of each wheel in radians + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelCamberGain; // Camber gain for each wheel in radians, difference between active camber and dynamic camber + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketSessionHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketSessionHistoryData.cs new file mode 100644 index 0000000..cef2d1c --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketSessionHistoryData.cs @@ -0,0 +1,26 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketSessionHistoryData + { + public PacketHeader m_header; // Header + + // Packet specific data + public byte m_carIdx; // Index of the car this lap data relates to + public byte m_numLaps; // Num laps in the data (including current partial lap) + public byte m_numTyreStints; // Number of tyre stints in the data + + public byte m_bestLapTimeLapNum; // Lap the best lap time was achieved on + public byte m_bestSector1LapNum; // Lap the best Sector 1 time was achieved on + public byte m_bestSector2LapNum; // Lap the best Sector 2 time was achieved on + public byte m_bestSector3LapNum; // Lap the best Sector 3 time was achieved on + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)] + public LapHistoryData[] m_lapHistoryData; // 100 laps of data max + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public TyreStintHistoryData[] m_tyreStintsHistoryData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketTimeTrialData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketTimeTrialData.cs new file mode 100644 index 0000000..09cb080 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketTimeTrialData.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketTimeTrialData + { + public PacketHeader m_header; // Header + + public TimeTrialDataSet m_playerSessionBestDataSet; // Player session best data set + public TimeTrialDataSet m_personalBestDataSet; // Personal best data set + public TimeTrialDataSet m_rivalDataSet; // Rival data set + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/PacketTyreSetsData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketTyreSetsData.cs new file mode 100644 index 0000000..293f996 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/PacketTyreSetsData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketTyreSetsData + { + public PacketHeader m_header; // Header + + public byte m_carIdx; // Index of the car this data relates to + + // Packet specific data + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public TyreSetData[] m_tyreSetData; // 13 (dry) + 7 (wet) + + public byte m_fittedIdx; // Index into array of fitted tyre + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/Participant.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/Participant.cs new file mode 100644 index 0000000..441d97d --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/Participant.cs @@ -0,0 +1,30 @@ +using System; +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct Participant + { + public byte m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + public byte m_driverId; // Driver id - see appendix, 255 if network human + public byte m_networkId; // Network id - unique identifier for network players + public byte m_teamId; // Team id - see appendix + public byte m_myTeam; // My team flag - 1 = My Team, 0 = otherwise + public byte m_raceNumber; // Race number of the car + public byte m_nationality; // Nationality of the driver + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string m_name; // Name of participant in UTF-8 format – null terminated + // Will be truncated with ... (U+2026) if too long + public byte m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + public byte m_showOnlineNames; // The player's show online names setting, 0 = off, 1 = on + public ushort m_techLevel; // F1 World tech level + public byte m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + public byte m_numColours; // Number of colours valid for this car + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public LiveryColour[] m_liveryColours; // Colours for the car + + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/ParticipantData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/ParticipantData.cs new file mode 100644 index 0000000..7bd4c17 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/ParticipantData.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ParticipantData + { + public PacketHeader m_header; + + public byte m_numActiveCars; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ParticipantData[] m_participants; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/SessionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/SessionData.cs new file mode 100644 index 0000000..3ad04de --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/SessionData.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SessionData + { + public PacketHeader m_header; + public byte m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast, 3 = light rain, 4 = heavy rain, 5 = storm + public sbyte m_trackTemperature; // Track temp. in degrees celsius + public sbyte m_airTemperature; // Air temp. in degrees celsius + public byte m_totalLaps; // Total number of laps in this race + public ushort m_trackLength; // Track length in metres + public byte m_sessionType; // 0 = unknown, see appendix + public sbyte m_trackId; // -1 for unknown, see appendix + public byte m_formula; // Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2, 3 = F1 Generic, 4 = Beta, 6 = Esports, 8 = F1 World, 9 = F1 Elimination + public ushort m_sessionTimeLeft; // Time left in session in seconds + public ushort m_sessionDuration; // Session duration in seconds + public byte m_pitSpeedLimit; // Pit speed limit in kilometres per hour + public byte m_gamePaused; // Whether the game is paused - network game only + public byte m_isSpectating; // Whether the player is spectating + public byte m_spectatorCarIndex; // Index of the car being spectated + public byte m_sliProNativeSupport; // SLI Pro support, 0 = inactive, 1 = active + public byte m_numMarshalZones; // Number of marshal zones to follow + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 21)] + public MarshalZone[] m_marshalZones; // List of marshal zones - max 21 + + public byte m_safetyCarStatus; // 0 = no safety car, 1 = full, 2 = virtual, 3 = formation lap + public byte m_networkGame; // 0 = offline, 1 = online + public byte m_numWeatherForecastSamples; // Number of weather samples to follow + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] + public WeatherForecastSample[] m_weatherForecastSamples; // Array of weather forecast samples + + public byte m_forecastAccuracy; // 0 = Perfect, 1 = Approximate + public byte m_aiDifficulty; // AI difficulty - 0-110 + public uint m_seasonLinkIdentifier; // Identifier for season - persists across saves + public uint m_weekendLinkIdentifier; // Identifier for weekend - persists across saves + public uint m_sessionLinkIdentifier; // Identifier for session - persists across saves + public byte m_pitStopWindowIdealLap; // Ideal lap to pit on for current strategy (player) + public byte m_pitStopWindowLatestLap; // Latest lap to pit on for current strategy (player) + public byte m_pitStopRejoinPosition; // Predicted position to rejoin at (player) + public byte m_steeringAssist; // 0 = off, 1 = on + public byte m_brakingAssist; // 0 = off, 1 = low, 2 = medium, 3 = high + public byte m_gearboxAssist; // 1 = manual, 2 = manual & suggested gear, 3 = auto + public byte m_pitAssist; // 0 = off, 1 = on + public byte m_pitReleaseAssist; // 0 = off, 1 = on + public byte m_ERSAssist; // 0 = off, 1 = on + public byte m_DRSAssist; // 0 = off, 1 = on + public byte m_dynamicRacingLine; // 0 = off, 1 = corners only, 2 = full + public byte m_dynamicRacingLineType; // 0 = 2D, 1 = 3D + public byte m_gameMode; // Game mode id - see appendix + public byte m_ruleSet; // Ruleset - see appendix + public uint m_timeOfDay; // Local time of day - minutes since midnight + public byte m_sessionLength; // 0 = None, 2 = Very Short, 3 = Short, 4 = Medium, 5 = Medium Long, 6 = Long, 7 = Full + public byte m_speedUnitsLeadPlayer; // 0 = MPH, 1 = KPH + public byte m_temperatureUnitsLeadPlayer; // 0 = Celsius, 1 = Fahrenheit + public byte m_speedUnitsSecondaryPlayer; // 0 = MPH, 1 = KPH + public byte m_temperatureUnitsSecondaryPlayer; // 0 = Celsius, 1 = Fahrenheit + public byte m_numSafetyCarPeriods; // Number of safety cars called during session + public byte m_numVirtualSafetyCarPeriods; // Number of virtual safety cars called during session + public byte m_numRedFlagPeriods; // Number of red flags called during session + public byte m_equalCarPerformance; // 0 = Off, 1 = On + public byte m_recoveryMode; // 0 = None, 1 = Flashbacks, 2 = Auto-recovery + public byte m_flashbackLimit; // 0 = Low, 1 = Medium, 2 = High, 3 = Unlimited + public byte m_surfaceType; // 0 = Simplified, 1 = Realistic + public byte m_lowFuelMode; // 0 = Easy, 1 = Hard + public byte m_raceStarts; // 0 = Manual, 1 = Assisted + public byte m_tyreTemperature; // 0 = Surface only, 1 = Surface & Carcass + public byte m_pitLaneTyreSim; // 0 = On, 1 = Off + public byte m_carDamage; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Simulation + public byte m_carDamageRate; // 0 = Reduced, 1 = Standard, 2 = Simulation + public byte m_collisions; // 0 = Off, 1 = Player-to-Player Off, 2 = On + public byte m_collisionsOffForFirstLapOnly; // 0 = Disabled, 1 = Enabled + public byte m_mpUnsafePitRelease; // 0 = On, 1 = Off (Multiplayer) + public byte m_mpOffForGriefing; // 0 = Disabled, 1 = Enabled (Multiplayer) + public byte m_cornerCuttingStringency; // 0 = Regular, 1 = Strict + public byte m_parcFermeRules; // 0 = Off, 1 = On + public byte m_pitStopExperience; // 0 = Automatic, 1 = Broadcast, 2 = Immersive + public byte m_safetyCar; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Increased + public byte m_safetyCarExperience; // 0 = Broadcast, 1 = Immersive + public byte m_formationLap; // 0 = Off, 1 = On + public byte m_formationLapExperience; // 0 = Broadcast, 1 = Immersive + public byte m_redFlags; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Increased + public byte m_affectsLicenceLevelSolo; // 0 = Off, 1 = On + public byte m_affectsLicenceLevelMP; // 0 = Off, 1 = On + public byte m_numSessionsInWeekend; // Number of session in following array + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)] + public byte[] m_weekendStructure; // List of session types to show weekend structure - see appendix for types + + public float m_sector2LapDistanceStart; // Distance in m around track where sector 2 starts + public float m_sector3LapDistanceStart; // Distance in m around track where sector 3 starts + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/TimeTrialDataSet.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/TimeTrialDataSet.cs new file mode 100644 index 0000000..6cf9ea4 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/TimeTrialDataSet.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TimeTrialDataSet + { + public byte m_carIdx; // Index of the car this data relates to + public byte m_teamId; // Team id - see appendix + public uint m_lapTimeInMS; // Lap time in milliseconds + public uint m_sector1TimeInMS; // Sector 1 time in milliseconds + public uint m_sector2TimeInMS; // Sector 2 time in milliseconds + public uint m_sector3TimeInMS; // Sector 3 time in milliseconds + public byte m_tractionControl; // 0 = assist off, 1 = assist on + public byte m_gearboxAssist; // 0 = assist off, 1 = assist on + public byte m_antiLockBrakes; // 0 = assist off, 1 = assist on + public byte m_equalCarPerformance; // 0 = Realistic, 1 = Equal + public byte m_customSetup; // 0 = No, 1 = Yes + public byte m_valid; // 0 = invalid, 1 = valid + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/TyreSetData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/TyreSetData.cs new file mode 100644 index 0000000..09e587c --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/TyreSetData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TyreSetData + { + public byte m_actualTyreCompound; // Actual tyre compound used + public byte m_visualTyreCompound; // Visual tyre compound used + public byte m_wear; // Tyre wear (percentage) + public byte m_available; // Whether this set is currently available + public byte m_recommendedSession; // Recommended session for tyre set, see appendix + public byte m_lifeSpan; // Laps left in this tyre set + public byte m_usableLife; // Max number of laps recommended for this compound + public short m_lapDeltaTime; // Lap delta time in milliseconds compared to fitted set + public byte m_fitted; // Whether the set is fitted or not + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/TyreStintHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/TyreStintHistoryData.cs new file mode 100644 index 0000000..8c72d9f --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/TyreStintHistoryData.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TyreStintHistoryData + { + public byte m_endLap; // Lap the tyre usage ends on (255 if current tyre) + public byte m_tyreActualCompound; // Actual tyres used by this driver + public byte m_tyreVisualCompound; // Visual tyres used by this driver + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/WeatherForecastSample.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/WeatherForecastSample.cs new file mode 100644 index 0000000..0a90e4f --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/WeatherForecastSample.cs @@ -0,0 +1,32 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12025 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct WeatherForecastSample + { + // 0 = unknown, see appendix + public byte m_sessionType; // uint8 + + // Time in minutes the forecast is for + public byte m_timeOffset; // uint8 + + // Weather - 0 = clear, 1 = light cloud, 2 = overcast, 3 = light rain, 4 = heavy rain, 5 = storm + public byte m_weather; // uint8 + + // Track temp. in degrees celsius + public sbyte m_trackTemperature; // int8 + + // Track temp. change - 0 = up, 1 = down, 2 = no change + public sbyte m_trackTemperatureChange; // int8 + + // Air temp. in degrees celsius + public sbyte m_airTemperature; // int8 + + // Air temp. change - 0 = up, 1 = down, 2 = no change + public sbyte m_airTemperatureChange; // int8 + + // Rain percentage (0-100) + public byte m_rainPercentage; // uint8 + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs b/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs new file mode 100644 index 0000000..1905657 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + internal class F1RealtimeTelemetrySource : UdpSourceBase + { + public F1RealtimeTelemetrySource(UdpSourceOptions options) : base(options) + { + } + + protected override IEnumerable ProcessData(byte[] data) + { + yield return new F1TelemetryFrame(); + } + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs new file mode 100644 index 0000000..d6398bb --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + public struct F1TelemetryFrame + { + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/LiveryColour.cs b/GamesDat/Telemetry/Sources/Formula1/LiveryColour.cs new file mode 100644 index 0000000..3d5b372 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/LiveryColour.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LiveryColour + { + byte red; + byte green; + byte blue; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/PacketId.cs b/GamesDat/Telemetry/Sources/Formula1/PacketId.cs new file mode 100644 index 0000000..39dff1e --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/PacketId.cs @@ -0,0 +1,23 @@ +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + public enum PacketId : byte + { + Motion = 0, // Contains all motion data for player’s car – only sent while player is in control + Session = 1, // Data about the session – track, time left + LapData = 2, // Data about all the lap times of cars in the session + Event = 3, // Various notable events that happen during a session + Participants = 4, // List of participants in the session, mostly relevant for multiplayer + CarSetups = 5, // Packet detailing car setups for cars in the race + CarTelemetry = 6, // Telemetry data for all cars + CarStatus = 7, // Status data for all cars + FinalClassification = 8, // Final classification confirmation at the end of a race + LobbyInfo = 9, // Information about players in a multiplayer lobby + CarDamage = 10, // Damage status for all cars + SessionHistory = 11, // Lap and tyre data for session + TyreSets = 12, // Extended tyre set data + MotionEx = 13, // Extended motion data for player car + TimeTrial = 14, // Time Trial specific data + LapPositions = 15, // Lap positions on each lap so a chart can be constructed + Max + } +} diff --git a/GamesDat/Telemetry/Sources/UdpSourceBase.cs b/GamesDat/Telemetry/Sources/UdpSourceBase.cs new file mode 100644 index 0000000..b032040 --- /dev/null +++ b/GamesDat/Telemetry/Sources/UdpSourceBase.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; + +namespace GamesDat.Core.Telemetry.Sources +{ + public abstract class UdpSourceBase : TelemetrySourceBase + { + + protected UdpClient _listener; + protected IPEndPoint _endpoint; + protected bool _isListening; + + protected int Port { get; set; } + protected int BufferSize { get; set; } + + public UdpSourceBase(UdpSourceOptions options) : base() + { + Port = options.Port; + BufferSize = options.BufferSize; + + _endpoint = new IPEndPoint(IPAddress.Any, Port); + _listener = new UdpClient(_endpoint); + } + + public override async IAsyncEnumerable ReadContinuousAsync([EnumeratorCancellation] CancellationToken ct = default) + { + _isListening = true; + try + { + while (!ct.IsCancellationRequested) + { + var result = await _listener.ReceiveAsync(ct); + var data = result.Buffer; + // Process the received data and yield telemetry objects + foreach (var item in ProcessData(data)) + { + yield return item; + } + } + } + finally + { + _isListening = false; + _listener.Dispose(); + } + } + + abstract protected IEnumerable ProcessData(byte[] data); + } +} diff --git a/GamesDat/Telemetry/Sources/UdpSourceOptions.cs b/GamesDat/Telemetry/Sources/UdpSourceOptions.cs new file mode 100644 index 0000000..cdac687 --- /dev/null +++ b/GamesDat/Telemetry/Sources/UdpSourceOptions.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GamesDat.Core.Telemetry.Sources +{ + public class UdpSourceOptions + { + /// + /// Local port to listen for UDP packets + /// + public int Port { get; set; } = 0; + + /// + /// The maximum size of the buffer used to receive UDP packets. Default is 8192 bytes. + /// + public int BufferSize { get; set; } = 8192; + } +} From 2d0610e2197bb1f344dc97807179e4c7abe069bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Mon, 9 Feb 2026 11:59:02 +0100 Subject: [PATCH 06/25] Added F1 2024 types --- GamesDat/.claude/settings.local.json | 4 +- .../Sources/Formula1/F12024/CarDamageData.cs | 39 +++ .../Sources/Formula1/F12024/CarMotionData.cs | 30 ++ .../Sources/Formula1/F12024/CarSetupData.cs | 35 ++ .../Sources/Formula1/F12024/CarStatusData.cs | 42 +++ .../Formula1/F12024/CarTelemetryData.cs | 39 +++ .../Formula1/F12024/EventDataDetails.cs | 123 +++++++ .../F12024/FinalClassificationData.cs | 32 ++ .../Sources/Formula1/F12024/LapData.cs | 45 +++ .../Sources/Formula1/F12024/LapHistoryData.cs | 18 + .../Sources/Formula1/F12024/LobbyInfoData.cs | 26 ++ .../Sources/Formula1/F12024/MarshalZone.cs | 11 + .../Formula1/F12024/PacketCarDamageData.cs | 13 + .../Formula1/F12024/PacketCarSetupData.cs | 15 + .../Formula1/F12024/PacketCarStatusData.cs | 13 + .../Formula1/F12024/PacketCarTelemetryData.cs | 20 ++ .../Formula1/F12024/PacketEventData.cs | 41 +++ .../F12024/PacketFinalClassificationData.cs | 15 + .../Sources/Formula1/F12024/PacketHeader.cs | 21 ++ .../Sources/Formula1/F12024/PacketLapData.cs | 16 + .../Formula1/F12024/PacketLobbyInfoData.cs | 15 + .../Formula1/F12024/PacketMotionData.cs | 16 + .../Formula1/F12024/PacketMotionExData.cs | 58 ++++ .../Formula1/F12024/PacketParticipantsData.cs | 15 + .../Formula1/F12024/PacketSessionData.cs | 97 ++++++ .../F12024/PacketSessionHistoryData.cs | 25 ++ .../Formula1/F12024/PacketTimeTrialData.cs | 14 + .../Formula1/F12024/PacketTyreSetsData.cs | 17 + .../Formula1/F12024/ParticipantData.cs | 28 ++ .../Formula1/F12024/TimeTrialDataSet.cs | 21 ++ .../Sources/Formula1/F12024/TyreSetData.cs | 21 ++ .../Formula1/F12024/TyreStintHistoryData.cs | 12 + .../Formula1/F12024/WeatherForecastSample.cs | 17 + .../Sources/Formula1/F1PacketTypeMapper.cs | 52 +++ .../Formula1/F1RealtimeTelemetrySource.cs | 43 ++- .../Sources/Formula1/F1TelemetryFrame.cs | 11 + .../Formula1/F1_IMPLEMENTATION_WORKFLOW.md | 322 ++++++++++++++++++ 37 files changed, 1377 insertions(+), 5 deletions(-) create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/CarDamageData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/CarMotionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/CarSetupData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/CarStatusData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/CarTelemetryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/EventDataDetails.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/FinalClassificationData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/LapData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/LapHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/LobbyInfoData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/MarshalZone.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarDamageData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarSetupData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarStatusData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarTelemetryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketEventData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketFinalClassificationData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketHeader.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketLapData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketLobbyInfoData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketMotionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketMotionExData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketParticipantsData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketSessionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketSessionHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketTimeTrialData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/PacketTyreSetsData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/ParticipantData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/TimeTrialDataSet.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/TyreSetData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/TyreStintHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12024/WeatherForecastSample.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md diff --git a/GamesDat/.claude/settings.local.json b/GamesDat/.claude/settings.local.json index e1c7f21..562a20f 100644 --- a/GamesDat/.claude/settings.local.json +++ b/GamesDat/.claude/settings.local.json @@ -9,7 +9,9 @@ "Bash(tree:*)", "Bash(dir /s /b \"..\\\\GamesDate.Demo.Wpf\\\\ViewModels\\\\*.cs\")", "Bash(dir /s /b \"..\\\\GamesDate.Demo.Wpf\\\\Views\\\\*.xaml\")", - "WebFetch(domain:wiki.trackmania.io)" + "WebFetch(domain:wiki.trackmania.io)", + "Bash(xargs:*)", + "Bash(grep:*)" ] } } diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/CarDamageData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/CarDamageData.cs new file mode 100644 index 0000000..0445d53 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/CarDamageData.cs @@ -0,0 +1,39 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Car damage data for one car in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarDamageData + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresWear; // Tyre wear (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresDamage; // Tyre damage (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_brakesDamage; // Brakes damage (percentage) + + public byte m_frontLeftWingDamage; // Front left wing damage (percentage) + public byte m_frontRightWingDamage; // Front right wing damage (percentage) + public byte m_rearWingDamage; // Rear wing damage (percentage) + public byte m_floorDamage; // Floor damage (percentage) + public byte m_diffuserDamage; // Diffuser damage (percentage) + public byte m_sidepodDamage; // Sidepod damage (percentage) + public byte m_drsFault; // Indicator for DRS fault, 0 = OK, 1 = fault + public byte m_ersFault; // Indicator for ERS fault, 0 = OK, 1 = fault + public byte m_gearBoxDamage; // Gear box damage (percentage) + public byte m_engineDamage; // Engine damage (percentage) + public byte m_engineMGUHWear; // Engine wear MGU-H (percentage) + public byte m_engineESWear; // Engine wear ES (percentage) + public byte m_engineCEWear; // Engine wear CE (percentage) + public byte m_engineICEWear; // Engine wear ICE (percentage) + public byte m_engineMGUKWear; // Engine wear MGU-K (percentage) + public byte m_engineTCWear; // Engine wear TC (percentage) + public byte m_engineBlown; // Engine blown, 0 = OK, 1 = fault + public byte m_engineSeized; // Engine seized, 0 = OK, 1 = fault + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/CarMotionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/CarMotionData.cs new file mode 100644 index 0000000..d548990 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/CarMotionData.cs @@ -0,0 +1,30 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Motion data for one car in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarMotionData + { + public float m_worldPositionX; // World space X position - metres + public float m_worldPositionY; // World space Y position + public float m_worldPositionZ; // World space Z position + public float m_worldVelocityX; // Velocity in world space X - metres/s + public float m_worldVelocityY; // Velocity in world space Y + public float m_worldVelocityZ; // Velocity in world space Z + public short m_worldForwardDirX; // World space forward X direction (normalised) + public short m_worldForwardDirY; // World space forward Y direction (normalised) + public short m_worldForwardDirZ; // World space forward Z direction (normalised) + public short m_worldRightDirX; // World space right X direction (normalised) + public short m_worldRightDirY; // World space right Y direction (normalised) + public short m_worldRightDirZ; // World space right Z direction (normalised) + public float m_gForceLateral; // Lateral G-Force component + public float m_gForceLongitudinal; // Longitudinal G-Force component + public float m_gForceVertical; // Vertical G-Force component + public float m_yaw; // Yaw angle in radians + public float m_pitch; // Pitch angle in radians + public float m_roll; // Roll angle in radians + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/CarSetupData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/CarSetupData.cs new file mode 100644 index 0000000..5d2ff56 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/CarSetupData.cs @@ -0,0 +1,35 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Data about one car setup in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarSetupData + { + public byte m_frontWing; // Front wing aero + public byte m_rearWing; // Rear wing aero + public byte m_onThrottle; // Differential adjustment on throttle (percentage) + public byte m_offThrottle; // Differential adjustment off throttle (percentage) + public float m_frontCamber; // Front camber angle (suspension geometry) + public float m_rearCamber; // Rear camber angle (suspension geometry) + public float m_frontToe; // Front toe angle (suspension geometry) + public float m_rearToe; // Rear toe angle (suspension geometry) + public byte m_frontSuspension; // Front suspension + public byte m_rearSuspension; // Rear suspension + public byte m_frontAntiRollBar; // Front anti-roll bar + public byte m_rearAntiRollBar; // Rear anti-roll bar + public byte m_frontSuspensionHeight; // Front ride height + public byte m_rearSuspensionHeight; // Rear ride height + public byte m_brakePressure; // Brake pressure (percentage) + public byte m_brakeBias; // Brake bias (percentage) + public byte m_engineBraking; // Engine braking (percentage) + public float m_rearLeftTyrePressure; // Rear left tyre pressure (PSI) + public float m_rearRightTyrePressure; // Rear right tyre pressure (PSI) + public float m_frontLeftTyrePressure; // Front left tyre pressure (PSI) + public float m_frontRightTyrePressure; // Front right tyre pressure (PSI) + public byte m_ballast; // Ballast + public float m_fuelLoad; // Fuel load + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/CarStatusData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/CarStatusData.cs new file mode 100644 index 0000000..1fbd8cb --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/CarStatusData.cs @@ -0,0 +1,42 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Car status data for one car in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarStatusData + { + public byte m_tractionControl; // Traction control - 0 = off, 1 = medium, 2 = full + public byte m_antiLockBrakes; // 0 (off) - 1 (on) + public byte m_fuelMix; // Fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max + public byte m_frontBrakeBias; // Front brake bias (percentage) + public byte m_pitLimiterStatus; // Pit limiter status - 0 = off, 1 = on + public float m_fuelInTank; // Current fuel mass + public float m_fuelCapacity; // Fuel capacity + public float m_fuelRemainingLaps; // Fuel remaining in terms of laps (value on MFD) + public ushort m_maxRPM; // Cars max RPM, point of rev limiter + public ushort m_idleRPM; // Cars idle RPM + public byte m_maxGears; // Maximum number of gears + public byte m_drsAllowed; // 0 = not allowed, 1 = allowed + public ushort m_drsActivationDistance; // 0 = DRS not available, non-zero - DRS will be available in [X] metres + public byte m_actualTyreCompound; // F1 Modern - 16 = C5, 17 = C4, 18 = C3, 19 = C2, 20 = C1, 21 = C0, 7 = inter, 8 = wet + // F1 Classic - 9 = dry, 10 = wet + // F2 – 11 = super soft, 12 = soft, 13 = medium, 14 = hard, 15 = wet + public byte m_visualTyreCompound; // F1 visual (can be different from actual compound) + // 16 = soft, 17 = medium, 18 = hard, 7 = inter, 8 = wet + // F1 Classic – same as above + // F2 '20, 15 = wet, 19 – super soft, 20 = soft, 21 = medium, 22 = hard + public byte m_tyresAgeLaps; // Age in laps of the current set of tyres + public sbyte m_vehicleFIAFlags; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow + public float m_enginePowerICE; // Engine power output of ICE (W) + public float m_enginePowerMGUK; // Engine power output of MGU-K (W) + public float m_ersStoreEnergy; // ERS energy store in Joules + public byte m_ersDeployMode; // ERS deployment mode, 0 = none, 1 = medium, 2 = hotlap, 3 = overtake + public float m_ersHarvestedThisLapMGUK; // ERS energy harvested this lap by MGU-K + public float m_ersHarvestedThisLapMGUH; // ERS energy harvested this lap by MGU-H + public float m_ersDeployedThisLap; // ERS energy deployed this lap + public byte m_networkPaused; // Whether the car is paused in a network game + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/CarTelemetryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/CarTelemetryData.cs new file mode 100644 index 0000000..060ba3c --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/CarTelemetryData.cs @@ -0,0 +1,39 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Telemetry data for one car in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarTelemetryData + { + public ushort m_speed; // Speed of car in kilometres per hour + public float m_throttle; // Amount of throttle applied (0.0 to 1.0) + public float m_steer; // Steering (-1.0 (full lock left) to 1.0 (full lock right)) + public float m_brake; // Amount of brake applied (0.0 to 1.0) + public byte m_clutch; // Amount of clutch applied (0 to 100) + public sbyte m_gear; // Gear selected (1-8, N=0, R=-1) + public ushort m_engineRPM; // Engine RPM + public byte m_drs; // 0 = off, 1 = on + public byte m_revLightsPercent; // Rev lights indicator (percentage) + public ushort m_revLightsBitValue; // Rev lights (bit 0 = leftmost LED, bit 14 = rightmost LED) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public ushort[] m_brakesTemperature; // Brakes temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresSurfaceTemperature; // Tyres surface temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresInnerTemperature; // Tyres inner temperature (celsius) + + public ushort m_engineTemperature; // Engine temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresPressure; // Tyre pressure (PSI) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_surfaceType; // Driving surface, see appendices + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/EventDataDetails.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/EventDataDetails.cs new file mode 100644 index 0000000..9385f4c --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/EventDataDetails.cs @@ -0,0 +1,123 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Explicit)] + public struct EventDataDetails + { + [FieldOffset(0)] public FastestLapData FastestLap; + [FieldOffset(0)] public RetirementData Retirement; + [FieldOffset(0)] public TeamMateInPitsData TeamMateInPits; + [FieldOffset(0)] public RaceWinnerData RaceWinner; + [FieldOffset(0)] public PenaltyData Penalty; + [FieldOffset(0)] public SpeedTrapData SpeedTrap; + [FieldOffset(0)] public StartLightsData StartLights; + [FieldOffset(0)] public DriveThroughPenaltyServedData DriveThroughPenaltyServed; + [FieldOffset(0)] public StopGoPenaltyServedData StopGoPenaltyServed; + [FieldOffset(0)] public FlashbackData Flashback; + [FieldOffset(0)] public ButtonsData Buttons; + [FieldOffset(0)] public OvertakeData Overtake; + [FieldOffset(0)] public SafetyCarData SafetyCar; + [FieldOffset(0)] public CollisionData Collision; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FastestLapData + { + public byte vehicleIdx; // Vehicle index of car achieving fastest lap + public float lapTime; // Lap time is in seconds + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RetirementData + { + public byte vehicleIdx; // Vehicle index of car retiring + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TeamMateInPitsData + { + public byte vehicleIdx; // Vehicle index of team mate + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RaceWinnerData + { + public byte vehicleIdx; // Vehicle index of the race winner + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PenaltyData + { + public byte penaltyType; // Penalty type – see Appendices + public byte infringementType; // Infringement type – see Appendices + public byte vehicleIdx; // Vehicle index of the car the penalty is applied to + public byte otherVehicleIdx; // Vehicle index of the other car involved + public byte time; // Time gained, or time spent doing action in seconds + public byte lapNum; // Lap the penalty occurred on + public byte placesGained; // Number of places gained by this + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SpeedTrapData + { + public byte vehicleIdx; // Vehicle index of the vehicle triggering speed trap + public float speed; // Top speed achieved in kilometres per hour + public byte isOverallFastestInSession; // Overall fastest speed in session = 1, otherwise 0 + public byte isDriverFastestInSession; // Fastest speed for driver in session = 1, otherwise 0 + public byte fastestVehicleIdxInSession; // Vehicle index of the vehicle that is the fastest in this session + public float fastestSpeedInSession; // Speed of the vehicle that is the fastest in this session + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct StartLightsData + { + public byte numLights; // Number of lights showing + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DriveThroughPenaltyServedData + { + public byte vehicleIdx; // Vehicle index of the vehicle serving drive through + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct StopGoPenaltyServedData + { + public byte vehicleIdx; // Vehicle index of the vehicle serving stop go + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FlashbackData + { + public uint flashbackFrameIdentifier; // Frame identifier flashed back to + public float flashbackSessionTime; // Session time flashed back to + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ButtonsData + { + public uint buttonStatus; // Bit flags specifying which buttons are being pressed currently - see appendices + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct OvertakeData + { + public byte overtakingVehicleIdx; // Vehicle index of the vehicle overtaking + public byte beingOvertakenVehicleIdx; // Vehicle index of the vehicle being overtaken + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SafetyCarData + { + public byte safetyCarType; // 0 = No Safety Car, 1 = Full Safety Car, 2 = Virtual Safety Car, 3 = Formation Lap Safety Car + public byte eventType; // 0 = Deployed, 1 = Returning, 2 = Returned, 3 = Resume Race + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CollisionData + { + public byte vehicle1Idx; // Vehicle index of the first vehicle involved in the collision + public byte vehicle2Idx; // Vehicle index of the second vehicle involved in the collision + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/FinalClassificationData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/FinalClassificationData.cs new file mode 100644 index 0000000..1b10fb2 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/FinalClassificationData.cs @@ -0,0 +1,32 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Data about one participant's final results in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FinalClassificationData + { + public byte m_position; // Finishing position + public byte m_numLaps; // Number of laps completed + public byte m_gridPosition; // Grid position of the car + public byte m_points; // Number of points scored + public byte m_numPitStops; // Number of pit stops made + public byte m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active, 3 = finished, 4 = didnotfinish, 5 = disqualified, 6 = not classified, 7 = retired + public uint m_bestLapTimeInMS; // Best lap time of the session in milliseconds + public double m_totalRaceTime; // Total race time in seconds without penalties + public byte m_penaltiesTime; // Total penalties accumulated in seconds + public byte m_numPenalties; // Number of penalties applied to this driver + public byte m_numTyreStints; // Number of tyres stints up to maximum + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsActual; // Actual tyres used by this driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsVisual; // Visual tyres used by this driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsEndLaps; // The lap number stints end on + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/LapData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/LapData.cs new file mode 100644 index 0000000..3b7587b --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/LapData.cs @@ -0,0 +1,45 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Lap data about one car in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LapData + { + public uint m_lastLapTimeInMS; // Last lap time in milliseconds + public uint m_currentLapTimeInMS; // Current time around the lap in milliseconds + public ushort m_sector1TimeMSPart; // Sector 1 time milliseconds part + public byte m_sector1TimeMinutesPart; // Sector 1 whole minute part + public ushort m_sector2TimeMSPart; // Sector 2 time milliseconds part + public byte m_sector2TimeMinutesPart; // Sector 2 whole minute part + public ushort m_deltaToCarInFrontMSPart; // Time delta to car in front milliseconds part + public byte m_deltaToCarInFrontMinutesPart; // Time delta to car in front whole minute part + public ushort m_deltaToRaceLeaderMSPart; // Time delta to race leader milliseconds part + public byte m_deltaToRaceLeaderMinutesPart; // Time delta to race leader whole minute part + public float m_lapDistance; // Distance vehicle is around current lap in metres – could be negative if line hasn't been crossed yet + public float m_totalDistance; // Total distance travelled in session in metres – could be negative if line hasn't been crossed yet + public float m_safetyCarDelta; // Delta in seconds for safety car + public byte m_carPosition; // Car race position + public byte m_currentLapNum; // Current lap number + public byte m_pitStatus; // 0 = none, 1 = pitting, 2 = in pit area + public byte m_numPitStops; // Number of pit stops taken in this race + public byte m_sector; // 0 = sector1, 1 = sector2, 2 = sector3 + public byte m_currentLapInvalid; // Current lap invalid - 0 = valid, 1 = invalid + public byte m_penalties; // Accumulated time penalties in seconds to be added + public byte m_totalWarnings; // Accumulated number of warnings issued + public byte m_cornerCuttingWarnings; // Accumulated number of corner cutting warnings issued + public byte m_numUnservedDriveThroughPens; // Num drive through pens left to serve + public byte m_numUnservedStopGoPens; // Num stop go pens left to serve + public byte m_gridPosition; // Grid position the vehicle started the race in + public byte m_driverStatus; // Status of driver - 0 = in garage, 1 = flying lap, 2 = in lap, 3 = out lap, 4 = on track + public byte m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active, 3 = finished, 4 = didnotfinish, 5 = disqualified, 6 = not classified, 7 = retired + public byte m_pitLaneTimerActive; // Pit lane timing, 0 = inactive, 1 = active + public ushort m_pitLaneTimeInLaneInMS; // If active, the current time spent in the pit lane in ms + public ushort m_pitStopTimerInMS; // Time of the actual pit stop in ms + public byte m_pitStopShouldServePen; // Whether the car should serve a penalty at this stop + public float m_speedTrapFastestSpeed; // Fastest speed through speed trap for this car in kmph + public byte m_speedTrapFastestLap; // Lap no the fastest speed was achieved, 255 = not set + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/LapHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/LapHistoryData.cs new file mode 100644 index 0000000..4609dbe --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/LapHistoryData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LapHistoryData + { + public uint m_lapTimeInMS; // Lap time in milliseconds + public ushort m_sector1TimeMSPart; // Sector 1 milliseconds part + public byte m_sector1TimeMinutesPart; // Sector 1 whole minute part + public ushort m_sector2TimeMSPart; // Sector 2 time milliseconds part + public byte m_sector2TimeMinutesPart; // Sector 2 whole minute part + public ushort m_sector3TimeMSPart; // Sector 3 time milliseconds part + public byte m_sector3TimeMinutesPart; // Sector 3 whole minute part + public byte m_lapValidBitFlags; // 0x01 bit set-lap valid, 0x02 bit set-sector 1 valid + // 0x04 bit set-sector 2 valid, 0x08 bit set-sector 3 valid + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/LobbyInfoData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/LobbyInfoData.cs new file mode 100644 index 0000000..a5b8e2d --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/LobbyInfoData.cs @@ -0,0 +1,26 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Data about one participant in the lobby in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LobbyInfoData + { + public byte m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + public byte m_teamId; // Team id - see appendix (255 if no team currently selected) + public byte m_nationality; // Nationality of the driver + public byte m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] + public byte[] m_name; // Name of participant in UTF-8 format – null terminated + // Will be truncated with ... (U+2026) if too long + + public byte m_carNumber; // Car number of the player + public byte m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + public byte m_showOnlineNames; // The player's show online names setting, 0 = off, 1 = on + public ushort m_techLevel; // F1 World tech level + public byte m_readyStatus; // 0 = not ready, 1 = ready, 2 = spectating + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/MarshalZone.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/MarshalZone.cs new file mode 100644 index 0000000..8590b16 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/MarshalZone.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MarshalZone + { + public float m_zoneStart; // Fraction (0..1) of way through the lap the marshal zone starts + public sbyte m_zoneFlag; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarDamageData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarDamageData.cs new file mode 100644 index 0000000..b60a793 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarDamageData.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarDamageData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarDamageData[] m_carDamageData; // data for all cars on track + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarSetupData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarSetupData.cs new file mode 100644 index 0000000..4063234 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarSetupData.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarSetupData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarSetupData[] m_carSetupData; + + public float m_nextFrontWingValue; // Value of front wing after next pit stop - player only + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarStatusData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarStatusData.cs new file mode 100644 index 0000000..75159ff --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarStatusData.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarStatusData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarStatusData[] m_carStatusData; // data for all cars on track + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarTelemetryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarTelemetryData.cs new file mode 100644 index 0000000..f26a62c --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketCarTelemetryData.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarTelemetryData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarTelemetryData[] m_carTelemetryData; // data for all cars on track + + public byte m_mfdPanelIndex; // Index of MFD panel open - 255 = MFD closed + // Single player, race – 0 = Car setup, 1 = Pits + // 2 = Damage, 3 = Engine, 4 = Temperatures + // May vary depending on game mode + public byte m_mfdPanelIndexSecondaryPlayer; // See above + public sbyte m_suggestedGear; // Suggested gear for the player (1-8), 0 if no gear suggested + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketEventData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketEventData.cs new file mode 100644 index 0000000..adb5979 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketEventData.cs @@ -0,0 +1,41 @@ +using System.Runtime.InteropServices; +using System.Text; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketEventData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_eventStringCode; // Event string code + + public EventDataDetails m_eventDetails; // Event details - should be interpreted differently for each type + + // Helper property to get event code as string + public string EventCode => Encoding.ASCII.GetString(m_eventStringCode); + + public T GetEventDetails() where T : struct + { + return EventCode switch + { + EventCodes.FastestLap when typeof(T) == typeof(FastestLapData) => (T)(object)m_eventDetails.FastestLap, + EventCodes.Retirement when typeof(T) == typeof(RetirementData) => (T)(object)m_eventDetails.Retirement, + EventCodes.TeamMateInPits when typeof(T) == typeof(TeamMateInPitsData) => (T)(object)m_eventDetails.TeamMateInPits, + EventCodes.RaceWinner when typeof(T) == typeof(RaceWinnerData) => (T)(object)m_eventDetails.RaceWinner, + EventCodes.Penalty when typeof(T) == typeof(PenaltyData) => (T)(object)m_eventDetails.Penalty, + EventCodes.SpeedTrap when typeof(T) == typeof(SpeedTrapData) => (T)(object)m_eventDetails.SpeedTrap, + EventCodes.StartLights when typeof(T) == typeof(StartLightsData) => (T)(object)m_eventDetails.StartLights, + EventCodes.DriveThroughServed when typeof(T) == typeof(DriveThroughPenaltyServedData) => (T)(object)m_eventDetails.DriveThroughPenaltyServed, + EventCodes.StopGoServed when typeof(T) == typeof(StopGoPenaltyServedData) => (T)(object)m_eventDetails.StopGoPenaltyServed, + EventCodes.Flashback when typeof(T) == typeof(FlashbackData) => (T)(object)m_eventDetails.Flashback, + EventCodes.ButtonStatus when typeof(T) == typeof(ButtonsData) => (T)(object)m_eventDetails.Buttons, + EventCodes.Overtake when typeof(T) == typeof(OvertakeData) => (T)(object)m_eventDetails.Overtake, + EventCodes.SafetyCar when typeof(T) == typeof(SafetyCarData) => (T)(object)m_eventDetails.SafetyCar, + EventCodes.Collision when typeof(T) == typeof(CollisionData) => (T)(object)m_eventDetails.Collision, + _ => throw new InvalidOperationException($"Cannot get {typeof(T).Name} for event {EventCode}") + }; + } + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketFinalClassificationData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketFinalClassificationData.cs new file mode 100644 index 0000000..9868f20 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketFinalClassificationData.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketFinalClassificationData + { + public PacketHeader m_header; + + public byte m_numCars; // Number of cars in the final classification + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public FinalClassificationData[] m_classificationData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketHeader.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketHeader.cs new file mode 100644 index 0000000..86390d4 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketHeader.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketHeader + { + public ushort m_packetFormat; // 2024 + public byte m_gameYear; // Game year - last two digits e.g. 24 + public byte m_gameMajorVersion; // Game major version - "X.00" + public byte m_gameMinorVersion; // Game minor version - "1.XX" + public byte m_packetVersion; // Version of this packet type + public byte m_packetId; // Identifier for the packet type + public ulong m_sessionUID; // Unique identifier for the session + public float m_sessionTime; // Session timestamp + public uint m_frameIdentifier; // Identifier for the frame the data was retrieved on + public uint m_overallFrameIdentifier; // Overall identifier for the frame the data was retrieved on, doesn't go back after flashbacks + public byte m_playerCarIndex; // Index of player's car in the array + public byte m_secondaryPlayerCarIndex; // Index of secondary player's car in the array (splitscreen) - 255 if no second player + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketLapData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketLapData.cs new file mode 100644 index 0000000..d6977fb --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketLapData.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLapData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public LapData[] m_lapData; // Lap data for all cars on track + + public byte m_timeTrialPBCarIdx; // Index of Personal Best car in time trial (255 if invalid) + public byte m_timeTrialRivalCarIdx; // Index of Rival car in time trial (255 if invalid) + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketLobbyInfoData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketLobbyInfoData.cs new file mode 100644 index 0000000..61ec950 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketLobbyInfoData.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLobbyInfoData + { + public PacketHeader m_header; + + public byte m_numPlayers; // Number of players in the lobby data + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public LobbyInfoData[] m_lobbyPlayers; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketMotionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketMotionData.cs new file mode 100644 index 0000000..4292f08 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketMotionData.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Motion data packet for F1 2024 telemetry. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketMotionData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarMotionData[] m_carMotionData; // Data for all cars on track + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketMotionExData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketMotionExData.cs new file mode 100644 index 0000000..b193250 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketMotionExData.cs @@ -0,0 +1,58 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketMotionExData + { + public PacketHeader m_header; + + // Extra player car ONLY data + // Note: All wheel arrays have the following order: RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionPosition; // RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionVelocity; // RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionAcceleration; // RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSpeed; // Speed of each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSlipRatio; // Slip ratio for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSlipAngle; // Slip angles for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelLatForce; // Lateral forces for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelLongForce; // Longitudinal forces for each wheel + + public float m_heightOfCOGAboveGround; // Height of centre of gravity above ground + public float m_localVelocityX; // Velocity in local space X - metres/s + public float m_localVelocityY; // Velocity in local space Y + public float m_localVelocityZ; // Velocity in local space Z + public float m_angularVelocityX; // Angular velocity x-component - radians/s + public float m_angularVelocityY; // Angular velocity y-component + public float m_angularVelocityZ; // Angular velocity z-component + public float m_angularAccelerationX; // Angular acceleration x-component - radians/s/s + public float m_angularAccelerationY; // Angular acceleration y-component + public float m_angularAccelerationZ; // Angular acceleration z-component + public float m_frontWheelsAngle; // Current front wheels angle in radians + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelVertForce; // Vertical forces for each wheel + + public float m_frontAeroHeight; // Front plank edge height above road surface + public float m_rearAeroHeight; // Rear plank edge height above road surface + public float m_frontRollAngle; // Roll angle of the front suspension + public float m_rearRollAngle; // Roll angle of the rear suspension + public float m_chassisYaw; // Yaw angle of the chassis relative to the direction of motion - radians + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketParticipantsData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketParticipantsData.cs new file mode 100644 index 0000000..f99fe39 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketParticipantsData.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketParticipantsData + { + public PacketHeader m_header; + + public byte m_numActiveCars; // Number of active cars in the data - should match number of cars on HUD + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ParticipantData[] m_participants; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketSessionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketSessionData.cs new file mode 100644 index 0000000..2ffdcf0 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketSessionData.cs @@ -0,0 +1,97 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketSessionData + { + public PacketHeader m_header; + + public byte m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast, 3 = light rain, 4 = heavy rain, 5 = storm + public sbyte m_trackTemperature; // Track temp. in degrees celsius + public sbyte m_airTemperature; // Air temp. in degrees celsius + public byte m_totalLaps; // Total number of laps in this race + public ushort m_trackLength; // Track length in metres + public byte m_sessionType; // 0 = unknown, see appendix + public sbyte m_trackId; // -1 for unknown, see appendix + public byte m_formula; // Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2, 3 = F1 Generic, 4 = Beta, 6 = Esports, 8 = F1 World, 9 = F1 Elimination + public ushort m_sessionTimeLeft; // Time left in session in seconds + public ushort m_sessionDuration; // Session duration in seconds + public byte m_pitSpeedLimit; // Pit speed limit in kilometres per hour + public byte m_gamePaused; // Whether the game is paused - network game only + public byte m_isSpectating; // Whether the player is spectating + public byte m_spectatorCarIndex; // Index of the car being spectated + public byte m_sliProNativeSupport; // SLI Pro support, 0 = inactive, 1 = active + public byte m_numMarshalZones; // Number of marshal zones to follow + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 21)] + public MarshalZone[] m_marshalZones; // List of marshal zones - max 21 + + public byte m_safetyCarStatus; // 0 = no safety car, 1 = full, 2 = virtual, 3 = formation lap + public byte m_networkGame; // 0 = offline, 1 = online + public byte m_numWeatherForecastSamples; // Number of weather samples to follow + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] + public WeatherForecastSample[] m_weatherForecastSamples; // Array of weather forecast samples + + public byte m_forecastAccuracy; // 0 = Perfect, 1 = Approximate + public byte m_aiDifficulty; // AI difficulty - 0-110 + public uint m_seasonLinkIdentifier; // Identifier for season - persists across saves + public uint m_weekendLinkIdentifier; // Identifier for weekend - persists across saves + public uint m_sessionLinkIdentifier; // Identifier for session - persists across saves + public byte m_pitStopWindowIdealLap; // Ideal lap to pit on for current strategy (player) + public byte m_pitStopWindowLatestLap; // Latest lap to pit on for current strategy (player) + public byte m_pitStopRejoinPosition; // Predicted position to rejoin at (player) + public byte m_steeringAssist; // 0 = off, 1 = on + public byte m_brakingAssist; // 0 = off, 1 = low, 2 = medium, 3 = high + public byte m_gearboxAssist; // 1 = manual, 2 = manual & suggested gear, 3 = auto + public byte m_pitAssist; // 0 = off, 1 = on + public byte m_pitReleaseAssist; // 0 = off, 1 = on + public byte m_ERSAssist; // 0 = off, 1 = on + public byte m_DRSAssist; // 0 = off, 1 = on + public byte m_dynamicRacingLine; // 0 = off, 1 = corners only, 2 = full + public byte m_dynamicRacingLineType; // 0 = 2D, 1 = 3D + public byte m_gameMode; // Game mode id - see appendix + public byte m_ruleSet; // Ruleset - see appendix + public uint m_timeOfDay; // Local time of day - minutes since midnight + public byte m_sessionLength; // 0 = None, 2 = Very Short, 3 = Short, 4 = Medium, 5 = Medium Long, 6 = Long, 7 = Full + public byte m_speedUnitsLeadPlayer; // 0 = MPH, 1 = KPH + public byte m_temperatureUnitsLeadPlayer; // 0 = Celsius, 1 = Fahrenheit + public byte m_speedUnitsSecondaryPlayer; // 0 = MPH, 1 = KPH + public byte m_temperatureUnitsSecondaryPlayer; // 0 = Celsius, 1 = Fahrenheit + public byte m_numSafetyCarPeriods; // Number of safety cars called during session + public byte m_numVirtualSafetyCarPeriods; // Number of virtual safety cars called during session + public byte m_numRedFlagPeriods; // Number of red flags called during session + public byte m_equalCarPerformance; // 0 = Off, 1 = On + public byte m_recoveryMode; // 0 = None, 1 = Flashbacks, 2 = Auto-recovery + public byte m_flashbackLimit; // 0 = Low, 1 = Medium, 2 = High, 3 = Unlimited + public byte m_surfaceType; // 0 = Simplified, 1 = Realistic + public byte m_lowFuelMode; // 0 = Easy, 1 = Hard + public byte m_raceStarts; // 0 = Manual, 1 = Assisted + public byte m_tyreTemperature; // 0 = Surface only, 1 = Surface & Carcass + public byte m_pitLaneTyreSim; // 0 = On, 1 = Off + public byte m_carDamage; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Simulation + public byte m_carDamageRate; // 0 = Reduced, 1 = Standard, 2 = Simulation + public byte m_collisions; // 0 = Off, 1 = Player-to-Player Off, 2 = On + public byte m_collisionsOffForFirstLapOnly; // 0 = Disabled, 1 = Enabled + public byte m_mpUnsafePitRelease; // 0 = On, 1 = Off (Multiplayer) + public byte m_mpOffForGriefing; // 0 = Disabled, 1 = Enabled (Multiplayer) + public byte m_cornerCuttingStringency; // 0 = Regular, 1 = Strict + public byte m_parcFermeRules; // 0 = Off, 1 = On + public byte m_pitStopExperience; // 0 = Automatic, 1 = Broadcast, 2 = Immersive + public byte m_safetyCar; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Increased + public byte m_safetyCarExperience; // 0 = Broadcast, 1 = Immersive + public byte m_formationLap; // 0 = Off, 1 = On + public byte m_formationLapExperience; // 0 = Broadcast, 1 = Immersive + public byte m_redFlags; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Increased + public byte m_affectsLicenceLevelSolo; // 0 = Off, 1 = On + public byte m_affectsLicenceLevelMP; // 0 = Off, 1 = On + public byte m_numSessionsInWeekend; // Number of session in following array + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)] + public byte[] m_weekendStructure; // List of session types to show weekend structure - see appendix for types + + public float m_sector2LapDistanceStart; // Distance in m around track where sector 2 starts + public float m_sector3LapDistanceStart; // Distance in m around track where sector 3 starts + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketSessionHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketSessionHistoryData.cs new file mode 100644 index 0000000..17d2078 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketSessionHistoryData.cs @@ -0,0 +1,25 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketSessionHistoryData + { + public PacketHeader m_header; + + public byte m_carIdx; // Index of the car this lap data relates to + public byte m_numLaps; // Num laps in the data (including current partial lap) + public byte m_numTyreStints; // Number of tyre stints in the data + + public byte m_bestLapTimeLapNum; // Lap the best lap time was achieved on + public byte m_bestSector1LapNum; // Lap the best Sector 1 time was achieved on + public byte m_bestSector2LapNum; // Lap the best Sector 2 time was achieved on + public byte m_bestSector3LapNum; // Lap the best Sector 3 time was achieved on + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)] + public LapHistoryData[] m_lapHistoryData; // 100 laps of data max + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public TyreStintHistoryData[] m_tyreStintsHistoryData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketTimeTrialData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketTimeTrialData.cs new file mode 100644 index 0000000..90b6bdf --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketTimeTrialData.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketTimeTrialData + { + public PacketHeader m_header; + + public TimeTrialDataSet m_playerSessionBestDataSet; // Player session best data set + public TimeTrialDataSet m_personalBestDataSet; // Personal best data set + public TimeTrialDataSet m_rivalDataSet; // Rival data set + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/PacketTyreSetsData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketTyreSetsData.cs new file mode 100644 index 0000000..b4fa500 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/PacketTyreSetsData.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketTyreSetsData + { + public PacketHeader m_header; + + public byte m_carIdx; // Index of the car this data relates to + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public TyreSetData[] m_tyreSetData; // 13 (dry) + 7 (wet) + + public byte m_fittedIdx; // Index into array of fitted tyre + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/ParticipantData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/ParticipantData.cs new file mode 100644 index 0000000..9668f72 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/ParticipantData.cs @@ -0,0 +1,28 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Data about one participant in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ParticipantData + { + public byte m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + public byte m_driverId; // Driver id - see appendix, 255 if network human + public byte m_networkId; // Network id - unique identifier for network players + public byte m_teamId; // Team id - see appendix + public byte m_myTeam; // My team flag - 1 = My Team, 0 = otherwise + public byte m_raceNumber; // Race number of the car + public byte m_nationality; // Nationality of the driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] + public byte[] m_name; // Name of participant in UTF-8 format – null terminated + // Will be truncated with ... (U+2026) if too long + + public byte m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + public byte m_showOnlineNames; // The player's show online names setting, 0 = off, 1 = on + public ushort m_techLevel; // F1 World tech level + public byte m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/TimeTrialDataSet.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/TimeTrialDataSet.cs new file mode 100644 index 0000000..9f3dc33 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/TimeTrialDataSet.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TimeTrialDataSet + { + public byte m_carIdx; // Index of the car this data relates to + public byte m_teamId; // Team id - see appendix + public uint m_lapTimeInMS; // Lap time in milliseconds + public uint m_sector1TimeInMS; // Sector 1 time in milliseconds + public uint m_sector2TimeInMS; // Sector 2 time in milliseconds + public uint m_sector3TimeInMS; // Sector 3 time in milliseconds + public byte m_tractionControl; // 0 = assist off, 1 = assist on + public byte m_gearboxAssist; // 0 = assist off, 1 = assist on + public byte m_antiLockBrakes; // 0 = assist off, 1 = assist on + public byte m_equalCarPerformance; // 0 = Realistic, 1 = Equal + public byte m_customSetup; // 0 = No, 1 = Yes + public byte m_valid; // 0 = invalid, 1 = valid + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/TyreSetData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/TyreSetData.cs new file mode 100644 index 0000000..c78945c --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/TyreSetData.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + /// + /// Data about one tyre set in F1 2024. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TyreSetData + { + public byte m_actualTyreCompound; // Actual tyre compound used + public byte m_visualTyreCompound; // Visual tyre compound used + public byte m_wear; // Tyre wear (percentage) + public byte m_available; // Whether this set is currently available + public byte m_recommendedSession; // Recommended session for tyre set, see appendix + public byte m_lifeSpan; // Laps left in this tyre set + public byte m_usableLife; // Max number of laps recommended for this compound + public short m_lapDeltaTime; // Lap delta time in milliseconds compared to fitted set + public byte m_fitted; // Whether the set is fitted or not + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/TyreStintHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/TyreStintHistoryData.cs new file mode 100644 index 0000000..f1497a3 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/TyreStintHistoryData.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TyreStintHistoryData + { + public byte m_endLap; // Lap the tyre usage ends on (255 if current tyre) + public byte m_tyreActualCompound; // Actual tyres used by this driver + public byte m_tyreVisualCompound; // Visual tyres used by this driver + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12024/WeatherForecastSample.cs b/GamesDat/Telemetry/Sources/Formula1/F12024/WeatherForecastSample.cs new file mode 100644 index 0000000..b6a80ab --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12024/WeatherForecastSample.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12024 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct WeatherForecastSample + { + public byte m_sessionType; // 0 = unknown, see appendix + public byte m_timeOffset; // Time in minutes the forecast is for + public byte m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast, 3 = light rain, 4 = heavy rain, 5 = storm + public sbyte m_trackTemperature; // Track temp. in degrees celsius + public sbyte m_trackTemperatureChange; // Track temp. change - 0 = up, 1 = down, 2 = no change + public sbyte m_airTemperature; // Air temp. in degrees celsius + public sbyte m_airTemperatureChange; // Air temp. change - 0 = up, 1 = down, 2 = no change + public byte m_rainPercentage; // Rain percentage (0-100) + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs b/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs new file mode 100644 index 0000000..5962cb5 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs @@ -0,0 +1,52 @@ +using GamesDat.Core.Telemetry.Sources.Formula1.F12024; +using GamesDat.Core.Telemetry.Sources.Formula1.F12025; + +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + internal static class F1PacketTypeMapper + { + private static readonly Dictionary<(ushort format, byte id), Type> _packetTypeMap = new() + { + // F1 2024 + [(2024, (byte)PacketId.Motion)] = typeof(F12024.PacketMotionData), + [(2024, (byte)PacketId.Session)] = typeof(F12024.PacketSessionData), + [(2024, (byte)PacketId.LapData)] = typeof(F12024.PacketLapData), + [(2024, (byte)PacketId.Event)] = typeof(F12024.PacketEventData), + [(2024, (byte)PacketId.Participants)] = typeof(F12024.PacketParticipantsData), + [(2024, (byte)PacketId.CarSetups)] = typeof(F12024.PacketCarSetupData), + [(2024, (byte)PacketId.CarTelemetry)] = typeof(F12024.PacketCarTelemetryData), + [(2024, (byte)PacketId.CarStatus)] = typeof(F12024.PacketCarStatusData), + [(2024, (byte)PacketId.FinalClassification)] = typeof(F12024.PacketFinalClassificationData), + [(2024, (byte)PacketId.LobbyInfo)] = typeof(F12024.PacketLobbyInfoData), + [(2024, (byte)PacketId.CarDamage)] = typeof(F12024.PacketCarDamageData), + [(2024, (byte)PacketId.SessionHistory)] = typeof(F12024.PacketSessionHistoryData), + [(2024, (byte)PacketId.TyreSets)] = typeof(F12024.PacketTyreSetsData), + [(2024, (byte)PacketId.MotionEx)] = typeof(F12024.PacketMotionExData), + [(2024, (byte)PacketId.TimeTrial)] = typeof(F12024.PacketTimeTrialData), + + // F1 2025 + [(2025, (byte)PacketId.Motion)] = typeof(F12025.MotionData), + [(2025, (byte)PacketId.Session)] = typeof(F12025.SessionData), + [(2025, (byte)PacketId.LapData)] = typeof(F12025.PacketLapData), + [(2025, (byte)PacketId.Event)] = typeof(F12025.EventData), + [(2025, (byte)PacketId.Participants)] = typeof(F12025.ParticipantData), + [(2025, (byte)PacketId.CarSetups)] = typeof(F12025.CarSetupData), + [(2025, (byte)PacketId.CarTelemetry)] = typeof(F12025.CarTelemetryData), + [(2025, (byte)PacketId.CarStatus)] = typeof(F12025.PacketCarStatusData), + [(2025, (byte)PacketId.FinalClassification)] = typeof(F12025.PacketFinalClassificationData), + [(2025, (byte)PacketId.LobbyInfo)] = typeof(F12025.PacketLobbyInfoData), + [(2025, (byte)PacketId.CarDamage)] = typeof(F12025.PacketCarDamageData), + [(2025, (byte)PacketId.SessionHistory)] = typeof(F12025.PacketSessionHistoryData), + [(2025, (byte)PacketId.TyreSets)] = typeof(F12025.PacketTyreSetsData), + [(2025, (byte)PacketId.MotionEx)] = typeof(F12025.PacketMotionExData), + [(2025, (byte)PacketId.TimeTrial)] = typeof(F12025.PacketTimeTrialData), + [(2025, (byte)PacketId.LapPositions)] = typeof(F12025.PacketLapPositionsData) + }; + + public static Type? GetPacketType(ushort packetFormat, byte packetId) + { + _packetTypeMap.TryGetValue((packetFormat, packetId), out var type); + return type; + } + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs b/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs index 1905657..394042e 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs @@ -1,20 +1,55 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.InteropServices; namespace GamesDat.Core.Telemetry.Sources.Formula1 { internal class F1RealtimeTelemetrySource : UdpSourceBase { + private const int MinRoutingHeaderSize = 7; + public F1RealtimeTelemetrySource(UdpSourceOptions options) : base(options) { } protected override IEnumerable ProcessData(byte[] data) { - yield return new F1TelemetryFrame(); + if (data.Length < MinRoutingHeaderSize) + yield break; + + var packetFormat = BitConverter.ToUInt16(data, 0); + var packetId = data[6]; + + var packetType = F1PacketTypeMapper.GetPacketType(packetFormat, packetId); + if (packetType == null) + yield break; + + var expectedSize = Marshal.SizeOf(packetType); + if (data.Length < expectedSize) + yield break; + + var packet = BytesToStruct(data, packetType); + + yield return new F1TelemetryFrame + { + Packet = packet, + PacketFormat = packetFormat, + PacketId = packetId, + PacketType = packetType + }; + } + + private static object BytesToStruct(byte[] bytes, Type type) + { + var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + try + { + return Marshal.PtrToStructure(handle.AddrOfPinnedObject(), type)!; + } + finally + { + handle.Free(); + } } } } diff --git a/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs index d6398bb..901664c 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs @@ -8,5 +8,16 @@ namespace GamesDat.Core.Telemetry.Sources.Formula1 { public struct F1TelemetryFrame { + public object Packet { get; init; } + public ushort PacketFormat { get; init; } + public byte PacketId { get; init; } + public Type PacketType { get; init; } + + public T GetPacket() where T : struct + { + if (Packet is T typed) + return typed; + throw new InvalidCastException($"Packet is {Packet?.GetType().Name ?? "null"}, not {typeof(T).Name}"); + } } } diff --git a/GamesDat/Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md b/GamesDat/Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md new file mode 100644 index 0000000..0fabbea --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md @@ -0,0 +1,322 @@ +# F1 UDP Telemetry Implementation Workflow + +This document describes the workflow for implementing F1 game UDP telemetry packet structures from C++ specifications into C# structs for the GamesDat project. + +## Overview + +The F1 games send UDP telemetry data using a specific binary format defined in C++ header files. This workflow guides the process of converting those C++ specifications into C# structs that can deserialize the UDP packets. + +## Prerequisites + +- C++ specification file (typically provided by EA/Codemasters) +- Understanding of C struct layout and C# interop +- Existing F1 implementation to use as reference (e.g., F12025) + +## Workflow Steps + +### 1. Analyze the C++ Specification + +Review the C++ header file to understand: +- **Packet format version** (e.g., 2024, 2025) +- **Packet sizes** (documented in comments) +- **Constants** (e.g., `cs_maxNumCarsInUDPData = 22`) +- **Struct hierarchy** (which structs contain others) +- **Array sizes** (fixed-size arrays in structs) + +### 2. Create the Namespace Folder + +Create a new folder under `Telemetry/Sources/Formula1/` with the naming pattern `F1YYYY` (e.g., `F12024`, `F12025`). + +### 3. Implement the PacketHeader + +Start with the `PacketHeader` struct as it's used by all packet types: + +```csharp +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F1YYYY +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketHeader + { + public ushort m_packetFormat; // YYYY + public byte m_gameYear; // Last two digits e.g. 24 + // ... other fields from spec + } +} +``` + +**Key points:** +- Always use `[StructLayout(LayoutKind.Sequential, Pack = 1)]` +- Keep field names exactly as in C++ spec (helps with debugging) +- Add inline comments from the C++ spec + +### 4. Implement Support Structures + +Create the smaller, reusable structs before the main packet types: + +Examples: `MarshalZone`, `WeatherForecastSample`, `CarMotionData`, etc. + +**Pattern:** +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct StructName +{ + public type m_fieldName; // comment from spec + // ... more fields +} +``` + +### 5. Implement Main Packet Structures + +For each packet type from the spec, create: + +1. **Component struct** (e.g., `LapData`) - data for one car/item +2. **Packet struct** (e.g., `PacketLapData`) - complete packet with header and array + +**Pattern:** +```csharp +// Component for one car +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct ComponentData +{ + public type m_field1; + public type m_field2; +} + +// Full packet +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct PacketComponentData +{ + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ComponentData[] m_dataArray; + + // ... any additional single fields +} +``` + +### 6. Handle Special Cases + +#### Fixed-Size Arrays +```csharp +[MarshalAs(UnmanagedType.ByValArray, SizeConst = SIZE)] +public Type[] m_arrayField; +``` + +#### Strings/Character Arrays +```csharp +[MarshalAs(UnmanagedType.ByValArray, SizeConst = LENGTH)] +public byte[] m_name; // UTF-8 null-terminated string +``` + +#### Unions (EventDataDetails) +Use `StructLayout(LayoutKind.Explicit)` with `FieldOffset`: + +```csharp +[StructLayout(LayoutKind.Explicit)] +public struct EventDataDetails +{ + [FieldOffset(0)] public FastestLapData FastestLap; + [FieldOffset(0)] public RetirementData Retirement; + // ... all union members at offset 0 +} +``` + +Then create a wrapper with helper methods: +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct PacketEventData +{ + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_eventStringCode; + + public EventDataDetails m_eventDetails; + + // Helper to get event code as string + public string EventCode => Encoding.ASCII.GetString(m_eventStringCode); + + // Helper to safely extract typed event details + public T GetEventDetails() where T : struct { ... } +} +``` + +### 7. Type Mapping Reference + +| C++ Type | C# Type | Notes | +|----------|---------|-------| +| `uint8` | `byte` | Unsigned 8-bit | +| `int8` | `sbyte` | Signed 8-bit | +| `uint16` | `ushort` | Unsigned 16-bit | +| `int16` | `short` | Signed 16-bit | +| `uint32` / `uint` | `uint` | Unsigned 32-bit | +| `int32` | `int` | Signed 32-bit | +| `uint64` | `ulong` | Unsigned 64-bit | +| `float` | `float` | 32-bit float | +| `double` | `double` | 64-bit float | +| `char[]` | `byte[]` | UTF-8 strings | + +### 8. Update F1PacketTypeMapper + +Add mappings for all packet types to `F1PacketTypeMapper.cs`: + +```csharp +private static readonly Dictionary<(ushort format, byte id), Type> _packetTypeMap = new() +{ + // F1 YYYY + [(YYYY, (byte)PacketId.Motion)] = typeof(F1YYYY.PacketMotionData), + [(YYYY, (byte)PacketId.Session)] = typeof(F1YYYY.PacketSessionData), + // ... all packet types + + // F1 previous years + // ... +}; +``` + +**Important:** Use fully qualified type names (e.g., `F12024.PacketMotionData`) to avoid conflicts between years. + +### 9. Packet Types Checklist + +Ensure you implement all standard packet types: + +- [ ] Motion (`PacketMotionData`) +- [ ] Session (`PacketSessionData`) +- [ ] Lap Data (`PacketLapData`) +- [ ] Event (`PacketEventData`) +- [ ] Participants (`PacketParticipantsData`) +- [ ] Car Setups (`PacketCarSetupData`) +- [ ] Car Telemetry (`PacketCarTelemetryData`) +- [ ] Car Status (`PacketCarStatusData`) +- [ ] Final Classification (`PacketFinalClassificationData`) +- [ ] Lobby Info (`PacketLobbyInfoData`) +- [ ] Car Damage (`PacketCarDamageData`) +- [ ] Session History (`PacketSessionHistoryData`) +- [ ] Tyre Sets (`PacketTyreSetsData`) +- [ ] Motion Ex (`PacketMotionExData`) +- [ ] Time Trial (`PacketTimeTrialData`) + +### 10. Validation + +After implementation: + +1. **Build the project** - ensure no compilation errors +2. **Check struct sizes** - if possible, verify binary size matches spec comments +3. **Test with real data** - use actual game UDP packets to validate deserialization +4. **Compare with previous year** - look for differences and ensure they're intentional + +## Common Patterns + +### Pattern 1: Simple Data Struct +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct SimpleData +{ + public byte m_field1; + public float m_field2; +} +``` + +### Pattern 2: Packet with Array +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct PacketWithArray +{ + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ItemData[] m_items; +} +``` + +### Pattern 3: Nested Arrays +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct DataWithArrays +{ + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresPressure; // 4 tyres +} +``` + +### Pattern 4: Data + Extra Fields +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct PacketWithExtras +{ + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ItemData[] m_items; + + public byte m_extraField1; + public byte m_extraField2; +} +``` + +## Best Practices + +1. **Maintain exact field order** - C# struct layout must match C++ exactly +2. **Use Pack = 1** - prevents automatic padding/alignment +3. **Keep original naming** - makes comparison with spec easier +4. **Add XML comments** - for complex structs, add summary docs +5. **Preserve spec comments** - inline comments help understand field meaning +6. **Test incrementally** - validate each packet type as you implement it +7. **Reference existing implementations** - use previous years as templates +8. **Watch for year-specific changes** - array sizes, new fields, removed fields + +## File Organization + +Each year's implementation should be self-contained: + +``` +Formula1/ +├── F12024/ +│ ├── PacketHeader.cs +│ ├── PacketMotionData.cs +│ ├── CarMotionData.cs +│ ├── PacketSessionData.cs +│ ├── MarshalZone.cs +│ ├── WeatherForecastSample.cs +│ └── ... (all other packet types) +├── F12025/ +│ └── ... (same structure) +├── F1PacketTypeMapper.cs +├── PacketId.cs (shared enum) +└── EventCodes.cs (shared constants) +``` + +## Troubleshooting + +### Issue: Struct size doesn't match spec +- Check for missing `Pack = 1` +- Verify all arrays have correct `SizeConst` +- Ensure no fields were skipped +- Check type mappings (e.g., `int8` vs `uint8`) + +### Issue: Deserialization fails +- Verify packet header format number matches +- Check PacketTypeMapper has correct entries +- Ensure union types use `LayoutKind.Explicit` +- Validate field order exactly matches spec + +### Issue: Data seems corrupted +- Check endianness (should be little-endian) +- Verify `Pack = 1` is set +- Ensure no extra padding between fields +- Check array sizes match spec constants + +## Example: Complete Implementation + +See `F12024/` folder for a complete reference implementation following this workflow. + +## Version History + +- **2024-02-09**: Created workflow documentation based on F12024 implementation +- **Future**: Update as new patterns or edge cases are discovered + +--- + +*This workflow is designed for GamesDat project contributors and AI agents implementing F1 telemetry support.* From 33773f400bdf796f47e8554d30a8c8cf97c5755f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Mon, 9 Feb 2026 14:30:33 +0100 Subject: [PATCH 07/25] Added F1 2023 structs --- .claude/skills/f1-add-game/SKILL.md | 409 +++++ GamesDat/.claude/settings.local.json | 3 +- .../Sources/Formula1/F12023/CarDamageData.cs | 36 + .../Sources/Formula1/F12023/CarMotionData.cs | 30 + .../Sources/Formula1/F12023/CarSetupData.cs | 31 + .../Sources/Formula1/F12023/CarStatusData.cs | 40 + .../Formula1/F12023/CarTelemetryData.cs | 36 + .../Formula1/F12023/EventDataDetails.cs | 110 ++ .../F12023/FinalClassificationData.cs | 31 + .../Sources/Formula1/F12023/LapData.cs | 43 + .../Sources/Formula1/F12023/LapHistoryData.cs | 18 + .../Sources/Formula1/F12023/LobbyInfoData.cs | 20 + .../Sources/Formula1/F12023/MarshalZone.cs | 11 + .../Formula1/F12023/PacketCarDamageData.cs | 19 + .../Formula1/F12023/PacketCarSetupData.cs | 19 + .../Formula1/F12023/PacketCarStatusData.cs | 19 + .../Formula1/F12023/PacketCarTelemetryData.cs | 27 + .../Formula1/F12023/PacketEventData.cs | 21 + .../F12023/PacketFinalClassificationData.cs | 21 + .../Sources/Formula1/F12023/PacketHeader.cs | 21 + .../Sources/Formula1/F12023/PacketLapData.cs | 22 + .../Formula1/F12023/PacketLobbyInfoData.cs | 21 + .../Formula1/F12023/PacketMotionData.cs | 19 + .../Formula1/F12023/PacketMotionExData.cs | 58 + .../Formula1/F12023/PacketParticipantsData.cs | 21 + .../Formula1/F12023/PacketSessionData.cs | 79 + .../F12023/PacketSessionHistoryData.cs | 31 + .../Formula1/F12023/PacketTyreSetsData.cs | 23 + .../Formula1/F12023/ParticipantData.cs | 24 + .../Sources/Formula1/F12023/TyreSetData.cs | 18 + .../Formula1/F12023/TyreStintHistoryData.cs | 12 + .../Formula1/F12023/WeatherForecastSample.cs | 20 + .../Sources/Formula1/F1PacketTypeMapper.cs | 17 + .../Formula1/Specifications/F1_2023_spec.h | 1342 +++++++++++++++++ .../Sources/Formula1/Specifications/README.md | 31 + 35 files changed, 2702 insertions(+), 1 deletion(-) create mode 100644 .claude/skills/f1-add-game/SKILL.md create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/CarDamageData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/CarMotionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/CarSetupData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/CarStatusData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/CarTelemetryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/EventDataDetails.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/FinalClassificationData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/LapData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/LapHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/LobbyInfoData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/MarshalZone.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarDamageData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarSetupData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarStatusData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarTelemetryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketEventData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketFinalClassificationData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketHeader.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketLapData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketLobbyInfoData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketMotionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketMotionExData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketParticipantsData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketSessionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketSessionHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/PacketTyreSetsData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/ParticipantData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/TyreSetData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/TyreStintHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12023/WeatherForecastSample.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2023_spec.h create mode 100644 GamesDat/Telemetry/Sources/Formula1/Specifications/README.md diff --git a/.claude/skills/f1-add-game/SKILL.md b/.claude/skills/f1-add-game/SKILL.md new file mode 100644 index 0000000..45a731b --- /dev/null +++ b/.claude/skills/f1-add-game/SKILL.md @@ -0,0 +1,409 @@ +--- +name: f1-add-game +description: Adds the required telemetry structs for a specific game year of the F1 game series made by EA/Codemasters, based on their C++ specification files. +--- + +# F1 UDP Telemetry Implementation Skill + +You are an expert F1 UDP telemetry implementation specialist for the GamesDat project. Your role is to convert C++ specification files from EA/Codemasters into C# structs that can deserialize UDP packets. + +## Invocation + +When invoked with `/f1-implementation ` (e.g., `/f1-implementation 2026`), follow this workflow to implement F1 telemetry packet structures. + +## Step 0: Locate the C++ Specification + +**Before starting implementation, you MUST locate the C++ specification file using these strategies:** + +### Strategy 1: Check Convention-Based Location +Look for specification files in these locations: +- `Telemetry/Sources/Formula1/Specifications/F1__spec.h` +- `Telemetry/Sources/Formula1/Specifications/F1_spec.h` +- `Telemetry/Sources/Formula1/Specifications/_udp_spec.h` +- `Telemetry/Sources/Formula1/Docs/` directory + +Use Glob tool to search: +``` +pattern: "Telemetry/Sources/Formula1/**/*.h" +pattern: "Telemetry/Sources/Formula1/**/*.cpp" +pattern: "Telemetry/Sources/Formula1/**/*spec*" +pattern: "Telemetry/Sources/Formula1/**/**" +``` + +### Strategy 2: Search Repository-Wide +If not found in Formula1 directory, search the entire repository: +``` +pattern: "**/*F1**.h" +pattern: "**/*udp**.h" +``` + +### Strategy 3: Ask User for Specification +If specification is not found, ask the user: + +**Use AskUserQuestion tool with these options:** +- **Question**: "I couldn't find the C++ specification file for F1 . How would you like to provide it?" +- **Header**: "Spec Location" +- **Options**: + 1. **Label**: "File path on disk", **Description**: "I'll provide the path to the .h or .cpp file" + 2. **Label**: "Paste content", **Description**: "I'll paste the C++ specification content directly" + 3. **Label**: "Download from URL", **Description**: "I'll provide a URL to download the spec" + 4. **Label**: "Use previous year as template", **Description**: "Copy from F1 and I'll modify manually" + +### Strategy 4: Handle User Response +- **If file path provided**: Read the file using Read tool +- **If content pasted**: Proceed with the pasted content +- **If URL provided**: Use WebFetch or Bash (curl) to download +- **If template requested**: Copy previous year's implementation and note differences for manual review + +**IMPORTANT**: Do NOT proceed with implementation until you have the specification content in hand. + +--- + +## Implementation Workflow + +Once you have the C++ specification, follow these steps: + +### 1. Analyze the C++ Specification + +Review the spec to understand: +- **Packet format version** (e.g., 2024, 2025) +- **Packet sizes** (documented in comments) +- **Constants** (e.g., `cs_maxNumCarsInUDPData = 22`) +- **Struct hierarchy** (which structs contain others) +- **Array sizes** (fixed-size arrays in structs) + +### 2. Create the Namespace Folder + +Create: `Telemetry/Sources/Formula1/F1/` + +Example: `Telemetry/Sources/Formula1/F12026/` + +### 3. Implement the PacketHeader + +Start with the `PacketHeader` struct as it's used by all packet types: + +```csharp +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F1 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketHeader + { + public ushort m_packetFormat; // + public byte m_gameYear; // Last two digits e.g. 26 + public byte m_gameMajorVersion; // Game major version + public byte m_gameMinorVersion; // Game minor version + public byte m_packetVersion; // Version of this packet type + public byte m_packetId; // Identifier for the packet type + public ulong m_sessionUID; // Unique identifier for the session + public float m_sessionTime; // Session timestamp + public uint m_frameIdentifier; // Identifier for the frame + public uint m_overallFrameIdentifier; // Overall identifier + public byte m_playerCarIndex; // Index of player's car + public byte m_secondaryPlayerCarIndex; // Index of secondary player's car + } +} +``` + +**Key points:** +- Always use `[StructLayout(LayoutKind.Sequential, Pack = 1)]` +- Keep field names exactly as in C++ spec (helps with debugging) +- Add inline comments from the C++ spec + +### 4. Implement Support Structures + +Create the smaller, reusable structs before the main packet types. + +Examples: `MarshalZone`, `WeatherForecastSample`, `CarMotionData`, etc. + +**Pattern:** +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct StructName +{ + public type m_fieldName; // comment from spec + // ... more fields +} +``` + +### 5. Implement Main Packet Structures + +For each packet type from the spec, create: + +1. **Component struct** (e.g., `LapData`) - data for one car/item +2. **Packet struct** (e.g., `PacketLapData`) - complete packet with header and array + +**Pattern:** +```csharp +// Component for one car +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct ComponentData +{ + public type m_field1; + public type m_field2; +} + +// Full packet +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct PacketComponentData +{ + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ComponentData[] m_dataArray; + + // ... any additional single fields +} +``` + +### 6. Handle Special Cases + +#### Fixed-Size Arrays +```csharp +[MarshalAs(UnmanagedType.ByValArray, SizeConst = SIZE)] +public Type[] m_arrayField; +``` + +#### Strings/Character Arrays +```csharp +[MarshalAs(UnmanagedType.ByValArray, SizeConst = LENGTH)] +public byte[] m_name; // UTF-8 null-terminated string +``` + +#### Unions (EventDataDetails) +Use `StructLayout(LayoutKind.Explicit)` with `FieldOffset`: + +```csharp +[StructLayout(LayoutKind.Explicit)] +public struct EventDataDetails +{ + [FieldOffset(0)] public FastestLapData FastestLap; + [FieldOffset(0)] public RetirementData Retirement; + // ... all union members at offset 0 +} +``` + +Then create a wrapper with helper methods: +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct PacketEventData +{ + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_eventStringCode; + + public EventDataDetails m_eventDetails; + + // Helper to get event code as string + public string EventCode => System.Text.Encoding.ASCII.GetString(m_eventStringCode); + + // Helper to safely extract typed event details + public T GetEventDetails() where T : struct + { + // Implementation for safe union access + } +} +``` + +### 7. Type Mapping Reference + +| C++ Type | C# Type | Notes | +|----------|---------|-------| +| `uint8` | `byte` | Unsigned 8-bit | +| `int8` | `sbyte` | Signed 8-bit | +| `uint16` | `ushort` | Unsigned 16-bit | +| `int16` | `short` | Signed 16-bit | +| `uint32` / `uint` | `uint` | Unsigned 32-bit | +| `int32` | `int` | Signed 32-bit | +| `uint64` | `ulong` | Unsigned 64-bit | +| `float` | `float` | 32-bit float | +| `double` | `double` | 64-bit float | +| `char[]` | `byte[]` | UTF-8 strings | + +### 8. Update F1PacketTypeMapper + +Add mappings for all packet types to `Telemetry/Sources/Formula1/F1PacketTypeMapper.cs`: + +```csharp +private static readonly Dictionary<(ushort format, byte id), Type> _packetTypeMap = new() +{ + // F1 + [(, (byte)PacketId.Motion)] = typeof(F1.PacketMotionData), + [(, (byte)PacketId.Session)] = typeof(F1.PacketSessionData), + [(, (byte)PacketId.LapData)] = typeof(F1.PacketLapData), + [(, (byte)PacketId.Event)] = typeof(F1.PacketEventData), + [(, (byte)PacketId.Participants)] = typeof(F1.PacketParticipantsData), + [(, (byte)PacketId.CarSetups)] = typeof(F1.PacketCarSetupData), + [(, (byte)PacketId.CarTelemetry)] = typeof(F1.PacketCarTelemetryData), + [(, (byte)PacketId.CarStatus)] = typeof(F1.PacketCarStatusData), + [(, (byte)PacketId.FinalClassification)] = typeof(F1.PacketFinalClassificationData), + [(, (byte)PacketId.LobbyInfo)] = typeof(F1.PacketLobbyInfoData), + [(, (byte)PacketId.CarDamage)] = typeof(F1.PacketCarDamageData), + [(, (byte)PacketId.SessionHistory)] = typeof(F1.PacketSessionHistoryData), + [(, (byte)PacketId.TyreSets)] = typeof(F1.PacketTyreSetsData), + [(, (byte)PacketId.MotionEx)] = typeof(F1.PacketMotionExData), + [(, (byte)PacketId.TimeTrial)] = typeof(F1.PacketTimeTrialData), + // ... existing previous years ... +}; +``` + +**Important:** Use fully qualified type names (e.g., `F12024.PacketMotionData`) to avoid conflicts between years. + +### 9. Packet Types Checklist + +Ensure you implement all standard packet types: + +- [ ] Motion (`PacketMotionData`) +- [ ] Session (`PacketSessionData`) +- [ ] Lap Data (`PacketLapData`) +- [ ] Event (`PacketEventData`) +- [ ] Participants (`PacketParticipantsData`) +- [ ] Car Setups (`PacketCarSetupData`) +- [ ] Car Telemetry (`PacketCarTelemetryData`) +- [ ] Car Status (`PacketCarStatusData`) +- [ ] Final Classification (`PacketFinalClassificationData`) +- [ ] Lobby Info (`PacketLobbyInfoData`) +- [ ] Car Damage (`PacketCarDamageData`) +- [ ] Session History (`PacketSessionHistoryData`) +- [ ] Tyre Sets (`PacketTyreSetsData`) +- [ ] Motion Ex (`PacketMotionExData`) +- [ ] Time Trial (`PacketTimeTrialData`) + +**Note:** Some years may have additional packet types or missing ones. Follow the specification exactly. + +### 10. Validation + +After implementation: + +1. **Build the project** - `dotnet build` to ensure no compilation errors +2. **Check struct sizes** - if possible, verify binary size matches spec comments +3. **Compare with previous year** - look for differences and ensure they're intentional +4. **Create summary** - list all implemented packet types and any notable changes from previous year + +--- + +## Best Practices + +1. **Maintain exact field order** - C# struct layout must match C++ exactly +2. **Use Pack = 1** - prevents automatic padding/alignment +3. **Keep original naming** - makes comparison with spec easier +4. **Add XML comments** - for complex structs, add summary docs +5. **Preserve spec comments** - inline comments help understand field meaning +6. **Reference existing implementations** - use previous years as templates (F12024, F12025) +7. **Watch for year-specific changes** - array sizes, new fields, removed fields +8. **One file per packet type** - keeps code organized and maintainable + +## File Organization Pattern + +``` +Formula1/ +├── F1/ +│ ├── PacketHeader.cs +│ ├── CarMotionData.cs +│ ├── PacketMotionData.cs +│ ├── MarshalZone.cs +│ ├── WeatherForecastSample.cs +│ ├── PacketSessionData.cs +│ ├── LapData.cs +│ ├── PacketLapData.cs +│ └── ... (all other packet types) +├── F1PacketTypeMapper.cs (update this) +├── PacketId.cs (shared enum) +└── EventCodes.cs (shared constants) +``` + +## Common Patterns Reference + +### Pattern 1: Simple Data Struct +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct SimpleData +{ + public byte m_field1; + public float m_field2; +} +``` + +### Pattern 2: Packet with Array +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct PacketWithArray +{ + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ItemData[] m_items; +} +``` + +### Pattern 3: Nested Arrays +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct DataWithArrays +{ + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresPressure; // 4 tyres +} +``` + +### Pattern 4: Data + Extra Fields +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct PacketWithExtras +{ + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ItemData[] m_items; + + public byte m_extraField1; + public byte m_extraField2; +} +``` + +## Troubleshooting + +### Issue: Struct size doesn't match spec +- Check for missing `Pack = 1` +- Verify all arrays have correct `SizeConst` +- Ensure no fields were skipped +- Check type mappings (e.g., `int8` vs `uint8`) + +### Issue: Deserialization fails +- Verify packet header format number matches +- Check PacketTypeMapper has correct entries +- Ensure union types use `LayoutKind.Explicit` +- Validate field order exactly matches spec + +### Issue: Data seems corrupted +- Check endianness (should be little-endian) +- Verify `Pack = 1` is set +- Ensure no extra padding between fields +- Check array sizes match spec constants + +--- + +## Execution Approach + +When this skill is invoked: + +1. **Extract the year** from the invocation (e.g., "2026" from `/f1-implementation 2026`) +2. **Locate the specification** using the strategies in Step 0 +3. **Read existing implementations** (F12024, F12025) to understand patterns +4. **Implement all packet types** following the workflow above +5. **Update F1PacketTypeMapper** with new mappings +6. **Build and validate** the implementation +7. **Provide summary** of what was implemented + +## Reference Implementations + +Use these existing implementations as templates: +- `Telemetry/Sources/Formula1/F12024/` - Complete F1 2024 implementation +- `Telemetry/Sources/Formula1/F12025/` - Complete F1 2025 implementation +- `Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md` - Original workflow documentation + +--- + +*This skill automates F1 UDP telemetry implementation for GamesDat.* diff --git a/GamesDat/.claude/settings.local.json b/GamesDat/.claude/settings.local.json index 562a20f..43a375f 100644 --- a/GamesDat/.claude/settings.local.json +++ b/GamesDat/.claude/settings.local.json @@ -11,7 +11,8 @@ "Bash(dir /s /b \"..\\\\GamesDate.Demo.Wpf\\\\Views\\\\*.xaml\")", "WebFetch(domain:wiki.trackmania.io)", "Bash(xargs:*)", - "Bash(grep:*)" + "Bash(grep:*)", + "Bash(dir:*)" ] } } diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/CarDamageData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/CarDamageData.cs new file mode 100644 index 0000000..b5036e8 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/CarDamageData.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarDamageData + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresWear; // Tyre wear (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresDamage; // Tyre damage (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_brakesDamage; // Brakes damage (percentage) + + public byte m_frontLeftWingDamage; // Front left wing damage (percentage) + public byte m_frontRightWingDamage; // Front right wing damage (percentage) + public byte m_rearWingDamage; // Rear wing damage (percentage) + public byte m_floorDamage; // Floor damage (percentage) + public byte m_diffuserDamage; // Diffuser damage (percentage) + public byte m_sidepodDamage; // Sidepod damage (percentage) + public byte m_drsFault; // Indicator for DRS fault, 0 = OK, 1 = fault + public byte m_ersFault; // Indicator for ERS fault, 0 = OK, 1 = fault + public byte m_gearBoxDamage; // Gear box damage (percentage) + public byte m_engineDamage; // Engine damage (percentage) + public byte m_engineMGUHWear; // Engine wear MGU-H (percentage) + public byte m_engineESWear; // Engine wear ES (percentage) + public byte m_engineCEWear; // Engine wear CE (percentage) + public byte m_engineICEWear; // Engine wear ICE (percentage) + public byte m_engineMGUKWear; // Engine wear MGU-K (percentage) + public byte m_engineTCWear; // Engine wear TC (percentage) + public byte m_engineBlown; // Engine blown, 0 = OK, 1 = fault + public byte m_engineSeized; // Engine seized, 0 = OK, 1 = fault + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/CarMotionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/CarMotionData.cs new file mode 100644 index 0000000..a10f9cd --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/CarMotionData.cs @@ -0,0 +1,30 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Motion data for one car in F1 2023. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarMotionData + { + public float m_worldPositionX; // World space X position - metres + public float m_worldPositionY; // World space Y position + public float m_worldPositionZ; // World space Z position + public float m_worldVelocityX; // Velocity in world space X - metres/s + public float m_worldVelocityY; // Velocity in world space Y + public float m_worldVelocityZ; // Velocity in world space Z + public short m_worldForwardDirX; // World space forward X direction (normalised) + public short m_worldForwardDirY; // World space forward Y direction (normalised) + public short m_worldForwardDirZ; // World space forward Z direction (normalised) + public short m_worldRightDirX; // World space right X direction (normalised) + public short m_worldRightDirY; // World space right Y direction (normalised) + public short m_worldRightDirZ; // World space right Z direction (normalised) + public float m_gForceLateral; // Lateral G-Force component + public float m_gForceLongitudinal; // Longitudinal G-Force component + public float m_gForceVertical; // Vertical G-Force component + public float m_yaw; // Yaw angle in radians + public float m_pitch; // Pitch angle in radians + public float m_roll; // Roll angle in radians + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/CarSetupData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/CarSetupData.cs new file mode 100644 index 0000000..dd927d3 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/CarSetupData.cs @@ -0,0 +1,31 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarSetupData + { + public byte m_frontWing; // Front wing aero + public byte m_rearWing; // Rear wing aero + public byte m_onThrottle; // Differential adjustment on throttle (percentage) + public byte m_offThrottle; // Differential adjustment off throttle (percentage) + public float m_frontCamber; // Front camber angle (suspension geometry) + public float m_rearCamber; // Rear camber angle (suspension geometry) + public float m_frontToe; // Front toe angle (suspension geometry) + public float m_rearToe; // Rear toe angle (suspension geometry) + public byte m_frontSuspension; // Front suspension + public byte m_rearSuspension; // Rear suspension + public byte m_frontAntiRollBar; // Front anti-roll bar + public byte m_rearAntiRollBar; // Rear anti-roll bar + public byte m_frontSuspensionHeight; // Front ride height + public byte m_rearSuspensionHeight; // Rear ride height + public byte m_brakePressure; // Brake pressure (percentage) + public byte m_brakeBias; // Brake bias (percentage) + public float m_rearLeftTyrePressure; // Rear left tyre pressure (PSI) + public float m_rearRightTyrePressure; // Rear right tyre pressure (PSI) + public float m_frontLeftTyrePressure; // Front left tyre pressure (PSI) + public float m_frontRightTyrePressure; // Front right tyre pressure (PSI) + public byte m_ballast; // Ballast + public float m_fuelLoad; // Fuel load + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/CarStatusData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/CarStatusData.cs new file mode 100644 index 0000000..ed99732 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/CarStatusData.cs @@ -0,0 +1,40 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarStatusData + { + public byte m_tractionControl; // Traction control - 0 = off, 1 = medium, 2 = full + public byte m_antiLockBrakes; // 0 (off) - 1 (on) + public byte m_fuelMix; // Fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max + public byte m_frontBrakeBias; // Front brake bias (percentage) + public byte m_pitLimiterStatus; // Pit limiter status - 0 = off, 1 = on + public float m_fuelInTank; // Current fuel mass + public float m_fuelCapacity; // Fuel capacity + public float m_fuelRemainingLaps; // Fuel remaining in terms of laps (value on MFD) + public ushort m_maxRPM; // Cars max RPM, point of rev limiter + public ushort m_idleRPM; // Cars idle RPM + public byte m_maxGears; // Maximum number of gears + public byte m_drsAllowed; // 0 = not allowed, 1 = allowed + public ushort m_drsActivationDistance; // 0 = DRS not available, non-zero - DRS will be available in [X] metres + public byte m_actualTyreCompound; // F1 Modern - 16 = C5, 17 = C4, 18 = C3, 19 = C2, 20 = C1 + // 21 = C0, 7 = inter, 8 = wet + // F1 Classic - 9 = dry, 10 = wet + // F2 - 11 = super soft, 12 = soft, 13 = medium, 14 = hard, 15 = wet + public byte m_visualTyreCompound; // F1 visual (can be different from actual compound) + // 16 = soft, 17 = medium, 18 = hard, 7 = inter, 8 = wet + // F1 Classic - same as above + // F2 - 19, 15 = wet, 19 - super soft, 20 = soft, 21 = medium, 22 = hard + public byte m_tyresAgeLaps; // Age in laps of the current set of tyres + public sbyte m_vehicleFiaFlags; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow + public float m_enginePowerICE; // Engine power output of ICE (W) + public float m_enginePowerMGUK; // Engine power output of MGU-K (W) + public float m_ersStoreEnergy; // ERS energy store in Joules + public byte m_ersDeployMode; // ERS deployment mode, 0 = none, 1 = medium, 2 = hotlap, 3 = overtake + public float m_ersHarvestedThisLapMGUK; // ERS energy harvested this lap by MGU-K + public float m_ersHarvestedThisLapMGUH; // ERS energy harvested this lap by MGU-H + public float m_ersDeployedThisLap; // ERS energy deployed this lap + public byte m_networkPaused; // Whether the car is paused in a network game + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/CarTelemetryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/CarTelemetryData.cs new file mode 100644 index 0000000..2636e92 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/CarTelemetryData.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarTelemetryData + { + public ushort m_speed; // Speed of car in kilometres per hour + public float m_throttle; // Amount of throttle applied (0.0 to 1.0) + public float m_steer; // Steering (-1.0 (full lock left) to 1.0 (full lock right)) + public float m_brake; // Amount of brake applied (0.0 to 1.0) + public byte m_clutch; // Amount of clutch applied (0 to 100) + public sbyte m_gear; // Gear selected (1-8, N=0, R=-1) + public ushort m_engineRPM; // Engine RPM + public byte m_drs; // 0 = off, 1 = on + public byte m_revLightsPercent; // Rev lights indicator (percentage) + public ushort m_revLightsBitValue; // Rev lights (bit 0 = leftmost LED, bit 14 = rightmost LED) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public ushort[] m_brakesTemperature; // Brakes temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresSurfaceTemperature; // Tyres surface temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresInnerTemperature; // Tyres inner temperature (celsius) + + public ushort m_engineTemperature; // Engine temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresPressure; // Tyres pressure (PSI) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_surfaceType; // Driving surface, see appendices + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/EventDataDetails.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/EventDataDetails.cs new file mode 100644 index 0000000..d0027bb --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/EventDataDetails.cs @@ -0,0 +1,110 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Union of all event data types. Use the event code to determine which struct to access. + /// + [StructLayout(LayoutKind.Explicit)] + public struct EventDataDetails + { + [FieldOffset(0)] public FastestLapData FastestLap; + [FieldOffset(0)] public RetirementData Retirement; + [FieldOffset(0)] public TeamMateInPitsData TeamMateInPits; + [FieldOffset(0)] public RaceWinnerData RaceWinner; + [FieldOffset(0)] public PenaltyData Penalty; + [FieldOffset(0)] public SpeedTrapData SpeedTrap; + [FieldOffset(0)] public StartLightsData StartLights; + [FieldOffset(0)] public DriveThroughPenaltyServedData DriveThroughPenaltyServed; + [FieldOffset(0)] public StopGoPenaltyServedData StopGoPenaltyServed; + [FieldOffset(0)] public FlashbackData Flashback; + [FieldOffset(0)] public ButtonsData Buttons; + [FieldOffset(0)] public OvertakeData Overtake; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FastestLapData + { + public byte vehicleIdx; // Vehicle index of car achieving fastest lap + public float lapTime; // Lap time is in seconds + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RetirementData + { + public byte vehicleIdx; // Vehicle index of car retiring + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TeamMateInPitsData + { + public byte vehicleIdx; // Vehicle index of team mate + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RaceWinnerData + { + public byte vehicleIdx; // Vehicle index of the race winner + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PenaltyData + { + public byte penaltyType; // Penalty type - see Appendices + public byte infringementType; // Infringement type - see Appendices + public byte vehicleIdx; // Vehicle index of the car the penalty is applied to + public byte otherVehicleIdx; // Vehicle index of the other car involved + public byte time; // Time gained, or time spent doing action in seconds + public byte lapNum; // Lap the penalty occurred on + public byte placesGained; // Number of places gained by this + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SpeedTrapData + { + public byte vehicleIdx; // Vehicle index of the vehicle triggering speed trap + public float speed; // Top speed achieved in kilometres per hour + public byte isOverallFastestInSession; // Overall fastest speed in session = 1, otherwise 0 + public byte isDriverFastestInSession; // Fastest speed for driver in session = 1, otherwise 0 + public byte fastestVehicleIdxInSession; // Vehicle index of the vehicle that is the fastest in this session + public float fastestSpeedInSession; // Speed of the vehicle that is the fastest in this session + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct StartLightsData + { + public byte numLights; // Number of lights showing + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DriveThroughPenaltyServedData + { + public byte vehicleIdx; // Vehicle index of the vehicle serving drive through + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct StopGoPenaltyServedData + { + public byte vehicleIdx; // Vehicle index of the vehicle serving stop go + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FlashbackData + { + public uint flashbackFrameIdentifier; // Frame identifier flashed back to + public float flashbackSessionTime; // Session time flashed back to + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ButtonsData + { + public uint buttonStatus; // Bit flags specifying which buttons are being pressed currently - see appendices + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct OvertakeData + { + public byte overtakingVehicleIdx; // Vehicle index of the vehicle overtaking + public byte beingOvertakenVehicleIdx; // Vehicle index of the vehicle being overtaken + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/FinalClassificationData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/FinalClassificationData.cs new file mode 100644 index 0000000..3ef3f5a --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/FinalClassificationData.cs @@ -0,0 +1,31 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FinalClassificationData + { + public byte m_position; // Finishing position + public byte m_numLaps; // Number of laps completed + public byte m_gridPosition; // Grid position of the car + public byte m_points; // Number of points scored + public byte m_numPitStops; // Number of pit stops made + public byte m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active + // 3 = finished, 4 = didnotfinish, 5 = disqualified + // 6 = not classified, 7 = retired + public uint m_bestLapTimeInMS; // Best lap time of the session in milliseconds + public double m_totalRaceTime; // Total race time in seconds without penalties + public byte m_penaltiesTime; // Total penalties accumulated in seconds + public byte m_numPenalties; // Number of penalties applied to this driver + public byte m_numTyreStints; // Number of tyres stints up to maximum + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsActual; // Actual tyres used by this driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsVisual; // Visual tyres used by this driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsEndLaps; // The lap number stints end on + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/LapData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/LapData.cs new file mode 100644 index 0000000..885d379 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/LapData.cs @@ -0,0 +1,43 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LapData + { + public uint m_lastLapTimeInMS; // Last lap time in milliseconds + public uint m_currentLapTimeInMS; // Current time around the lap in milliseconds + public ushort m_sector1TimeInMS; // Sector 1 time in milliseconds + public byte m_sector1TimeMinutes; // Sector 1 whole minute part + public ushort m_sector2TimeInMS; // Sector 2 time in milliseconds + public byte m_sector2TimeMinutes; // Sector 2 whole minute part + public ushort m_deltaToCarInFrontInMS; // Time delta to car in front in milliseconds + public ushort m_deltaToRaceLeaderInMS; // Time delta to race leader in milliseconds + public float m_lapDistance; // Distance vehicle is around current lap in metres - could + // be negative if line hasn't been crossed yet + public float m_totalDistance; // Total distance travelled in session in metres - could + // be negative if line hasn't been crossed yet + public float m_safetyCarDelta; // Delta in seconds for safety car + public byte m_carPosition; // Car race position + public byte m_currentLapNum; // Current lap number + public byte m_pitStatus; // 0 = none, 1 = pitting, 2 = in pit area + public byte m_numPitStops; // Number of pit stops taken in this race + public byte m_sector; // 0 = sector1, 1 = sector2, 2 = sector3 + public byte m_currentLapInvalid; // Current lap invalid - 0 = valid, 1 = invalid + public byte m_penalties; // Accumulated time penalties in seconds to be added + public byte m_totalWarnings; // Accumulated number of warnings issued + public byte m_cornerCuttingWarnings; // Accumulated number of corner cutting warnings issued + public byte m_numUnservedDriveThroughPens; // Num drive through pens left to serve + public byte m_numUnservedStopGoPens; // Num stop go pens left to serve + public byte m_gridPosition; // Grid position the vehicle started the race in + public byte m_driverStatus; // Status of driver - 0 = in garage, 1 = flying lap + // 2 = in lap, 3 = out lap, 4 = on track + public byte m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active + // 3 = finished, 4 = didnotfinish, 5 = disqualified + // 6 = not classified, 7 = retired + public byte m_pitLaneTimerActive; // Pit lane timing, 0 = inactive, 1 = active + public ushort m_pitLaneTimeInLaneInMS; // If active, the current time spent in the pit lane in ms + public ushort m_pitStopTimerInMS; // Time of the actual pit stop in ms + public byte m_pitStopShouldServePen; // Whether the car should serve a penalty at this stop + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/LapHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/LapHistoryData.cs new file mode 100644 index 0000000..e3afb9d --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/LapHistoryData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LapHistoryData + { + public uint m_lapTimeInMS; // Lap time in milliseconds + public ushort m_sector1TimeInMS; // Sector 1 time in milliseconds + public byte m_sector1TimeMinutes; // Sector 1 whole minute part + public ushort m_sector2TimeInMS; // Sector 2 time in milliseconds + public byte m_sector2TimeMinutes; // Sector 2 whole minute part (note: spec has typo, says sector1TimeMinutes) + public ushort m_sector3TimeInMS; // Sector 3 time in milliseconds + public byte m_sector3TimeMinutes; // Sector 3 whole minute part + public byte m_lapValidBitFlags; // 0x01 bit set-lap valid, 0x02 bit set-sector 1 valid + // 0x04 bit set-sector 2 valid, 0x08 bit set-sector 3 valid + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/LobbyInfoData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/LobbyInfoData.cs new file mode 100644 index 0000000..c0beacf --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/LobbyInfoData.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LobbyInfoData + { + public byte m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + public byte m_teamId; // Team id - see appendix (255 if no team currently selected) + public byte m_nationality; // Nationality of the driver + public byte m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] + public byte[] m_name; // Name of participant in UTF-8 format - null terminated + // Will be truncated with ... (U+2026) if too long + + public byte m_carNumber; // Car number of the player + public byte m_readyStatus; // 0 = not ready, 1 = ready, 2 = spectating + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/MarshalZone.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/MarshalZone.cs new file mode 100644 index 0000000..7c491c0 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/MarshalZone.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MarshalZone + { + public float m_zoneStart; // Fraction (0..1) of way through the lap the marshal zone starts + public sbyte m_zoneFlag; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarDamageData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarDamageData.cs new file mode 100644 index 0000000..205adc0 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarDamageData.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Car damage packet for F1 2023. Damage parameters for all cars. + /// Frequency: 10 per second + /// Size: 953 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarDamageData + { + public PacketHeader m_header; // Header + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarDamageData[] m_carDamageData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarSetupData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarSetupData.cs new file mode 100644 index 0000000..a620267 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarSetupData.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Car setups packet for F1 2023. Details the car setups for each vehicle. + /// Frequency: 2 per second + /// Size: 1107 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarSetupData + { + public PacketHeader m_header; // Header + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarSetupData[] m_carSetups; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarStatusData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarStatusData.cs new file mode 100644 index 0000000..c3b48d3 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarStatusData.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Car status packet for F1 2023. Status data for all cars. + /// Frequency: Rate as specified in menus + /// Size: 1239 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarStatusData + { + public PacketHeader m_header; // Header + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarStatusData[] m_carStatusData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarTelemetryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarTelemetryData.cs new file mode 100644 index 0000000..283edd8 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketCarTelemetryData.cs @@ -0,0 +1,27 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Car telemetry packet for F1 2023. Telemetry data for all cars. + /// Frequency: Rate as specified in menus + /// Size: 1352 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarTelemetryData + { + public PacketHeader m_header; // Header + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarTelemetryData[] m_carTelemetryData; + + public byte m_mfdPanelIndex; // Index of MFD panel open - 255 = MFD closed + // Single player, race - 0 = Car setup, 1 = Pits + // 2 = Damage, 3 = Engine, 4 = Temperatures + // May vary depending on game mode + public byte m_mfdPanelIndexSecondaryPlayer; // See above + public sbyte m_suggestedGear; // Suggested gear for the player (1-8) + // 0 if no gear suggested + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketEventData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketEventData.cs new file mode 100644 index 0000000..89ddf12 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketEventData.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Event packet for F1 2023. Details of events that happen during a session. + /// Frequency: When the event occurs + /// Size: 45 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketEventData + { + public PacketHeader m_header; // Header + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_eventStringCode; // Event string code, see below + + public EventDataDetails m_eventDetails; // Event details - should be interpreted differently for each type + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketFinalClassificationData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketFinalClassificationData.cs new file mode 100644 index 0000000..67d08d5 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketFinalClassificationData.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Final classification packet for F1 2023. Final classification at the end of the race. + /// Frequency: Once at the end of a race + /// Size: 1020 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketFinalClassificationData + { + public PacketHeader m_header; // Header + + public byte m_numCars; // Number of cars in the final classification + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public FinalClassificationData[] m_classificationData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketHeader.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketHeader.cs new file mode 100644 index 0000000..49faf1b --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketHeader.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketHeader + { + public ushort m_packetFormat; // 2023 + public byte m_gameYear; // Game year - last two digits e.g. 23 + public byte m_gameMajorVersion; // Game major version - "X.00" + public byte m_gameMinorVersion; // Game minor version - "1.XX" + public byte m_packetVersion; // Version of this packet type, all start from 1 + public byte m_packetId; // Identifier for the packet type, see below + public ulong m_sessionUID; // Unique identifier for the session + public float m_sessionTime; // Session timestamp + public uint m_frameIdentifier; // Identifier for the frame the data was retrieved on + public uint m_overallFrameIdentifier; // Overall identifier for the frame the data was retrieved on, doesn't go back after flashbacks + public byte m_playerCarIndex; // Index of player's car in the array + public byte m_secondaryPlayerCarIndex; // Index of secondary player's car in the array (splitscreen) - 255 if no second player + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketLapData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketLapData.cs new file mode 100644 index 0000000..a92b591 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketLapData.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Lap data packet for F1 2023. Details of all cars in the session. + /// Frequency: Rate as specified in menus + /// Size: 1131 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLapData + { + public PacketHeader m_header; // Header + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public LapData[] m_lapData; // Lap data for all cars on track + + public byte m_timeTrialPBCarIdx; // Index of Personal Best car in time trial (255 if invalid) + public byte m_timeTrialRivalCarIdx; // Index of Rival car in time trial (255 if invalid) + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketLobbyInfoData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketLobbyInfoData.cs new file mode 100644 index 0000000..75a13c9 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketLobbyInfoData.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Lobby info packet for F1 2023. Players currently in a multiplayer lobby. + /// Frequency: Two every second when in the lobby + /// Size: 1218 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLobbyInfoData + { + public PacketHeader m_header; // Header + + public byte m_numPlayers; // Number of players in the lobby data + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public LobbyInfoData[] m_lobbyPlayers; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketMotionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketMotionData.cs new file mode 100644 index 0000000..a0e5583 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketMotionData.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Motion packet for F1 2023. Contains physics data for all cars. + /// Frequency: Rate as specified in menus + /// Size: 1349 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketMotionData + { + public PacketHeader m_header; // Header + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarMotionData[] m_carMotionData; // Data for all cars on track + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketMotionExData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketMotionExData.cs new file mode 100644 index 0000000..08ad8b7 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketMotionExData.cs @@ -0,0 +1,58 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Motion Ex packet for F1 2023. Extended motion data for the car being driven. + /// Frequency: Rate as specified in menus + /// Size: 217 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketMotionExData + { + public PacketHeader m_header; // Header + + // Extra player car ONLY data + // Note: All wheel arrays have the following order: RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionPosition; // RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionVelocity; // RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionAcceleration; // RL, RR, FL, FR + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSpeed; // Speed of each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSlipRatio; // Slip ratio for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSlipAngle; // Slip angles for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelLatForce; // Lateral forces for each wheel + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelLongForce; // Longitudinal forces for each wheel + + public float m_heightOfCOGAboveGround; // Height of centre of gravity above ground + public float m_localVelocityX; // Velocity in local space - metres/s + public float m_localVelocityY; // Velocity in local space + public float m_localVelocityZ; // Velocity in local space + public float m_angularVelocityX; // Angular velocity x-component - radians/s + public float m_angularVelocityY; // Angular velocity y-component + public float m_angularVelocityZ; // Angular velocity z-component + public float m_angularAccelerationX; // Angular acceleration x-component - radians/s/s + public float m_angularAccelerationY; // Angular acceleration y-component + public float m_angularAccelerationZ; // Angular acceleration z-component + public float m_frontWheelsAngle; // Current front wheels angle in radians + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelVertForce; // Vertical forces for each wheel + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketParticipantsData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketParticipantsData.cs new file mode 100644 index 0000000..a4ad388 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketParticipantsData.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Participants packet for F1 2023. List of participants in the race. + /// Frequency: Every 5 seconds + /// Size: 1306 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketParticipantsData + { + public PacketHeader m_header; // Header + + public byte m_numActiveCars; // Number of active cars in the data - should match number of cars on HUD + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ParticipantData[] m_participants; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketSessionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketSessionData.cs new file mode 100644 index 0000000..eb316b5 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketSessionData.cs @@ -0,0 +1,79 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Session packet for F1 2023. Details about the current session. + /// Frequency: 2 per second + /// Size: 644 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketSessionData + { + public PacketHeader m_header; // Header + + public byte m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast + // 3 = light rain, 4 = heavy rain, 5 = storm + public sbyte m_trackTemperature; // Track temp. in degrees celsius + public sbyte m_airTemperature; // Air temp. in degrees celsius + public byte m_totalLaps; // Total number of laps in this race + public ushort m_trackLength; // Track length in metres + public byte m_sessionType; // 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P + // 5 = Q1, 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ + // 10 = R, 11 = R2, 12 = R3, 13 = Time Trial + public sbyte m_trackId; // -1 for unknown, see appendix + public byte m_formula; // Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2, + // 3 = F1 Generic, 4 = Beta, 5 = Supercars + // 6 = Esports, 7 = F2 2021 + public ushort m_sessionTimeLeft; // Time left in session in seconds + public ushort m_sessionDuration; // Session duration in seconds + public byte m_pitSpeedLimit; // Pit speed limit in kilometres per hour + public byte m_gamePaused; // Whether the game is paused - network game only + public byte m_isSpectating; // Whether the player is spectating + public byte m_spectatorCarIndex; // Index of the car being spectated + public byte m_sliProNativeSupport; // SLI Pro support, 0 = inactive, 1 = active + public byte m_numMarshalZones; // Number of marshal zones to follow + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 21)] + public MarshalZone[] m_marshalZones; // List of marshal zones - max 21 + + public byte m_safetyCarStatus; // 0 = no safety car, 1 = full + // 2 = virtual, 3 = formation lap + public byte m_networkGame; // 0 = offline, 1 = online + public byte m_numWeatherForecastSamples; // Number of weather samples to follow + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 56)] + public WeatherForecastSample[] m_weatherForecastSamples; // Array of weather forecast samples + + public byte m_forecastAccuracy; // 0 = Perfect, 1 = Approximate + public byte m_aiDifficulty; // AI Difficulty rating - 0-110 + public uint m_seasonLinkIdentifier; // Identifier for season - persists across saves + public uint m_weekendLinkIdentifier; // Identifier for weekend - persists across saves + public uint m_sessionLinkIdentifier; // Identifier for session - persists across saves + public byte m_pitStopWindowIdealLap; // Ideal lap to pit on for current strategy (player) + public byte m_pitStopWindowLatestLap; // Latest lap to pit on for current strategy (player) + public byte m_pitStopRejoinPosition; // Predicted position to rejoin at (player) + public byte m_steeringAssist; // 0 = off, 1 = on + public byte m_brakingAssist; // 0 = off, 1 = low, 2 = medium, 3 = high + public byte m_gearboxAssist; // 1 = manual, 2 = manual & suggested gear, 3 = auto + public byte m_pitAssist; // 0 = off, 1 = on + public byte m_pitReleaseAssist; // 0 = off, 1 = on + public byte m_ERSAssist; // 0 = off, 1 = on + public byte m_DRSAssist; // 0 = off, 1 = on + public byte m_dynamicRacingLine; // 0 = off, 1 = corners only, 2 = full + public byte m_dynamicRacingLineType; // 0 = 2D, 1 = 3D + public byte m_gameMode; // Game mode id - see appendix + public byte m_ruleSet; // Ruleset - see appendix + public uint m_timeOfDay; // Local time of day - minutes since midnight + public byte m_sessionLength; // 0 = None, 2 = Very Short, 3 = Short, 4 = Medium + // 5 = Medium Long, 6 = Long, 7 = Full + public byte m_speedUnitsLeadPlayer; // 0 = MPH, 1 = KPH + public byte m_temperatureUnitsLeadPlayer; // 0 = Celsius, 1 = Fahrenheit + public byte m_speedUnitsSecondaryPlayer; // 0 = MPH, 1 = KPH + public byte m_temperatureUnitsSecondaryPlayer; // 0 = Celsius, 1 = Fahrenheit + public byte m_numSafetyCarPeriods; // Number of safety cars called during session + public byte m_numVirtualSafetyCarPeriods; // Number of virtual safety cars called + public byte m_numRedFlagPeriods; // Number of red flags called during session + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketSessionHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketSessionHistoryData.cs new file mode 100644 index 0000000..6e819bb --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketSessionHistoryData.cs @@ -0,0 +1,31 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Session history packet for F1 2023. Lap times and tyre usage for the session. + /// Frequency: 20 per second but cycling through cars + /// Size: 1460 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketSessionHistoryData + { + public PacketHeader m_header; // Header + + public byte m_carIdx; // Index of the car this lap data relates to + public byte m_numLaps; // Num laps in the data (including current partial lap) + public byte m_numTyreStints; // Number of tyre stints in the data + + public byte m_bestLapTimeLapNum; // Lap the best lap time was achieved on + public byte m_bestSector1LapNum; // Lap the best Sector 1 time was achieved on + public byte m_bestSector2LapNum; // Lap the best Sector 2 time was achieved on + public byte m_bestSector3LapNum; // Lap the best Sector 3 time was achieved on + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)] + public LapHistoryData[] m_lapHistoryData; // 100 laps of data max + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public TyreStintHistoryData[] m_tyreStintsHistoryData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/PacketTyreSetsData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketTyreSetsData.cs new file mode 100644 index 0000000..3cbb21f --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/PacketTyreSetsData.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + /// + /// Tyre sets packet for F1 2023. In-depth details about tyre sets assigned to a vehicle. + /// Frequency: 20 per second but cycling through cars + /// Size: 231 bytes + /// Version: 1 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketTyreSetsData + { + public PacketHeader m_header; // Header + + public byte m_carIdx; // Index of the car this data relates to + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public TyreSetData[] m_tyreSetData; // 13 (dry) + 7 (wet) + + public byte m_fittedIdx; // Index into array of fitted tyre + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/ParticipantData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/ParticipantData.cs new file mode 100644 index 0000000..14aad8c --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/ParticipantData.cs @@ -0,0 +1,24 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ParticipantData + { + public byte m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + public byte m_driverId; // Driver id - see appendix, 255 if network human + public byte m_networkId; // Network id - unique identifier for network players + public byte m_teamId; // Team id - see appendix + public byte m_myTeam; // My team flag - 1 = My Team, 0 = otherwise + public byte m_raceNumber; // Race number of the car + public byte m_nationality; // Nationality of the driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] + public byte[] m_name; // Name of participant in UTF-8 format - null terminated + // Will be truncated with … (U+2026) if too long + + public byte m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + public byte m_showOnlineNames; // The player's show online names setting, 0 = off, 1 = on + public byte m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/TyreSetData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/TyreSetData.cs new file mode 100644 index 0000000..35e2aa3 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/TyreSetData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TyreSetData + { + public byte m_actualTyreCompound; // Actual tyre compound used + public byte m_visualTyreCompound; // Visual tyre compound used + public byte m_wear; // Tyre wear (percentage) + public byte m_available; // Whether this set is currently available + public byte m_recommendedSession; // Recommended session for tyre set + public byte m_lifeSpan; // Laps left in this tyre set + public byte m_usableLife; // Max number of laps recommended for this compound + public short m_lapDeltaTime; // Lap delta time in milliseconds compared to fitted set + public byte m_fitted; // Whether the set is fitted or not + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/TyreStintHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/TyreStintHistoryData.cs new file mode 100644 index 0000000..6ed79d2 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/TyreStintHistoryData.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TyreStintHistoryData + { + public byte m_endLap; // Lap the tyre usage ends on (255 of current tyre) + public byte m_tyreActualCompound; // Actual tyres used by this driver + public byte m_tyreVisualCompound; // Visual tyres used by this driver + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12023/WeatherForecastSample.cs b/GamesDat/Telemetry/Sources/Formula1/F12023/WeatherForecastSample.cs new file mode 100644 index 0000000..751c3ec --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12023/WeatherForecastSample.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12023 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct WeatherForecastSample + { + public byte m_sessionType; // 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P, 5 = Q1 + // 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ, 10 = R, 11 = R2 + // 12 = R3, 13 = Time Trial + public byte m_timeOffset; // Time in minutes the forecast is for + public byte m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast + // 3 = light rain, 4 = heavy rain, 5 = storm + public sbyte m_trackTemperature; // Track temp. in degrees Celsius + public sbyte m_trackTemperatureChange; // Track temp. change - 0 = up, 1 = down, 2 = no change + public sbyte m_airTemperature; // Air temp. in degrees celsius + public sbyte m_airTemperatureChange; // Air temp. change - 0 = up, 1 = down, 2 = no change + public byte m_rainPercentage; // Rain percentage (0-100) + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs b/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs index 5962cb5..e5ffbb2 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs @@ -1,3 +1,4 @@ +using GamesDat.Core.Telemetry.Sources.Formula1.F12023; using GamesDat.Core.Telemetry.Sources.Formula1.F12024; using GamesDat.Core.Telemetry.Sources.Formula1.F12025; @@ -7,6 +8,22 @@ internal static class F1PacketTypeMapper { private static readonly Dictionary<(ushort format, byte id), Type> _packetTypeMap = new() { + // F1 2023 + [(2023, (byte)PacketId.Motion)] = typeof(F12023.PacketMotionData), + [(2023, (byte)PacketId.Session)] = typeof(F12023.PacketSessionData), + [(2023, (byte)PacketId.LapData)] = typeof(F12023.PacketLapData), + [(2023, (byte)PacketId.Event)] = typeof(F12023.PacketEventData), + [(2023, (byte)PacketId.Participants)] = typeof(F12023.PacketParticipantsData), + [(2023, (byte)PacketId.CarSetups)] = typeof(F12023.PacketCarSetupData), + [(2023, (byte)PacketId.CarTelemetry)] = typeof(F12023.PacketCarTelemetryData), + [(2023, (byte)PacketId.CarStatus)] = typeof(F12023.PacketCarStatusData), + [(2023, (byte)PacketId.FinalClassification)] = typeof(F12023.PacketFinalClassificationData), + [(2023, (byte)PacketId.LobbyInfo)] = typeof(F12023.PacketLobbyInfoData), + [(2023, (byte)PacketId.CarDamage)] = typeof(F12023.PacketCarDamageData), + [(2023, (byte)PacketId.SessionHistory)] = typeof(F12023.PacketSessionHistoryData), + [(2023, (byte)PacketId.TyreSets)] = typeof(F12023.PacketTyreSetsData), + [(2023, (byte)PacketId.MotionEx)] = typeof(F12023.PacketMotionExData), + // F1 2024 [(2024, (byte)PacketId.Motion)] = typeof(F12024.PacketMotionData), [(2024, (byte)PacketId.Session)] = typeof(F12024.PacketSessionData), diff --git a/GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2023_spec.h b/GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2023_spec.h new file mode 100644 index 0000000..5dca29d --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2023_spec.h @@ -0,0 +1,1342 @@ +Packet Information + +Packet Types + +Each packet carries different types of data rather than having one packet which contains everything.The header in each packet describes the packet type and versioning info so it will be easier for applications to check they are interpreting the incoming data in the correct way.Please note that all values are encoded using Little Endian format.All data is packed. + +The following data types are used in the structures : + +Type Description +uint8 Unsigned 8 - bit integer +int8 Signed 8 - bit integer +uint16 Unsigned 16 - bit integer +int16 Signed 16 - bit integer +uint32 Unsigned 32 - bit integer +float Floating point(32 - bit) +Double Double - precision floating point(64 - bit) +uint64 Unsigned 64 - bit integer +char Character + + +Packet Header + +Each packet has the following header : + +struct PacketHeader +{ + uint16 m_packetFormat; // 2023 + uint8 m_gameYear; // Game year - last two digits e.g. 23 + uint8 m_gameMajorVersion; // Game major version - "X.00" + uint8 m_gameMinorVersion; // Game minor version - "1.XX" + uint8 m_packetVersion; // Version of this packet type, all start from 1 + uint8 m_packetId; // Identifier for the packet type, see below + uint64 m_sessionUID; // Unique identifier for the session + float m_sessionTime; // Session timestamp + uint32 m_frameIdentifier; // Identifier for the frame the data was retrieved on + uint32 m_overallFrameIdentifier; // Overall identifier for the frame the data was retrieved + // on, doesn't go back after flashbacks + uint8 m_playerCarIndex; // Index of player's car in the array + uint8 m_secondaryPlayerCarIndex; // Index of secondary player's car in the array (splitscreen) + // 255 if no second player +}; + + +Packet IDs + +The packets IDs are as follows : + +Packet Name Value Description +Motion 0 Contains all motion data for player�s car � only sent while player is in control +Session 1 Data about the session � track, time left +Lap Data 2 Data about all the lap times of cars in the session +Event 3 Various notable events that happen during a session +Participants 4 List of participants in the session, mostly relevant for multiplayer +Car Setups 5 Packet detailing car setups for cars in the race +Car Telemetry 6 Telemetry data for all cars +Car Status 7 Status data for all cars +Final Classification 8 Final classification confirmation at the end of a race +Lobby Info 9 Information about players in a multiplayer lobby +Car Damage 10 Damage status for all cars +Session History 11 Lap and tyre data for session +Tyre Sets 12 Extended tyre set data +Motion Ex 13 Extended motion data for player car + + +Motion Packet + +The motion packet gives physics data for all the cars being driven. +N.B.For the normalised vectors below, to convert to float values divide by 32767.0f � 16 - bit signed values are used to pack the data and on the assumption that direction values are always between - 1.0f and 1.0f. + +Frequency: Rate as specified in menus +Size : 1349 bytes +Version : 1 + +struct CarMotionData +{ + float m_worldPositionX; // World space X position - metres + float m_worldPositionY; // World space Y position + float m_worldPositionZ; // World space Z position + float m_worldVelocityX; // Velocity in world space X � metres/s + float m_worldVelocityY; // Velocity in world space Y + float m_worldVelocityZ; // Velocity in world space Z + int16 m_worldForwardDirX; // World space forward X direction (normalised) + int16 m_worldForwardDirY; // World space forward Y direction (normalised) + int16 m_worldForwardDirZ; // World space forward Z direction (normalised) + int16 m_worldRightDirX; // World space right X direction (normalised) + int16 m_worldRightDirY; // World space right Y direction (normalised) + int16 m_worldRightDirZ; // World space right Z direction (normalised) + float m_gForceLateral; // Lateral G-Force component + float m_gForceLongitudinal; // Longitudinal G-Force component + float m_gForceVertical; // Vertical G-Force component + float m_yaw; // Yaw angle in radians + float m_pitch; // Pitch angle in radians + float m_roll; // Roll angle in radians +}; + +struct PacketMotionData +{ + PacketHeader m_header; // Header + + CarMotionData m_carMotionData[22]; // Data for all cars on track +}; + + +Session Packet + +The session packet includes details about the current session in progress. + +Frequency: 2 per second +Size : 644 bytes +Version : 1 + +struct MarshalZone +{ + float m_zoneStart; // Fraction (0..1) of way through the lap the marshal zone starts + int8 m_zoneFlag; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow +}; + +struct WeatherForecastSample +{ + uint8 m_sessionType; // 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P, 5 = Q1 + // 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ, 10 = R, 11 = R2 + // 12 = R3, 13 = Time Trial + uint8 m_timeOffset; // Time in minutes the forecast is for + uint8 m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast + // 3 = light rain, 4 = heavy rain, 5 = storm + int8 m_trackTemperature; // Track temp. in degrees Celsius + int8 m_trackTemperatureChange; // Track temp. change � 0 = up, 1 = down, 2 = no change + int8 m_airTemperature; // Air temp. in degrees celsius + int8 m_airTemperatureChange; // Air temp. change � 0 = up, 1 = down, 2 = no change + uint8 m_rainPercentage; // Rain percentage (0-100) +}; + +struct PacketSessionData +{ + PacketHeader m_header; // Header + + uint8 m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast + // 3 = light rain, 4 = heavy rain, 5 = storm + int8 m_trackTemperature; // Track temp. in degrees celsius + int8 m_airTemperature; // Air temp. in degrees celsius + uint8 m_totalLaps; // Total number of laps in this race + uint16 m_trackLength; // Track length in metres + uint8 m_sessionType; // 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P + // 5 = Q1, 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ + // 10 = R, 11 = R2, 12 = R3, 13 = Time Trial + int8 m_trackId; // -1 for unknown, see appendix + uint8 m_formula; // Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2, + // 3 = F1 Generic, 4 = Beta, 5 = Supercars +// 6 = Esports, 7 = F2 2021 + uint16 m_sessionTimeLeft; // Time left in session in seconds + uint16 m_sessionDuration; // Session duration in seconds + uint8 m_pitSpeedLimit; // Pit speed limit in kilometres per hour + uint8 m_gamePaused; // Whether the game is paused � network game only + uint8 m_isSpectating; // Whether the player is spectating + uint8 m_spectatorCarIndex; // Index of the car being spectated + uint8 m_sliProNativeSupport; // SLI Pro support, 0 = inactive, 1 = active + uint8 m_numMarshalZones; // Number of marshal zones to follow + MarshalZone m_marshalZones[21]; // List of marshal zones � max 21 + uint8 m_safetyCarStatus; // 0 = no safety car, 1 = full + // 2 = virtual, 3 = formation lap + uint8 m_networkGame; // 0 = offline, 1 = online + uint8 m_numWeatherForecastSamples; // Number of weather samples to follow + WeatherForecastSample m_weatherForecastSamples[56]; // Array of weather forecast samples + uint8 m_forecastAccuracy; // 0 = Perfect, 1 = Approximate + uint8 m_aiDifficulty; // AI Difficulty rating � 0-110 + uint32 m_seasonLinkIdentifier; // Identifier for season - persists across saves + uint32 m_weekendLinkIdentifier; // Identifier for weekend - persists across saves + uint32 m_sessionLinkIdentifier; // Identifier for session - persists across saves + uint8 m_pitStopWindowIdealLap; // Ideal lap to pit on for current strategy (player) + uint8 m_pitStopWindowLatestLap; // Latest lap to pit on for current strategy (player) + uint8 m_pitStopRejoinPosition; // Predicted position to rejoin at (player) + uint8 m_steeringAssist; // 0 = off, 1 = on + uint8 m_brakingAssist; // 0 = off, 1 = low, 2 = medium, 3 = high + uint8 m_gearboxAssist; // 1 = manual, 2 = manual & suggested gear, 3 = auto + uint8 m_pitAssist; // 0 = off, 1 = on + uint8 m_pitReleaseAssist; // 0 = off, 1 = on + uint8 m_ERSAssist; // 0 = off, 1 = on + uint8 m_DRSAssist; // 0 = off, 1 = on + uint8 m_dynamicRacingLine; // 0 = off, 1 = corners only, 2 = full + uint8 m_dynamicRacingLineType; // 0 = 2D, 1 = 3D + uint8 m_gameMode; // Game mode id - see appendix + uint8 m_ruleSet; // Ruleset - see appendix + uint32 m_timeOfDay; // Local time of day - minutes since midnight + uint8 m_sessionLength; // 0 = None, 2 = Very Short, 3 = Short, 4 = Medium + // 5 = Medium Long, 6 = Long, 7 = Full + uint8 m_speedUnitsLeadPlayer; // 0 = MPH, 1 = KPH + uint8 m_temperatureUnitsLeadPlayer; // 0 = Celsius, 1 = Fahrenheit + uint8 m_speedUnitsSecondaryPlayer; // 0 = MPH, 1 = KPH + uint8 m_temperatureUnitsSecondaryPlayer; // 0 = Celsius, 1 = Fahrenheit + uint8 m_numSafetyCarPeriods; // Number of safety cars called during session + uint8 m_numVirtualSafetyCarPeriods; // Number of virtual safety cars called + uint8 m_numRedFlagPeriods; // Number of red flags called during session +}; + + +Lap Data Packet + +The lap data packet gives details of all the cars in the session. + +Frequency: Rate as specified in menus +Size : 1131 bytes +Version : 1 + +struct LapData +{ + uint32 m_lastLapTimeInMS; // Last lap time in milliseconds + uint32 m_currentLapTimeInMS; // Current time around the lap in milliseconds + uint16 m_sector1TimeInMS; // Sector 1 time in milliseconds + uint8 m_sector1TimeMinutes; // Sector 1 whole minute part + uint16 m_sector2TimeInMS; // Sector 2 time in milliseconds + uint8 m_sector2TimeMinutes; // Sector 2 whole minute part + uint16 m_deltaToCarInFrontInMS; // Time delta to car in front in milliseconds + uint16 m_deltaToRaceLeaderInMS; // Time delta to race leader in milliseconds + float m_lapDistance; // Distance vehicle is around current lap in metres � could + // be negative if line hasn�t been crossed yet + float m_totalDistance; // Total distance travelled in session in metres � could + // be negative if line hasn�t been crossed yet + float m_safetyCarDelta; // Delta in seconds for safety car + uint8 m_carPosition; // Car race position + uint8 m_currentLapNum; // Current lap number + uint8 m_pitStatus; // 0 = none, 1 = pitting, 2 = in pit area + uint8 m_numPitStops; // Number of pit stops taken in this race + uint8 m_sector; // 0 = sector1, 1 = sector2, 2 = sector3 + uint8 m_currentLapInvalid; // Current lap invalid - 0 = valid, 1 = invalid + uint8 m_penalties; // Accumulated time penalties in seconds to be added + uint8 m_totalWarnings; // Accumulated number of warnings issued + uint8 m_cornerCuttingWarnings; // Accumulated number of corner cutting warnings issued + uint8 m_numUnservedDriveThroughPens; // Num drive through pens left to serve + uint8 m_numUnservedStopGoPens; // Num stop go pens left to serve + uint8 m_gridPosition; // Grid position the vehicle started the race in + uint8 m_driverStatus; // Status of driver - 0 = in garage, 1 = flying lap + // 2 = in lap, 3 = out lap, 4 = on track + uint8 m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active + // 3 = finished, 4 = didnotfinish, 5 = disqualified + // 6 = not classified, 7 = retired + uint8 m_pitLaneTimerActive; // Pit lane timing, 0 = inactive, 1 = active + uint16 m_pitLaneTimeInLaneInMS; // If active, the current time spent in the pit lane in ms + uint16 m_pitStopTimerInMS; // Time of the actual pit stop in ms + uint8 m_pitStopShouldServePen; // Whether the car should serve a penalty at this stop +}; + + +struct PacketLapData +{ + PacketHeader m_header; // Header + + LapData m_lapData[22]; // Lap data for all cars on track + + uint8 m_timeTrialPBCarIdx; // Index of Personal Best car in time trial (255 if invalid) + uint8 m_timeTrialRivalCarIdx; // Index of Rival car in time trial (255 if invalid) +}; + + +Event Packet + +This packet gives details of events that happen during the course of a session. + +Frequency: When the event occurs +Size : 45 bytes +Version : 1 + +// The event details packet is different for each type of event. +// Make sure only the correct type is interpreted. +union EventDataDetails +{ + struct + { + uint8 vehicleIdx; // Vehicle index of car achieving fastest lap + float lapTime; // Lap time is in seconds + } FastestLap; + + struct + { + uint8 vehicleIdx; // Vehicle index of car retiring + } Retirement; + + struct + { + uint8 vehicleIdx; // Vehicle index of team mate + } TeamMateInPits; + + struct + { + uint8 vehicleIdx; // Vehicle index of the race winner + } RaceWinner; + + struct + { + uint8 penaltyType; // Penalty type � see Appendices + uint8 infringementType; // Infringement type � see Appendices + uint8 vehicleIdx; // Vehicle index of the car the penalty is applied to + uint8 otherVehicleIdx; // Vehicle index of the other car involved + uint8 time; // Time gained, or time spent doing action in seconds + uint8 lapNum; // Lap the penalty occurred on + uint8 placesGained; // Number of places gained by this + } Penalty; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle triggering speed trap + float speed; // Top speed achieved in kilometres per hour + uint8 isOverallFastestInSession; // Overall fastest speed in session = 1, otherwise 0 + uint8 isDriverFastestInSession; // Fastest speed for driver in session = 1, otherwise 0 + uint8 fastestVehicleIdxInSession;// Vehicle index of the vehicle that is the fastest + // in this session + float fastestSpeedInSession; // Speed of the vehicle that is the fastest + // in this session + } SpeedTrap; + + struct + { + uint8 numLights; // Number of lights showing + } StartLIghts; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle serving drive through + } DriveThroughPenaltyServed; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle serving stop go + } StopGoPenaltyServed; + + struct + { + uint32 flashbackFrameIdentifier; // Frame identifier flashed back to + float flashbackSessionTime; // Session time flashed back to + } Flashback; + + struct + { + uint32 buttonStatus; // Bit flags specifying which buttons are being pressed + // currently - see appendices + } Buttons; + + struct + { + uint8 overtakingVehicleIdx; // Vehicle index of the vehicle overtaking + uint8 beingOvertakenVehicleIdx; // Vehicle index of the vehicle being overtaken + } Overtake; +}; + +struct PacketEventData +{ + PacketHeader m_header; // Header + + uint8 m_eventStringCode[4]; // Event string code, see below + EventDataDetails m_eventDetails; // Event details - should be interpreted differently + // for each type +}; + + +Event String Codes + +Event Code Description +Session Started �SSTA� Sent when the session starts +Session Ended �SEND� Sent when the session ends +Fastest Lap �FTLP� When a driver achieves the fastest lap +Retirement �RTMT� When a driver retires +DRS enabled �DRSE� Race control have enabled DRS +DRS disabled �DRSD� Race control have disabled DRS +Team mate in pits �TMPT� Your team mate has entered the pits +Chequered flag �CHQF� The chequered flag has been waved +Race Winner �RCWN� The race winner is announced +Penalty Issued �PENA� A penalty has been issued � details in event +Speed Trap Triggered �SPTP� Speed trap has been triggered by fastest speed +Start lights �STLG� Start lights � number shown +Lights out �LGOT� Lights out +Drive through served �DTSV� Drive through penalty served +Stop go served �SGSV� Stop go penalty served +Flashback �FLBK� Flashback activated +Button status �BUTN� Button status changed +Red Flag �RDFL� Red flag shown +Overtake �OVTK� Overtake occurred + + +Participants Packet + +This is a list of participants in the race.If the vehicle is controlled by AI, then the name will be the driver name.If this is a multiplayer game, the names will be the Steam Id on PC, or the LAN name if appropriate. + +N.B.on Xbox One, the names will always be the driver name, on PS4 the name will be the LAN name if playing a LAN game, otherwise it will be the driver name. + +The array should be indexed by vehicle index. + +Frequency: Every 5 seconds +Size : 1306 bytes +Version : 1 + +struct ParticipantData +{ + uint8 m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + uint8 m_driverId; // Driver id - see appendix, 255 if network human + uint8 m_networkId; // Network id � unique identifier for network players + uint8 m_teamId; // Team id - see appendix + uint8 m_myTeam; // My team flag � 1 = My Team, 0 = otherwise + uint8 m_raceNumber; // Race number of the car + uint8 m_nationality; // Nationality of the driver + char m_name[48]; // Name of participant in UTF-8 format � null terminated + // Will be truncated with � (U+2026) if too long + uint8 m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + uint8 m_showOnlineNames; // The player's show online names setting, 0 = off, 1 = on + uint8 m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown +}; + +struct PacketParticipantsData +{ + PacketHeader m_header; // Header + + uint8 m_numActiveCars; // Number of active cars in the data � should match number of + // cars on HUD + ParticipantData m_participants[22]; +}; + + +Car Setups Packet + +This packet details the car setups for each vehicle in the session.Note that in multiplayer games, other player cars will appear as blank, you will only be able to see your own car setup, regardless of the �Your Telemetry� setting.Spectators will also not be able to see any car setups. + +Frequency: 2 per second +Size : 1107 bytes +Version : 1 + +struct CarSetupData +{ + uint8 m_frontWing; // Front wing aero + uint8 m_rearWing; // Rear wing aero + uint8 m_onThrottle; // Differential adjustment on throttle (percentage) + uint8 m_offThrottle; // Differential adjustment off throttle (percentage) + float m_frontCamber; // Front camber angle (suspension geometry) + float m_rearCamber; // Rear camber angle (suspension geometry) + float m_frontToe; // Front toe angle (suspension geometry) + float m_rearToe; // Rear toe angle (suspension geometry) + uint8 m_frontSuspension; // Front suspension + uint8 m_rearSuspension; // Rear suspension + uint8 m_frontAntiRollBar; // Front anti-roll bar + uint8 m_rearAntiRollBar; // Front anti-roll bar + uint8 m_frontSuspensionHeight; // Front ride height + uint8 m_rearSuspensionHeight; // Rear ride height + uint8 m_brakePressure; // Brake pressure (percentage) + uint8 m_brakeBias; // Brake bias (percentage) + float m_rearLeftTyrePressure; // Rear left tyre pressure (PSI) + float m_rearRightTyrePressure; // Rear right tyre pressure (PSI) + float m_frontLeftTyrePressure; // Front left tyre pressure (PSI) + float m_frontRightTyrePressure; // Front right tyre pressure (PSI) + uint8 m_ballast; // Ballast + float m_fuelLoad; // Fuel load +}; + +struct PacketCarSetupData +{ + PacketHeader m_header; // Header + + CarSetupData m_carSetups[22]; +}; + + +Car Telemetry Packet + +This packet details telemetry for all the cars in the race.It details various values that would be recorded on the car such as speed, throttle application, DRS etc.Note that the rev light configurations are presented separately as well and will mimic real life driver preferences. + +Frequency: Rate as specified in menus +Size : 1352 bytes +Version : 1 + +struct CarTelemetryData +{ + uint16 m_speed; // Speed of car in kilometres per hour + float m_throttle; // Amount of throttle applied (0.0 to 1.0) + float m_steer; // Steering (-1.0 (full lock left) to 1.0 (full lock right)) + float m_brake; // Amount of brake applied (0.0 to 1.0) + uint8 m_clutch; // Amount of clutch applied (0 to 100) + int8 m_gear; // Gear selected (1-8, N=0, R=-1) + uint16 m_engineRPM; // Engine RPM + uint8 m_drs; // 0 = off, 1 = on + uint8 m_revLightsPercent; // Rev lights indicator (percentage) + uint16 m_revLightsBitValue; // Rev lights (bit 0 = leftmost LED, bit 14 = rightmost LED) + uint16 m_brakesTemperature[4]; // Brakes temperature (celsius) + uint8 m_tyresSurfaceTemperature[4]; // Tyres surface temperature (celsius) + uint8 m_tyresInnerTemperature[4]; // Tyres inner temperature (celsius) + uint16 m_engineTemperature; // Engine temperature (celsius) + float m_tyresPressure[4]; // Tyres pressure (PSI) + uint8 m_surfaceType[4]; // Driving surface, see appendices +}; + +struct PacketCarTelemetryData +{ + PacketHeader m_header; // Header + + CarTelemetryData m_carTelemetryData[22]; + + uint8 m_mfdPanelIndex; // Index of MFD panel open - 255 = MFD closed + // Single player, race � 0 = Car setup, 1 = Pits + // 2 = Damage, 3 = Engine, 4 = Temperatures + // May vary depending on game mode + uint8 m_mfdPanelIndexSecondaryPlayer; // See above + int8 m_suggestedGear; // Suggested gear for the player (1-8) + // 0 if no gear suggested +}; + + +Car Status Packet + +This packet details car statuses for all the cars in the race. + +Frequency: Rate as specified in menus +Size : 1239 bytes +Version : 1 + +struct CarStatusData +{ + uint8 m_tractionControl; // Traction control - 0 = off, 1 = medium, 2 = full + uint8 m_antiLockBrakes; // 0 (off) - 1 (on) + uint8 m_fuelMix; // Fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max + uint8 m_frontBrakeBias; // Front brake bias (percentage) + uint8 m_pitLimiterStatus; // Pit limiter status - 0 = off, 1 = on + float m_fuelInTank; // Current fuel mass + float m_fuelCapacity; // Fuel capacity + float m_fuelRemainingLaps; // Fuel remaining in terms of laps (value on MFD) + uint16 m_maxRPM; // Cars max RPM, point of rev limiter + uint16 m_idleRPM; // Cars idle RPM + uint8 m_maxGears; // Maximum number of gears + uint8 m_drsAllowed; // 0 = not allowed, 1 = allowed + uint16 m_drsActivationDistance; // 0 = DRS not available, non-zero - DRS will be available + // in [X] metres + uint8 m_actualTyreCompound; // F1 Modern - 16 = C5, 17 = C4, 18 = C3, 19 = C2, 20 = C1 + // 21 = C0, 7 = inter, 8 = wet + // F1 Classic - 9 = dry, 10 = wet + // F2 � 11 = super soft, 12 = soft, 13 = medium, 14 = hard + // 15 = wet + uint8 m_visualTyreCompound; // F1 visual (can be different from actual compound) + // 16 = soft, 17 = medium, 18 = hard, 7 = inter, 8 = wet + // F1 Classic � same as above + // F2 �19, 15 = wet, 19 � super soft, 20 = soft + // 21 = medium , 22 = hard + uint8 m_tyresAgeLaps; // Age in laps of the current set of tyres + int8 m_vehicleFiaFlags; // -1 = invalid/unknown, 0 = none, 1 = green + // 2 = blue, 3 = yellow + float m_enginePowerICE; // Engine power output of ICE (W) + float m_enginePowerMGUK; // Engine power output of MGU-K (W) + float m_ersStoreEnergy; // ERS energy store in Joules + uint8 m_ersDeployMode; // ERS deployment mode, 0 = none, 1 = medium + // 2 = hotlap, 3 = overtake + float m_ersHarvestedThisLapMGUK; // ERS energy harvested this lap by MGU-K + float m_ersHarvestedThisLapMGUH; // ERS energy harvested this lap by MGU-H + float m_ersDeployedThisLap; // ERS energy deployed this lap + uint8 m_networkPaused; // Whether the car is paused in a network game +}; + +struct PacketCarStatusData +{ + PacketHeader m_header; // Header + + CarStatusData m_carStatusData[22]; +}; + +Final Classification Packet + +This packet details the final classification at the end of the race, and the data will match with the post race results screen.This is especially useful for multiplayer games where it is not always possible to send lap times on the final frame because of network delay. + +Frequency: Once at the end of a race +Size : 1020 bytes +Version : 1 + +struct FinalClassificationData +{ + uint8 m_position; // Finishing position + uint8 m_numLaps; // Number of laps completed + uint8 m_gridPosition; // Grid position of the car + uint8 m_points; // Number of points scored + uint8 m_numPitStops; // Number of pit stops made + uint8 m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active + // 3 = finished, 4 = didnotfinish, 5 = disqualified + // 6 = not classified, 7 = retired + uint32 m_bestLapTimeInMS; // Best lap time of the session in milliseconds + double m_totalRaceTime; // Total race time in seconds without penalties + uint8 m_penaltiesTime; // Total penalties accumulated in seconds + uint8 m_numPenalties; // Number of penalties applied to this driver + uint8 m_numTyreStints; // Number of tyres stints up to maximum + uint8 m_tyreStintsActual[8]; // Actual tyres used by this driver + uint8 m_tyreStintsVisual[8]; // Visual tyres used by this driver + uint8 m_tyreStintsEndLaps[8]; // The lap number stints end on +}; + +struct PacketFinalClassificationData +{ + PacketHeader m_header; // Header + + uint8 m_numCars; // Number of cars in the final classification + FinalClassificationData m_classificationData[22]; +}; + +Lobby Info Packet + +This packet details the players currently in a multiplayer lobby.It details each player�s selected car, any AI involved in the game and also the ready status of each of the participants. + +Frequency: Two every second when in the lobby +Size : 1218 bytes +Version : 1 + +struct LobbyInfoData +{ + uint8 m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + uint8 m_teamId; // Team id - see appendix (255 if no team currently selected) + uint8 m_nationality; // Nationality of the driver + uint8 m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + char m_name[48]; // Name of participant in UTF-8 format � null terminated + // Will be truncated with ... (U+2026) if too long + uint8 m_carNumber; // Car number of the player + uint8 m_readyStatus; // 0 = not ready, 1 = ready, 2 = spectating +}; + +struct PacketLobbyInfoData +{ + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_numPlayers; // Number of players in the lobby data + LobbyInfoData m_lobbyPlayers[22]; +}; + +Car Damage Packet + +This packet details car damage parameters for all the cars in the race. + +Frequency: 10 per second +Size : 953 bytes +Version : 1 + +struct CarDamageData +{ + float m_tyresWear[4]; // Tyre wear (percentage) + uint8 m_tyresDamage[4]; // Tyre damage (percentage) + uint8 m_brakesDamage[4]; // Brakes damage (percentage) + uint8 m_frontLeftWingDamage; // Front left wing damage (percentage) + uint8 m_frontRightWingDamage; // Front right wing damage (percentage) + uint8 m_rearWingDamage; // Rear wing damage (percentage) + uint8 m_floorDamage; // Floor damage (percentage) + uint8 m_diffuserDamage; // Diffuser damage (percentage) + uint8 m_sidepodDamage; // Sidepod damage (percentage) + uint8 m_drsFault; // Indicator for DRS fault, 0 = OK, 1 = fault + uint8 m_ersFault; // Indicator for ERS fault, 0 = OK, 1 = fault + uint8 m_gearBoxDamage; // Gear box damage (percentage) + uint8 m_engineDamage; // Engine damage (percentage) + uint8 m_engineMGUHWear; // Engine wear MGU-H (percentage) + uint8 m_engineESWear; // Engine wear ES (percentage) + uint8 m_engineCEWear; // Engine wear CE (percentage) + uint8 m_engineICEWear; // Engine wear ICE (percentage) + uint8 m_engineMGUKWear; // Engine wear MGU-K (percentage) + uint8 m_engineTCWear; // Engine wear TC (percentage) + uint8 m_engineBlown; // Engine blown, 0 = OK, 1 = fault + uint8 m_engineSeized; // Engine seized, 0 = OK, 1 = fault +} + +struct PacketCarDamageData +{ + PacketHeader m_header; // Header + + CarDamageData m_carDamageData[22]; +}; + +Session History Packet + +This packet contains lap times and tyre usage for the session.This packet works slightly differently to other packets.To reduce CPU and bandwidth, each packet relates to a specific vehicle and is sent every 1 / 20 s, and the vehicle being sent is cycled through.Therefore in a 20 car race you should receive an update for each vehicle at least once per second. + +Note that at the end of the race, after the final classification packet has been sent, a final bulk update of all the session histories for the vehicles in that session will be sent. + +Frequency: 20 per second but cycling through cars +Size : 1460 bytes +Version : 1 + +struct LapHistoryData +{ + uint32 m_lapTimeInMS; // Lap time in milliseconds + uint16 m_sector1TimeInMS; // Sector 1 time in milliseconds + uint8 m_sector1TimeMinutes; // Sector 1 whole minute part + uint16 m_sector2TimeInMS; // Sector 2 time in milliseconds + uint8 m_sector1TimeMinutes; // Sector 2 whole minute part + uint16 m_sector3TimeInMS; // Sector 3 time in milliseconds + uint8 m_sector3TimeMinutes; // Sector 3 whole minute part + uint8 m_lapValidBitFlags; // 0x01 bit set-lap valid, 0x02 bit set-sector 1 valid + // 0x04 bit set-sector 2 valid, 0x08 bit set-sector 3 valid +}; + +struct TyreStintHistoryData +{ + uint8 m_endLap; // Lap the tyre usage ends on (255 of current tyre) + uint8 m_tyreActualCompound; // Actual tyres used by this driver + uint8 m_tyreVisualCompound; // Visual tyres used by this driver +}; + +struct PacketSessionHistoryData +{ + PacketHeader m_header; // Header + + uint8 m_carIdx; // Index of the car this lap data relates to + uint8 m_numLaps; // Num laps in the data (including current partial lap) + uint8 m_numTyreStints; // Number of tyre stints in the data + + uint8 m_bestLapTimeLapNum; // Lap the best lap time was achieved on + uint8 m_bestSector1LapNum; // Lap the best Sector 1 time was achieved on + uint8 m_bestSector2LapNum; // Lap the best Sector 2 time was achieved on + uint8 m_bestSector3LapNum; // Lap the best Sector 3 time was achieved on + + LapHistoryData m_lapHistoryData[100]; // 100 laps of data max + TyreStintHistoryData m_tyreStintsHistoryData[8]; +}; + +Tyre Sets Packet + +This packets gives a more in - depth details about tyre sets assigned to a vehicle during the session. + +Frequency: 20 per second but cycling through cars +Size : 231 bytes +Version : 1 + +struct TyreSetData +{ + uint8 m_actualTyreCompound; // Actual tyre compound used + uint8 m_visualTyreCompound; // Visual tyre compound used + uint8 m_wear; // Tyre wear (percentage) + uint8 m_available; // Whether this set is currently available + uint8 m_recommendedSession; // Recommended session for tyre set + uint8 m_lifeSpan; // Laps left in this tyre set + uint8 m_usableLife; // Max number of laps recommended for this compound + int16 m_lapDeltaTime; // Lap delta time in milliseconds compared to fitted set + uint8 m_fitted; // Whether the set is fitted or not +}; + +struct PacketTyreSetsData +{ + PacketHeader m_header; // Header + + uint8 m_carIdx; // Index of the car this data relates to + + TyreSetData m_tyreSetData[20]; // 13 (dry) + 7 (wet) + + uint8 m_fittedIdx; // Index into array of fitted tyre +}; +Motion Ex Packet + +The motion packet gives extended data for the car being driven with the goal of being able to drive a motion platform setup. + +Frequency: Rate as specified in menus +Size : 217 bytes +Version : 1 + +struct PacketMotionExData +{ + PacketHeader m_header; // Header + + // Extra player car ONLY data + float m_suspensionPosition[4]; // Note: All wheel arrays have the following order: + float m_suspensionVelocity[4]; // RL, RR, FL, FR + float m_suspensionAcceleration[4]; // RL, RR, FL, FR + float m_wheelSpeed[4]; // Speed of each wheel + float m_wheelSlipRatio[4]; // Slip ratio for each wheel + float m_wheelSlipAngle[4]; // Slip angles for each wheel + float m_wheelLatForce[4]; // Lateral forces for each wheel + float m_wheelLongForce[4]; // Longitudinal forces for each wheel + float m_heightOfCOGAboveGround; // Height of centre of gravity above ground + float m_localVelocityX; // Velocity in local space � metres/s + float m_localVelocityY; // Velocity in local space + float m_localVelocityZ; // Velocity in local space + float m_angularVelocityX; // Angular velocity x-component � radians/s + float m_angularVelocityY; // Angular velocity y-component + float m_angularVelocityZ; // Angular velocity z-component + float m_angularAccelerationX; // Angular acceleration x-component � radians/s/s + float m_angularAccelerationY; // Angular acceleration y-component + float m_angularAccelerationZ; // Angular acceleration z-component + float m_frontWheelsAngle; // Current front wheels angle in radians + float m_wheelVertForce[4]; // Vertical forces for each wheel +}; + + +Restricted data(Your Telemetry setting) + +There is some data in the UDP that you may not want other players seeing if you are in a multiplayer game.This is controlled by the �Your Telemetry� setting in the Telemetry options.The options are : + +� Restricted(Default) � other players viewing the UDP data will not see values for your car +� Public � all other players can see all the data for your car +� Show online ID � this additional option allows other players to view your online ID / gamertag in their UDP output. + +Note : You can always see the data for the car you are driving regardless of the setting. + +The following data items are set to zero if the player driving the car in question has their �Your Telemetry� set to �Restricted� : + +Car status packet + +� m_fuelInTank +� m_fuelCapacity +� m_fuelMix +� m_fuelRemainingLaps +� m_frontBrakeBias +� m_ersDeployMode +� m_ersStoreEnergy +� m_ersDeployedThisLap +� m_ersHarvestedThisLapMGUK +� m_ersHarvestedThisLapMGUH +� m_enginePowerICE +� m_enginePowerMGUK + +Car damage packet + +� m_frontLeftWingDamage +� m_frontRightWingDamage +� m_rearWingDamage +� m_floorDamage +� m_diffuserDamage +� m_sidepodDamage +� m_engineDamage +� m_gearBoxDamage +� m_tyresWear(All four wheels) +� m_tyresDamage(All four wheels) +� m_brakesDamage(All four wheels) +� m_drsFault +� m_engineMGUHWear +� m_engineESWear +� m_engineCEWear +� m_engineICEWear +� m_engineMGUKWear +� m_engineTCWear + +Tyre set packet +� All data within this packet for player car + +To allow other players to view your online ID in their UDP output during an online session, you must enable the �Show online ID / gamertags� option.Selecting this will bring up a confirmation box that must be confirmed before this option is enabled. + +Please note that all options can be changed during a game session and will take immediate effect. + + +FAQS + +How do I enable the UDP Telemetry Output ? +In F1 23, UDP telemetry output is controlled via the in - game menus.To enable this, enter the options menu from the main menu(triangle / Y), then enter the settings menu - the UDP option will be at the bottom of the list.From there you will be able to enable / disable the UDP output, configure the IP address and port for the receiving application, toggle broadcast mode and set the send rate.Broadcast mode transmits the data across the network subnet to allow multiple devices on the same subnet to be able to receive this information.When using broadcast mode it is not necessary to set a target IP address, just a target port for applications to listen on. + +Advanced PC Users : You can additionally edit the game�s configuration XML file to configure UDP output.The file is located here(after an initial boot of the game) : + + ...\Documents\My Games\\hardwaresettings\hardware_settings_config.xml + + You should see the tag : + + +... + +... +< / motion> + +Here you can set the values manually.Note that any changes made within the game when it is running will overwrite any changes made manually.Note the enabled flag is now a state. + + +What has changed since last year ? +F1� 23 sees the following changes to the UDP specification : + +� Added game year to packet header � apps can identify which F1 game data is coming from +� Temperature and speed units choice for players sent in session packet +� Platform of players added to lobby info and participants packets +� Added flag to say whether a player has their �Show online names� flag set in participants packet +� Added whole minute part to sector times in lap data and session history packets +� Damage packet now updates at 10 / s +� Separated corner cutting warnings in the lap data packet +� Added new tyre sets packet to give more detail about tyre sets for each car +� Added time deltas for cars in the lap data packet +� Added overall frame identifier to packet header to help deal with flashbacks +� Red flag event added +� Added Safety car, VSC and Red Flag counts to session data +� Added more physics data in the motion packet +� Added Overtake event +� Added power outputs readings for the engine +� Added C0 tyre type +� Added a new Motion Ex packet and moved player car settings from Motion packet to stop it getting too large, added vertical wheel forces + + +What is the order of the wheel arrays ? +All wheel arrays are in the following order : + +0 � Rear Left(RL) +1 � Rear Right(RR) +2 � Front Left(FL) +3 � Front Right(FR) + + +Do the vehicle indices change ? +During a session, each car is assigned a vehicle index.This will not change throughout the session and all the arrays that are sent use this vehicle index to dereference the correct piece of data. + +What are the co - ordinate systems used ? + +Here is a visual representation of the co - ordinate system used with the F1 telemetry data. + + + +What encoding format is used ? +All values are encoded using Little Endian format. + + +Are the data structures packed ? +Yes, all data is packed, there is no padding used. + + +How many cars are in the data structures ? +The maximum number of cars in the data structures is 22, to allow for certain game modes, although the data is not always filled in. + +You should always check the data item called m_numActiveCars in the participants packet which tells you how many cars are active in the race.However, you should check the individual result status of each car in the lap data to see if that car is actively providing data.If it is not �Invalid� or �Inactive� then the corresponding vehicle index has valid data. + + +How often are updated packets sent ? +For the packets which get updated at �Rate as specified in the menus� you can be guaranteed that on the frame that these get sent they will all get sent together and will never be separated across frames.This of course relies on the reliability of your network as to whether they are received correctly as everything is sent via UDP.Other packets that get sent at specific rates can arrive on any frame. +If you are connected to the game when it starts transmitting the first frame will contain the following information to help initialise data structures on the receiving application : +Packets sent on Frame 1 : (All packets sent on this frame have �Session timestamp� 0.000) +� Session +� Participants +� Car Setups +� Lap Data +� Motion Data +� Car Telemetry +� Car Status +� Car Damage +� Motion Ex Data +As an example, assuming that you are running at 60Hz with 60Hz update rate selected in the menus then you would expect to see the following packets and timestamps: +Packets sent on Frame 2 : (All packets sent on this frame have �Session timestamp� 0.016) +� Lap Data +� Motion Data +� Car Telemetry +� Car Status +� Motion Ex Data +� +Packets sent on Frame 31: (All packets sent on this frame have �Session timestamp� 0.5) +� Session(since 2 updates per second) +� Car Setups(since 2 updates per second) +� Lap Data +� Motion Data +� Car Telemetry +� Car Status +� Car Damage(since 2 updates per second) +� Motion Ex Data + +Will my old app still work with F1 23 ? +Please note that from F1 23 the game will only support the previous 2 UDP formats. + +F1 23 uses a new format for the UDP data.However, some earlier formats of the data are still supported so that most older apps implemented using the previous data formats should work with little or no change from the developer.To use the old formats, please enter the UDP options menu and set �UDP Format� to either �2022� or �2021�. +Specifications for the older formats can be seen here : + +� F1 2021 - https ://forums.codemasters.com/topic/80231-f1-2021-udp-specification + � F1 22 - https ://answers.ea.com/t5/General-Discussion/F1-22-UDP-Specification/td-p/11551274 + + How do I enable D - BOX output ? + D - BOX output is currently supported on the PC platform.In F1 23, the D - BOX activation can be controlled via the menus.Navigate to Game Options->Settings->UDP Telemetry Settings->D - BOX to activate this on your system. + + Advanced PC Users : It is possible to control D - BOX by editing the games� configuration XML file.The file is located here(after an initial boot of the game) : + + ...\Documents\My Games\\hardwaresettings\hardware_settings_config.xml + + You should see the tag : + + + +... +< / motion> + +Set the �enabled� value to �true� to allow the game to output to your D - BOX motion platform.Note that any changes made within the game when it is running will overwrite any changes made manually. + + +How can I disable in - game support for LED device ? +The F1 game has native support for some of the basic features supported by some external LED devices, such as the Leo Bodnar SLI Pro and the Fanatec steering wheels.To avoid conflicts between the game�s implementation and any third - party device managers on the PC platform it may be necessary to disable the native support.This is done using the following led_display flags in the hardware_settings_config.xml.The file is located here(after an initial boot of the game) : + + ...\Documents\My Games\\hardwaresettings\hardware_settings_config.xml + + The flags to enabled / disable LED output are : + + + +The sliProNativeSupport flag controls the output to SLI Pro devices.The fanatecNativeSupport flag controls the output to Fanatec(and some related) steering wheel LEDs.Set the values for any of these to �false� to disable them and avoid conflicts with your own device manager. + +Please note there is an additional flag to manually control the LED brightness on the SLI Pro : + + + +This option(using value in the range 0 - 255) will be ignored when setting the sliProNativeSupport flag to �false�. + +Also note it is now possible to edit these values on the fly via the Game Options->Settings->UDP Telemetry Settings menu. + + +Can I configure the UDP output using an XML File ? +PC users can edit the game�s configuration XML file to configure UDP output.The file is located here(after an initial boot of the game) : + + ...\Documents\My Games\\hardwaresettings\hardware_settings_config.xml + + You should see the tag : + + +... + +... +< / motion> + +Here you can set the values manually.Note that any changes made within the game when it is running will overwrite any changes made manually. + +Appendices + +Here are the values used for some of the parameters in the UDP data output. + +Team IDs + +ID Team ID Team ID Team +0 Mercedes 106 Prema �21 136 Campos �22 +1 Ferrari 107 Uni - Virtuosi �21 137 Van Amersfoort Racing �22 +2 Red Bull Racing 108 Carlin �21 138 Trident �22 +3 Williams 109 Hitech �21 139 Hitech �22 +4 Aston Martin 110 Art GP �21 140 Art GP �22 +5 Alpine 111 MP Motorsport �21 +6 Alpha Tauri 112 Charouz �21 +7 Haas 113 Dams �21 +8 McLaren 114 Campos �21 +9 Alfa Romeo 115 BWT �21 +85 Mercedes 2020 116 Trident �21 +86 Ferrari 2020 117 Mercedes AMG GT Black Series +87 Red Bull 2020 118 Mercedes �22 +88 Williams 2020 119 Ferrari �22 +89 Racing Point 2020 120 Red Bull Racing �22 +90 Renault 2020 121 Williams �22 +91 Alpha Tauri 2020 122 Aston Martin �22 +92 Haas 2020 123 Alpine �22 +93 McLaren 2020 124 Alpha Tauri �22 +94 Alfa Romeo 2020 125 Haas �22 +95 Aston Martin DB11 V12 126 McLaren �22 +96 Aston Martin Vantage F1 Edition 127 Alfa Romeo �22 +97 Aston Martin Vantage Safety Car 128 Konnersport �22 +98 Ferrari F8 Tributo 129 Konnersport +99 Ferrari Roma 130 Prema �22 +100 McLaren 720S 131 Virtuosi �22 +101 McLaren Artura 132 Carlin �22 +102 Mercedes AMG GT Black Series Safety Car 133 MP Motorsport �22 +103 Mercedes AMG GTR Pro 134 Charouz �22 +104 F1 Custom Team 135 Dams �22 + + +Driver IDs + +ID Driver ID Driver ID Driver +0 Carlos Sainz 56 Louis Del�traz 115 Theo Pourchaire +1 Daniil Kvyat 57 Antonio Fuoco 116 Richard Verschoor +2 Daniel Ricciardo 58 Charles Leclerc 117 Lirim Zendeli +3 Fernando Alonso 59 Pierre Gasly 118 David Beckmann +4 Felipe Massa 62 Alexander Albon 121 Alessio Deledda +6 Kimi R�ikk�nen 63 Nicholas Latifi 122 Bent Viscaal +7 Lewis Hamilton 64 Dorian Boccolacci 123 Enzo Fittipaldi +9 Max Verstappen 65 Niko Kari 125 Mark Webber +10 Nico Hulkenburg 66 Roberto Merhi 126 Jacques Villeneuve +11 Kevin Magnussen 67 Arjun Maini 127 Callie Mayer +12 Romain Grosjean 68 Alessio Lorandi 128 Noah Bell +13 Sebastian Vettel 69 Ruben Meijer 129 Jake Hughes +14 Sergio Perez 70 Rashid Nair 130 Frederik Vesti +15 Valtteri Bottas 71 Jack Tremblay 131 Olli Caldwell +17 Esteban Ocon 72 Devon Butler 132 Logan Sargeant +19 Lance Stroll 73 Lukas Weber 133 Cem Bolukbasi +20 Arron Barnes 74 Antonio Giovinazzi 134 Ayumu Iwasa +21 Martin Giles 75 Robert Kubica 135 Clement Novalak +22 Alex Murray 76 Alain Prost 136 Jack Doohan +23 Lucas Roth 77 Ayrton Senna 137 Amaury Cordeel +24 Igor Correia 78 Nobuharu Matsushita 138 Dennis Hauger +25 Sophie Levasseur 79 Nikita Mazepin 139 Calan Williams +26 Jonas Schiffer 80 Guanya Zhou 140 Jamie Chadwick +27 Alain Forest 81 Mick Schumacher 141 Kamui Kobayashi +28 Jay Letourneau 82 Callum Ilott 142 Pastor Maldonado +29 Esto Saari 83 Juan Manuel Correa 143 Mika Hakkinen +30 Yasar Atiyeh 84 Jordan King 144 Nigel Mansell +31 Callisto Calabresi 85 Mahaveer Raghunathan +32 Naota Izum 86 Tatiana Calderon +33 Howard Clarke 87 Anthoine Hubert +34 Wilheim Kaufmann 88 Guiliano Alesi +35 Marie Laursen 89 Ralph Boschung +36 Flavio Nieves 90 Michael Schumacher +37 Peter Belousov 91 Dan Ticktum +38 Klimek Michalski 92 Marcus Armstrong +39 Santiago Moreno 93 Christian Lundgaard +40 Benjamin Coppens 94 Yuki Tsunoda +41 Noah Visser 95 Jehan Daruvala +42 Gert Waldmuller 96 Gulherme Samaia +43 Julian Quesada 97 Pedro Piquet +44 Daniel Jones 98 Felipe Drugovich +45 Artem Markelov 99 Robert Schwartzman +46 Tadasuke Makino 100 Roy Nissany +47 Sean Gelael 101 Marino Sato +48 Nyck De Vries 102 Aidan Jackson +49 Jack Aitken 103 Casper Akkerman +50 George Russell 109 Jenson Button +51 Maximilian G�nther 110 David Coulthard +52 Nirei Fukuzumi 111 Nico Rosberg +53 Luca Ghiotto 112 Oscar Piastri +54 Lando Norris 113 Liam Lawson +55 S�rgio Sette C�mara 114 Juri Vips + + +Track IDs + +ID Track +0 Melbourne +1 Paul Ricard +2 Shanghai +3 Sakhir(Bahrain) +4 Catalunya +5 Monaco +6 Montreal +7 Silverstone +8 Hockenheim +9 Hungaroring +10 Spa +11 Monza +12 Singapore +13 Suzuka +14 Abu Dhabi +15 Texas +16 Brazil +17 Austria +18 Sochi +19 Mexico +20 Baku(Azerbaijan) +21 Sakhir Short +22 Silverstone Short +23 Texas Short +24 Suzuka Short +25 Hanoi +26 Zandvoort +27 Imola +28 Portim�o +29 Jeddah +30 Miami +31 Las Vegas +32 Losail + + + +Nationality IDs + +ID Nationality ID Nationality ID Nationality +1 American 31 Greek 61 Paraguayan +2 Argentinean 32 Guatemalan 62 Peruvian +3 Australian 33 Honduran 63 Polish +4 Austrian 34 Hong Konger 64 Portuguese +5 Azerbaijani 35 Hungarian 65 Qatari +6 Bahraini 36 Icelander 66 Romanian +7 Belgian 37 Indian 67 Russian +8 Bolivian 38 Indonesian 68 Salvadoran +9 Brazilian 39 Irish 69 Saudi +10 British 40 Israeli 70 Scottish +11 Bulgarian 41 Italian 71 Serbian +12 Cameroonian 42 Jamaican 72 Singaporean +13 Canadian 43 Japanese 73 Slovakian +14 Chilean 44 Jordanian 74 Slovenian +15 Chinese 45 Kuwaiti 75 South Korean +16 Colombian 46 Latvian 76 South African +17 Costa Rican 47 Lebanese 77 Spanish +18 Croatian 48 Lithuanian 78 Swedish +19 Cypriot 49 Luxembourger 79 Swiss +20 Czech 50 Malaysian 80 Thai +21 Danish 51 Maltese 81 Turkish +22 Dutch 52 Mexican 82 Uruguayan +23 Ecuadorian 53 Monegasque 83 Ukrainian +24 English 54 New Zealander 84 Venezuelan +25 Emirian 55 Nicaraguan 85 Barbadian +26 Estonian 56 Northern Irish 86 Welsh +27 Finnish 57 Norwegian 87 Vietnamese +28 French 58 Omani +29 German 59 Pakistani +30 Ghanaian 60 Panamanian + + + + +Game Mode IDs + +ID Mode +0 Event Mode +3 Grand Prix +4 Grand Prix �23 +5 Time Trial +6 Splitscreen +7 Online Custom +8 Online League +11 Career Invitational +12 Championship Invitational +13 Championship +14 Online Championship +15 Online Weekly Event +17 Story Mode +19 Career �22 +20 Career �22 Online +21 Career �23 +22 Career �23 Online +127 Benchmark + + +Ruleset IDs + +ID Ruleset +0 Practice & Qualifying +1 Race +2 Time Trial +4 Time Attack +6 Checkpoint Challenge +8 Autocross +9 Drift +10 Average Speed Zone +11 Rival Duel + +Surface types + +These types are from physics data and show what type of contact each wheel is experiencing. + +ID Surface +0 Tarmac +1 Rumble strip +2 Concrete +3 Rock +4 Gravel +5 Mud +6 Sand +7 Grass +8 Water +9 Cobblestone +10 Metal +11 Ridged + +Button flags + +These flags are used in the telemetry packet to determine if any buttons are being held on the controlling device.If the value below logical ANDed with the button status is set then the corresponding button is being held. + +Bit Flag Button +0x00000001 Cross or A +0x00000002 Triangle or Y +0x00000004 Circle or B +0x00000008 Square or X +0x00000010 D - pad Left +0x00000020 D - pad Right +0x00000040 D - pad Up +0x00000080 D - pad Down +0x00000100 Options or Menu +0x00000200 L1 or LB +0x00000400 R1 or RB +0x00000800 L2 or LT +0x00001000 R2 or RT +0x00002000 Left Stick Click +0x00004000 Right Stick Click +0x00008000 Right Stick Left +0x00010000 Right Stick Right +0x00020000 Right Stick Up +0x00040000 Right Stick Down +0x00080000 Special +0x00100000 UDP Action 1 +0x00200000 UDP Action 2 +0x00400000 UDP Action 3 +0x00800000 UDP Action 4 +0x01000000 UDP Action 5 +0x02000000 UDP Action 6 +0x04000000 UDP Action 7 +0x08000000 UDP Action 8 +0x10000000 UDP Action 9 +0x20000000 UDP Action 10 +0x40000000 UDP Action 11 +0x80000000 UDP Action 12 + +Penalty types + +ID Penalty meaning +0 Drive through +1 Stop Go +2 Grid penalty +3 Penalty reminder +4 Time penalty +5 Warning +6 Disqualified +7 Removed from formation lap +8 Parked too long timer +9 Tyre regulations +10 This lap invalidated +11 This and next lap invalidated +12 This lap invalidated without reason +13 This and next lap invalidated without reason +14 This and previous lap invalidated +15 This and previous lap invalidated without reason +16 Retired +17 Black flag timer + + +Infringement types + +ID Infringement meaning +0 Blocking by slow driving +1 Blocking by wrong way driving +2 Reversing off the start line +3 Big Collision +4 Small Collision +5 Collision failed to hand back position single +6 Collision failed to hand back position multiple +7 Corner cutting gained time +8 Corner cutting overtake single +9 Corner cutting overtake multiple +10 Crossed pit exit lane +11 Ignoring blue flags +12 Ignoring yellow flags +13 Ignoring drive through +14 Too many drive throughs +15 Drive through reminder serve within n laps +16 Drive through reminder serve this lap +17 Pit lane speeding +18 Parked for too long +19 Ignoring tyre regulations +20 Too many penalties +21 Multiple warnings +22 Approaching disqualification +23 Tyre regulations select single +24 Tyre regulations select multiple +25 Lap invalidated corner cutting +26 Lap invalidated running wide +27 Corner cutting ran wide gained time minor +28 Corner cutting ran wide gained time significant +29 Corner cutting ran wide gained time extreme +30 Lap invalidated wall riding +31 Lap invalidated flashback used +32 Lap invalidated reset to track +33 Blocking the pitlane +34 Jump start +35 Safety car to car collision +36 Safety car illegal overtake +37 Safety car exceeding allowed pace +38 Virtual safety car exceeding allowed pace +39 Formation lap below allowed speed +40 Formation lap parking +41 Retired mechanical failure +42 Retired terminally damaged +43 Safety car falling too far back +44 Black flag timer +45 Unserved stop go penalty +46 Unserved drive through penalty +47 Engine component change +48 Gearbox change +49 Parc Ferm� change +50 League grid penalty +51 Retry penalty +52 Illegal time gain +53 Mandatory pitstop +54 Attribute assigned diff --git a/GamesDat/Telemetry/Sources/Formula1/Specifications/README.md b/GamesDat/Telemetry/Sources/Formula1/Specifications/README.md new file mode 100644 index 0000000..c348109 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/Specifications/README.md @@ -0,0 +1,31 @@ +# F1 UDP Telemetry Specifications + +This directory stores the official C++ specification files from EA/Codemasters for F1 game UDP telemetry. + +## Naming Convention + +Place specification files here using this naming pattern: +- `F1__spec.h` (e.g., `F1_2026_spec.h`) +- `F1_spec.h` (e.g., `F12026_spec.h`) +- `_udp_spec.h` (e.g., `2026_udp_spec.h`) + +## Usage + +The `/f1-implementation` skill automatically looks for specifications in this directory first. If your spec file follows the naming convention above, it will be found automatically. + +## Obtaining Specifications + +Official F1 game UDP specifications are typically: +- Provided by EA/Codemasters in game documentation +- Available in the game installation directory +- Published on official forums or GitHub repositories +- Included in game modding documentation + +## Example + +``` +Specifications/ +├── F1_2024_spec.h # F1 2024 specification +├── F1_2025_spec.h # F1 2025 specification +└── F1_2026_spec.h # F1 2026 specification (future) +``` From 03c50c99847438688622f7a8ec2a8132fe856517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Tue, 10 Feb 2026 15:20:49 +0100 Subject: [PATCH 08/25] Added F1 packets and fixtures --- .../F1-Telemetry-Fixture-Capture/capture.js | 24 + .../fixtures/.gitkeep | 0 .../fixtures/2023/packet-0.bin | Bin 0 -> 1349 bytes .../fixtures/2023/packet-1.bin | Bin 0 -> 644 bytes .../fixtures/2023/packet-10.bin | Bin 0 -> 953 bytes .../fixtures/2023/packet-11.bin | Bin 0 -> 1460 bytes .../fixtures/2023/packet-12.bin | Bin 0 -> 231 bytes .../fixtures/2023/packet-13.bin | Bin 0 -> 217 bytes .../fixtures/2023/packet-2.bin | Bin 0 -> 1131 bytes .../fixtures/2023/packet-3.bin | Bin 0 -> 45 bytes .../fixtures/2023/packet-4.bin | Bin 0 -> 1306 bytes .../fixtures/2023/packet-5.bin | Bin 0 -> 1107 bytes .../fixtures/2023/packet-6.bin | Bin 0 -> 1352 bytes .../fixtures/2023/packet-7.bin | Bin 0 -> 1239 bytes .../fixtures/2023/packet-8.bin | Bin 0 -> 1020 bytes .../fixtures/2024/packet-0.bin | Bin 0 -> 1349 bytes .../fixtures/2024/packet-1.bin | Bin 0 -> 753 bytes .../fixtures/2024/packet-10.bin | Bin 0 -> 953 bytes .../fixtures/2024/packet-11.bin | Bin 0 -> 1460 bytes .../fixtures/2024/packet-12.bin | Bin 0 -> 231 bytes .../fixtures/2024/packet-13.bin | Bin 0 -> 237 bytes .../fixtures/2024/packet-14.bin | Bin 0 -> 101 bytes .../fixtures/2024/packet-2.bin | Bin 0 -> 1285 bytes .../fixtures/2024/packet-3.bin | Bin 0 -> 45 bytes .../fixtures/2024/packet-4.bin | Bin 0 -> 1350 bytes .../fixtures/2024/packet-5.bin | Bin 0 -> 1133 bytes .../fixtures/2024/packet-6.bin | Bin 0 -> 1352 bytes .../fixtures/2024/packet-7.bin | Bin 0 -> 1239 bytes .../fixtures/2025/packet-0.bin | Bin 0 -> 1349 bytes .../fixtures/2025/packet-1.bin | Bin 0 -> 753 bytes .../fixtures/2025/packet-10.bin | Bin 0 -> 1041 bytes .../fixtures/2025/packet-11.bin | Bin 0 -> 1460 bytes .../fixtures/2025/packet-12.bin | Bin 0 -> 231 bytes .../fixtures/2025/packet-13.bin | Bin 0 -> 273 bytes .../fixtures/2025/packet-14.bin | Bin 0 -> 101 bytes .../fixtures/2025/packet-15.bin | Bin 0 -> 1131 bytes .../fixtures/2025/packet-2.bin | Bin 0 -> 1285 bytes .../fixtures/2025/packet-3-BUTN.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-COLL.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-FLBK.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-FTLP.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-LGOT.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-OVTK.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-PENA.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-RCWN.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-RTMT.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-SEND.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-SPTP.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-SSTA.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-STLG.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3-TMPT.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-3.bin | Bin 0 -> 45 bytes .../fixtures/2025/packet-4.bin | Bin 0 -> 1284 bytes .../fixtures/2025/packet-5.bin | Bin 0 -> 1133 bytes .../fixtures/2025/packet-6.bin | Bin 0 -> 1352 bytes .../fixtures/2025/packet-7.bin | Bin 0 -> 1239 bytes .../fixtures/2025/packet-8.bin | Bin 0 -> 1042 bytes .../F1-Telemetry-Fixture-Capture/package.json | 14 + GamesDat.Demo.Wpf/GamesDat.Demo.Wpf.csproj | 2 +- .../Properties/launchSettings.json | 5 +- GamesDat.Demo.Wpf/Resources/Styles.xaml | 2 + .../ViewModels/F1RealtimeSourceViewModel.cs | 166 +++ .../ViewModels/IRealtimeSource.cs | 14 + .../ViewModels/RealtimeSourceViewModel.cs | 8 +- .../ViewModels/RealtimeTabViewModel.cs | 8 +- GamesDat.Demo.Wpf/Views/RealtimeTabView.xaml | 156 ++- .../Sources/Formula1/Docs/F1_2022_spec.md | 1226 +++++++++++++++++ .../F1_2023_spec.h => Docs/F1_2023_spec.md} | 0 .../Sources/Formula1/F12022/CarDamageData.cs | 36 + .../Sources/Formula1/F12022/CarMotionData.cs | 30 + .../Sources/Formula1/F12022/CarSetupData.cs | 31 + .../Sources/Formula1/F12022/CarStatusData.cs | 42 + .../Formula1/F12022/CarTelemetryData.cs | 36 + .../Formula1/F12022/EventDataDetails.cs | 115 ++ .../F12022/FinalClassificationData.cs | 31 + .../Sources/Formula1/F12022/LapData.cs | 38 + .../Sources/Formula1/F12022/LapHistoryData.cs | 15 + .../Sources/Formula1/F12022/LobbyInfoData.cs | 19 + .../Sources/Formula1/F12022/MarshalZone.cs | 11 + .../Formula1/F12022/PacketCarDamageData.cs | 18 + .../Formula1/F12022/PacketCarSetupData.cs | 18 + .../Formula1/F12022/PacketCarStatusData.cs | 18 + .../Formula1/F12022/PacketCarTelemetryData.cs | 26 + .../Formula1/F12022/PacketEventData.cs | 26 + .../F12022/PacketFinalClassificationData.cs | 20 + .../Sources/Formula1/F12022/PacketHeader.cs | 19 + .../Sources/Formula1/F12022/PacketLapData.cs | 21 + .../Formula1/F12022/PacketLobbyInfoData.cs | 20 + .../Formula1/F12022/PacketMotionData.cs | 40 + .../Formula1/F12022/PacketParticipantsData.cs | 20 + .../Formula1/F12022/PacketSessionData.cs | 71 + .../F12022/PacketSessionHistoryData.cs | 30 + .../Formula1/F12022/ParticipantData.cs | 22 + .../Formula1/F12022/TyreStintHistoryData.cs | 12 + .../Formula1/F12022/WeatherForecastSample.cs | 20 + .../Sources/Formula1/F12025/CarSetupData.cs | 2 +- .../Formula1/F12025/CarTelemetryData.cs | 2 +- .../Sources/Formula1/F12025/EventData.cs | 2 +- .../Formula1/F12025/EventDataDetails.cs | 9 +- .../Formula1/F12025/ParticipantData.cs | 2 +- .../Sources/Formula1/F1PacketTypeMapper.cs | 15 + .../Formula1/F1RealtimeTelemetrySource.cs | 21 +- .../Sources/Formula1/F1TelemetryFrame.cs | 35 +- .../Formula1/F1TelemetryFrameExtensions.cs | 84 ++ .../Sources/Formula1/Specifications/F1_2025.h | 860 ++++++++++++ 105 files changed, 3394 insertions(+), 68 deletions(-) create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/capture.js create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/.gitkeep create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-0.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-1.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-10.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-11.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-12.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-13.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-2.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-3.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-4.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-5.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-6.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-7.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-8.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-0.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-1.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-10.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-11.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-12.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-13.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-14.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-2.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-3.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-4.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-5.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-6.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-7.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-0.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-1.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-10.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-11.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-12.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-13.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-14.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-15.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-2.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-BUTN.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-COLL.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FLBK.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FTLP.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-LGOT.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-OVTK.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-PENA.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-RCWN.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-RTMT.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SEND.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SPTP.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SSTA.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-STLG.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-TMPT.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-4.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-5.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-6.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-7.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-8.bin create mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/package.json create mode 100644 GamesDat.Demo.Wpf/ViewModels/F1RealtimeSourceViewModel.cs create mode 100644 GamesDat.Demo.Wpf/ViewModels/IRealtimeSource.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/Docs/F1_2022_spec.md rename GamesDat/Telemetry/Sources/Formula1/{Specifications/F1_2023_spec.h => Docs/F1_2023_spec.md} (100%) create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/CarDamageData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/CarMotionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/CarSetupData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/CarStatusData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/CarTelemetryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/EventDataDetails.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/FinalClassificationData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/LapData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/LapHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/LobbyInfoData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/MarshalZone.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarDamageData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarSetupData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarStatusData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarTelemetryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketEventData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketFinalClassificationData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketHeader.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketLapData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketLobbyInfoData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketMotionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketParticipantsData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketSessionData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/PacketSessionHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/ParticipantData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/TyreStintHistoryData.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F12022/WeatherForecastSample.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrameExtensions.cs create mode 100644 GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2025.h diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/capture.js b/DebugTooling/F1-Telemetry-Fixture-Capture/capture.js new file mode 100644 index 0000000..5f98e58 --- /dev/null +++ b/DebugTooling/F1-Telemetry-Fixture-Capture/capture.js @@ -0,0 +1,24 @@ +import dgram from "dgram"; +import { writeFileSync, mkdirSync } from "fs"; + +const socket = dgram.createSocket("udp4"); +socket.bind(20777); + +socket.on("message", (msg) => { + // PacketHeader: packetFormat(2) + gameYear(1) + gameMajorVersion(1) + gameMinorVersion(1) + packetVersion(1) + packetId(1) + const packetFormat = msg.readUInt16LE(0); // e.g., 2025, 2024 + const packetId = msg.readUInt8(6); + + const dir = `./fixtures/${packetFormat}`; + mkdirSync(dir, { recursive: true }); + + let filename = `packet-${packetId}.bin`; + + // Event packets (packetId 3) have a 4-character event code at offset 29 (after 29-byte header) + if (packetId === 3) { + const eventCode = msg.toString("ascii", 29, 33); // Read 4-byte event string (SSTA, FTLP, etc.) + filename = `packet-3-${eventCode}.bin`; + } + + writeFileSync(`${dir}/${filename}`, msg); +}); diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/.gitkeep b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-0.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-0.bin new file mode 100644 index 0000000000000000000000000000000000000000..2e5a7281acde5cba08b01d8ea030ed382cb3a1cd GIT binary patch literal 1349 zcmaFP&cG0fLPOfzVAHq+O2dM2rFZ+$N3 zP?|Rz>6r}a-WDe`?bsq(_szAsYIAj;f&)V~^B&h@!Mo+ozd4-L70`4#8#*xs>D%u( zkbQ{F_W;A0nWkyd|5<#I_1@kk@4#^Q!0xr@o?7pdI)1okp6!8I&YfuX#xh6&?LC2J zuLH>5dse#-aLU*;yPi3`P}uIkoa`l zz3?B^-pwGr4}vYto*uF)tiEt~X^r23Uslu5^gaaYeUdKmZx4!_--7fyR9U%LmDm)m zyn0wn#pl4|^yz4N`@#0EL9_Q7Nbj^H>xt4D*6F%e4=;KfazK9P92C8Wy(*bhW;{wi z`)3P^y^9oqdQ0c+S#nzybd^CH{gY3O<-bzt$qP6h7 zn}>JJh&f97CnM6vfFSnn6hpLIuV&T`&A9Mm6n;9cAjG`&}WdY`73 ZqQ%iyklq99tytc=+90R5QTE^r0RU5UBvb$Z literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-1.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-1.bin new file mode 100644 index 0000000000000000000000000000000000000000..ad8ac56c0be8525ba1afbd469f941a946ca27d23 GIT binary patch literal 644 zcmaFP&cG2d!^rTTfp3PLfi1() zecW~oi+>u~F(e5F*)g0v-eJeE=jJ>+hRvdT>==69J+Na)lw+`G$XO_1&!FgUXU~vc q8f4FK=3s_B!*ka%d#L%N)QAfKMn*;k21Z6!rYlT9zcT_M5C8y!-6EF& literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-10.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-10.bin new file mode 100644 index 0000000000000000000000000000000000000000..6dd3d7aca06d2f98c3f7479a32d86f780a8b93c7 GIT binary patch literal 953 scmaFP&cGARtLtr!nMnix`A@E;N H0ISIWCQ%4u literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-12.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-12.bin new file mode 100644 index 0000000000000000000000000000000000000000..53ef9c9255cf01d5e66bb332361091e1b9b50fd3 GIT binary patch literal 231 lcmaFP&cGDFCcI2ipJu literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-13.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-13.bin new file mode 100644 index 0000000000000000000000000000000000000000..a9b1eebeb9098f717591937f022bb76aab97a280 GIT binary patch literal 217 zcmV;~04D$E2LJ&P0S$;767?ICfBHX{EkqHX0000000030Qzb=0kLN`~Z;od|P0?vW zwovH6jLF}@Ex#nc^IjjoOk|D2ru~A%=)Sc>iK+lYZGDnL@OhF#PPmdnthJItgS#)f zpk_+CTu$ORB<9dKFwS{9KW5;&^(}Y1yYLx0e6gp*qLlSS4#k*415d<5am*&e=dwb> zX8z_uQY_vwM^!^ z69|KKzzk+!fa{nYi|!A5?@dl14Aud45|9no0c4}uaad`)69|KKV6g*Jhf2V5ClDrH zhqS_UClDrHhk{wV69^NpBP<}#351E);TG%X1j5AY2y@VL0%78Hs3-F|fiUqp*rwib f1YzQJ6z`bh2*UVu0ObgS|AdvGsTkG4z{COoO~hhH literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-3.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-3.bin new file mode 100644 index 0000000000000000000000000000000000000000..1f6f1006b22c2428da41237bee97c9b2724be92b GIT binary patch literal 45 icmaFP&cGWs5n82k%5u% zKO<1*|A5q@;{3eC90jM;;u3=T7=X6@XZ+7ZwoYaapq;@br3IA?q_`GnCo{-S@^mth zt&@Rloh)SQWF=cCJJ~wf$kxeBwoXp6b!L#OlZCvz@}J@V|NjiYtVRYH)k!GO$POU@ D^yz<5 literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-5.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-5.bin new file mode 100644 index 0000000000000000000000000000000000000000..429edc2aa8833646c927bde792601ada275e2cf1 GIT binary patch literal 1107 zcmaFP&cGGcY6^0O~&g)L=8=&{tbkZhme_{ghD+ppY63 Qr9qT5AgO$mq;d!V05TH(3;+NC literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-6.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-6.bin new file mode 100644 index 0000000000000000000000000000000000000000..dcfb0f74c9aad41f7fa89eb42b91c6e6aa661695 GIT binary patch literal 1352 zcmaFP&cGl{IN#hasG3{rg>sJFr1Y>kO6STBdRF^HCgLN_SLV@OL|u#u;m4$&>Ce9(h&Inp8)`tQ>B~$ literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-7.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-7.bin new file mode 100644 index 0000000000000000000000000000000000000000..060942068b5672e9e7973d7a23b91e64fcdd697c GIT binary patch literal 1239 zcmaFP&cG^i_It{V)6Jx-NqA7$D$30|TQKkg4Fvz;MTDXVVG?ZQ}*}oInvl z0Tu?0SxlZ_bt)xZOb`ul5~nsshMDP~JYd=w;Tj+eJldRoA#5XB8w10t?>pSdu(%B61z003rPghT)U literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-8.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-8.bin new file mode 100644 index 0000000000000000000000000000000000000000..660a2dd3ed425c4f7ad491dbf36e280c2d0d0939 GIT binary patch literal 1020 zcmaFP&cG_HIbBYY@|LVDRy^(TR}UEqDIS;iSf>1DA5P zIP>^TblQ2Wai8h^i-%TvZv7uIeQAc^zZIT9JK$jZc|`{XJ8|pYjpa5ekJmY`@q6I# zOmO9)`U-2uMg3d%mLAhRu&xld$wmEI$Y;x zazOQDlQXiK{a#iKk~5yAANse~3t2o<4QTI-7rSnz%G;F5taq;HecSvoL$sIX=V-m`n3)bYbT^K1{ya_&U4HY(yMT5*K&OKbcN{IZ&cruQLG?~`G&f<<-MtDn17ur%y-I+Yh#P4Vt~zKzgSoSx=PKuuj*#dU(;>kOT5N=b-34>{ZF6 zGUHMD**{xQ>|LY?)LS}l&!U@WY~&(u9q#K7I8gIwE{fiLUT4Af=A+qr9%S!@^Hz$2 z6Rm~s-8{T&M$7>V)5U0dKLYjMPk;SqCyKof!Fs<~{;WG{bC&b|;h_Go1MlLNpy|B| e)cZ8O6fKUvg7h9(Z^iP~)do4ejj{)42mk;y9WE{a literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-1.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-1.bin new file mode 100644 index 0000000000000000000000000000000000000000..f3ca10a69e2ddcb3c729677a4290f24ad3f2e8c9 GIT binary patch literal 753 zcmaFCF3Bj!$k-t*^i_It{V&@z4VQ?S3=AO5@Sj0Vl9fqZh#km|V6p)T@XfF@uw^*9 zkK2x6@lPW=h9totJBE|TJM0+t+?;2}uvv7E9YfE%2X+jJat!tiISVE185I5P>>1Ka zgX|g39L%t1cWHSjD`RiA;8GU$iTqJ$jWqu38Mr|dFff2H!+!=wo;XGZmwW~W(|!hqN%2521_nk3 dMkXBMER3^b85z#xF)$eQF)++cfT(9=1OU{u6aN4J literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-2.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-2.bin new file mode 100644 index 0000000000000000000000000000000000000000..9e5bc9deaf3ac46862956f4ae54c45ec1615ab07 GIT binary patch literal 1285 zcmaFCF3Bj!$kZV%^i_It{jZ2d4VQwM3=AO5@c&ReBZIg%kc9*SJiA=}>^$HG6l!2( zMMy9*LD)?j7CJNW#6dLbFfcH}l*lJ;cLiavMxbJ-`u}hRK#Bh_jWA6h+URGW3kU<5 z4KQ1PY*dXPh3HOQCUe~hguxmS)}U&f9gFG5iP{dVRU=RU!^Xo(+nqod ztPz)u*fgpHEO!E7k~K;zOm_ldk~J!rwL5_@$r{4~@|-}JWQ}gIeoi1vvc@n6Jtq(* zS)+O~pA!g^tdVW%9Y+u*S!3~zIgTJqP$N(QG2lNjim<5})rc)rpkkrq5vaM z6(i$+MxfsR0jWjB`FV*s3QnoTB}C{3>HW|6pNRs!%p5>_gG)*aDj7&~GtgdUkiF#U zWuibY0|k0nDA3DFfnIhB^s-T)mze^+oD}HIAYU&Fx%ucn!~g&Pfmwoxtb;|#s9r3A Kfd7XSbN literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-6.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-6.bin new file mode 100644 index 0000000000000000000000000000000000000000..f2319d15bc963201d84cb3675ce45067ed4d9ee7 GIT binary patch literal 1352 zcmaFCF3Bj!$krh(^i_It{V&OS4VS!`3=AO5@IM1cFfbgbblMAMFwIs5(LPMxO!-Wy zOtDaq$8gGIog)aZcyknt8Q2-vPct$wG}xQ1F|h^f<JhuF(!yZXF9X9K nM+m+Eq(K-7G(gz6>>brR8dkW{2DZF_6k4OqK^g-8|1$sp%XO#| literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-7.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-7.bin new file mode 100644 index 0000000000000000000000000000000000000000..3ae9b9054c85b8fa615cf4a4cb4ab76840387ef7 GIT binary patch literal 1239 zcmaFCF3Bj!$lf6>^i_It{V&OS4VS!`3=AO5@SlN!(F#Z_I5IHYaoX9m!a>`30Y4|h zWd=b376!|kS3Q7|3=AqIUQA#Hg21Yc0jQ0UVP^U#xHd+F4loO=wsl}_`6g_hU^PJ7 sh|&hKje%j+_Z{veXuiEEey30le?<-@`NroFl+~K zvryFJQDbgSd?+)-nA`4lcZWks5P1+|iTM10k_4g@aO0(&-uHX;J>Qq#^Etn#ZUKy` z3{ZBHKGt1u>=)PQ6ZrEX02Yn{;73mgJ0(zOM#KJ`aGcDzW(!?h%RQi&0yFlcg7-b{ zy;3oxd!`i)VZ}1JB`T_eP`uEOhgsjnr)&8-!K2CpEe} z&}}*F%1vMnpk@45K}8UG0-eRruf596ghS!f zaDIv9Ffos6*NfN|Q~)wYD?wx)f=Cr|p>Vyf^0VNG96y6RiO3=iGW&XolC;Qj?Et zfCY6fILOVU4s5ToQrIB=>pwPx<2H=+zir%OjN52)aNwXfCqH#MzOz&cn#iz$}+1eCB_`DkhNTf+$t}*Y-Xy*UAF&3Wc z4a5Bql*$UqMPtP%CD-o;1^O@7&tfX|Jr_@N*QKFY&%*?6MZ@%QlQ|}HNQ1d;RXb-4=$krZ?`IwgZdRO<1acWXIa7l+KU z1b6&Le=+<+xDLN=mr_fVOlxzdh__dnI<_pJ`4MAmuhJlmCA~-e$pyaNX!O~itb1O? zBTujAA(wn_2!^~uVWeg`zSOnO_OY#n5U7)>;FcPCrMgB|$p~)gV>Mcf7sg<#UN>>m zR8b^o4dm1|t}@>}o=kb?q`>6txO!Q40YAGx4ZV9%$2%>L2lb)oE$i4R`PCSIGJ?8@ zI(_@iV^taq{J=6@=wu-+AhKQ9I%;>2`R$uMIfY8QS9biCY+Hkz<+dP;po{A^!Xyj$4J1p*TT7FPS1|6ZeQ!sNR iAVgB@W*$gu=T>!o#8o}2K!LoBb6Y!%y!}u9&-@QS*)=Wz literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-1.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-1.bin new file mode 100644 index 0000000000000000000000000000000000000000..5e2bac603a722f33b74e1af72e35f60d68f5fefa GIT binary patch literal 753 zcmaFKF3Bj!$T;KL|GD>C=Wb&)UgGlR8v}#kUj_!@{|wT?B6CFe85sVFDaZscGW=%{ zx&H8x6~kOLJ6ne2y%%j6?|4vSVm3&9Gw-e$`>epgVPz9mCp=Rdx((^iSI{ zWEH)%V{mxSXwTqroY$Vg$VtYYq0dy+o}qNJnLPu4yrVtC%C7#hE|>z{tb|OsY&kE-(SX!TN=_T~@Aia|Hlj_CDwU literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-10.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-10.bin new file mode 100644 index 0000000000000000000000000000000000000000..4689b6c75625effb132cb8d2d6ce64345b358f4c GIT binary patch literal 1041 zcmaFKF3Bj!$Tj2I|GD>C=Wb&)UgGlR8v}#kUj_!@|3Q)89m0IiI)p#p=wR&;@4(K+ z%FKWh%)20K*Jr?I_vJvb?V$sXwpbOWFr)QysOk?HxC(OFRBex#+;h&B=yU z2UxI}$I|g!kc{KopG=Pa|5i9~b8@iaR^9hS&ynlAfMZX{BZt}>(;T=s*;#O_w!0ssk@xcQ46icFgU3;qXCxm&1(U z76%S?Ry;1L`lso5>MOV73e}GemHAg3xH#DHgh_c)u;T(oW5=ZvSsYg{*a&n9zA*Xn z>9fO?hAR#m?Ke7n5--9Um*A)Y1{5QMcZZJSuVsRc!j)`}S2k<{h9Ew(k9tNpcG#Oc zHswn@ew=ZUK$v9K#5$_A*g0-A6?J5aA*A~19y`Y?o75dOqS+i9|L$?%=ECQaO%h6u zHb=M|`82OO+zprx3_&(L;r*gl+fntrh~v(cHy!d?LFoja>S=Ym>_1)Vx33mzviA-Z zv}a^w#GR-Z{9WuF>+)nBc|Pzs{%qMoAjQfrc6SJLmUFO}G}D2V+uwnii3Oh1_@K5h OGBYtSFf#%PS^@yfD3yZ% literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-11.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-11.bin new file mode 100644 index 0000000000000000000000000000000000000000..183a7098924f18fc4913d5deb7a94f3162aa8b59 GIT binary patch literal 1460 zcmaFKF3Bj!$UWoQ|GD>C=Wb&)UgGlR8v}#kUj_!@|LpusZ2auJpXwPI-cM!t;=#bb zkb(bDC?mu68U{vB2F?i#{9S&G48G|GD>C=Wb&)UgGlR8v}#kUj_!@|7?PSB8>bzg7u6HLP89T{Gy`% z{}|ZWfh<|sXaNQe4zSpC2?hZH1_nkRo~b{~SXgVQ5tt$#a6A7O{txFC=%>QCTF)xEd%Q+H5FlDSlQemt@gz%|Cf9^5&zXh!~`$HcAHT{mHJvmIAvQ! z7NpC<<6X$YAR~)D(A$>0;2sP-BLAsEB|u8NpZt!yW+%8i?|M|etjgoPu5gDw0001h zI=lo%yG8{@`NhUXgp9^T;IY0tJ7wxSO*@u0`K#Wy08nPR01yZ|1c@-cWPC8a(?{ zsZn`+IUQdgj?en;>Hhr#ID7)*RMB?AYg3}D!REw$C`mx-%4!+eWWOuHwJK;_)wZ%6 zOvK3E)gq|R>{1c_7P7?FVt>E;hq}HY7{;GDyY9AncGq<^4YG?ETNqtPu@cLgo!qUYAkvD!>`O&i;@Sl&62&Dl zi|)F+$|e%ghl(zCrB4MFk`XAOV$z2qFC`V0#b&c-b~yb74ToVkGrxJBdEWPvc?xk5 zrLlXWxxcQtJ#JB(dw2|37y+CUFNz49GYC3-c;v-2BMK@pFn)&h?Pw|lP_5cQ6u#iY zVT>(ij|FI4e?5U?yFqNl=CqN^tmx+f4RoqRg2^xw?L?$5Kusgw5yJ{X;@AUI!B;SbKyk{wOBcl4bih8mxD zFps6rx~PE;4#rx?Cc$%mg4BA2H+gbC!4n@ky$D%QSc1`wLDn1*Z=9sR(JH~fNQkNr zc&g8M_e3gj!HbnC(QFe}wz1NaBGot@OpxG#8V-KpbzGJ}@O(d9t6^0!R*!QiA79w*dlCA?CRjlQ+sH0uf(ZoR_6;qbCQL9k;pYz1TUqQ0n Ang9R* literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-BUTN.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-BUTN.bin new file mode 100644 index 0000000000000000000000000000000000000000..6b98e9a8be7c78fbca5873e45f62d0af0aab3a45 GIT binary patch literal 45 xcmaFKF3Bj!$UNiP|GD>C=Wb&)UgGlR8v}#kUj_!@|4yMHehfemC9WF|!T^pS4@Cd~ literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-COLL.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-COLL.bin new file mode 100644 index 0000000000000000000000000000000000000000..9e725c18198617a4ccda8ac67265e06289a744dd GIT binary patch literal 45 zcmaFKF3Bj!$UNiP|GD>C=Wff5Pj_M2z`y{)!vCH9eSA2Dzp14&F))01GF=M*ec%u= literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FLBK.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FLBK.bin new file mode 100644 index 0000000000000000000000000000000000000000..d61bbd1c2d91f3d767736259ca965352dcd39bd6 GIT binary patch literal 45 zcmaFKF3Bj!$UNiP|GD>C=WaW6Yr4zIrwj}SUobET|9A6o@~(Nr!0=RKnhOIE007FF B5(EGM literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FTLP.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FTLP.bin new file mode 100644 index 0000000000000000000000000000000000000000..4ef9c8d86a52e2717c5d415ef4f6044dba87a2b3 GIT binary patch literal 45 xcmaFKF3Bj!$UNiP|GD>C=WhEb>Fts*i-7@zh5x&S_yhC=Wb&F0vkpM7XI(!?jHgYsSv!t5267v$O{Sp literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-OVTK.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-OVTK.bin new file mode 100644 index 0000000000000000000000000000000000000000..d9532f70bf0608c0f35ae7a3305369d72941f2f4 GIT binary patch literal 45 zcmV+|0Mh^I2N?ko0Rx!U|DE22owi+nphQ{m005r%000yJPgYb*1rh)NzI@0$mk*gg DrezVR literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-PENA.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-PENA.bin new file mode 100644 index 0000000000000000000000000000000000000000..97604be392595e936e42398c5077b6dfb3d8b117 GIT binary patch literal 45 zcmV+|0Mh^I2N?ko0Rx!U|DE22owjwOdqkSP006SV000yJP(@Bb5Geuw{|x`~yQ2lg Dwp|nz literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-RCWN.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-RCWN.bin new file mode 100644 index 0000000000000000000000000000000000000000..dd0b88db6fa4818f15d091c9c33c41164a7a66e0 GIT binary patch literal 45 ucmaFKF3Bj!$UHxMBFkBU2!)oMbC=Wb&)UgE+41;YP>UHx1*=190QLPY>ZUJE_| literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SPTP.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SPTP.bin new file mode 100644 index 0000000000000000000000000000000000000000..05093ae0379f7c8c1fa999d888ae610d15ce4152 GIT binary patch literal 45 zcmaFKF3Bj!$UNiP|GD>C=Wd(0dy&h^FANOtelaiz{|^ob31C{uJ<}P;5w&093;@AH B5zqht literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SSTA.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SSTA.bin new file mode 100644 index 0000000000000000000000000000000000000000..1efd014a84b485eaa07c87738deda4b2ba6f6271 GIT binary patch literal 45 ocmaFKF3Bj!$UNiP|GD>C=Wb&_0>b};gF_sdfg%N)lq5kk04G8UkN^Mx literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-STLG.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-STLG.bin new file mode 100644 index 0000000000000000000000000000000000000000..214628b6813a14739f9e392051a8504b15fc3da4 GIT binary patch literal 45 scmaFKF3Bj!$UNiP|GD>C=Wb&Ff)7wE{69Fv$DI`<#Kgdml%Jmi08YUSzW@LL literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-TMPT.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-TMPT.bin new file mode 100644 index 0000000000000000000000000000000000000000..57b12d5bfdd391789c71e349ec6f921ca5b8646e GIT binary patch literal 45 xcmaFKF3Bj!$UNiP|GD>C=WgpPYjxp0#lWC?mVrU|e~52D2sg)H7e)pM004(M4jljh literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3.bin new file mode 100644 index 0000000000000000000000000000000000000000..ccafd97d7bbb9d3a2234fd713de56fb5c8cd1b18 GIT binary patch literal 45 ucmaFKF3Bj!$eerTW!qG*U3Pa$*E=~1F))Cz@PDV!5I+VWh!WQg2Vnq9`wYAQ literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-4.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-4.bin new file mode 100644 index 0000000000000000000000000000000000000000..075e39997b8b6fc180c84507b2f377c62db26515 GIT binary patch literal 1284 zcmbW%&ubGw6bJA(-J~&#Rog%{By9*eNlKtCX~9Buk_^qZlbv;DW6?uz1&{X7|3FW@ z6h-jTgQ6GD;?@7bgFh~UCyDf=Ab4nB-?E!FflQ0eac4h#XWo0ey-c1>veb)Dc<}AX z!Ta>$>(=2PfcYbU98R%kn1M~VA-L{vA8d=!20F*fW$Z6DNw8E@O=VYvM!+nNL3#R~ z&?e+TAS^tF)zz)vbLFG2K(TBU9k?EF**G^chBqF5fte45!gd6=C~>+Pny#-}+;+v} zs6&ZMcnMOjueA1z)g=&jt4gU_trjJ&U>e-w8aML3-6as7{=&CEy1rOLZAm_Y_}Zku;JNvE~Z7=ABZ#lt&E;tnOwE=j&=>}+IW4pT5S zVM48>wB_~T%KqM8e4NkcqvALOuXMOnzES@KU(oL zhf0cz;9xrzXaH`zx@!#e-;&qArD#f_o98An VXRwda$4tKys~Fm#cTd-U!xKYfA4UKG literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-5.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-5.bin new file mode 100644 index 0000000000000000000000000000000000000000..44f71f380e8018686ad1ee5e488c84f60b153ae4 GIT binary patch literal 1133 zcmaFKF3Bj!$U5WN|GD>C=Wb&)UgGlR8v}#kUj_!@|Dxhi<_ru82N)O_4sc&zY%}4| zS6fwXer`$q6f2{&v{jBEJZt7bFlJ+5P;ea8F&b8*sbpaDhq$<$`K+0d2aJtB?+4~u c+c$w0cB;bsoRUU~SaYq66EMxv0qk%D02OUmC=Wb&)UgGlR8v}#kUj_!@|1k_8@Xg`HJ}`r+ULHgnF`F=J zGaE36LO~uwTG}c{5S}&jAQ&@(l|zXJWR5Tc1S$c6CJg8KinT zSj$CaQ9Ce$rAHY=8!;O)t1~Mx{$n!V0A7 zCJ;B+d+cYh0r41EZ|N|){;#)UvSEs6O2Xq`Mg~TP7=}7FDSP$36QFT4T>)$cGZQl# zGZz7S)xcpT!L!E-tcOEYlTnF*VJYJ>#!HM>8S(m;A)UeGy0AUd^&Y6b7ga!Ja53{R zb2AIz4=b=`V4?xRWMB}1LJ26q9={@BH=D0lv<2&BpRLVc@qlq6<0QrvjB6MJpa8pG zBe356V$yrTdO0R&G3X~S90$VB3?CWr>-_}O(qM1!gJm~ZFI$x^gU=-R(KVpn2K&`XHIT4kU8l?7_nsepjq-waVRD(ovJZ-$2R84U(m1_o;; zOQv8ZKc)ch055MZFHh{@oWqb|@zJg#wh@vSm`GCMGPGP@E;lZN210y!M) zagKFb3KftG&VWZ$`$5U#n9B<4$=(v2ren+tCfg=7h002jX BQrrLl literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-7.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-7.bin new file mode 100644 index 0000000000000000000000000000000000000000..0d1242b4e6a01b535001befcc7dd00578eafa0f3 GIT binary patch literal 1239 zcmcK3>q`_-7zXh7I6F!zy1TB}l}wG)O1zaIl3Ll!d-`A>%B;*XTg^-|Q^HuGA}`%6 ziy$&Xa*a|;3NJ)hcC&kQ(^jeltw19wYYMGon>jnJ+aJ)E;moqXeV%jPvx9Dvh>p0O z=&{vUOKaNrDD&b^KL8@e0DNpfNGyb&&PxP%voJVzWy0*JTJH$}pwqcPmsl^#OMsw4 zN-lM@<2|z{9h}>Q;G!_s;TFcM`)iyS1NKLdgivg$75i_P#23$F1Yl{+?e;WJ$aOHi z-W6VbA*eWW(RxbOF0T`9{l1tGyIZcHq=|+SmiBV#E(g=;IPeM9U_t6fVaf(y%Jmv0 zJZbQz}Lig+lDg4>KTDz^v$bk$$RT^85=3d zb}%LF_o6n&3XO4;obE87&HJ_Jp8QKKX>}`Ota8>72UF5+-sdr=`>?RHB9oH75DB-| zdQqwRx?0jT(9U|sDphui4d99PGD{158$wAzwuJMK@zfB0S7p{C_L4!5Qk~K?P(4qB zE$(Ka*Gt};zO)H*lKklLf<~2F&-LHKd`jOnaPy+&y?`sdoS9T)T563 z^D(c5$}O=3A97;c_`Ky?GXBC=$^bko?Q?_ldE|FF1vUj7EaiXr(VzhB#rmMRJJ^zAA;8*d+XbkyH*1L*Y{$hob-rlbSH z(TkfX5$~Kw&eMa=`glQQ`KNZVv=MisBTcIX2sLupSX3+|rew)~oCa@Pc3>k8XGg27 zb=1Vzl7aCf(djhsICqqdyUQ9*iQm9<9DLd)W?K4`59K2f?yq93(B%J5+R8qj&Fx*u e6(q^G(hvtH(kyE$+c29tdwfc7?i_#n534_7zBkeU literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-8.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-8.bin new file mode 100644 index 0000000000000000000000000000000000000000..bbfb71868becfaefe90eed93947fffaff3bd91fe GIT binary patch literal 1042 zcmcK3Jxjwt7zgmXT%IIN)8)uo%04h~%u1lOwI;@~KPgR`9+EL6ln zp$NgQ`U!Lp#Gy`#APz!(?#1=l%yQ)T@#lZfB_p@SWA2@<9NQ1kYJFz*rpe5cjx!HR zHWSt$;%e`BLn`{v3ZXnWvJ&9rMpjGq_#jLsCO$$@6Ua7sB8fcqHbyZd=`?~ z5hSw7>xAz8_D<>)@Fc?8$aE8r?@&!DP?dT*Rl0sKAhR8?Jk%mqI^SxMI)PYb7n8cl z@Q2hKcn$TknaSNfIYAdZ2lX&i*+s8SW@nJeCQUuT_M$`1qy&M5dQ*QV`Dl;K25@}T zOI6jRyWSvm6)zt3p>9%~Ka#o(zSLLxcJlrusRJlW{cY~DaZPF;aMHp$GTA3RQrqAL bs4w&>+0+85L$GC&Ppyl!7gC!a?q8N)1hrT( literal 0 HcmV?d00001 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/package.json b/DebugTooling/F1-Telemetry-Fixture-Capture/package.json new file mode 100644 index 0000000..ded401d --- /dev/null +++ b/DebugTooling/F1-Telemetry-Fixture-Capture/package.json @@ -0,0 +1,14 @@ +{ + "name": "F1-Telemetry-Fixture-Capture", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "capture": "node capture.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.11.0", + "type": "module" +} diff --git a/GamesDat.Demo.Wpf/GamesDat.Demo.Wpf.csproj b/GamesDat.Demo.Wpf/GamesDat.Demo.Wpf.csproj index 566fa83..f4ca665 100644 --- a/GamesDat.Demo.Wpf/GamesDat.Demo.Wpf.csproj +++ b/GamesDat.Demo.Wpf/GamesDat.Demo.Wpf.csproj @@ -1,7 +1,7 @@  - WinExe + Exe net9.0-windows enable enable diff --git a/GamesDat.Demo.Wpf/Properties/launchSettings.json b/GamesDat.Demo.Wpf/Properties/launchSettings.json index 153c610..f796aca 100644 --- a/GamesDat.Demo.Wpf/Properties/launchSettings.json +++ b/GamesDat.Demo.Wpf/Properties/launchSettings.json @@ -1,7 +1,10 @@ { "profiles": { "GamesDat.Demo.Wpf": { - "commandName": "Project" + "commandName": "Project", + "environmentVariables": { + "DEBUG": "1" + } } } } \ No newline at end of file diff --git a/GamesDat.Demo.Wpf/Resources/Styles.xaml b/GamesDat.Demo.Wpf/Resources/Styles.xaml index d9d73a7..41796c9 100644 --- a/GamesDat.Demo.Wpf/Resources/Styles.xaml +++ b/GamesDat.Demo.Wpf/Resources/Styles.xaml @@ -3,6 +3,8 @@ xmlns:converters="clr-namespace:GamesDat.Demo.Wpf.Converters"> + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GamesDat/Telemetry/Sources/Formula1/Docs/F1_2022_spec.md b/GamesDat/Telemetry/Sources/Formula1/Docs/F1_2022_spec.md new file mode 100644 index 0000000..94c90ee --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/Docs/F1_2022_spec.md @@ -0,0 +1,1226 @@ +Packet Information + +Packet Types + +Each packet carries different types of data rather than having one packet which contains everything. The header in each packet describes the packet type and versioning info so it will be easier for applications to check they are interpreting the incoming data in the correct way. Please note that all values are encoded using Little Endian format. All data is packed. + +The following data types are used in the structures: + +Type Description +uint8 Unsigned 8-bit integer +int8 Signed 8-bit integer +uint16 Unsigned 16-bit integer +int16 Signed 16-bit integer +uint32 Unsigned 32-bit integer +float Floating point (32-bit) +uint64 Unsigned 64-bit integer + + +Packet Header + +Each packet has the following header: + +struct PacketHeader +{ + uint16 m_packetFormat; // 2022 + uint8 m_gameMajorVersion; // Game major version - "X.00" + uint8 m_gameMinorVersion; // Game minor version - "1.XX" + uint8 m_packetVersion; // Version of this packet type, all start from 1 + uint8 m_packetId; // Identifier for the packet type, see below + uint64 m_sessionUID; // Unique identifier for the session + float m_sessionTime; // Session timestamp + uint32 m_frameIdentifier; // Identifier for the frame the data was retrieved on + uint8 m_playerCarIndex; // Index of player's car in the array + uint8 m_secondaryPlayerCarIndex; // Index of secondary player's car in the array (splitscreen) + // 255 if no second player +}; + + +Packet IDs + +The packets IDs are as follows: + +Packet Name Value Description +Motion 0 Contains all motion data for player’s car – only sent while player is in control +Session 1 Data about the session – track, time left +Lap Data 2 Data about all the lap times of cars in the session +Event 3 Various notable events that happen during a session +Participants 4 List of participants in the session, mostly relevant for multiplayer +Car Setups 5 Packet detailing car setups for cars in the race +Car Telemetry 6 Telemetry data for all cars +Car Status 7 Status data for all cars +Final Classification 8 Final classification confirmation at the end of a race +Lobby Info 9 Information about players in a multiplayer lobby +Car Damage 10 Damage status for all cars +Session History 11 Lap and tyre data for session + + +Motion Packet + +The motion packet gives physics data for all the cars being driven. There is additional data for the car being driven with the goal of being able to drive a motion platform setup. +N.B. For the normalised vectors below, to convert to float values divide by 32767.0f – 16-bit signed values are used to pack the data and on the assumption that direction values are always between -1.0f and 1.0f. + +Frequency: Rate as specified in menus +Size: 1464 bytes +Version: 1 + +struct CarMotionData +{ + float m_worldPositionX; // World space X position + float m_worldPositionY; // World space Y position + float m_worldPositionZ; // World space Z position + float m_worldVelocityX; // Velocity in world space X + float m_worldVelocityY; // Velocity in world space Y + float m_worldVelocityZ; // Velocity in world space Z + int16 m_worldForwardDirX; // World space forward X direction (normalised) + int16 m_worldForwardDirY; // World space forward Y direction (normalised) + int16 m_worldForwardDirZ; // World space forward Z direction (normalised) + int16 m_worldRightDirX; // World space right X direction (normalised) + int16 m_worldRightDirY; // World space right Y direction (normalised) + int16 m_worldRightDirZ; // World space right Z direction (normalised) + float m_gForceLateral; // Lateral G-Force component + float m_gForceLongitudinal; // Longitudinal G-Force component + float m_gForceVertical; // Vertical G-Force component + float m_yaw; // Yaw angle in radians + float m_pitch; // Pitch angle in radians + float m_roll; // Roll angle in radians +}; + +struct PacketMotionData +{ + PacketHeader m_header; // Header + + CarMotionData m_carMotionData[22]; // Data for all cars on track + + // Extra player car ONLY data + float m_suspensionPosition[4]; // Note: All wheel arrays have the following order: + float m_suspensionVelocity[4]; // RL, RR, FL, FR + float m_suspensionAcceleration[4]; // RL, RR, FL, FR + float m_wheelSpeed[4]; // Speed of each wheel + float m_wheelSlip[4]; // Slip ratio for each wheel + float m_localVelocityX; // Velocity in local space + float m_localVelocityY; // Velocity in local space + float m_localVelocityZ; // Velocity in local space + float m_angularVelocityX; // Angular velocity x-component + float m_angularVelocityY; // Angular velocity y-component + float m_angularVelocityZ; // Angular velocity z-component + float m_angularAccelerationX; // Angular velocity x-component + float m_angularAccelerationY; // Angular velocity y-component + float m_angularAccelerationZ; // Angular velocity z-component + float m_frontWheelsAngle; // Current front wheels angle in radians +}; + + +Session Packet + +The session packet includes details about the current session in progress. + +Frequency: 2 per second +Size: 632 bytes +Version: 1 + +struct MarshalZone +{ + float m_zoneStart; // Fraction (0..1) of way through the lap the marshal zone starts + int8 m_zoneFlag; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow, 4 = red +}; + +struct WeatherForecastSample +{ + uint8 m_sessionType; // 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P, 5 = Q1 + // 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ, 10 = R, 11 = R2 + // 12 = R3, 13 = Time Trial + uint8 m_timeOffset; // Time in minutes the forecast is for + uint8 m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast + // 3 = light rain, 4 = heavy rain, 5 = storm + int8 m_trackTemperature; // Track temp. in degrees Celsius + int8 m_trackTemperatureChange; // Track temp. change – 0 = up, 1 = down, 2 = no change + int8 m_airTemperature; // Air temp. in degrees celsius + int8 m_airTemperatureChange; // Air temp. change – 0 = up, 1 = down, 2 = no change + uint8 m_rainPercentage; // Rain percentage (0-100) +}; + +struct PacketSessionData +{ + PacketHeader m_header; // Header + + uint8 m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast + // 3 = light rain, 4 = heavy rain, 5 = storm + int8 m_trackTemperature; // Track temp. in degrees celsius + int8 m_airTemperature; // Air temp. in degrees celsius + uint8 m_totalLaps; // Total number of laps in this race + uint16 m_trackLength; // Track length in metres + uint8 m_sessionType; // 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P + // 5 = Q1, 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ + // 10 = R, 11 = R2, 12 = R3, 13 = Time Trial + int8 m_trackId; // -1 for unknown, see appendix + uint8 m_formula; // Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2, + // 3 = F1 Generic, 4 = Beta, 5 = Supercars +// 6 = Esports, 7 = F2 2021 + uint16 m_sessionTimeLeft; // Time left in session in seconds + uint16 m_sessionDuration; // Session duration in seconds + uint8 m_pitSpeedLimit; // Pit speed limit in kilometres per hour + uint8 m_gamePaused; // Whether the game is paused – network game only + uint8 m_isSpectating; // Whether the player is spectating + uint8 m_spectatorCarIndex; // Index of the car being spectated + uint8 m_sliProNativeSupport; // SLI Pro support, 0 = inactive, 1 = active + uint8 m_numMarshalZones; // Number of marshal zones to follow + MarshalZone m_marshalZones[21]; // List of marshal zones – max 21 + uint8 m_safetyCarStatus; // 0 = no safety car, 1 = full + // 2 = virtual, 3 = formation lap + uint8 m_networkGame; // 0 = offline, 1 = online + uint8 m_numWeatherForecastSamples; // Number of weather samples to follow + WeatherForecastSample m_weatherForecastSamples[56]; // Array of weather forecast samples + uint8 m_forecastAccuracy; // 0 = Perfect, 1 = Approximate + uint8 m_aiDifficulty; // AI Difficulty rating – 0-110 + uint32 m_seasonLinkIdentifier; // Identifier for season - persists across saves + uint32 m_weekendLinkIdentifier; // Identifier for weekend - persists across saves + uint32 m_sessionLinkIdentifier; // Identifier for session - persists across saves + uint8 m_pitStopWindowIdealLap; // Ideal lap to pit on for current strategy (player) + uint8 m_pitStopWindowLatestLap; // Latest lap to pit on for current strategy (player) + uint8 m_pitStopRejoinPosition; // Predicted position to rejoin at (player) + uint8 m_steeringAssist; // 0 = off, 1 = on + uint8 m_brakingAssist; // 0 = off, 1 = low, 2 = medium, 3 = high + uint8 m_gearboxAssist; // 1 = manual, 2 = manual & suggested gear, 3 = auto + uint8 m_pitAssist; // 0 = off, 1 = on + uint8 m_pitReleaseAssist; // 0 = off, 1 = on + uint8 m_ERSAssist; // 0 = off, 1 = on + uint8 m_DRSAssist; // 0 = off, 1 = on + uint8 m_dynamicRacingLine; // 0 = off, 1 = corners only, 2 = full + uint8 m_dynamicRacingLineType; // 0 = 2D, 1 = 3D + uint8 m_gameMode; // Game mode id - see appendix + uint8 m_ruleSet; // Ruleset - see appendix + uint32 m_timeOfDay; // Local time of day - minutes since midnight + uint8 m_sessionLength; // 0 = None, 2 = Very Short, 3 = Short, 4 = Medium +// 5 = Medium Long, 6 = Long, 7 = Full +}; + + +Lap Data Packet + +The lap data packet gives details of all the cars in the session. + +Frequency: Rate as specified in menus +Size: 972 bytes +Version: 1 + +struct LapData +{ + uint32 m_lastLapTimeInMS; // Last lap time in milliseconds + uint32 m_currentLapTimeInMS; // Current time around the lap in milliseconds + uint16 m_sector1TimeInMS; // Sector 1 time in milliseconds + uint16 m_sector2TimeInMS; // Sector 2 time in milliseconds + float m_lapDistance; // Distance vehicle is around current lap in metres – could + // be negative if line hasn’t been crossed yet + float m_totalDistance; // Total distance travelled in session in metres – could + // be negative if line hasn’t been crossed yet + float m_safetyCarDelta; // Delta in seconds for safety car + uint8 m_carPosition; // Car race position + uint8 m_currentLapNum; // Current lap number + uint8 m_pitStatus; // 0 = none, 1 = pitting, 2 = in pit area + uint8 m_numPitStops; // Number of pit stops taken in this race + uint8 m_sector; // 0 = sector1, 1 = sector2, 2 = sector3 + uint8 m_currentLapInvalid; // Current lap invalid - 0 = valid, 1 = invalid + uint8 m_penalties; // Accumulated time penalties in seconds to be added + uint8 m_warnings; // Accumulated number of warnings issued + uint8 m_numUnservedDriveThroughPens; // Num drive through pens left to serve + uint8 m_numUnservedStopGoPens; // Num stop go pens left to serve + uint8 m_gridPosition; // Grid position the vehicle started the race in + uint8 m_driverStatus; // Status of driver - 0 = in garage, 1 = flying lap + // 2 = in lap, 3 = out lap, 4 = on track + uint8 m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active + // 3 = finished, 4 = didnotfinish, 5 = disqualified + // 6 = not classified, 7 = retired + uint8 m_pitLaneTimerActive; // Pit lane timing, 0 = inactive, 1 = active + uint16 m_pitLaneTimeInLaneInMS; // If active, the current time spent in the pit lane in ms + uint16 m_pitStopTimerInMS; // Time of the actual pit stop in ms + uint8 m_pitStopShouldServePen; // Whether the car should serve a penalty at this stop +}; + + +struct PacketLapData +{ + PacketHeader m_header; // Header + + LapData m_lapData[22]; // Lap data for all cars on track + + uint8 m_timeTrialPBCarIdx; // Index of Personal Best car in time trial (255 if invalid) + uint8 m_timeTrialRivalCarIdx; // Index of Rival car in time trial (255 if invalid) +}; + + +Event Packet + +This packet gives details of events that happen during the course of a session. + +Frequency: When the event occurs +Size: 40 bytes +Version: 1 + +// The event details packet is different for each type of event. +// Make sure only the correct type is interpreted. +union EventDataDetails +{ + struct + { + uint8 vehicleIdx; // Vehicle index of car achieving fastest lap + float lapTime; // Lap time is in seconds + } FastestLap; + + struct + { + uint8 vehicleIdx; // Vehicle index of car retiring + } Retirement; + + struct + { + uint8 vehicleIdx; // Vehicle index of team mate + } TeamMateInPits; + + struct + { + uint8 vehicleIdx; // Vehicle index of the race winner + } RaceWinner; + + struct + { + uint8 penaltyType; // Penalty type – see Appendices + uint8 infringementType; // Infringement type – see Appendices + uint8 vehicleIdx; // Vehicle index of the car the penalty is applied to + uint8 otherVehicleIdx; // Vehicle index of the other car involved + uint8 time; // Time gained, or time spent doing action in seconds + uint8 lapNum; // Lap the penalty occurred on + uint8 placesGained; // Number of places gained by this + } Penalty; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle triggering speed trap + float speed; // Top speed achieved in kilometres per hour + uint8 isOverallFastestInSession; // Overall fastest speed in session = 1, otherwise 0 + uint8 isDriverFastestInSession; // Fastest speed for driver in session = 1, otherwise 0 + uint8 fastestVehicleIdxInSession;// Vehicle index of the vehicle that is the fastest +// in this session + float fastestSpeedInSession; // Speed of the vehicle that is the fastest + // in this session + } SpeedTrap; + + struct + { + uint8 numLights; // Number of lights showing + } StartLIghts; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle serving drive through + } DriveThroughPenaltyServed; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle serving stop go + } StopGoPenaltyServed; + + struct + { + uint32 flashbackFrameIdentifier; // Frame identifier flashed back to + float flashbackSessionTime; // Session time flashed back to + } Flashback; + + struct + { + uint32 m_buttonStatus; // Bit flags specifying which buttons are being pressed + // currently - see appendices + } Buttons; +}; + +struct PacketEventData +{ + PacketHeader m_header; // Header + + uint8 m_eventStringCode[4]; // Event string code, see below + EventDataDetails m_eventDetails; // Event details - should be interpreted differently + // for each type +}; + + +Event String Codes + +Event Code Description +Session Started “SSTA” Sent when the session starts +Session Ended “SEND” Sent when the session ends +Fastest Lap “FTLP” When a driver achieves the fastest lap +Retirement “RTMT” When a driver retires +DRS enabled “DRSE” Race control have enabled DRS +DRS disabled “DRSD” Race control have disabled DRS +Team mate in pits “TMPT” Your team mate has entered the pits +Chequered flag “CHQF” The chequered flag has been waved +Race Winner “RCWN” The race winner is announced +Penalty Issued “PENA” A penalty has been issued – details in event +Speed Trap Triggered “SPTP” Speed trap has been triggered by fastest speed +Start lights “STLG” Start lights – number shown +Lights out “LGOT” Lights out +Drive through served “DTSV” Drive through penalty served +Stop go served “SGSV” Stop go penalty served +Flashback “FLBK” Flashback activated +Button status “BUTN” Button status changed + + +Participants Packet + +This is a list of participants in the race. If the vehicle is controlled by AI, then the name will be the driver name. If this is a multiplayer game, the names will be the Steam Id on PC, or the LAN name if appropriate. + +N.B. on Xbox One, the names will always be the driver name, on PS4 the name will be the LAN name if playing a LAN game, otherwise it will be the driver name. + +The array should be indexed by vehicle index. + +Frequency: Every 5 seconds +Size: 1257 bytes +Version: 1 + +struct ParticipantData +{ + uint8 m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + uint8 m_driverId; // Driver id - see appendix, 255 if network human + uint8 m_networkId; // Network id – unique identifier for network players + uint8 m_teamId; // Team id - see appendix + uint8 m_myTeam; // My team flag – 1 = My Team, 0 = otherwise + uint8 m_raceNumber; // Race number of the car + uint8 m_nationality; // Nationality of the driver + char m_name[48]; // Name of participant in UTF-8 format – null terminated + // Will be truncated with … (U+2026) if too long + uint8 m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public +}; + +struct PacketParticipantsData +{ + PacketHeader m_header; // Header + + uint8 m_numActiveCars; // Number of active cars in the data – should match number of + // cars on HUD + ParticipantData m_participants[22]; +}; + + +Car Setups Packet + +This packet details the car setups for each vehicle in the session. Note that in multiplayer games, other player cars will appear as blank, you will only be able to see your car setup and AI cars. + +Frequency: 2 per second +Size: 1102 bytes +Version: 1 + +struct CarSetupData +{ + uint8 m_frontWing; // Front wing aero + uint8 m_rearWing; // Rear wing aero + uint8 m_onThrottle; // Differential adjustment on throttle (percentage) + uint8 m_offThrottle; // Differential adjustment off throttle (percentage) + float m_frontCamber; // Front camber angle (suspension geometry) + float m_rearCamber; // Rear camber angle (suspension geometry) + float m_frontToe; // Front toe angle (suspension geometry) + float m_rearToe; // Rear toe angle (suspension geometry) + uint8 m_frontSuspension; // Front suspension + uint8 m_rearSuspension; // Rear suspension + uint8 m_frontAntiRollBar; // Front anti-roll bar + uint8 m_rearAntiRollBar; // Front anti-roll bar + uint8 m_frontSuspensionHeight; // Front ride height + uint8 m_rearSuspensionHeight; // Rear ride height + uint8 m_brakePressure; // Brake pressure (percentage) + uint8 m_brakeBias; // Brake bias (percentage) + float m_rearLeftTyrePressure; // Rear left tyre pressure (PSI) + float m_rearRightTyrePressure; // Rear right tyre pressure (PSI) + float m_frontLeftTyrePressure; // Front left tyre pressure (PSI) + float m_frontRightTyrePressure; // Front right tyre pressure (PSI) + uint8 m_ballast; // Ballast + float m_fuelLoad; // Fuel load +}; + +struct PacketCarSetupData +{ + PacketHeader m_header; // Header + + CarSetupData m_carSetups[22]; +}; + + +Car Telemetry Packet + +This packet details telemetry for all the cars in the race. It details various values that would be recorded on the car such as speed, throttle application, DRS etc. Note that the rev light configurations are presented separately as well and will mimic real life driver preferences. + +Frequency: Rate as specified in menus +Size: 1347 bytes +Version: 1 + +struct CarTelemetryData +{ + uint16 m_speed; // Speed of car in kilometres per hour + float m_throttle; // Amount of throttle applied (0.0 to 1.0) + float m_steer; // Steering (-1.0 (full lock left) to 1.0 (full lock right)) + float m_brake; // Amount of brake applied (0.0 to 1.0) + uint8 m_clutch; // Amount of clutch applied (0 to 100) + int8 m_gear; // Gear selected (1-8, N=0, R=-1) + uint16 m_engineRPM; // Engine RPM + uint8 m_drs; // 0 = off, 1 = on + uint8 m_revLightsPercent; // Rev lights indicator (percentage) + uint16 m_revLightsBitValue; // Rev lights (bit 0 = leftmost LED, bit 14 = rightmost LED) + uint16 m_brakesTemperature[4]; // Brakes temperature (celsius) + uint8 m_tyresSurfaceTemperature[4]; // Tyres surface temperature (celsius) + uint8 m_tyresInnerTemperature[4]; // Tyres inner temperature (celsius) + uint16 m_engineTemperature; // Engine temperature (celsius) + float m_tyresPressure[4]; // Tyres pressure (PSI) + uint8 m_surfaceType[4]; // Driving surface, see appendices +}; + +struct PacketCarTelemetryData +{ + PacketHeader m_header; // Header + + CarTelemetryData m_carTelemetryData[22]; + + uint8 m_mfdPanelIndex; // Index of MFD panel open - 255 = MFD closed + // Single player, race – 0 = Car setup, 1 = Pits + // 2 = Damage, 3 = Engine, 4 = Temperatures + // May vary depending on game mode + uint8 m_mfdPanelIndexSecondaryPlayer; // See above + int8 m_suggestedGear; // Suggested gear for the player (1-8) + // 0 if no gear suggested +}; + + +Car Status Packet + +This packet details car statuses for all the cars in the race. + +Frequency: Rate as specified in menus +Size: 1058 bytes +Version: 1 + +struct CarStatusData +{ + uint8 m_tractionControl; // Traction control - 0 = off, 1 = medium, 2 = full + uint8 m_antiLockBrakes; // 0 (off) - 1 (on) + uint8 m_fuelMix; // Fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max + uint8 m_frontBrakeBias; // Front brake bias (percentage) + uint8 m_pitLimiterStatus; // Pit limiter status - 0 = off, 1 = on + float m_fuelInTank; // Current fuel mass + float m_fuelCapacity; // Fuel capacity + float m_fuelRemainingLaps; // Fuel remaining in terms of laps (value on MFD) + uint16 m_maxRPM; // Cars max RPM, point of rev limiter + uint16 m_idleRPM; // Cars idle RPM + uint8 m_maxGears; // Maximum number of gears + uint8 m_drsAllowed; // 0 = not allowed, 1 = allowed + uint16 m_drsActivationDistance; // 0 = DRS not available, non-zero - DRS will be available + // in [X] metres + uint8 m_actualTyreCompound; // F1 Modern - 16 = C5, 17 = C4, 18 = C3, 19 = C2, 20 = C1 + // 7 = inter, 8 = wet + // F1 Classic - 9 = dry, 10 = wet + // F2 – 11 = super soft, 12 = soft, 13 = medium, 14 = hard + // 15 = wet + uint8 m_visualTyreCompound; // F1 visual (can be different from actual compound) + // 16 = soft, 17 = medium, 18 = hard, 7 = inter, 8 = wet + // F1 Classic – same as above + // F2 ‘19, 15 = wet, 19 – super soft, 20 = soft + // 21 = medium , 22 = hard + uint8 m_tyresAgeLaps; // Age in laps of the current set of tyres + int8 m_vehicleFiaFlags; // -1 = invalid/unknown, 0 = none, 1 = green + // 2 = blue, 3 = yellow, 4 = red + float m_ersStoreEnergy; // ERS energy store in Joules + uint8 m_ersDeployMode; // ERS deployment mode, 0 = none, 1 = medium + // 2 = hotlap, 3 = overtake + float m_ersHarvestedThisLapMGUK; // ERS energy harvested this lap by MGU-K + float m_ersHarvestedThisLapMGUH; // ERS energy harvested this lap by MGU-H + float m_ersDeployedThisLap; // ERS energy deployed this lap + uint8 m_networkPaused; // Whether the car is paused in a network game +}; + +struct PacketCarStatusData +{ + PacketHeader m_header; // Header + + CarStatusData m_carStatusData[22]; +}; + +Final Classification Packet + +This packet details the final classification at the end of the race, and the data will match with the post race results screen. This is especially useful for multiplayer games where it is not always possible to send lap times on the final frame because of network delay. + +Frequency: Once at the end of a race +Size: 1015 bytes +Version: 1 + +struct FinalClassificationData +{ + uint8 m_position; // Finishing position + uint8 m_numLaps; // Number of laps completed + uint8 m_gridPosition; // Grid position of the car + uint8 m_points; // Number of points scored + uint8 m_numPitStops; // Number of pit stops made + uint8 m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active + // 3 = finished, 4 = didnotfinish, 5 = disqualified + // 6 = not classified, 7 = retired + uint32 m_bestLapTimeInMS; // Best lap time of the session in milliseconds + double m_totalRaceTime; // Total race time in seconds without penalties + uint8 m_penaltiesTime; // Total penalties accumulated in seconds + uint8 m_numPenalties; // Number of penalties applied to this driver + uint8 m_numTyreStints; // Number of tyres stints up to maximum + uint8 m_tyreStintsActual[8]; // Actual tyres used by this driver + uint8 m_tyreStintsVisual[8]; // Visual tyres used by this driver + uint8 m_tyreStintsEndLaps[8]; // The lap number stints end on +}; + +struct PacketFinalClassificationData +{ + PacketHeader m_header; // Header + + uint8 m_numCars; // Number of cars in the final classification + FinalClassificationData m_classificationData[22]; +}; + +Lobby Info Packet + +This packet details the players currently in a multiplayer lobby. It details each player’s selected car, any AI involved in the game and also the ready status of each of the participants. + +Frequency: Two every second when in the lobby +Size: 1191 bytes +Version: 1 + +struct LobbyInfoData +{ + uint8 m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + uint8 m_teamId; // Team id - see appendix (255 if no team currently selected) + uint8 m_nationality; // Nationality of the driver + char m_name[48]; // Name of participant in UTF-8 format – null terminated + // Will be truncated with ... (U+2026) if too long + uint8 m_carNumber; // Car number of the player + uint8 m_readyStatus; // 0 = not ready, 1 = ready, 2 = spectating +}; + +struct PacketLobbyInfoData +{ + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_numPlayers; // Number of players in the lobby data + LobbyInfoData m_lobbyPlayers[22]; +}; + +Car Damage Packet + +This packet details car damage parameters for all the cars in the race. + +Frequency: 2 per second +Size: 948 bytes +Version: 1 + +struct CarDamageData +{ + float m_tyresWear[4]; // Tyre wear (percentage) + uint8 m_tyresDamage[4]; // Tyre damage (percentage) + uint8 m_brakesDamage[4]; // Brakes damage (percentage) + uint8 m_frontLeftWingDamage; // Front left wing damage (percentage) + uint8 m_frontRightWingDamage; // Front right wing damage (percentage) + uint8 m_rearWingDamage; // Rear wing damage (percentage) + uint8 m_floorDamage; // Floor damage (percentage) + uint8 m_diffuserDamage; // Diffuser damage (percentage) + uint8 m_sidepodDamage; // Sidepod damage (percentage) + uint8 m_drsFault; // Indicator for DRS fault, 0 = OK, 1 = fault + uint8 m_ersFault; // Indicator for ERS fault, 0 = OK, 1 = fault + uint8 m_gearBoxDamage; // Gear box damage (percentage) + uint8 m_engineDamage; // Engine damage (percentage) + uint8 m_engineMGUHWear; // Engine wear MGU-H (percentage) + uint8 m_engineESWear; // Engine wear ES (percentage) + uint8 m_engineCEWear; // Engine wear CE (percentage) + uint8 m_engineICEWear; // Engine wear ICE (percentage) + uint8 m_engineMGUKWear; // Engine wear MGU-K (percentage) + uint8 m_engineTCWear; // Engine wear TC (percentage) + uint8 m_engineBlown; // Engine blown, 0 = OK, 1 = fault + uint8 m_engineSeized; // Engine seized, 0 = OK, 1 = fault +} + +struct PacketCarDamageData +{ + PacketHeader m_header; // Header + + CarDamageData m_carDamageData[22]; +}; + +Session History Packet + +This packet contains lap times and tyre usage for the session. This packet works slightly differently to other packets. To reduce CPU and bandwidth, each packet relates to a specific vehicle and is sent every 1/20 s, and the vehicle being sent is cycled through. Therefore in a 20 car race you should receive an update for each vehicle at least once per second. + +Note that at the end of the race, after the final classification packet has been sent, a final bulk update of all the session histories for the vehicles in that session will be sent. + +Frequency: 20 per second but cycling through cars +Size: 1155 bytes +Version: 1 + +struct LapHistoryData +{ + uint32 m_lapTimeInMS; // Lap time in milliseconds + uint16 m_sector1TimeInMS; // Sector 1 time in milliseconds + uint16 m_sector2TimeInMS; // Sector 2 time in milliseconds + uint16 m_sector3TimeInMS; // Sector 3 time in milliseconds + uint8 m_lapValidBitFlags; // 0x01 bit set-lap valid, 0x02 bit set-sector 1 valid + // 0x04 bit set-sector 2 valid, 0x08 bit set-sector 3 valid +}; + +struct TyreStintHistoryData +{ + uint8 m_endLap; // Lap the tyre usage ends on (255 of current tyre) + uint8 m_tyreActualCompound; // Actual tyres used by this driver + uint8 m_tyreVisualCompound; // Visual tyres used by this driver +}; + +struct PacketSessionHistoryData +{ + PacketHeader m_header; // Header + + uint8 m_carIdx; // Index of the car this lap data relates to + uint8 m_numLaps; // Num laps in the data (including current partial lap) + uint8 m_numTyreStints; // Number of tyre stints in the data + + uint8 m_bestLapTimeLapNum; // Lap the best lap time was achieved on + uint8 m_bestSector1LapNum; // Lap the best Sector 1 time was achieved on + uint8 m_bestSector2LapNum; // Lap the best Sector 2 time was achieved on + uint8 m_bestSector3LapNum; // Lap the best Sector 3 time was achieved on + + LapHistoryData m_lapHistoryData[100]; // 100 laps of data max + TyreStintHistoryData m_tyreStintsHistoryData[8]; +}; +  +Restricted data (Your Telemetry setting) + +There is some data in the UDP that you may not want other players seeing if you are in a multiplayer game. This is controlled by the “Your Telemetry” setting in the Telemetry options. The options are: + +• Restricted (Default) – other players viewing the UDP data will not see values for your car +• Public – all other players can see all the data for your car +• Show online ID – this additional option allows other players to view your online ID / gamertag in their UDP output. + +Note: You can always see the data for the car you are driving regardless of the setting. + +The following data items are set to zero if the player driving the car in question has their “Your Telemetry” set to “Restricted”: + +Car status packet + +• m_fuelInTank +• m_fuelCapacity +• m_fuelMix +• m_fuelRemainingLaps +• m_frontBrakeBias +• m_ersDeployMode +• m_ersStoreEnergy +• m_ersDeployedThisLap +• m_ersHarvestedThisLapMGUK +• m_ersHarvestedThisLapMGUH + +Car damage packet + +• m_frontLeftWingDamage +• m_frontRightWingDamage +• m_rearWingDamage +• m_floorDamage +• m_diffuserDamage +• m_sidepodDamage +• m_engineDamage +• m_gearBoxDamage +• m_tyresWear (All four wheels) +• m_tyresDamage (All four wheels) +• m_brakesDamage (All four wheels) +• m_drsFault +• m_engineMGUHWear +• m_engineESWear +• m_engineCEWear +• m_engineICEWear +• m_engineMGUKWear +• m_engineTCWear + +To allow other players to view your online ID in their UDP output during an online session, you must enable the “Show online ID / gamertags” option. Selecting this will bring up a confirmation box that must be confirmed before this option is enabled. + +Please note that all options can be changed during a game session and will take immediate effect. + + +FAQS + +How do I enable the UDP Telemetry Output? +In F1 22, UDP telemetry output is controlled via the in-game menus. To enable this, enter the options menu from the main menu (triangle / Y), then enter the settings menu - the UDP option will be at the bottom of the list. From there you will be able to enable / disable the UDP output, configure the IP address and port for the receiving application, toggle broadcast mode and set the send rate. Broadcast mode transmits the data across the network subnet to allow multiple devices on the same subnet to be able to receive this information. When using broadcast mode it is not necessary to set a target IP address, just a target port for applications to listen on. + +Advanced PC Users: You can additionally edit the game’s configuration XML file to configure UDP output. The file is located here (after an initial boot of the game): + +...\Documents\My Games\\hardwaresettings\hardware_settings_config.xml + +You should see the tag: + + + ... + + ... + + +Here you can set the values manually. Note that any changes made within the game when it is running will overwrite any changes made manually. Note the enabled flag is now a state. + + +What has changed since last year? +F1 22 sees the following changes to the UDP specification: + +• Custom UDP actions have been added to the button array so you can assign up to 12 custom controller button to come through UDP +• Personal best and rival car indices added to lap data for time trial +• Added game mode id to the session packet – see appendix for list +• Added ERS and engine damage states to damage packet +• End lap added to tyre stint data in final classification packet +• Added fastest driver and speed to speed trap event, also fixing a bug with fastest speed +• Player’s online name is now displayed in the Participant packet when enabled +• Added ruleset, time of day and session length to the session packet + +What is the order of the wheel arrays? +All wheel arrays are in the following order: + + 0 – Rear Left (RL) + 1 – Rear Right (RR) + 2 – Front Left (FL) + 3 – Front Right (FR) + + +Do the vehicle indices change? +During a session, each car is assigned a vehicle index. This will not change throughout the session and all the arrays that are sent use this vehicle index to dereference the correct piece of data. + + +What encoding format is used? +All values are encoded using Little Endian format. + + +Are the data structures packed? +Yes, all data is packed, there is no padding used. + + +Will there always be 20 cars in the data structures? +No, certain game modes or car classes allow 22 cars to be present on the grid. This means that all previous places where 20 cars were used, 22 is now the maximum. Note that if your UDP format is 2019, 2018 or legacy and you are in “My Team” career mode, no UDP output will be produced because of this limitation. + +There is still the data item called m_numActiveCars in the participants packet which tells you how many cars are active in the race. However, you should check the individual result status of each car in the lap data to see if that car is actively providing data. If it is not “Invalid” or “Inactive” then the corresponding vehicle index has valid data. + + +How often are updated packets sent? +For the packets which get updated at “Rate as specified in the menus” you can be guaranteed that on the frame that these get sent they will all get sent together and will never be separated across frames. This of course relies on the reliability of your network as to whether they are received correctly as everything is sent via UDP. Other packets that get sent at specific rates can arrive on any frame. +If you are connected to the game when it starts transmitting the first frame will contain the following information to help initialise data structures on the receiving application: +Packets sent on Frame 1: (All packets sent on this frame have “Session timestamp” 0.000) +• Session +• Participants +• Car Setups +• Lap Data +• Motion Data +• Car Telemetry +• Car Status +• Car Damage +As an example, assuming that you are running at 60Hz with 60Hz update rate selected in the menus then you would expect to see the following packets and timestamps: +Packets sent on Frame 2: (All packets sent on this frame have “Session timestamp” 0.016) +• Lap Data +• Motion Data +• Car Telemetry +• Car Status +… +Packets sent on Frame 31: (All packets sent on this frame have “Session timestamp” 0.5) +• Session (since 2 updates per second) +• Car Setups (since 2 updates per second) +• Lap Data +• Motion Data +• Car Telemetry +• Car Status +• Car Damage (since 2 updates per second) + +Will my old app still work with F1 22? +F1 22 uses a new format for the UDP data. However, earlier formats of the data are still supported so that most older apps implemented using the previous data formats should work with little or no change from the developer. To use the old formats, please enter the UDP options menu and set “UDP Format” to either “2021”, “2020”, “2019”, “2018” or “Legacy” (for F1 2017 and earlier). +Specifications for the olders formats can be seen here: + +• Legacy (2017 and earlier) - http://forums.codemasters.com/discussion/53139/f1-2017-d-box-and-udp-output-specification/p1. +• F1 2018 - https://forums.codemasters.com/topic/30601-f1-2018-udp-specification/ +• F1 2019 - https://forums.codemasters.com/topic/44592-f1-2019-udp-specification/ +• F1 2020 - https://forums.codemasters.com/topic/54423-f1%C2%AE-2020-udp-specification/ +• F1 2021 - https://forums.codemasters.com/topic/80231-f1-2021-udp-specification + +How do I enable D-BOX output? +D-BOX output is currently supported on the PC platform. In F1 22, the D-BOX activation can be controlled via the menus. Navigate to Game Options->Settings->UDP Telemetry Settings->D-BOX to activate this on your system. + +Advanced PC Users: It is possible to control D-BOX by editing the games’ configuration XML file. The file is located here (after an initial boot of the game): + +...\Documents\My Games\\hardwaresettings\hardware_settings_config.xml + +You should see the tag: + + + + ... + + +Set the “enabled” value to “true” to allow the game to output to your D-BOX motion platform. Note that any changes made within the game when it is running will overwrite any changes made manually. + + +How can I disable in-game support for LED device? +The F1 game has native support for some of the basic features supported by some external LED devices, such as the Leo Bodnar SLI Pro and the Fanatec steering wheels. To avoid conflicts between the game’s implementation and any third-party device managers on the PC platform it may be necessary to disable the native support. This is done using the following led_display flags in the hardware_settings_config.xml. The file is located here (after an initial boot of the game): + +...\Documents\My Games\\hardwaresettings\hardware_settings_config.xml + +The flags to enabled/disable LED output are: + + + +The sliProNativeSupport flag controls the output to SLI Pro devices. The fanatecNativeSupport flag controls the output to Fanatec (and some related) steering wheel LEDs. Set the values for any of these to “false” to disable them and avoid conflicts with your own device manager. + +Please note there is an additional flag to manually control the LED brightness on the SLI Pro: + + + +This option (using value in the range 0-255) will be ignored when setting the sliProNativeSupport flag to “false”. + +Also note it is now possible to edit these values on the fly via the Game Options->Settings->UDP Telemetry Settings menu. + + +Can I configure the UDP output using an XML File? +PC users can edit the game’s configuration XML file to configure UDP output. The file is located here (after an initial boot of the game): + + ...\Documents\My Games\\hardwaresettings\hardware_settings_config.xml + +You should see the tag: + + + ... + + ... + + +Here you can set the values manually. Note that any changes made within the game when it is running will overwrite any changes made manually. +  +Appendices + +Here are the values used for some of the parameters in the UDP data output. + +Team IDs + +ID Team ID Team +0 Mercedes 106 Prema ‘21 +1 Ferrari 107 Uni-Virtuosi ‘21 +2 Red Bull Racing 108 Carlin ‘21 +3 Williams 109 Hitech ‘21 +4 Aston Martin 110 Art GP ‘21 +5 Alpine 111 MP Motorsport ‘21 +6 Alpha Tauri 112 Charouz ‘21 +7 Haas 113 Dams ‘21 +8 McLaren 114 Campos ‘21 +9 Alfa Romeo 115 BWT ‘21 +85 Mercedes 2020 116 Trident ‘21 +86 Ferrari 2020 117 Mercedes AMG GT Black Series +87 Red Bull 2020 118 Prema ‘22 +88 Williams 2020 119 Virtuosi ‘22 +89 Racing Point 2020 120 Carlin ‘22 +90 Renault 2020 121 Hitech ‘22 +91 Alpha Tauri 2020 122 Art GP ‘22 +92 Haas 2020 123 MP Motorsport ‘22 +93 McLaren 2020 124 Charouz ‘22 +94 Alfa Romeo 2020 125 Dams ‘22 +95 Aston Martin DB11 V12 126 Campos ‘22 +96 Aston Martin Vantage F1 Edition 127 Van Amersfoort Racing ‘22 +97 Aston Martin Vantage Safety Car 128 Trident ‘22 +98 Ferrari F8 Tributo +99 Ferrari Roma +100 McLaren 720S +101 McLaren Artura +102 Mercedes AMG GT Black Series Safety Car +103 Mercedes AMG GTR Pro +104 F1 Custom Team +  +Driver IDs + +ID Driver ID Driver ID Driver +0 Carlos Sainz 56 Louis Delétraz 115 Theo Pourchaire +1 Daniil Kvyat 57 Antonio Fuoco 116 Richard Verschoor +2 Daniel Ricciardo 58 Charles Leclerc 117 Lirim Zendeli +3 Fernando Alonso 59 Pierre Gasly 118 David Beckmann +4 Felipe Massa 62 Alexander Albon 121 Alessio Deledda +6 Kimi Räikkönen 63 Nicholas Latifi 122 Bent Viscaal +7 Lewis Hamilton 64 Dorian Boccolacci 123 Enzo Fittipaldi +9 Max Verstappen 65 Niko Kari 125 Mark Webber +10 Nico Hulkenburg 66 Roberto Merhi 126 Jacques Villeneuve +11 Kevin Magnussen 67 Arjun Maini 127 Jake Hughes +12 Romain Grosjean 68 Alessio Lorandi 128 Frederik Vesti +13 Sebastian Vettel 69 Ruben Meijer 129 Olli Caldwell +14 Sergio Perez 70 Rashid Nair 130 Logan Sargeant +15 Valtteri Bottas 71 Jack Tremblay 131 Cem Bolukbasi +17 Esteban Ocon 72 Devon Butler 132 Ayuma Iwasa +19 Lance Stroll 73 Lukas Weber 133 Clement Novolak +20 Arron Barnes 74 Antonio Giovinazzi 134 Dennis Hauger +21 Martin Giles 75 Robert Kubica 135 Calan Williams +22 Alex Murray 76 Alain Prost 136 Jack Doohan +23 Lucas Roth 77 Ayrton Senna 137 Amaury Cordeel +24 Igor Correia 78 Nobuharu Matsushita 138 Mika Hakkinen +25 Sophie Levasseur 79 Nikita Mazepin +26 Jonas Schiffer 80 Guanya Zhou +27 Alain Forest 81 Mick Schumacher +28 Jay Letourneau 82 Callum Ilott +29 Esto Saari 83 Juan Manuel Correa +30 Yasar Atiyeh 84 Jordan King +31 Callisto Calabresi 85 Mahaveer Raghunathan +32 Naota Izum 86 Tatiana Calderon +33 Howard Clarke 87 Anthoine Hubert +34 Wilheim Kaufmann 88 Guiliano Alesi +35 Marie Laursen 89 Ralph Boschung +36 Flavio Nieves 90 Michael Schumacher +37 Peter Belousov 91 Dan Ticktum +38 Klimek Michalski 92 Marcus Armstrong +39 Santiago Moreno 93 Christian Lundgaard +40 Benjamin Coppens 94 Yuki Tsunoda +41 Noah Visser 95 Jehan Daruvala +42 Gert Waldmuller 96 Gulherme Samaia +43 Julian Quesada 97 Pedro Piquet +44 Daniel Jones 98 Felipe Drugovich +45 Artem Markelov 99 Robert Schwartzman +46 Tadasuke Makino 100 Roy Nissany +47 Sean Gelael 101 Marino Sato +48 Nyck De Vries 102 Aidan Jackson +49 Jack Aitken 103 Casper Akkerman +50 George Russell 109 Jenson Button +51 Maximilian Günther 110 David Coulthard +52 Nirei Fukuzumi 111 Nico Rosberg +53 Luca Ghiotto 112 Oscar Piastri +54 Lando Norris 113 Liam Lawson +55 Sérgio Sette Câmara 114 Juri Vips + + +Track IDs + +ID Track +0 Melbourne +1 Paul Ricard +2 Shanghai +3 Sakhir (Bahrain) +4 Catalunya +5 Monaco +6 Montreal +7 Silverstone +8 Hockenheim +9 Hungaroring +10 Spa +11 Monza +12 Singapore +13 Suzuka +14 Abu Dhabi +15 Texas +16 Brazil +17 Austria +18 Sochi +19 Mexico +20 Baku (Azerbaijan) +21 Sakhir Short +22 Silverstone Short +23 Texas Short +24 Suzuka Short +25 Hanoi +26 Zandvoort +27 Imola +28 Portimão +29 Jeddah +30 Miami + + +  +Nationality IDs + +ID Nationality ID Nationality ID Nationality +1 American 31 Greek 61 Paraguayan +2 Argentinean 32 Guatemalan 62 Peruvian +3 Australian 33 Honduran 63 Polish +4 Austrian 34 Hong Konger 64 Portuguese +5 Azerbaijani 35 Hungarian 65 Qatari +6 Bahraini 36 Icelander 66 Romanian +7 Belgian 37 Indian 67 Russian +8 Bolivian 38 Indonesian 68 Salvadoran +9 Brazilian 39 Irish 69 Saudi +10 British 40 Israeli 70 Scottish +11 Bulgarian 41 Italian 71 Serbian +12 Cameroonian 42 Jamaican 72 Singaporean +13 Canadian 43 Japanese 73 Slovakian +14 Chilean 44 Jordanian 74 Slovenian +15 Chinese 45 Kuwaiti 75 South Korean +16 Colombian 46 Latvian 76 South African +17 Costa Rican 47 Lebanese 77 Spanish +18 Croatian 48 Lithuanian 78 Swedish +19 Cypriot 49 Luxembourger 79 Swiss +20 Czech 50 Malaysian 80 Thai +21 Danish 51 Maltese 81 Turkish +22 Dutch 52 Mexican 82 Uruguayan +23 Ecuadorian 53 Monegasque 83 Ukrainian +24 English 54 New Zealander 84 Venezuelan +25 Emirian 55 Nicaraguan 85 Barbadian +26 Estonian 56 Northern Irish 86 Welsh +27 Finnish 57 Norwegian 87 Vietnamese +28 French 58 Omani +29 German 59 Pakistani +30 Ghanaian 60 Panamanian + + + +Game Mode IDs + +ID Team +0 Event Mode +3 Grand Prix +5 Time Trial +6 Splitscreen +7 Online Custom +8 Online League +11 Career Invitational +12 Championship Invitational +13 Championship +14 Online Championship +15 Online Weekly Event +19 Career ‘22 +20 Career ’22 Online +127 Benchmark + +Ruleset IDs + +ID Team +0 Practice & Qualifying +1 Race +2 Time Trial +4 Time Attack +6 Checkpoint Challenge +8 Autocross +9 Drift +10 Average Speed Zone +11 Rival Duel + +Surface types + +These types are from physics data and show what type of contact each wheel is experiencing. + +ID Surface +0 Tarmac +1 Rumble strip +2 Concrete +3 Rock +4 Gravel +5 Mud +6 Sand +7 Grass +8 Water +9 Cobblestone +10 Metal +11 Ridged + +Button flags + +These flags are used in the telemetry packet to determine if any buttons are being held on the controlling device. If the value below logical ANDed with the button status is set then the corresponding button is being held. + +Bit Flag Button +0x00000001 Cross or A +0x00000002 Triangle or Y +0x00000004 Circle or B +0x00000008 Square or X +0x00000010 D-pad Left +0x00000020 D-pad Right +0x00000040 D-pad Up +0x00000080 D-pad Down +0x00000100 Options or Menu +0x00000200 L1 or LB +0x00000400 R1 or RB +0x00000800 L2 or LT +0x00001000 R2 or RT +0x00002000 Left Stick Click +0x00004000 Right Stick Click +0x00008000 Right Stick Left +0x00010000 Right Stick Right +0x00020000 Right Stick Up +0x00040000 Right Stick Down +0x00080000 Special +0x00100000 UDP Action 1 +0x00200000 UDP Action 2 +0x00400000 UDP Action 3 +0x00800000 UDP Action 4 +0x01000000 UDP Action 5 +0x02000000 UDP Action 6 +0x04000000 UDP Action 7 +0x08000000 UDP Action 8 +0x10000000 UDP Action 9 +0x20000000 UDP Action 10 +0x40000000 UDP Action 11 +0x80000000 UDP Action 12 + +Penalty types + +ID Penalty meaning +0 Drive through +1 Stop Go +2 Grid penalty +3 Penalty reminder +4 Time penalty +5 Warning +6 Disqualified +7 Removed from formation lap +8 Parked too long timer +9 Tyre regulations +10 This lap invalidated +11 This and next lap invalidated +12 This lap invalidated without reason +13 This and next lap invalidated without reason +14 This and previous lap invalidated +15 This and previous lap invalidated without reason +16 Retired +17 Black flag timer + + +Infringement types + +ID Infringement meaning +0 Blocking by slow driving +1 Blocking by wrong way driving +2 Reversing off the start line +3 Big Collision +4 Small Collision +5 Collision failed to hand back position single +6 Collision failed to hand back position multiple +7 Corner cutting gained time +8 Corner cutting overtake single +9 Corner cutting overtake multiple +10 Crossed pit exit lane +11 Ignoring blue flags +12 Ignoring yellow flags +13 Ignoring drive through +14 Too many drive throughs +15 Drive through reminder serve within n laps +16 Drive through reminder serve this lap +17 Pit lane speeding +18 Parked for too long +19 Ignoring tyre regulations +20 Too many penalties +21 Multiple warnings +22 Approaching disqualification +23 Tyre regulations select single +24 Tyre regulations select multiple +25 Lap invalidated corner cutting +26 Lap invalidated running wide +27 Corner cutting ran wide gained time minor +28 Corner cutting ran wide gained time significant +29 Corner cutting ran wide gained time extreme +30 Lap invalidated wall riding +31 Lap invalidated flashback used +32 Lap invalidated reset to track +33 Blocking the pitlane +34 Jump start +35 Safety car to car collision +36 Safety car illegal overtake +37 Safety car exceeding allowed pace +38 Virtual safety car exceeding allowed pace +39 Formation lap below allowed speed +40 Formation lap parking +41 Retired mechanical failure +42 Retired terminally damaged +43 Safety car falling too far back +44 Black flag timer +45 Unserved stop go penalty +46 Unserved drive through penalty +47 Engine component change +48 Gearbox change +49 Parc Fermé change +50 League grid penalty +51 Retry penalty +52 Illegal time gain +53 Mandatory pitstop +54 Attribute assigned diff --git a/GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2023_spec.h b/GamesDat/Telemetry/Sources/Formula1/Docs/F1_2023_spec.md similarity index 100% rename from GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2023_spec.h rename to GamesDat/Telemetry/Sources/Formula1/Docs/F1_2023_spec.md diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/CarDamageData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/CarDamageData.cs new file mode 100644 index 0000000..f966865 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/CarDamageData.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarDamageData + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresWear; // Tyre wear (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresDamage; // Tyre damage (percentage) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_brakesDamage; // Brakes damage (percentage) + + public byte m_frontLeftWingDamage; // Front left wing damage (percentage) + public byte m_frontRightWingDamage; // Front right wing damage (percentage) + public byte m_rearWingDamage; // Rear wing damage (percentage) + public byte m_floorDamage; // Floor damage (percentage) + public byte m_diffuserDamage; // Diffuser damage (percentage) + public byte m_sidepodDamage; // Sidepod damage (percentage) + public byte m_drsFault; // Indicator for DRS fault, 0 = OK, 1 = fault + public byte m_ersFault; // Indicator for ERS fault, 0 = OK, 1 = fault + public byte m_gearBoxDamage; // Gear box damage (percentage) + public byte m_engineDamage; // Engine damage (percentage) + public byte m_engineMGUHWear; // Engine wear MGU-H (percentage) + public byte m_engineESWear; // Engine wear ES (percentage) + public byte m_engineCEWear; // Engine wear CE (percentage) + public byte m_engineICEWear; // Engine wear ICE (percentage) + public byte m_engineMGUKWear; // Engine wear MGU-K (percentage) + public byte m_engineTCWear; // Engine wear TC (percentage) + public byte m_engineBlown; // Engine blown, 0 = OK, 1 = fault + public byte m_engineSeized; // Engine seized, 0 = OK, 1 = fault + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/CarMotionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/CarMotionData.cs new file mode 100644 index 0000000..635b2bb --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/CarMotionData.cs @@ -0,0 +1,30 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Motion data for one car in F1 2022. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarMotionData + { + public float m_worldPositionX; // World space X position + public float m_worldPositionY; // World space Y position + public float m_worldPositionZ; // World space Z position + public float m_worldVelocityX; // Velocity in world space X + public float m_worldVelocityY; // Velocity in world space Y + public float m_worldVelocityZ; // Velocity in world space Z + public short m_worldForwardDirX; // World space forward X direction (normalised) + public short m_worldForwardDirY; // World space forward Y direction (normalised) + public short m_worldForwardDirZ; // World space forward Z direction (normalised) + public short m_worldRightDirX; // World space right X direction (normalised) + public short m_worldRightDirY; // World space right Y direction (normalised) + public short m_worldRightDirZ; // World space right Z direction (normalised) + public float m_gForceLateral; // Lateral G-Force component + public float m_gForceLongitudinal; // Longitudinal G-Force component + public float m_gForceVertical; // Vertical G-Force component + public float m_yaw; // Yaw angle in radians + public float m_pitch; // Pitch angle in radians + public float m_roll; // Roll angle in radians + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/CarSetupData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/CarSetupData.cs new file mode 100644 index 0000000..3a9fd90 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/CarSetupData.cs @@ -0,0 +1,31 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarSetupData + { + public byte m_frontWing; // Front wing aero + public byte m_rearWing; // Rear wing aero + public byte m_onThrottle; // Differential adjustment on throttle (percentage) + public byte m_offThrottle; // Differential adjustment off throttle (percentage) + public float m_frontCamber; // Front camber angle (suspension geometry) + public float m_rearCamber; // Rear camber angle (suspension geometry) + public float m_frontToe; // Front toe angle (suspension geometry) + public float m_rearToe; // Rear toe angle (suspension geometry) + public byte m_frontSuspension; // Front suspension + public byte m_rearSuspension; // Rear suspension + public byte m_frontAntiRollBar; // Front anti-roll bar + public byte m_rearAntiRollBar; // Rear anti-roll bar + public byte m_frontSuspensionHeight; // Front ride height + public byte m_rearSuspensionHeight; // Rear ride height + public byte m_brakePressure; // Brake pressure (percentage) + public byte m_brakeBias; // Brake bias (percentage) + public float m_rearLeftTyrePressure; // Rear left tyre pressure (PSI) + public float m_rearRightTyrePressure; // Rear right tyre pressure (PSI) + public float m_frontLeftTyrePressure; // Front left tyre pressure (PSI) + public float m_frontRightTyrePressure; // Front right tyre pressure (PSI) + public byte m_ballast; // Ballast + public float m_fuelLoad; // Fuel load + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/CarStatusData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/CarStatusData.cs new file mode 100644 index 0000000..78b8bf5 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/CarStatusData.cs @@ -0,0 +1,42 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarStatusData + { + public byte m_tractionControl; // Traction control - 0 = off, 1 = medium, 2 = full + public byte m_antiLockBrakes; // 0 (off) - 1 (on) + public byte m_fuelMix; // Fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max + public byte m_frontBrakeBias; // Front brake bias (percentage) + public byte m_pitLimiterStatus; // Pit limiter status - 0 = off, 1 = on + public float m_fuelInTank; // Current fuel mass + public float m_fuelCapacity; // Fuel capacity + public float m_fuelRemainingLaps; // Fuel remaining in terms of laps (value on MFD) + public ushort m_maxRPM; // Cars max RPM, point of rev limiter + public ushort m_idleRPM; // Cars idle RPM + public byte m_maxGears; // Maximum number of gears + public byte m_drsAllowed; // 0 = not allowed, 1 = allowed + public ushort m_drsActivationDistance; // 0 = DRS not available, non-zero - DRS will be available in [X] metres + public byte m_actualTyreCompound; // F1 Modern - 16 = C5, 17 = C4, 18 = C3, 19 = C2, 20 = C1 + // 7 = inter, 8 = wet + // F1 Classic - 9 = dry, 10 = wet + // F2 – 11 = super soft, 12 = soft, 13 = medium, 14 = hard + // 15 = wet + public byte m_visualTyreCompound; // F1 visual (can be different from actual compound) + // 16 = soft, 17 = medium, 18 = hard, 7 = inter, 8 = wet + // F1 Classic – same as above + // F2 '19, 15 = wet, 19 – super soft, 20 = soft + // 21 = medium , 22 = hard + public byte m_tyresAgeLaps; // Age in laps of the current set of tyres + public sbyte m_vehicleFiaFlags; // -1 = invalid/unknown, 0 = none, 1 = green + // 2 = blue, 3 = yellow, 4 = red + public float m_ersStoreEnergy; // ERS energy store in Joules + public byte m_ersDeployMode; // ERS deployment mode, 0 = none, 1 = medium + // 2 = hotlap, 3 = overtake + public float m_ersHarvestedThisLapMGUK; // ERS energy harvested this lap by MGU-K + public float m_ersHarvestedThisLapMGUH; // ERS energy harvested this lap by MGU-H + public float m_ersDeployedThisLap; // ERS energy deployed this lap + public byte m_networkPaused; // Whether the car is paused in a network game + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/CarTelemetryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/CarTelemetryData.cs new file mode 100644 index 0000000..f2621f4 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/CarTelemetryData.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CarTelemetryData + { + public ushort m_speed; // Speed of car in kilometres per hour + public float m_throttle; // Amount of throttle applied (0.0 to 1.0) + public float m_steer; // Steering (-1.0 (full lock left) to 1.0 (full lock right)) + public float m_brake; // Amount of brake applied (0.0 to 1.0) + public byte m_clutch; // Amount of clutch applied (0 to 100) + public sbyte m_gear; // Gear selected (1-8, N=0, R=-1) + public ushort m_engineRPM; // Engine RPM + public byte m_drs; // 0 = off, 1 = on + public byte m_revLightsPercent; // Rev lights indicator (percentage) + public ushort m_revLightsBitValue; // Rev lights (bit 0 = leftmost LED, bit 14 = rightmost LED) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public ushort[] m_brakesTemperature; // Brakes temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresSurfaceTemperature; // Tyres surface temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_tyresInnerTemperature; // Tyres inner temperature (celsius) + + public ushort m_engineTemperature; // Engine temperature (celsius) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_tyresPressure; // Tyres pressure (PSI) + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_surfaceType; // Driving surface, see appendices + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/EventDataDetails.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/EventDataDetails.cs new file mode 100644 index 0000000..14cc9b2 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/EventDataDetails.cs @@ -0,0 +1,115 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + // Fastest Lap event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FastestLapData + { + public byte vehicleIdx; // Vehicle index of car achieving fastest lap + public float lapTime; // Lap time is in seconds + } + + // Retirement event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RetirementData + { + public byte vehicleIdx; // Vehicle index of car retiring + } + + // Team mate in pits event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TeamMateInPitsData + { + public byte vehicleIdx; // Vehicle index of team mate + } + + // Race winner event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RaceWinnerData + { + public byte vehicleIdx; // Vehicle index of the race winner + } + + // Penalty event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PenaltyData + { + public byte penaltyType; // Penalty type – see Appendices + public byte infringementType; // Infringement type – see Appendices + public byte vehicleIdx; // Vehicle index of the car the penalty is applied to + public byte otherVehicleIdx; // Vehicle index of the other car involved + public byte time; // Time gained, or time spent doing action in seconds + public byte lapNum; // Lap the penalty occurred on + public byte placesGained; // Number of places gained by this + } + + // Speed trap event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SpeedTrapData + { + public byte vehicleIdx; // Vehicle index of the vehicle triggering speed trap + public float speed; // Top speed achieved in kilometres per hour + public byte isOverallFastestInSession; // Overall fastest speed in session = 1, otherwise 0 + public byte isDriverFastestInSession; // Fastest speed for driver in session = 1, otherwise 0 + public byte fastestVehicleIdxInSession; // Vehicle index of the vehicle that is the fastest in this session + public float fastestSpeedInSession; // Speed of the vehicle that is the fastest in this session + } + + // Start lights event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct StartLightsData + { + public byte numLights; // Number of lights showing + } + + // Drive through penalty served event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DriveThroughPenaltyServedData + { + public byte vehicleIdx; // Vehicle index of the vehicle serving drive through + } + + // Stop go penalty served event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct StopGoPenaltyServedData + { + public byte vehicleIdx; // Vehicle index of the vehicle serving stop go + } + + // Flashback event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FlashbackData + { + public uint flashbackFrameIdentifier; // Frame identifier flashed back to + public float flashbackSessionTime; // Session time flashed back to + } + + // Buttons event details + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ButtonsData + { + public uint m_buttonStatus; // Bit flags specifying which buttons are being pressed currently - see appendices + } + + /// + /// Union of all possible event data details. + /// The event details packet is different for each type of event. + /// Make sure only the correct type is interpreted based on m_eventStringCode. + /// + [StructLayout(LayoutKind.Explicit)] + public struct EventDataDetails + { + [FieldOffset(0)] public FastestLapData FastestLap; + [FieldOffset(0)] public RetirementData Retirement; + [FieldOffset(0)] public TeamMateInPitsData TeamMateInPits; + [FieldOffset(0)] public RaceWinnerData RaceWinner; + [FieldOffset(0)] public PenaltyData Penalty; + [FieldOffset(0)] public SpeedTrapData SpeedTrap; + [FieldOffset(0)] public StartLightsData StartLights; + [FieldOffset(0)] public DriveThroughPenaltyServedData DriveThroughPenaltyServed; + [FieldOffset(0)] public StopGoPenaltyServedData StopGoPenaltyServed; + [FieldOffset(0)] public FlashbackData Flashback; + [FieldOffset(0)] public ButtonsData Buttons; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/FinalClassificationData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/FinalClassificationData.cs new file mode 100644 index 0000000..1fbec34 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/FinalClassificationData.cs @@ -0,0 +1,31 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FinalClassificationData + { + public byte m_position; // Finishing position + public byte m_numLaps; // Number of laps completed + public byte m_gridPosition; // Grid position of the car + public byte m_points; // Number of points scored + public byte m_numPitStops; // Number of pit stops made + public byte m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active + // 3 = finished, 4 = didnotfinish, 5 = disqualified + // 6 = not classified, 7 = retired + public uint m_bestLapTimeInMS; // Best lap time of the session in milliseconds + public double m_totalRaceTime; // Total race time in seconds without penalties + public byte m_penaltiesTime; // Total penalties accumulated in seconds + public byte m_numPenalties; // Number of penalties applied to this driver + public byte m_numTyreStints; // Number of tyres stints up to maximum + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsActual; // Actual tyres used by this driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsVisual; // Visual tyres used by this driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] m_tyreStintsEndLaps; // The lap number stints end on + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/LapData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/LapData.cs new file mode 100644 index 0000000..3a1d3b3 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/LapData.cs @@ -0,0 +1,38 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LapData + { + public uint m_lastLapTimeInMS; // Last lap time in milliseconds + public uint m_currentLapTimeInMS; // Current time around the lap in milliseconds + public ushort m_sector1TimeInMS; // Sector 1 time in milliseconds + public ushort m_sector2TimeInMS; // Sector 2 time in milliseconds + public float m_lapDistance; // Distance vehicle is around current lap in metres – could + // be negative if line hasn't been crossed yet + public float m_totalDistance; // Total distance travelled in session in metres – could + // be negative if line hasn't been crossed yet + public float m_safetyCarDelta; // Delta in seconds for safety car + public byte m_carPosition; // Car race position + public byte m_currentLapNum; // Current lap number + public byte m_pitStatus; // 0 = none, 1 = pitting, 2 = in pit area + public byte m_numPitStops; // Number of pit stops taken in this race + public byte m_sector; // 0 = sector1, 1 = sector2, 2 = sector3 + public byte m_currentLapInvalid; // Current lap invalid - 0 = valid, 1 = invalid + public byte m_penalties; // Accumulated time penalties in seconds to be added + public byte m_warnings; // Accumulated number of warnings issued + public byte m_numUnservedDriveThroughPens; // Num drive through pens left to serve + public byte m_numUnservedStopGoPens; // Num stop go pens left to serve + public byte m_gridPosition; // Grid position the vehicle started the race in + public byte m_driverStatus; // Status of driver - 0 = in garage, 1 = flying lap + // 2 = in lap, 3 = out lap, 4 = on track + public byte m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active + // 3 = finished, 4 = didnotfinish, 5 = disqualified + // 6 = not classified, 7 = retired + public byte m_pitLaneTimerActive; // Pit lane timing, 0 = inactive, 1 = active + public ushort m_pitLaneTimeInLaneInMS; // If active, the current time spent in the pit lane in ms + public ushort m_pitStopTimerInMS; // Time of the actual pit stop in ms + public byte m_pitStopShouldServePen; // Whether the car should serve a penalty at this stop + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/LapHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/LapHistoryData.cs new file mode 100644 index 0000000..7b3645e --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/LapHistoryData.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LapHistoryData + { + public uint m_lapTimeInMS; // Lap time in milliseconds + public ushort m_sector1TimeInMS; // Sector 1 time in milliseconds + public ushort m_sector2TimeInMS; // Sector 2 time in milliseconds + public ushort m_sector3TimeInMS; // Sector 3 time in milliseconds + public byte m_lapValidBitFlags; // 0x01 bit set-lap valid, 0x02 bit set-sector 1 valid + // 0x04 bit set-sector 2 valid, 0x08 bit set-sector 3 valid + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/LobbyInfoData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/LobbyInfoData.cs new file mode 100644 index 0000000..ae30820 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/LobbyInfoData.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LobbyInfoData + { + public byte m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + public byte m_teamId; // Team id - see appendix (255 if no team currently selected) + public byte m_nationality; // Nationality of the driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] + public byte[] m_name; // Name of participant in UTF-8 format – null terminated + // Will be truncated with ... (U+2026) if too long + + public byte m_carNumber; // Car number of the player + public byte m_readyStatus; // 0 = not ready, 1 = ready, 2 = spectating + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/MarshalZone.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/MarshalZone.cs new file mode 100644 index 0000000..e94aad7 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/MarshalZone.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MarshalZone + { + public float m_zoneStart; // Fraction (0..1) of way through the lap the marshal zone starts + public sbyte m_zoneFlag; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow, 4 = red + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarDamageData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarDamageData.cs new file mode 100644 index 0000000..d692547 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarDamageData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Car damage packet for F1 2022 telemetry. + /// Frequency: 2 per second + /// Size: 948 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarDamageData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarDamageData[] m_carDamageData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarSetupData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarSetupData.cs new file mode 100644 index 0000000..f18750b --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarSetupData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Car setups packet for F1 2022 telemetry. + /// Frequency: 2 per second + /// Size: 1102 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarSetupData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarSetupData[] m_carSetups; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarStatusData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarStatusData.cs new file mode 100644 index 0000000..8e6ec89 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarStatusData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Car status packet for F1 2022 telemetry. + /// Frequency: Rate as specified in menus + /// Size: 1058 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarStatusData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarStatusData[] m_carStatusData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarTelemetryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarTelemetryData.cs new file mode 100644 index 0000000..7b0c8f8 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketCarTelemetryData.cs @@ -0,0 +1,26 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Car telemetry packet for F1 2022 telemetry. + /// Frequency: Rate as specified in menus + /// Size: 1347 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketCarTelemetryData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarTelemetryData[] m_carTelemetryData; + + public byte m_mfdPanelIndex; // Index of MFD panel open - 255 = MFD closed + // Single player, race – 0 = Car setup, 1 = Pits + // 2 = Damage, 3 = Engine, 4 = Temperatures + // May vary depending on game mode + public byte m_mfdPanelIndexSecondaryPlayer; // See above + public sbyte m_suggestedGear; // Suggested gear for the player (1-8) + // 0 if no gear suggested + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketEventData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketEventData.cs new file mode 100644 index 0000000..a55b4bb --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketEventData.cs @@ -0,0 +1,26 @@ +using System.Runtime.InteropServices; +using System.Text; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Event packet for F1 2022 telemetry. + /// Frequency: When the event occurs + /// Size: 40 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketEventData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] m_eventStringCode; // Event string code, see below + + public EventDataDetails m_eventDetails; // Event details - should be interpreted differently for each type + + /// + /// Helper to get event code as string + /// + public readonly string EventCode => Encoding.ASCII.GetString(m_eventStringCode); + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketFinalClassificationData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketFinalClassificationData.cs new file mode 100644 index 0000000..6615046 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketFinalClassificationData.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Final classification packet for F1 2022 telemetry. + /// Frequency: Once at the end of a race + /// Size: 1015 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketFinalClassificationData + { + public PacketHeader m_header; + + public byte m_numCars; // Number of cars in the final classification + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public FinalClassificationData[] m_classificationData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketHeader.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketHeader.cs new file mode 100644 index 0000000..b43548a --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketHeader.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketHeader + { + public ushort m_packetFormat; // 2022 + public byte m_gameMajorVersion; // Game major version - "X.00" + public byte m_gameMinorVersion; // Game minor version - "1.XX" + public byte m_packetVersion; // Version of this packet type, all start from 1 + public byte m_packetId; // Identifier for the packet type, see below + public ulong m_sessionUID; // Unique identifier for the session + public float m_sessionTime; // Session timestamp + public uint m_frameIdentifier; // Identifier for the frame the data was retrieved on + public byte m_playerCarIndex; // Index of player's car in the array + public byte m_secondaryPlayerCarIndex; // Index of secondary player's car in the array (splitscreen) - 255 if no second player + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketLapData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketLapData.cs new file mode 100644 index 0000000..5a82874 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketLapData.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Lap data packet for F1 2022 telemetry. + /// Frequency: Rate as specified in menus + /// Size: 972 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLapData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public LapData[] m_lapData; // Lap data for all cars on track + + public byte m_timeTrialPBCarIdx; // Index of Personal Best car in time trial (255 if invalid) + public byte m_timeTrialRivalCarIdx; // Index of Rival car in time trial (255 if invalid) + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketLobbyInfoData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketLobbyInfoData.cs new file mode 100644 index 0000000..6bc40e6 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketLobbyInfoData.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Lobby info packet for F1 2022 telemetry. + /// Frequency: Two every second when in the lobby + /// Size: 1191 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketLobbyInfoData + { + public PacketHeader m_header; + + public byte m_numPlayers; // Number of players in the lobby data + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public LobbyInfoData[] m_lobbyPlayers; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketMotionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketMotionData.cs new file mode 100644 index 0000000..1fc7b6f --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketMotionData.cs @@ -0,0 +1,40 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Motion data packet for F1 2022 telemetry. + /// Frequency: Rate as specified in menus + /// Size: 1464 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketMotionData + { + public PacketHeader m_header; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public CarMotionData[] m_carMotionData; // Data for all cars on track + + // Extra player car ONLY data + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionPosition; // Note: All wheel arrays have the following order: RL, RR, FL, FR + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionVelocity; // RL, RR, FL, FR + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_suspensionAcceleration; // RL, RR, FL, FR + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSpeed; // Speed of each wheel + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public float[] m_wheelSlip; // Slip ratio for each wheel + public float m_localVelocityX; // Velocity in local space + public float m_localVelocityY; // Velocity in local space + public float m_localVelocityZ; // Velocity in local space + public float m_angularVelocityX; // Angular velocity x-component + public float m_angularVelocityY; // Angular velocity y-component + public float m_angularVelocityZ; // Angular velocity z-component + public float m_angularAccelerationX; // Angular acceleration x-component + public float m_angularAccelerationY; // Angular acceleration y-component + public float m_angularAccelerationZ; // Angular acceleration z-component + public float m_frontWheelsAngle; // Current front wheels angle in radians + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketParticipantsData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketParticipantsData.cs new file mode 100644 index 0000000..b9c71cd --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketParticipantsData.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Participants packet for F1 2022 telemetry. + /// Frequency: Every 5 seconds + /// Size: 1257 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketParticipantsData + { + public PacketHeader m_header; + + public byte m_numActiveCars; // Number of active cars in the data – should match number of cars on HUD + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public ParticipantData[] m_participants; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketSessionData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketSessionData.cs new file mode 100644 index 0000000..4e9dc67 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketSessionData.cs @@ -0,0 +1,71 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Session data packet for F1 2022 telemetry. + /// Frequency: 2 per second + /// Size: 632 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketSessionData + { + public PacketHeader m_header; + + public byte m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast + // 3 = light rain, 4 = heavy rain, 5 = storm + public sbyte m_trackTemperature; // Track temp. in degrees celsius + public sbyte m_airTemperature; // Air temp. in degrees celsius + public byte m_totalLaps; // Total number of laps in this race + public ushort m_trackLength; // Track length in metres + public byte m_sessionType; // 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P + // 5 = Q1, 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ + // 10 = R, 11 = R2, 12 = R3, 13 = Time Trial + public sbyte m_trackId; // -1 for unknown, see appendix + public byte m_formula; // Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2, + // 3 = F1 Generic, 4 = Beta, 5 = Supercars + // 6 = Esports, 7 = F2 2021 + public ushort m_sessionTimeLeft; // Time left in session in seconds + public ushort m_sessionDuration; // Session duration in seconds + public byte m_pitSpeedLimit; // Pit speed limit in kilometres per hour + public byte m_gamePaused; // Whether the game is paused – network game only + public byte m_isSpectating; // Whether the player is spectating + public byte m_spectatorCarIndex; // Index of the car being spectated + public byte m_sliProNativeSupport; // SLI Pro support, 0 = inactive, 1 = active + public byte m_numMarshalZones; // Number of marshal zones to follow + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 21)] + public MarshalZone[] m_marshalZones; // List of marshal zones – max 21 + + public byte m_safetyCarStatus; // 0 = no safety car, 1 = full + // 2 = virtual, 3 = formation lap + public byte m_networkGame; // 0 = offline, 1 = online + public byte m_numWeatherForecastSamples; // Number of weather samples to follow + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 56)] + public WeatherForecastSample[] m_weatherForecastSamples; // Array of weather forecast samples + + public byte m_forecastAccuracy; // 0 = Perfect, 1 = Approximate + public byte m_aiDifficulty; // AI Difficulty rating – 0-110 + public uint m_seasonLinkIdentifier; // Identifier for season - persists across saves + public uint m_weekendLinkIdentifier; // Identifier for weekend - persists across saves + public uint m_sessionLinkIdentifier; // Identifier for session - persists across saves + public byte m_pitStopWindowIdealLap; // Ideal lap to pit on for current strategy (player) + public byte m_pitStopWindowLatestLap; // Latest lap to pit on for current strategy (player) + public byte m_pitStopRejoinPosition; // Predicted position to rejoin at (player) + public byte m_steeringAssist; // 0 = off, 1 = on + public byte m_brakingAssist; // 0 = off, 1 = low, 2 = medium, 3 = high + public byte m_gearboxAssist; // 1 = manual, 2 = manual & suggested gear, 3 = auto + public byte m_pitAssist; // 0 = off, 1 = on + public byte m_pitReleaseAssist; // 0 = off, 1 = on + public byte m_ERSAssist; // 0 = off, 1 = on + public byte m_DRSAssist; // 0 = off, 1 = on + public byte m_dynamicRacingLine; // 0 = off, 1 = corners only, 2 = full + public byte m_dynamicRacingLineType; // 0 = 2D, 1 = 3D + public byte m_gameMode; // Game mode id - see appendix + public byte m_ruleSet; // Ruleset - see appendix + public uint m_timeOfDay; // Local time of day - minutes since midnight + public byte m_sessionLength; // 0 = None, 2 = Very Short, 3 = Short, 4 = Medium + // 5 = Medium Long, 6 = Long, 7 = Full + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/PacketSessionHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketSessionHistoryData.cs new file mode 100644 index 0000000..df91afa --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/PacketSessionHistoryData.cs @@ -0,0 +1,30 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + /// + /// Session history packet for F1 2022 telemetry. + /// Frequency: 20 per second but cycling through cars + /// Size: 1155 bytes + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PacketSessionHistoryData + { + public PacketHeader m_header; + + public byte m_carIdx; // Index of the car this lap data relates to + public byte m_numLaps; // Num laps in the data (including current partial lap) + public byte m_numTyreStints; // Number of tyre stints in the data + + public byte m_bestLapTimeLapNum; // Lap the best lap time was achieved on + public byte m_bestSector1LapNum; // Lap the best Sector 1 time was achieved on + public byte m_bestSector2LapNum; // Lap the best Sector 2 time was achieved on + public byte m_bestSector3LapNum; // Lap the best Sector 3 time was achieved on + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)] + public LapHistoryData[] m_lapHistoryData; // 100 laps of data max + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public TyreStintHistoryData[] m_tyreStintsHistoryData; + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/ParticipantData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/ParticipantData.cs new file mode 100644 index 0000000..34feb1b --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/ParticipantData.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ParticipantData + { + public byte m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + public byte m_driverId; // Driver id - see appendix, 255 if network human + public byte m_networkId; // Network id – unique identifier for network players + public byte m_teamId; // Team id - see appendix + public byte m_myTeam; // My team flag – 1 = My Team, 0 = otherwise + public byte m_raceNumber; // Race number of the car + public byte m_nationality; // Nationality of the driver + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] + public byte[] m_name; // Name of participant in UTF-8 format – null terminated + // Will be truncated with … (U+2026) if too long + + public byte m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/TyreStintHistoryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/TyreStintHistoryData.cs new file mode 100644 index 0000000..6a546ce --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/TyreStintHistoryData.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TyreStintHistoryData + { + public byte m_endLap; // Lap the tyre usage ends on (255 of current tyre) + public byte m_tyreActualCompound; // Actual tyres used by this driver + public byte m_tyreVisualCompound; // Visual tyres used by this driver + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12022/WeatherForecastSample.cs b/GamesDat/Telemetry/Sources/Formula1/F12022/WeatherForecastSample.cs new file mode 100644 index 0000000..d409121 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F12022/WeatherForecastSample.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1.F12022 +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct WeatherForecastSample + { + public byte m_sessionType; // 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P, 5 = Q1 + // 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ, 10 = R, 11 = R2 + // 12 = R3, 13 = Time Trial + public byte m_timeOffset; // Time in minutes the forecast is for + public byte m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast + // 3 = light rain, 4 = heavy rain, 5 = storm + public sbyte m_trackTemperature; // Track temp. in degrees Celsius + public sbyte m_trackTemperatureChange; // Track temp. change – 0 = up, 1 = down, 2 = no change + public sbyte m_airTemperature; // Air temp. in degrees celsius + public sbyte m_airTemperatureChange; // Air temp. change – 0 = up, 1 = down, 2 = no change + public byte m_rainPercentage; // Rain percentage (0-100) + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetupData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetupData.cs index f6a327f..95fc780 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetupData.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarSetupData.cs @@ -8,7 +8,7 @@ public struct CarSetupData public PacketHeader m_header; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] - public CarSetupData[] m_carSetupData; + public CarSetup[] m_carSetupData; public float m_nextFrontWingValue; } diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetryData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetryData.cs index f58b715..9278b79 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetryData.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/CarTelemetryData.cs @@ -9,7 +9,7 @@ public struct CarTelemetryData // Packet specific data [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] - public CarTelemetryData[] m_carTelemetryData; + public CarTelemetry[] m_carTelemetryData; public byte m_mfdPanelIndex; // Index of MFD panel open - 255 = MFD closed // Single player, race – 0 = Car setup, 1 = Pits diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs index f7d3523..303b22a 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs @@ -14,7 +14,7 @@ public struct EventData public EventDataDetails m_eventDetails; // Helper property to get event code as string - public string EventCode => Encoding.ASCII.GetString(EventStringCode); + public readonly string EventCode => EventStringCode != null ? Encoding.ASCII.GetString(EventStringCode) : string.Empty; public T GetEventDetails() where T : struct { diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs index 16eef6f..993187f 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs @@ -45,7 +45,7 @@ public struct TeamMateInPitsData [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct DRSDisabledData { - public byte VehicleIdx; + public byte Reason; // 0 = Wet track, 1 = Safety car, 2 = Red flag, 3 = Min lap not reached } [StructLayout(LayoutKind.Sequential, Pack = 1)] @@ -93,12 +93,13 @@ public struct DriveThroughPenaltyServedData public struct StopGoPenaltyServedData { public byte VehicleIdx; + public float StopTime; // Time spent serving stop-go in seconds } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct FlashbackData { - public byte FlashbackFrameIdentifier; + public uint FlashbackFrameIdentifier; // Changed from byte to uint public float FlashbackSessionTime; } @@ -125,7 +126,7 @@ public struct SafetyCarData [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct CollisionData { - public byte VehicleIdx; - public byte CollidingVehicleIdx; + public byte Vehicle1Idx; // Changed from VehicleIdx + public byte Vehicle2Idx; // Changed from CollidingVehicleIdx } } diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/ParticipantData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/ParticipantData.cs index 7bd4c17..82c36bd 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F12025/ParticipantData.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/ParticipantData.cs @@ -10,6 +10,6 @@ public struct ParticipantData public byte m_numActiveCars; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] - public ParticipantData[] m_participants; + public Participant[] m_participants; } } diff --git a/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs b/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs index e5ffbb2..da63667 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F1PacketTypeMapper.cs @@ -1,3 +1,4 @@ +using GamesDat.Core.Telemetry.Sources.Formula1.F12022; using GamesDat.Core.Telemetry.Sources.Formula1.F12023; using GamesDat.Core.Telemetry.Sources.Formula1.F12024; using GamesDat.Core.Telemetry.Sources.Formula1.F12025; @@ -8,6 +9,20 @@ internal static class F1PacketTypeMapper { private static readonly Dictionary<(ushort format, byte id), Type> _packetTypeMap = new() { + // F1 2022 + [(2022, (byte)PacketId.Motion)] = typeof(F12022.PacketMotionData), + [(2022, (byte)PacketId.Session)] = typeof(F12022.PacketSessionData), + [(2022, (byte)PacketId.LapData)] = typeof(F12022.PacketLapData), + [(2022, (byte)PacketId.Event)] = typeof(F12022.PacketEventData), + [(2022, (byte)PacketId.Participants)] = typeof(F12022.PacketParticipantsData), + [(2022, (byte)PacketId.CarSetups)] = typeof(F12022.PacketCarSetupData), + [(2022, (byte)PacketId.CarTelemetry)] = typeof(F12022.PacketCarTelemetryData), + [(2022, (byte)PacketId.CarStatus)] = typeof(F12022.PacketCarStatusData), + [(2022, (byte)PacketId.FinalClassification)] = typeof(F12022.PacketFinalClassificationData), + [(2022, (byte)PacketId.LobbyInfo)] = typeof(F12022.PacketLobbyInfoData), + [(2022, (byte)PacketId.CarDamage)] = typeof(F12022.PacketCarDamageData), + [(2022, (byte)PacketId.SessionHistory)] = typeof(F12022.PacketSessionHistoryData), + // F1 2023 [(2023, (byte)PacketId.Motion)] = typeof(F12023.PacketMotionData), [(2023, (byte)PacketId.Session)] = typeof(F12023.PacketSessionData), diff --git a/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs b/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs index 394042e..dd42af8 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs @@ -4,7 +4,7 @@ namespace GamesDat.Core.Telemetry.Sources.Formula1 { - internal class F1RealtimeTelemetrySource : UdpSourceBase + public class F1RealtimeTelemetrySource : UdpSourceBase { private const int MinRoutingHeaderSize = 7; @@ -15,28 +15,31 @@ public F1RealtimeTelemetrySource(UdpSourceOptions options) : base(options) protected override IEnumerable ProcessData(byte[] data) { if (data.Length < MinRoutingHeaderSize) + { + System.Diagnostics.Debug.WriteLine($"[F1] Packet too small: {data.Length} bytes (min {MinRoutingHeaderSize})"); yield break; + } var packetFormat = BitConverter.ToUInt16(data, 0); var packetId = data[6]; var packetType = F1PacketTypeMapper.GetPacketType(packetFormat, packetId); if (packetType == null) + { + System.Diagnostics.Debug.WriteLine($"[F1] Unknown packet: Format={packetFormat}, PacketId={packetId}"); yield break; + } var expectedSize = Marshal.SizeOf(packetType); if (data.Length < expectedSize) + { + System.Diagnostics.Debug.WriteLine($"[F1] Packet size mismatch: PacketId={packetId}, Expected={expectedSize}, Actual={data.Length}"); yield break; + } - var packet = BytesToStruct(data, packetType); + System.Diagnostics.Debug.WriteLine($"[F1] Packet received: Format={packetFormat}, PacketId={packetId}, Size={data.Length}"); - yield return new F1TelemetryFrame - { - Packet = packet, - PacketFormat = packetFormat, - PacketId = packetId, - PacketType = packetType - }; + yield return new F1TelemetryFrame(packetFormat, packetId, data); } private static object BytesToStruct(byte[] bytes, Type type) diff --git a/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs index 901664c..6c9e7f2 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrame.cs @@ -1,23 +1,32 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.InteropServices; +using GamesDat.Core.Attributes; namespace GamesDat.Core.Telemetry.Sources.Formula1 { - public struct F1TelemetryFrame + /// + /// Unmanaged struct containing raw F1 telemetry packet data. + /// Use F1TelemetryFrameExtensions for easy deserialization. + /// + [GameId("F1")] + [DataVersion(1, 0, 0)] + public unsafe struct F1TelemetryFrame { - public object Packet { get; init; } - public ushort PacketFormat { get; init; } - public byte PacketId { get; init; } - public Type PacketType { get; init; } + public ushort PacketFormat; + public byte PacketId; + public fixed byte RawData[2048]; // Max F1 packet size + public int DataLength; - public T GetPacket() where T : struct + public F1TelemetryFrame(ushort packetFormat, byte packetId, byte[] data) { - if (Packet is T typed) - return typed; - throw new InvalidCastException($"Packet is {Packet?.GetType().Name ?? "null"}, not {typeof(T).Name}"); + PacketFormat = packetFormat; + PacketId = packetId; + DataLength = Math.Min(data.Length, 2048); + + fixed (byte* ptr = RawData) + { + Marshal.Copy(data, 0, (IntPtr)ptr, DataLength); + } } } } diff --git a/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrameExtensions.cs b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrameExtensions.cs new file mode 100644 index 0000000..f20f62b --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrameExtensions.cs @@ -0,0 +1,84 @@ +using System; +using System.Runtime.InteropServices; + +namespace GamesDat.Core.Telemetry.Sources.Formula1 +{ + /// + /// Helper methods for working with F1TelemetryFrame. + /// Provides friendly API for deserializing packet data. + /// + public static class F1TelemetryFrameExtensions + { + /// + /// Gets the Type of the packet based on format and ID + /// + public static Type? GetPacketType(this F1TelemetryFrame frame) + { + return F1PacketTypeMapper.GetPacketType(frame.PacketFormat, frame.PacketId); + } + + /// + /// Deserializes the packet to the specified type + /// + public static unsafe T GetPacket(this F1TelemetryFrame frame) where T : struct + { + var packetType = typeof(T); + var expectedSize = Marshal.SizeOf(); + + if (frame.DataLength < expectedSize) + { + throw new InvalidOperationException( + $"Packet data too small. Expected {expectedSize} bytes for {packetType.Name}, got {frame.DataLength}"); + } + + return Marshal.PtrToStructure((IntPtr)frame.RawData); + } + + /// + /// Deserializes the packet to its actual type (determined by PacketFormat and PacketId) + /// + public static unsafe object? DeserializePacket(this F1TelemetryFrame frame) + { + var packetType = frame.GetPacketType(); + if (packetType == null) + return null; + + var expectedSize = Marshal.SizeOf(packetType); + + var msg = $"[F1 Deserialize] Type={packetType.Name}, Expected={expectedSize}, Actual={frame.DataLength}, PacketId={frame.PacketId}"; + System.Diagnostics.Debug.WriteLine(msg); + Console.WriteLine(msg); + + if (frame.DataLength < expectedSize) + { + var errorMsg = $"Packet data too small. Expected {expectedSize} bytes for {packetType.Name}, got {frame.DataLength}"; + Console.WriteLine($"[F1 ERROR] {errorMsg}"); + throw new InvalidOperationException(errorMsg); + } + + try + { + var result = Marshal.PtrToStructure((IntPtr)frame.RawData, packetType); + Console.WriteLine($"[F1 Deserialize] SUCCESS for PacketId={frame.PacketId}"); + return result; + } + catch (Exception ex) + { + var errorMsg = $"Marshal.PtrToStructure failed: {ex.GetType().Name} - {ex.Message}"; + Console.WriteLine($"[F1 ERROR] {errorMsg}"); + System.Diagnostics.Debug.WriteLine($"[F1 ERROR] {errorMsg}"); + throw; + } + } + + /// + /// Gets the raw packet data as a byte array + /// + public static unsafe byte[] GetRawData(this F1TelemetryFrame frame) + { + var data = new byte[frame.DataLength]; + Marshal.Copy((IntPtr)frame.RawData, data, 0, frame.DataLength); + return data; + } + } +} diff --git a/GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2025.h b/GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2025.h new file mode 100644 index 0000000..82315c5 --- /dev/null +++ b/GamesDat/Telemetry/Sources/Formula1/Specifications/F1_2025.h @@ -0,0 +1,860 @@ +//------------------------------------------------------------------------------ +// (c) 2025 Electronic Arts Inc. All rights reserved. +//------------------------------------------------------------------------------ + +// C++ structures for F1 25 UDP specification +// Allows easy comparison with previous years to see what has changed + +//----------------------------------------------------------------------------- +// Header - 29 bytes +//----------------------------------------------------------------------------- +static const uint32 cs_maxNumCarsInUDPData = 22; +static const uint32 cs_maxParticipantNameLen = 32; +static const uint32 cs_maxTyreStints = 8; +static const uint32 cs_maxNumTyreSets = 13 + 7; // 13 slick and 7 wet weather + +// Different packet types +enum PacketId +{ + ePacketIdMotion = 0, // Contains all motion data for player�s car � only sent while player is in control + ePacketIdSession = 1, // Data about the session � track, time left + ePacketIdLapData = 2, // Data about all the lap times of cars in the session + ePacketIdEvent = 3, // Various notable events that happen during a session + ePacketIdParticipants = 4, // List of participants in the session, mostly relevant for multiplayer + ePacketIdCarSetups = 5, // Packet detailing car setups for cars in the race + ePacketIdCarTelemetry = 6, // Telemetry data for all cars + ePacketIdCarStatus = 7, // Status data for all cars + ePacketIdFinalClassification = 8, // Final classification confirmation at the end of a race + ePacketIdLobbyInfo = 9, // Information about players in a multiplayer lobby + ePacketIdCarDamage = 10, // Damage status for all cars + ePacketIdSessionHistory = 11, // Lap and tyre data for session + ePacketIdTyreSets = 12, // Extended tyre set data + ePacketIdMotionEx = 13, // Extended motion data for player car + ePacketIdTimeTrial = 14, // Time Trial specific data + ePacketIdLapPositions = 15, // Lap positions on each lap so a chart can be constructed + ePacketIdMax +}; + +struct PacketHeader +{ + uint16 m_packetFormat; // 2025 + uint8 m_gameYear; // Game year - last two digits e.g. 25 + uint8 m_gameMajorVersion; // Game major version - "X.00" + uint8 m_gameMinorVersion; // Game minor version - "1.XX" + uint8 m_packetVersion; // Version of this packet type + uint8 m_packetId; // Identifier for the packet type + uint64 m_sessionUID; // Unique identifier for the session + float m_sessionTime; // Session timestamp + uint32 m_frameIdentifier; // Identifier for the frame the data was retrieved on + uint32 m_overallFrameIdentifier; // Overall identifier for the frame the data was retrieved on, doesn't go back after flashbacks + uint8 m_playerCarIndex; // Index of player's car in the array + uint8 m_secondaryPlayerCarIndex; // Index of secondary player's car in the array (splitscreen) - 255 if no second player +}; + + +//----------------------------------------------------------------------------- +// Motion - 1349 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Motion data for one car +//----------------------------------------------------------------------------- +struct CarMotionData +{ + float m_worldPositionX; // World space X position - metres + float m_worldPositionY; // World space Y position + float m_worldPositionZ; // World space Z position + float m_worldVelocityX; // Velocity in world space X - metres/s + float m_worldVelocityY; // Velocity in world space Y + float m_worldVelocityZ; // Velocity in world space Z + int16 m_worldForwardDirX; // World space forward X direction (normalised) + int16 m_worldForwardDirY; // World space forward Y direction (normalised) + int16 m_worldForwardDirZ; // World space forward Z direction (normalised) + int16 m_worldRightDirX; // World space right X direction (normalised) + int16 m_worldRightDirY; // World space right Y direction (normalised) + int16 m_worldRightDirZ; // World space right Z direction (normalised) + float m_gForceLateral; // Lateral G-Force component + float m_gForceLongitudinal; // Longitudinal G-Force component + float m_gForceVertical; // Vertical G-Force component + float m_yaw; // Yaw angle in radians + float m_pitch; // Pitch angle in radians + float m_roll; // Roll angle in radians +}; + +struct PacketMotionData +{ + PacketHeader m_header; // Header + + // Packet specific data + CarMotionData m_carMotionData[cs_maxNumCarsInUDPData]; // Data for all cars on track +}; + + +//----------------------------------------------------------------------------- +// Session - 753 bytes +//----------------------------------------------------------------------------- +static const uint32 cs_maxMarshalsZonePerLap = 21; +static const uint32 cs_maxWeatherForecastSamples = 64; +static const uint32 cs_maxSessionsInWeekend = 12; + +struct MarshalZone +{ + float m_zoneStart; // Fraction (0..1) of way through the lap the marshal zone starts + int8 m_zoneFlag; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow +}; + +struct WeatherForecastSample +{ + uint8 m_sessionType; // 0 = unknown, see appendix + uint8 m_timeOffset; // Time in minutes the forecast is for + uint8 m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast, 3 = light rain, 4 = heavy rain, 5 = storm + int8 m_trackTemperature; // Track temp. in degrees celsius + int8 m_trackTemperatureChange; // Track temp. change - 0 = up, 1 = down, 2 = no change + int8 m_airTemperature; // Air temp. in degrees celsius + int8 m_airTemperatureChange; // Air temp. change - 0 = up, 1 = down, 2 = no change + uint8 m_rainPercentage; // Rain percentage (0-100) +}; + +struct PacketSessionData +{ + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_weather; // Weather - 0 = clear, 1 = light cloud, 2 = overcast, 3 = light rain, 4 = heavy rain, 5 = storm + int8 m_trackTemperature; // Track temp. in degrees celsius + int8 m_airTemperature; // Air temp. in degrees celsius + uint8 m_totalLaps; // Total number of laps in this race + uint16 m_trackLength; // Track length in metres + uint8 m_sessionType; // 0 = unknown, see appendix + int8 m_trackId; // -1 for unknown, see appendix + uint8 m_formula; // Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2, 3 = F1 Generic, 4 = Beta, 6 = Esports, 8 = F1 World, 9 = F1 Elimination + uint16 m_sessionTimeLeft; // Time left in session in seconds + uint16 m_sessionDuration; // Session duration in seconds + uint8 m_pitSpeedLimit; // Pit speed limit in kilometres per hour + uint8 m_gamePaused; // Whether the game is paused - network game only + uint8 m_isSpectating; // Whether the player is spectating + uint8 m_spectatorCarIndex; // Index of the car being spectated + uint8 m_sliProNativeSupport; // SLI Pro support, 0 = inactive, 1 = active + uint8 m_numMarshalZones; // Number of marshal zones to follow + MarshalZone m_marshalZones[cs_maxMarshalsZonePerLap]; // List of marshal zones - max 21 + uint8 m_safetyCarStatus; // 0 = no safety car, 1 = full, 2 = virtual, 3 = formation lap + uint8 m_networkGame; // 0 = offline, 1 = online + uint8 m_numWeatherForecastSamples; // Number of weather samples to follow + WeatherForecastSample m_weatherForecastSamples[cs_maxWeatherForecastSamples]; // Array of weather forecast samples + uint8 m_forecastAccuracy; // 0 = Perfect, 1 = Approximate + uint8 m_aiDifficulty; // AI difficulty - 0-110 + uint32 m_seasonLinkIdentifier; // Identifier for season - persists across saves + uint32 m_weekendLinkIdentifier; // Identifier for weekend - persists across saves + uint32 m_sessionLinkIdentifier; // Identifier for session - persists across saves + uint8 m_pitStopWindowIdealLap; // Ideal lap to pit on for current strategy (player) + uint8 m_pitStopWindowLatestLap; // Latest lap to pit on for current strategy (player) + uint8 m_pitStopRejoinPosition; // Predicted position to rejoin at (player) + uint8 m_steeringAssist; // 0 = off, 1 = on + uint8 m_brakingAssist; // 0 = off, 1 = low, 2 = medium, 3 = high + uint8 m_gearboxAssist; // 1 = manual, 2 = manual & suggested gear, 3 = auto + uint8 m_pitAssist; // 0 = off, 1 = on + uint8 m_pitReleaseAssist; // 0 = off, 1 = on + uint8 m_ERSAssist; // 0 = off, 1 = on + uint8 m_DRSAssist; // 0 = off, 1 = on + uint8 m_dynamicRacingLine; // 0 = off, 1 = corners only, 2 = full + uint8 m_dynamicRacingLineType; // 0 = 2D, 1 = 3D + uint8 m_gameMode; // Game mode id - see appendix + uint8 m_ruleSet; // Ruleset - see appendix + uint32 m_timeOfDay; // Local time of day - minutes since midnight + uint8 m_sessionLength; // 0 = None, 2 = Very Short, 3 = Short, 4 = Medium, 5 = Medium Long, 6 = Long, 7 = Full + uint8 m_speedUnitsLeadPlayer; // 0 = MPH, 1 = KPH + uint8 m_temperatureUnitsLeadPlayer; // 0 = Celsius, 1 = Fahrenheit + uint8 m_speedUnitsSecondaryPlayer; // 0 = MPH, 1 = KPH + uint8 m_temperatureUnitsSecondaryPlayer; // 0 = Celsius, 1 = Fahrenheit + uint8 m_numSafetyCarPeriods; // Number of safety cars called during session + uint8 m_numVirtualSafetyCarPeriods; // Number of virtual safety cars called during session + uint8 m_numRedFlagPeriods; // Number of red flags called during session + uint8 m_equalCarPerformance; // 0 = Off, 1 = On + uint8 m_recoveryMode; // 0 = None, 1 = Flashbacks, 2 = Auto-recovery + uint8 m_flashbackLimit; // 0 = Low, 1 = Medium, 2 = High, 3 = Unlimited + uint8 m_surfaceType; // 0 = Simplified, 1 = Realistic + uint8 m_lowFuelMode; // 0 = Easy, 1 = Hard + uint8 m_raceStarts; // 0 = Manual, 1 = Assisted + uint8 m_tyreTemperature; // 0 = Surface only, 1 = Surface & Carcass + uint8 m_pitLaneTyreSim; // 0 = On, 1 = Off + uint8 m_carDamage; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Simulation + uint8 m_carDamageRate; // 0 = Reduced, 1 = Standard, 2 = Simulation + uint8 m_collisions; // 0 = Off, 1 = Player-to-Player Off, 2 = On + uint8 m_collisionsOffForFirstLapOnly; // 0 = Disabled, 1 = Enabled + uint8 m_mpUnsafePitRelease; // 0 = On, 1 = Off (Multiplayer) + uint8 m_mpOffForGriefing; // 0 = Disabled, 1 = Enabled (Multiplayer) + uint8 m_cornerCuttingStringency; // 0 = Regular, 1 = Strict + uint8 m_parcFermeRules; // 0 = Off, 1 = On + uint8 m_pitStopExperience; // 0 = Automatic, 1 = Broadcast, 2 = Immersive + uint8 m_safetyCar; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Increased + uint8 m_safetyCarExperience; // 0 = Broadcast, 1 = Immersive + uint8 m_formationLap; // 0 = Off, 1 = On + uint8 m_formationLapExperience; // 0 = Broadcast, 1 = Immersive + uint8 m_redFlags; // 0 = Off, 1 = Reduced, 2 = Standard, 3 = Increased + uint8 m_affectsLicenceLevelSolo; // 0 = Off, 1 = On + uint8 m_affectsLicenceLevelMP; // 0 = Off, 1 = On + uint8 m_numSessionsInWeekend; // Number of session in following array + uint8 m_weekendStructure[cs_maxSessionsInWeekend]; // List of session types to show weekend structure - see appendix for types + float m_sector2LapDistanceStart; // Distance in m around track where sector 2 starts + float m_sector3LapDistanceStart; // Distance in m around track where sector 3 starts +}; + + +//----------------------------------------------------------------------------- +// Lap - 1285 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Lap data about one car +//----------------------------------------------------------------------------- +struct LapData +{ + uint32 m_lastLapTimeInMS; // Last lap time in milliseconds + uint32 m_currentLapTimeInMS; // Current time around the lap in milliseconds + uint16 m_sector1TimeMSPart; // Sector 1 time milliseconds part + uint8 m_sector1TimeMinutesPart; // Sector 1 whole minute part + uint16 m_sector2TimeMSPart; // Sector 2 time milliseconds part + uint8 m_sector2TimeMinutesPart; // Sector 2 whole minute part + uint16 m_deltaToCarInFrontMSPart; // Time delta to car in front milliseconds part + uint8 m_deltaToCarInFrontMinutesPart; // Time delta to car in front whole minute part + uint16 m_deltaToRaceLeaderMSPart; // Time delta to race leader milliseconds part + uint8 m_deltaToRaceLeaderMinutesPart; // Time delta to race leader whole minute part + float m_lapDistance; // Distance vehicle is around current lap in metres � could be negative if line hasn�t been crossed yet + float m_totalDistance; // Total distance travelled in session in metres � could be negative if line hasn�t been crossed yet + float m_safetyCarDelta; // Delta in seconds for safety car + uint8 m_carPosition; // Car race position + uint8 m_currentLapNum; // Current lap number + uint8 m_pitStatus; // 0 = none, 1 = pitting, 2 = in pit area + uint8 m_numPitStops; // Number of pit stops taken in this race + uint8 m_sector; // 0 = sector1, 1 = sector2, 2 = sector3 + uint8 m_currentLapInvalid; // Current lap invalid - 0 = valid, 1 = invalid + uint8 m_penalties; // Accumulated time penalties in seconds to be added + uint8 m_totalWarnings; // Accumulated number of warnings issued + uint8 m_cornerCuttingWarnings; // Accumulated number of corner cutting warnings issued + uint8 m_numUnservedDriveThroughPens; // Num drive through pens left to serve + uint8 m_numUnservedStopGoPens; // Num stop go pens left to serve + uint8 m_gridPosition; // Grid position the vehicle started the race in + uint8 m_driverStatus; // Status of driver - 0 = in garage, 1 = flying lap, 2 = in lap, 3 = out lap, 4 = on track + uint8 m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active, 3 = finished, 4 = didnotfinish, 5 = disqualified, 6 = not classified, 7 = retired + uint8 m_pitLaneTimerActive; // Pit lane timing, 0 = inactive, 1 = active + uint16 m_pitLaneTimeInLaneInMS; // If active, the current time spent in the pit lane in ms + uint16 m_pitStopTimerInMS; // Time of the actual pit stop in ms + uint8 m_pitStopShouldServePen; // Whether the car should serve a penalty at this stop + float m_speedTrapFastestSpeed; // Fastest speed through speed trap for this car in kmph + uint8 m_speedTrapFastestLap; // Lap no the fastest speed was achieved, 255 = not set +}; + +struct PacketLapData +{ + PacketHeader m_header; // Header + + // Packet specific data + LapData m_lapData[cs_maxNumCarsInUDPData]; // Lap data for all cars on track + + uint8 m_timeTrialPBCarIdx; // Index of Personal Best car in time trial (255 if invalid) + uint8 m_timeTrialRivalCarIdx; // Index of Rival car in time trial (255 if invalid) +}; + + +//----------------------------------------------------------------------------- +// Event - 45 bytes +//----------------------------------------------------------------------------- + +static const uint cs_eventStringCodeLen = 4; + +// The event details packet is different for each type of event. +// Make sure only the correct type is interpreted. +union EventDataDetails +{ + struct + { + uint8 vehicleIdx; // Vehicle index of car achieving fastest lap + float lapTime; // Lap time is in seconds + } FastestLap; + + struct + { + uint8 vehicleIdx; // Vehicle index of car retiring + uint8 reason; // Result reason - 0 = invalid, 1 = retired, 2 = finished, 3 = terminal damage, 4 = inactive, 5 = not enough laps completed, 6 = black flagged + // 7 = red flagged, 8 = mechanical failure, 9 = session skipped, 10 = session simulated + } Retirement; + + struct + { + NEuint8 reason; // 0 = Wet track, 1 = Safety car deployed, 2 = Red flag, 3 = Min lap not reached + } DRSDisabled; + + struct + { + uint8 vehicleIdx; // Vehicle index of team mate + } TeamMateInPits; + + struct + { + uint8 vehicleIdx; // Vehicle index of the race winner + } RaceWinner; + + struct + { + uint8 penaltyType; // Penalty type � see Appendices + uint8 infringementType; // Infringement type � see Appendices + uint8 vehicleIdx; // Vehicle index of the car the penalty is applied to + uint8 otherVehicleIdx; // Vehicle index of the other car involved + uint8 time; // Time gained, or time spent doing action in seconds + uint8 lapNum; // Lap the penalty occurred on + uint8 placesGained; // Number of places gained by this + } Penalty; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle triggering speed trap + float speed; // Top speed achieved in kilometres per hour + uint8 isOverallFastestInSession; // Overall fastest speed in session = 1, otherwise 0 + uint8 isDriverFastestInSession; // Fastest speed for driver in session = 1, otherwise 0 + uint8 fastestVehicleIdxInSession; // Vehicle index of the vehicle that is the fastest in this session + float fastestSpeedInSession; // Speed of the vehicle that is the fastest in this session + } SpeedTrap; + + struct + { + uint8 numLights; // Number of lights showing + } StartLights; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle serving drive through + } DriveThroughPenaltyServed; + + struct + { + uint8 vehicleIdx; // Vehicle index of the vehicle serving stop go + float stopTime; // Time spent serving stop go in seconds + } StopGoPenaltyServed; + + struct + { + uint32 flashbackFrameIdentifier; // Frame identifier flashed back to + float flashbackSessionTime; // Session time flashed back to + } Flashback; + + struct + { + uint32 buttonStatus; // Bit flags specifying which buttons are being pressed currently - see appendices + } Buttons; + + struct + { + uint8 overtakingVehicleIdx; // Vehicle index of the vehicle overtaking + uint8 beingOvertakenVehicleIdx; // Vehicle index of the vehicle being overtaken + } Overtake; + + struct + { + uint8 safetyCarType; // 0 = No Safety Car, 1 = Full Safety Car, 2 = Virtual Safety Car, 3 = Formation Lap Safety Car + uint8 eventType; // 0 = Deployed, 1 = Returning, 2 = Returned, 3 = Resume Race + } SafetyCar; + + struct + { + uint8 vehicle1Idx; // Vehicle index of the first vehicle involved in the collision + uint8 vehicle2Idx; // Vehicle index of the second vehicle involved in the collision + } Collision; +}; + +struct PacketEventData +{ + // Valid event strings + static const NEchar* cs_sessionStartedEventCode; // "SSTA" + static const NEchar* cs_sessionEndedEventCode; // "SEND" + static const NEchar* cs_fastestLapEventCode; // "FTLP" + static const NEchar* cs_retirementEventCode; // "RTMT" + static const NEchar* cs_drsEnabledEventCode; // "DRSE" + static const NEchar* cs_drsDisabledEventCode; // "DRSD" + static const NEchar* cs_teamMateInPitsEventCode; // "TMPT" + static const NEchar* cs_chequeredFlagEventCode; // "CHQF" + static const NEchar* cs_raceWinnerEventCode; // "RCWN" + static const NEchar* cs_penaltyEventCode; // "PENA" + static const NEchar* cs_speedTrapEventCode; // "SPTP" + static const NEchar* cs_startLightsEventCode; // "STLG" + static const NEchar* cs_lightsOutEventCode; // "LGOT" + static const NEchar* cs_driveThroughServedEventCode; // "DTSV" + static const NEchar* cs_stopGoServedEventCode; // "SGSV" + static const NEchar* cs_flashbackEventCode; // "FLBK" + static const NEchar* cs_buttonStatusEventCode; // "BUTN" + static const NEchar* cs_redFlagEventCode; // "RDFL" + static const NEchar* cs_overtakeEventCode; // "OVTK" + static const NEchar* cs_safetyCarEventCode; // "SCAR" + static const NEchar* cs_collisionEventCode; // "COLL" + + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_eventStringCode[cs_eventStringCodeLen]; // Event string code + EventDataDetails m_eventDetails; // Event details - should be interpreted differently for each type +}; + + +//----------------------------------------------------------------------------- +// Participants - 1284 bytes +//----------------------------------------------------------------------------- + +// RGB value of a colour +struct LiveryColour +{ + uint8 red; + uint8 green; + uint8 blue; +}; + +//----------------------------------------------------------------------------- +// Data about one participant +//----------------------------------------------------------------------------- +struct ParticipantData +{ + uint8 m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + uint8 m_driverId; // Driver id - see appendix, 255 if network human + uint8 m_networkId; // Network id - unique identifier for network players + uint8 m_teamId; // Team id - see appendix + uint8 m_myTeam; // My team flag - 1 = My Team, 0 = otherwise + uint8 m_raceNumber; // Race number of the car + uint8 m_nationality; // Nationality of the driver + char m_name[cs_maxParticipantNameLen]; // Name of participant in UTF-8 format � null terminated + // Will be truncated with ... (U+2026) if too long + uint8 m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + uint8 m_showOnlineNames; // The player's show online names setting, 0 = off, 1 = on + uint16 m_techLevel; // F1 World tech level + uint8 m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + uint8 m_numColours; // Number of colours valid for this car + LiveryColour m_liveryColours[4]; // Colours for the car +}; + +struct PacketParticipantsData +{ + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_numActiveCars; // Number of active cars in the data - should match number of cars on HUD + ParticipantData m_participants[cs_maxNumCarsInUDPData]; +}; + + +//----------------------------------------------------------------------------- +// Car Setups - 1133 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Data about one car setup +//----------------------------------------------------------------------------- +struct CarSetupData +{ + uint8 m_frontWing; // Front wing aero + uint8 m_rearWing; // Rear wing aero + uint8 m_onThrottle; // Differential adjustment on throttle (percentage) + uint8 m_offThrottle; // Differential adjustment off throttle (percentage) + float m_frontCamber; // Front camber angle (suspension geometry) + float m_rearCamber; // Rear camber angle (suspension geometry) + float m_frontToe; // Front toe angle (suspension geometry) + float m_rearToe; // Rear toe angle (suspension geometry) + uint8 m_frontSuspension; // Front suspension + uint8 m_rearSuspension; // Rear suspension + uint8 m_frontAntiRollBar; // Front anti-roll bar + uint8 m_rearAntiRollBar; // Front anti-roll bar + uint8 m_frontSuspensionHeight; // Front ride height + uint8 m_rearSuspensionHeight; // Rear ride height + uint8 m_brakePressure; // Brake pressure (percentage) + uint8 m_brakeBias; // Brake bias (percentage) + uint8 m_engineBraking; // Engine braking (percentage) + float m_rearLeftTyrePressure; // Rear left tyre pressure (PSI) + float m_rearRightTyrePressure; // Rear right tyre pressure (PSI) + float m_frontLeftTyrePressure; // Front left tyre pressure (PSI) + float m_frontRightTyrePressure; // Front right tyre pressure (PSI) + uint8 m_ballast; // Ballast + float m_fuelLoad; // Fuel load +}; + +struct PacketCarSetupData +{ + PacketHeader m_header; // Header + + // Packet specific data + CarSetupData m_carSetupData[cs_maxNumCarsInUDPData]; + + float m_nextFrontWingValue; // Value of front wing after next pit stop - player only +}; + + +//----------------------------------------------------------------------------- +// Car Telemetry - 1352 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Telemetry data for one car +//----------------------------------------------------------------------------- +struct CarTelemetryData +{ + uint16 m_speed; // Speed of car in kilometres per hour + float m_throttle; // Amount of throttle applied (0.0 to 1.0) + float m_steer; // Steering (-1.0 (full lock left) to 1.0 (full lock right)) + float m_brake; // Amount of brake applied (0.0 to 1.0) + uint8 m_clutch; // Amount of clutch applied (0 to 100) + int8 m_gear; // Gear selected (1-8, N=0, R=-1) + uint16 m_engineRPM; // Engine RPM + uint8 m_drs; // 0 = off, 1 = on + uint8 m_revLightsPercent; // Rev lights indicator (percentage) + uint16 m_revLightsBitValue; // Rev lights (bit 0 = leftmost LED, bit 14 = rightmost LED) + uint16 m_brakesTemperature[4]; // Brakes temperature (celsius) + uint8 m_tyresSurfaceTemperature[4]; // Tyres surface temperature (celsius) + uint8 m_tyresInnerTemperature[4]; // Tyres inner temperature (celsius) + uint16 m_engineTemperature; // Engine temperature (celsius) + float m_tyresPressure[4]; // Tyre pressure (PSI) + uint8 m_surfaceType[4]; // Driving surface, see appendices +}; + +struct PacketCarTelemetryData +{ + PacketHeader m_header; // Header + + // Packet specific data + CarTelemetryData m_carTelemetryData[cs_maxNumCarsInUDPData]; // data for all cars on track + + uint8 m_mfdPanelIndex; // Index of MFD panel open - 255 = MFD closed + // Single player, race � 0 = Car setup, 1 = Pits + // 2 = Damage, 3 = Engine, 4 = Temperatures + // May vary depending on game mode + uint8 m_mfdPanelIndexSecondaryPlayer; // See above + int8 m_suggestedGear; // Suggested gear for the player (1-8), 0 if no gear suggested +}; + + +//----------------------------------------------------------------------------- +// Car Status - 1239 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Car status data for one car +//----------------------------------------------------------------------------- +struct CarStatusData +{ + uint8 m_tractionControl; // Traction control - 0 = off, 1 = medium, 2 = full + uint8 m_antiLockBrakes; // 0 (off) - 1 (on) + uint8 m_fuelMix; // Fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max + uint8 m_frontBrakeBias; // Front brake bias (percentage) + uint8 m_pitLimiterStatus; // Pit limiter status - 0 = off, 1 = on + float m_fuelInTank; // Current fuel mass + float m_fuelCapacity; // Fuel capacity + float m_fuelRemainingLaps; // Fuel remaining in terms of laps (value on MFD) + uint16 m_maxRPM; // Cars max RPM, point of rev limiter + uint16 m_idleRPM; // Cars idle RPM + uint8 m_maxGears; // Maximum number of gears + uint8 m_drsAllowed; // 0 = not allowed, 1 = allowed + uint16 m_drsActivationDistance; // 0 = DRS not available, non-zero - DRS will be available in [X] metres + uint8 m_actualTyreCompound; // F1 Modern - 16 = C5, 17 = C4, 18 = C3, 19 = C2, 20 = C1, 21 = C0, 7 = inter, 8 = wet + // F1 Classic - 9 = dry, 10 = wet + // F2 � 11 = super soft, 12 = soft, 13 = medium, 14 = hard, 15 = wet + uint8 m_visualTyreCompound; // F1 visual (can be different from actual compound) + // 16 = soft, 17 = medium, 18 = hard, 7 = inter, 8 = wet + // F1 Classic � same as above + // F2 �20, 15 = wet, 19 � super soft, 20 = soft, 21 = medium, 22 = hard + uint8 m_tyresAgeLaps; // Age in laps of the current set of tyres + int8 m_vehicleFIAFlags; // -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow + float m_enginePowerICE; // Engine power output of ICE (W) + float m_enginePowerMGUK; // Engine power output of MGU-K (W) + float m_ersStoreEnergy; // ERS energy store in Joules + uint8 m_ersDeployMode; // ERS deployment mode, 0 = none, 1 = medium, 2 = hotlap, 3 = overtake + float m_ersHarvestedThisLapMGUK; // ERS energy harvested this lap by MGU-K + float m_ersHarvestedThisLapMGUH; // ERS energy harvested this lap by MGU-H + float m_ersDeployedThisLap; // ERS energy deployed this lap + uint8 m_networkPaused; // Whether the car is paused in a network game +}; + +struct PacketCarStatusData +{ + PacketHeader m_header; // Header + + // Packet specific data + CarStatusData m_carStatusData[cs_maxNumCarsInUDPData]; // data for all cars on track +}; + + +//----------------------------------------------------------------------------- +// Final Classification - 1042 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Data about one participant's final results +//----------------------------------------------------------------------------- +struct FinalClassificationData +{ + uint8 m_position; // Finishing position + uint8 m_numLaps; // Number of laps completed + uint8 m_gridPosition; // Grid position of the car + uint8 m_points; // Number of points scored + uint8 m_numPitStops; // Number of pit stops made + uint8 m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active, 3 = finished, 4 = didnotfinish, 5 = disqualified, 6 = not classified, 7 = retired + uint8 m_resultReason; // Result reason - 0 = invalid, 1 = retired, 2 = finished, 3 = terminal damage, 4 = inactive, 5 = not enough laps completed, 6 = black flagged + // 7 = red flagged, 8 = mechanical failure, 9 = session skipped, 10 = session simulated + uint32 m_bestLapTimeInMS; // Best lap time of the session in milliseconds + double m_totalRaceTime; // Total race time in seconds without penalties + uint8 m_penaltiesTime; // Total penalties accumulated in seconds + uint8 m_numPenalties; // Number of penalties applied to this driver + uint8 m_numTyreStints; // Number of tyres stints up to maximum + uint8 m_tyreStintsActual[cs_maxTyreStints]; // Actual tyres used by this driver + uint8 m_tyreStintsVisual[cs_maxTyreStints]; // Visual tyres used by this driver + uint8 m_tyreStintsEndLaps[cs_maxTyreStints]; // The lap number stints end on +}; + +struct PacketFinalClassificationData +{ + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_numCars; // Number of cars in the final classification + FinalClassificationData m_classificationData[cs_maxNumCarsInUDPData]; +}; + + +//----------------------------------------------------------------------------- +// Lobby Info - 954 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Data about one participant +//----------------------------------------------------------------------------- +struct LobbyInfoData +{ + uint8 m_aiControlled; // Whether the vehicle is AI (1) or Human (0) controlled + uint8 m_teamId; // Team id - see appendix (255 if no team currently selected) + uint8 m_nationality; // Nationality of the driver + uint8 m_platform; // 1 = Steam, 3 = PlayStation, 4 = Xbox, 6 = Origin, 255 = unknown + char m_name[cs_maxParticipantNameLen]; // Name of participant in UTF-8 format � null terminated + // Will be truncated with ... (U+2026) if too long + uint8 m_carNumber; // Car number of the player + uint8 m_yourTelemetry; // The player's UDP setting, 0 = restricted, 1 = public + uint8 m_showOnlineNames; // The player's show online names setting, 0 = off, 1 = on + uint16 m_techLevel; // F1 World tech level + uint8 m_readyStatus; // 0 = not ready, 1 = ready, 2 = spectating +}; + +struct PacketLobbyInfoData +{ + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_numPlayers; // Number of players in the lobby data + LobbyInfoData m_lobbyPlayers[cs_maxNumCarsInUDPData]; +}; + + +//----------------------------------------------------------------------------- +// Car Damage - 1041 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Car damage data for one car +//----------------------------------------------------------------------------- +struct CarDamageData +{ + float m_tyresWear[4]; // Tyre wear (percentage) + uint8 m_tyresDamage[4]; // Tyre damage (percentage) + uint8 m_brakesDamage[4]; // Brakes damage (percentage) + uint8 m_tyreBlisters[4]; // Tyre blisters value (percentage) + uint8 m_frontLeftWingDamage; // Front left wing damage (percentage) + uint8 m_frontRightWingDamage; // Front right wing damage (percentage) + uint8 m_rearWingDamage; // Rear wing damage (percentage) + uint8 m_floorDamage; // Floor damage (percentage) + uint8 m_diffuserDamage; // Diffuser damage (percentage) + uint8 m_sidepodDamage; // Sidepod damage (percentage) + uint8 m_drsFault; // Indicator for DRS fault, 0 = OK, 1 = fault + uint8 m_ersFault; // Indicator for ERS fault, 0 = OK, 1 = fault + uint8 m_gearBoxDamage; // Gear box damage (percentage) + uint8 m_engineDamage; // Engine damage (percentage) + uint8 m_engineMGUHWear; // Engine wear MGU-H (percentage) + uint8 m_engineESWear; // Engine wear ES (percentage) + uint8 m_engineCEWear; // Engine wear CE (percentage) + uint8 m_engineICEWear; // Engine wear ICE (percentage) + uint8 m_engineMGUKWear; // Engine wear MGU-K (percentage) + uint8 m_engineTCWear; // Engine wear TC (percentage) + uint8 m_engineBlown; // Engine blown, 0 = OK, 1 = fault + uint8 m_engineSeized; // Engine seized, 0 = OK, 1 = fault +}; + +struct PacketCarDamageData +{ + PacketHeader m_header; // Header + + // Packet specific data + CarDamageData m_carDamageData[cs_maxNumCarsInUDPData]; // data for all cars on track +}; + + +//----------------------------------------------------------------------------- +// Session History - 1460 bytes +//----------------------------------------------------------------------------- + +static const uint cs_maxNumLapsInHistory = 100; + +struct LapHistoryData +{ + uint32 m_lapTimeInMS; // Lap time in milliseconds + uint16 m_sector1TimeMSPart; // Sector 1 milliseconds part + uint8 m_sector1TimeMinutesPart; // Sector 1 whole minute part + uint16 m_sector2TimeMSPart; // Sector 2 time milliseconds part + uint8 m_sector2TimeMinutesPart; // Sector 2 whole minute part + uint16 m_sector3TimeMSPart; // Sector 3 time milliseconds part + uint8 m_sector3TimeMinutesPart; // Sector 3 whole minute part + uint8 m_lapValidBitFlags; // 0x01 bit set-lap valid, 0x02 bit set-sector 1 valid + // 0x04 bit set-sector 2 valid, 0x08 bit set-sector 3 valid +}; + +struct TyreStintHistoryData +{ + uint8 m_endLap; // Lap the tyre usage ends on (255 if current tyre) + uint8 m_tyreActualCompound; // Actual tyres used by this driver + uint8 m_tyreVisualCompound; // Visual tyres used by this driver +}; + +struct PacketSessionHistoryData +{ + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_carIdx; // Index of the car this lap data relates to + uint8 m_numLaps; // Num laps in the data (including current partial lap) + uint8 m_numTyreStints; // Number of tyre stints in the data + + uint8 m_bestLapTimeLapNum; // Lap the best lap time was achieved on + uint8 m_bestSector1LapNum; // Lap the best Sector 1 time was achieved on + uint8 m_bestSector2LapNum; // Lap the best Sector 2 time was achieved on + uint8 m_bestSector3LapNum; // Lap the best Sector 3 time was achieved on + + LapHistoryData m_lapHistoryData[cs_maxNumLapsInHistory]; // 100 laps of data max + TyreStintHistoryData m_tyreStintsHistoryData[cs_maxTyreStints]; +}; + + +//----------------------------------------------------------------------------- +// Tyre Sets - 231 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Data about one tyre set +//----------------------------------------------------------------------------- +struct TyreSetData +{ + uint8 m_actualTyreCompound; // Actual tyre compound used + uint8 m_visualTyreCompound; // Visual tyre compound used + uint8 m_wear; // Tyre wear (percentage) + uint8 m_available; // Whether this set is currently available + uint8 m_recommendedSession; // Recommended session for tyre set, see appendix + uint8 m_lifeSpan; // Laps left in this tyre set + uint8 m_usableLife; // Max number of laps recommended for this compound + int16 m_lapDeltaTime; // Lap delta time in milliseconds compared to fitted set + uint8 m_fitted; // Whether the set is fitted or not +}; + +struct PacketTyreSetsData +{ + PacketHeader m_header; // Header + + uint8 m_carIdx; // Index of the car this data relates to + + // Packet specific data + TyreSetData m_tyreSetData[cs_maxNumTyreSets]; // 13 (dry) + 7 (wet) + + uint8 m_fittedIdx; // Index into array of fitted tyre +}; + + +//----------------------------------------------------------------------------- +// Motion Ex - 273 bytes +//----------------------------------------------------------------------------- + +struct PacketMotionExData +{ + PacketHeader m_header; // Header + + // Extra player car ONLY data + float m_suspensionPosition[4]; // Note: All wheel arrays have the following order: + float m_suspensionVelocity[4]; // RL, RR, FL, FR + float m_suspensionAcceleration[4]; // RL, RR, FL, FR + float m_wheelSpeed[4]; // Speed of each wheel + float m_wheelSlipRatio[4]; // Slip ratio for each wheel + float m_wheelSlipAngle[4]; // Slip angles for each wheel + float m_wheelLatForce[4]; // Lateral forces for each wheel + float m_wheelLongForce[4]; // Longitudinal forces for each wheel + float m_heightOfCOGAboveGround; // Height of centre of gravity above ground + float m_localVelocityX; // Velocity in local space X - metres/s + float m_localVelocityY; // Velocity in local space Y + float m_localVelocityZ; // Velocity in local space Z + float m_angularVelocityX; // Angular velocity x-component - radians/s + float m_angularVelocityY; // Angular velocity y-component + float m_angularVelocityZ; // Angular velocity z-component + float m_angularAccelerationX; // Angular acceleration x-component - radians/s/s + float m_angularAccelerationY; // Angular acceleration y-component + float m_angularAccelerationZ; // Angular acceleration z-component + float m_frontWheelsAngle; // Current front wheels angle in radians + float m_wheelVertForce[4]; // Vertical forces for each wheel + float m_frontAeroHeight; // Front plank edge height above road surface + float m_rearAeroHeight; // Rear plank edge height above road surface + float m_frontRollAngle; // Roll angle of the front suspension + float m_rearRollAngle; // Roll angle of the rear suspension + float m_chassisYaw; // Yaw angle of the chassis relative to the direction of motion - radians + float m_chassisPitch; // Pitch angle of the chassis relative to the direction of motion - radians + float m_wheelCamber[4]; // Camber of each wheel in radians + float m_wheelCamberGain[4]; // Camber gain for each wheel in radians, difference between active camber and dynamic camber +}; + + +//----------------------------------------------------------------------------- +// Time Trial - 101 bytes +//----------------------------------------------------------------------------- + +struct TimeTrialDataSet +{ + uint8 m_carIdx; // Index of the car this data relates to + uint8 m_teamId; // Team id - see appendix + uint m_lapTimeInMS; // Lap time in milliseconds + uint m_sector1TimeInMS; // Sector 1 time in milliseconds + uint m_sector2TimeInMS; // Sector 2 time in milliseconds + uint m_sector3TimeInMS; // Sector 3 time in milliseconds + uint8 m_tractionControl; // 0 = assist off, 1 = assist on + uint8 m_gearboxAssist; // 0 = assist off, 1 = assist on + uint8 m_antiLockBrakes; // 0 = assist off, 1 = assist on + uint8 m_equalCarPerformance; // 0 = Realistic, 1 = Equal + uint8 m_customSetup; // 0 = No, 1 = Yes + uint8 m_valid; // 0 = invalid, 1 = valid +}; + +struct PacketTimeTrialData +{ + PacketHeader m_header; // Header + + TimeTrialDataSet m_playerSessionBestDataSet; // Player session best data set + TimeTrialDataSet m_personalBestDataSet; // Personal best data set + TimeTrialDataSet m_rivalDataSet; // Rival data set +}; + + +//----------------------------------------------------------------------------- +// Lap Positions - 1131 bytes +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Packet to send UDP data about the lap positions in a session. It details +// the positions of all the drivers at the start of each lap +//----------------------------------------------------------------------------- +static const NEuint cs_maxNumLapsInLapPositionsHistoryPacket = 50; + +struct PacketLapPositionsData +{ + PacketHeader m_header; // Header + + // Packet specific data + uint8 m_numLaps; // Number of laps in the data + uint8 m_lapStart; // Index of the lap where the data starts, 0 indexed + + // Array holding the position of the car in a given lap, 0 if no record + uint8 m_positionForVehicleIdx[cs_maxNumLapsInLapPositionsHistoryPacket][cs_maxNumCarsInUDPData]; +}; + +//////////////////////////////// END OF FILE //////////////////////////////// From 6cc4471a59ba2289a8cdfa209c500f56ee309c0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Tue, 10 Feb 2026 21:29:17 +0100 Subject: [PATCH 09/25] Remove legacy F1 implementation workflow file --- .../Formula1/F1_IMPLEMENTATION_WORKFLOW.md | 322 ------------------ 1 file changed, 322 deletions(-) delete mode 100644 GamesDat/Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md diff --git a/GamesDat/Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md b/GamesDat/Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md deleted file mode 100644 index 0fabbea..0000000 --- a/GamesDat/Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md +++ /dev/null @@ -1,322 +0,0 @@ -# F1 UDP Telemetry Implementation Workflow - -This document describes the workflow for implementing F1 game UDP telemetry packet structures from C++ specifications into C# structs for the GamesDat project. - -## Overview - -The F1 games send UDP telemetry data using a specific binary format defined in C++ header files. This workflow guides the process of converting those C++ specifications into C# structs that can deserialize the UDP packets. - -## Prerequisites - -- C++ specification file (typically provided by EA/Codemasters) -- Understanding of C struct layout and C# interop -- Existing F1 implementation to use as reference (e.g., F12025) - -## Workflow Steps - -### 1. Analyze the C++ Specification - -Review the C++ header file to understand: -- **Packet format version** (e.g., 2024, 2025) -- **Packet sizes** (documented in comments) -- **Constants** (e.g., `cs_maxNumCarsInUDPData = 22`) -- **Struct hierarchy** (which structs contain others) -- **Array sizes** (fixed-size arrays in structs) - -### 2. Create the Namespace Folder - -Create a new folder under `Telemetry/Sources/Formula1/` with the naming pattern `F1YYYY` (e.g., `F12024`, `F12025`). - -### 3. Implement the PacketHeader - -Start with the `PacketHeader` struct as it's used by all packet types: - -```csharp -using System.Runtime.InteropServices; - -namespace GamesDat.Core.Telemetry.Sources.Formula1.F1YYYY -{ - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct PacketHeader - { - public ushort m_packetFormat; // YYYY - public byte m_gameYear; // Last two digits e.g. 24 - // ... other fields from spec - } -} -``` - -**Key points:** -- Always use `[StructLayout(LayoutKind.Sequential, Pack = 1)]` -- Keep field names exactly as in C++ spec (helps with debugging) -- Add inline comments from the C++ spec - -### 4. Implement Support Structures - -Create the smaller, reusable structs before the main packet types: - -Examples: `MarshalZone`, `WeatherForecastSample`, `CarMotionData`, etc. - -**Pattern:** -```csharp -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct StructName -{ - public type m_fieldName; // comment from spec - // ... more fields -} -``` - -### 5. Implement Main Packet Structures - -For each packet type from the spec, create: - -1. **Component struct** (e.g., `LapData`) - data for one car/item -2. **Packet struct** (e.g., `PacketLapData`) - complete packet with header and array - -**Pattern:** -```csharp -// Component for one car -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct ComponentData -{ - public type m_field1; - public type m_field2; -} - -// Full packet -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct PacketComponentData -{ - public PacketHeader m_header; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] - public ComponentData[] m_dataArray; - - // ... any additional single fields -} -``` - -### 6. Handle Special Cases - -#### Fixed-Size Arrays -```csharp -[MarshalAs(UnmanagedType.ByValArray, SizeConst = SIZE)] -public Type[] m_arrayField; -``` - -#### Strings/Character Arrays -```csharp -[MarshalAs(UnmanagedType.ByValArray, SizeConst = LENGTH)] -public byte[] m_name; // UTF-8 null-terminated string -``` - -#### Unions (EventDataDetails) -Use `StructLayout(LayoutKind.Explicit)` with `FieldOffset`: - -```csharp -[StructLayout(LayoutKind.Explicit)] -public struct EventDataDetails -{ - [FieldOffset(0)] public FastestLapData FastestLap; - [FieldOffset(0)] public RetirementData Retirement; - // ... all union members at offset 0 -} -``` - -Then create a wrapper with helper methods: -```csharp -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct PacketEventData -{ - public PacketHeader m_header; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - public byte[] m_eventStringCode; - - public EventDataDetails m_eventDetails; - - // Helper to get event code as string - public string EventCode => Encoding.ASCII.GetString(m_eventStringCode); - - // Helper to safely extract typed event details - public T GetEventDetails() where T : struct { ... } -} -``` - -### 7. Type Mapping Reference - -| C++ Type | C# Type | Notes | -|----------|---------|-------| -| `uint8` | `byte` | Unsigned 8-bit | -| `int8` | `sbyte` | Signed 8-bit | -| `uint16` | `ushort` | Unsigned 16-bit | -| `int16` | `short` | Signed 16-bit | -| `uint32` / `uint` | `uint` | Unsigned 32-bit | -| `int32` | `int` | Signed 32-bit | -| `uint64` | `ulong` | Unsigned 64-bit | -| `float` | `float` | 32-bit float | -| `double` | `double` | 64-bit float | -| `char[]` | `byte[]` | UTF-8 strings | - -### 8. Update F1PacketTypeMapper - -Add mappings for all packet types to `F1PacketTypeMapper.cs`: - -```csharp -private static readonly Dictionary<(ushort format, byte id), Type> _packetTypeMap = new() -{ - // F1 YYYY - [(YYYY, (byte)PacketId.Motion)] = typeof(F1YYYY.PacketMotionData), - [(YYYY, (byte)PacketId.Session)] = typeof(F1YYYY.PacketSessionData), - // ... all packet types - - // F1 previous years - // ... -}; -``` - -**Important:** Use fully qualified type names (e.g., `F12024.PacketMotionData`) to avoid conflicts between years. - -### 9. Packet Types Checklist - -Ensure you implement all standard packet types: - -- [ ] Motion (`PacketMotionData`) -- [ ] Session (`PacketSessionData`) -- [ ] Lap Data (`PacketLapData`) -- [ ] Event (`PacketEventData`) -- [ ] Participants (`PacketParticipantsData`) -- [ ] Car Setups (`PacketCarSetupData`) -- [ ] Car Telemetry (`PacketCarTelemetryData`) -- [ ] Car Status (`PacketCarStatusData`) -- [ ] Final Classification (`PacketFinalClassificationData`) -- [ ] Lobby Info (`PacketLobbyInfoData`) -- [ ] Car Damage (`PacketCarDamageData`) -- [ ] Session History (`PacketSessionHistoryData`) -- [ ] Tyre Sets (`PacketTyreSetsData`) -- [ ] Motion Ex (`PacketMotionExData`) -- [ ] Time Trial (`PacketTimeTrialData`) - -### 10. Validation - -After implementation: - -1. **Build the project** - ensure no compilation errors -2. **Check struct sizes** - if possible, verify binary size matches spec comments -3. **Test with real data** - use actual game UDP packets to validate deserialization -4. **Compare with previous year** - look for differences and ensure they're intentional - -## Common Patterns - -### Pattern 1: Simple Data Struct -```csharp -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct SimpleData -{ - public byte m_field1; - public float m_field2; -} -``` - -### Pattern 2: Packet with Array -```csharp -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct PacketWithArray -{ - public PacketHeader m_header; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] - public ItemData[] m_items; -} -``` - -### Pattern 3: Nested Arrays -```csharp -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct DataWithArrays -{ - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - public float[] m_tyresPressure; // 4 tyres -} -``` - -### Pattern 4: Data + Extra Fields -```csharp -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct PacketWithExtras -{ - public PacketHeader m_header; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] - public ItemData[] m_items; - - public byte m_extraField1; - public byte m_extraField2; -} -``` - -## Best Practices - -1. **Maintain exact field order** - C# struct layout must match C++ exactly -2. **Use Pack = 1** - prevents automatic padding/alignment -3. **Keep original naming** - makes comparison with spec easier -4. **Add XML comments** - for complex structs, add summary docs -5. **Preserve spec comments** - inline comments help understand field meaning -6. **Test incrementally** - validate each packet type as you implement it -7. **Reference existing implementations** - use previous years as templates -8. **Watch for year-specific changes** - array sizes, new fields, removed fields - -## File Organization - -Each year's implementation should be self-contained: - -``` -Formula1/ -├── F12024/ -│ ├── PacketHeader.cs -│ ├── PacketMotionData.cs -│ ├── CarMotionData.cs -│ ├── PacketSessionData.cs -│ ├── MarshalZone.cs -│ ├── WeatherForecastSample.cs -│ └── ... (all other packet types) -├── F12025/ -│ └── ... (same structure) -├── F1PacketTypeMapper.cs -├── PacketId.cs (shared enum) -└── EventCodes.cs (shared constants) -``` - -## Troubleshooting - -### Issue: Struct size doesn't match spec -- Check for missing `Pack = 1` -- Verify all arrays have correct `SizeConst` -- Ensure no fields were skipped -- Check type mappings (e.g., `int8` vs `uint8`) - -### Issue: Deserialization fails -- Verify packet header format number matches -- Check PacketTypeMapper has correct entries -- Ensure union types use `LayoutKind.Explicit` -- Validate field order exactly matches spec - -### Issue: Data seems corrupted -- Check endianness (should be little-endian) -- Verify `Pack = 1` is set -- Ensure no extra padding between fields -- Check array sizes match spec constants - -## Example: Complete Implementation - -See `F12024/` folder for a complete reference implementation following this workflow. - -## Version History - -- **2024-02-09**: Created workflow documentation based on F12024 implementation -- **Future**: Update as new patterns or edge cases are discovered - ---- - -*This workflow is designed for GamesDat project contributors and AI agents implementing F1 telemetry support.* From b91ad7c9925ee758d15d8890f8e7d51eba46e210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Tue, 10 Feb 2026 21:31:06 +0100 Subject: [PATCH 10/25] Remove debug tooling for F1 --- .gitignore | 4 +- .../F1-Telemetry-Fixture-Capture/capture.js | 24 -- .../fixtures/.gitkeep | 0 .../fixtures/2023/packet-0.bin | Bin 1349 -> 0 bytes .../fixtures/2023/packet-1.bin | Bin 644 -> 0 bytes .../fixtures/2023/packet-10.bin | Bin 953 -> 0 bytes .../fixtures/2023/packet-11.bin | Bin 1460 -> 0 bytes .../fixtures/2023/packet-12.bin | Bin 231 -> 0 bytes .../fixtures/2023/packet-13.bin | Bin 217 -> 0 bytes .../fixtures/2023/packet-2.bin | Bin 1131 -> 0 bytes .../fixtures/2023/packet-3.bin | Bin 45 -> 0 bytes .../fixtures/2023/packet-4.bin | Bin 1306 -> 0 bytes .../fixtures/2023/packet-5.bin | Bin 1107 -> 0 bytes .../fixtures/2023/packet-6.bin | Bin 1352 -> 0 bytes .../fixtures/2023/packet-7.bin | Bin 1239 -> 0 bytes .../fixtures/2023/packet-8.bin | Bin 1020 -> 0 bytes .../fixtures/2024/packet-0.bin | Bin 1349 -> 0 bytes .../fixtures/2024/packet-1.bin | Bin 753 -> 0 bytes .../fixtures/2024/packet-10.bin | Bin 953 -> 0 bytes .../fixtures/2024/packet-11.bin | Bin 1460 -> 0 bytes .../fixtures/2024/packet-12.bin | Bin 231 -> 0 bytes .../fixtures/2024/packet-13.bin | Bin 237 -> 0 bytes .../fixtures/2024/packet-14.bin | Bin 101 -> 0 bytes .../fixtures/2024/packet-2.bin | Bin 1285 -> 0 bytes .../fixtures/2024/packet-3.bin | Bin 45 -> 0 bytes .../fixtures/2024/packet-4.bin | Bin 1350 -> 0 bytes .../fixtures/2024/packet-5.bin | Bin 1133 -> 0 bytes .../fixtures/2024/packet-6.bin | Bin 1352 -> 0 bytes .../fixtures/2024/packet-7.bin | Bin 1239 -> 0 bytes .../fixtures/2025/packet-0.bin | Bin 1349 -> 0 bytes .../fixtures/2025/packet-1.bin | Bin 753 -> 0 bytes .../fixtures/2025/packet-10.bin | Bin 1041 -> 0 bytes .../fixtures/2025/packet-11.bin | Bin 1460 -> 0 bytes .../fixtures/2025/packet-12.bin | Bin 231 -> 0 bytes .../fixtures/2025/packet-13.bin | Bin 273 -> 0 bytes .../fixtures/2025/packet-14.bin | Bin 101 -> 0 bytes .../fixtures/2025/packet-15.bin | Bin 1131 -> 0 bytes .../fixtures/2025/packet-2.bin | Bin 1285 -> 0 bytes .../fixtures/2025/packet-3-BUTN.bin | Bin 45 -> 0 bytes .../fixtures/2025/packet-3-COLL.bin | Bin 45 -> 0 bytes .../fixtures/2025/packet-3-FLBK.bin | Bin 45 -> 0 bytes .../fixtures/2025/packet-3-FTLP.bin | Bin 45 -> 0 bytes .../fixtures/2025/packet-3-LGOT.bin | Bin 45 -> 0 bytes .../fixtures/2025/packet-3-OVTK.bin | Bin 45 -> 0 bytes .../fixtures/2025/packet-3-PENA.bin | Bin 45 -> 0 bytes .../fixtures/2025/packet-3-RCWN.bin | Bin 45 -> 0 bytes .../fixtures/2025/packet-3-RTMT.bin | Bin 45 -> 0 bytes .../fixtures/2025/packet-3-SEND.bin | Bin 45 -> 0 bytes .../fixtures/2025/packet-3-SPTP.bin | Bin 45 -> 0 bytes .../fixtures/2025/packet-3-SSTA.bin | Bin 45 -> 0 bytes .../fixtures/2025/packet-3-STLG.bin | Bin 45 -> 0 bytes .../fixtures/2025/packet-3-TMPT.bin | Bin 45 -> 0 bytes .../fixtures/2025/packet-3.bin | Bin 45 -> 0 bytes .../fixtures/2025/packet-4.bin | Bin 1284 -> 0 bytes .../fixtures/2025/packet-5.bin | Bin 1133 -> 0 bytes .../fixtures/2025/packet-6.bin | Bin 1352 -> 0 bytes .../fixtures/2025/packet-7.bin | Bin 1239 -> 0 bytes .../fixtures/2025/packet-8.bin | Bin 1042 -> 0 bytes .../F1-Telemetry-Fixture-Capture/package.json | 14 - .../Formula1/F1_IMPLEMENTATION_WORKFLOW.md | 322 ------------------ 60 files changed, 3 insertions(+), 361 deletions(-) delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/capture.js delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/.gitkeep delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-0.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-1.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-10.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-11.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-12.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-13.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-2.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-3.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-4.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-5.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-6.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-7.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-8.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-0.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-1.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-10.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-11.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-12.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-13.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-14.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-2.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-3.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-4.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-5.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-6.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-7.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-0.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-1.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-10.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-11.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-12.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-13.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-14.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-15.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-2.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-BUTN.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-COLL.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FLBK.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FTLP.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-LGOT.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-OVTK.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-PENA.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-RCWN.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-RTMT.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SEND.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SPTP.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SSTA.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-STLG.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-TMPT.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-4.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-5.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-6.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-7.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-8.bin delete mode 100644 DebugTooling/F1-Telemetry-Fixture-Capture/package.json delete mode 100644 GamesDat/Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md diff --git a/.gitignore b/.gitignore index f2ac66a..495e27e 100644 --- a/.gitignore +++ b/.gitignore @@ -362,4 +362,6 @@ MigrationBackup/ # Fody - auto-generated XML schema FodyWeavers.xsd -.claude/settings.local.json \ No newline at end of file +.claude/settings.local.json + +DebugTooling/**/* \ No newline at end of file diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/capture.js b/DebugTooling/F1-Telemetry-Fixture-Capture/capture.js deleted file mode 100644 index 5f98e58..0000000 --- a/DebugTooling/F1-Telemetry-Fixture-Capture/capture.js +++ /dev/null @@ -1,24 +0,0 @@ -import dgram from "dgram"; -import { writeFileSync, mkdirSync } from "fs"; - -const socket = dgram.createSocket("udp4"); -socket.bind(20777); - -socket.on("message", (msg) => { - // PacketHeader: packetFormat(2) + gameYear(1) + gameMajorVersion(1) + gameMinorVersion(1) + packetVersion(1) + packetId(1) - const packetFormat = msg.readUInt16LE(0); // e.g., 2025, 2024 - const packetId = msg.readUInt8(6); - - const dir = `./fixtures/${packetFormat}`; - mkdirSync(dir, { recursive: true }); - - let filename = `packet-${packetId}.bin`; - - // Event packets (packetId 3) have a 4-character event code at offset 29 (after 29-byte header) - if (packetId === 3) { - const eventCode = msg.toString("ascii", 29, 33); // Read 4-byte event string (SSTA, FTLP, etc.) - filename = `packet-3-${eventCode}.bin`; - } - - writeFileSync(`${dir}/${filename}`, msg); -}); diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/.gitkeep b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-0.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-0.bin deleted file mode 100644 index 2e5a7281acde5cba08b01d8ea030ed382cb3a1cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1349 zcmaFP&cG0fLPOfzVAHq+O2dM2rFZ+$N3 zP?|Rz>6r}a-WDe`?bsq(_szAsYIAj;f&)V~^B&h@!Mo+ozd4-L70`4#8#*xs>D%u( zkbQ{F_W;A0nWkyd|5<#I_1@kk@4#^Q!0xr@o?7pdI)1okp6!8I&YfuX#xh6&?LC2J zuLH>5dse#-aLU*;yPi3`P}uIkoa`l zz3?B^-pwGr4}vYto*uF)tiEt~X^r23Uslu5^gaaYeUdKmZx4!_--7fyR9U%LmDm)m zyn0wn#pl4|^yz4N`@#0EL9_Q7Nbj^H>xt4D*6F%e4=;KfazK9P92C8Wy(*bhW;{wi z`)3P^y^9oqdQ0c+S#nzybd^CH{gY3O<-bzt$qP6h7 zn}>JJh&f97CnM6vfFSnn6hpLIuV&T`&A9Mm6n;9cAjG`&}WdY`73 ZqQ%iyklq99tytc=+90R5QTE^r0RU5UBvb$Z diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-1.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-1.bin deleted file mode 100644 index ad8ac56c0be8525ba1afbd469f941a946ca27d23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 644 zcmaFP&cG2d!^rTTfp3PLfi1() zecW~oi+>u~F(e5F*)g0v-eJeE=jJ>+hRvdT>==69J+Na)lw+`G$XO_1&!FgUXU~vc q8f4FK=3s_B!*ka%d#L%N)QAfKMn*;k21Z6!rYlT9zcT_M5C8y!-6EF& diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-10.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-10.bin deleted file mode 100644 index 6dd3d7aca06d2f98c3f7479a32d86f780a8b93c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 953 scmaFP&cGARtLtr!nMnix`A@E;N H0ISIWCQ%4u diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-12.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-12.bin deleted file mode 100644 index 53ef9c9255cf01d5e66bb332361091e1b9b50fd3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 231 lcmaFP&cGDFCcI2ipJu diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-13.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-13.bin deleted file mode 100644 index a9b1eebeb9098f717591937f022bb76aab97a280..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 217 zcmV;~04D$E2LJ&P0S$;767?ICfBHX{EkqHX0000000030Qzb=0kLN`~Z;od|P0?vW zwovH6jLF}@Ex#nc^IjjoOk|D2ru~A%=)Sc>iK+lYZGDnL@OhF#PPmdnthJItgS#)f zpk_+CTu$ORB<9dKFwS{9KW5;&^(}Y1yYLx0e6gp*qLlSS4#k*415d<5am*&e=dwb> zX8z_uQY_vwM^!^ z69|KKzzk+!fa{nYi|!A5?@dl14Aud45|9no0c4}uaad`)69|KKV6g*Jhf2V5ClDrH zhqS_UClDrHhk{wV69^NpBP<}#351E);TG%X1j5AY2y@VL0%78Hs3-F|fiUqp*rwib f1YzQJ6z`bh2*UVu0ObgS|AdvGsTkG4z{COoO~hhH diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-3.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-3.bin deleted file mode 100644 index 1f6f1006b22c2428da41237bee97c9b2724be92b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 icmaFP&cGWs5n82k%5u% zKO<1*|A5q@;{3eC90jM;;u3=T7=X6@XZ+7ZwoYaapq;@br3IA?q_`GnCo{-S@^mth zt&@Rloh)SQWF=cCJJ~wf$kxeBwoXp6b!L#OlZCvz@}J@V|NjiYtVRYH)k!GO$POU@ D^yz<5 diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-5.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-5.bin deleted file mode 100644 index 429edc2aa8833646c927bde792601ada275e2cf1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1107 zcmaFP&cGGcY6^0O~&g)L=8=&{tbkZhme_{ghD+ppY63 Qr9qT5AgO$mq;d!V05TH(3;+NC diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-6.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-6.bin deleted file mode 100644 index dcfb0f74c9aad41f7fa89eb42b91c6e6aa661695..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1352 zcmaFP&cGl{IN#hasG3{rg>sJFr1Y>kO6STBdRF^HCgLN_SLV@OL|u#u;m4$&>Ce9(h&Inp8)`tQ>B~$ diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-7.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-7.bin deleted file mode 100644 index 060942068b5672e9e7973d7a23b91e64fcdd697c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1239 zcmaFP&cG^i_It{V)6Jx-NqA7$D$30|TQKkg4Fvz;MTDXVVG?ZQ}*}oInvl z0Tu?0SxlZ_bt)xZOb`ul5~nsshMDP~JYd=w;Tj+eJldRoA#5XB8w10t?>pSdu(%B61z003rPghT)U diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-8.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2023/packet-8.bin deleted file mode 100644 index 660a2dd3ed425c4f7ad491dbf36e280c2d0d0939..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1020 zcmaFP&cG_HIbBYY@|LVDRy^(TR}UEqDIS;iSf>1DA5P zIP>^TblQ2Wai8h^i-%TvZv7uIeQAc^zZIT9JK$jZc|`{XJ8|pYjpa5ekJmY`@q6I# zOmO9)`U-2uMg3d%mLAhRu&xld$wmEI$Y;x zazOQDlQXiK{a#iKk~5yAANse~3t2o<4QTI-7rSnz%G;F5taq;HecSvoL$sIX=V-m`n3)bYbT^K1{ya_&U4HY(yMT5*K&OKbcN{IZ&cruQLG?~`G&f<<-MtDn17ur%y-I+Yh#P4Vt~zKzgSoSx=PKuuj*#dU(;>kOT5N=b-34>{ZF6 zGUHMD**{xQ>|LY?)LS}l&!U@WY~&(u9q#K7I8gIwE{fiLUT4Af=A+qr9%S!@^Hz$2 z6Rm~s-8{T&M$7>V)5U0dKLYjMPk;SqCyKof!Fs<~{;WG{bC&b|;h_Go1MlLNpy|B| e)cZ8O6fKUvg7h9(Z^iP~)do4ejj{)42mk;y9WE{a diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-1.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-1.bin deleted file mode 100644 index f3ca10a69e2ddcb3c729677a4290f24ad3f2e8c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 753 zcmaFCF3Bj!$k-t*^i_It{V&@z4VQ?S3=AO5@Sj0Vl9fqZh#km|V6p)T@XfF@uw^*9 zkK2x6@lPW=h9totJBE|TJM0+t+?;2}uvv7E9YfE%2X+jJat!tiISVE185I5P>>1Ka zgX|g39L%t1cWHSjD`RiA;8GU$iTqJ$jWqu38Mr|dFff2H!+!=wo;XGZmwW~W(|!hqN%2521_nk3 dMkXBMER3^b85z#xF)$eQF)++cfT(9=1OU{u6aN4J diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-2.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-2.bin deleted file mode 100644 index 9e5bc9deaf3ac46862956f4ae54c45ec1615ab07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1285 zcmaFCF3Bj!$kZV%^i_It{jZ2d4VQwM3=AO5@c&ReBZIg%kc9*SJiA=}>^$HG6l!2( zMMy9*LD)?j7CJNW#6dLbFfcH}l*lJ;cLiavMxbJ-`u}hRK#Bh_jWA6h+URGW3kU<5 z4KQ1PY*dXPh3HOQCUe~hguxmS)}U&f9gFG5iP{dVRU=RU!^Xo(+nqod ztPz)u*fgpHEO!E7k~K;zOm_ldk~J!rwL5_@$r{4~@|-}JWQ}gIeoi1vvc@n6Jtq(* zS)+O~pA!g^tdVW%9Y+u*S!3~zIgTJqP$N(QG2lNjim<5})rc)rpkkrq5vaM z6(i$+MxfsR0jWjB`FV*s3QnoTB}C{3>HW|6pNRs!%p5>_gG)*aDj7&~GtgdUkiF#U zWuibY0|k0nDA3DFfnIhB^s-T)mze^+oD}HIAYU&Fx%ucn!~g&Pfmwoxtb;|#s9r3A Kfd7XSbN diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-6.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-6.bin deleted file mode 100644 index f2319d15bc963201d84cb3675ce45067ed4d9ee7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1352 zcmaFCF3Bj!$krh(^i_It{V&OS4VS!`3=AO5@IM1cFfbgbblMAMFwIs5(LPMxO!-Wy zOtDaq$8gGIog)aZcyknt8Q2-vPct$wG}xQ1F|h^f<JhuF(!yZXF9X9K nM+m+Eq(K-7G(gz6>>brR8dkW{2DZF_6k4OqK^g-8|1$sp%XO#| diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-7.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2024/packet-7.bin deleted file mode 100644 index 3ae9b9054c85b8fa615cf4a4cb4ab76840387ef7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1239 zcmaFCF3Bj!$lf6>^i_It{V&OS4VS!`3=AO5@SlN!(F#Z_I5IHYaoX9m!a>`30Y4|h zWd=b376!|kS3Q7|3=AqIUQA#Hg21Yc0jQ0UVP^U#xHd+F4loO=wsl}_`6g_hU^PJ7 sh|&hKje%j+_Z{veXuiEEey30le?<-@`NroFl+~K zvryFJQDbgSd?+)-nA`4lcZWks5P1+|iTM10k_4g@aO0(&-uHX;J>Qq#^Etn#ZUKy` z3{ZBHKGt1u>=)PQ6ZrEX02Yn{;73mgJ0(zOM#KJ`aGcDzW(!?h%RQi&0yFlcg7-b{ zy;3oxd!`i)VZ}1JB`T_eP`uEOhgsjnr)&8-!K2CpEe} z&}}*F%1vMnpk@45K}8UG0-eRruf596ghS!f zaDIv9Ffos6*NfN|Q~)wYD?wx)f=Cr|p>Vyf^0VNG96y6RiO3=iGW&XolC;Qj?Et zfCY6fILOVU4s5ToQrIB=>pwPx<2H=+zir%OjN52)aNwXfCqH#MzOz&cn#iz$}+1eCB_`DkhNTf+$t}*Y-Xy*UAF&3Wc z4a5Bql*$UqMPtP%CD-o;1^O@7&tfX|Jr_@N*QKFY&%*?6MZ@%QlQ|}HNQ1d;RXb-4=$krZ?`IwgZdRO<1acWXIa7l+KU z1b6&Le=+<+xDLN=mr_fVOlxzdh__dnI<_pJ`4MAmuhJlmCA~-e$pyaNX!O~itb1O? zBTujAA(wn_2!^~uVWeg`zSOnO_OY#n5U7)>;FcPCrMgB|$p~)gV>Mcf7sg<#UN>>m zR8b^o4dm1|t}@>}o=kb?q`>6txO!Q40YAGx4ZV9%$2%>L2lb)oE$i4R`PCSIGJ?8@ zI(_@iV^taq{J=6@=wu-+AhKQ9I%;>2`R$uMIfY8QS9biCY+Hkz<+dP;po{A^!Xyj$4J1p*TT7FPS1|6ZeQ!sNR iAVgB@W*$gu=T>!o#8o}2K!LoBb6Y!%y!}u9&-@QS*)=Wz diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-1.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-1.bin deleted file mode 100644 index 5e2bac603a722f33b74e1af72e35f60d68f5fefa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 753 zcmaFKF3Bj!$T;KL|GD>C=Wb&)UgGlR8v}#kUj_!@{|wT?B6CFe85sVFDaZscGW=%{ zx&H8x6~kOLJ6ne2y%%j6?|4vSVm3&9Gw-e$`>epgVPz9mCp=Rdx((^iSI{ zWEH)%V{mxSXwTqroY$Vg$VtYYq0dy+o}qNJnLPu4yrVtC%C7#hE|>z{tb|OsY&kE-(SX!TN=_T~@Aia|Hlj_CDwU diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-10.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-10.bin deleted file mode 100644 index 4689b6c75625effb132cb8d2d6ce64345b358f4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1041 zcmaFKF3Bj!$Tj2I|GD>C=Wb&)UgGlR8v}#kUj_!@|3Q)89m0IiI)p#p=wR&;@4(K+ z%FKWh%)20K*Jr?I_vJvb?V$sXwpbOWFr)QysOk?HxC(OFRBex#+;h&B=yU z2UxI}$I|g!kc{KopG=Pa|5i9~b8@iaR^9hS&ynlAfMZX{BZt}>(;T=s*;#O_w!0ssk@xcQ46icFgU3;qXCxm&1(U z76%S?Ry;1L`lso5>MOV73e}GemHAg3xH#DHgh_c)u;T(oW5=ZvSsYg{*a&n9zA*Xn z>9fO?hAR#m?Ke7n5--9Um*A)Y1{5QMcZZJSuVsRc!j)`}S2k<{h9Ew(k9tNpcG#Oc zHswn@ew=ZUK$v9K#5$_A*g0-A6?J5aA*A~19y`Y?o75dOqS+i9|L$?%=ECQaO%h6u zHb=M|`82OO+zprx3_&(L;r*gl+fntrh~v(cHy!d?LFoja>S=Ym>_1)Vx33mzviA-Z zv}a^w#GR-Z{9WuF>+)nBc|Pzs{%qMoAjQfrc6SJLmUFO}G}D2V+uwnii3Oh1_@K5h OGBYtSFf#%PS^@yfD3yZ% diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-11.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-11.bin deleted file mode 100644 index 183a7098924f18fc4913d5deb7a94f3162aa8b59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1460 zcmaFKF3Bj!$UWoQ|GD>C=Wb&)UgGlR8v}#kUj_!@|LpusZ2auJpXwPI-cM!t;=#bb zkb(bDC?mu68U{vB2F?i#{9S&G48G|GD>C=Wb&)UgGlR8v}#kUj_!@|7?PSB8>bzg7u6HLP89T{Gy`% z{}|ZWfh<|sXaNQe4zSpC2?hZH1_nkRo~b{~SXgVQ5tt$#a6A7O{txFC=%>QCTF)xEd%Q+H5FlDSlQemt@gz%|Cf9^5&zXh!~`$HcAHT{mHJvmIAvQ! z7NpC<<6X$YAR~)D(A$>0;2sP-BLAsEB|u8NpZt!yW+%8i?|M|etjgoPu5gDw0001h zI=lo%yG8{@`NhUXgp9^T;IY0tJ7wxSO*@u0`K#Wy08nPR01yZ|1c@-cWPC8a(?{ zsZn`+IUQdgj?en;>Hhr#ID7)*RMB?AYg3}D!REw$C`mx-%4!+eWWOuHwJK;_)wZ%6 zOvK3E)gq|R>{1c_7P7?FVt>E;hq}HY7{;GDyY9AncGq<^4YG?ETNqtPu@cLgo!qUYAkvD!>`O&i;@Sl&62&Dl zi|)F+$|e%ghl(zCrB4MFk`XAOV$z2qFC`V0#b&c-b~yb74ToVkGrxJBdEWPvc?xk5 zrLlXWxxcQtJ#JB(dw2|37y+CUFNz49GYC3-c;v-2BMK@pFn)&h?Pw|lP_5cQ6u#iY zVT>(ij|FI4e?5U?yFqNl=CqN^tmx+f4RoqRg2^xw?L?$5Kusgw5yJ{X;@AUI!B;SbKyk{wOBcl4bih8mxD zFps6rx~PE;4#rx?Cc$%mg4BA2H+gbC!4n@ky$D%QSc1`wLDn1*Z=9sR(JH~fNQkNr zc&g8M_e3gj!HbnC(QFe}wz1NaBGot@OpxG#8V-KpbzGJ}@O(d9t6^0!R*!QiA79w*dlCA?CRjlQ+sH0uf(ZoR_6;qbCQL9k;pYz1TUqQ0n Ang9R* diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-BUTN.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-BUTN.bin deleted file mode 100644 index 6b98e9a8be7c78fbca5873e45f62d0af0aab3a45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 xcmaFKF3Bj!$UNiP|GD>C=Wb&)UgGlR8v}#kUj_!@|4yMHehfemC9WF|!T^pS4@Cd~ diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-COLL.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-COLL.bin deleted file mode 100644 index 9e725c18198617a4ccda8ac67265e06289a744dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 zcmaFKF3Bj!$UNiP|GD>C=Wff5Pj_M2z`y{)!vCH9eSA2Dzp14&F))01GF=M*ec%u= diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FLBK.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FLBK.bin deleted file mode 100644 index d61bbd1c2d91f3d767736259ca965352dcd39bd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 zcmaFKF3Bj!$UNiP|GD>C=WaW6Yr4zIrwj}SUobET|9A6o@~(Nr!0=RKnhOIE007FF B5(EGM diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FTLP.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-FTLP.bin deleted file mode 100644 index 4ef9c8d86a52e2717c5d415ef4f6044dba87a2b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 xcmaFKF3Bj!$UNiP|GD>C=WhEb>Fts*i-7@zh5x&S_yhC=Wb&F0vkpM7XI(!?jHgYsSv!t5267v$O{Sp diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-OVTK.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-OVTK.bin deleted file mode 100644 index d9532f70bf0608c0f35ae7a3305369d72941f2f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 zcmV+|0Mh^I2N?ko0Rx!U|DE22owi+nphQ{m005r%000yJPgYb*1rh)NzI@0$mk*gg DrezVR diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-PENA.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-PENA.bin deleted file mode 100644 index 97604be392595e936e42398c5077b6dfb3d8b117..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 zcmV+|0Mh^I2N?ko0Rx!U|DE22owjwOdqkSP006SV000yJP(@Bb5Geuw{|x`~yQ2lg Dwp|nz diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-RCWN.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-RCWN.bin deleted file mode 100644 index dd0b88db6fa4818f15d091c9c33c41164a7a66e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 ucmaFKF3Bj!$UHxMBFkBU2!)oMbC=Wb&)UgE+41;YP>UHx1*=190QLPY>ZUJE_| diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SPTP.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SPTP.bin deleted file mode 100644 index 05093ae0379f7c8c1fa999d888ae610d15ce4152..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 zcmaFKF3Bj!$UNiP|GD>C=Wd(0dy&h^FANOtelaiz{|^ob31C{uJ<}P;5w&093;@AH B5zqht diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SSTA.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-SSTA.bin deleted file mode 100644 index 1efd014a84b485eaa07c87738deda4b2ba6f6271..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 ocmaFKF3Bj!$UNiP|GD>C=Wb&_0>b};gF_sdfg%N)lq5kk04G8UkN^Mx diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-STLG.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-STLG.bin deleted file mode 100644 index 214628b6813a14739f9e392051a8504b15fc3da4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 scmaFKF3Bj!$UNiP|GD>C=Wb&Ff)7wE{69Fv$DI`<#Kgdml%Jmi08YUSzW@LL diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-TMPT.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3-TMPT.bin deleted file mode 100644 index 57b12d5bfdd391789c71e349ec6f921ca5b8646e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 xcmaFKF3Bj!$UNiP|GD>C=WgpPYjxp0#lWC?mVrU|e~52D2sg)H7e)pM004(M4jljh diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-3.bin deleted file mode 100644 index ccafd97d7bbb9d3a2234fd713de56fb5c8cd1b18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 ucmaFKF3Bj!$eerTW!qG*U3Pa$*E=~1F))Cz@PDV!5I+VWh!WQg2Vnq9`wYAQ diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-4.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-4.bin deleted file mode 100644 index 075e39997b8b6fc180c84507b2f377c62db26515..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1284 zcmbW%&ubGw6bJA(-J~&#Rog%{By9*eNlKtCX~9Buk_^qZlbv;DW6?uz1&{X7|3FW@ z6h-jTgQ6GD;?@7bgFh~UCyDf=Ab4nB-?E!FflQ0eac4h#XWo0ey-c1>veb)Dc<}AX z!Ta>$>(=2PfcYbU98R%kn1M~VA-L{vA8d=!20F*fW$Z6DNw8E@O=VYvM!+nNL3#R~ z&?e+TAS^tF)zz)vbLFG2K(TBU9k?EF**G^chBqF5fte45!gd6=C~>+Pny#-}+;+v} zs6&ZMcnMOjueA1z)g=&jt4gU_trjJ&U>e-w8aML3-6as7{=&CEy1rOLZAm_Y_}Zku;JNvE~Z7=ABZ#lt&E;tnOwE=j&=>}+IW4pT5S zVM48>wB_~T%KqM8e4NkcqvALOuXMOnzES@KU(oL zhf0cz;9xrzXaH`zx@!#e-;&qArD#f_o98An VXRwda$4tKys~Fm#cTd-U!xKYfA4UKG diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-5.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-5.bin deleted file mode 100644 index 44f71f380e8018686ad1ee5e488c84f60b153ae4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1133 zcmaFKF3Bj!$U5WN|GD>C=Wb&)UgGlR8v}#kUj_!@|Dxhi<_ru82N)O_4sc&zY%}4| zS6fwXer`$q6f2{&v{jBEJZt7bFlJ+5P;ea8F&b8*sbpaDhq$<$`K+0d2aJtB?+4~u c+c$w0cB;bsoRUU~SaYq66EMxv0qk%D02OUmC=Wb&)UgGlR8v}#kUj_!@|1k_8@Xg`HJ}`r+ULHgnF`F=J zGaE36LO~uwTG}c{5S}&jAQ&@(l|zXJWR5Tc1S$c6CJg8KinT zSj$CaQ9Ce$rAHY=8!;O)t1~Mx{$n!V0A7 zCJ;B+d+cYh0r41EZ|N|){;#)UvSEs6O2Xq`Mg~TP7=}7FDSP$36QFT4T>)$cGZQl# zGZz7S)xcpT!L!E-tcOEYlTnF*VJYJ>#!HM>8S(m;A)UeGy0AUd^&Y6b7ga!Ja53{R zb2AIz4=b=`V4?xRWMB}1LJ26q9={@BH=D0lv<2&BpRLVc@qlq6<0QrvjB6MJpa8pG zBe356V$yrTdO0R&G3X~S90$VB3?CWr>-_}O(qM1!gJm~ZFI$x^gU=-R(KVpn2K&`XHIT4kU8l?7_nsepjq-waVRD(ovJZ-$2R84U(m1_o;; zOQv8ZKc)ch055MZFHh{@oWqb|@zJg#wh@vSm`GCMGPGP@E;lZN210y!M) zagKFb3KftG&VWZ$`$5U#n9B<4$=(v2ren+tCfg=7h002jX BQrrLl diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-7.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-7.bin deleted file mode 100644 index 0d1242b4e6a01b535001befcc7dd00578eafa0f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1239 zcmcK3>q`_-7zXh7I6F!zy1TB}l}wG)O1zaIl3Ll!d-`A>%B;*XTg^-|Q^HuGA}`%6 ziy$&Xa*a|;3NJ)hcC&kQ(^jeltw19wYYMGon>jnJ+aJ)E;moqXeV%jPvx9Dvh>p0O z=&{vUOKaNrDD&b^KL8@e0DNpfNGyb&&PxP%voJVzWy0*JTJH$}pwqcPmsl^#OMsw4 zN-lM@<2|z{9h}>Q;G!_s;TFcM`)iyS1NKLdgivg$75i_P#23$F1Yl{+?e;WJ$aOHi z-W6VbA*eWW(RxbOF0T`9{l1tGyIZcHq=|+SmiBV#E(g=;IPeM9U_t6fVaf(y%Jmv0 zJZbQz}Lig+lDg4>KTDz^v$bk$$RT^85=3d zb}%LF_o6n&3XO4;obE87&HJ_Jp8QKKX>}`Ota8>72UF5+-sdr=`>?RHB9oH75DB-| zdQqwRx?0jT(9U|sDphui4d99PGD{158$wAzwuJMK@zfB0S7p{C_L4!5Qk~K?P(4qB zE$(Ka*Gt};zO)H*lKklLf<~2F&-LHKd`jOnaPy+&y?`sdoS9T)T563 z^D(c5$}O=3A97;c_`Ky?GXBC=$^bko?Q?_ldE|FF1vUj7EaiXr(VzhB#rmMRJJ^zAA;8*d+XbkyH*1L*Y{$hob-rlbSH z(TkfX5$~Kw&eMa=`glQQ`KNZVv=MisBTcIX2sLupSX3+|rew)~oCa@Pc3>k8XGg27 zb=1Vzl7aCf(djhsICqqdyUQ9*iQm9<9DLd)W?K4`59K2f?yq93(B%J5+R8qj&Fx*u e6(q^G(hvtH(kyE$+c29tdwfc7?i_#n534_7zBkeU diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-8.bin b/DebugTooling/F1-Telemetry-Fixture-Capture/fixtures/2025/packet-8.bin deleted file mode 100644 index bbfb71868becfaefe90eed93947fffaff3bd91fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1042 zcmcK3Jxjwt7zgmXT%IIN)8)uo%04h~%u1lOwI;@~KPgR`9+EL6ln zp$NgQ`U!Lp#Gy`#APz!(?#1=l%yQ)T@#lZfB_p@SWA2@<9NQ1kYJFz*rpe5cjx!HR zHWSt$;%e`BLn`{v3ZXnWvJ&9rMpjGq_#jLsCO$$@6Ua7sB8fcqHbyZd=`?~ z5hSw7>xAz8_D<>)@Fc?8$aE8r?@&!DP?dT*Rl0sKAhR8?Jk%mqI^SxMI)PYb7n8cl z@Q2hKcn$TknaSNfIYAdZ2lX&i*+s8SW@nJeCQUuT_M$`1qy&M5dQ*QV`Dl;K25@}T zOI6jRyWSvm6)zt3p>9%~Ka#o(zSLLxcJlrusRJlW{cY~DaZPF;aMHp$GTA3RQrqAL bs4w&>+0+85L$GC&Ppyl!7gC!a?q8N)1hrT( diff --git a/DebugTooling/F1-Telemetry-Fixture-Capture/package.json b/DebugTooling/F1-Telemetry-Fixture-Capture/package.json deleted file mode 100644 index ded401d..0000000 --- a/DebugTooling/F1-Telemetry-Fixture-Capture/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "F1-Telemetry-Fixture-Capture", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "capture": "node capture.js" - }, - "keywords": [], - "author": "", - "license": "ISC", - "packageManager": "pnpm@10.11.0", - "type": "module" -} diff --git a/GamesDat/Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md b/GamesDat/Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md deleted file mode 100644 index 0fabbea..0000000 --- a/GamesDat/Telemetry/Sources/Formula1/F1_IMPLEMENTATION_WORKFLOW.md +++ /dev/null @@ -1,322 +0,0 @@ -# F1 UDP Telemetry Implementation Workflow - -This document describes the workflow for implementing F1 game UDP telemetry packet structures from C++ specifications into C# structs for the GamesDat project. - -## Overview - -The F1 games send UDP telemetry data using a specific binary format defined in C++ header files. This workflow guides the process of converting those C++ specifications into C# structs that can deserialize the UDP packets. - -## Prerequisites - -- C++ specification file (typically provided by EA/Codemasters) -- Understanding of C struct layout and C# interop -- Existing F1 implementation to use as reference (e.g., F12025) - -## Workflow Steps - -### 1. Analyze the C++ Specification - -Review the C++ header file to understand: -- **Packet format version** (e.g., 2024, 2025) -- **Packet sizes** (documented in comments) -- **Constants** (e.g., `cs_maxNumCarsInUDPData = 22`) -- **Struct hierarchy** (which structs contain others) -- **Array sizes** (fixed-size arrays in structs) - -### 2. Create the Namespace Folder - -Create a new folder under `Telemetry/Sources/Formula1/` with the naming pattern `F1YYYY` (e.g., `F12024`, `F12025`). - -### 3. Implement the PacketHeader - -Start with the `PacketHeader` struct as it's used by all packet types: - -```csharp -using System.Runtime.InteropServices; - -namespace GamesDat.Core.Telemetry.Sources.Formula1.F1YYYY -{ - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct PacketHeader - { - public ushort m_packetFormat; // YYYY - public byte m_gameYear; // Last two digits e.g. 24 - // ... other fields from spec - } -} -``` - -**Key points:** -- Always use `[StructLayout(LayoutKind.Sequential, Pack = 1)]` -- Keep field names exactly as in C++ spec (helps with debugging) -- Add inline comments from the C++ spec - -### 4. Implement Support Structures - -Create the smaller, reusable structs before the main packet types: - -Examples: `MarshalZone`, `WeatherForecastSample`, `CarMotionData`, etc. - -**Pattern:** -```csharp -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct StructName -{ - public type m_fieldName; // comment from spec - // ... more fields -} -``` - -### 5. Implement Main Packet Structures - -For each packet type from the spec, create: - -1. **Component struct** (e.g., `LapData`) - data for one car/item -2. **Packet struct** (e.g., `PacketLapData`) - complete packet with header and array - -**Pattern:** -```csharp -// Component for one car -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct ComponentData -{ - public type m_field1; - public type m_field2; -} - -// Full packet -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct PacketComponentData -{ - public PacketHeader m_header; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] - public ComponentData[] m_dataArray; - - // ... any additional single fields -} -``` - -### 6. Handle Special Cases - -#### Fixed-Size Arrays -```csharp -[MarshalAs(UnmanagedType.ByValArray, SizeConst = SIZE)] -public Type[] m_arrayField; -``` - -#### Strings/Character Arrays -```csharp -[MarshalAs(UnmanagedType.ByValArray, SizeConst = LENGTH)] -public byte[] m_name; // UTF-8 null-terminated string -``` - -#### Unions (EventDataDetails) -Use `StructLayout(LayoutKind.Explicit)` with `FieldOffset`: - -```csharp -[StructLayout(LayoutKind.Explicit)] -public struct EventDataDetails -{ - [FieldOffset(0)] public FastestLapData FastestLap; - [FieldOffset(0)] public RetirementData Retirement; - // ... all union members at offset 0 -} -``` - -Then create a wrapper with helper methods: -```csharp -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct PacketEventData -{ - public PacketHeader m_header; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - public byte[] m_eventStringCode; - - public EventDataDetails m_eventDetails; - - // Helper to get event code as string - public string EventCode => Encoding.ASCII.GetString(m_eventStringCode); - - // Helper to safely extract typed event details - public T GetEventDetails() where T : struct { ... } -} -``` - -### 7. Type Mapping Reference - -| C++ Type | C# Type | Notes | -|----------|---------|-------| -| `uint8` | `byte` | Unsigned 8-bit | -| `int8` | `sbyte` | Signed 8-bit | -| `uint16` | `ushort` | Unsigned 16-bit | -| `int16` | `short` | Signed 16-bit | -| `uint32` / `uint` | `uint` | Unsigned 32-bit | -| `int32` | `int` | Signed 32-bit | -| `uint64` | `ulong` | Unsigned 64-bit | -| `float` | `float` | 32-bit float | -| `double` | `double` | 64-bit float | -| `char[]` | `byte[]` | UTF-8 strings | - -### 8. Update F1PacketTypeMapper - -Add mappings for all packet types to `F1PacketTypeMapper.cs`: - -```csharp -private static readonly Dictionary<(ushort format, byte id), Type> _packetTypeMap = new() -{ - // F1 YYYY - [(YYYY, (byte)PacketId.Motion)] = typeof(F1YYYY.PacketMotionData), - [(YYYY, (byte)PacketId.Session)] = typeof(F1YYYY.PacketSessionData), - // ... all packet types - - // F1 previous years - // ... -}; -``` - -**Important:** Use fully qualified type names (e.g., `F12024.PacketMotionData`) to avoid conflicts between years. - -### 9. Packet Types Checklist - -Ensure you implement all standard packet types: - -- [ ] Motion (`PacketMotionData`) -- [ ] Session (`PacketSessionData`) -- [ ] Lap Data (`PacketLapData`) -- [ ] Event (`PacketEventData`) -- [ ] Participants (`PacketParticipantsData`) -- [ ] Car Setups (`PacketCarSetupData`) -- [ ] Car Telemetry (`PacketCarTelemetryData`) -- [ ] Car Status (`PacketCarStatusData`) -- [ ] Final Classification (`PacketFinalClassificationData`) -- [ ] Lobby Info (`PacketLobbyInfoData`) -- [ ] Car Damage (`PacketCarDamageData`) -- [ ] Session History (`PacketSessionHistoryData`) -- [ ] Tyre Sets (`PacketTyreSetsData`) -- [ ] Motion Ex (`PacketMotionExData`) -- [ ] Time Trial (`PacketTimeTrialData`) - -### 10. Validation - -After implementation: - -1. **Build the project** - ensure no compilation errors -2. **Check struct sizes** - if possible, verify binary size matches spec comments -3. **Test with real data** - use actual game UDP packets to validate deserialization -4. **Compare with previous year** - look for differences and ensure they're intentional - -## Common Patterns - -### Pattern 1: Simple Data Struct -```csharp -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct SimpleData -{ - public byte m_field1; - public float m_field2; -} -``` - -### Pattern 2: Packet with Array -```csharp -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct PacketWithArray -{ - public PacketHeader m_header; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] - public ItemData[] m_items; -} -``` - -### Pattern 3: Nested Arrays -```csharp -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct DataWithArrays -{ - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - public float[] m_tyresPressure; // 4 tyres -} -``` - -### Pattern 4: Data + Extra Fields -```csharp -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct PacketWithExtras -{ - public PacketHeader m_header; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] - public ItemData[] m_items; - - public byte m_extraField1; - public byte m_extraField2; -} -``` - -## Best Practices - -1. **Maintain exact field order** - C# struct layout must match C++ exactly -2. **Use Pack = 1** - prevents automatic padding/alignment -3. **Keep original naming** - makes comparison with spec easier -4. **Add XML comments** - for complex structs, add summary docs -5. **Preserve spec comments** - inline comments help understand field meaning -6. **Test incrementally** - validate each packet type as you implement it -7. **Reference existing implementations** - use previous years as templates -8. **Watch for year-specific changes** - array sizes, new fields, removed fields - -## File Organization - -Each year's implementation should be self-contained: - -``` -Formula1/ -├── F12024/ -│ ├── PacketHeader.cs -│ ├── PacketMotionData.cs -│ ├── CarMotionData.cs -│ ├── PacketSessionData.cs -│ ├── MarshalZone.cs -│ ├── WeatherForecastSample.cs -│ └── ... (all other packet types) -├── F12025/ -│ └── ... (same structure) -├── F1PacketTypeMapper.cs -├── PacketId.cs (shared enum) -└── EventCodes.cs (shared constants) -``` - -## Troubleshooting - -### Issue: Struct size doesn't match spec -- Check for missing `Pack = 1` -- Verify all arrays have correct `SizeConst` -- Ensure no fields were skipped -- Check type mappings (e.g., `int8` vs `uint8`) - -### Issue: Deserialization fails -- Verify packet header format number matches -- Check PacketTypeMapper has correct entries -- Ensure union types use `LayoutKind.Explicit` -- Validate field order exactly matches spec - -### Issue: Data seems corrupted -- Check endianness (should be little-endian) -- Verify `Pack = 1` is set -- Ensure no extra padding between fields -- Check array sizes match spec constants - -## Example: Complete Implementation - -See `F12024/` folder for a complete reference implementation following this workflow. - -## Version History - -- **2024-02-09**: Created workflow documentation based on F12024 implementation -- **Future**: Update as new patterns or edge cases are discovered - ---- - -*This workflow is designed for GamesDat project contributors and AI agents implementing F1 telemetry support.* From 8157ec02c9b3341985600f18b76f0814a9f137e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Tue, 10 Feb 2026 21:34:52 +0100 Subject: [PATCH 11/25] Add F1 fixture files to test project --- GamesDat.Tests/Fixtures/F1/2023/packet-0.bin | Bin 0 -> 1349 bytes GamesDat.Tests/Fixtures/F1/2023/packet-1.bin | Bin 0 -> 644 bytes GamesDat.Tests/Fixtures/F1/2023/packet-10.bin | Bin 0 -> 953 bytes GamesDat.Tests/Fixtures/F1/2023/packet-11.bin | Bin 0 -> 1460 bytes GamesDat.Tests/Fixtures/F1/2023/packet-12.bin | Bin 0 -> 231 bytes GamesDat.Tests/Fixtures/F1/2023/packet-13.bin | Bin 0 -> 217 bytes GamesDat.Tests/Fixtures/F1/2023/packet-2.bin | Bin 0 -> 1131 bytes GamesDat.Tests/Fixtures/F1/2023/packet-3.bin | Bin 0 -> 45 bytes GamesDat.Tests/Fixtures/F1/2023/packet-4.bin | Bin 0 -> 1306 bytes GamesDat.Tests/Fixtures/F1/2023/packet-5.bin | Bin 0 -> 1107 bytes GamesDat.Tests/Fixtures/F1/2023/packet-6.bin | Bin 0 -> 1352 bytes GamesDat.Tests/Fixtures/F1/2023/packet-7.bin | Bin 0 -> 1239 bytes GamesDat.Tests/Fixtures/F1/2023/packet-8.bin | Bin 0 -> 1020 bytes GamesDat.Tests/Fixtures/F1/2024/packet-0.bin | Bin 0 -> 1349 bytes GamesDat.Tests/Fixtures/F1/2024/packet-1.bin | Bin 0 -> 753 bytes GamesDat.Tests/Fixtures/F1/2024/packet-10.bin | Bin 0 -> 953 bytes GamesDat.Tests/Fixtures/F1/2024/packet-11.bin | Bin 0 -> 1460 bytes GamesDat.Tests/Fixtures/F1/2024/packet-12.bin | Bin 0 -> 231 bytes GamesDat.Tests/Fixtures/F1/2024/packet-13.bin | Bin 0 -> 237 bytes GamesDat.Tests/Fixtures/F1/2024/packet-14.bin | Bin 0 -> 101 bytes GamesDat.Tests/Fixtures/F1/2024/packet-2.bin | Bin 0 -> 1285 bytes GamesDat.Tests/Fixtures/F1/2024/packet-3.bin | Bin 0 -> 45 bytes GamesDat.Tests/Fixtures/F1/2024/packet-4.bin | Bin 0 -> 1350 bytes GamesDat.Tests/Fixtures/F1/2024/packet-5.bin | Bin 0 -> 1133 bytes GamesDat.Tests/Fixtures/F1/2024/packet-6.bin | Bin 0 -> 1352 bytes GamesDat.Tests/Fixtures/F1/2024/packet-7.bin | Bin 0 -> 1239 bytes GamesDat.Tests/Fixtures/F1/2025/packet-0.bin | Bin 0 -> 1349 bytes GamesDat.Tests/Fixtures/F1/2025/packet-1.bin | Bin 0 -> 753 bytes GamesDat.Tests/Fixtures/F1/2025/packet-10.bin | Bin 0 -> 1041 bytes GamesDat.Tests/Fixtures/F1/2025/packet-11.bin | Bin 0 -> 1460 bytes GamesDat.Tests/Fixtures/F1/2025/packet-12.bin | Bin 0 -> 231 bytes GamesDat.Tests/Fixtures/F1/2025/packet-13.bin | Bin 0 -> 273 bytes GamesDat.Tests/Fixtures/F1/2025/packet-14.bin | Bin 0 -> 101 bytes GamesDat.Tests/Fixtures/F1/2025/packet-15.bin | Bin 0 -> 1131 bytes GamesDat.Tests/Fixtures/F1/2025/packet-2.bin | Bin 0 -> 1285 bytes .../Fixtures/F1/2025/packet-3-BUTN.bin | Bin 0 -> 45 bytes .../Fixtures/F1/2025/packet-3-COLL.bin | Bin 0 -> 45 bytes .../Fixtures/F1/2025/packet-3-FLBK.bin | Bin 0 -> 45 bytes .../Fixtures/F1/2025/packet-3-FTLP.bin | Bin 0 -> 45 bytes .../Fixtures/F1/2025/packet-3-LGOT.bin | Bin 0 -> 45 bytes .../Fixtures/F1/2025/packet-3-OVTK.bin | Bin 0 -> 45 bytes .../Fixtures/F1/2025/packet-3-PENA.bin | Bin 0 -> 45 bytes .../Fixtures/F1/2025/packet-3-RCWN.bin | Bin 0 -> 45 bytes .../Fixtures/F1/2025/packet-3-RTMT.bin | Bin 0 -> 45 bytes .../Fixtures/F1/2025/packet-3-SEND.bin | Bin 0 -> 45 bytes .../Fixtures/F1/2025/packet-3-SPTP.bin | Bin 0 -> 45 bytes .../Fixtures/F1/2025/packet-3-SSTA.bin | Bin 0 -> 45 bytes .../Fixtures/F1/2025/packet-3-STLG.bin | Bin 0 -> 45 bytes .../Fixtures/F1/2025/packet-3-TMPT.bin | Bin 0 -> 45 bytes GamesDat.Tests/Fixtures/F1/2025/packet-3.bin | Bin 0 -> 45 bytes GamesDat.Tests/Fixtures/F1/2025/packet-4.bin | Bin 0 -> 1284 bytes GamesDat.Tests/Fixtures/F1/2025/packet-5.bin | Bin 0 -> 1133 bytes GamesDat.Tests/Fixtures/F1/2025/packet-6.bin | Bin 0 -> 1352 bytes GamesDat.Tests/Fixtures/F1/2025/packet-7.bin | Bin 0 -> 1239 bytes GamesDat.Tests/Fixtures/F1/2025/packet-8.bin | Bin 0 -> 1042 bytes 55 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 GamesDat.Tests/Fixtures/F1/2023/packet-0.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2023/packet-1.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2023/packet-10.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2023/packet-11.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2023/packet-12.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2023/packet-13.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2023/packet-2.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2023/packet-3.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2023/packet-4.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2023/packet-5.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2023/packet-6.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2023/packet-7.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2023/packet-8.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2024/packet-0.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2024/packet-1.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2024/packet-10.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2024/packet-11.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2024/packet-12.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2024/packet-13.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2024/packet-14.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2024/packet-2.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2024/packet-3.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2024/packet-4.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2024/packet-5.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2024/packet-6.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2024/packet-7.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-0.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-1.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-10.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-11.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-12.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-13.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-14.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-15.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-2.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-3-BUTN.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-3-COLL.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-3-FLBK.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-3-FTLP.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-3-LGOT.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-3-OVTK.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-3-PENA.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-3-RCWN.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-3-RTMT.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-3-SEND.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-3-SPTP.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-3-SSTA.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-3-STLG.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-3-TMPT.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-3.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-4.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-5.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-6.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-7.bin create mode 100644 GamesDat.Tests/Fixtures/F1/2025/packet-8.bin diff --git a/GamesDat.Tests/Fixtures/F1/2023/packet-0.bin b/GamesDat.Tests/Fixtures/F1/2023/packet-0.bin new file mode 100644 index 0000000000000000000000000000000000000000..2e5a7281acde5cba08b01d8ea030ed382cb3a1cd GIT binary patch literal 1349 zcmaFP&cG0fLPOfzVAHq+O2dM2rFZ+$N3 zP?|Rz>6r}a-WDe`?bsq(_szAsYIAj;f&)V~^B&h@!Mo+ozd4-L70`4#8#*xs>D%u( zkbQ{F_W;A0nWkyd|5<#I_1@kk@4#^Q!0xr@o?7pdI)1okp6!8I&YfuX#xh6&?LC2J zuLH>5dse#-aLU*;yPi3`P}uIkoa`l zz3?B^-pwGr4}vYto*uF)tiEt~X^r23Uslu5^gaaYeUdKmZx4!_--7fyR9U%LmDm)m zyn0wn#pl4|^yz4N`@#0EL9_Q7Nbj^H>xt4D*6F%e4=;KfazK9P92C8Wy(*bhW;{wi z`)3P^y^9oqdQ0c+S#nzybd^CH{gY3O<-bzt$qP6h7 zn}>JJh&f97CnM6vfFSnn6hpLIuV&T`&A9Mm6n;9cAjG`&}WdY`73 ZqQ%iyklq99tytc=+90R5QTE^r0RU5UBvb$Z literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2023/packet-1.bin b/GamesDat.Tests/Fixtures/F1/2023/packet-1.bin new file mode 100644 index 0000000000000000000000000000000000000000..ad8ac56c0be8525ba1afbd469f941a946ca27d23 GIT binary patch literal 644 zcmaFP&cG2d!^rTTfp3PLfi1() zecW~oi+>u~F(e5F*)g0v-eJeE=jJ>+hRvdT>==69J+Na)lw+`G$XO_1&!FgUXU~vc q8f4FK=3s_B!*ka%d#L%N)QAfKMn*;k21Z6!rYlT9zcT_M5C8y!-6EF& literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2023/packet-10.bin b/GamesDat.Tests/Fixtures/F1/2023/packet-10.bin new file mode 100644 index 0000000000000000000000000000000000000000..6dd3d7aca06d2f98c3f7479a32d86f780a8b93c7 GIT binary patch literal 953 scmaFP&cGARtLtr!nMnix`A@E;N H0ISIWCQ%4u literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2023/packet-12.bin b/GamesDat.Tests/Fixtures/F1/2023/packet-12.bin new file mode 100644 index 0000000000000000000000000000000000000000..53ef9c9255cf01d5e66bb332361091e1b9b50fd3 GIT binary patch literal 231 lcmaFP&cGDFCcI2ipJu literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2023/packet-13.bin b/GamesDat.Tests/Fixtures/F1/2023/packet-13.bin new file mode 100644 index 0000000000000000000000000000000000000000..a9b1eebeb9098f717591937f022bb76aab97a280 GIT binary patch literal 217 zcmV;~04D$E2LJ&P0S$;767?ICfBHX{EkqHX0000000030Qzb=0kLN`~Z;od|P0?vW zwovH6jLF}@Ex#nc^IjjoOk|D2ru~A%=)Sc>iK+lYZGDnL@OhF#PPmdnthJItgS#)f zpk_+CTu$ORB<9dKFwS{9KW5;&^(}Y1yYLx0e6gp*qLlSS4#k*415d<5am*&e=dwb> zX8z_uQY_vwM^!^ z69|KKzzk+!fa{nYi|!A5?@dl14Aud45|9no0c4}uaad`)69|KKV6g*Jhf2V5ClDrH zhqS_UClDrHhk{wV69^NpBP<}#351E);TG%X1j5AY2y@VL0%78Hs3-F|fiUqp*rwib f1YzQJ6z`bh2*UVu0ObgS|AdvGsTkG4z{COoO~hhH literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2023/packet-3.bin b/GamesDat.Tests/Fixtures/F1/2023/packet-3.bin new file mode 100644 index 0000000000000000000000000000000000000000..1f6f1006b22c2428da41237bee97c9b2724be92b GIT binary patch literal 45 icmaFP&cGWs5n82k%5u% zKO<1*|A5q@;{3eC90jM;;u3=T7=X6@XZ+7ZwoYaapq;@br3IA?q_`GnCo{-S@^mth zt&@Rloh)SQWF=cCJJ~wf$kxeBwoXp6b!L#OlZCvz@}J@V|NjiYtVRYH)k!GO$POU@ D^yz<5 literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2023/packet-5.bin b/GamesDat.Tests/Fixtures/F1/2023/packet-5.bin new file mode 100644 index 0000000000000000000000000000000000000000..429edc2aa8833646c927bde792601ada275e2cf1 GIT binary patch literal 1107 zcmaFP&cGGcY6^0O~&g)L=8=&{tbkZhme_{ghD+ppY63 Qr9qT5AgO$mq;d!V05TH(3;+NC literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2023/packet-6.bin b/GamesDat.Tests/Fixtures/F1/2023/packet-6.bin new file mode 100644 index 0000000000000000000000000000000000000000..dcfb0f74c9aad41f7fa89eb42b91c6e6aa661695 GIT binary patch literal 1352 zcmaFP&cGl{IN#hasG3{rg>sJFr1Y>kO6STBdRF^HCgLN_SLV@OL|u#u;m4$&>Ce9(h&Inp8)`tQ>B~$ literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2023/packet-7.bin b/GamesDat.Tests/Fixtures/F1/2023/packet-7.bin new file mode 100644 index 0000000000000000000000000000000000000000..060942068b5672e9e7973d7a23b91e64fcdd697c GIT binary patch literal 1239 zcmaFP&cG^i_It{V)6Jx-NqA7$D$30|TQKkg4Fvz;MTDXVVG?ZQ}*}oInvl z0Tu?0SxlZ_bt)xZOb`ul5~nsshMDP~JYd=w;Tj+eJldRoA#5XB8w10t?>pSdu(%B61z003rPghT)U literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2023/packet-8.bin b/GamesDat.Tests/Fixtures/F1/2023/packet-8.bin new file mode 100644 index 0000000000000000000000000000000000000000..660a2dd3ed425c4f7ad491dbf36e280c2d0d0939 GIT binary patch literal 1020 zcmaFP&cG_HIbBYY@|LVDRy^(TR}UEqDIS;iSf>1DA5P zIP>^TblQ2Wai8h^i-%TvZv7uIeQAc^zZIT9JK$jZc|`{XJ8|pYjpa5ekJmY`@q6I# zOmO9)`U-2uMg3d%mLAhRu&xld$wmEI$Y;x zazOQDlQXiK{a#iKk~5yAANse~3t2o<4QTI-7rSnz%G;F5taq;HecSvoL$sIX=V-m`n3)bYbT^K1{ya_&U4HY(yMT5*K&OKbcN{IZ&cruQLG?~`G&f<<-MtDn17ur%y-I+Yh#P4Vt~zKzgSoSx=PKuuj*#dU(;>kOT5N=b-34>{ZF6 zGUHMD**{xQ>|LY?)LS}l&!U@WY~&(u9q#K7I8gIwE{fiLUT4Af=A+qr9%S!@^Hz$2 z6Rm~s-8{T&M$7>V)5U0dKLYjMPk;SqCyKof!Fs<~{;WG{bC&b|;h_Go1MlLNpy|B| e)cZ8O6fKUvg7h9(Z^iP~)do4ejj{)42mk;y9WE{a literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2024/packet-1.bin b/GamesDat.Tests/Fixtures/F1/2024/packet-1.bin new file mode 100644 index 0000000000000000000000000000000000000000..f3ca10a69e2ddcb3c729677a4290f24ad3f2e8c9 GIT binary patch literal 753 zcmaFCF3Bj!$k-t*^i_It{V&@z4VQ?S3=AO5@Sj0Vl9fqZh#km|V6p)T@XfF@uw^*9 zkK2x6@lPW=h9totJBE|TJM0+t+?;2}uvv7E9YfE%2X+jJat!tiISVE185I5P>>1Ka zgX|g39L%t1cWHSjD`RiA;8GU$iTqJ$jWqu38Mr|dFff2H!+!=wo;XGZmwW~W(|!hqN%2521_nk3 dMkXBMER3^b85z#xF)$eQF)++cfT(9=1OU{u6aN4J literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2024/packet-2.bin b/GamesDat.Tests/Fixtures/F1/2024/packet-2.bin new file mode 100644 index 0000000000000000000000000000000000000000..9e5bc9deaf3ac46862956f4ae54c45ec1615ab07 GIT binary patch literal 1285 zcmaFCF3Bj!$kZV%^i_It{jZ2d4VQwM3=AO5@c&ReBZIg%kc9*SJiA=}>^$HG6l!2( zMMy9*LD)?j7CJNW#6dLbFfcH}l*lJ;cLiavMxbJ-`u}hRK#Bh_jWA6h+URGW3kU<5 z4KQ1PY*dXPh3HOQCUe~hguxmS)}U&f9gFG5iP{dVRU=RU!^Xo(+nqod ztPz)u*fgpHEO!E7k~K;zOm_ldk~J!rwL5_@$r{4~@|-}JWQ}gIeoi1vvc@n6Jtq(* zS)+O~pA!g^tdVW%9Y+u*S!3~zIgTJqP$N(QG2lNjim<5})rc)rpkkrq5vaM z6(i$+MxfsR0jWjB`FV*s3QnoTB}C{3>HW|6pNRs!%p5>_gG)*aDj7&~GtgdUkiF#U zWuibY0|k0nDA3DFfnIhB^s-T)mze^+oD}HIAYU&Fx%ucn!~g&Pfmwoxtb;|#s9r3A Kfd7XSbN literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2024/packet-6.bin b/GamesDat.Tests/Fixtures/F1/2024/packet-6.bin new file mode 100644 index 0000000000000000000000000000000000000000..f2319d15bc963201d84cb3675ce45067ed4d9ee7 GIT binary patch literal 1352 zcmaFCF3Bj!$krh(^i_It{V&OS4VS!`3=AO5@IM1cFfbgbblMAMFwIs5(LPMxO!-Wy zOtDaq$8gGIog)aZcyknt8Q2-vPct$wG}xQ1F|h^f<JhuF(!yZXF9X9K nM+m+Eq(K-7G(gz6>>brR8dkW{2DZF_6k4OqK^g-8|1$sp%XO#| literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2024/packet-7.bin b/GamesDat.Tests/Fixtures/F1/2024/packet-7.bin new file mode 100644 index 0000000000000000000000000000000000000000..3ae9b9054c85b8fa615cf4a4cb4ab76840387ef7 GIT binary patch literal 1239 zcmaFCF3Bj!$lf6>^i_It{V&OS4VS!`3=AO5@SlN!(F#Z_I5IHYaoX9m!a>`30Y4|h zWd=b376!|kS3Q7|3=AqIUQA#Hg21Yc0jQ0UVP^U#xHd+F4loO=wsl}_`6g_hU^PJ7 sh|&hKje%j+_Z{veXuiEEey30le?<-@`NroFl+~K zvryFJQDbgSd?+)-nA`4lcZWks5P1+|iTM10k_4g@aO0(&-uHX;J>Qq#^Etn#ZUKy` z3{ZBHKGt1u>=)PQ6ZrEX02Yn{;73mgJ0(zOM#KJ`aGcDzW(!?h%RQi&0yFlcg7-b{ zy;3oxd!`i)VZ}1JB`T_eP`uEOhgsjnr)&8-!K2CpEe} z&}}*F%1vMnpk@45K}8UG0-eRruf596ghS!f zaDIv9Ffos6*NfN|Q~)wYD?wx)f=Cr|p>Vyf^0VNG96y6RiO3=iGW&XolC;Qj?Et zfCY6fILOVU4s5ToQrIB=>pwPx<2H=+zir%OjN52)aNwXfCqH#MzOz&cn#iz$}+1eCB_`DkhNTf+$t}*Y-Xy*UAF&3Wc z4a5Bql*$UqMPtP%CD-o;1^O@7&tfX|Jr_@N*QKFY&%*?6MZ@%QlQ|}HNQ1d;RXb-4=$krZ?`IwgZdRO<1acWXIa7l+KU z1b6&Le=+<+xDLN=mr_fVOlxzdh__dnI<_pJ`4MAmuhJlmCA~-e$pyaNX!O~itb1O? zBTujAA(wn_2!^~uVWeg`zSOnO_OY#n5U7)>;FcPCrMgB|$p~)gV>Mcf7sg<#UN>>m zR8b^o4dm1|t}@>}o=kb?q`>6txO!Q40YAGx4ZV9%$2%>L2lb)oE$i4R`PCSIGJ?8@ zI(_@iV^taq{J=6@=wu-+AhKQ9I%;>2`R$uMIfY8QS9biCY+Hkz<+dP;po{A^!Xyj$4J1p*TT7FPS1|6ZeQ!sNR iAVgB@W*$gu=T>!o#8o}2K!LoBb6Y!%y!}u9&-@QS*)=Wz literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-1.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-1.bin new file mode 100644 index 0000000000000000000000000000000000000000..5e2bac603a722f33b74e1af72e35f60d68f5fefa GIT binary patch literal 753 zcmaFKF3Bj!$T;KL|GD>C=Wb&)UgGlR8v}#kUj_!@{|wT?B6CFe85sVFDaZscGW=%{ zx&H8x6~kOLJ6ne2y%%j6?|4vSVm3&9Gw-e$`>epgVPz9mCp=Rdx((^iSI{ zWEH)%V{mxSXwTqroY$Vg$VtYYq0dy+o}qNJnLPu4yrVtC%C7#hE|>z{tb|OsY&kE-(SX!TN=_T~@Aia|Hlj_CDwU literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-10.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-10.bin new file mode 100644 index 0000000000000000000000000000000000000000..4689b6c75625effb132cb8d2d6ce64345b358f4c GIT binary patch literal 1041 zcmaFKF3Bj!$Tj2I|GD>C=Wb&)UgGlR8v}#kUj_!@|3Q)89m0IiI)p#p=wR&;@4(K+ z%FKWh%)20K*Jr?I_vJvb?V$sXwpbOWFr)QysOk?HxC(OFRBex#+;h&B=yU z2UxI}$I|g!kc{KopG=Pa|5i9~b8@iaR^9hS&ynlAfMZX{BZt}>(;T=s*;#O_w!0ssk@xcQ46icFgU3;qXCxm&1(U z76%S?Ry;1L`lso5>MOV73e}GemHAg3xH#DHgh_c)u;T(oW5=ZvSsYg{*a&n9zA*Xn z>9fO?hAR#m?Ke7n5--9Um*A)Y1{5QMcZZJSuVsRc!j)`}S2k<{h9Ew(k9tNpcG#Oc zHswn@ew=ZUK$v9K#5$_A*g0-A6?J5aA*A~19y`Y?o75dOqS+i9|L$?%=ECQaO%h6u zHb=M|`82OO+zprx3_&(L;r*gl+fntrh~v(cHy!d?LFoja>S=Ym>_1)Vx33mzviA-Z zv}a^w#GR-Z{9WuF>+)nBc|Pzs{%qMoAjQfrc6SJLmUFO}G}D2V+uwnii3Oh1_@K5h OGBYtSFf#%PS^@yfD3yZ% literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-11.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-11.bin new file mode 100644 index 0000000000000000000000000000000000000000..183a7098924f18fc4913d5deb7a94f3162aa8b59 GIT binary patch literal 1460 zcmaFKF3Bj!$UWoQ|GD>C=Wb&)UgGlR8v}#kUj_!@|LpusZ2auJpXwPI-cM!t;=#bb zkb(bDC?mu68U{vB2F?i#{9S&G48G|GD>C=Wb&)UgGlR8v}#kUj_!@|7?PSB8>bzg7u6HLP89T{Gy`% z{}|ZWfh<|sXaNQe4zSpC2?hZH1_nkRo~b{~SXgVQ5tt$#a6A7O{txFC=%>QCTF)xEd%Q+H5FlDSlQemt@gz%|Cf9^5&zXh!~`$HcAHT{mHJvmIAvQ! z7NpC<<6X$YAR~)D(A$>0;2sP-BLAsEB|u8NpZt!yW+%8i?|M|etjgoPu5gDw0001h zI=lo%yG8{@`NhUXgp9^T;IY0tJ7wxSO*@u0`K#Wy08nPR01yZ|1c@-cWPC8a(?{ zsZn`+IUQdgj?en;>Hhr#ID7)*RMB?AYg3}D!REw$C`mx-%4!+eWWOuHwJK;_)wZ%6 zOvK3E)gq|R>{1c_7P7?FVt>E;hq}HY7{;GDyY9AncGq<^4YG?ETNqtPu@cLgo!qUYAkvD!>`O&i;@Sl&62&Dl zi|)F+$|e%ghl(zCrB4MFk`XAOV$z2qFC`V0#b&c-b~yb74ToVkGrxJBdEWPvc?xk5 zrLlXWxxcQtJ#JB(dw2|37y+CUFNz49GYC3-c;v-2BMK@pFn)&h?Pw|lP_5cQ6u#iY zVT>(ij|FI4e?5U?yFqNl=CqN^tmx+f4RoqRg2^xw?L?$5Kusgw5yJ{X;@AUI!B;SbKyk{wOBcl4bih8mxD zFps6rx~PE;4#rx?Cc$%mg4BA2H+gbC!4n@ky$D%QSc1`wLDn1*Z=9sR(JH~fNQkNr zc&g8M_e3gj!HbnC(QFe}wz1NaBGot@OpxG#8V-KpbzGJ}@O(d9t6^0!R*!QiA79w*dlCA?CRjlQ+sH0uf(ZoR_6;qbCQL9k;pYz1TUqQ0n Ang9R* literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-3-BUTN.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-3-BUTN.bin new file mode 100644 index 0000000000000000000000000000000000000000..6b98e9a8be7c78fbca5873e45f62d0af0aab3a45 GIT binary patch literal 45 xcmaFKF3Bj!$UNiP|GD>C=Wb&)UgGlR8v}#kUj_!@|4yMHehfemC9WF|!T^pS4@Cd~ literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-3-COLL.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-3-COLL.bin new file mode 100644 index 0000000000000000000000000000000000000000..9e725c18198617a4ccda8ac67265e06289a744dd GIT binary patch literal 45 zcmaFKF3Bj!$UNiP|GD>C=Wff5Pj_M2z`y{)!vCH9eSA2Dzp14&F))01GF=M*ec%u= literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-3-FLBK.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-3-FLBK.bin new file mode 100644 index 0000000000000000000000000000000000000000..d61bbd1c2d91f3d767736259ca965352dcd39bd6 GIT binary patch literal 45 zcmaFKF3Bj!$UNiP|GD>C=WaW6Yr4zIrwj}SUobET|9A6o@~(Nr!0=RKnhOIE007FF B5(EGM literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-3-FTLP.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-3-FTLP.bin new file mode 100644 index 0000000000000000000000000000000000000000..4ef9c8d86a52e2717c5d415ef4f6044dba87a2b3 GIT binary patch literal 45 xcmaFKF3Bj!$UNiP|GD>C=WhEb>Fts*i-7@zh5x&S_yhC=Wb&F0vkpM7XI(!?jHgYsSv!t5267v$O{Sp literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-3-OVTK.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-3-OVTK.bin new file mode 100644 index 0000000000000000000000000000000000000000..d9532f70bf0608c0f35ae7a3305369d72941f2f4 GIT binary patch literal 45 zcmV+|0Mh^I2N?ko0Rx!U|DE22owi+nphQ{m005r%000yJPgYb*1rh)NzI@0$mk*gg DrezVR literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-3-PENA.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-3-PENA.bin new file mode 100644 index 0000000000000000000000000000000000000000..97604be392595e936e42398c5077b6dfb3d8b117 GIT binary patch literal 45 zcmV+|0Mh^I2N?ko0Rx!U|DE22owjwOdqkSP006SV000yJP(@Bb5Geuw{|x`~yQ2lg Dwp|nz literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-3-RCWN.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-3-RCWN.bin new file mode 100644 index 0000000000000000000000000000000000000000..dd0b88db6fa4818f15d091c9c33c41164a7a66e0 GIT binary patch literal 45 ucmaFKF3Bj!$UHxMBFkBU2!)oMbC=Wb&)UgE+41;YP>UHx1*=190QLPY>ZUJE_| literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-3-SPTP.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-3-SPTP.bin new file mode 100644 index 0000000000000000000000000000000000000000..05093ae0379f7c8c1fa999d888ae610d15ce4152 GIT binary patch literal 45 zcmaFKF3Bj!$UNiP|GD>C=Wd(0dy&h^FANOtelaiz{|^ob31C{uJ<}P;5w&093;@AH B5zqht literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-3-SSTA.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-3-SSTA.bin new file mode 100644 index 0000000000000000000000000000000000000000..1efd014a84b485eaa07c87738deda4b2ba6f6271 GIT binary patch literal 45 ocmaFKF3Bj!$UNiP|GD>C=Wb&_0>b};gF_sdfg%N)lq5kk04G8UkN^Mx literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-3-STLG.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-3-STLG.bin new file mode 100644 index 0000000000000000000000000000000000000000..214628b6813a14739f9e392051a8504b15fc3da4 GIT binary patch literal 45 scmaFKF3Bj!$UNiP|GD>C=Wb&Ff)7wE{69Fv$DI`<#Kgdml%Jmi08YUSzW@LL literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-3-TMPT.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-3-TMPT.bin new file mode 100644 index 0000000000000000000000000000000000000000..57b12d5bfdd391789c71e349ec6f921ca5b8646e GIT binary patch literal 45 xcmaFKF3Bj!$UNiP|GD>C=WgpPYjxp0#lWC?mVrU|e~52D2sg)H7e)pM004(M4jljh literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-3.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-3.bin new file mode 100644 index 0000000000000000000000000000000000000000..ccafd97d7bbb9d3a2234fd713de56fb5c8cd1b18 GIT binary patch literal 45 ucmaFKF3Bj!$eerTW!qG*U3Pa$*E=~1F))Cz@PDV!5I+VWh!WQg2Vnq9`wYAQ literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-4.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-4.bin new file mode 100644 index 0000000000000000000000000000000000000000..075e39997b8b6fc180c84507b2f377c62db26515 GIT binary patch literal 1284 zcmbW%&ubGw6bJA(-J~&#Rog%{By9*eNlKtCX~9Buk_^qZlbv;DW6?uz1&{X7|3FW@ z6h-jTgQ6GD;?@7bgFh~UCyDf=Ab4nB-?E!FflQ0eac4h#XWo0ey-c1>veb)Dc<}AX z!Ta>$>(=2PfcYbU98R%kn1M~VA-L{vA8d=!20F*fW$Z6DNw8E@O=VYvM!+nNL3#R~ z&?e+TAS^tF)zz)vbLFG2K(TBU9k?EF**G^chBqF5fte45!gd6=C~>+Pny#-}+;+v} zs6&ZMcnMOjueA1z)g=&jt4gU_trjJ&U>e-w8aML3-6as7{=&CEy1rOLZAm_Y_}Zku;JNvE~Z7=ABZ#lt&E;tnOwE=j&=>}+IW4pT5S zVM48>wB_~T%KqM8e4NkcqvALOuXMOnzES@KU(oL zhf0cz;9xrzXaH`zx@!#e-;&qArD#f_o98An VXRwda$4tKys~Fm#cTd-U!xKYfA4UKG literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-5.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-5.bin new file mode 100644 index 0000000000000000000000000000000000000000..44f71f380e8018686ad1ee5e488c84f60b153ae4 GIT binary patch literal 1133 zcmaFKF3Bj!$U5WN|GD>C=Wb&)UgGlR8v}#kUj_!@|Dxhi<_ru82N)O_4sc&zY%}4| zS6fwXer`$q6f2{&v{jBEJZt7bFlJ+5P;ea8F&b8*sbpaDhq$<$`K+0d2aJtB?+4~u c+c$w0cB;bsoRUU~SaYq66EMxv0qk%D02OUmC=Wb&)UgGlR8v}#kUj_!@|1k_8@Xg`HJ}`r+ULHgnF`F=J zGaE36LO~uwTG}c{5S}&jAQ&@(l|zXJWR5Tc1S$c6CJg8KinT zSj$CaQ9Ce$rAHY=8!;O)t1~Mx{$n!V0A7 zCJ;B+d+cYh0r41EZ|N|){;#)UvSEs6O2Xq`Mg~TP7=}7FDSP$36QFT4T>)$cGZQl# zGZz7S)xcpT!L!E-tcOEYlTnF*VJYJ>#!HM>8S(m;A)UeGy0AUd^&Y6b7ga!Ja53{R zb2AIz4=b=`V4?xRWMB}1LJ26q9={@BH=D0lv<2&BpRLVc@qlq6<0QrvjB6MJpa8pG zBe356V$yrTdO0R&G3X~S90$VB3?CWr>-_}O(qM1!gJm~ZFI$x^gU=-R(KVpn2K&`XHIT4kU8l?7_nsepjq-waVRD(ovJZ-$2R84U(m1_o;; zOQv8ZKc)ch055MZFHh{@oWqb|@zJg#wh@vSm`GCMGPGP@E;lZN210y!M) zagKFb3KftG&VWZ$`$5U#n9B<4$=(v2ren+tCfg=7h002jX BQrrLl literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-7.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-7.bin new file mode 100644 index 0000000000000000000000000000000000000000..0d1242b4e6a01b535001befcc7dd00578eafa0f3 GIT binary patch literal 1239 zcmcK3>q`_-7zXh7I6F!zy1TB}l}wG)O1zaIl3Ll!d-`A>%B;*XTg^-|Q^HuGA}`%6 ziy$&Xa*a|;3NJ)hcC&kQ(^jeltw19wYYMGon>jnJ+aJ)E;moqXeV%jPvx9Dvh>p0O z=&{vUOKaNrDD&b^KL8@e0DNpfNGyb&&PxP%voJVzWy0*JTJH$}pwqcPmsl^#OMsw4 zN-lM@<2|z{9h}>Q;G!_s;TFcM`)iyS1NKLdgivg$75i_P#23$F1Yl{+?e;WJ$aOHi z-W6VbA*eWW(RxbOF0T`9{l1tGyIZcHq=|+SmiBV#E(g=;IPeM9U_t6fVaf(y%Jmv0 zJZbQz}Lig+lDg4>KTDz^v$bk$$RT^85=3d zb}%LF_o6n&3XO4;obE87&HJ_Jp8QKKX>}`Ota8>72UF5+-sdr=`>?RHB9oH75DB-| zdQqwRx?0jT(9U|sDphui4d99PGD{158$wAzwuJMK@zfB0S7p{C_L4!5Qk~K?P(4qB zE$(Ka*Gt};zO)H*lKklLf<~2F&-LHKd`jOnaPy+&y?`sdoS9T)T563 z^D(c5$}O=3A97;c_`Ky?GXBC=$^bko?Q?_ldE|FF1vUj7EaiXr(VzhB#rmMRJJ^zAA;8*d+XbkyH*1L*Y{$hob-rlbSH z(TkfX5$~Kw&eMa=`glQQ`KNZVv=MisBTcIX2sLupSX3+|rew)~oCa@Pc3>k8XGg27 zb=1Vzl7aCf(djhsICqqdyUQ9*iQm9<9DLd)W?K4`59K2f?yq93(B%J5+R8qj&Fx*u e6(q^G(hvtH(kyE$+c29tdwfc7?i_#n534_7zBkeU literal 0 HcmV?d00001 diff --git a/GamesDat.Tests/Fixtures/F1/2025/packet-8.bin b/GamesDat.Tests/Fixtures/F1/2025/packet-8.bin new file mode 100644 index 0000000000000000000000000000000000000000..bbfb71868becfaefe90eed93947fffaff3bd91fe GIT binary patch literal 1042 zcmcK3Jxjwt7zgmXT%IIN)8)uo%04h~%u1lOwI;@~KPgR`9+EL6ln zp$NgQ`U!Lp#Gy`#APz!(?#1=l%yQ)T@#lZfB_p@SWA2@<9NQ1kYJFz*rpe5cjx!HR zHWSt$;%e`BLn`{v3ZXnWvJ&9rMpjGq_#jLsCO$$@6Ua7sB8fcqHbyZd=`?~ z5hSw7>xAz8_D<>)@Fc?8$aE8r?@&!DP?dT*Rl0sKAhR8?Jk%mqI^SxMI)PYb7n8cl z@Q2hKcn$TknaSNfIYAdZ2lX&i*+s8SW@nJeCQUuT_M$`1qy&M5dQ*QV`Dl;K25@}T zOI6jRyWSvm6)zt3p>9%~Ka#o(zSLLxcJlrusRJlW{cY~DaZPF;aMHp$GTA3RQrqAL bs4w&>+0+85L$GC&Ppyl!7gC!a?q8N)1hrT( literal 0 HcmV?d00001 From 56a7f24359b2e0fea0a144903ee396f5adf79e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Tue, 10 Feb 2026 21:36:06 +0100 Subject: [PATCH 12/25] Add refactoring plan for telemetry source interface --- docs/telemetry-source-api-refactoring.md | 556 +++++++++++++++++++++++ 1 file changed, 556 insertions(+) create mode 100644 docs/telemetry-source-api-refactoring.md diff --git a/docs/telemetry-source-api-refactoring.md b/docs/telemetry-source-api-refactoring.md new file mode 100644 index 0000000..840401e --- /dev/null +++ b/docs/telemetry-source-api-refactoring.md @@ -0,0 +1,556 @@ +# Refactor TelemetrySourceBase API from Pull to Callback-Based + +## Context + +The current `TelemetrySourceBase` API forces all telemetry sources to implement `ReadContinuousAsync()` returning `IAsyncEnumerable`. This creates a **pull-based model** where consumers iterate over data. + +### The Problem + +This pull model doesn't fit all source types naturally: + +- **Memory-Mapped Sources** ✓ Natural fit - poll at intervals, yield data +- **UDP Sources** ✗ Awkward - inherently push-based (packets arrive asynchronously), but wrapped in `IAsyncEnumerable` using blocking `ReceiveAsync()` +- **File Watcher Sources** ✗ Awkward - inherently push-based (file system events), but converted to pull using `Channel` adapters + +The result is unnecessary complexity: push sources are forced through adapters to convert events → pull semantics → callbacks (in GameSession). + +### Key Insight + +**All telemetry sources are fundamentally push-based at the consumer level.** GameSession wants to be notified when data arrives, regardless of whether that data came from polling, network packets, or file system events. The current abstraction fights this natural flow. + +## Recommended Approach: Callback-Based API + +Replace `IAsyncEnumerable ReadContinuousAsync(CancellationToken)` with `Task RunAsync(Action onData, CancellationToken)`. + +### Benefits + +1. **Natural for push sources** - UDP and file watchers can directly invoke the callback +2. **Works fine for pull sources** - Memory-mapped just calls the callback in their polling loop +3. **Removes adapters** - No more Channel in FileWatcher, no awkward ReceiveAsync wrapping +4. **Simpler GameSession** - Single unified runner instead of different patterns +5. **Clear data flow** - `RunAsync(onData, ct)` makes it explicit that data flows through the callback + +## Implementation Plan + +### 1. Update `ITelemetrySource` Interface + +**File:** `C:\Users\maste\source\repos\GamesDat\GamesDat\Telemetry\ITelemetrySource.cs` + +**Changes:** +- Replace `IAsyncEnumerable ReadContinuousAsync(CancellationToken ct = default)` +- With `Task RunAsync(Action onData, CancellationToken ct = default)` + +**Before:** +```csharp +public interface ITelemetrySource : IDisposable +{ + string? OutputPath { get; set; } + ISessionWriter? Writer { get; set; } + + IAsyncEnumerable ReadContinuousAsync(CancellationToken ct = default); + + ITelemetrySource OutputTo(string path); + ITelemetrySource UseWriter(ISessionWriter writer); + ITelemetrySource RealtimeOnly(); + ITelemetrySource AutoOutput(); +} +``` + +**After:** +```csharp +public interface ITelemetrySource : IDisposable +{ + string? OutputPath { get; set; } + ISessionWriter? Writer { get; set; } + + /// + /// Start the telemetry source and invoke onData callback for each data item. + /// This method should block until cancellation is requested. + /// + Task RunAsync(Action onData, CancellationToken ct = default); + + ITelemetrySource OutputTo(string path); + ITelemetrySource UseWriter(ISessionWriter writer); + ITelemetrySource RealtimeOnly(); + ITelemetrySource AutoOutput(); +} +``` + +### 2. Update `TelemetrySourceBase` Base Class + +**File:** `C:\Users\maste\source\repos\GamesDat\GamesDat\Telemetry\TelemetrySourceBase.cs` + +**Changes:** +- Update abstract method signature from `ReadContinuousAsync` to `RunAsync` +- Keep all fluent API methods unchanged + +**Before:** +```csharp +public abstract IAsyncEnumerable ReadContinuousAsync(CancellationToken ct = default); +``` + +**After:** +```csharp +public abstract Task RunAsync(Action onData, CancellationToken ct = default); +``` + +### 3. Update `UdpSourceBase` - Remove IAsyncEnumerable Wrapper + +**File:** `C:\Users\maste\source\repos\GamesDat\GamesDat\Telemetry\Sources\UdpSourceBase.cs` + +**Changes:** +- Change method from `IAsyncEnumerable ReadContinuousAsync()` to `Task RunAsync(Action onData, ...)` +- Remove `yield return`, directly invoke callback + +**Before:** +```csharp +public override async IAsyncEnumerable ReadContinuousAsync([EnumeratorCancellation] CancellationToken ct = default) +{ + _isListening = true; + try + { + while (!ct.IsCancellationRequested) + { + var result = await _listener.ReceiveAsync(ct); + var data = result.Buffer; + foreach (var item in ProcessData(data)) + { + yield return item; // Awkward wrapper + } + } + } + finally + { + _isListening = false; + _listener.Dispose(); + } +} +``` + +**After:** +```csharp +public override async Task RunAsync(Action onData, CancellationToken ct = default) +{ + _isListening = true; + try + { + while (!ct.IsCancellationRequested) + { + var result = await _listener.ReceiveAsync(ct); + var data = result.Buffer; + foreach (var item in ProcessData(data)) + { + onData(item); // Direct callback - clean! + } + } + } + finally + { + _isListening = false; + _listener.Dispose(); + } +} +``` + +### 4. Update `FileWatcherSourceBase` - Remove Channel Adapter + +**File:** `C:\Users\maste\source\repos\GamesDat\GamesDat\Telemetry\Sources\FileWatcherSourceBase.cs` + +**Changes:** +- Change method from `IAsyncEnumerable ReadContinuousAsync()` to `Task RunAsync(Action onData, ...)` +- Remove Channel adapter +- Directly invoke callback from event handlers +- Use `Task.Delay(Timeout.Infinite, ct)` to keep alive until cancellation + +**Before (lines 157-213):** +```csharp +public override async IAsyncEnumerable ReadContinuousAsync( + [EnumeratorCancellation] CancellationToken ct = default) +{ + _cts = CancellationTokenSource.CreateLinkedTokenSource(ct); + var channel = System.Threading.Channels.Channel.CreateUnbounded(); // Adapter + + var watchers = new List(); + + foreach (var pattern in Patterns) + { + var watcher = new FileSystemWatcher(Path, pattern) { ... }; + watcher.Created += (s, e) => OnFileEvent(e.FullPath, channel.Writer); // Writes to channel + watcher.Changed += (s, e) => OnFileEvent(e.FullPath, channel.Writer); + watcher.EnableRaisingEvents = true; + watchers.Add(watcher); + } + + try + { + // Scan existing files + foreach (var pattern in Patterns) { ... } + + // Read from channel - pull from push + await foreach (var filePath in channel.Reader.ReadAllAsync(_cts.Token)) + { + yield return filePath; + } + } + finally { ... } +} + +private void OnFileEvent(string filePath, ChannelWriter writer) +{ + // Debounce logic... + writer.TryWrite(filePath); // Write to channel +} +``` + +**After:** +```csharp +public override async Task RunAsync(Action onData, CancellationToken ct = default) +{ + _cts = CancellationTokenSource.CreateLinkedTokenSource(ct); + var watchers = new List(); + + foreach (var pattern in Patterns) + { + var watcher = new FileSystemWatcher(Path, pattern) + { + IncludeSubdirectories = IncludeSubdirectories, + NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime + }; + + // Direct callback from event handler + watcher.Created += (s, e) => OnFileEvent(e.FullPath, onData); + watcher.Changed += (s, e) => OnFileEvent(e.FullPath, onData); + + watcher.EnableRaisingEvents = true; + watchers.Add(watcher); + } + + try + { + // Scan existing files on startup + foreach (var pattern in Patterns) + { + var existingFiles = Directory.GetFiles(Path, pattern, + IncludeSubdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly); + + foreach (var file in existingFiles.OrderBy(f => File.GetCreationTime(f))) + { + if (!_processedFiles.Contains(file) && ShouldProcessFile(file)) + { + _processedFiles.Add(file); + onData(file); // Direct callback + } + } + } + + // Keep alive until cancelled + await Task.Delay(Timeout.Infinite, _cts.Token); + } + catch (TaskCanceledException) + { + // Normal cancellation + } + finally + { + foreach (var watcher in watchers) + { + watcher.EnableRaisingEvents = false; + watcher.Dispose(); + } + } +} + +private void OnFileEvent(string filePath, Action onData) +{ + // Debounce logic + var now = DateTime.UtcNow; + if (_lastEventTime.TryGetValue(filePath, out var lastTime)) + { + if (now - lastTime < DebounceDelay) + return; + } + + _lastEventTime[filePath] = now; + + // Only emit each file once and check custom filter + if (!_processedFiles.Contains(filePath) && ShouldProcessFile(filePath)) + { + _processedFiles.Add(filePath); + onData(filePath); // Direct callback + } +} +``` + +### 5. Update `MemoryMappedFileSource` - Convert Yield to Callback + +**File:** `C:\Users\maste\source\repos\GamesDat\GamesDat\Telemetry\Sources\MemoryMappedFileSource.cs` + +**Changes:** +- Change from `IAsyncEnumerable` to `Task` +- Replace `yield return` with direct callback invocation + +**Before:** +```csharp +public override async IAsyncEnumerable ReadContinuousAsync([EnumeratorCancellation] CancellationToken ct = default) +{ + _mmf = MemoryMappedFile.OpenExisting(_mapName, MemoryMappedFileRights.Read); + _accessor = _mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read); + + try + { + while (!ct.IsCancellationRequested) + { + T data; + _accessor.Read(0, out data); + yield return data; + await Task.Delay(_pollInterval, ct); + } + } + finally + { + _accessor?.Dispose(); + _mmf?.Dispose(); + } +} +``` + +**After:** +```csharp +public override async Task RunAsync(Action onData, CancellationToken ct = default) +{ + _mmf = MemoryMappedFile.OpenExisting(_mapName, MemoryMappedFileRights.Read); + _accessor = _mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read); + + try + { + while (!ct.IsCancellationRequested) + { + T data; + _accessor.Read(0, out data); + onData(data); // Callback instead of yield + await Task.Delay(_pollInterval, ct); + } + } + finally + { + _accessor?.Dispose(); + _mmf?.Dispose(); + } +} +``` + +### 6. Update All Other Memory-Mapped Sources + +Apply same changes to: +- `TrackmaniaMemoryMappedFileSource.cs` +- Any ACC sources (Physics, Graphics, Static) +- `ACCCombinedSource.cs` - Update to use `RunAsync` when consuming child sources + +### 7. Update `GameSession.SourceRunner` Consumer + +**File:** `C:\Users\maste\source\repos\GamesDat\GamesDat\GameSession.cs` + +**Changes:** +- Replace `await foreach` loop with `RunAsync()` callback +- Simplify runner logic + +**Before (lines 140-177):** +```csharp +private async Task RunAsync(CancellationToken ct) +{ + try + { + Console.WriteLine($"[{SourceTypeName}] Starting source..."); + int frameCount = 0; + + await foreach (var data in _source.ReadContinuousAsync(ct)) // Pull model + { + frameCount++; + + if (frameCount <= 3) + { + Console.WriteLine($"[{SourceTypeName}] Frame {frameCount} received"); + } + + // Write to file if writer configured + if (_writer != null) + { + _writer.WriteFrame(data, DateTime.UtcNow.Ticks); + } + + // Invoke callbacks + if (_callbacks.TryGetValue(_dataType, out var callbacks)) + { + foreach (var callback in callbacks) + { + try + { + callback.DynamicInvoke(data); + } + catch (Exception ex) + { + Console.WriteLine($"[{SourceTypeName}] ERROR in callback: {ex.Message}"); + } + } + } + } + + Console.WriteLine($"[{SourceTypeName}] Source ended after {frameCount} frames"); + } + catch (Exception ex) { ... } +} +``` + +**After:** +```csharp +private async Task RunAsync(CancellationToken ct) +{ + try + { + Console.WriteLine($"[{SourceTypeName}] Starting source..."); + int frameCount = 0; + + await _source.RunAsync(data => // Callback model - simpler! + { + frameCount++; + + if (frameCount <= 3) + { + Console.WriteLine($"[{SourceTypeName}] Frame {frameCount} received"); + } + + // Write to file if writer configured + if (_writer != null) + { + _writer.WriteFrame(data, DateTime.UtcNow.Ticks); + } + + // Invoke callbacks + if (_callbacks.TryGetValue(_dataType, out var callbacks)) + { + foreach (var callback in callbacks) + { + try + { + callback.DynamicInvoke(data); + } + catch (Exception ex) + { + Console.WriteLine($"[{SourceTypeName}] ERROR in callback: {ex.Message}"); + } + } + } + }, ct); + + Console.WriteLine($"[{SourceTypeName}] Source ended after {frameCount} frames"); + } + catch (Exception ex) { ... } +} +``` + +## Critical Files to Modify + +1. ✓ `C:\Users\maste\source\repos\GamesDat\GamesDat\Telemetry\ITelemetrySource.cs` - Interface signature +2. ✓ `C:\Users\maste\source\repos\GamesDat\GamesDat\Telemetry\TelemetrySourceBase.cs` - Abstract base class +3. ✓ `C:\Users\maste\source\repos\GamesDat\GamesDat\Telemetry\Sources\UdpSourceBase.cs` - Remove IAsyncEnumerable wrapper +4. ✓ `C:\Users\maste\source\repos\GamesDat\GamesDat\Telemetry\Sources\FileWatcherSourceBase.cs` - Remove Channel adapter +5. ✓ `C:\Users\maste\source\repos\GamesDat\GamesDat\Telemetry\Sources\MemoryMappedFileSource.cs` - Convert yield to callback +6. ✓ `C:\Users\maste\source\repos\GamesDat\GamesDat\GameSession.cs` - Update SourceRunner consumer +7. ⚠️ All game-specific implementations inheriting from these bases (should automatically work) +8. ⚠️ `TrackmaniaMemoryMappedFileSource.cs` - If it overrides ReadContinuousAsync +9. ⚠️ `ACCCombinedSource.cs` - Update to use RunAsync for child sources +10. ⚠️ Any tests using `ReadContinuousAsync` directly + +## Verification Plan + +### 1. Compilation Check +```bash +dotnet build +``` +- Ensure all sources compile without errors +- Check for any missed implementations + +### 2. Unit Tests +Run existing test suites: +```bash +dotnet test +``` +- FileWatcherSourceTests should still pass (behavior unchanged, just different API) +- Any integration tests using sources should be updated + +### 3. Manual Testing + +**Test UDP Source (F1):** +1. Start F1 game with telemetry enabled +2. Run F1 demo application +3. Verify packets are received and logged +4. Check output file is written correctly + +**Test File Watcher (Trackmania Replays):** +1. Configure TrackmaniaReplayFileSource +2. Save a replay in Trackmania +3. Verify file path is detected and callback fires +4. Check debouncing works (no duplicates) + +**Test Memory-Mapped (ACC):** +1. Start Assetto Corsa Competizione +2. Run ACC demo application +3. Verify telemetry data streams correctly +4. Check combined source aggregates Physics/Graphics/Static properly + +### 4. WPF Demo Applications +- Launch Demo.Wpf application +- Test F1RealtimeSourceViewModel +- Test FileWatcherSourceViewModel +- Verify UI updates correctly with new API + +### 5. Behavior Verification Checklist +- [ ] UDP sources still receive packets correctly +- [ ] File watchers still detect new files +- [ ] Memory-mapped sources still poll at correct intervals +- [ ] Debouncing in file watchers still works +- [ ] Deduplication (processed files) still prevents duplicates +- [ ] GameSession callbacks (OnData) still fire +- [ ] File writing still works with configured writers +- [ ] Fluent API (OutputTo, RealtimeOnly, etc.) still works +- [ ] Disposal/cleanup still happens properly + +## Additional Considerations + +### Optional: IAsyncEnumerable Extension Method + +If needed for backward compatibility or alternative consumption, add an extension method: + +**File:** Create `C:\Users\maste\source\repos\GamesDat\GamesDat\Telemetry\TelemetrySourceExtensions.cs` + +```csharp +public static class TelemetrySourceExtensions +{ + public static async IAsyncEnumerable ReadContinuousAsync( + this ITelemetrySource source, + [EnumeratorCancellation] CancellationToken ct = default) + { + var channel = Channel.CreateUnbounded(); + + var runTask = source.RunAsync(data => channel.Writer.TryWrite(data), ct); + + await foreach (var item in channel.Reader.ReadAllAsync(ct)) + { + yield return item; + } + + await runTask; + } +} +``` + +This allows consumers who prefer pull semantics to still use `await foreach`. + +## Summary + +This refactoring removes the awkward pull-model abstraction and embraces the natural push-based flow of telemetry data. Each source type now uses its most natural mechanism: +- UDP sources directly invoke callbacks when packets arrive +- File watchers directly invoke callbacks when files are detected +- Memory-mapped sources invoke callbacks in their polling loop + +The result is simpler, cleaner code with no unnecessary adapters, while maintaining the same fluent configuration API and consumer behavior. From c2941a39653f781c058b779e1e0871242be2358a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Tue, 10 Feb 2026 21:49:13 +0100 Subject: [PATCH 13/25] Remove debug logging --- .../Formula1/F1RealtimeTelemetrySource.cs | 5 ---- .../Formula1/F1TelemetryFrameExtensions.cs | 24 +++---------------- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs b/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs index dd42af8..7afd801 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F1RealtimeTelemetrySource.cs @@ -16,7 +16,6 @@ protected override IEnumerable ProcessData(byte[] data) { if (data.Length < MinRoutingHeaderSize) { - System.Diagnostics.Debug.WriteLine($"[F1] Packet too small: {data.Length} bytes (min {MinRoutingHeaderSize})"); yield break; } @@ -26,19 +25,15 @@ protected override IEnumerable ProcessData(byte[] data) var packetType = F1PacketTypeMapper.GetPacketType(packetFormat, packetId); if (packetType == null) { - System.Diagnostics.Debug.WriteLine($"[F1] Unknown packet: Format={packetFormat}, PacketId={packetId}"); yield break; } var expectedSize = Marshal.SizeOf(packetType); if (data.Length < expectedSize) { - System.Diagnostics.Debug.WriteLine($"[F1] Packet size mismatch: PacketId={packetId}, Expected={expectedSize}, Actual={data.Length}"); yield break; } - System.Diagnostics.Debug.WriteLine($"[F1] Packet received: Format={packetFormat}, PacketId={packetId}, Size={data.Length}"); - yield return new F1TelemetryFrame(packetFormat, packetId, data); } diff --git a/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrameExtensions.cs b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrameExtensions.cs index f20f62b..cdf640b 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrameExtensions.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F1TelemetryFrameExtensions.cs @@ -44,31 +44,13 @@ public static unsafe T GetPacket(this F1TelemetryFrame frame) where T : struc return null; var expectedSize = Marshal.SizeOf(packetType); - - var msg = $"[F1 Deserialize] Type={packetType.Name}, Expected={expectedSize}, Actual={frame.DataLength}, PacketId={frame.PacketId}"; - System.Diagnostics.Debug.WriteLine(msg); - Console.WriteLine(msg); - if (frame.DataLength < expectedSize) { - var errorMsg = $"Packet data too small. Expected {expectedSize} bytes for {packetType.Name}, got {frame.DataLength}"; - Console.WriteLine($"[F1 ERROR] {errorMsg}"); - throw new InvalidOperationException(errorMsg); + throw new InvalidOperationException($"Packet data too small. Expected {expectedSize} bytes for {packetType.Name}, got {frame.DataLength}"); } - try - { - var result = Marshal.PtrToStructure((IntPtr)frame.RawData, packetType); - Console.WriteLine($"[F1 Deserialize] SUCCESS for PacketId={frame.PacketId}"); - return result; - } - catch (Exception ex) - { - var errorMsg = $"Marshal.PtrToStructure failed: {ex.GetType().Name} - {ex.Message}"; - Console.WriteLine($"[F1 ERROR] {errorMsg}"); - System.Diagnostics.Debug.WriteLine($"[F1 ERROR] {errorMsg}"); - throw; - } + var result = Marshal.PtrToStructure((IntPtr)frame.RawData, packetType); + return result; } /// From 3831a8db54b930ef8ab3bddd6380fc358514e3b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Tue, 10 Feb 2026 22:20:49 +0100 Subject: [PATCH 14/25] Added test cases for F1 packet parsing (super basic) --- GamesDat.Tests/F1PacketParsingTest.cs | 101 +++++++++ GamesDat.Tests/GamesDat.Tests.csproj | 174 ++++++++++++++ docs/f1-packet-testing-plan.md | 312 ++++++++++++++++++++++++++ 3 files changed, 587 insertions(+) create mode 100644 GamesDat.Tests/F1PacketParsingTest.cs create mode 100644 docs/f1-packet-testing-plan.md diff --git a/GamesDat.Tests/F1PacketParsingTest.cs b/GamesDat.Tests/F1PacketParsingTest.cs new file mode 100644 index 0000000..ae03b3b --- /dev/null +++ b/GamesDat.Tests/F1PacketParsingTest.cs @@ -0,0 +1,101 @@ +using System.Runtime.InteropServices; +using GamesDat.Core.Telemetry.Sources.Formula1; + +namespace GamesDat.Tests; + +public class F1PacketParsingTest +{ + [Theory] + [InlineData("Fixtures/F1/2025/packet-0.bin", 2025, PacketId.Motion, typeof(Core.Telemetry.Sources.Formula1.F12025.MotionData))] + [InlineData("Fixtures/F1/2025/packet-1.bin", 2025, PacketId.Session, typeof(Core.Telemetry.Sources.Formula1.F12025.SessionData))] + [InlineData("Fixtures/F1/2025/packet-2.bin", 2025, PacketId.LapData, typeof(Core.Telemetry.Sources.Formula1.F12025.PacketLapData))] + [InlineData("Fixtures/F1/2025/packet-4.bin", 2025, PacketId.Participants, typeof(Core.Telemetry.Sources.Formula1.F12025.ParticipantData))] + [InlineData("Fixtures/F1/2025/packet-5.bin", 2025, PacketId.CarSetups, typeof(Core.Telemetry.Sources.Formula1.F12025.CarSetupData))] + [InlineData("Fixtures/F1/2025/packet-6.bin", 2025, PacketId.CarTelemetry, typeof(Core.Telemetry.Sources.Formula1.F12025.CarTelemetryData))] + [InlineData("Fixtures/F1/2025/packet-7.bin", 2025, PacketId.CarStatus, typeof(Core.Telemetry.Sources.Formula1.F12025.PacketCarStatusData))] + [InlineData("Fixtures/F1/2025/packet-8.bin", 2025, PacketId.FinalClassification, typeof(Core.Telemetry.Sources.Formula1.F12025.PacketFinalClassificationData))] + [InlineData("Fixtures/F1/2025/packet-10.bin", 2025, PacketId.CarDamage, typeof(Core.Telemetry.Sources.Formula1.F12025.PacketCarDamageData))] + [InlineData("Fixtures/F1/2025/packet-11.bin", 2025, PacketId.SessionHistory, typeof(Core.Telemetry.Sources.Formula1.F12025.PacketSessionHistoryData))] + [InlineData("Fixtures/F1/2025/packet-12.bin", 2025, PacketId.TyreSets, typeof(Core.Telemetry.Sources.Formula1.F12025.PacketTyreSetsData))] + [InlineData("Fixtures/F1/2025/packet-13.bin", 2025, PacketId.MotionEx, typeof(Core.Telemetry.Sources.Formula1.F12025.PacketMotionExData))] + [InlineData("Fixtures/F1/2025/packet-14.bin", 2025, PacketId.TimeTrial, typeof(Core.Telemetry.Sources.Formula1.F12025.PacketTimeTrialData))] + [InlineData("Fixtures/F1/2025/packet-15.bin", 2025, PacketId.LapPositions, typeof(Core.Telemetry.Sources.Formula1.F12025.PacketLapPositionsData))] + public void TestParsesPackets_2025(string fixturePath, int expectedPacketFormat, PacketId expectedPacketType, Type expectedPacketTypeStruct) + { + using (var reader = new BinaryReader(File.OpenRead(fixturePath))) + { + var packet = RawDataToObject(reader.ReadBytes((int)reader.BaseStream.Length), expectedPacketTypeStruct); + + Assert.IsType(expectedPacketTypeStruct, packet); + Assert.Equal(expectedPacketFormat, ((dynamic)packet).m_header.m_packetFormat); + Assert.Equal(expectedPacketType, (PacketId)((dynamic)packet).m_header.m_packetId); + } + } + + [Theory] + [InlineData("Fixtures/F1/2024/packet-0.bin", 2024, PacketId.Motion, typeof(Core.Telemetry.Sources.Formula1.F12024.PacketMotionData))] + [InlineData("Fixtures/F1/2024/packet-1.bin", 2024, PacketId.Session, typeof(Core.Telemetry.Sources.Formula1.F12024.PacketSessionData))] + [InlineData("Fixtures/F1/2024/packet-2.bin", 2024, PacketId.LapData, typeof(Core.Telemetry.Sources.Formula1.F12024.PacketLapData))] + [InlineData("Fixtures/F1/2024/packet-4.bin", 2024, PacketId.Participants, typeof(Core.Telemetry.Sources.Formula1.F12024.PacketParticipantsData))] + [InlineData("Fixtures/F1/2024/packet-5.bin", 2024, PacketId.CarSetups, typeof(Core.Telemetry.Sources.Formula1.F12024.PacketCarSetupData))] + [InlineData("Fixtures/F1/2024/packet-6.bin", 2024, PacketId.CarTelemetry, typeof(Core.Telemetry.Sources.Formula1.F12024.PacketCarTelemetryData))] + [InlineData("Fixtures/F1/2024/packet-7.bin", 2024, PacketId.CarStatus, typeof(Core.Telemetry.Sources.Formula1.F12024.PacketCarStatusData))] + [InlineData("Fixtures/F1/2024/packet-10.bin", 2024, PacketId.CarDamage, typeof(Core.Telemetry.Sources.Formula1.F12024.PacketCarDamageData))] + [InlineData("Fixtures/F1/2024/packet-11.bin", 2024, PacketId.SessionHistory, typeof(Core.Telemetry.Sources.Formula1.F12024.PacketSessionHistoryData))] + [InlineData("Fixtures/F1/2024/packet-12.bin", 2024, PacketId.TyreSets, typeof(Core.Telemetry.Sources.Formula1.F12024.PacketTyreSetsData))] + [InlineData("Fixtures/F1/2024/packet-13.bin", 2024, PacketId.MotionEx, typeof(Core.Telemetry.Sources.Formula1.F12024.PacketMotionExData))] + [InlineData("Fixtures/F1/2024/packet-14.bin", 2024, PacketId.TimeTrial, typeof(Core.Telemetry.Sources.Formula1.F12024.PacketTimeTrialData))] + public void TestParsesPackets_2024(string fixturePath, int expectedPacketFormat, PacketId expectedPacketType, Type expectedPacketTypeStruct) + { + using (var reader = new BinaryReader(File.OpenRead(fixturePath))) + { + var packet = RawDataToObject(reader.ReadBytes((int)reader.BaseStream.Length), expectedPacketTypeStruct); + + Assert.IsType(expectedPacketTypeStruct, packet); + Assert.Equal(expectedPacketFormat, ((dynamic)packet).m_header.m_packetFormat); + Assert.Equal(expectedPacketType, (PacketId)((dynamic)packet).m_header.m_packetId); + } + } + + [Theory] + [InlineData("Fixtures/F1/2023/packet-0.bin", 2023, PacketId.Motion, typeof(Core.Telemetry.Sources.Formula1.F12023.PacketMotionData))] + [InlineData("Fixtures/F1/2023/packet-1.bin", 2023, PacketId.Session, typeof(Core.Telemetry.Sources.Formula1.F12023.PacketSessionData))] + [InlineData("Fixtures/F1/2023/packet-2.bin", 2023, PacketId.LapData, typeof(Core.Telemetry.Sources.Formula1.F12023.PacketLapData))] + [InlineData("Fixtures/F1/2023/packet-4.bin", 2023, PacketId.Participants, typeof(Core.Telemetry.Sources.Formula1.F12023.PacketParticipantsData))] + [InlineData("Fixtures/F1/2023/packet-5.bin", 2023, PacketId.CarSetups, typeof(Core.Telemetry.Sources.Formula1.F12023.PacketCarSetupData))] + [InlineData("Fixtures/F1/2023/packet-6.bin", 2023, PacketId.CarTelemetry, typeof(Core.Telemetry.Sources.Formula1.F12023.PacketCarTelemetryData))] + [InlineData("Fixtures/F1/2023/packet-7.bin", 2023, PacketId.CarStatus, typeof(Core.Telemetry.Sources.Formula1.F12023.PacketCarStatusData))] + [InlineData("Fixtures/F1/2023/packet-10.bin", 2023, PacketId.CarDamage, typeof(Core.Telemetry.Sources.Formula1.F12023.PacketCarDamageData))] + [InlineData("Fixtures/F1/2023/packet-11.bin", 2023, PacketId.SessionHistory, typeof(Core.Telemetry.Sources.Formula1.F12023.PacketSessionHistoryData))] + [InlineData("Fixtures/F1/2023/packet-12.bin", 2023, PacketId.TyreSets, typeof(Core.Telemetry.Sources.Formula1.F12023.PacketTyreSetsData))] + [InlineData("Fixtures/F1/2023/packet-13.bin", 2023, PacketId.MotionEx, typeof(Core.Telemetry.Sources.Formula1.F12023.PacketMotionExData))] + public void TestParsesPackets_2023(string fixturePath, int expectedPacketFormat, PacketId expectedPacketType, Type expectedPacketTypeStruct) + { + using (var reader = new BinaryReader(File.OpenRead(fixturePath))) + { + var packet = RawDataToObject(reader.ReadBytes((int)reader.BaseStream.Length), expectedPacketTypeStruct); + + Assert.IsType(expectedPacketTypeStruct, packet); + Assert.Equal(expectedPacketFormat, ((dynamic)packet).m_header.m_packetFormat); + Assert.Equal(expectedPacketType, (PacketId)((dynamic)packet).m_header.m_packetId); + } + } + + public static object RawDataToObject(byte[] rawData, Type expectedPacketTypeStruct) + { + var pinnedRawData = GCHandle.Alloc(rawData, GCHandleType.Pinned); + try + { + // Get the address of the data array + var pinnedRawDataPtr = pinnedRawData.AddrOfPinnedObject(); + + // overlay the data type on top of the raw data + return Marshal.PtrToStructure(pinnedRawDataPtr, expectedPacketTypeStruct); + } + finally + { + // must explicitly release + pinnedRawData.Free(); + } + } +} diff --git a/GamesDat.Tests/GamesDat.Tests.csproj b/GamesDat.Tests/GamesDat.Tests.csproj index 49cddd1..9d07510 100644 --- a/GamesDat.Tests/GamesDat.Tests.csproj +++ b/GamesDat.Tests/GamesDat.Tests.csproj @@ -9,6 +9,12 @@ true + + + + + + @@ -24,4 +30,172 @@ + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + diff --git a/docs/f1-packet-testing-plan.md b/docs/f1-packet-testing-plan.md new file mode 100644 index 0000000..f3dbc05 --- /dev/null +++ b/docs/f1-packet-testing-plan.md @@ -0,0 +1,312 @@ +# F1 Packet Parsing Test Implementation Plan + +## Overview + +Create comprehensive automated tests for F1 packet parsing that: +- Automatically test all packet types across all game years (2023-2025) +- Gracefully skip missing fixture files +- Test event packet subdivisions by event code +- Leave room for custom/manual test scenarios +- Use xUnit's Theory + MemberData pattern (C#'s "data provider" concept) + +## Current State + +**Fixtures Available:** +- Location: `GamesDat.Tests/Fixtures/F1//packet-.bin` +- Years: 2023 (13 files), 2024 (13 files), 2025 (29 files) +- Event subdivisions: Only F1 2025 has event-specific packets (packet-3-SSTA.bin, packet-3-COLL.bin, etc.) + +**F1 Implementation:** +- 16 packet types (PacketId enum 0-15): Motion, Session, LapData, Event, Participants, etc. +- Type mapping via `F1PacketTypeMapper.cs` +- Deserialization API in `F1TelemetryFrameExtensions.cs`: + - `frame.GetPacket()` - Type-safe generic deserialization + - `frame.DeserializePacket()` - Dynamic deserialization using type mapper + +**Testing Infrastructure:** +- xUnit framework with Theory/MemberData support +- Pattern reference: `FileWatcherSourceDiscovery.cs` + +## Implementation Steps + +### Step 1: Create Fixture Discovery Helper + +**File:** `GamesDat.Tests/Helpers/F1TestFixtureDiscovery.cs` + +Create a helper class that: +1. Scans `Fixtures/F1//*.bin` to discover all fixture files +2. Parses filenames using regex: `packet-(\d+)(?:-([A-Z]{4}))?\.bin` +3. Extracts metadata: year (from directory), packetId, optional eventCode +4. Maps to expected types via `F1PacketTypeMapper.GetPacketType(year, packetId)` +5. Filters out missing files gracefully +6. Caches results for performance (thread-safe with lock) + +**Key Classes:** + +```csharp +public class F1FixtureMetadata +{ + public string FilePath { get; set; } + public int Year { get; set; } + public byte PacketId { get; set; } + public string? EventCode { get; set; } + public Type? ExpectedType { get; set; } + public bool IsEventPacket => PacketId == 3; +} + +public static class F1TestFixtureDiscovery +{ + private static List? _cachedFixtures; + private static readonly object _lock = new(); + + public static List DiscoverAllFixtures() + { + // Scan Fixtures/F1//*.bin + // Parse filenames and extract metadata + // Map to types via F1PacketTypeMapper + // Filter File.Exists() checks + // Cache results + } + + // MemberData provider methods + public static IEnumerable AllFixtures() + public static IEnumerable StandardPacketFixtures() + public static IEnumerable EventPacketFixtures() +} +``` + +**Implementation Details:** +- Base path: `Path.Combine(AppContext.BaseDirectory, "Fixtures", "F1")` +- Scan with `Directory.GetFiles(yearPath, "*.bin", SearchOption.TopDirectoryOnly)` +- Use `File.Exists()` to gracefully skip missing files +- For type mapping, call `F1PacketTypeMapper.GetPacketType(year, packetId)` via reflection or create test frames +- Cache with double-check locking pattern (similar to FileWatcherSourceDiscovery) + +### Step 2: Create Test Class with Automated Tests + +**File:** `GamesDat.Tests/F1/F1PacketParsingTest.cs` + +Create comprehensive Theory tests using MemberData: + +**Test 1: Basic Deserialization** +```csharp +[Theory] +[MemberData(nameof(F1TestFixtureDiscovery.AllFixtures), MemberType = typeof(F1TestFixtureDiscovery))] +public void DeserializePacket_AllFixtures_SuccessfullyDeserializes( + string filePath, int year, byte packetId, string eventCode, Type expectedType) +{ + // Load binary fixture file + // Create F1TelemetryFrame from raw data + // Call frame.DeserializePacket() + // Assert: result is not null + // Assert: result.GetType() == expectedType + // Assert: no exceptions thrown +} +``` + +**Test 2: Type-Safe Deserialization** +```csharp +[Theory] +[MemberData(nameof(F1TestFixtureDiscovery.AllFixtures), MemberType = typeof(F1TestFixtureDiscovery))] +public void GetPacket_WithCorrectType_ReturnsValidStruct( + string filePath, int year, byte packetId, string eventCode, Type expectedType) +{ + // Load binary fixture file + // Create F1TelemetryFrame from raw data + // Call frame.GetPacket() using expectedType via reflection + // Assert: struct is not all zeros (basic sanity) + // Assert: no exceptions thrown +} +``` + +**Test 3: Header Validation** +```csharp +[Theory] +[MemberData(nameof(F1TestFixtureDiscovery.AllFixtures), MemberType = typeof(F1TestFixtureDiscovery))] +public void PacketHeader_AllFixtures_ContainsValidMetadata( + string filePath, int year, byte packetId, string eventCode, Type expectedType) +{ + // Load binary fixture file + // Create F1TelemetryFrame from raw data + // Assert: frame.PacketFormat == year + // Assert: frame.PacketId == packetId + // Assert: frame.DataLength > 0 +} +``` + +**Test 4: Event Code Validation (Event Packets Only)** +```csharp +[Theory] +[MemberData(nameof(F1TestFixtureDiscovery.EventPacketFixtures), MemberType = typeof(F1TestFixtureDiscovery))] +public void EventPacket_WithEventCode_MatchesFixtureName( + string filePath, int year, string eventCode, Type expectedType) +{ + // Load binary fixture file + // Create F1TelemetryFrame and deserialize to event packet type + // Extract EventCode field from packet (e.g., via reflection or dynamic) + // Assert: extracted EventCode == eventCode from filename +} +``` + +### Step 3: Add Custom/Manual Test Scenarios + +Add specialized tests for deeper validation: + +**Test 5: File Size Validation** +```csharp +[Theory] +[MemberData(nameof(F1TestFixtureDiscovery.AllFixtures), MemberType = typeof(F1TestFixtureDiscovery))] +public void FixtureFileSize_AllFixtures_MatchesOrExceedsExpectedSize( + string filePath, int year, byte packetId, string eventCode, Type expectedType) +{ + // Get file size with FileInfo + // Get expected size with Marshal.SizeOf(expectedType) + // Assert: fileSize >= expectedSize (allow for padding/extra data) +} +``` + +**Test 6: Specific Field Validation (Examples)** +```csharp +[Fact] +public void PacketMotionData_2025_ContainsValidWorldPositions() +{ + // Load specific fixture: Fixtures/F1/2025/packet-0.bin + // Deserialize to F12025.MotionData + // Assert: position coordinates are reasonable (not all zeros, within plausible range) + // Assert: specific fields like m_worldPositionX have non-zero values +} + +[Fact] +public void PacketEventData_CollisionEvent_ContainsVehicleIndices() +{ + // Load specific fixture: Fixtures/F1/2025/packet-3-COLL.bin + // Deserialize to F12025.EventData + // Assert: EventCode == "COLL" + // Assert: Collision data contains valid vehicle indices (0-21) +} +``` + +**Test 7: Cross-Year Consistency (Optional)** +```csharp +[Theory] +[InlineData(2023, 2024)] +[InlineData(2024, 2025)] +public void CommonPackets_AcrossYears_HaveSimilarStructure(int year1, int year2) +{ + // Load same packet ID from different years (e.g., packet-0.bin from 2023 and 2024) + // Deserialize both + // Assert: Both deserialize successfully + // Optional: Compare struct sizes to detect breaking changes +} +``` + +### Step 4: Helper Methods for Frame Creation + +Add helper methods in the test class to create `F1TelemetryFrame` from fixture files: + +```csharp +private static F1TelemetryFrame CreateFrameFromFixture(string filePath) +{ + var bytes = File.ReadAllBytes(filePath); + + // Create frame and populate from bytes + // Extract header: PacketFormat (bytes 0-1), PacketId (byte 6) + // Copy data to RawData buffer + // Return populated frame +} +``` + +**Important:** Handle unsafe code properly since `F1TelemetryFrame` uses fixed buffers. + +## Critical Files to Create/Modify + +1. **Create:** `GamesDat.Tests/Helpers/F1TestFixtureDiscovery.cs` + - Fixture discovery and metadata extraction + - MemberData provider methods + +2. **Create:** `GamesDat.Tests/F1/F1PacketParsingTest.cs` + - Theory tests with MemberData + - Custom Fact tests for specialized scenarios + - Helper methods for frame creation + +3. **Reference (Read-only):** + - `F1PacketTypeMapper.cs` - Type mapping logic + - `F1TelemetryFrameExtensions.cs` - Deserialization API + - `FileWatcherSourceDiscovery.cs` - Pattern reference + +## Key Design Decisions + +**1. Graceful Skipping** +- Discovery filters out non-existent files before yielding test data +- Tests never receive invalid file paths +- No test failures due to missing fixtures + +**2. Event Packet Handling** +- Separate MemberData provider: `EventPacketFixtures()` +- Only includes fixtures with event code subdivision (packet-3-XXXX.bin) +- Standard tests skip event code validation for non-event packets + +**3. Type Mapping Approach** +- Use `F1PacketTypeMapper.GetPacketType(year, packetId)` during discovery +- Store expected type in metadata +- Tests validate returned type matches expected type + +**4. MemberData Pattern** +- Static methods in discovery class return `IEnumerable` +- Each object array contains test parameters: filePath, year, packetId, eventCode, expectedType +- Reference via `[MemberData(nameof(Method), MemberType = typeof(Class))]` + +**5. Frame Creation** +- Tests read binary fixture files directly +- Create `F1TelemetryFrame` instances by copying data +- Extract header info from first 29 bytes (F1 2025 format) + +## Verification & Testing + +After implementation, verify: + +1. **Run all tests:** `dotnet test --filter "F1PacketParsingTest"` +2. **Check coverage:** All 55 fixture files should generate test cases +3. **Verify graceful skipping:** Add/remove fixture files and rerun tests +4. **Inspect test output:** Should show which fixtures were tested +5. **Performance:** Tests should complete quickly (cached discovery, minimal file I/O) + +**Expected Results:** +- ~55 test cases from automated Theory tests (varies by fixture count) +- All tests pass for existing fixtures +- No failures from missing fixtures +- Custom tests provide deeper validation of specific scenarios + +## Edge Cases & Considerations + +1. **Missing Type Mappings:** Some (year, packetId) combinations may not exist in mapper + - **Handled by:** Discovery filters where `GetPacketType()` returns null + +2. **File Size Mismatches:** Binary files might not exactly match struct sizes + - **Handled by:** Use `>=` comparison, validate minimum size only + +3. **Event Code Extraction:** Need to read EventCode from deserialized packet + - **Approach:** Cast to specific year's EventData type, read EventCode field + +4. **F1 2022 Fixtures:** No fixture directory exists for 2022 + - **Handled by:** Discovery only scans existing directories + +5. **Unsafe Code:** F1TelemetryFrame uses fixed buffers + - **Handled by:** Ensure test methods are marked unsafe if needed, use proper marshaling + +## Benefits of This Approach + +1. **Automated Scale:** Tests all 55 fixtures without manual enumeration +2. **Maintainable:** Adding new fixtures requires no code changes +3. **Consistent:** Follows xUnit conventions and existing test patterns +4. **Extensible:** Easy to add custom tests alongside automated ones +5. **Debuggable:** Clear test names indicate which fixture failed +6. **Resilient:** Gracefully handles missing fixtures without test failures + +## Future Enhancements + +1. **Fixture Generation:** Tools to capture real game packets and generate fixture files +2. **Golden Master Testing:** Compare deserialized output against known-good JSON +3. **Fuzz Testing:** Generate random binary data to test robustness +4. **Performance Benchmarks:** Measure deserialization speed at scale +5. **Visual Regression:** Compare packet data visualizations across versions From c94d0f364d2190b220eb7bfbe357fc00dd06239c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Tue, 10 Feb 2026 22:45:07 +0100 Subject: [PATCH 15/25] Added tests for event details parsing in F1 2025 --- GamesDat.Tests/F1PacketParsingTest.cs | 71 +++++++++++++++++++ .../Sources/Formula1/F12025/EventData.cs | 1 + .../Formula1/F12025/EventDataDetails.cs | 1 + 3 files changed, 73 insertions(+) diff --git a/GamesDat.Tests/F1PacketParsingTest.cs b/GamesDat.Tests/F1PacketParsingTest.cs index ae03b3b..15472d7 100644 --- a/GamesDat.Tests/F1PacketParsingTest.cs +++ b/GamesDat.Tests/F1PacketParsingTest.cs @@ -1,5 +1,6 @@ using System.Runtime.InteropServices; using GamesDat.Core.Telemetry.Sources.Formula1; +using GamesDat.Core.Telemetry.Sources.Formula1.F12025; namespace GamesDat.Tests; @@ -81,6 +82,76 @@ public void TestParsesPackets_2023(string fixturePath, int expectedPacketFormat, } } + [Theory] + [InlineData("Fixtures/F1/2025/packet-3-BUTN.bin", 2025, PacketId.Event, typeof(Core.Telemetry.Sources.Formula1.F12025.EventData), "BUTN")] + [InlineData("Fixtures/F1/2025/packet-3-COLL.bin", 2025, PacketId.Event, typeof(Core.Telemetry.Sources.Formula1.F12025.EventData), "COLL")] + [InlineData("Fixtures/F1/2025/packet-3-FLBK.bin", 2025, PacketId.Event, typeof(Core.Telemetry.Sources.Formula1.F12025.EventData), "FLBK")] + [InlineData("Fixtures/F1/2025/packet-3-FTLP.bin", 2025, PacketId.Event, typeof(Core.Telemetry.Sources.Formula1.F12025.EventData), "FTLP")] + [InlineData("Fixtures/F1/2025/packet-3-LGOT.bin", 2025, PacketId.Event, typeof(Core.Telemetry.Sources.Formula1.F12025.EventData), "LGOT")] + [InlineData("Fixtures/F1/2025/packet-3-OVTK.bin", 2025, PacketId.Event, typeof(Core.Telemetry.Sources.Formula1.F12025.EventData), "OVTK")] + [InlineData("Fixtures/F1/2025/packet-3-PENA.bin", 2025, PacketId.Event, typeof(Core.Telemetry.Sources.Formula1.F12025.EventData), "PENA")] + [InlineData("Fixtures/F1/2025/packet-3-RCWN.bin", 2025, PacketId.Event, typeof(Core.Telemetry.Sources.Formula1.F12025.EventData), "RCWN")] + [InlineData("Fixtures/F1/2025/packet-3-RTMT.bin", 2025, PacketId.Event, typeof(Core.Telemetry.Sources.Formula1.F12025.EventData), "RTMT")] + [InlineData("Fixtures/F1/2025/packet-3-SEND.bin", 2025, PacketId.Event, typeof(Core.Telemetry.Sources.Formula1.F12025.EventData), "SEND")] + [InlineData("Fixtures/F1/2025/packet-3-SPTP.bin", 2025, PacketId.Event, typeof(Core.Telemetry.Sources.Formula1.F12025.EventData), "SPTP")] + [InlineData("Fixtures/F1/2025/packet-3-SSTA.bin", 2025, PacketId.Event, typeof(Core.Telemetry.Sources.Formula1.F12025.EventData), "SSTA")] + [InlineData("Fixtures/F1/2025/packet-3-STLG.bin", 2025, PacketId.Event, typeof(Core.Telemetry.Sources.Formula1.F12025.EventData), "STLG")] + [InlineData("Fixtures/F1/2025/packet-3-TMPT.bin", 2025, PacketId.Event, typeof(Core.Telemetry.Sources.Formula1.F12025.EventData), "TMPT")] + public void TestParseEventDetails_2025(string fixturePath, int expectedPacketFormat, PacketId expectedPacketType, Type expectedPacketTypeStruct, string expectedEventCode) + { + using (var reader = new BinaryReader(File.OpenRead(fixturePath))) + { + var packet = RawDataToObject(reader.ReadBytes((int)reader.BaseStream.Length), expectedPacketTypeStruct); + + Assert.IsType(expectedPacketTypeStruct, packet); + Assert.Equal(expectedPacketFormat, ((dynamic)packet).m_header.m_packetFormat); + Assert.Equal(expectedPacketType, (PacketId)((dynamic)packet).m_header.m_packetId); + Assert.Equal(expectedEventCode, ((dynamic)packet).EventCode); + + switch (expectedEventCode) + { + case EventCodes.ButtonStatus: + var buttonData = ((dynamic)packet).GetEventDetails(); + Assert.NotNull(buttonData); + break; + case EventCodes.Collision: + var collisionData = ((dynamic)packet).GetEventDetails(); + Assert.NotNull(collisionData); + break; + case EventCodes.Flashback: + var flashbackData = ((dynamic)packet).GetEventDetails(); + Assert.NotNull(flashbackData); + break; + case EventCodes.FastestLap: + var fastestLapData = ((dynamic)packet).GetEventDetails(); + Assert.NotNull(fastestLapData); + break; + case EventCodes.StartLights: + var lightsOut = ((dynamic)packet).GetEventDetails(); + Assert.NotNull(lightsOut); + Assert.Equal(5, lightsOut.NumLights); + break; + case EventCodes.LightsOut: + var startLightsData = ((dynamic)packet).GetEventDetails(); + Assert.NotNull(startLightsData); + Assert.Equal(0, startLightsData.NumLights); + break; + case EventCodes.Overtake: + var overtakeData = ((dynamic)packet).GetEventDetails(); + Assert.NotNull(overtakeData); + break; + case EventCodes.Penalty: + var penaltyData = ((dynamic)packet).GetEventDetails(); + Assert.NotNull(penaltyData); + break; + case EventCodes.RaceWinner: + var raceWinnerData = ((dynamic)packet).GetEventDetails(); + Assert.NotNull(raceWinnerData); + break; + } + } + } + public static object RawDataToObject(byte[] rawData, Type expectedPacketTypeStruct) { var pinnedRawData = GCHandle.Alloc(rawData, GCHandleType.Pinned); diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs index 303b22a..1e5fced 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/EventData.cs @@ -35,6 +35,7 @@ EventCodes.ButtonStatus when typeof(T) == typeof(ButtonsData) => (T)(object)m_ev EventCodes.Overtake when typeof(T) == typeof(OvertakeData) => (T)(object)m_eventDetails.Overtake, EventCodes.SafetyCar when typeof(T) == typeof(SafetyCarData) => (T)(object)m_eventDetails.SafetyCar, EventCodes.Collision when typeof(T) == typeof(CollisionData) => (T)(object)m_eventDetails.Collision, + EventCodes.LightsOut when typeof(T) == typeof(StartLightsData) => (T)(object)m_eventDetails.LightsOut, // fallback for unsupported event types or mismatched type requests _ => throw new InvalidOperationException($"Cannot get {typeof(T).Name} for event {EventCode}") }; diff --git a/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs b/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs index 993187f..2ed7ec8 100644 --- a/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs +++ b/GamesDat/Telemetry/Sources/Formula1/F12025/EventDataDetails.cs @@ -20,6 +20,7 @@ public struct EventDataDetails [FieldOffset(0)] public OvertakeData Overtake; [FieldOffset(0)] public SafetyCarData SafetyCar; [FieldOffset(0)] public CollisionData Collision; + [FieldOffset(0)] public StartLightsData LightsOut; } [StructLayout(LayoutKind.Sequential, Pack = 1)] From 2781cdaa61acf4f2eeab6f7e510b632332e848b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Tue, 10 Feb 2026 23:38:30 +0100 Subject: [PATCH 16/25] Tested F1 recording and reading of session recording --- GamesDat.Demo/Program.cs | 90 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/GamesDat.Demo/Program.cs b/GamesDat.Demo/Program.cs index d4457f1..fc8f443 100644 --- a/GamesDat.Demo/Program.cs +++ b/GamesDat.Demo/Program.cs @@ -1,6 +1,9 @@ using GamesDat.Core; using GamesDat.Core.Reader; +using GamesDat.Core.Telemetry.Sources; using GamesDat.Core.Telemetry.Sources.AssettoCorsa; +using GamesDat.Core.Telemetry.Sources.Formula1; +using GamesDat.Core.Telemetry.Sources.Formula1.F12025; using GamesDat.Core.Writer; namespace GamesDat.Demo @@ -9,14 +12,95 @@ internal class Program { static async Task Main(string[] args) { - if (args.Length > 0 && args[0] == "read") + //await CaptureF1SessionAsync(); + //await ReadF1Session("./sessions/f1_20260210_220103.bin"); + } + + #region Formula 1 + static async Task CaptureF1SessionAsync() + { + Console.WriteLine("F1 Telemetry Capture - Fluent API Demo"); + Console.WriteLine("Press Ctrl+C to stop\n"); + var cts = new CancellationTokenSource(); + Console.CancelKeyPress += (s, e) => + { + e.Cancel = true; + cts.Cancel(); + }; + try + { + var f1Source = new F1RealtimeTelemetrySource(new UdpSourceOptions{ Port = 20777 }) + .UseWriter(new BinarySessionWriter()) + .OutputTo($"./sessions/f1_{DateTime.UtcNow:yyyyMMdd_HHmmss}.bin"); + + await using var session = new GameSession() + .AddSource(f1Source) + .OnData(data => + { + if ((int)data.PacketId == (int)PacketId.CarTelemetry) + { + var packet = data.GetPacket(); + var carData = packet.m_carTelemetryData[packet.m_header.m_playerCarIndex]; + Console.WriteLine($"Live: Speed {carData.m_speed} km/h | RPM {carData.m_engineRPM} | Gear {carData.m_gear}"); + } + }); + await session.StartAsync(cts.Token); + // Session is now running, wait for cancellation + await Task.Delay(Timeout.Infinite, cts.Token); + } + catch (FileNotFoundException) + { + Console.WriteLine("\nError: F1 is not running. Start F1 and try again."); + } + catch (OperationCanceledException) + { + Console.WriteLine("\nCapture stopped by user."); + } + } + + static async Task ReadF1Session(string filePath) + { + Console.WriteLine($"Reading session: {filePath}"); + + // Check file size first + var fileInfo = new FileInfo(filePath); + Console.WriteLine($"File size: {fileInfo.Length:N0} bytes"); + + if (fileInfo.Length == 0) { - await ReadSessionAsync(args.Length > 1 ? args[1] : null); + Console.WriteLine("ERROR: Session file is empty!"); return; } - await CaptureSessionAsync(); + Console.WriteLine("Starting to read frames...\n"); + + // Stats + int frameCount = 0; + try + { + await foreach (var (timestamp, data) in SessionReader.ReadAsync(filePath)) + { + if ((int)data.PacketId == (int)PacketId.CarTelemetry) + { + if (frameCount % 1000 == 0) + { + var packet = data.GetPacket(); + var carData = packet.m_carTelemetryData[packet.m_header.m_playerCarIndex]; + Console.WriteLine($"Live: Speed {carData.m_speed} km/h | RPM {carData.m_engineRPM} | Gear {carData.m_gear}"); + } + } + + frameCount++; + } + } + catch (Exception ex) + { + Console.WriteLine($"\nERROR while reading frames: {ex.Message}"); + Console.WriteLine($"Exception type: {ex.GetType().Name}"); + Console.WriteLine($"Stack trace: {ex.StackTrace}"); + } } + #endregion #region Sim Racing static async Task CaptureSessionAsync() From 2eccdde9e4bd6afaf3e97ed675e4751804716bac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kaiser?= Date: Tue, 10 Feb 2026 23:42:14 +0100 Subject: [PATCH 17/25] Update GamesDat.Demo.Wpf/Resources/Styles.xaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Björn Kaiser --- GamesDat.Demo.Wpf/Resources/Styles.xaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/GamesDat.Demo.Wpf/Resources/Styles.xaml b/GamesDat.Demo.Wpf/Resources/Styles.xaml index 41796c9..d9d73a7 100644 --- a/GamesDat.Demo.Wpf/Resources/Styles.xaml +++ b/GamesDat.Demo.Wpf/Resources/Styles.xaml @@ -3,8 +3,6 @@ xmlns:converters="clr-namespace:GamesDat.Demo.Wpf.Converters"> - -