use anyhow::Context;
use anyhow::{bail, Result};
use log::{debug, error, warn};
use std::io::{self, Cursor, Read};
use crate::utils::hex;
pub fn read_vlq_size<R: Read>(stream: &mut R) -> Result<Option<(u32, usize)>> {
let mut buf = [0u8; 1];
match stream.read(&mut buf)? {
0 => return Ok(None), 1 => {}
_ => bail!("Unexpected read count when reading first byte of size prefix"),
}
let first_byte = buf[0];
let mut prefix_bytes_read: usize = 1;
let payload_size: i64;
if (first_byte & 0x80) != 0 {
if (first_byte & 0x40) == 0 {
payload_size = (first_byte & 0x7F) as i64;
} else {
bail!(
"Invalid first size prefix byte encountered: {:#02x}",
first_byte
);
}
} else {
if (first_byte & 0x40) != 0 {
let mut b1_buf = [0u8; 1];
stream
.read_exact(&mut b1_buf)
.context("Failed to read 2nd byte of 2-byte size prefix")?;
prefix_bytes_read += 1;
payload_size = (((first_byte as i64) << 8) | (b1_buf[0] as i64)) ^ 0x4000;
} else if (first_byte & 0x20) != 0 {
let mut b1_b2_buf = [0u8; 2];
stream
.read_exact(&mut b1_b2_buf)
.context("Failed to read bytes 2-3 of 3-byte size prefix")?;
prefix_bytes_read += 2;
payload_size = (((first_byte as i64) << 16)
| ((b1_b2_buf[0] as i64) << 8)
| (b1_b2_buf[1] as i64))
^ 0x200000;
} else if (first_byte & 0x10) != 0 {
let mut b1_b3_buf = [0u8; 3];
stream
.read_exact(&mut b1_b3_buf)
.context("Failed to read bytes 2-4 of 4-byte size prefix")?;
prefix_bytes_read += 3;
payload_size = (((first_byte as i64) << 24)
| ((b1_b3_buf[0] as i64) << 16)
| ((b1_b3_buf[1] as i64) << 8)
| (b1_b3_buf[2] as i64))
^ 0x10000000;
} else {
let mut b1_b4_buf = [0u8; 4];
stream
.read_exact(&mut b1_b4_buf)
.context("Failed to read bytes 2-5 of 5-byte size prefix")?;
prefix_bytes_read += 4;
payload_size = u32::from_le_bytes(b1_b4_buf) as i64;
}
}
if payload_size < 0 {
warn!(
"Calculated negative payload size ({}). This bodes ill.",
payload_size
);
}
let final_size = payload_size.try_into().with_context(|| {
format!(
"Payload size {} cannot fit into u32 (prefix starts with {:#02x})",
payload_size, first_byte
)
})?;
Ok(Some((final_size, prefix_bytes_read)))
}
pub fn read_packet_header<R: Read>(
stream: &mut R,
last_timestamp_ticks: u32,
) -> Result<Option<(u8, u32, usize)>> {
let mut first_byte_buf = [0u8; 1];
match stream.read(&mut first_byte_buf)? {
0 => return Ok(None), 1 => {}
_ => bail!("Unexpected read count reading first byte of packet header"),
}
let first_byte = first_byte_buf[0];
let mut bytes_read_for_header = 1;
let mut timestamp_ticks = last_timestamp_ticks;
let packet_type_val: u8;
if (first_byte & 0x10) != 0 {
packet_type_val = first_byte ^ 0x10;
} else {
packet_type_val = first_byte;
let mut ts_bytes = [0u8; 4];
match stream.read_exact(&mut ts_bytes) {
Ok(_) => {
timestamp_ticks = u32::from_le_bytes(ts_bytes);
bytes_read_for_header += 4;
}
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
warn!("Unexpected EOF reading timestamp after type byte {:#02x}. Using last known timestamp.", packet_type_val);
return Ok(Some((
packet_type_val,
timestamp_ticks,
bytes_read_for_header,
)));
}
Err(e) => {
return Err(e).context("Failed to read timestamp bytes");
}
}
}
Ok(Some((
packet_type_val,
timestamp_ticks,
bytes_read_for_header,
)))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReplayPacketType {
EndMarker = 0,
StartMarker = 1,
AircraftSmall = 2,
Chat = 3,
MPI = 4,
NextSegment = 5,
ECS = 6,
Snapshot = 7,
ReplayHeaderInfo = 8,
Unknown = 255,
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct PacketInfo {
pub packet_type: ReplayPacketType,
pub timestamp_ticks: u32,
pub payload: Vec<u8>,
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct ChatInfo {
pub timestamp_ticks: u32,
pub sender: String,
pub message: String,
pub channel_type: Option<u8>,
pub is_enemy: Option<u8>,
}
#[derive(Debug)]
pub struct AwardInfo {
pub timestamp_ticks: u32,
pub award_type: u8,
pub player: u8,
pub award_name: String,
}
pub fn parse_chat_packet(payload: &[u8], timestamp_ticks: u32) -> Option<ChatInfo> {
let mut cursor = Cursor::new(payload);
fn read_u8(cur: &mut Cursor<&[u8]>) -> Result<u8> {
let mut buf = [0u8; 1];
cur.read_exact(&mut buf).context("Failed to read byte")?;
Ok(buf[0])
}
fn read_string(cur: &mut Cursor<&[u8]>, len: usize, full_len: usize) -> Result<String> {
let current_pos = cur.position() as usize;
if current_pos + len > full_len {
bail!("Payload too short for string of length {}", len);
}
let mut buf = vec![0u8; len];
cur.read_exact(&mut buf)?;
String::from_utf8(buf).context("Failed to decode UTF-8 string")
}
if payload.is_empty() {
warn!("[Chat Type 3] Empty payload.");
return None;
}
let mut skip_buf = [0u8; 1];
if let Err(e) = cursor.read_exact(&mut skip_buf) {
if e.kind() == std::io::ErrorKind::UnexpectedEof {
warn!("[Chat Type 3] Payload was empty when trying to read subtype/flag byte.");
} else {
error!("[Chat Type 3] Error reading subtype/flag byte: {:?}", e);
}
return None;
}
if cursor.position() as usize >= payload.len() {
warn!("[Chat Type 3] Payload contained only the initial subtype/flag byte.");
return None;
}
match (|| -> Result<ChatInfo> {
let sender_len = read_u8(&mut cursor)? as usize;
let sender_name = read_string(&mut cursor, sender_len, payload.len())?;
let message_len = read_u8(&mut cursor)? as usize;
let message = read_string(&mut cursor, message_len, payload.len())?;
let remaining = payload.len() as u64 - cursor.position();
let channel_type = if remaining >= 1 {
Some(read_u8(&mut cursor)?)
} else {
None
};
let is_enemy = if remaining >= 2 {
Some(read_u8(&mut cursor)?)
} else {
None
};
debug!(
"[Chat] Decoded message - Timestamp: {} ms, Sender: '{}', Message: '{}', Channel: {:?}, Enemy: {:?}",
timestamp_ticks, sender_name, message, channel_type, is_enemy
);
Ok(ChatInfo {
timestamp_ticks,
sender: sender_name,
message,
channel_type,
is_enemy,
})
})() {
Ok(chat_info) => Some(chat_info),
Err(e) => {
error!(
"[Chat Type 3] Error parsing packet payload: {:?}. Payload start: {}...",
e,
hex::encode(&payload[..std::cmp::min(payload.len(), 30)])
);
None
}
}
}
pub fn parse_award_packet(payload: &[u8], timestamp_ticks: u32) -> Option<AwardInfo> {
let mut cursor = Cursor::new(payload);
let mut sig = [0u8; 4];
if cursor.read_exact(&mut sig).is_err() {
return None;
}
if sig != [0x00, 0x02, 0x58, 0x78] {
return None;
}
let mut marker = [0u8; 1];
cursor.read_exact(&mut marker).ok()?;
let mut at = [0u8; 1];
cursor.read_exact(&mut at).ok()?;
let award_type = at[0];
let mut b003e = [0u8; 2];
if cursor.read_exact(&mut b003e).is_err() {
return None;
}
let mut b_player = [0u8; 1];
if cursor.read_exact(&mut b_player).is_err() {
return None;
}
let player = b_player[0];
let mut b000000 = [0u8; 3];
if cursor.read_exact(&mut b000000).is_err() {
return None;
}
let mut len_buf = [0u8; 1];
if cursor.read_exact(&mut len_buf).is_err() {
return None;
}
let name_len = len_buf[0] as usize;
let mut name_buf = vec![0u8; name_len];
if cursor.read_exact(&mut name_buf).is_err() {
return None;
}
let award_name = String::from_utf8_lossy(&name_buf).into_owned();
let _ = cursor.read_to_end(&mut Vec::new());
Some(AwardInfo {
timestamp_ticks,
award_type,
player,
award_name,
})
}
#[derive(Debug)]
pub struct KillInfo {
pub timestamp_ticks: u32,
pub control: u8,
pub damage_type: u8,
pub killer_id: u8,
pub killer_vehicle: String,
}
pub fn parse_kill_packet(payload: &[u8], timestamp_ticks: u32) -> Option<KillInfo> {
let mut cursor = Cursor::new(payload);
let mut signature = [0u8; 4];
let _ = cursor.read_exact(&mut signature);
let mut always_0xf0 = [0u8; 1];
let _ = cursor.read_exact(&mut always_0xf0);
let mut control = [0u8; 1];
let _ = cursor.read_exact(&mut control);
let control_byte = control[0];
let damage_type = control_byte & 0xF0;
let mut always_0x00fe3f = [0u8; 3];
let _ = cursor.read_exact(&mut always_0x00fe3f);
let mut killer_id_buf = [0u8; 1];
let _ = cursor.read_exact(&mut killer_id_buf);
let killer_id = killer_id_buf[0];
let mut always_0x000000 = [0u8; 3];
let _ = cursor.read_exact(&mut always_0x000000);
let mut vehicle_len_buf = [0u8; 1];
let _ = cursor.read_exact(&mut vehicle_len_buf);
let vehicle_len = vehicle_len_buf[0] as usize;
let mut vehicle_buf = vec![0u8; vehicle_len];
let _ = cursor.read_exact(&mut vehicle_buf);
let killer_vehicle = String::from_utf8(vehicle_buf).unwrap_or_else(|_| String::new());
Some(KillInfo {
timestamp_ticks,
control: control_byte,
damage_type,
killer_id,
killer_vehicle,
})
}