mocari 0.2.0

A pure Rust Live2D/Cubism-compatible runtime
Documentation
use crate::{Error, Result};

use super::{Endianness, Moc3SectionOffsets};

pub(super) fn read_i32_section(
    bytes: &[u8],
    offsets: &Moc3SectionOffsets,
    slot: usize,
    count: usize,
    endianness: Endianness,
) -> Result<Vec<i32>> {
    read_section(bytes, offsets, slot, count, 4, |bytes, offset| {
        let raw = [
            bytes[offset],
            bytes[offset + 1],
            bytes[offset + 2],
            bytes[offset + 3],
        ];
        match endianness {
            Endianness::Little => i32::from_le_bytes(raw),
            Endianness::Big => i32::from_be_bytes(raw),
        }
    })
}

pub(super) fn read_i32_section_or_default(
    bytes: &[u8],
    offsets: &Moc3SectionOffsets,
    slot: usize,
    count: usize,
    endianness: Endianness,
    default: i32,
) -> Result<Vec<i32>> {
    match offsets.section_offset(slot) {
        Some(0) | None => Ok(vec![default; count]),
        Some(_) => read_i32_section(bytes, offsets, slot, count, endianness),
    }
}

pub(super) fn read_i16_section(
    bytes: &[u8],
    offsets: &Moc3SectionOffsets,
    slot: usize,
    count: usize,
    endianness: Endianness,
) -> Result<Vec<i16>> {
    read_section(bytes, offsets, slot, count, 2, |bytes, offset| {
        let raw = [bytes[offset], bytes[offset + 1]];
        match endianness {
            Endianness::Little => i16::from_le_bytes(raw),
            Endianness::Big => i16::from_be_bytes(raw),
        }
    })
}

pub(super) fn read_u16_section(
    bytes: &[u8],
    offsets: &Moc3SectionOffsets,
    slot: usize,
    count: usize,
    endianness: Endianness,
) -> Result<Vec<u16>> {
    read_section(bytes, offsets, slot, count, 2, |bytes, offset| {
        let raw = [bytes[offset], bytes[offset + 1]];
        match endianness {
            Endianness::Little => u16::from_le_bytes(raw),
            Endianness::Big => u16::from_be_bytes(raw),
        }
    })
}

pub(super) fn read_f32_section(
    bytes: &[u8],
    offsets: &Moc3SectionOffsets,
    slot: usize,
    count: usize,
    endianness: Endianness,
) -> Result<Vec<f32>> {
    read_section(bytes, offsets, slot, count, 4, |bytes, offset| {
        let raw = [
            bytes[offset],
            bytes[offset + 1],
            bytes[offset + 2],
            bytes[offset + 3],
        ];
        match endianness {
            Endianness::Little => f32::from_le_bytes(raw),
            Endianness::Big => f32::from_be_bytes(raw),
        }
    })
}

pub(super) fn read_f32_section_or_default(
    bytes: &[u8],
    offsets: &Moc3SectionOffsets,
    slot: usize,
    count: usize,
    endianness: Endianness,
    default: f32,
) -> Result<Vec<f32>> {
    match offsets.section_offset(slot) {
        Some(0) | None => Ok(vec![default; count]),
        Some(_) => read_f32_section(bytes, offsets, slot, count, endianness),
    }
}

pub(super) fn read_u8_section(
    bytes: &[u8],
    offsets: &Moc3SectionOffsets,
    slot: usize,
    count: usize,
) -> Result<Vec<u8>> {
    read_section(bytes, offsets, slot, count, 1, |bytes, offset| {
        bytes[offset]
    })
}

pub(super) fn read_bool_section(
    bytes: &[u8],
    offsets: &Moc3SectionOffsets,
    slot: usize,
    count: usize,
    endianness: Endianness,
) -> Result<Vec<bool>> {
    read_i32_section(bytes, offsets, slot, count, endianness)
        .map(|values| values.into_iter().map(|value| value == 1).collect())
}

pub(super) fn read_section<T>(
    bytes: &[u8],
    offsets: &Moc3SectionOffsets,
    slot: usize,
    count: usize,
    element_size: usize,
    read: impl Fn(&[u8], usize) -> T,
) -> Result<Vec<T>> {
    if count == 0 {
        return Ok(Vec::new());
    }

    let offset = offsets
        .section_offset(slot)
        .ok_or_else(|| invalid_moc3(format!("section slot {slot} is outside offset table")))?;
    if offset == 0 {
        return Err(invalid_moc3(format!("section slot {slot} has no offset")));
    }

    let offset = usize::try_from(offset)
        .map_err(|_| invalid_moc3(format!("section slot {slot} offset is too large")))?;
    let byte_len = count
        .checked_mul(element_size)
        .ok_or_else(|| invalid_moc3(format!("section slot {slot} size overflows")))?;
    if bytes.len().saturating_sub(offset) < byte_len {
        return Err(invalid_moc3(format!("section slot {slot} is incomplete")));
    }

    let mut values = Vec::with_capacity(count);
    for index in 0..count {
        values.push(read(bytes, offset + index * element_size));
    }

    Ok(values)
}

pub(super) fn to_usize(value: u32, name: &'static str) -> Result<usize> {
    usize::try_from(value).map_err(|_| invalid_moc3(format!("{name} is too large")))
}

pub(super) fn invalid_moc3(message: impl Into<String>) -> Error {
    Error::InvalidMoc3 {
        message: message.into(),
    }
}