1use std::collections::HashMap;
2use std::io::{Error, ErrorKind, Read, Seek, SeekFrom};
3
4use byteorder::{BigEndian, ReadBytesExt};
5
6
7#[derive(Debug)]
9pub enum Usage {
10 Pict,
13
14 Snd,
17
18 Data,
21
22 Exec,
25}
26
27
28#[derive(Debug)]
31pub struct IndexEntry {
32 pub usage: Usage,
33 pub num: u32,
34 start: u32,
35}
36
37
38#[derive(Debug)]
40pub struct ResourceIndex {
41 pub entries: HashMap<usize, IndexEntry>,
42}
43
44
45#[derive(Debug)]
48pub struct ChunkData {
49 pub id: String,
50 len: u32,
51}
52
53
54pub enum Chunk {
56
57 Unknown{meta: ChunkData, data: Vec<u8>},
61
62 ResourceIndex{index: ResourceIndex},
66
67 Metadata{info: String},
70
71 Frontispiece{num: u32},
75
76 Glulx{code: Vec<u8>},
80
81 Png{data: Vec<u8>},
85
86 Jpeg{data: Vec<u8>},
90}
91
92
93pub struct Blorb<R: Read + Seek> {
97 pub len: u32,
99 index: ResourceIndex,
100 file: R,
101}
102
103
104impl<R: Read + Seek> Blorb<R> {
105
106 pub fn from_file(file: R) -> Result<Blorb<R>, Error> {
110 let mut file = file;
111
112 let form = try!(Blorb::load_chunk_data(&mut file));
113 assert_eq!(&form.id, "FORM");
114
115 let id = try!(Blorb::load_4bw(&mut file));
117 assert_eq!(&id, "IFRS");
118
119 let index = if let Chunk::ResourceIndex{index} =
120 try!(Blorb::load_chunk(&mut file)) {
121 index
122 } else {
123 return Err(Error::new(
124 ErrorKind::InvalidInput,
125 "could not locate index"
126 ));
127 };
128
129 Ok(Blorb{
130 len: form.len,
131 index: index,
132 file: file,
133 })
134 }
135
136 pub fn load_resource(&mut self, num: usize) -> Result<Chunk, Error> {
138 let entry = match self.index.entries.get(&num) {
139 Some(entry) => entry,
140 None => {
141 return Err(Error::new(
142 ErrorKind::NotFound,
143 "resource not found"
144 ))
145 },
146 };
147 try!(self.file.seek(SeekFrom::Start(entry.start as u64)));
148
149 Blorb::load_chunk(&mut self.file)
150 }
151
152 fn load_chunk(file: &mut R) -> Result<Chunk, Error> {
154 let meta = try!(Blorb::load_chunk_data(file));
155
156 match meta.id.as_ref() {
157 "RIdx" => {
158 let num = try!(file.read_u32::<BigEndian>());
159 let mut entries = HashMap::with_capacity(num as usize);
160 for _ in 0..num {
161 let entry = try!(Blorb::load_index_entry(file));
162 entries.insert(entry.num as usize, entry);
163 }
164
165 Ok(Chunk::ResourceIndex{index: ResourceIndex{entries:entries}})
166 },
167 "GLUL" => {
168 let mut data = Vec::with_capacity(meta.len as usize);
169 try!(file.take(meta.len as u64).read_to_end(&mut data));
170 Ok(Chunk::Glulx{code: data})
171 },
172 _ => {
173 let mut data = Vec::with_capacity(meta.len as usize);
174 try!(file.take(meta.len as u64).read_to_end(&mut data));
175 Ok(Chunk::Unknown{meta: meta, data: data})
176 },
177 }
178 }
179
180 fn load_index_entry(file: &mut R) -> Result<IndexEntry, Error> {
182 let usage = try!(Blorb::load_4bw(file));
183 let num = try!(file.read_u32::<BigEndian>());
184 let start = try!(file.read_u32::<BigEndian>());
185
186 let usage = match usage.as_ref() {
187 "Pict" => Usage::Pict,
188 "Snd " => Usage::Snd,
189 "Data" => Usage::Data,
190 "Exec" => Usage::Exec,
191 _ => return Err(Error::new(
192 ErrorKind::InvalidInput,
193 "could not identify index entry usage"
194 )),
195 };
196
197 Ok(IndexEntry{usage: usage, num: num, start: start})
198 }
199
200 fn load_chunk_data(file: &mut R) -> Result<ChunkData, Error> {
203 let id = try!(Blorb::load_4bw(file));
204 let len = try!(file.read_u32::<BigEndian>());
205 Ok(ChunkData{id: id, len: len})
206 }
207
208 fn load_4bw(file: &mut R) -> Result<String, Error> {
210 let mut id = String::with_capacity(0x4);
211 try!(file.take(4).read_to_string(&mut id));
212 Ok(id)
213 }
214}