mirror of
https://github.com/maciejpedzich/f1-game-packet-parser.git
synced 2025-04-17 02:21:10 +02:00
349 lines
12 KiB
Rust
349 lines
12 KiB
Rust
pub mod car_setups;
|
|
pub mod car_status;
|
|
pub mod car_telemetry;
|
|
pub mod event;
|
|
pub mod lap_data;
|
|
pub mod motion;
|
|
pub mod participants;
|
|
pub mod session;
|
|
|
|
use crate::constants::{
|
|
BrakingAssist, DynamicRacingLine, DynamicRacingLineType, ForecastAccuracy,
|
|
Formula, GameMode, GearboxAssist, MfdPanelIndex, Ruleset, SafetyCarStatus,
|
|
SessionLength, SessionType, TrackId, Weather, MAX_NUM_CARS,
|
|
};
|
|
use crate::packets::event::EventDataDetails;
|
|
use crate::packets::lap_data::LapData;
|
|
use crate::packets::motion::CarMotionData;
|
|
use crate::packets::participants::ParticipantsData;
|
|
use crate::packets::session::{MarshalZone, WeatherForecastSample};
|
|
|
|
use crate::packets::car_setups::CarSetupData;
|
|
use crate::packets::car_status::CarStatusData;
|
|
use crate::packets::car_telemetry::CarTelemetryData;
|
|
use binrw::BinRead;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::string::FromUtf8Error;
|
|
|
|
/// Physics data for all the cars being driven.
|
|
#[non_exhaustive]
|
|
#[derive(
|
|
BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize,
|
|
)]
|
|
#[br(little, import(packet_format: u16))]
|
|
pub struct F1PacketMotionData {
|
|
/// Motion data for all cars on track.
|
|
#[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
|
|
pub car_motion_data: Vec<CarMotionData>,
|
|
/// Extra player car only motion data (2022 format only).
|
|
#[br(if(packet_format == 2022))]
|
|
pub motion_ex_data: Option<F1PacketMotionExData>,
|
|
}
|
|
|
|
/// Data about the ongoing session.
|
|
#[allow(clippy::struct_excessive_bools)]
|
|
#[non_exhaustive]
|
|
#[derive(
|
|
BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize,
|
|
)]
|
|
#[br(
|
|
little,
|
|
import(packet_format: u16),
|
|
assert(
|
|
num_marshal_zones <= 21,
|
|
"Session packet has an invalid number of marshal zones: {}",
|
|
num_marshal_zones
|
|
),
|
|
assert(
|
|
num_weather_forecast_samples <= 56,
|
|
"Session packet has an invalid number of weather forecast samples: {}",
|
|
num_weather_forecast_samples
|
|
),
|
|
assert(
|
|
ai_difficulty <= 110,
|
|
"Session packet has an invalid AI difficulty value: {}",
|
|
ai_difficulty
|
|
),
|
|
)]
|
|
pub struct F1PacketSessionData {
|
|
/// Current weather.
|
|
pub weather: Weather,
|
|
/// Track temperature in degrees Celsius.
|
|
pub track_temperature: i8,
|
|
/// Air temperature in degrees Celsius.
|
|
pub air_temperature: i8,
|
|
/// Total number of laps in this session.
|
|
pub total_laps: u8,
|
|
/// Track's length in metres.
|
|
pub track_length: u16,
|
|
/// Session's type.
|
|
pub session_type: SessionType,
|
|
/// Unique identifier of the track.
|
|
pub track_id: TrackId,
|
|
/// Formula of cars being raced.
|
|
pub formula: Formula,
|
|
/// Time left in the session in seconds.
|
|
pub session_time_left: u16,
|
|
/// Session's duration in seconds.
|
|
pub session_duration: u16,
|
|
/// Pit lane's speed limit in kilometres per hour.
|
|
pub pit_speed_limit: u8,
|
|
/// Whether the game is paused.
|
|
#[br(map(u8_to_bool))]
|
|
pub game_paused: bool,
|
|
/// Whether the player is spectating.
|
|
#[br(map(u8_to_bool))]
|
|
pub is_spectating: bool,
|
|
/// Index of the car being spectated.
|
|
#[br(map(u8_to_usize))]
|
|
pub spectator_car_index: usize,
|
|
/// Whether SLI Pro support is active.
|
|
#[br(map(u8_to_bool))]
|
|
pub sli_pro_native_support: bool,
|
|
/// Number of marshal zones to follow (no greater than 21).
|
|
#[br(map(u8_to_usize))]
|
|
pub num_marshal_zones: usize,
|
|
/// List of marshal zones.
|
|
/// Has a size of 21 regardless of the `num_marshal_zones` value.
|
|
#[br(count(21), args{ inner: (packet_format,) })]
|
|
pub marshal_zones: Vec<MarshalZone>,
|
|
/// Safety car deployment status.
|
|
pub safety_car_status: SafetyCarStatus,
|
|
/// Whether this game is online.
|
|
#[br(map(u8_to_bool))]
|
|
pub network_game: bool,
|
|
/// Number of weather samples to follow (no greater than 56).
|
|
#[br(map(u8_to_usize))]
|
|
pub num_weather_forecast_samples: usize,
|
|
/// List of up to weather forecast samples.
|
|
/// Has a size of 56 regardless of the `num_weather_forecast_samples` value.
|
|
#[br(count(56), args{ inner: (packet_format,) })]
|
|
pub weather_forecast_samples: Vec<WeatherForecastSample>,
|
|
/// Weather forecast accuracy.
|
|
pub forecast_accuracy: ForecastAccuracy,
|
|
/// AI difficulty rating in range `(0..=110)`.
|
|
pub ai_difficulty: u8,
|
|
/// Identifier for season - persists across saves.
|
|
pub season_link_identifier: u32,
|
|
/// Identifier for weekend - persists across saves.
|
|
pub weekend_link_identifier: u32,
|
|
/// Identifier for session - persists across saves.
|
|
pub session_link_identifier: u32,
|
|
/// Ideal lap for the player to pit on for current strategy.
|
|
pub pit_stop_window_ideal_lap: u8,
|
|
/// The latest lap for the player to pit on for current strategy.
|
|
pub pit_stop_window_latest_lap: u8,
|
|
/// Predicted position for the player to rejoin at.
|
|
pub pit_stop_rejoin_position: u8,
|
|
/// Whether the steering assist is enabled.
|
|
#[br(map(u8_to_bool))]
|
|
pub steering_assist: bool,
|
|
/// Type of braking assist enabled.
|
|
pub braking_assist: BrakingAssist,
|
|
/// Type of gearbox assist enabled.
|
|
pub gearbox_assist: GearboxAssist,
|
|
/// Whether the pit assist is enabled.
|
|
#[br(map(u8_to_bool))]
|
|
pub pit_assist: bool,
|
|
/// Whether the pit release assist is enabled.
|
|
#[br(map(u8_to_bool))]
|
|
pub pit_release_assist: bool,
|
|
/// Whether the ERS assist is enabled.
|
|
#[br(map(u8_to_bool))]
|
|
pub ers_assist: bool,
|
|
/// Whether the DRS assist is enabled.
|
|
#[br(map(u8_to_bool))]
|
|
pub drs_assist: bool,
|
|
/// Type of the dynamic racing line assist.
|
|
pub dynamic_racing_line: DynamicRacingLine,
|
|
/// Type of the dynamic racing line (2D/3D).
|
|
pub dynamic_racing_line_type: DynamicRacingLineType,
|
|
/// Game mode's identifier.
|
|
pub game_mode: GameMode,
|
|
/// Ruleset's identifier.
|
|
pub ruleset: Ruleset,
|
|
/// Local time of day - minutes since midnight
|
|
pub time_of_day: u32,
|
|
/// Session's length.
|
|
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,
|
|
}
|
|
|
|
/// Various notable events that happen during a session.
|
|
#[non_exhaustive]
|
|
#[derive(
|
|
BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize,
|
|
)]
|
|
#[br(little, import(_packet_format: u16))]
|
|
pub struct F1PacketEventData {
|
|
/// 4-letter event code.
|
|
#[br(
|
|
try_map(|bytes: [u8; 4]| String::from_utf8(bytes.to_vec())),
|
|
restore_position
|
|
)]
|
|
pub event_string_code: String,
|
|
/// Extra data for this event.
|
|
pub event_details: EventDataDetails,
|
|
}
|
|
|
|
/// Data of participants in the session, mostly relevant for multiplayer.
|
|
#[non_exhaustive]
|
|
#[derive(
|
|
BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize,
|
|
)]
|
|
#[br(
|
|
little,
|
|
import(packet_format: u16),
|
|
assert(
|
|
num_active_cars <= MAX_NUM_CARS,
|
|
"Participants packet has an invalid number of active cars: {}",
|
|
num_active_cars
|
|
)
|
|
)]
|
|
pub struct F1PacketParticipantsData {
|
|
/// Number of active cars in the data (no greater than 22)
|
|
#[br(map(u8_to_usize))]
|
|
pub num_active_cars: usize,
|
|
/// Data for all participants.
|
|
/// Should have a size equal to `num_active_cars`.
|
|
#[br(count(num_active_cars), args{ inner: (packet_format,) })]
|
|
pub participants: Vec<ParticipantsData>,
|
|
}
|
|
|
|
/// Car setups for all cars in the race.
|
|
/// In multiplayer games, other player cars will appear as blank.
|
|
/// You will only be able to see your car setup and AI cars.
|
|
#[non_exhaustive]
|
|
#[derive(
|
|
BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize,
|
|
)]
|
|
#[br(little, import(packet_format: u16))]
|
|
pub struct F1PacketCarSetupsData {
|
|
/// Setup data for all cars on track.
|
|
/// Should have a size of 22.
|
|
#[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
|
|
pub car_setups: Vec<CarSetupData>,
|
|
}
|
|
|
|
/// Telemetry (such as speed, DRS, throttle application, etc.)
|
|
/// for all cars in the race.
|
|
#[derive(
|
|
BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize,
|
|
)]
|
|
#[br(
|
|
little,
|
|
import(packet_format: u16),
|
|
assert(
|
|
(-1..=8).contains(&suggested_gear),
|
|
"Car telemetry entry has an invalid suggested gear value: {}",
|
|
suggested_gear
|
|
),
|
|
)]
|
|
pub struct F1PacketCarTelemetryData {
|
|
/// Telemetry data for all cars on track.
|
|
/// Should have a size of 22.
|
|
#[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
|
|
pub car_telemetry_data: Vec<CarTelemetryData>,
|
|
/// Index of currently open MFD panel.
|
|
pub mfd_panel_index: MfdPanelIndex,
|
|
/// See [`mfd_panel_index`](field@F1PacketCarTelemetryData::mfd_panel_index).
|
|
pub mfd_panel_index_secondary_player: MfdPanelIndex,
|
|
/// Suggested gear (0 if no gear suggested).
|
|
pub suggested_gear: i8,
|
|
}
|
|
|
|
/// Car status data for each car in the race.
|
|
#[non_exhaustive]
|
|
#[derive(
|
|
BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize,
|
|
)]
|
|
#[br(little, import(packet_format: u16))]
|
|
pub struct F1PacketCarStatusData {
|
|
/// Status data for all cars. Should have a size of 22.
|
|
#[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
|
|
pub car_status_data: Vec<CarStatusData>,
|
|
}
|
|
|
|
/// Extended motion data for player's car. Available as a:
|
|
/// - part of [`F1PacketMotionData`] in the 2022 format
|
|
/// - standalone packet from the 2023 format onwards
|
|
#[non_exhaustive]
|
|
#[derive(
|
|
BinRead, PartialEq, PartialOrd, Copy, Clone, Debug, Serialize, Deserialize,
|
|
)]
|
|
#[br(little, import(_packet_format: u16))]
|
|
pub struct F1PacketMotionExData {
|
|
/// Positions of suspension for each wheel.
|
|
/// See [`wheel_index`](mod@crate::constants::wheel_index)
|
|
/// for wheel order.
|
|
pub suspension_position: [f32; 4],
|
|
/// Velocities of suspension.
|
|
/// See [`wheel_index`](mod@crate::constants::wheel_index)
|
|
/// for wheel order.
|
|
pub suspension_velocity: [f32; 4],
|
|
/// Accelerations of suspension in the following order:
|
|
/// See [`wheel_index`](mod@crate::constants::wheel_index)
|
|
/// for wheel order.
|
|
pub suspension_acceleration: [f32; 4],
|
|
/// Speed of each wheel in the following order:
|
|
/// See [`wheel_index`](mod@crate::constants::wheel_index)
|
|
/// for wheel order.
|
|
pub wheel_speed: [f32; 4],
|
|
/// Slip ratio of each wheel in the following order:
|
|
/// See [`wheel_index`](mod@crate::constants::wheel_index)
|
|
/// for wheel order.
|
|
pub wheel_slip: [f32; 4],
|
|
/// X velocity in local space.
|
|
pub local_velocity_x: f32,
|
|
/// Y velocity in local space.
|
|
pub local_velocity_y: f32,
|
|
/// Z velocity in local space.
|
|
pub local_velocity_z: f32,
|
|
/// Angular velocity X component.
|
|
pub angular_velocity_x: f32,
|
|
/// Angular velocity Y component.
|
|
pub angular_velocity_y: f32,
|
|
/// Angular velocity Z component.
|
|
pub angular_velocity_z: f32,
|
|
/// Angular acceleration X component.
|
|
pub angular_acceleration_x: f32,
|
|
/// Angular acceleration Y component.
|
|
pub angular_acceleration_y: f32,
|
|
/// Angular acceleration Z component.
|
|
pub angular_acceleration_z: f32,
|
|
/// Current front wheels angle in radians.
|
|
pub front_wheels_angle: f32,
|
|
}
|
|
|
|
pub(crate) fn u8_to_bool(value: u8) -> bool {
|
|
value == 1
|
|
}
|
|
|
|
pub(crate) fn u8_to_usize(value: u8) -> usize {
|
|
value as usize
|
|
}
|
|
|
|
pub(crate) fn read_name(bytes: [u8; 48]) -> Result<String, FromUtf8Error> {
|
|
let first_nul_index =
|
|
bytes.iter().position(|&byte| byte == b'\0').unwrap_or(bytes.len());
|
|
|
|
String::from_utf8(bytes[..first_nul_index].to_vec())
|
|
}
|