ea_big/
lib.rs

1//! Library to decode ea .big files
2//!
3//! ```rust
4//! use std::fs::File;
5//! use std::io;
6//!
7//! fn main() -> io::Result<()> {
8//!     let file = File::open("./example.big");
9//!     let (header, entries) = ea_big::from_reader(&file)?;
10//!
11//!     let embed = ea_big::open_file(&file, &entries[0]);
12//!
13//!     Ok(())   
14//! }
15//! ```
16use std::ffi::CString;
17use std::io::{Error, ErrorKind, Read, Result, Seek, SeekFrom};
18
19/// Header of the BIG file
20#[derive(Debug, Clone)]
21pub struct Header {
22    /// Header name
23    pub name: String,
24    /// File size
25    pub size: u32,
26    /// Embedded files
27    pub files: u32,
28    /// Index table size
29    pub indices: u32,
30}
31
32impl Header {
33    /// Decodes the Header from a [`Reader`](https://doc.rust-lang.org/std/io/trait.Read.html)
34    pub fn from_reader<M: Read>(mut reader: M) -> Result<Self> {
35        let name = {
36            let mut buf = vec![0; 4];
37            reader.read_exact(&mut buf)?;
38            String::from_utf8(buf).map_err(|e| Error::new(ErrorKind::InvalidData, e))?
39        };
40
41        let size = {
42            let mut buf = [0; 4];
43            reader.read_exact(&mut buf)?;
44            u32::from_le_bytes(buf)
45        };
46
47        let files = {
48            let mut buf = [0; 4];
49            reader.read_exact(&mut buf)?;
50            u32::from_be_bytes(buf)
51        };
52
53        let indices = {
54            let mut buf = [0; 4];
55            reader.read_exact(&mut buf)?;
56            u32::from_be_bytes(buf)
57        };
58
59        Ok(Header {
60            name,
61            size,
62            files,
63            indices,
64        })
65    }
66}
67
68/// A entry in the table of indices
69#[derive(Debug, Clone)]
70pub struct TableEntry {
71    /// position of the embedded file within the big file
72    pub pos: u32,
73    /// size of the embedded file
74    pub size: u32,
75    /// embedded file name
76    pub name: String,
77}
78
79impl TableEntry {
80    /// Decodes a table entry from a [`Reader`](https://doc.rust-lang.org/std/io/trait.Read.html)
81    ///
82    /// The [`Reader`](https://doc.rust-lang.org/std/io/trait.Read.html) must be at the start of an entry
83    pub fn from_reader<M: Read>(mut reader: M) -> Result<Self> {
84        let pos = {
85            let mut buf = [0; 4];
86            reader.read_exact(&mut buf)?;
87            u32::from_be_bytes(buf)
88        };
89
90        let size = {
91            let mut buf = [0; 4];
92            reader.read_exact(&mut buf)?;
93            u32::from_be_bytes(buf)
94        };
95
96        let bytes = reader
97            .bytes()
98            .scan((), |_, opt| opt.ok())
99            .take_while(|&n| n != 0)
100            .collect::<Vec<u8>>();
101
102        let name = CString::new(bytes)?.to_string_lossy().into_owned();
103
104        Ok(TableEntry { pos, size, name })
105    }
106}
107
108/// Helper struct to read a file embedded in the big file
109pub struct EmbeddedFile<M: Read + Seek> {
110    reader: M,
111    offset: u64,
112    size: u64,
113    cursor: u64,
114}
115
116impl<M: Read + Seek> EmbeddedFile<M> {
117    fn new(reader: M, offset: u64, size: u64) -> Self {
118        EmbeddedFile {
119            reader,
120            offset,
121            size,
122            cursor: 0,
123        }
124    }
125}
126
127impl<M: Read + Seek> Read for EmbeddedFile<M> {
128    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
129        self.reader
130            .seek(SeekFrom::Start(self.offset + self.cursor))?;
131
132        let len = buf.len().min((self.size - self.cursor) as usize);
133
134        let read = self.reader.read(&mut buf[..len])?;
135
136        self.cursor += read as u64;
137
138        Ok(read)
139    }
140}
141
142impl<M: Read + Seek> Seek for EmbeddedFile<M> {
143    fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
144        let offset = match pos {
145            SeekFrom::Start(pos) => pos as i64,
146            SeekFrom::End(pos) => self.size as i64 + pos,
147            SeekFrom::Current(pos) => self.cursor as i64 + pos,
148        };
149
150        if offset < 0 {
151            return Err(Error::new(
152                ErrorKind::InvalidInput,
153                String::from("Cannot seek behind 0"),
154            ));
155        }
156
157        self.cursor = offset as u64;
158
159        Ok(self.cursor)
160    }
161}
162
163/// Decodes the header and the indices table from a [`Reader`](https://doc.rust-lang.org/std/io/trait.Read.html)
164pub fn from_reader<M: Read>(mut reader: M) -> Result<(Header, Vec<TableEntry>)> {
165    let header = Header::from_reader(&mut reader)?;
166
167    let mut entries = Vec::with_capacity(header.files as usize);
168
169    for _ in 0..header.files as usize {
170        let entry = TableEntry::from_reader(&mut reader)?;
171
172        entries.push(entry);
173    }
174
175    Ok((header, entries))
176}
177
178/// Returns a [`EmbeddedFile`](struct.EmbeddedFile.html) representing the [`TableEntry`](struct.TableEntry.html)
179pub fn open_file<M: Read + Seek>(reader: M, entry: &TableEntry) -> EmbeddedFile<M> {
180    EmbeddedFile::new(reader, entry.pos as u64, entry.size as u64)
181}