1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
use std::collections::HashMap;
use std::io::{Error, ErrorKind, Read, Seek, SeekFrom};

use byteorder::{BigEndian, ReadBytesExt};


/// The usage information for an `IndexEntry`.
#[derive(Debug)]
pub enum Usage {
    /// Identifier: 'Pict'.
    /// Indicates the resource is an image.
    Pict,

    /// Identifier: 'Snd '.
    /// Indicates the resource is a sound.
    Snd,

    /// Indentifier: 'Data'.
    /// Indicates the resource is some data.
    Data,

    /// Identifier: 'Exec'.
    /// Indicates the resource is an executable.
    Exec,
}


/// Contains the usage information for an entry, the resource number of
/// the entry, and where in the blob the entry starts.
#[derive(Debug)]
pub struct IndexEntry {
    pub usage: Usage,
    pub num: u32,
    start: u32,
}


/// Container for list of resource index entries.
#[derive(Debug)]
pub struct ResourceIndex {
    pub entries: HashMap<usize, IndexEntry>,
}


/// Container for chunk metadata. Used for identifying a chunk without
/// loading the full chunk into memory.
#[derive(Debug)]
pub struct ChunkData {
    pub id: String,
    len: u32,
}


/// Representation for loaded blorb chunks
pub enum Chunk {

    /// Chunk returned when the loaded chunk type is unable to be
    /// identified. Per Specification, the machine must ignore unknown
    /// chunks, and this type will be used to do so when necessary.
    Unknown{meta: ChunkData, data: Vec<u8>},

    /// Identifier: 'RIdx'.
    /// Contains a resource index for the IF.
    /// This chunk is mandatory and must be the first chunk in the form.
    ResourceIndex{index: ResourceIndex},

    /// Identifier: 'IFmd'.
    /// Contains xml metadata content for the IF.
    Metadata{info: String},

    /// Identifier: 'Fspec'.
    /// Contains a reference to a frontispiece image.
    /// This chunk is optional.
    Frontispiece{num: u32},

    /// Identifier: 'GLUL'.
    /// Contains Glulx executable.
    /// This is a executable resource chunk.
    Glulx{code: Vec<u8>},

    /// Identifier: 'PNG '.
    /// Contains a PNG image.
    /// This is a picture resource chunk.
    Png{data: Vec<u8>},

    /// Identifier: 'JPEG'.
    /// Contains a JPEG image.
    /// This is a picture resource chunk.
    Jpeg{data: Vec<u8>},
}


/// Access point for lazy loading blorb contents. This struct contains
/// the methods to load resources from the provided blorb. This does not
/// cache the results.
pub struct Blorb<R: Read + Seek> {
    /// The length of the IFRS chunk
    pub len: u32,
    index: ResourceIndex,
    file: R,
}


impl<R: Read + Seek> Blorb<R> {

    /// Creates a new Blorb from a file. The code goes through the
    /// given game file, validates the file type, and extracts the
    /// basic game objects for the blorb.
    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");

        // Check that the form type is IFRS
        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,
        })
    }

    /// loads a resource using the given index entry.
    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)
    }

    /// Load a `Chunk` from the 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})
            },
        }
    }

    /// Load an `IndexEntry` from the file.
    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})
    }

    /// Read a chunk and return the metadata in the form of a
    /// `ChunkData`.
    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})
    }

    /// Load a 4 byte ASCII word from the file
    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)
    }
}