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
//! Structs to represent chunks and iterate them
use std::io::Cursor;
use byteorder::{LittleEndian, ReadBytesExt};

pub mod string_table;
mod chunk_header;
mod package;
pub mod table_type;
mod resource;
mod table_type_spec;
mod xml;

pub use self::string_table::StringTableWrapper;
pub use self::string_table::StringTableCache;
pub use self::chunk_header::ChunkHeader;
pub use self::package::PackageWrapper;
pub use self::table_type_spec::TypeSpecWrapper;
pub use self::table_type::TableTypeWrapper;
pub use self::table_type::ConfigurationWrapper;

pub use self::resource::ResourceWrapper;
pub use self::xml::XmlNamespaceStartWrapper;
pub use self::xml::XmlNamespaceEndWrapper;
pub use self::xml::XmlTagStartWrapper;
pub use self::xml::XmlTagEndWrapper;
pub use self::xml::XmlTextWrapper;

use errors::*;

pub const TOKEN_STRING_TABLE: u16 = 0x0001;
pub const TOKEN_RESOURCE: u16 = 0x0180;
pub const TOKEN_PACKAGE: u16 = 0x0200;
pub const TOKEN_TABLE_TYPE: u16 = 0x201;
pub const TOKEN_TABLE_SPEC: u16 = 0x202;
pub const TOKEN_XML_START_NAMESPACE: u16 = 0x100;
pub const TOKEN_XML_END_NAMESPACE: u16 = 0x101;
pub const TOKEN_XML_TAG_START: u16 = 0x102;
pub const TOKEN_XML_TAG_END: u16 = 0x103;
pub const TOKEN_XML_TEXT: u16 = 0x104;

pub enum Chunk<'a> {
    StringTable(StringTableWrapper<'a>),
    Package(PackageWrapper<'a>),
    TableTypeSpec(TypeSpecWrapper<'a>),
    TableType(TableTypeWrapper<'a>),
    XmlNamespaceStart(XmlNamespaceStartWrapper<'a>),
    XmlNamespaceEnd(XmlNamespaceEndWrapper<'a>),
    XmlTagStart(XmlTagStartWrapper<'a>),
    XmlTagEnd(XmlTagEndWrapper<'a>),
    XmlText(XmlTextWrapper<'a>),
    Resource(ResourceWrapper<'a>),
    Unknown,
}

pub struct ChunkLoaderStream<'a> {
    cursor: Cursor<&'a [u8]>,
    previous: Option<u64>,
}

impl<'a> ChunkLoaderStream<'a> {
    pub fn new(cursor: Cursor<&'a [u8]>) -> Self {
        ChunkLoaderStream {
            cursor: cursor,
            previous: None,
        }
    }

    fn read_one(&mut self) -> Result<Chunk<'a>> {
        let initial_position = self.cursor.position();
        let token = self.cursor.read_u16::<LittleEndian>()?;
        let header_size = self.cursor.read_u16::<LittleEndian>()?;
        let chunk_size = self.cursor.read_u32::<LittleEndian>()?;
        let chunk_header = ChunkHeader::new(initial_position, header_size, chunk_size, token);

        let chunk = self.get_chunk(&chunk_header);

        if let Chunk::Package(_) = chunk {
            self.cursor.set_position(chunk_header.get_data_offset());
        } else {
            self.cursor.set_position(chunk_header.get_chunk_end());
        }

        Ok(chunk)
    }

    fn get_chunk(&self, header: &ChunkHeader) -> Chunk<'a> {
        let raw_data = self.cursor.get_ref();
        let slice = &raw_data[header.get_offset() as usize..header.get_chunk_end() as usize];


        let chunk = match header.get_token() {
            TOKEN_STRING_TABLE => Chunk::StringTable(StringTableWrapper::new(slice)),
            TOKEN_PACKAGE => Chunk::Package(PackageWrapper::new(slice)),
            TOKEN_TABLE_SPEC => Chunk::TableTypeSpec(TypeSpecWrapper::new(slice)),
            TOKEN_TABLE_TYPE => {
                let current_chunk_data_offset = header.get_data_offset() - header.get_offset();
                Chunk::TableType(TableTypeWrapper::new(slice, current_chunk_data_offset))
            }
            TOKEN_XML_START_NAMESPACE => {
                Chunk::XmlNamespaceStart(XmlNamespaceStartWrapper::new(slice))
            }
            TOKEN_XML_END_NAMESPACE => Chunk::XmlNamespaceEnd(XmlNamespaceEndWrapper::new(slice)),
            TOKEN_XML_TAG_START => Chunk::XmlTagStart(XmlTagStartWrapper::new(slice)),
            TOKEN_XML_TAG_END => Chunk::XmlTagEnd(XmlTagEndWrapper::new(slice)),
            TOKEN_XML_TEXT => Chunk::XmlText(XmlTextWrapper::new(slice)),
            TOKEN_RESOURCE => Chunk::Resource(ResourceWrapper::new(slice)),
            t => {
                error!("Unknown chunk: 0x{:X}", t);

                Chunk::Unknown
            }
        };

        chunk
    }
}

impl<'a> Iterator for ChunkLoaderStream<'a> {
    type Item = Result<Chunk<'a>>;

    fn next(&mut self) -> Option<Result<Chunk<'a>>> {
        if let Some(prev) = self.previous {
            if prev == self.cursor.position() {
                return None;
            }
        }

        if self.cursor.position() >= self.cursor.get_ref().len() as u64 {
            return None;
        }

        self.previous = Some(self.cursor.position());
        let chunk = self.read_one();

        Some(chunk)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::Cursor;
    use model::owned::{StringTableBuf, ResourcesBuf, OwnedBuf};

    #[test]
    fn it_can_detect_loops() {
        let data = vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        let cursor: Cursor<&[u8]> = Cursor::new(&data);
        let mut stream = ChunkLoaderStream::new(cursor);

        let _ = stream.next().unwrap();
        assert!(stream.next().is_none());
    }

    #[test]
    fn it_stops_the_iteration_if_out_of_bounds() {
        let data = vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        let mut cursor: Cursor<&[u8]> = Cursor::new(&data);
        cursor.set_position(30);
        let mut stream = ChunkLoaderStream::new(cursor);

        assert!(stream.next().is_none());
    }

    #[test]
    fn it_can_iterate_over_chunks() {
        let mut data = vec![0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        let st = StringTableBuf::default();
        data.extend(st.to_vec().unwrap());
        let res = ResourcesBuf::default();
        data.extend(res.to_vec().unwrap());

        let mut cursor: Cursor<&[u8]> = Cursor::new(&data);
        // Skip header
        cursor.set_position(12);
        let mut stream = ChunkLoaderStream::new(cursor);

        // Assert string table
        let _ = stream.next().unwrap();
        let _ = stream.next().unwrap();

        assert!(stream.next().is_none());
    }
}