Expand description
Convert binary data from F1 24, F1 23, and F1 22 UDP telemetry into organised structs.
§Getting started
Add f1-game-packet-parser to your project by running the following command:
cargo add f1-game-packet-parser§Basic UDP client
This crate doesn’t provide a UDP client out of the box. Here’s how to write one that will parse and pretty-print incoming packets:
use f1_game_packet_parser::parse;
use std::error::Error;
use std::net::UdpSocket;
fn main() -> Result<(), Box<dyn Error>> {
// This IP and port should be set in the game's options by default.
let socket = UdpSocket::bind("127.0.0.1:20777")?;
let mut buf = [0u8; 1464];
loop {
// Receive raw packet data from the game.
// The buf array should be large enough for all types of packets.
let (amt, _) = socket.recv_from(&mut buf)?;
// Convert received bytes to an F1Packet struct and print it.
let packet = parse(&buf[..amt])?;
println!("{:#?}", packet);
}
}§Determining a packet’s type and extracting its payload
An F1Packet consists of a universal header
and an Option field for a payload of every single packet type.
Only one of these can be set to Some for a given F1Packet instance.
Therefore, you can use the following if-else-if chain to differentiate between all packet types and extract their payloads. Of course, you can remove the branches you don’t need.
use f1_game_packet_parser::parse;
let placeholder_data = include_bytes!("placeholder.bin");
let packet = parse(placeholder_data)?;
if let Some(motion) = &packet.motion {
// Do whatever with motion.
} else if let Some(session) = &packet.session {
// Do whatever with session.
} else if let Some(laps) = &packet.laps {
// Do whatever with laps.
} else if let Some(event) = &packet.event {
// Do whatever with event.
} else if let Some(participants) = &packet.participants {
// Do whatever with participants.
} else if let Some(car_setups) = &packet.car_setups {
// Do whatever with car_setups.
} else if let Some(car_telemetry) = &packet.car_telemetry {
// Do whatever with car_telemetry.
} else if let Some(car_status) = &packet.car_status {
// Do whatever with car_status.
} else if let Some(final_classification) = &packet.final_classification {
// Do whatever with final_classification.
} else if let Some(lobby) = &packet.lobby {
// Do whatever with lobby.
} else if let Some(car_damage) = &packet.car_damage {
// Do whatever with car_damage.
} else if let Some(session_history) = &packet.session_history {
// Do whatever with session_history.
} else if let Some(tyre_sets) = &packet.tyre_sets {
// Available from the 2023 format onwards.
// Do whatever with tyre_sets.
} else if let Some(motion_ex) = &packet.motion_ex {
// Available from the 2023 format onwards.
// Do whatever with motion_ex.
} else if let Some(time_trial) = &packet.time_trial {
// Available from the 2024 format onwards.
// Do whatever with time_trial.
}§Working with event packets
F1PacketEvent is unique among other kinds of packets.
Its payload consists of a 4-letter code that determines
the type of the event, followed by optional details
about this event.
These extra details are represented by the EventDetails
enum (even if a certain event doesn’t come with additional data).
You can import the enum and use a matcher to determine an event’s type
and extract its payload (if available) like so:
use f1_game_packet_parser::packets::event::EventDetails;
use f1_game_packet_parser::parse;
let placeholder_data = include_bytes!("placeholder.bin");
let packet = parse(placeholder_data)?;
if let Some(event) = &packet.event {
match event.details {
/// Event with no extra details.
EventDetails::LightsOut => {
println!("It's lights out, and away we go!");
}
/// You can skip the details if you don't need them.
EventDetails::Flashback { .. } => {
println!("Flashback has been triggered!");
}
/// Extracting details from an event.
EventDetails::RaceWinner { vehicle_index } => {
println!(
"Driver at index {} is the winner!",
vehicle_index
);
}
_ => (),
}
}§Working with bitmaps
There are 3 fields that use a bitflags-powered bitmap struct:
EventDetails::Buttons::button_statusCarTelemetryData::rev_lights_bit_valueLapHistoryData::lap_valid_bit_flags
Each bitmap struct is publicly available via the constants module
and comes with a handful of constants representing specific bit values,
as well as methods and operator overloads for common bit operations.
Here’s an example that checks if a given binary file is a car telemetry packet. If so, it will grab player car’s telemetry data and determine whether the revs are high, medium or low based on the specific bit values being set.
use f1_game_packet_parser::constants::RevLights;
use f1_game_packet_parser::parse;
let placeholder_data = include_bytes!("placeholder.bin");
let packet = parse(placeholder_data)?;
let player_car_index = packet.header.player_car_index;
if let Some(car_telemetry) = &packet.car_telemetry {
let player = car_telemetry.data[player_car_index];
let is_high_rev =
player.rev_lights_bit_value.contains(RevLights::RIGHT_1);
let is_medium_rev =
player.rev_lights_bit_value.contains(RevLights::MIDDLE_1);
let revs_desc = if is_high_rev {
"High"
} else if is_medium_rev {
"Medium"
} else {
"Low"
};
println!("{} revs", revs_desc);
}See the respective structs’ documentation for a complete list of constants and methods.
§Original documentation links
Modules§
- constants
- Contains appendix constants and enums for various packet-specific struct field values.
- packets
- Contains structures for each kind of packet payload and submodules for packet-specific structs.
Structs§
- F1Packet
- Structured representation of raw F1 game packet data that’s
returned as a successful result of the
parsefunction. - F1Packet
Header - F1 game packet’s header. It contains metadata about the game, the ongoing session, the frame this packet was sent on, and player car indexes.