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)
}