mirror of
https://github.com/maciejpedzich/f1-game-packet-parser.git
synced 2025-04-19 03:01:10 +02:00
feat: add 2022 spec lap data packet parser
This commit is contained in:
parent
79d92c5774
commit
1fd708d821
@ -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,
|
||||||
|
}
|
||||||
|
@ -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
75
src/packets/lap_data.rs
Normal 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 hasn’t 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 hasn’t 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,
|
||||||
|
}
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user