use std::io::Cursor;
use std::io::Read;
use bstr::BString;
use byteorder::LittleEndian;
use byteorder::ReadBytesExt;
#[cfg(feature = "serde")]
use serde::Deserialize;
#[cfg(feature = "serde")]
use serde::Serialize;
use super::Rule;
use crate::errors::Error;
use crate::errors::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct WorkshopId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct ModHash(pub u32);
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[non_exhaustive]
pub struct Mod {
pub hash: ModHash,
pub is_dlc: bool,
pub steam_id: WorkshopId,
pub name: BString,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[non_exhaustive]
pub struct Arma3Rules {
pub version: u32,
pub mods: Vec<Mod>,
pub signatures: Vec<BString>,
pub description: BString,
pub rules: Vec<Rule>,
}
impl Arma3Rules {
pub fn from_rules(rules: &[Rule]) -> Result<Self> {
let mut chunks: Vec<(u8, &[u8])> = Vec::new();
let mut standard: Vec<Rule> = Vec::new();
for rule in rules {
if rule.name.len() == 2 && rule.name[0] < 0x20 && rule.name[1] < 0x20 {
chunks.push((rule.name[0], &rule.value));
} else {
standard.push(rule.clone());
}
}
if chunks.is_empty() {
return Err(Error::NoBinaryChunks);
}
chunks.sort_by_key(|&(idx, _)| idx);
let total_len: usize = chunks.iter().map(|(_, v)| v.len()).sum();
let mut raw = Vec::with_capacity(total_len);
for (_, payload) in &chunks {
raw.extend_from_slice(payload);
}
let stream = unescape(&raw);
let mut cursor = Cursor::new(&stream);
let version = cursor.read_u32::<LittleEndian>()?;
let mod_count = cursor.read_u8()?;
let mut mods = Vec::with_capacity(mod_count as usize);
for _ in 0..mod_count {
let hash = ModHash(cursor.read_u32::<LittleEndian>()?);
let flags_byte = cursor.read_u8()?;
let is_dlc = flags_byte & 0x10 != 0;
let steam_id_len = (flags_byte & 0x0F) as usize;
let steam_id = read_var_uint(&mut cursor, steam_id_len)?;
let name_len = cursor.read_u8()? as usize;
let mut name_buf = vec![0u8; name_len];
cursor.read_exact(&mut name_buf)?;
mods.push(Mod {
hash,
is_dlc,
steam_id: WorkshopId(steam_id),
name: BString::new(name_buf),
});
}
let sig_count = cursor.read_u8()?;
let mut signatures = Vec::with_capacity(sig_count as usize);
for _ in 0..sig_count {
let sig_len = cursor.read_u8()? as usize;
let mut sig_buf = vec![0u8; sig_len];
cursor.read_exact(&mut sig_buf)?;
signatures.push(BString::new(sig_buf));
}
let desc_len = cursor.read_u8()? as usize;
let mut desc_buf = vec![0u8; desc_len];
cursor.read_exact(&mut desc_buf)?;
let description = BString::new(desc_buf);
Ok(Arma3Rules {
version,
mods,
signatures,
description,
rules: standard,
})
}
}
fn unescape(data: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(data.len());
let mut i = 0;
while i < data.len() {
if data[i] == 0x01 && i + 1 < data.len() {
match data[i + 1] {
0x01 => {
out.push(0x01);
i += 2;
continue;
}
0x02 => {
out.push(0x00);
i += 2;
continue;
}
0x03 => {
out.push(0xFF);
i += 2;
continue;
}
_ => {}
}
}
out.push(data[i]);
i += 1;
}
out
}
fn read_var_uint<R: Read>(r: &mut R, len: usize) -> Result<u64> {
if len == 0 {
return Ok(0);
}
if len > 8 {
return Err(Error::SteamIdTooLong);
}
let mut buf = [0u8; 8];
r.read_exact(&mut buf[..len])?;
Ok(u64::from_le_bytes(buf))
}