diff options
-rw-r--r-- | src/event.rs | 5 | ||||
-rw-r--r-- | src/lib.rs | 87 | ||||
-rw-r--r-- | src/raw/mod.rs | 9 | ||||
-rw-r--r-- | src/raw/parser.rs | 29 | ||||
-rw-r--r-- | src/raw/types.rs | 3 |
5 files changed, 123 insertions, 10 deletions
diff --git a/src/event.rs b/src/event.rs index 0ccbced..6dc6591 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,3 +1,6 @@ +//! Event definitions. +//! +//! This module contains the different types of events in their high-level form. use super::raw; use std::convert::TryFrom; @@ -19,7 +22,7 @@ pub enum FromRawEventError { /// A rusty enum for all possible combat events. /// -/// This makes dealing with `CbtEvent` a bit saner (and safer). +/// This makes dealing with [`CbtEvent`][raw::CbtEvent] a bit saner (and safer). #[derive(Clone, Debug, PartialEq)] pub enum EventKind { // State change events @@ -1,21 +1,58 @@ //! `evtclib` is a crate aiming to provide utility functions to parse and work //! with `.evtc` reports generated by arcdps. //! +//! # About evtc Files +//! +//! evtc files are files generated by the (inofficial) arcdps addon to Guild Wars 2. They contain +//! metadata about a fight in the game, such as the boss's name (if it was a raid or fractal boss), +//! the participants, and a stripped-down log of the complete fight. +//! +//! There are other programs (such as +//! [GW2-Elite-Insights-Parser](https://github.com/baaron4/GW2-Elite-Insights-Parser/)) and +//! websites (such as [dps.report](https://dps.report)) which allow you to generate reports from +//! evtc files. +//! +//! A common way to store and distribute evtc files is to zip them to either a `.evtc.zip` (old +//! way) or a `.zevtc` (new way). evtclib uses [`zip`](https://crates.io/crates/zip) to read them, +//! prodiving the [`raw::parse_zip`][raw::parse_zip] convenience function. +//! +//! # Crate Structure +//! +//! The crate consists of two main parts: The [`raw`][raw] parser, which is used to read structured +//! data from binary input streams, and the higher-level abstrations provided in the root and +//! [`event`][event] submodules. +//! +//! Additionally, there are some defintions (such as IDs for various game items) in the +//! [`gamedata`][gamedata] module. +//! +//! The main structs that you should be dealing with are the [`Log`][Log] and its components, such +//! as [`Event`][Event] and [`Agent`][Agent]. +//! //! # Workflow //! +//! Currently, there is no convenience function to turn a file into a [`Log`][Log] directly, so you +//! have to use the [`raw`][raw] submodule to obtain a low-level [`Evtc`][raw::Evtc], and then +//! convert it to the high-level [`Log`][Log]. +//! //! ```no_run -//! # use std::fs::File; -//! // Open some file for processing -//! let mut file = File::open("my_log.evtc").unwrap(); +//! # fn main() -> Result<(), Box<dyn std::error::Error>> { +//! use std::fs::File; +//! // Open a file for processing +//! let mut file = File::open("my_log.evtc")?; //! // Parse the raw content of the file -//! let raw_log = evtclib::raw::parse_file(&mut file).unwrap(); +//! let raw_log = evtclib::raw::parse_file(&mut file)?; //! // Process the file to do the nitty-gritty low-level stuff done -//! let log = evtclib::process(&raw_log).unwrap(); +//! let log = evtclib::process(&raw_log)?; //! // Do work on the log +//! for player in log.players() { +//! println!("Player {} participated!", player.account_name()); +//! } +//! # Ok(()) +//! # } //! ``` //! -//! (Look at the note on "Buffering" in the [parser -//! module](raw/parser/index.html#buffering)) +//! Make sure to take a look at the note on "Buffering" in the [parser +//! module](raw/parser/index.html#buffering) in order to increase the speed of your application. use std::convert::TryFrom; use std::marker::PhantomData; @@ -32,14 +69,23 @@ pub use event::{Event, EventKind}; pub mod gamedata; pub use gamedata::{Boss, EliteSpec, Profession}; +/// Any error that can occur during the processing of evtc files. #[derive(Error, Debug)] pub enum EvtcError { + /// Generic error for invalid data in the evtc file. #[error("invalid data has been provided")] InvalidData, + /// The profession id is not known. + /// + /// The field contains the unknown profession id. #[error("invalid profession id: {0}")] InvalidProfession(u32), + /// The elite specialization id is not known. + /// + /// The field contains the unknown elite specialization id. #[error("invalid elite specialization id: {0}")] InvalidEliteSpec(u32), + /// The file contains invalid utf-8. #[error("utf8 decoding error: {0}")] Utf8Error(#[from] std::str::Utf8Error), } @@ -95,12 +141,17 @@ impl Player { /// [Entangle](https://wiki.guildwars2.com/wiki/Entangle) or the other objects in the arena. #[derive(Debug, Clone, Hash, PartialEq, Eq, CopyGetters)] pub struct Gadget { + /// The id of the gadget. + /// + /// Note that gadgets do not have true ids and the id is generated "through a combination of + /// gadget parameters". #[get_copy = "pub"] id: u16, name: String, } impl Gadget { + /// The name of the gadget. pub fn name(&self) -> &str { &self.name } @@ -112,12 +163,14 @@ impl Gadget { /// friendly characters like Mesmer's clones and illusions, Necromancer minions, and so on. #[derive(Debug, Clone, Hash, PartialEq, Eq, CopyGetters)] pub struct Character { + /// The id of the character. #[get_copy = "pub"] id: u16, name: String, } impl Character { + /// The name of the character. pub fn name(&self) -> &str { &self.name } @@ -144,8 +197,17 @@ impl Character { /// ``` #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum AgentKind { + /// The agent is a player. + /// + /// The player-specific data is in the included [`Player`][Player] struct. Player(Player), + /// The agent is a gadget. + /// + /// The gadget-specific data is in the included [`Gadget`][Gadget] struct. Gadget(Gadget), + /// The agent is a character. + /// + /// The character-specific data is in the included [`Character`][Character] struct. Character(Character), } @@ -753,6 +815,17 @@ impl Log { } } +/// Main function to turn a low-level [`Evtc`][raw::Evtc] to a high-level [`Log`][Log]. +/// +/// This function takes an [`Evtc`][raw::Evtc] and does the required type conversions and +/// pre-processing to get a high-level [`Log`][Log]. This pre-processing includes +/// +/// * Setting the correct aware times for the agents +/// * Setting the master agents for each agent +/// * Converting all events +/// +/// Note that the structures are quite different, so this function does not consume the given +/// [`Evtc`][raw::Evtc]. pub fn process(data: &raw::Evtc) -> Result<Log, EvtcError> { // Prepare "augmented" agents let mut agents = setup_agents(data)?; diff --git a/src/raw/mod.rs b/src/raw/mod.rs index 77a8571..0b17933 100644 --- a/src/raw/mod.rs +++ b/src/raw/mod.rs @@ -33,6 +33,15 @@ pub fn parse_zip<R: Read + Seek>(input: R) -> ParseResult<Evtc> { /// at the first nul byte instead of raising an error. /// /// If the slice does not end with a nul byte, this function returns `None`. +/// +/// ``` +/// # use evtclib::raw::cstr_up_to_nul; +/// # fn doctest() -> Option<()> { +/// assert_eq!(cstr_up_to_nul(b"foo\0bar\0")?.to_bytes(), b"foo"); +/// # Some(()) +/// # } +/// # doctest().unwrap(); +/// ``` pub fn cstr_up_to_nul(bytes: &[u8]) -> Option<&CStr> { let index = bytes.iter().position(|c| *c == 0)?; CStr::from_bytes_with_nul(&bytes[..index + 1]).ok() diff --git a/src/raw/parser.rs b/src/raw/parser.rs index b7d6aad..eaf8e6b 100644 --- a/src/raw/parser.rs +++ b/src/raw/parser.rs @@ -62,6 +62,11 @@ //! buffered: cargo run --release 0.22s user 0.04s system 94% cpu 0.275 total //! raw file: cargo run --release 0.79s user 1.47s system 98% cpu 2.279 total //! ``` +//! +//! # Resources +//! +//! * [evtc readme](https://www.deltaconnected.com/arcdps/evtc/README.txt) +//! * [C++ output code](https://www.deltaconnected.com/arcdps/evtc/writeencounter.cpp) use byteorder::{LittleEndian, ReadBytesExt, LE}; use num_traits::FromPrimitive; @@ -84,9 +89,12 @@ pub struct Header { } /// A completely parsed (raw) EVTC file. +/// +/// Note that this struct does not yet do any preprocessing of the events. It is simply a +/// representation of the input file as a structured object. #[derive(Clone, Debug)] pub struct Evtc { - /// The file header values + /// The file header values. pub header: Header, /// The skill count. pub skill_count: u32, @@ -99,27 +107,44 @@ pub struct Evtc { } /// A partially-parsed EVTC file, containing everything but the events. -/// This can speed up parsing for applications which can work with the header. +/// +/// This can speed up parsing for applications which can work with the header, as the event stream +/// is the largest chunk of data that has to be parsed. #[derive(Clone, Debug)] pub struct PartialEvtc { + /// The file header values. pub header: Header, + /// The skill count. pub skill_count: u32, + /// The actual agents. pub agents: Vec<Agent>, + /// The skills. pub skills: Vec<Skill>, } +/// Any error that can occur during parsing. #[derive(Error, Debug)] pub enum ParseError { + /// The error stems from an underlying input/output error. #[error("IO error: {0}")] Io(#[from] io::Error), + /// The error is caused by invalid UTF-8 data in the file. + /// + /// Names in the evtc are expected to be encoded with UTF-8. #[error("utf8 decoding error: {0}")] Utf8Error(#[from] std::string::FromUtf8Error), + /// A generic error to signal invalid data has been encountered. #[error("invalid data")] InvalidData, + /// The header is malformed. + /// + /// This is the error that you get when you try to parse a non-evtc file. #[error("malformed header")] MalformedHeader, + /// The revision used by the file is not known. #[error("unknown revision: {0}")] UnknownRevision(u8), + /// The given ZIP archive is invalid. #[error("invalid archive: {0}")] InvalidZip(#[from] zip::result::ZipError), } diff --git a/src/raw/types.rs b/src/raw/types.rs index 6d72892..2e1958c 100644 --- a/src/raw/types.rs +++ b/src/raw/types.rs @@ -1,3 +1,6 @@ +//! Raw evtc structs. +//! +//! This module contains the translated definitions from arcdps's C structs. use num_derive::FromPrimitive; use std::{self, fmt}; |