From e4728bcf457335d76945bef2dead4cee6e5d5a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20P=C4=99dzich?= Date: Mon, 17 Feb 2025 21:19:29 +0100 Subject: [PATCH] feat: add 2022 spec event event packet parser --- Cargo.lock | 7 ++ Cargo.toml | 1 + src/constants/mod.rs | 201 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 8 +- src/packets/event.rs | 125 +++++++++++++++++++++++++++ src/packets/mod.rs | 24 +++++- 6 files changed, 362 insertions(+), 4 deletions(-) create mode 100644 src/packets/event.rs diff --git a/Cargo.lock b/Cargo.lock index bd9151d..768062f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + [[package]] name = "bytemuck" version = "1.21.0" @@ -49,6 +55,7 @@ name = "f1-game-packet-parser" version = "0.1.0" dependencies = [ "binrw", + "bitflags", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index 53f94bd..ce7d425 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,5 @@ edition = "2021" [dependencies] binrw = "0.14" +bitflags = "2.8" serde = { version = "1.0", features = ["derive"] } diff --git a/src/constants/mod.rs b/src/constants/mod.rs index fcafac2..708aec0 100644 --- a/src/constants/mod.rs +++ b/src/constants/mod.rs @@ -1,6 +1,7 @@ pub mod wheel_index; use binrw::BinRead; +use bitflags::bitflags; use serde::{Deserialize, Serialize}; pub(crate) const MAX_NUM_CARS: usize = 22; @@ -414,6 +415,7 @@ pub enum SessionLength { Full = 7, } +#[non_exhaustive] #[derive( BinRead, Eq, @@ -433,6 +435,7 @@ pub enum PitStatus { InPitArea = 2, } +#[non_exhaustive] #[derive( BinRead, Eq, @@ -454,6 +457,7 @@ pub enum DriverStatus { OnTrack = 4, } +#[non_exhaustive] #[derive( BinRead, Eq, @@ -477,3 +481,200 @@ pub enum ResultStatus { NotClassified = 6, Retired = 7, } + +#[non_exhaustive] +#[derive( + BinRead, + Eq, + PartialEq, + Ord, + PartialOrd, + Copy, + Clone, + Debug, + Serialize, + Deserialize, +)] +#[br(little, repr(u8))] +pub enum PenaltyType { + DriveThrough = 0, + StopGo = 1, + GridPenalty = 2, + PenaltyReminder = 3, + TimePenalty = 4, + Warning = 5, + Disqualified = 6, + RemovedFromFormationLap = 7, + ParkedTooLongTimer = 8, + TyreRegulations = 9, + ThisLapInvalidated = 10, + ThisAndNextLapInvalidated = 11, + ThisLapInvalidatedWithoutReason = 12, + ThisAndNextLapInvalidatedWithoutReason = 13, + ThisAndPreviousLapInvalidated = 14, + ThisAndPreviousLapInvalidatedWithoutReason = 15, + Retired = 16, + BlackFlagTimer = 17, +} + +#[non_exhaustive] +#[derive( + BinRead, + Eq, + PartialEq, + Ord, + PartialOrd, + Copy, + Clone, + Debug, + Serialize, + Deserialize, +)] +#[br(little, repr(u8))] +pub enum InfringementType { + BlockingBySlowDriving = 0, + BlockingByWrongWayDriving = 1, + ReversingOffTheStartLine = 2, + BigCollision = 3, + SmallCollision = 4, + CollisionFailedToHandBackPositionSingle = 5, + CollisionFailedToHandBackPositionMultiple = 6, + CornerCuttingGainedTime = 7, + CornerCuttingOvertakeSingle = 8, + CornerCuttingOvertakeMultiple = 9, + CrossedPitExitLane = 10, + IgnoringBlueFlags = 11, + IgnoringYellowFlags = 12, + IgnoringDriveThrough = 13, + TooManyDriveThroughs = 14, + DriveThroughReminderServeWithinNLaps = 15, + DriveThroughReminderServeThisLap = 16, + PitLaneSpeeding = 17, + ParkedForTooLong = 18, + IgnoringTyreRegulations = 19, + TooManyPenalties = 20, + MultipleWarnings = 21, + ApproachingDisqualification = 22, + TyreRegulationsSelectSingle = 23, + TyreRegulationsSelectMultiple = 24, + LapInvalidatedCornerCutting = 25, + LapInvalidatedRunningWide = 26, + CornerCuttingRanWideMinorTimeGain = 27, + CornerCuttingRanWideSignificantTimeGain = 28, + CornerCuttingRanWideExtremeTimeGain = 29, + LapInvalidatedWallRiding = 30, + LapInvalidatedFlashbackUsed = 31, + LapInvalidatedResetToTrack = 32, + BlockingThePitLane = 33, + JumpStart = 34, + SafetyCarCollision = 35, + SafetyCarIllegalOvertake = 36, + SafetyCarExceedingAllowedPace = 37, + VirtualSafetyCarExceedingAllowedPace = 38, + FormationLapBelowAllowedSpeed = 39, + RetiredMechanicalFailure = 40, + RetiredTerminallyDamaged = 41, + SafetyCarFallingTooFarBack = 42, + BlackFlagTimer = 43, + UnservedStopGoPenalty = 44, + UnservedDriveThroughPenalty = 45, + EngineComponentChange = 46, + GearboxChange = 47, + LeagueGridPenalty = 48, + RetryPenalty = 49, + IllegalTimeGain = 50, + MandatoryPitStop = 51, +} + +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + Ord, + PartialOrd, + Hash, + Serialize, + Deserialize, +)] +pub struct ButtonStatus(u32); + +bitflags! { + impl ButtonStatus: u32 { + /// The "A" button on an Xbox controller + /// or the cross button on a PlayStation controller. + /// Has a value of `0x00000001`. + const A_OR_CROSS = 0x00000001; + /// The "Y" button on an Xbox controller + /// or the triangle button on a PlayStation controller. + /// Has a value of `0x00000002`. + const Y_OR_TRIANGLE = 0x00000002; + /// The "B" button on an Xbox controller + /// or the circle button on a PlayStation controller. + /// Has a value of `0x00000004`. + const B_OR_CIRCLE = 0x00000004; + /// The "X" button on an Xbox controller + /// or the square button on a PlayStation controller. + /// Has a value of `0x00000008`. + const X_OR_SQUARE = 0x00000008; + /// Left directional pad button. Has a value of `0x00000010`. + const DPAD_LEFT = 0x00000010; + /// Right directional pad button. Has a value of `0x00000020`. + const DPAD_RIGHT = 0x00000020; + /// Up directional pad button. Has a value of `0x00000040`. + const DPAD_UP = 0x00000040; + /// Down directional pad button. Has a value of `0x00000080`. + const DPAD_DOWN = 0x00000080; + /// The "Menu" button on an Xbox controller + /// or the "Options" button on a PlayStation controller. + /// Has a value of `0x00000100`. + const MENU_OR_OPTIONS = 0x00000100; + /// Left bumper. Has a value of `0x00000200`. + const LEFT_BUMPER = 0x00000200; + /// Right bumper. Has a value of `0x00000400`. + const RIGHT_BUMPER = 0x00000400; + /// Left trigger. Has a value of `0x00000800`. + const LEFT_TRIGGER = 0x00000800; + /// Right trigger. Has a value of `0x00001000`. + const RIGHT_TRIGGER = 0x00001000; + /// Left analog stick click. Has a value of `0x00002000`. + const LEFT_STICK_CLICK = 0x00002000; + /// Right analog stick click. Has a value of `0x00004000`. + const RIGHT_STICK_CLICK = 0x00004000; + /// Right analog stick left. Has a value of `0x00008000`. + const RIGHT_STICK_LEFT = 0x00008000; + /// Right analog stick right. Has a value of `0x00010000` + const RIGHT_STICK_RIGHT = 0x00010000; + /// Right analog stick up. Has a value of `0x00020000` + const RIGHT_STICK_UP = 0x00020000; + /// Right analog stick down. Has a value of `0x00040000` + const RIGHT_STICK_DOWN = 0x00040000; + /// Special button. Has a value of `0x00080000`. + const SPECIAL = 0x00080000; + /// UDP Action 1. Has a value of `0x00100000`. + const UDP_ACTION_1 = 0x00100000; + /// UDP Action 2. Has a value of `0x00200000`. + const UDP_ACTION_2 = 0x00200000; + /// UDP Action 3. Has a value of `0x00400000`. + const UDP_ACTION_3 = 0x00400000; + /// UDP Action 4. Has a value of `0x00800000`. + const UDP_ACTION_4 = 0x00800000; + /// UDP Action 5. Has a value of `0x01000000`. + const UDP_ACTION_5 = 0x01000000; + /// UDP Action 6. Has a value of `0x02000000`. + const UDP_ACTION_6 = 0x02000000; + /// UDP Action 7. Has a value of `0x04000000`. + const UDP_ACTION_7 = 0x04000000; + /// UDP Action 8. Has a value of `0x08000000`. + const UDP_ACTION_8 = 0x08000000; + /// UDP Action 9. Has a value of `0x10000000`. + const UDP_ACTION_9 = 0x10000000; + /// UDP Action 10. Has a value of `0x20000000`. + const UDP_ACTION_10 = 0x20000000; + /// UDP Action 11. Has a value of `0x40000000`. + const UDP_ACTION_11 = 0x40000000; + /// UDP Action 12. Has a value of `0x80000000`. + const UDP_ACTION_12 = 0x80000000; + } +} diff --git a/src/lib.rs b/src/lib.rs index 9172084..38a8857 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ pub mod constants; pub mod packets; use crate::constants::PacketId; -use crate::packets::{F1PacketMotionData, F1PacketSessionData}; +use crate::packets::{F1PacketEventData, F1PacketLapData, F1PacketMotionData, F1PacketSessionData}; use binrw::io::Cursor; use binrw::{BinRead, BinReaderExt, BinResult}; @@ -75,4 +75,10 @@ pub struct F1PacketBody { /// Data about the ongoing session. #[br(if(packet_id == PacketId::Session), args(packet_format))] pub session: Option, + /// Lap data for all cars on track. + #[br(if(packet_id == PacketId::LapData), args(packet_format))] + pub lap: Option, + /// Details of events that happen during the course of a session. + #[br(if(packet_id == PacketId::Event), args(packet_format))] + pub event: Option, } diff --git a/src/packets/event.rs b/src/packets/event.rs new file mode 100644 index 0000000..f84510c --- /dev/null +++ b/src/packets/event.rs @@ -0,0 +1,125 @@ +use super::u8_to_usize; +use crate::constants::{ButtonStatus, InfringementType, PenaltyType}; + +use binrw::BinRead; +use serde::{Deserialize, Serialize}; + +#[non_exhaustive] +#[derive( + BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize, +)] +#[br(little)] +pub enum EventDataDetails { + /// Sent when the session starts. + #[br(magic = b"SSTA")] + SessionStarted, + /// Sent when the session ends. + #[br(magic = b"SEND")] + SessionEnded, + /// Sent when a driver achieves the fastest lap. + #[br(magic = b"FTLP")] + FastestLap { + /// Index of the car that's achieved the fastest lap. + #[br(map(u8_to_usize))] + vehicle_index: usize, + /// Lap time in seconds. + lap_time: f32, + }, + /// Sent when a driver retires. + #[br(magic = b"RTMT")] + Retirement { + /// Index of the retiring car. + #[br(map(u8_to_usize))] + vehicle_index: usize, + }, + /// Sent when race control enable DRS. + #[br(magic = b"DRSE")] + DrsEnabled, + /// Sent when race control disable DRS. + #[br(magic = b"DRSD")] + DrsDisabled, + /// Sent when your teammate enters the pit lane. + #[br(magic = b"TMPT")] + TeamMateInPits { + /// Index of teammate's car. + #[br(map(u8_to_usize))] + vehicle_index: usize, + }, + /// Sent when the chequered flag has been waved. + #[br(magic = b"CHQF")] + ChequeredFlag, + /// Sent when the race winner is announced. + #[br(magic = b"RCWN")] + RaceWinner { + /// Index of race winner's car. + #[br(map(u8_to_usize))] + vehicle_index: usize, + }, + /// Sent when a penalty has been issued. + #[br(magic = b"PENA")] + Penalty { + /// Penalty type. + penalty_type: PenaltyType, + /// Infringement type. + infringement_type: InfringementType, + /// Index of the car the penalty is applied to. + #[br(map(u8_to_usize))] + vehicle_index: usize, + /// Index of the other car involved. + #[br(map(u8_to_usize))] + other_vehicle_index: usize, + /// Time gained/spent doing the action in seconds. + time: u8, + /// Number of the lap the infringement occurred on. + lap_num: u8, + /// Number of places gained by this infringement. + places_gained: u8, + }, + /// Sent when a speed trap is triggered. + #[br(magic = b"SPTP")] + SpeedTrap { + /// Index of the car that's triggered the speed trap. + #[br(map(u8_to_usize))] + vehicle_index: usize, + /// Top speed achieved in kilometres per hour. + speed: f32, + }, + /// Sent when a start light is lit. + #[br(magic = b"STLG")] + StartLights { + /// Number of lights showing. + num_lights: u8, + }, + /// "It's lights out, and away we go!" + #[br(magic = b"LGOT")] + LightsOut, + /// Sent when a driver has served a drive-through penalty. + #[br(magic = b"DTSV")] + DriveThroughServed { + /// Index of the vehicle serving the penalty. + #[br(map(u8_to_usize))] + vehicle_index: usize, + }, + /// Sent when a driver has served a stop-go penalty. + #[br(magic = b"SGSV")] + StopGoServed { + /// Index of the vehicle serving the penalty. + #[br(map(u8_to_usize))] + vehicle_index: usize, + }, + /// Sent when a flashback is activated. + #[br(magic = b"FLBK")] + Flashback { + /// Frame identifier that's been flashed back to. + frame_identifier: u32, + /// Session time that's been flashed back to. + flashback_session_time: f32, + }, + /// Sent when the button status has changed. + #[br(magic = b"BUTN")] + Buttons { + /// Bit flags specifying which buttons are currently pressed. + #[br(map(|bits: u32| ButtonStatus::from_bits_truncate(bits)))] + button_status: ButtonStatus + }, +} diff --git a/src/packets/mod.rs b/src/packets/mod.rs index 3e34da4..d9bae0b 100644 --- a/src/packets/mod.rs +++ b/src/packets/mod.rs @@ -1,8 +1,7 @@ pub mod motion; pub mod session; pub mod lap_data; - -use crate::packets::motion::CarMotionData; +pub mod event; use crate::constants::{ BrakingAssist, DynamicRacingLine, @@ -11,9 +10,11 @@ use crate::constants::{ TrackId, Weather, MAX_AI_DIFFICULTY, MAX_NUM_CARS, MAX_NUM_MARSHAL_ZONES, MAX_NUM_WEATHER_FORECAST_SAMPLES, }; +use crate::packets::event::EventDataDetails; +use crate::packets::lap_data::LapData; +use crate::packets::motion::CarMotionData; use crate::packets::session::{MarshalZone, WeatherForecastSample}; -use crate::packets::lap_data::LapData; use binrw::BinRead; use serde::{Deserialize, Serialize}; use std::string::FromUtf8Error; @@ -180,6 +181,23 @@ pub struct F1PacketLapData { 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, +} + /// Extended motion data for player's car. Available as a: /// - part of [`F1PacketMotionData`] in the 2022 format /// - standalone packet from the 2023 format onwards