blorb/
blorb.rs

1use std::collections::HashMap;
2use std::io::{Error, ErrorKind, Read, Seek, SeekFrom};
3
4use byteorder::{BigEndian, ReadBytesExt};
5
6
7/// The usage information for an `IndexEntry`.
8#[derive(Debug)]
9pub enum Usage {
10    /// Identifier: 'Pict'.
11    /// Indicates the resource is an image.
12    Pict,
13
14    /// Identifier: 'Snd '.
15    /// Indicates the resource is a sound.
16    Snd,
17
18    /// Indentifier: 'Data'.
19    /// Indicates the resource is some data.
20    Data,
21
22    /// Identifier: 'Exec'.
23    /// Indicates the resource is an executable.
24    Exec,
25}
26
27
28/// Contains the usage information for an entry, the resource number of
29/// the entry, and where in the blob the entry starts.
30#[derive(Debug)]
31pub struct IndexEntry {
32    pub usage: Usage,
33    pub num: u32,
34    start: u32,
35}
36
37
38/// Container for list of resource index entries.
39#[derive(Debug)]
40pub struct ResourceIndex {
41    pub entries: HashMap<usize, IndexEntry>,
42}
43
44
45/// Container for chunk metadata. Used for identifying a chunk without
46/// loading the full chunk into memory.
47#[derive(Debug)]
48pub struct ChunkData {
49    pub id: String,
50    len: u32,
51}
52
53
54/// Representation for loaded blorb chunks
55pub enum Chunk {
56
57    /// Chunk returned when the loaded chunk type is unable to be
58    /// identified. Per Specification, the machine must ignore unknown
59    /// chunks, and this type will be used to do so when necessary.
60    Unknown{meta: ChunkData, data: Vec<u8>},
61
62    /// Identifier: 'RIdx'.
63    /// Contains a resource index for the IF.
64    /// This chunk is mandatory and must be the first chunk in the form.
65    ResourceIndex{index: ResourceIndex},
66
67    /// Identifier: 'IFmd'.
68    /// Contains xml metadata content for the IF.
69    Metadata{info: String},
70
71    /// Identifier: 'Fspec'.
72    /// Contains a reference to a frontispiece image.
73    /// This chunk is optional.
74    Frontispiece{num: u32},
75
76    /// Identifier: 'GLUL'.
77    /// Contains Glulx executable.
78    /// This is a executable resource chunk.
79    Glulx{code: Vec<u8>},
80
81    /// Identifier: 'PNG '.
82    /// Contains a PNG image.
83    /// This is a picture resource chunk.
84    Png{data: Vec<u8>},
85
86    /// Identifier: 'JPEG'.
87    /// Contains a JPEG image.
88    /// This is a picture resource chunk.
89    Jpeg{data: Vec<u8>},
90}
91
92
93/// Access point for lazy loading blorb contents. This struct contains
94/// the methods to load resources from the provided blorb. This does not
95/// cache the results.
96pub struct Blorb<R: Read + Seek> {
97    /// The length of the IFRS chunk
98    pub len: u32,
99    index: ResourceIndex,
100    file: R,
101}
102
103
104impl<R: Read + Seek> Blorb<R> {
105
106    /// Creates a new Blorb from a file. The code goes through the
107    /// given game file, validates the file type, and extracts the
108    /// basic game objects for the blorb.
109    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        // Check that the form type is IFRS
116        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    /// loads a resource using the given index entry.
137    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    /// Load a `Chunk` from the file.
153    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    /// Load an `IndexEntry` from the file.
181    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    /// Read a chunk and return the metadata in the form of a
201    /// `ChunkData`.
202    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    /// Load a 4 byte ASCII word from the file
209    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}