Skip to content

Instantly share code, notes, and snippets.

@Measter
Created October 21, 2020 01:50
Show Gist options
  • Save Measter/e2cd1f2696f7d894e27d447df18f69f0 to your computer and use it in GitHub Desktop.
Save Measter/e2cd1f2696f7d894e27d447df18f69f0 to your computer and use it in GitHub Desktop.
AC Server Plugin Server Protocol
use nom::{IResult, number::complete::*, error::ErrorKind};
use nom_derive::Nom;
use thiserror::Error;
use std::{time::Duration, convert::TryFrom};
pub(crate) enum AcsCommand {
RealtimePosInterval = 200,
GetCarInfo = 201,
SendChat = 202,
BroadcastChat = 203,
GetSessionInfo = 204,
SetSessionInfo = 205,
KickUser = 206,
NextSession = 207,
RestartSession = 208,
AdminCommand = 209,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum AcsMessageId {
NewSession,
NewConnection,
ConnectionClosed,
CarUpdate,
CarInfo,
EndSession,
LapCompleted,
Version,
Chat,
ClientLoaded,
SessionInfo,
Error,
ClientEvent,
}
impl PartialEq<u8> for AcsMessageId {
fn eq(&self, val: &u8) -> bool {
Self::try_from(*val).map(|v| v == *self).unwrap_or(false)
}
}
#[derive(Debug, Copy, Clone, Error)]
#[error("Unknown packet ID: {0}")]
pub struct PacketIdError(pub u8);
impl TryFrom<u8> for AcsMessageId {
type Error = PacketIdError;
fn try_from(id: u8) -> Result<Self, PacketIdError> {
let val = match id {
50 => AcsMessageId::NewSession,
51 => AcsMessageId::NewConnection,
52 => AcsMessageId::ConnectionClosed,
53 => AcsMessageId::CarUpdate,
54 => AcsMessageId::CarInfo,
55 => AcsMessageId::EndSession,
73 => AcsMessageId::LapCompleted,
56 => AcsMessageId::Version,
57 => AcsMessageId::Chat,
58 => AcsMessageId::ClientLoaded,
59 => AcsMessageId::SessionInfo,
60 => AcsMessageId::Error,
130 => AcsMessageId::ClientEvent,
_ => return Err(PacketIdError(id)),
};
Ok(val)
}
}
#[derive(Debug, Clone, Nom)]
#[nom(Selector = "AcsMessageId")]
pub enum AcsMessage {
#[nom(Selector = "AcsMessageId::Version")]
Version(u8),
#[nom(Selector = "AcsMessageId::NewSession")]
NewSession(SessionInfo),
#[nom(Selector = "AcsMessageId::NewConnection")]
NewConnection(ConnectionInfo),
#[nom(Selector = "AcsMessageId::ConnectionClosed")]
ConnectionClosed(ConnectionInfo),
#[nom(Selector = "AcsMessageId::ClientLoaded")]
ClientLoaded(u8),
#[nom(Selector = "AcsMessageId::SessionInfo")]
SessionInfo(SessionInfo),
#[nom(Selector = "AcsMessageId::Chat")]
Chat(ChatInfo),
#[nom(Selector = "AcsMessageId::CarUpdate")]
CarUpdate(CarUpdateInfo),
#[nom(Selector = "AcsMessageId::CarInfo")]
CarInfo(CarInfo),
#[nom(Selector = "AcsMessageId::EndSession")]
EndSession(#[nom(Parse = "parse_u32_string")] String),
#[nom(Selector = "AcsMessageId::Error")]
Error(#[nom(Parse = "parse_u32_string")] String),
#[nom(Selector = "AcsMessageId::LapCompleted")]
LapCompleted(LapCompletedInfo),
#[nom(Selector = "AcsMessageId::ClientEvent")]
ClientEvent(ClientEventInfo),
}
#[repr(u8)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Nom)]
pub enum SessionType {
Practice = 1,
Qualifying = 2,
Race = 3,
None,
}
impl SessionType {
fn from_u8(v: u8) -> SessionType {
match v {
1 => SessionType::Practice,
2 => SessionType::Qualifying,
3 => SessionType::Race,
_ => SessionType::None,
}
}
}
#[derive(Debug, Copy, Clone)]
pub enum SessionLength {
Time(Duration),
Laps(u16),
}
#[derive(Debug, Clone, Nom)]
#[nom(LittleEndian)]
pub struct SessionInfo {
pub version: u8,
pub message_session_index: u8,
pub server_session_index: u8,
pub session_count: u8,
#[nom(Parse="parse_u32_string")]
pub server_name: String,
#[nom(Parse="parse_ascii_string")]
pub track_name: String,
#[nom(Parse="parse_ascii_string")]
pub track_layout: String,
#[nom(Parse="parse_ascii_string")]
pub session_name: String,
#[nom(Parse="le_u8", Map = "|x: u8| SessionType::from_u8(x)")]
pub session_type: SessionType,
#[nom(Parse="parse_session_length")]
pub session_length: SessionLength,
pub wait_time: u16,
pub ambient_temp: u8,
pub road_temp: u8,
#[nom(Parse="parse_ascii_string")]
pub weather: String,
pub elapsed: i32,
}
#[derive(Debug, Clone, Nom)]
#[nom(LittleEndian)]
pub struct ConnectionInfo {
#[nom(Parse="parse_u32_string")]
pub driver_name: String,
#[nom(Parse="parse_u32_string")]
pub driver_guid: String,
pub car_id: u8,
#[nom(Parse="parse_ascii_string")]
pub car_model: String,
#[nom(Parse="parse_ascii_string")]
pub car_skin: String,
}
#[derive(Debug, Clone, Nom)]
pub struct ChatInfo {
pub car_id: u8,
#[nom(Parse="parse_u32_string")]
pub message: String,
}
#[derive(Debug, Copy, Clone, PartialEq, Nom, Default)]
#[nom(LittleEndian)]
pub struct Vector3F {
#[nom(Parse="le_f32")]
pub x: f32,
#[nom(Parse="le_f32")]
pub y: f32,
#[nom(Parse="le_f32")]
pub z: f32,
}
impl Vector3F {
pub fn length(self) -> f32 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
}
#[derive(Debug, Clone, Nom)]
#[nom(LittleEndian)]
pub struct CarUpdateInfo {
pub car_id: u8,
pub position: Vector3F,
pub velocity: Vector3F,
pub gear: u8,
pub engine_rpm: u16,
#[nom(Parse="le_f32")]
pub spline_pos: f32,
}
#[derive(Debug, Clone, Nom)]
#[nom(LittleEndian)]
pub struct CarInfo {
pub car_id: u8,
#[nom(Parse = "le_u8", Map = "|x| x != 0")]
pub is_connected: bool,
#[nom(Parse = "parse_u32_string")]
pub car_model: String,
#[nom(Parse = "parse_u32_string")]
pub car_skin: String,
#[nom(Parse = "parse_u32_string")]
pub driver_name: String,
#[nom(Parse = "parse_u32_string")]
pub driver_team: String,
#[nom(Parse = "parse_u32_string")]
pub driver_guid: String,
}
#[derive(Debug, Clone, Nom)]
#[nom(LittleEndian)]
pub struct LeaderboardEntry {
pub car_id: u8,
#[nom(Parse = "le_u32", Map = "|m| Duration::from_millis(m as u64)")]
pub laptime: Duration,
pub laps: u16,
#[nom(Parse = "le_u8", Map = "|x| x != 0")]
pub has_finished: bool,
}
#[derive(Debug, Clone, Nom)]
pub struct LapCompletedInfo {
pub car_id: u8,
#[nom(Parse = "le_u32", Map = "|m| Duration::from_millis(m as u64)")]
pub laptime: Duration,
pub cuts: u8,
#[nom(Parse = "parse_leaderboard")]
pub leaderboard: Vec<LeaderboardEntry>,
#[nom(Parse = "le_f32")]
pub grip_level: f32,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum CollisionType {
Environment,
Car(u8),
}
#[derive(Debug, Clone, Nom)]
#[nom(LittleEndian)]
pub struct ClientEventInfo {
#[nom(Parse = "parse_collision_type")]
pub collision_type: CollisionType,
pub car_id: u8,
#[nom(Parse = "le_f32")]
pub speed: f32,
pub world_position: Vector3F,
pub relative_position: Vector3F,
}
pub fn parse_message(data: &[u8], id: AcsMessageId) -> IResult<&[u8], AcsMessage> {
AcsMessage::parse(data, id)
}
fn parse_collision_type(data: &[u8]) -> IResult<&[u8], CollisionType> {
let (mut rem, type_id) = le_u8(data)?;
let col_type = match type_id {
10 => {
let (r, car_id) = le_u8(rem)?;
rem = r;
CollisionType::Car(car_id)
},
11 => CollisionType::Environment,
_ => {
return Err(nom::Err::Failure((data, ErrorKind::NoneOf)))
}
};
Ok((rem, col_type))
}
fn parse_leaderboard(input: &[u8]) -> IResult<&[u8], Vec<LeaderboardEntry>> {
let (mut rem, entries) = le_u8(input)?;
let mut board = Vec::with_capacity(entries as usize);
for _ in 0..entries {
let (r, entry) = LeaderboardEntry::parse(rem)?;
rem = r;
board.push(entry);
}
Ok((rem, board))
}
fn parse_session_length(input: &[u8]) -> IResult<&[u8], SessionLength> {
// pub session_length_minutes: u16,
// pub session_length_laps: u16,
let (rem, minutes) = le_u16(input)?;
let (rem, laps) = le_u16(rem)?;
let retval = if laps == 0 {
SessionLength::Time(Duration::from_secs(minutes as u64 * 60))
} else {
SessionLength::Laps(laps)
};
Ok((rem, retval))
}
fn parse_u32_string(input: &[u8]) -> IResult<&[u8], String> {
let (mut rem, length) = le_u8(input)?;
let mut string = String::new();
for _ in 0..length {
let (remaining, character) = le_u32(rem)?;
rem = remaining;
match std::char::from_u32(character) {
Some(c) => string.push(c),
None => {
return Err(nom::Err::Failure( (input, ErrorKind::Char) ))
}
}
}
Ok((rem, string))
}
fn parse_ascii_string(input: &[u8]) -> IResult<&[u8], String> {
let (mut rem, length) = le_u8(input)?;
let mut string = String::with_capacity(length as usize);
for _ in 0..length {
let (remaining, character) = le_u8(rem)?;
rem = remaining;
string.push(character as char);
}
Ok((rem, string))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment