ignis 0.1.0

A library for editing various 3DSFE formats.
Documentation
use anyhow::Result;
use byteorder::{LittleEndian, WriteBytesExt};
use encoding_rs::SHIFT_JIS;
use linked_hash_map::LinkedHashMap;
use std::collections::HashMap;
use std::io::{Cursor, Seek, SeekFrom, Write};

pub struct SerializationState {
    raw_text: Vec<u8>,
    written_text_offsets: HashMap<String, usize>,
    raw_pointers: Vec<u32>,
    raw_labels: Vec<u32>,
}

impl SerializationState {
    pub fn new() -> Self {
        SerializationState {
            raw_text: Vec::new(),
            written_text_offsets: HashMap::new(),
            raw_pointers: Vec::new(),
            raw_labels: Vec::new(),
        }
    }

    pub fn assemble(&self, data: &Vec<u8>) -> Result<Vec<u8>> {
        let mut output: Vec<u8> = Vec::new();
        let file_size = data.len()
            + self.raw_pointers.len() * 4
            + (self.raw_labels.len() / 2) * 8
            + self.raw_text.len()
            + 0x20;
        output.write_u32::<LittleEndian>(file_size as u32)?;
        output.write_u32::<LittleEndian>(data.len() as u32)?;
        output.write_u32::<LittleEndian>(self.raw_pointers.len() as u32)?;
        output.write_u32::<LittleEndian>((self.raw_labels.len() / 2) as u32)?;
        output.write_u64::<LittleEndian>(0)?;
        output.write_u64::<LittleEndian>(0)?;
        output.write_all(data)?;
        for ptr in &self.raw_pointers {
            output.write_u32::<LittleEndian>(*ptr)?;
        }
        for ptr in &self.raw_labels {
            output.write_u32::<LittleEndian>(*ptr)?;
        }
        output.write_all(&self.raw_text)?;
        Ok(output)
    }

    pub fn add_text(&mut self, text: &str) -> u32 {
        if self.written_text_offsets.contains_key(text) {
            return *self.written_text_offsets.get(text).unwrap() as u32;
        }

        let offset = self.raw_text.len();
        let (result, _enc, _errors) = SHIFT_JIS.encode(text);
        let bytes = result.to_vec();
        for byte in bytes {
            self.raw_text.push(byte);
        }
        self.raw_text.push(0);
        self.written_text_offsets.insert(text.into(), offset);
        offset as u32
    }

    pub fn load_internal_pointers(
        &mut self,
        data: &mut Vec<u8>,
        internal_pointers: &HashMap<usize, usize>,
    ) -> Result<()> {
        let mut cursor = Cursor::new(data);
        let mut pointers: Vec<(&usize, &usize)> = internal_pointers.iter().collect();
        pointers.sort_by(|a, b| a.0.cmp(b.0));
        for pointer_pair in pointers {
            cursor.seek(SeekFrom::Start(*pointer_pair.0 as u64))?;
            cursor.write_u32::<LittleEndian>(*pointer_pair.1 as u32)?;
            self.raw_pointers.push(*pointer_pair.0 as u32);
        }
        Ok(())
    }

    pub fn load_labels(&mut self, labels: &HashMap<usize, Vec<String>>) {
        let mut pointers: Vec<(&usize, &Vec<String>)> = labels.iter().collect();
        pointers.sort_by(|a, b| a.0.cmp(b.0));
        for pointer_pair in pointers {
            for string in pointer_pair.1 {
                let text_offset = self.add_text(string);
                self.raw_labels.push(*pointer_pair.0 as u32);
                self.raw_labels.push(text_offset);
            }
        }
    }

    pub fn load_text_pointers(
        &mut self,
        data: &mut Vec<u8>,
        text_pointers: &HashMap<usize, String>,
        label_start: u32,
    ) -> Result<()> {
        let mut cursor = Cursor::new(data);
        let mut pointers: Vec<(&usize, &String)> = text_pointers.iter().collect();
        pointers.sort_by(|a, b| a.0.cmp(b.0));
        let mut ptr_data_pairs: LinkedHashMap<usize, Vec<u32>> = LinkedHashMap::new();
        for pointer_pair in pointers {
            let text_offset = self.add_text(pointer_pair.1);
            let text_address = label_start + text_offset;
            if !ptr_data_pairs.contains_key(&(text_address as usize)) {
                ptr_data_pairs.insert(text_address as usize, Vec::new());
            }
            let bucket = ptr_data_pairs.get_mut(&(text_address as usize)).unwrap();
            bucket.push(*pointer_pair.0 as u32);

            cursor.seek(SeekFrom::Start(*pointer_pair.0 as u64))?;
            cursor.write_u32::<LittleEndian>(text_address as u32)?;
        }

        for mut ptr_data_pair in ptr_data_pairs {
            ptr_data_pair.1.sort();
            for ptr in ptr_data_pair.1 {
                self.raw_pointers.push(ptr);
            }
        }
        Ok(())
    }
}