use std::collections::HashMap;
use std::io::{Error, ErrorKind, Read, Seek, SeekFrom};
use byteorder::{BigEndian, ReadBytesExt};
#[derive(Debug)]
pub enum Usage {
Pict,
Snd,
Data,
Exec,
}
#[derive(Debug)]
pub struct IndexEntry {
pub usage: Usage,
pub num: u32,
start: u32,
}
#[derive(Debug)]
pub struct ResourceIndex {
pub entries: HashMap<usize, IndexEntry>,
}
#[derive(Debug)]
pub struct ChunkData {
pub id: String,
len: u32,
}
pub enum Chunk {
Unknown{meta: ChunkData, data: Vec<u8>},
ResourceIndex{index: ResourceIndex},
Metadata{info: String},
Frontispiece{num: u32},
Glulx{code: Vec<u8>},
Png{data: Vec<u8>},
Jpeg{data: Vec<u8>},
}
pub struct Blorb<R: Read + Seek> {
pub len: u32,
index: ResourceIndex,
file: R,
}
impl<R: Read + Seek> Blorb<R> {
pub fn from_file(file: R) -> Result<Blorb<R>, Error> {
let mut file = file;
let form = try!(Blorb::load_chunk_data(&mut file));
assert_eq!(&form.id, "FORM");
let id = try!(Blorb::load_4bw(&mut file));
assert_eq!(&id, "IFRS");
let index = if let Chunk::ResourceIndex{index} =
try!(Blorb::load_chunk(&mut file)) {
index
} else {
return Err(Error::new(
ErrorKind::InvalidInput,
"could not locate index"
));
};
Ok(Blorb{
len: form.len,
index: index,
file: file,
})
}
pub fn load_resource(&mut self, num: usize) -> Result<Chunk, Error> {
let entry = match self.index.entries.get(&num) {
Some(entry) => entry,
None => {
return Err(Error::new(
ErrorKind::NotFound,
"resource not found"
))
},
};
try!(self.file.seek(SeekFrom::Start(entry.start as u64)));
Blorb::load_chunk(&mut self.file)
}
fn load_chunk(file: &mut R) -> Result<Chunk, Error> {
let meta = try!(Blorb::load_chunk_data(file));
match meta.id.as_ref() {
"RIdx" => {
let num = try!(file.read_u32::<BigEndian>());
let mut entries = HashMap::with_capacity(num as usize);
for _ in 0..num {
let entry = try!(Blorb::load_index_entry(file));
entries.insert(entry.num as usize, entry);
}
Ok(Chunk::ResourceIndex{index: ResourceIndex{entries:entries}})
},
"GLUL" => {
let mut data = Vec::with_capacity(meta.len as usize);
try!(file.take(meta.len as u64).read_to_end(&mut data));
Ok(Chunk::Glulx{code: data})
},
_ => {
let mut data = Vec::with_capacity(meta.len as usize);
try!(file.take(meta.len as u64).read_to_end(&mut data));
Ok(Chunk::Unknown{meta: meta, data: data})
},
}
}
fn load_index_entry(file: &mut R) -> Result<IndexEntry, Error> {
let usage = try!(Blorb::load_4bw(file));
let num = try!(file.read_u32::<BigEndian>());
let start = try!(file.read_u32::<BigEndian>());
let usage = match usage.as_ref() {
"Pict" => Usage::Pict,
"Snd " => Usage::Snd,
"Data" => Usage::Data,
"Exec" => Usage::Exec,
_ => return Err(Error::new(
ErrorKind::InvalidInput,
"could not identify index entry usage"
)),
};
Ok(IndexEntry{usage: usage, num: num, start: start})
}
fn load_chunk_data(file: &mut R) -> Result<ChunkData, Error> {
let id = try!(Blorb::load_4bw(file));
let len = try!(file.read_u32::<BigEndian>());
Ok(ChunkData{id: id, len: len})
}
fn load_4bw(file: &mut R) -> Result<String, Error> {
let mut id = String::with_capacity(0x4);
try!(file.take(4).read_to_string(&mut id));
Ok(id)
}
}