use std::io::{self, BufRead, BufReader, Read};
use crate::{GMAFile, GmaError, HEADER, VERSION};
pub fn read<R: Read>(reader: R) -> Result<Vec<GMAFile>, GmaError> {
let mut r = BufReader::new(reader);
let mut hdr = [0u8; 4];
r.read_exact(&mut hdr)?;
if &hdr != HEADER {
return Err(GmaError::InvalidHeader(hdr));
}
let v = read_i8(&mut r)?;
if v != VERSION {
return Err(GmaError::InvalidVersion(v));
}
discard_exact(&mut r, 8)?;
discard_exact(&mut r, 8)?;
if v > 1 {
loop {
let s = read_c_string(&mut r)?;
if s.is_empty() {
break;
}
}
} else {
discard_exact(&mut r, 1)?;
}
read_c_string(&mut r)?; read_c_string(&mut r)?; read_c_string(&mut r)?;
discard_exact(&mut r, 4)?;
let mut entries_meta: Vec<(String, i64)> = Vec::with_capacity(10);
loop {
let idx = read_u32(&mut r)?;
if idx == 0 {
break;
}
let name = read_c_string(&mut r)?;
let size = read_i64(&mut r)?;
if size < 0 {
return Err(GmaError::SizeOutOfRange(size));
}
discard_exact(&mut r, 4)?;
entries_meta.push((name, size));
}
let mut entries = Vec::with_capacity(entries_meta.len());
for (name, size) in entries_meta {
let size_usize = usize::try_from(size).map_err(|_| GmaError::SizeOutOfRange(size))?;
let mut content = vec![0u8; size_usize];
r.read_exact(&mut content)?;
entries.push(GMAFile {
name,
size,
content,
});
}
let _ = read_u32(&mut r);
Ok(entries)
}
fn discard_exact<R: Read>(r: &mut R, n: u64) -> Result<(), GmaError> {
let copied = io::copy(&mut r.take(n), &mut io::sink())?;
if copied == n {
Ok(())
} else {
Err(io::Error::from(io::ErrorKind::UnexpectedEof).into())
}
}
fn read_i8<R: Read>(r: &mut R) -> Result<i8, GmaError> {
let mut b = [0u8; 1];
r.read_exact(&mut b)?;
Ok(i8::from_le_bytes(b))
}
fn read_i64<R: Read>(r: &mut R) -> Result<i64, GmaError> {
let mut b = [0u8; 8];
r.read_exact(&mut b)?;
Ok(i64::from_le_bytes(b))
}
fn read_u32<R: Read>(r: &mut R) -> Result<u32, GmaError> {
let mut b = [0u8; 4];
r.read_exact(&mut b)?;
Ok(u32::from_le_bytes(b))
}
fn read_c_string<R: BufRead>(r: &mut R) -> Result<String, GmaError> {
let mut buf = Vec::with_capacity(32);
let n = r.read_until(0, &mut buf)?; if n == 0 || *buf.last().unwrap_or(&1) != 0 {
return Err(GmaError::MissingNullTerminator);
}
buf.pop(); Ok(String::from_utf8_lossy(&buf).into_owned())
}