feat: add 2022 spec lap data packet parser

This commit is contained in:
Maciej Pędzich 2025-02-17 19:04:53 +01:00
parent 79d92c5774
commit 1fd708d821
Signed by: maciejpedzich
GPG Key ID: CE4A303D84882F0D
4 changed files with 203 additions and 12 deletions

View File

@ -3,6 +3,11 @@ pub mod wheel_index;
use binrw::BinRead; use binrw::BinRead;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub(crate) const MAX_NUM_CARS: usize = 22;
pub(crate) const MAX_NUM_MARSHAL_ZONES: usize = 21;
pub(crate) const MAX_NUM_WEATHER_FORECAST_SAMPLES: usize = 56;
pub(crate) const MAX_AI_DIFFICULTY: u8 = 110;
#[non_exhaustive] #[non_exhaustive]
#[derive( #[derive(
BinRead, BinRead,
@ -408,3 +413,67 @@ pub enum SessionLength {
Long = 6, Long = 6,
Full = 7, Full = 7,
} }
#[derive(
BinRead,
Eq,
PartialEq,
Ord,
PartialOrd,
Copy,
Clone,
Debug,
Serialize,
Deserialize,
)]
#[br(little, repr(u8))]
pub enum PitStatus {
None = 0,
Pitting = 1,
InPitArea = 2,
}
#[derive(
BinRead,
Eq,
PartialEq,
Ord,
PartialOrd,
Copy,
Clone,
Debug,
Serialize,
Deserialize,
)]
#[br(little, repr(u8))]
pub enum DriverStatus {
InGarage = 0,
FlyingLap = 1,
InLap = 2,
OutLap = 3,
OnTrack = 4,
}
#[derive(
BinRead,
Eq,
PartialEq,
Ord,
PartialOrd,
Copy,
Clone,
Debug,
Serialize,
Deserialize,
)]
#[br(little, repr(u8))]
pub enum ResultStatus {
Unknown = 0,
Inactive = 1,
Active = 2,
Finished = 3,
DidNotFinish = 4,
Disqualified = 5,
NotClassified = 6,
Retired = 7,
}

View File

@ -34,7 +34,8 @@ pub struct F1Packet {
little, little,
assert( assert(
(2022..=2024).contains(&packet_format), (2022..=2024).contains(&packet_format),
"Invalid or unsupported packet format" "Invalid or unsupported packet format: {}",
packet_format
) )
)] )]
pub struct F1PacketHeader { pub struct F1PacketHeader {
@ -57,6 +58,7 @@ pub struct F1PacketHeader {
/// Index of player's car in the array. /// Index of player's car in the array.
pub player_car_index: u8, pub player_car_index: u8,
/// Index of secondary player's car in the array in splitscreen mode. /// Index of secondary player's car in the array in splitscreen mode.
/// Set to 255 if not in splitscreen mode.
pub secondary_player_car_index: u8, pub secondary_player_car_index: u8,
} }
@ -67,8 +69,10 @@ pub struct F1PacketHeader {
)] )]
#[br(little, import(packet_format: u16, packet_id: PacketId))] #[br(little, import(packet_format: u16, packet_id: PacketId))]
pub struct F1PacketBody { pub struct F1PacketBody {
/// Physics data for all the cars being driven.
#[br(if(packet_id == PacketId::Motion), args(packet_format))] #[br(if(packet_id == PacketId::Motion), args(packet_format))]
pub motion: Option<F1PacketMotionData>, pub motion: Option<F1PacketMotionData>,
/// Data about the ongoing session.
#[br(if(packet_id == PacketId::Session), args(packet_format))] #[br(if(packet_id == PacketId::Session), args(packet_format))]
pub session: Option<F1PacketSessionData>, pub session: Option<F1PacketSessionData>,
} }

75
src/packets/lap_data.rs Normal file
View File

@ -0,0 +1,75 @@
use super::u8_to_bool;
use crate::constants::{DriverStatus, PitStatus, ResultStatus};
use binrw::BinRead;
use serde::{Deserialize, Serialize};
/// Lap data for a car on track.
#[derive(
BinRead, PartialEq, PartialOrd, Copy, Clone, Debug, Serialize, Deserialize,
)]
#[br(
little,
import(_packet_format: u16),
assert(
sector <= 2,
"Lap data entry has an invalid sector number: {}",
sector
)
)]
pub struct LapData {
/// Last lap time in milliseconds.
pub last_lap_time_ms: u32,
/// Current lap time in milliseconds.
pub current_lap_time_ms: u32,
/// Current sector 1 time in milliseconds.
pub sector1_time_ms: u16,
/// Current sector 2 time in milliseconds.
pub sector2_time_ms: u16,
/// The distance the vehicle is around current lap in metres.
/// It may be negative if the start/finish line hasnt been crossed yet.
pub lap_distance: f32,
/// The total distance the vehicle has gone around in this session in metres.
/// It may be negative if the start/finish line hasnt been crossed yet.
pub total_distance: f32,
/// Delta for the safety car in seconds.
pub safety_car_delta: f32,
/// Car's race position.
pub car_position: u8,
/// Current lap number.
pub current_lap_num: u8,
/// Car's pit status.
pub pit_status: PitStatus,
/// Number of pit stops taken in this race.
pub num_pit_stops: u8,
/// Zero-based number of the sector the driver is currently going through.
/// S1 = 0, S2 = 1, S3 = 2.
pub sector: u8,
/// Whether the current lap is invalid.
#[br(map(u8_to_bool))]
pub current_lap_invalid: bool,
/// Accumulated time penalties to be added in seconds.
pub penalties: u8,
/// Accumulated number of warnings issued.
pub warnings: u8,
/// Number of unserved drive through penalties left to serve.
pub num_unserved_drive_through_pens: u8,
/// Number of unserved stop-go penalties left to serve.
pub num_unserved_stop_go_pens: u8,
/// The grid position the vehicle started the race in.
pub grid_position: u8,
/// Status of the driver.
pub driver_status: DriverStatus,
/// Status of the driver's result.
pub result_status: ResultStatus,
/// Whether the pit lane timer is active.
#[br(map(u8_to_bool))]
pub pit_lane_timer_active: bool,
/// Current time spent in the pit lane in milliseconds.
pub pit_lane_time_in_lane_ms: u16,
/// Time of the actual pit stop in milliseconds.
pub pit_stop_timer_ms: u16,
/// Whether the car should serve a penalty at this stop.
#[br(map(u8_to_bool))]
pub pit_stop_should_serve_pen: bool,
}

View File

@ -1,15 +1,19 @@
pub mod motion; pub mod motion;
pub mod session; pub mod session;
pub mod lap_data;
use crate::packets::motion::CarMotionData; use crate::packets::motion::CarMotionData;
use crate::constants::{ use crate::constants::{
BrakingAssist, DynamicRacingLine, DynamicRacingLineType, ForecastAccuracy, BrakingAssist, DynamicRacingLine,
Formula, GameMode, GearboxAssist, Ruleset, SafetyCarStatus, SessionLength, DynamicRacingLineType, ForecastAccuracy, Formula, GameMode,
SessionType, TrackId, Weather, GearboxAssist, Ruleset, SafetyCarStatus, SessionLength, SessionType,
TrackId, Weather, MAX_AI_DIFFICULTY, MAX_NUM_CARS, MAX_NUM_MARSHAL_ZONES,
MAX_NUM_WEATHER_FORECAST_SAMPLES,
}; };
use crate::packets::session::{MarshalZone, WeatherForecastSample}; use crate::packets::session::{MarshalZone, WeatherForecastSample};
use crate::packets::lap_data::LapData;
use binrw::BinRead; use binrw::BinRead;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
@ -22,13 +26,14 @@ use std::string::FromUtf8Error;
#[br(little, import(packet_format: u16))] #[br(little, import(packet_format: u16))]
pub struct F1PacketMotionData { pub struct F1PacketMotionData {
/// Motion data for all cars on track. /// Motion data for all cars on track.
#[br(count(22))] #[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
pub car_motion_data: Vec<CarMotionData>, pub car_motion_data: Vec<CarMotionData>,
/// Extra player car only motion data (2022 format only). /// Extra player car only motion data (2022 format only).
#[br(if(packet_format == 2022))] #[br(if(packet_format == 2022))]
pub motion_ex_data: Option<F1PacketMotionExData>, pub motion_ex_data: Option<F1PacketMotionExData>,
} }
/// Data about the ongoing session.
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools)]
#[non_exhaustive] #[non_exhaustive]
#[derive( #[derive(
@ -37,6 +42,21 @@ pub struct F1PacketMotionData {
#[br( #[br(
little, little,
import(packet_format: u16), import(packet_format: u16),
assert(
num_marshal_zones <= MAX_NUM_MARSHAL_ZONES,
"Session packet has an invalid number of marshal zones: {}",
num_marshal_zones
),
assert(
num_weather_forecast_samples <= MAX_NUM_WEATHER_FORECAST_SAMPLES,
"Session packet has an invalid number of weather forecast samples: {}",
num_weather_forecast_samples
),
assert(
ai_difficulty <= MAX_AI_DIFFICULTY,
"Session packet has an invalid AI difficulty value: {}",
ai_difficulty
),
)] )]
pub struct F1PacketSessionData { pub struct F1PacketSessionData {
/// Current weather. /// Current weather.
@ -73,26 +93,31 @@ pub struct F1PacketSessionData {
/// Whether SLI Pro support is active. /// Whether SLI Pro support is active.
#[br(map(u8_to_bool))] #[br(map(u8_to_bool))]
pub sli_pro_native_support: bool, pub sli_pro_native_support: bool,
/// Number of marshal zones to follow. /// Number of marshal zones to follow (no greater than 21).
#[br(map(u8_to_usize))] #[br(map(u8_to_usize))]
pub num_marshal_zones: usize, pub num_marshal_zones: usize,
/// List of up to 21 marshal zones. /// List of marshal zones.
#[br(count(21), args{ inner: (packet_format,) })] /// Has a size of 21 regardless of the `num_marshal_zones` value.
#[br(count(MAX_NUM_MARSHAL_ZONES), args{ inner: (packet_format,) })]
pub marshal_zones: Vec<MarshalZone>, pub marshal_zones: Vec<MarshalZone>,
/// Safety car deployment status. /// Safety car deployment status.
pub safety_car_status: SafetyCarStatus, pub safety_car_status: SafetyCarStatus,
/// Whether this game is online. /// Whether this game is online.
#[br(map(u8_to_bool))] #[br(map(u8_to_bool))]
pub network_game: bool, pub network_game: bool,
/// Number of weather samples to follow. /// Number of weather samples to follow (no greater than 56).
#[br(map(u8_to_usize))] #[br(map(u8_to_usize))]
pub num_weather_forecast_samples: usize, pub num_weather_forecast_samples: usize,
/// List of up to 56 weather forecast samples. /// List of up to weather forecast samples.
#[br(count(56), args{ inner: (packet_format,) })] /// Has a size of 56 regardless of the `num_weather_forecast_samples` value.
#[br(
count(MAX_NUM_WEATHER_FORECAST_SAMPLES),
args{ inner: (packet_format,) }
)]
pub weather_forecast_samples: Vec<WeatherForecastSample>, pub weather_forecast_samples: Vec<WeatherForecastSample>,
/// Weather forecast accuracy. /// Weather forecast accuracy.
pub forecast_accuracy: ForecastAccuracy, pub forecast_accuracy: ForecastAccuracy,
/// AI difficulty rating (0-110). /// AI difficulty rating in range `(0..=110)`.
pub ai_difficulty: u8, pub ai_difficulty: u8,
/// Identifier for season - persists across saves. /// Identifier for season - persists across saves.
pub season_link_identifier: u32, pub season_link_identifier: u32,
@ -137,6 +162,24 @@ pub struct F1PacketSessionData {
pub session_length: SessionLength, pub session_length: SessionLength,
} }
/// Data about all the lap times of cars in the session.
#[non_exhaustive]
#[derive(
BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize,
)]
#[br(little, import(packet_format: u16))]
pub struct F1PacketLapData {
/// Lap data for all cars on track.
#[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
pub lap_data: Vec<LapData>,
/// Index of personal best car in time trial mode (255 if invalid).
#[br(map(u8_to_usize))]
pub time_trial_pb_car_index: usize,
/// Index of rival's car in time trial mode (255 if invalid).
#[br(map(u8_to_usize))]
pub time_trial_rival_car_index: usize,
}
/// Extended motion data for player's car. Available as a: /// Extended motion data for player's car. Available as a:
/// - part of [`F1PacketMotionData`] in the 2022 format /// - part of [`F1PacketMotionData`] in the 2022 format
/// - standalone packet from the 2023 format onwards /// - standalone packet from the 2023 format onwards