#[cfg(feature = "arrow")]
pub mod arrow;
pub mod bit_packed_decoder;
pub mod cli;
pub mod common;
pub mod details;
pub mod error;
pub mod filters;
pub mod game_events;
pub mod generator;
pub mod init_data;
pub mod message_events;
pub mod protocol_version_decoder;
pub mod state;
pub mod tracker_events;
pub mod versioned_decoder;
pub mod versions;
use crate::game_events::ReplayGameEvent;
pub use crate::state::*;
use crate::tracker_events::ReplayTrackerEvent;
pub use crate::versions::read_details;
pub use crate::versions::read_game_events;
pub use crate::versions::read_init_data;
pub use crate::versions::read_message_events;
pub use crate::versions::read_tracker_events;
#[cfg(feature = "arrow")]
pub use arrow::*;
pub use bit_packed_decoder::*;
pub use cli::*;
use colored::*;
pub use common::*;
pub use error::*;
pub use filters::*;
pub use init_data::*;
use nom::number::complete::u8;
use nom::IResult;
pub use protocol_version_decoder::read_protocol_header;
use std::collections::HashMap;
use std::io::Read;
use std::path::PathBuf;
use std::str;
pub use versioned_decoder::*;
pub use nom_mpq::parser::{self, peek_hex};
pub use nom_mpq::MPQ;
#[macro_export]
macro_rules! ok_or_return_missing_field_err {
($req_field:ident) => {
match $req_field {
Some(v) => v,
None => {
tracing::error!(
missing_field = stringify!($req_field),
"Required field not provided"
);
return Err(S2ProtocolError::MissingField(
stringify!($req_field).to_string(),
));
}
}
};
}
pub fn read_file(path: &PathBuf) -> Result<Vec<u8>, S2ProtocolError> {
let mut f = std::fs::File::open(path)?;
let mut buffer: Vec<u8> = vec![];
f.read_to_end(&mut buffer)?;
Ok(buffer)
}
pub const MAX_INITIAL_CAPACITY_BYTES: usize = 65536;
pub fn read_mpq(path: &str) -> Result<(MPQ, Vec<u8>), S2ProtocolError> {
tracing::info!("Processing MPQ file {}", path);
let file_contents = parser::read_file(path);
let (_, mpq) = parser::parse(&file_contents)?;
Ok((mpq, file_contents))
}
pub fn peek_bits(input: (&[u8], usize)) -> String {
if input.0.is_empty() {
return String::from("[]");
}
let input_str = format!("{:08b}", input.0[0]);
let mut res = String::from("[0b");
for (idx, bit_str) in input_str.chars().enumerate() {
match idx.cmp(&input.1) {
std::cmp::Ordering::Less => res.push_str(&format!("{}", bit_str).blue()),
std::cmp::Ordering::Equal => res.push_str(&format!(">{}<", bit_str).green()),
std::cmp::Ordering::Greater => res.push_str(&format!("{}", bit_str).yellow()),
};
}
res.push(']');
res.push_str(&peek_hex(input.0));
res
}
pub fn dbg_peek_bits<'a, F, O, E: std::fmt::Debug>(
f: F,
context: &'static str,
) -> impl Fn((&'a [u8], usize)) -> IResult<(&'a [u8], usize), O, E>
where
F: Fn((&'a [u8], usize)) -> IResult<(&'a [u8], usize), O, E>,
{
move |i: (&'a [u8], usize)| match f(i) {
Err(e) => {
tracing::error!("{}: Error({:?}) at: {}", context, e, peek_bits(i));
Err(e)
}
a => a,
}
}
pub fn dbg_peek_hex<'a, F, O, E: std::fmt::Debug>(
f: F,
context: &'static str,
) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], O, E>
where
F: Fn(&'a [u8]) -> IResult<&'a [u8], O, E>,
{
move |i: &'a [u8]| match f(i) {
Err(e) => {
tracing::error!("{}: Error({:?}) at: {}", context, e, peek_hex(i));
Err(e)
}
a => a,
}
}
#[tracing::instrument(level = "debug", skip(input), fields(input = peek_hex(input)))]
#[allow(clippy::unnecessary_cast)]
pub fn parse_vlq_int(input: &[u8]) -> IResult<&[u8], i64> {
let (mut tail, mut v_int_value) = dbg_peek_hex(u8, "v_int")(input)?;
let is_negative = v_int_value & 1 != 0;
let mut result: i64 = ((v_int_value >> 1) & 0x3f) as i64;
let mut bits: i64 = 6;
while (v_int_value & 0x80) != 0 {
let (new_tail, new_v_int_value) = dbg_peek_hex(u8, "v_int")(tail)?;
tail = new_tail;
result |= ((new_v_int_value as i64 & 0x7fi64) << bits) as i64;
v_int_value = new_v_int_value;
bits += 7;
}
if is_negative {
result = -result;
}
Ok((tail, result))
}
pub fn transform_to_naivetime(
time_utc: i64,
time_local_offset: i64,
) -> Option<chrono::NaiveDateTime> {
let micros = time_utc / 10 - 11_644_473_600_000_000 - time_local_offset / 10;
let secs = micros.div_euclid(1_000_000);
let nsecs = micros.rem_euclid(1_000_000) as u32 * 1000;
chrono::NaiveDateTime::from_timestamp_opt(secs, nsecs)
}
pub fn convert_tracker_loop_to_seconds(tracker_replay_loop: i64) -> u32 {
let ext_replay_milliseconds =
crate::TRACKER_SPEED_RATIO * tracker_replay_loop as f32 * 68.58391;
(ext_replay_milliseconds / 1000.0) as u32
}
#[cfg(test)]
mod tests {
use super::*;
#[test_log::test]
fn it_reads_v_int() {
let data: Vec<u8> = vec![
0x12, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let (tail, v_int) = parse_vlq_int(&data).unwrap();
assert_eq!(v_int, 9);
let (_tail, v_int) = parse_vlq_int(tail).unwrap();
assert_eq!(v_int, 22);
let input: Vec<u8> = vec![
0xac, 0xda, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let (_tail, v_int) = parse_vlq_int(&input).unwrap();
assert_eq!(v_int, 87702);
let input: Vec<u8> = vec![
0x09, 0xac, 0xda, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let (_tail, v_int) = tagged_vlq_int(&input).unwrap();
assert_eq!(v_int, 87702);
}
}