halbu 0.3.0

Diablo II save file parsing library.
Documentation
use std::ops::Range;

use crate::ParseHardError;

pub(crate) fn read_range<'a>(
    bytes: &'a [u8],
    range: Range<usize>,
    field_name: &str,
) -> Result<&'a [u8], ParseHardError> {
    let start = range.start;
    let end = range.end;
    bytes.get(start..end).ok_or_else(|| ParseHardError {
        message: format!(
            "Character field {field_name} is out of bounds: requested {}..{}, available length {}.",
            start,
            end,
            bytes.len()
        ),
    })
}

pub(crate) fn write_range<'a>(
    bytes: &'a mut [u8],
    range: Range<usize>,
    field_name: &str,
) -> Result<&'a mut [u8], ParseHardError> {
    let start = range.start;
    let end = range.end;
    let bytes_length = bytes.len();
    bytes.get_mut(start..end).ok_or_else(|| ParseHardError {
        message: format!(
            "Character field {field_name} is out of bounds for write: requested {start}..{end}, buffer length {bytes_length}."
        ),
    })
}

pub(crate) fn read_u8_at(
    bytes: &[u8],
    offset: usize,
    field_name: &str,
) -> Result<u8, ParseHardError> {
    bytes.get(offset).copied().ok_or_else(|| ParseHardError {
        message: format!(
            "Character field {field_name} is out of bounds: requested byte {offset}, available length {}.",
            bytes.len()
        ),
    })
}

pub(crate) fn write_u8_at(
    bytes: &mut [u8],
    offset: usize,
    value: u8,
    field_name: &str,
) -> Result<(), ParseHardError> {
    let bytes_length = bytes.len();
    let target_byte = bytes.get_mut(offset).ok_or_else(|| ParseHardError {
        message: format!(
            "Character field {field_name} is out of bounds for write: requested byte {offset}, buffer length {}.",
            bytes_length
        ),
    })?;
    *target_byte = value;
    Ok(())
}

pub(crate) fn read_u32_le_at(
    bytes: &[u8],
    offset: usize,
    field_name: &str,
) -> Result<u32, ParseHardError> {
    let slice = read_range(bytes, offset..(offset + 4), field_name)?;
    let parsed_bytes: [u8; 4] = slice.try_into().map_err(|_| ParseHardError {
        message: format!("Failed to read 4-byte value for {field_name}."),
    })?;
    Ok(u32::from_le_bytes(parsed_bytes))
}

pub(crate) fn write_u32_le_at(
    bytes: &mut [u8],
    offset: usize,
    value: u32,
    field_name: &str,
) -> Result<(), ParseHardError> {
    let target_slice = write_range(bytes, offset..(offset + 4), field_name)?;
    target_slice.copy_from_slice(&value.to_le_bytes());
    Ok(())
}

pub(crate) fn read_fixed_array<const ARRAY_LENGTH: usize>(
    bytes: &[u8],
    range: Range<usize>,
    field_name: &str,
) -> Result<[u8; ARRAY_LENGTH], ParseHardError> {
    let source_slice = read_range(bytes, range, field_name)?;
    source_slice.try_into().map_err(|_| ParseHardError {
        message: format!(
            "Character field {field_name} has invalid length: expected {ARRAY_LENGTH}, found {}.",
            source_slice.len()
        ),
    })
}

pub(crate) fn write_exact_bytes(
    bytes: &mut [u8],
    range: Range<usize>,
    source_bytes: &[u8],
    field_name: &str,
) -> Result<(), ParseHardError> {
    let start = range.start;
    let end = range.end;
    let target_slice = write_range(bytes, start..end, field_name)?;
    if source_bytes.len() != target_slice.len() {
        return Err(ParseHardError {
            message: format!(
                "Character field {field_name} length mismatch: target {} bytes for range {}..{}, source {} bytes.",
                target_slice.len(),
                start,
                end,
                source_bytes.len()
            ),
        });
    }
    target_slice.copy_from_slice(source_bytes);
    Ok(())
}

pub(crate) fn read_name_string(
    bytes: &[u8],
    range: Range<usize>,
    field_name: &str,
) -> Result<String, ParseHardError> {
    let raw_name_bytes = read_range(bytes, range, field_name)?;
    let decoded_name =
        String::from_utf8_lossy(raw_name_bytes).trim_matches(char::from(0)).to_string();
    Ok(decoded_name)
}

pub(crate) fn write_name_string(
    bytes: &mut [u8],
    range: Range<usize>,
    value: &str,
    field_name: &str,
) -> Result<(), ParseHardError> {
    let target_slice = write_range(bytes, range, field_name)?;
    target_slice.fill(0x00);
    let source_bytes = value.as_bytes();
    let copied_length = usize::min(source_bytes.len(), target_slice.len());
    target_slice[..copied_length].copy_from_slice(&source_bytes[..copied_length]);
    Ok(())
}