use byteorder::{LittleEndian, ReadBytesExt, LE};
use num_traits::FromPrimitive;
use std::io::{self, ErrorKind, Read};
use thiserror::Error;
use super::*;
#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct Header {
pub arcdps_build: String,
pub combat_id: u16,
pub agent_count: u32,
pub revision: u8,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct Evtc {
pub header: Header,
pub skill_count: u32,
pub agents: Vec<Agent>,
pub skills: Vec<Skill>,
pub events: Vec<CbtEvent>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct PartialEvtc {
pub header: Header,
pub skill_count: u32,
pub agents: Vec<Agent>,
pub skills: Vec<Skill>,
}
impl From<Evtc> for PartialEvtc {
fn from(evtc: Evtc) -> Self {
Self {
header: evtc.header,
skill_count: evtc.skill_count,
agents: evtc.agents,
skills: evtc.skills,
}
}
}
#[derive(Error, Debug)]
pub enum ParseError {
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("utf8 decoding error: {0}")]
Utf8Error(#[from] std::string::FromUtf8Error),
#[error("invalid data")]
InvalidData,
#[error("malformed header")]
MalformedHeader,
#[error("unknown revision: {0}")]
UnknownRevision(u8),
#[error("unknown statechange event: {0}")]
UnknownStateChange(u8),
#[error("invalid archive: {0}")]
InvalidZip(#[from] zip::result::ZipError),
}
pub type ParseResult<T> = Result<T, ParseError>;
pub fn parse_header<R: Read>(mut input: R) -> ParseResult<Header> {
let mut magic_number = [0; 4];
input.read_exact(&mut magic_number)?;
if &magic_number != b"EVTC" {
return Err(ParseError::MalformedHeader);
}
let mut arcdps_build = vec![0; 8];
input.read_exact(&mut arcdps_build)?;
let build_string = String::from_utf8(arcdps_build)?;
let mut revision = [0];
input.read_exact(&mut revision)?;
let revision = revision[0];
let combat_id = input.read_u16::<LittleEndian>()?;
let mut zero = [0];
input.read_exact(&mut zero)?;
if zero != [0] {
return Err(ParseError::MalformedHeader);
}
let agent_count = input.read_u32::<LittleEndian>()?;
Ok(Header {
arcdps_build: build_string,
combat_id,
agent_count,
revision,
})
}
pub fn parse_agents<R: Read>(mut input: R, count: u32) -> ParseResult<Vec<Agent>> {
let mut result = Vec::with_capacity(count as usize);
for _ in 0..count {
result.push(parse_agent(&mut input)?);
}
Ok(result)
}
pub fn parse_agent<R: Read>(mut input: R) -> ParseResult<Agent> {
let addr = input.read_u64::<LittleEndian>()?;
let prof = input.read_u32::<LittleEndian>()?;
let is_elite = input.read_u32::<LittleEndian>()?;
let toughness = input.read_i16::<LittleEndian>()?;
let concentration = input.read_i16::<LittleEndian>()?;
let healing = input.read_i16::<LittleEndian>()?;
input.read_i16::<LittleEndian>()?;
let condition = input.read_i16::<LittleEndian>()?;
input.read_i16::<LittleEndian>()?;
let mut name = [0; 64];
input.read_exact(&mut name)?;
let mut skip = [0; 4];
input.read_exact(&mut skip)?;
Ok(Agent {
addr,
prof,
is_elite,
toughness,
concentration,
healing,
condition,
name,
})
}
pub fn parse_skills<R: Read>(mut input: R, count: u32) -> ParseResult<Vec<Skill>> {
let mut result = Vec::with_capacity(count as usize);
for _ in 0..count {
result.push(parse_skill(&mut input)?);
}
Ok(result)
}
pub fn parse_skill<R: Read>(mut input: R) -> ParseResult<Skill> {
let id = input.read_i32::<LittleEndian>()?;
let mut name = [0; 64];
input.read_exact(&mut name)?;
Ok(Skill { id, name })
}
pub fn parse_events<R: Read>(
mut input: R,
parser: fn(&mut R) -> ParseResult<CbtEvent>,
) -> ParseResult<Vec<CbtEvent>> {
let mut result = Vec::new();
loop {
let event = parser(&mut input);
match event {
Ok(x) => result.push(x),
Err(ParseError::UnknownStateChange(_)) => {
}
Err(ParseError::Io(ref e)) if e.kind() == ErrorKind::UnexpectedEof => {
return Ok(result)
}
Err(e) => return Err(e),
}
}
}
pub fn parse_event_rev0<R: Read>(mut input: R) -> ParseResult<CbtEvent> {
let time = input.read_u64::<LittleEndian>()?;
let src_agent = input.read_u64::<LE>()?;
let dst_agent = input.read_u64::<LE>()?;
let value = input.read_i32::<LE>()?;
let buff_dmg = input.read_i32::<LE>()?;
let overstack_value = input.read_u16::<LE>()? as u32;
let skillid = input.read_u16::<LE>()? as u32;
let src_instid = input.read_u16::<LE>()?;
let dst_instid = input.read_u16::<LE>()?;
let src_master_instid = input.read_u16::<LE>()?;
let mut skip = [0; 9];
input.read_exact(&mut skip)?;
let iff = IFF::from_u8(input.read_u8()?).unwrap_or(IFF::None);
let buff = input.read_u8()?;
let result = CbtResult::from_u8(input.read_u8()?).unwrap_or(CbtResult::None);
let is_activation = CbtActivation::from_u8(input.read_u8()?).unwrap_or(CbtActivation::None);
let is_buffremove = CbtBuffRemove::from_u8(input.read_u8()?).unwrap_or(CbtBuffRemove::None);
let is_ninety = input.read_u8()? != 0;
let is_fifty = input.read_u8()? != 0;
let is_moving = input.read_u8()? != 0;
let statechange = input.read_u8()?;
let is_statechange =
CbtStateChange::from_u8(statechange).ok_or(ParseError::UnknownStateChange(statechange));
let is_flanking = input.read_u8()? != 0;
let is_shields = input.read_u8()? != 0;
input.read_u16::<LE>()?;
Ok(CbtEvent {
time,
src_agent,
dst_agent,
value,
buff_dmg,
overstack_value,
skillid,
src_instid,
dst_instid,
src_master_instid,
dst_master_instid: 0,
iff,
buff,
result,
is_activation,
is_buffremove,
is_ninety,
is_fifty,
is_moving,
is_statechange: is_statechange?,
is_flanking,
is_shields,
is_offcycle: false,
padding_end: 0,
})
}
pub fn parse_event_rev1<R: Read>(mut input: R) -> ParseResult<CbtEvent> {
let time = input.read_u64::<LittleEndian>()?;
let src_agent = input.read_u64::<LE>()?;
let dst_agent = input.read_u64::<LE>()?;
let value = input.read_i32::<LE>()?;
let buff_dmg = input.read_i32::<LE>()?;
let overstack_value = input.read_u32::<LE>()?;
let skillid = input.read_u32::<LE>()?;
let src_instid = input.read_u16::<LE>()?;
let dst_instid = input.read_u16::<LE>()?;
let src_master_instid = input.read_u16::<LE>()?;
let dst_master_instid = input.read_u16::<LE>()?;
let iff = IFF::from_u8(input.read_u8()?).unwrap_or(IFF::None);
let buff = input.read_u8()?;
let result = CbtResult::from_u8(input.read_u8()?).unwrap_or(CbtResult::None);
let is_activation = CbtActivation::from_u8(input.read_u8()?).unwrap_or(CbtActivation::None);
let is_buffremove = CbtBuffRemove::from_u8(input.read_u8()?).unwrap_or(CbtBuffRemove::None);
let is_ninety = input.read_u8()? != 0;
let is_fifty = input.read_u8()? != 0;
let is_moving = input.read_u8()? != 0;
let statechange = input.read_u8()?;
let is_statechange =
CbtStateChange::from_u8(statechange).ok_or(ParseError::UnknownStateChange(statechange));
let is_flanking = input.read_u8()? != 0;
let is_shields = input.read_u8()? != 0;
let is_offcycle = input.read_u8()? != 0;
let padding_end = input.read_u32::<LE>()?;
Ok(CbtEvent {
time,
src_agent,
dst_agent,
value,
buff_dmg,
overstack_value,
skillid,
src_instid,
dst_instid,
src_master_instid,
dst_master_instid,
iff,
buff,
result,
is_activation,
is_buffremove,
is_ninety,
is_fifty,
is_moving,
is_statechange: is_statechange?,
is_flanking,
is_shields,
is_offcycle,
padding_end,
})
}
pub fn parse_partial_file<R: Read>(mut input: R) -> ParseResult<PartialEvtc> {
let header = parse_header(&mut input)?;
let agents = parse_agents(&mut input, header.agent_count)?;
let skill_count = input.read_u32::<LittleEndian>()?;
let skills = parse_skills(input, skill_count)?;
Ok(PartialEvtc {
header,
skill_count,
agents,
skills,
})
}
#[allow(clippy::redundant_closure)]
pub fn finish_parsing<R: Read>(partial: PartialEvtc, input: R) -> ParseResult<Evtc> {
let events = match partial.header.revision {
0 => parse_events(input, |r| parse_event_rev0(r))?,
1 => parse_events(input, |r| parse_event_rev1(r))?,
x => return Err(ParseError::UnknownRevision(x)),
};
Ok(Evtc {
header: partial.header,
skill_count: partial.skill_count,
agents: partial.agents,
skills: partial.skills,
events,
})
}
pub fn parse_file<R: Read>(mut input: R) -> ParseResult<Evtc> {
let partial = parse_partial_file(&mut input)?;
finish_parsing(partial, input)
}