w3grs 0.1.0

A Rust port of w3gjs for parsing Warcraft III replay files.
Documentation
use crate::error::{Error, Result};

#[derive(Debug, Clone)]
pub struct StatefulBufferParser<'a> {
    buffer: &'a [u8],
    offset: usize,
}

impl<'a> StatefulBufferParser<'a> {
    pub fn new(buffer: &'a [u8]) -> Self {
        Self { buffer, offset: 0 }
    }

    pub fn reset(&mut self, buffer: &'a [u8]) {
        self.buffer = buffer;
        self.offset = 0;
    }

    pub fn buffer(&self) -> &'a [u8] {
        self.buffer
    }

    pub fn offset(&self) -> usize {
        self.offset
    }

    pub fn set_offset(&mut self, offset: usize) {
        self.offset = offset;
    }

    pub fn skip(&mut self, byte_count: isize) -> Result<()> {
        if byte_count < 0 {
            let amount = byte_count.unsigned_abs();
            self.offset = self
                .offset
                .checked_sub(amount)
                .ok_or(Error::UnexpectedEof {
                    offset: self.offset,
                    needed: amount,
                })?;
            return Ok(());
        }

        self.ensure(byte_count as usize)?;
        self.offset += byte_count as usize;
        Ok(())
    }

    pub fn remaining(&self) -> usize {
        self.buffer.len().saturating_sub(self.offset)
    }

    pub fn is_done(&self) -> bool {
        self.offset >= self.buffer.len()
    }

    pub fn read_u8(&mut self) -> Result<u8> {
        self.ensure(1)?;
        let value = self.buffer[self.offset];
        self.offset += 1;
        Ok(value)
    }

    pub fn peek_u8(&self) -> Result<u8> {
        self.ensure(1)?;
        Ok(self.buffer[self.offset])
    }

    pub fn read_u16_le(&mut self) -> Result<u16> {
        let bytes = self.read_array::<2>()?;
        Ok(u16::from_le_bytes(bytes))
    }

    pub fn read_u32_le(&mut self) -> Result<u32> {
        let bytes = self.read_array::<4>()?;
        Ok(u32::from_le_bytes(bytes))
    }

    pub fn read_f32_le(&mut self) -> Result<f32> {
        let bytes = self.read_array::<4>()?;
        Ok(f32::from_le_bytes(bytes))
    }

    pub fn read_bytes(&mut self, length: usize) -> Result<&'a [u8]> {
        self.ensure(length)?;
        let start = self.offset;
        self.offset += length;
        Ok(&self.buffer[start..start + length])
    }

    pub fn read_string(&mut self, length: usize) -> Result<String> {
        Ok(String::from_utf8_lossy(self.read_bytes(length)?).to_string())
    }

    pub fn read_hex_string(&mut self, length: usize) -> Result<String> {
        Ok(to_hex(self.read_bytes(length)?))
    }

    pub fn read_zero_term_string(&mut self) -> Result<String> {
        let start = self.offset;
        while self.offset < self.buffer.len() && self.buffer[self.offset] != 0 {
            self.offset += 1;
        }
        self.ensure(1)?;
        let end = self.offset;
        self.offset += 1;
        Ok(String::from_utf8_lossy(&self.buffer[start..end]).to_string())
    }

    fn read_array<const N: usize>(&mut self) -> Result<[u8; N]> {
        self.ensure(N)?;
        let mut bytes = [0; N];
        bytes.copy_from_slice(&self.buffer[self.offset..self.offset + N]);
        self.offset += N;
        Ok(bytes)
    }

    fn ensure(&self, needed: usize) -> Result<()> {
        if self.offset + needed > self.buffer.len() {
            return Err(Error::UnexpectedEof {
                offset: self.offset,
                needed,
            });
        }
        Ok(())
    }
}

pub fn to_hex(bytes: &[u8]) -> String {
    const HEX: &[u8; 16] = b"0123456789abcdef";
    let mut out = String::with_capacity(bytes.len() * 2);
    for byte in bytes {
        out.push(HEX[(byte >> 4) as usize] as char);
        out.push(HEX[(byte & 0x0f) as usize] as char);
    }
    out
}