ignis 0.1.0

A library for editing various 3DSFE formats.
Documentation
use super::BinArchive;
use anyhow::{anyhow, Result};
use byteorder::{LittleEndian, ReadBytesExt};
use encoding_rs::SHIFT_JIS;
use std::collections::HashMap;
use std::io::{Cursor, Read, Seek, SeekFrom};

struct BinArchiveHeader {
    file_size: u32,
    data_size: u32,
    normal_pointer_count: u32,
    label_count: u32,
}

fn read_header(bytes: &[u8]) -> Result<BinArchiveHeader> {
    if bytes.len() < 0x20 {
        return Err(anyhow!(
            "File is not large enough to contain a valid bin archive header"
        ));
    }

    let mut reader = Cursor::new(bytes);
    let file_size = reader.read_u32::<LittleEndian>()?;
    let data_size = reader.read_u32::<LittleEndian>()?;
    let normal_pointer_count = reader.read_u32::<LittleEndian>()?;
    let label_count = reader.read_u32::<LittleEndian>()?;
    Ok(BinArchiveHeader {
        file_size,
        data_size,
        normal_pointer_count,
        label_count,
    })
}

fn validate_header(header: &BinArchiveHeader, raw_archive: &[u8]) -> Result<()> {
    let min_archive_size =
        (header.data_size + header.normal_pointer_count * 4 + header.label_count * 8 + 0x20)
            as usize;
    if header.file_size as usize != raw_archive.len() {
        return Err(anyhow!(
            "The actual file and the archive header disagree on the archive length"
        ));
    }
    if min_archive_size > raw_archive.len() {
        return Err(anyhow!(
            "File is not large enough to support size indicated in the archive header"
        ));
    }
    Ok(())
}

fn read_string(reader: &mut Cursor<&[u8]>, addr: u64) -> Result<String> {
    let end_addr = reader.position();
    reader.seek(SeekFrom::Start(addr))?;
    let mut buffer: Vec<u8> = Vec::new();
    let mut next = reader.read_u8()?;
    while next != 0 {
        buffer.push(next);
        next = reader.read_u8()?;
    }

    let (result, _enc, errors) = SHIFT_JIS.decode(buffer.as_slice());
    if errors {
        return Err(anyhow!("Unable to decode shift-jis string."));
    }
    reader.seek(SeekFrom::Start(end_addr))?;
    Ok(result.into())
}

fn deserialize_normal_pointers(
    archive: &mut BinArchive,
    header: &BinArchiveHeader,
    reader: &mut Cursor<&[u8]>,
) -> Result<()> {
    for i in 0..header.normal_pointer_count {
        let ptr = reader.read_u32::<LittleEndian>()? as usize;
        let end_addr = reader.position();
        if ptr >= archive.size() {
            return Err(anyhow!("Bad pointer number {} to address 0x{:x}", i, ptr));
        }

        reader.seek(SeekFrom::Start((ptr + 0x20) as u64))?;
        let data_ptr = reader.read_u32::<LittleEndian>()?;
        if data_ptr >= header.data_size {
            let text = read_string(reader, (data_ptr + 0x20) as u64)?;
            archive.put_text(ptr, Some(&text))?;
        } else {
            archive.put_pointer(ptr, data_ptr as usize)?;
        }
        reader.seek(SeekFrom::Start(end_addr))?;
    }

    Ok(())
}

fn deserialize_labels(
    archive: &mut BinArchive,
    header: &BinArchiveHeader,
    reader: &mut Cursor<&[u8]>,
) -> Result<()> {
    let text_start =
        header.data_size + header.normal_pointer_count * 4 + header.label_count * 8 + 0x20;
    for i in 0..header.label_count {
        let ptr = reader.read_u32::<LittleEndian>()? as usize;
        if ptr >= archive.size() {
            return Err(anyhow!("Bad pointer number {} to address 0x{:x}", i, ptr));
        }

        let text_ptr = text_start + reader.read_u32::<LittleEndian>()?;
        let text = read_string(reader, text_ptr as u64)?;
        archive.put_label(ptr, &text)?;
    }

    Ok(())
}

pub fn from_bytes(raw_archive: &[u8]) -> Result<BinArchive> {
    let header = read_header(raw_archive)?;
    validate_header(&header, raw_archive)?;

    let mut reader = Cursor::new(raw_archive);
    reader.seek(SeekFrom::Start(0x20))?;

    let mut data = Vec::new();
    data.resize(header.data_size as usize, 0);
    reader.read_exact(data.as_mut_slice())?;

    let mut archive = BinArchive {
        data,
        text_pointers: HashMap::new(),
        labels: HashMap::new(),
        internal_pointers: HashMap::new(),
    };
    deserialize_normal_pointers(&mut archive, &header, &mut reader)?;
    deserialize_labels(&mut archive, &header, &mut reader)?;
    Ok(archive)
}