aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lib.rs123
-rw-r--r--src/raw/mod.rs42
2 files changed, 109 insertions, 56 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 72b35ef..05f9f2d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -40,7 +40,7 @@ pub enum EvtcError {
#[error("invalid elite specialization id: {0}")]
InvalidEliteSpec(u32),
#[error("utf8 decoding error: {0}")]
- Utf8Error(#[from] std::string::FromUtf8Error),
+ Utf8Error(#[from] std::str::Utf8Error),
}
/// Player-specific agent data.
@@ -113,6 +113,71 @@ pub enum AgentKind {
}
impl AgentKind {
+ fn from_raw_character(raw_agent: &raw::Agent) -> Result<Character, EvtcError> {
+ assert!(raw_agent.is_character());
+ let name = raw::cstr_up_to_nul(&raw_agent.name).ok_or(EvtcError::InvalidData)?;
+ Ok(Character {
+ id: raw_agent.prof as u16,
+ name: name.to_str()?.to_owned(),
+ })
+ }
+
+ fn from_raw_gadget(raw_agent: &raw::Agent) -> Result<Gadget, EvtcError> {
+ assert!(raw_agent.is_gadget());
+ let name = raw::cstr_up_to_nul(&raw_agent.name).ok_or(EvtcError::InvalidData)?;
+ Ok(Gadget {
+ id: raw_agent.prof as u16,
+ name: name.to_str()?.to_owned(),
+ })
+ }
+
+ fn from_raw_player(raw_agent: &raw::Agent) -> Result<Player, EvtcError> {
+ assert!(raw_agent.is_player());
+ let character_name = raw::cstr_up_to_nul(&raw_agent.name)
+ .ok_or(EvtcError::InvalidData)?
+ .to_str()?;
+ let account_name = raw::cstr_up_to_nul(&raw_agent.name[character_name.len() + 1..])
+ .ok_or(EvtcError::InvalidData)?
+ .to_str()?;
+ let subgroup = raw_agent.name[character_name.len() + account_name.len() + 2] - b'0';
+ let elite = if raw_agent.is_elite == 0 {
+ None
+ } else {
+ Some(
+ EliteSpec::from_u32(raw_agent.is_elite)
+ .ok_or(EvtcError::InvalidEliteSpec(raw_agent.is_elite))?,
+ )
+ };
+ Ok(Player {
+ profession: Profession::from_u32(raw_agent.prof)
+ .ok_or(EvtcError::InvalidProfession(raw_agent.prof))?,
+ elite,
+ character_name: character_name.to_owned(),
+ account_name: account_name.to_owned(),
+ subgroup,
+ })
+ }
+
+ /// Extract the correct `AgentKind` from the given [raw agent][raw::Agent].
+ ///
+ /// This automatically discerns between player, gadget and characters.
+ ///
+ /// Note that in most cases, you probably want to use [`Agent::from_raw`][Agent::from_raw] or
+ /// even [`process`][process] instead of this function.
+ pub fn from_raw(raw_agent: &raw::Agent) -> Result<AgentKind, EvtcError> {
+ if raw_agent.is_character() {
+ Ok(AgentKind::Character(AgentKind::from_raw_character(
+ raw_agent,
+ )?))
+ } else if raw_agent.is_gadget() {
+ Ok(AgentKind::Gadget(AgentKind::from_raw_gadget(raw_agent)?))
+ } else if raw_agent.is_player() {
+ Ok(AgentKind::Player(AgentKind::from_raw_player(raw_agent)?))
+ } else {
+ Err(EvtcError::InvalidData)
+ }
+ }
+
/// Accesses the inner [`Player`][Player] struct, if available.
pub fn as_player(&self) -> Option<&Player> {
if let AgentKind::Player(ref player) = *self {
@@ -268,61 +333,7 @@ pub struct Agent<Kind = ()> {
impl Agent {
/// Parse a raw agent.
pub fn from_raw(raw_agent: &raw::Agent) -> Result<Agent, EvtcError> {
- let kind = if raw_agent.is_character() || raw_agent.is_gadget() {
- let name = String::from_utf8(
- raw_agent
- .name
- .iter()
- .cloned()
- .take_while(|c| *c != 0)
- .collect::<Vec<_>>(),
- )?;
- if raw_agent.is_character() {
- AgentKind::Character(Character {
- id: raw_agent.prof as u16,
- name,
- })
- } else {
- AgentKind::Gadget(Gadget {
- id: raw_agent.prof as u16,
- name,
- })
- }
- } else if raw_agent.is_player() {
- let first = raw_agent
- .name
- .iter()
- .cloned()
- .take_while(|c| *c != 0)
- .collect::<Vec<_>>();
- let second = raw_agent
- .name
- .iter()
- .cloned()
- .skip(first.len() + 1)
- .take_while(|c| *c != 0)
- .collect::<Vec<_>>();
- let third = raw_agent.name[first.len() + second.len() + 2] - b'0';
- let elite = if raw_agent.is_elite == 0 {
- None
- } else {
- Some(
- EliteSpec::from_u32(raw_agent.is_elite)
- .ok_or(EvtcError::InvalidEliteSpec(raw_agent.is_elite))?,
- )
- };
- AgentKind::Player(Player {
- profession: Profession::from_u32(raw_agent.prof)
- .ok_or(EvtcError::InvalidProfession(raw_agent.prof))?,
- elite,
- character_name: String::from_utf8(first)?,
- account_name: String::from_utf8(second)?,
- subgroup: third,
- })
- } else {
- return Err(EvtcError::InvalidData);
- };
-
+ let kind = AgentKind::from_raw(raw_agent)?;
Ok(Agent {
addr: raw_agent.addr,
kind,
diff --git a/src/raw/mod.rs b/src/raw/mod.rs
index 7451b5a..50ad9b4 100644
--- a/src/raw/mod.rs
+++ b/src/raw/mod.rs
@@ -18,6 +18,7 @@ pub mod parser;
pub use self::parser::{parse_file, Evtc, ParseError, ParseResult};
use std::io::{Read, Seek, BufReader};
+use std::ffi::CStr;
/// Parse a complete log that was compressed as a zip file.
pub fn parse_zip<T: Read + Seek>(input: &mut T) -> ParseResult<Evtc> {
@@ -25,3 +26,44 @@ pub fn parse_zip<T: Read + Seek>(input: &mut T) -> ParseResult<Evtc> {
let mut file = BufReader::new(archive.by_index(0)?);
parse_file(&mut file)
}
+
+/// Return a [`CStr`][CStr] up to the first nul byte.
+///
+/// This is different to [`CStr::from_bytes_with_nul`][CStr::from_bytes_with_nul] in that it stops
+/// at the first nul byte instead of raising an error.
+///
+/// If the slice does not end with a nul byte, this function returns `None`.
+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()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_cstr_up_to_nul_terminated() {
+ let bytes = b"foo\0";
+ let cstr = cstr_up_to_nul(bytes).unwrap();
+ assert_eq!(cstr.to_bytes(), b"foo");
+ }
+
+ #[test]
+ fn test_cstr_up_to_nul_multiple() {
+ let bytes = b"foo\0\0\0";
+ let cstr = cstr_up_to_nul(bytes).unwrap();
+ assert_eq!(cstr.to_bytes(), b"foo");
+
+ let bytes = b"foo\0bar\0\0";
+ let cstr = cstr_up_to_nul(bytes).unwrap();
+ assert_eq!(cstr.to_bytes(), b"foo");
+ }
+
+ #[test]
+ fn test_cstr_up_to_nul_unterminated() {
+ let bytes = b"foo";
+ let cstr = cstr_up_to_nul(bytes);
+ assert!(cstr.is_none());
+ }
+}