1pub mod car_damage;
2pub mod car_setups;
3pub mod car_status;
4pub mod car_telemetry;
5pub mod event;
6pub mod final_classification;
7pub mod laps;
8pub mod lobby;
9pub mod motion;
10pub mod participants;
11pub mod session;
12pub mod session_history;
13pub mod time_trial;
14pub mod tyre_sets;
15
16use crate::constants::{
17 BrakingAssist, CarDamage, CarDamageRate, Collisions, CornerCuttingStringency,
18 DynamicRacingLine, DynamicRacingLineType, FlashbackLimit, ForecastAccuracy,
19 FormationLapExperience, Formula, GameMode, GearboxAssist, LowFuelMode, MfdPanelIndex,
20 PitStopExperience, RaceStarts, RecoveryMode, RedFlagIntensity, RuleSet,
21 SafetyCarExperience, SafetyCarIntensity, SafetyCarStatus, SessionLength, SpeedUnit,
22 SurfaceSimType, TemperatureUnit, TrackId, TyreTemperature, Weather, MAX_NUM_CARS,
23};
24use crate::packets::car_damage::CarDamageData;
25use crate::packets::car_setups::CarSetupData;
26use crate::packets::car_status::CarStatusData;
27use crate::packets::car_telemetry::CarTelemetryData;
28use crate::packets::event::EventDetails;
29use crate::packets::final_classification::FinalClassificationData;
30use crate::packets::laps::LapData;
31use crate::packets::lobby::LobbyInfoData;
32use crate::packets::motion::CarMotionData;
33use crate::packets::participants::ParticipantsData;
34use crate::packets::session::{
35 check_num_forecast_samples, get_forecast_samples_padding, MarshalZone,
36 WeatherForecastSample, MARSHAL_ZONE_RAW_SIZE, MAX_AI_DIFFICULTY,
37 MAX_NUM_MARSHAL_ZONES, MAX_NUM_SESSIONS,
38};
39use crate::packets::session_history::{
40 get_lap_history_raw_size, LapHistoryData, TyreStintHistoryData, MAX_NUM_LAPS,
41 MAX_NUM_TYRE_STINTS,
42};
43use crate::packets::time_trial::TimeTrialDataSet;
44use crate::packets::tyre_sets::{TyreSetData, NUM_TYRE_SETS};
45
46use binrw::BinRead;
47use serde::{Deserialize, Serialize};
48use std::error::Error;
49use std::fmt;
50use std::string::FromUtf8Error;
51
52#[non_exhaustive]
54#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
55#[br(little, import(packet_format: u16))]
56pub struct F1PacketMotion {
57 #[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
59 pub data: Vec<CarMotionData>,
60 #[br(if(packet_format == 2022))]
63 pub motion_ex: Option<F1PacketMotionEx>,
64}
65
66#[allow(clippy::struct_excessive_bools)]
69#[non_exhaustive]
70#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
71#[br(little, import(packet_format: u16))]
72pub struct F1PacketSession {
73 pub weather: Weather,
75 pub track_temperature: i8,
77 pub air_temperature: i8,
79 pub total_laps: u8,
81 pub track_length: u16,
83 pub session_type: u8,
87 pub track_id: TrackId,
89 pub formula: Formula,
91 pub session_time_left: u16,
93 pub session_duration: u16,
95 pub pit_speed_limit: u8,
97 #[br(try_map(u8_to_bool))]
99 pub game_paused: bool,
100 #[br(try_map(u8_to_bool))]
102 pub is_spectating: bool,
103 #[br(map(u8_to_usize))]
105 pub spectator_car_index: usize,
106 #[br(try_map(u8_to_bool))]
108 pub sli_pro_native_support: bool,
109 #[br(
111 map(u8_to_usize),
112 assert(
113 num_marshal_zones <= MAX_NUM_MARSHAL_ZONES,
114 "Session packet has an invalid number of marshal zones: {}",
115 num_marshal_zones
116 )
117 )]
118 pub num_marshal_zones: usize,
119 #[br(
123 count(num_marshal_zones),
124 args{ inner: (packet_format,) },
125 pad_after(
126 (MAX_NUM_MARSHAL_ZONES - num_marshal_zones) * MARSHAL_ZONE_RAW_SIZE
127 )
128 )]
129 pub marshal_zones: Vec<MarshalZone>,
130 pub safety_car_status: SafetyCarStatus,
132 #[br(try_map(u8_to_bool))]
134 pub network_game: bool,
135 #[br(
137 map(u8_to_usize),
138 assert(
139 check_num_forecast_samples(packet_format, num_weather_forecast_samples),
140 "Session packet has an invalid number of weather forecast samples: {}",
141 num_weather_forecast_samples
142 )
143 )]
144 pub num_weather_forecast_samples: usize,
145 #[br(
149 count(num_weather_forecast_samples),
150 args{ inner: (packet_format,) },
151 pad_after(
152 get_forecast_samples_padding(packet_format, num_weather_forecast_samples)
153 )
154 )]
155 pub weather_forecast_samples: Vec<WeatherForecastSample>,
156 pub forecast_accuracy: ForecastAccuracy,
158 #[br(
160 assert(
161 ai_difficulty <= MAX_AI_DIFFICULTY,
162 "Session packet has an invalid AI difficulty value: {}",
163 ai_difficulty
164 )
165 )]
166 pub ai_difficulty: u8,
167 pub season_link_identifier: u32,
169 pub weekend_link_identifier: u32,
171 pub session_link_identifier: u32,
173 pub pit_stop_window_ideal_lap: u8,
175 pub pit_stop_window_latest_lap: u8,
177 pub pit_stop_rejoin_position: u8,
179 #[br(try_map(u8_to_bool))]
181 pub steering_assist: bool,
182 pub braking_assist: BrakingAssist,
184 pub gearbox_assist: GearboxAssist,
186 #[br(try_map(u8_to_bool))]
188 pub pit_assist: bool,
189 #[br(try_map(u8_to_bool))]
191 pub pit_release_assist: bool,
192 #[br(try_map(u8_to_bool))]
194 pub ers_assist: bool,
195 #[br(try_map(u8_to_bool))]
197 pub drs_assist: bool,
198 pub dynamic_racing_line: DynamicRacingLine,
200 pub dynamic_racing_line_type: DynamicRacingLineType,
202 pub game_mode: GameMode,
204 pub rule_set: RuleSet,
206 pub time_of_day: u32,
208 pub session_length: SessionLength,
210 #[br(if(packet_format >= 2023))]
213 pub speed_unit_lead_player: Option<SpeedUnit>,
214 #[br(if(packet_format >= 2023))]
217 pub temperature_unit_lead_player: Option<TemperatureUnit>,
218 #[br(if(packet_format >= 2023))]
221 pub speed_unit_secondary_player: Option<SpeedUnit>,
222 #[br(if(packet_format >= 2023))]
225 pub temperature_unit_secondary_player: Option<TemperatureUnit>,
226 #[br(if(packet_format >= 2023))]
229 pub num_safety_car_periods: u8,
230 #[br(if(packet_format >= 2023))]
233 pub num_virtual_safety_car_periods: u8,
234 #[br(if(packet_format >= 2023))]
237 pub num_red_flag_periods: u8,
238 #[br(if(packet_format >= 2024), try_map(u8_to_bool))]
241 pub equal_car_performance: bool,
242 #[br(if(packet_format >= 2024))]
245 pub recovery_mode: Option<RecoveryMode>,
246 #[br(if(packet_format >= 2024))]
249 pub flashback_limit: Option<FlashbackLimit>,
250 #[br(if(packet_format >= 2024))]
253 pub surface_sim_type: Option<SurfaceSimType>,
254 #[br(if(packet_format >= 2024))]
257 pub low_fuel_mode: Option<LowFuelMode>,
258 #[br(if(packet_format >= 2024))]
261 pub race_starts: Option<RaceStarts>,
262 #[br(if(packet_format >= 2024))]
265 pub tyre_temperature: Option<TyreTemperature>,
266 #[br(if(packet_format >= 2024), try_map(u8_to_bool))]
270 pub pit_lane_tyre_sim: bool,
271 #[br(if(packet_format >= 2024))]
274 pub car_damage: Option<CarDamage>,
275 #[br(if(packet_format >= 2024))]
278 pub car_damage_rate: Option<CarDamageRate>,
279 #[br(if(packet_format >= 2024))]
282 pub collisions: Option<Collisions>,
283 #[br(if(packet_format >= 2024), try_map(u8_to_bool))]
286 pub collisions_off_for_first_lap_only: bool,
287 #[br(if(packet_format >= 2024), try_map(u8_to_bool))]
290 pub mp_unsafe_pit_release_disabled: bool,
291 #[br(if(packet_format >= 2024), try_map(u8_to_bool))]
294 pub mp_collisions_off_for_griefing: bool,
295 #[br(if(packet_format >= 2024))]
298 pub corner_cutting_stringency: Option<CornerCuttingStringency>,
299 #[br(if(packet_format >= 2024), try_map(u8_to_bool))]
302 pub parc_ferme_rules: bool,
303 #[br(if(packet_format >= 2024))]
306 pub pit_stop_experience: Option<PitStopExperience>,
307 #[br(if(packet_format >= 2024))]
310 pub safety_car_intensity: Option<SafetyCarIntensity>,
311 #[br(if(packet_format >= 2024))]
314 pub safety_car_experience: Option<SafetyCarExperience>,
315 #[br(if(packet_format >= 2024), try_map(u8_to_bool))]
318 pub formation_lap: bool,
319 #[br(if(packet_format >= 2024))]
322 pub formation_lap_experience: Option<FormationLapExperience>,
323 #[br(if(packet_format >= 2024))]
326 pub red_flag_intensity: Option<RedFlagIntensity>,
327 #[br(if(packet_format >= 2024), try_map(u8_to_bool))]
330 pub affects_license_level_solo: bool,
331 #[br(if(packet_format >= 2024), try_map(u8_to_bool))]
334 pub affects_license_level_mp: bool,
335 #[br(
337 map(u8_to_usize),
338 if(packet_format >= 2024),
339 assert(
340 num_sessions_in_weekend <= MAX_NUM_SESSIONS,
341 "Session packet has an invalid number of sessions in a weekend: {}",
342 num_sessions_in_weekend
343 )
344 )]
345 pub num_sessions_in_weekend: usize,
346 #[br(
353 if(packet_format >= 2024),
354 count(num_sessions_in_weekend),
355 pad_after(MAX_NUM_SESSIONS - num_sessions_in_weekend)
356 )]
357 pub weekend_structure: Vec<u8>,
358 #[br(if(packet_format >= 2024))]
361 pub sector2_lap_distance_start: f32,
362 #[br(if(packet_format >= 2024))]
365 pub sector3_lap_distance_start: f32,
366}
367
368#[non_exhaustive]
370#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
371#[br(little, import(packet_format: u16))]
372pub struct F1PacketLaps {
373 #[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
375 pub data: Vec<LapData>,
376 #[br(map(u8_to_usize))]
378 pub time_trial_pb_car_index: usize,
379 #[br(map(u8_to_usize))]
381 pub time_trial_rival_car_index: usize,
382}
383
384#[non_exhaustive]
386#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
387#[br(little, import(packet_format: u16))]
388pub struct F1PacketEvent {
389 #[br(
391 try_map(|bytes: [u8; 4]| String::from_utf8(bytes.to_vec())),
392 restore_position
393 )]
394 pub code: String,
395 #[br(args(packet_format))]
397 pub details: EventDetails,
398}
399
400#[non_exhaustive]
402#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
403#[br(
404 little,
405 import(packet_format: u16),
406 assert(
407 num_active_cars <= MAX_NUM_CARS,
408 "Participants packet has an invalid number of cars: {}",
409 num_active_cars
410 )
411)]
412pub struct F1PacketParticipants {
413 #[br(map(u8_to_usize))]
415 pub num_active_cars: usize,
416 #[br(count(num_active_cars), args{ inner: (packet_format,) })]
420 pub data: Vec<ParticipantsData>,
421}
422
423#[non_exhaustive]
427#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
428#[br(little, import(packet_format: u16))]
429pub struct F1PacketCarSetups {
430 #[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
432 pub data: Vec<CarSetupData>,
433 #[br(if(packet_format >= 2024))]
436 pub next_front_wing_value: f32,
437}
438
439#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
442#[br(little, import(packet_format: u16))]
443pub struct F1PacketCarTelemetry {
444 #[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
446 pub data: Vec<CarTelemetryData>,
447 pub mfd_panel_index: MfdPanelIndex,
449 pub mfd_panel_index_secondary_player: MfdPanelIndex,
451 #[br(
453 assert(
454 (-1..=8).contains(&suggested_gear),
455 "Car telemetry entry has an invalid suggested gear value: {}",
456 suggested_gear
457 )
458 )]
459 pub suggested_gear: i8,
460}
461
462#[non_exhaustive]
464#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
465#[br(little, import(packet_format: u16))]
466pub struct F1PacketCarStatus {
467 #[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
469 pub data: Vec<CarStatusData>,
470}
471
472#[non_exhaustive]
474#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
475#[br(little, import(packet_format: u16))]
476pub struct F1PacketFinalClassification {
477 #[br(
479 map(u8_to_usize),
480 assert(
481 num_cars <= MAX_NUM_CARS,
482 "Final classification packet has an invalid number of cars: {}",
483 num_cars
484 )
485 )]
486 pub num_cars: usize,
487 #[br(count(num_cars), args{ inner: (packet_format,) })]
491 pub data: Vec<FinalClassificationData>,
492}
493
494#[non_exhaustive]
496#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
497#[br(little, import(packet_format: u16))]
498pub struct F1PacketLobby {
499 #[br(
501 map(u8_to_usize),
502 assert(
503 num_players <= MAX_NUM_CARS,
504 "Lobby packet has an invalid number of players: {}",
505 num_players
506 )
507 )]
508 pub num_players: usize,
509 #[br(count(num_players), args{ inner: (packet_format,) })]
513 pub data: Vec<LobbyInfoData>,
514}
515
516#[non_exhaustive]
518#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
519#[br(little, import(packet_format: u16))]
520pub struct F1PacketCarDamage {
521 #[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
523 pub data: Vec<CarDamageData>,
524}
525
526#[non_exhaustive]
528#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
529#[br(little, import(packet_format: u16))]
530pub struct F1PacketSessionHistory {
531 #[br(
533 map(u8_to_usize),
534 assert(
535 vehicle_index < MAX_NUM_CARS,
536 "Session history packet has an invalid vehicle index: {}",
537 vehicle_index
538 )
539 )]
540 pub vehicle_index: usize,
541 #[br(
543 map(u8_to_usize),
544 assert(
545 num_laps <= MAX_NUM_LAPS,
546 "Session history packet has an invalid number of laps: {}",
547 num_laps
548 ),
549 )]
550 pub num_laps: usize,
551 #[br(
553 map(u8_to_usize),
554 assert(
555 num_tyre_stints <= MAX_NUM_TYRE_STINTS,
556 "Session history packet has an invalid number of tyre stints: {}",
557 num_tyre_stints
558 )
559 )]
560 pub num_tyre_stints: usize,
561 #[br(map(u8_to_usize))]
563 pub best_lap_time_lap_num: usize,
564 #[br(map(u8_to_usize))]
566 pub best_sector1_lap_num: usize,
567 #[br(map(u8_to_usize))]
569 pub best_sector2_lap_num: usize,
570 #[br(map(u8_to_usize))]
572 pub best_sector3_lap_num: usize,
573 #[br(
576 count(num_laps),
577 args{ inner: (packet_format,) },
578 pad_after((MAX_NUM_LAPS - num_laps) * get_lap_history_raw_size(packet_format))
579 )]
580 pub lap_history_data: Vec<LapHistoryData>,
581 #[br(count(num_tyre_stints), args{ inner: (packet_format,) })]
585 pub tyre_stint_history_data: Vec<TyreStintHistoryData>,
586}
587
588#[non_exhaustive]
591#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
592#[br(little, import(packet_format: u16))]
593pub struct F1PacketTyreSets {
594 #[br(map(u8_to_usize))]
596 pub vehicle_index: usize,
597 #[br(count(NUM_TYRE_SETS), args{ inner: (packet_format,) })]
599 pub data: Vec<TyreSetData>,
600 #[br(
602 map(u8_to_usize),
603 assert(
604 fitted_index < NUM_TYRE_SETS,
605 "Tyre sets packet has an invalid fitted set index: {}",
606 fitted_index
607 )
608 )]
609 pub fitted_index: usize,
610}
611
612#[non_exhaustive]
616#[derive(BinRead, PartialEq, PartialOrd, Copy, Clone, Debug, Serialize, Deserialize)]
617#[br(little, import(packet_format: u16))]
618pub struct F1PacketMotionEx {
619 pub suspension_position: [f32; 4],
623 pub suspension_velocity: [f32; 4],
627 pub suspension_acceleration: [f32; 4],
631 pub wheel_speed: [f32; 4],
635 pub wheel_slip_ratio: [f32; 4],
639 #[br(if(packet_format >= 2024))]
644 pub wheel_slip_angle: [f32; 4],
645 #[br(if(packet_format >= 2024))]
650 pub wheel_lat_force: [f32; 4],
651 #[br(if(packet_format >= 2024))]
656 pub wheel_long_force: [f32; 4],
657 #[br(if(packet_format >= 2024))]
660 pub height_of_cog_above_ground: f32,
661 pub local_velocity_x: f32,
663 pub local_velocity_y: f32,
665 pub local_velocity_z: f32,
667 pub angular_velocity_x: f32,
669 pub angular_velocity_y: f32,
671 pub angular_velocity_z: f32,
673 pub angular_acceleration_x: f32,
675 pub angular_acceleration_y: f32,
677 pub angular_acceleration_z: f32,
679 pub front_wheels_angle: f32,
681 #[br(if(packet_format >= 2023))]
686 pub wheel_vert_force: [f32; 4],
687 #[br(if(packet_format >= 2024))]
690 pub front_aero_height: f32,
691 #[br(if(packet_format >= 2024))]
694 pub rear_aero_height: f32,
695 #[br(if(packet_format >= 2024))]
698 pub front_roll_angle: f32,
699 #[br(if(packet_format >= 2024))]
702 pub rear_roll_angle: f32,
703 #[br(if(packet_format >= 2024))]
706 pub chassis_yaw: f32,
707}
708
709#[non_exhaustive]
712#[derive(
713 BinRead, Eq, PartialEq, Ord, PartialOrd, Clone, Debug, Serialize, Deserialize,
714)]
715#[br(little, import(_packet_format: u16))]
716pub struct F1PacketTimeTrial {
717 pub player_session_best_data_set: TimeTrialDataSet,
719 pub personal_best_data_set: TimeTrialDataSet,
721 pub rival_data_set: TimeTrialDataSet,
723}
724
725#[derive(Debug, PartialEq)]
726pub(crate) struct MapBoolError(u8);
727
728impl fmt::Display for MapBoolError {
729 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
730 write!(f, "Invalid bool value: {}", self.0)
731 }
732}
733
734impl Error for MapBoolError {}
735
736pub(crate) fn u8_to_bool(value: u8) -> Result<bool, MapBoolError> {
737 match value {
738 0 => Ok(false),
739 1 => Ok(true),
740 _ => Err(MapBoolError(value)),
741 }
742}
743
744pub(crate) fn u8_to_usize(value: u8) -> usize {
745 value as usize
746}
747
748pub(crate) fn read_name(bytes: [u8; 48]) -> Result<String, FromUtf8Error> {
749 let first_nul_index =
750 bytes.iter().position(|&byte| byte == b'\0').unwrap_or(bytes.len());
751
752 String::from_utf8(bytes[..first_nul_index].to_vec())
753}