Skip to main content

uorustlibs/
mul.rs

1//! Methods for reading from standardized Mul and Idx files
2//!
3//! IDX files are defined as `|index:u32|size:u32|opt1:u16|opt2:u16|`
4//!
5//! Where index and size represent references into the equivalent Mul file
6//!
7//! Index values of `0xFEFEFEFF` and `0xFFFFFFFF` are considered undefined, and should be skipped
8
9use std::fs::{File, OpenOptions};
10use std::io::{Read, Seek, SeekFrom, Write};
11use std::path::Path;
12
13use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
14
15use crate::error::{MulReaderError, MulReaderResult, MulWriterResult};
16
17const UNDEF_RECORD: u32 = 0xFEFEFEFF;
18const INDEX_SIZE: u32 = 12;
19
20/// An individual record, read from a Mul file
21#[derive(Debug, PartialEq, Eq, Clone)]
22pub struct MulRecord {
23    ///Raw Mul data
24    pub data: Vec<u8>,
25    ///The index position in the Mul of this item
26    pub start: u32,
27    ///The total length in the Mul of this item
28    pub length: u32,
29    ///An implementation-specific variable
30    pub opt1: u16,
31    ///An implementation-specific variable
32    pub opt2: u16,
33}
34
35///Read Mul records out of an idx and a mul
36#[derive(Debug)]
37pub struct MulReader<T: Read + Seek> {
38    idx_reader: T,
39    data_reader: T,
40}
41
42impl MulReader<File> {
43    /// Create a new mul reader from an index and mul path
44    pub fn new(idx_path: &Path, mul_path: &Path) -> MulReaderResult<MulReader<File>> {
45        let idx_reader = File::open(idx_path)?;
46        let data_reader = File::open(mul_path)?;
47
48        Ok(MulReader {
49            idx_reader,
50            data_reader,
51        })
52    }
53}
54
55impl<T: Read + Seek> MulReader<T> {
56    /// Create a new mul reader from existing index and mul readers
57    pub fn from_readables(idx_reader: T, data_reader: T) -> MulReader<T> {
58        MulReader {
59            idx_reader,
60            data_reader,
61        }
62    }
63
64    /// Read a specific entry from the Mul.
65    ///
66    /// This method will return OffsetOutOfBounds if the index is marked invalid.
67    pub fn read(&mut self, index: u32) -> MulReaderResult<MulRecord> {
68        //Wind the idx reader to the index position
69        self.idx_reader
70            .seek(SeekFrom::Start((index * INDEX_SIZE) as u64))?;
71
72        let start = self.idx_reader.read_u32::<LittleEndian>()?;
73        //Check for empty cell
74        if start == UNDEF_RECORD || start == u32::MAX {
75            return Err(MulReaderError::OffsetOutOfBounds {
76                index,
77                offset: start,
78            });
79        }
80
81        let length = self.idx_reader.read_u32::<LittleEndian>()?;
82        let mut data = vec![0; length as usize];
83        let opt1 = self.idx_reader.read_u16::<LittleEndian>()?;
84        let opt2 = self.idx_reader.read_u16::<LittleEndian>()?;
85
86        self.data_reader.seek(SeekFrom::Start(start as u64))?;
87        self.data_reader.read_exact(data.as_mut_slice())?;
88
89        Ok(MulRecord {
90            data,
91            start,
92            length,
93            opt1,
94            opt2,
95        })
96    }
97}
98
99///Write new records onto existing Mul and Idx files
100#[derive(Debug)]
101pub struct MulWriter<T: Write + Seek> {
102    idx_writer: T,
103    data_writer: T,
104}
105
106pub enum MulWriterMode {
107    Append,
108    Truncate,
109}
110
111impl MulWriter<File> {
112    /// Create a new mul writer from an index and mul path
113    pub fn new(
114        idx_path: &Path,
115        mul_path: &Path,
116        mode: MulWriterMode,
117    ) -> MulWriterResult<MulWriter<File>> {
118        let mut options = OpenOptions::new();
119        let options = options.write(true).create(true).truncate(match mode {
120            MulWriterMode::Append => false,
121            MulWriterMode::Truncate => true,
122        });
123
124        let idx_writer = options.open(idx_path)?;
125        let data_writer = options.open(mul_path)?;
126
127        Ok(MulWriter {
128            idx_writer,
129            data_writer,
130        })
131    }
132}
133
134impl<T: Write + Seek> MulWriter<T> {
135    /// Create a new mul writer from an index and mul path
136    pub fn from_writables(idx_writer: T, data_writer: T) -> MulWriter<T> {
137        MulWriter {
138            idx_writer,
139            data_writer,
140        }
141    }
142    /// Append a new value to the mul files
143    pub fn append(
144        &mut self,
145        data: &[u8],
146        opt1: Option<u16>,
147        opt2: Option<u16>,
148    ) -> MulWriterResult<()> {
149        //Wind the files to the end
150        self.idx_writer.seek(SeekFrom::End(0))?;
151        let mul_size = self.data_writer.seek(SeekFrom::End(0))?;
152
153        //Fill up our fields
154        let start = mul_size as u32;
155        let length = data.len() as u32;
156        self.data_writer.write_all(data)?;
157        self.idx_writer.write_u32::<LittleEndian>(start)?;
158        self.idx_writer.write_u32::<LittleEndian>(length)?;
159        self.idx_writer
160            .write_u16::<LittleEndian>(opt1.unwrap_or_default())?;
161        self.idx_writer
162            .write_u16::<LittleEndian>(opt2.unwrap_or_default())?;
163
164        Ok(())
165    }
166}
167
168#[cfg(test)]
169pub mod tests {
170    use super::*;
171    use std::io::Cursor;
172
173    pub fn simple_from_vecs(vectors: Vec<(Vec<u8>, u16, u16)>) -> MulReader<Cursor<Vec<u8>>> {
174        let mut idx_reader = Cursor::new(vec![]);
175        let mut mul_reader = Cursor::new(vec![]);
176        //For every MUL record, we should have an index record pointing at it
177        for (vec, opt1, opt2) in vectors {
178            let len = vec.len();
179            let mul_size = mul_reader.seek(SeekFrom::End(0)).unwrap();
180            let mut idx_cursor = Cursor::new(vec![]);
181            idx_cursor
182                .write_u32::<LittleEndian>(mul_size as u32)
183                .unwrap(); //Position
184            idx_cursor.write_u32::<LittleEndian>(len as u32).unwrap(); //Length
185            idx_cursor.write_u16::<LittleEndian>(opt1).unwrap(); //Opt1
186            idx_cursor.write_u16::<LittleEndian>(opt2).unwrap(); //Opt2
187            idx_reader.write_all(idx_cursor.get_ref()).unwrap();
188            mul_reader.write_all(&vec).unwrap();
189        }
190        MulReader::from_readables(idx_reader, mul_reader)
191    }
192
193    pub fn simple_from_mul_records(records: Vec<MulRecord>) -> MulReader<Cursor<Vec<u8>>> {
194        let mut idx_reader = Cursor::new(vec![]);
195        let mut mul_reader = Cursor::new(vec![]);
196        //For every MUL record, we should have an index record pointing at it
197        for record in records {
198            let mul_size = mul_reader.seek(SeekFrom::End(0)).unwrap();
199            let mut idx_cursor = Cursor::new(vec![]);
200            idx_cursor
201                .write_u32::<LittleEndian>(mul_size as u32)
202                .unwrap(); //Position
203            idx_cursor
204                .write_u32::<LittleEndian>(record.data.len() as u32)
205                .unwrap(); //Length
206            idx_cursor.write_u16::<LittleEndian>(record.opt1).unwrap(); //Opt1
207            idx_cursor.write_u16::<LittleEndian>(record.opt2).unwrap(); //Opt2
208            idx_reader.write_all(idx_cursor.get_ref()).unwrap();
209            mul_reader.write_all(&record.data).unwrap();
210        }
211        MulReader::from_readables(idx_reader, mul_reader)
212    }
213}