Created
October 21, 2020 01:50
-
-
Save Measter/e2cd1f2696f7d894e27d447df18f69f0 to your computer and use it in GitHub Desktop.
AC Server Plugin Server Protocol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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