prc/
prc_trait.rs

1use std::cmp::Ordering;
2use std::convert::TryFrom;
3use std::io::{Read, Seek, SeekFrom};
4
5use byteorder::{LittleEndian, ReadBytesExt};
6use hash40::{Hash40, ReadHash40};
7
8/// A trait allowing a type to be converted from the param container format
9pub trait Prc: Sized {
10    /// Creates Self by reading the from the data. The reader should be
11    /// positioned at the start of the param marker before calling this
12    fn read_param<R: Read + Seek>(reader: &mut R, offsets: FileOffsets) -> Result<Self>;
13
14    /// Controls how structs should try to behave when reading the param.
15    /// Implementing this manually is usually not necessary, but it can
16    /// be useful in certain cases. For example, [Option] types work by
17    /// returning [None] when the hash isn't found in the struct.
18    fn read_from_struct<R: Read + Seek>(
19        reader: &mut R,
20        hash: Hash40,
21        offsets: FileOffsets,
22        struct_data: StructData,
23    ) -> Result<Self> {
24        struct_data.read_child(reader, hash, offsets)
25    }
26
27    /// A blanket implementation which reads the entire file to create
28    /// Self. The reader should be at the beginning of the file before
29    /// calling this.
30    fn read_file<R: Read + Seek>(reader: &mut R) -> Result<Self> {
31        let offsets = prepare(reader)?;
32        Self::read_param(reader, offsets)
33    }
34}
35
36// make custom reader struct to return errors in the correct type?
37
38pub type Result<T> = std::result::Result<T, Error>;
39
40/// The error type returned from [Prc] trait operations, including
41/// the Hash40 path and reader position
42#[derive(Debug)]
43pub struct Error {
44    pub path: Vec<ErrorPathPart>,
45    pub position: std::io::Result<u64>,
46    pub kind: ErrorKind,
47}
48
49/// The original error thrown
50#[derive(Debug)]
51pub enum ErrorKind {
52    WrongParamNumber { expected: ParamNumber, received: u8 },
53    ParamNotFound(Hash40),
54    Io(std::io::Error),
55}
56
57/// Used for the path of an error. Could be a hash (for structs) or
58/// an index (for a list)
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum ErrorPathPart {
61    Index(u32),
62    Hash(Hash40),
63}
64
65/// Offsets to tables derived from the file header, necessary when reading
66/// certain params
67#[derive(Debug, Copy, Clone)]
68pub struct FileOffsets {
69    pub hashes: u64,
70    pub ref_table: u64,
71}
72
73/// Information read from a struct to facilitate reading child params
74#[derive(Debug, Copy, Clone)]
75pub struct StructData {
76    pub position: u64,
77    pub len: u32,
78    pub ref_offset: u32,
79}
80
81/// The number associated with each type of param in a file
82#[repr(u8)]
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum ParamNumber {
85    Bool = 1,
86    I8,
87    U8,
88    I16,
89    U16,
90    I32,
91    U32,
92    Float,
93    Hash,
94    String,
95    List,
96    Struct,
97}
98
99pub fn check_type<R: Read + Seek>(reader: &mut R, value: ParamNumber) -> Result<()> {
100    let pre_pos = reader.stream_position();
101    let read = reader.read_u8().map_err(|e| Error::new(e, reader))?;
102
103    if read != value.into() {
104        Err(Error::new_with_pos(
105            ErrorKind::WrongParamNumber {
106                expected: value,
107                received: read,
108            },
109            pre_pos,
110        ))
111    } else {
112        Ok(())
113    }
114}
115
116impl StructData {
117    pub fn from_stream<R: Read + Seek>(reader: &mut R) -> Result<Self> {
118        let position = reader
119            .seek(SeekFrom::Current(0))
120            .map_err(|e| Error::new(e, reader))?;
121
122        check_type(reader, ParamNumber::Struct)?;
123
124        let len = reader
125            .read_u32::<LittleEndian>()
126            .map_err(|e| Error::new(e, reader))?;
127        let ref_offset = reader
128            .read_u32::<LittleEndian>()
129            .map_err(|e| Error::new(e, reader))?;
130
131        reader
132            .seek(SeekFrom::Start(position))
133            .map_err(|e| Error::new(e, reader))?;
134
135        Ok(Self {
136            position,
137            len,
138            ref_offset,
139        })
140    }
141
142    /// Moves the reader to the child param with the provided hash
143    fn search_child<R: Read + Seek>(
144        &self,
145        reader: &mut R,
146        hash: Hash40,
147        offsets: FileOffsets,
148    ) -> Result<()> {
149        let mut low = 0;
150        // on a zero length vec, high = -1, which ends the loop
151        let mut high = self.len as i64 - 1;
152        while low <= high {
153            let i = (low + high) / 2;
154            reader
155                .seek(SeekFrom::Start(
156                    offsets.ref_table + self.ref_offset as u64 + (i as u64 * 8),
157                ))
158                .map_err(|e| Error::new(e, reader))?;
159
160            let hash_index = reader
161                .read_u32::<LittleEndian>()
162                .map_err(|e| Error::new(e, reader))?;
163            let param_offset = reader
164                .read_u32::<LittleEndian>()
165                .map_err(|e| Error::new(e, reader))?;
166
167            reader
168                .seek(SeekFrom::Start(offsets.hashes + (hash_index as u64 * 8)))
169                .map_err(|e| Error::new(e, reader))?;
170            let read_hash = reader
171                .read_hash40::<LittleEndian>()
172                .map_err(|e| Error::new(e, reader))?;
173
174            match read_hash.cmp(&hash) {
175                Ordering::Less => low = i + 1,
176                Ordering::Greater => high = i - 1,
177                Ordering::Equal => {
178                    reader
179                        .seek(SeekFrom::Start(self.position + (param_offset as u64)))
180                        .map_err(|e| Error::new(e, reader))?;
181                    return Ok(());
182                }
183            }
184        }
185        Err(Error::new_with_pos(
186            ErrorKind::ParamNotFound(hash),
187            Ok(self.position),
188        ))
189    }
190
191    /// Moves the reader to the child param with the provided hash and reads
192    /// the param fulfilling the [Prc] trait.
193    pub fn read_child<R: Read + Seek, T: Prc>(
194        &self,
195        reader: &mut R,
196        hash: Hash40,
197        offsets: FileOffsets,
198    ) -> Result<T> {
199        // If the child param isn't found, we don't push that hash into the error path
200        self.search_child(reader, hash, offsets)?;
201
202        // Errors caused while doing anything else will add the hash to the path
203        T::read_param(reader, offsets).map_err(|mut e| {
204            e.path.insert(0, ErrorPathPart::Hash(hash));
205            Error {
206                path: e.path,
207                position: e.position,
208                kind: e.kind,
209            }
210        })
211    }
212}
213
214/// Reads the header data and moves the reader to the start of the params
215pub fn prepare<R: Read + Seek>(reader: &mut R) -> Result<FileOffsets> {
216    prepare_internal(reader).map_err(|e| Error::new(e, reader))
217}
218
219fn prepare_internal<R: Read + Seek>(reader: &mut R) -> std::io::Result<FileOffsets> {
220    reader.seek(SeekFrom::Current(8))?;
221    let hashes_size = reader.read_u32::<LittleEndian>()?;
222    let ref_table_size = reader.read_u32::<LittleEndian>()?;
223
224    let hashes = reader.seek(SeekFrom::Current(0))?;
225
226    reader.seek(SeekFrom::Current(hashes_size as i64))?;
227    let ref_table = reader.seek(SeekFrom::Current(0))?;
228
229    reader.seek(SeekFrom::Current(ref_table_size as i64))?;
230    Ok(FileOffsets { hashes, ref_table })
231}
232
233// basic implementations for all types except struct here
234
235impl Prc for bool {
236    fn read_param<R: Read + Seek>(reader: &mut R, _offsets: FileOffsets) -> Result<Self> {
237        check_type(reader, ParamNumber::Bool)?;
238        reader
239            .read_u8()
240            .map_err(|e| Error::new(e, reader))
241            .map(|byte| byte > 0)
242    }
243}
244
245macro_rules! impl_read_byte {
246    ($(($param_type:ty, $num:path, $read_func:ident)),*) => {
247        $(
248            impl Prc for $param_type {
249                fn read_param<R: Read + Seek>(reader: &mut R, _offsets: FileOffsets) -> Result<Self> {
250                    check_type(reader, $num)?;
251                    ReadBytesExt::$read_func(reader).map_err(|e| Error::new(e, reader))
252                }
253            }
254        )*
255    };
256}
257
258macro_rules! impl_read_value {
259    ($(($param_type:ty, $num:path, $read_func:ident)),*) => {
260        $(
261            impl Prc for $param_type {
262                fn read_param<R: Read + Seek>(reader: &mut R, _offsets: FileOffsets) -> Result<Self> {
263                    check_type(reader, $num)?;
264                    ReadBytesExt::$read_func::<LittleEndian>(reader).map_err(|e| Error::new(e, reader))
265                }
266            }
267        )*
268    };
269}
270
271impl_read_byte!(
272    (i8, ParamNumber::I8, read_i8),
273    (u8, ParamNumber::U8, read_u8)
274);
275
276impl_read_value!(
277    (i16, ParamNumber::I16, read_i16),
278    (u16, ParamNumber::U16, read_u16),
279    (i32, ParamNumber::I32, read_i32),
280    (u32, ParamNumber::U32, read_u32),
281    (f32, ParamNumber::Float, read_f32)
282);
283
284impl Prc for Hash40 {
285    fn read_param<R: Read + Seek>(reader: &mut R, offsets: FileOffsets) -> Result<Self> {
286        check_type(reader, ParamNumber::Hash)?;
287        let hash_index = reader
288            .read_u32::<LittleEndian>()
289            .map_err(|e| Error::new(e, reader))?;
290        let end_position = reader
291            .seek(SeekFrom::Current(0))
292            .map_err(|e| Error::new(e, reader))?;
293
294        reader
295            .seek(SeekFrom::Start(offsets.hashes + (hash_index as u64 * 8)))
296            .map_err(|e| Error::new(e, reader))?;
297        let hash = reader
298            .read_hash40::<LittleEndian>()
299            .map_err(|e| Error::new(e, reader))?;
300
301        reader
302            .seek(SeekFrom::Start(end_position))
303            .map_err(|e| Error::new(e, reader))?;
304        Ok(hash)
305    }
306}
307
308impl Prc for String {
309    fn read_param<R: Read + Seek>(reader: &mut R, offsets: FileOffsets) -> Result<Self> {
310        check_type(reader, ParamNumber::String)?;
311        let str_offset = reader
312            .read_u32::<LittleEndian>()
313            .map_err(|e| Error::new(e, reader))?;
314        let end_position = reader
315            .seek(SeekFrom::Current(0))
316            .map_err(|e| Error::new(e, reader))?;
317
318        reader
319            .seek(SeekFrom::Start(offsets.ref_table + str_offset as u64))
320            .map_err(|e| Error::new(e, reader))?;
321        let mut string = String::new();
322
323        loop {
324            let byte = reader.read_u8().map_err(|e| Error::new(e, reader))?;
325            if byte == 0 {
326                break;
327            }
328            string.push(byte as char);
329        }
330
331        reader
332            .seek(SeekFrom::Start(end_position))
333            .map_err(|e| Error::new(e, reader))?;
334        Ok(string)
335    }
336}
337
338impl<T: Prc> Prc for Vec<T> {
339    fn read_param<R: Read + Seek>(reader: &mut R, offsets: FileOffsets) -> Result<Self> {
340        let start = reader
341            .seek(SeekFrom::Current(0))
342            .map_err(|e| Error::new(e, reader))?;
343        check_type(reader, ParamNumber::List)?;
344        let len = reader
345            .read_u32::<LittleEndian>()
346            .map_err(|e| Error::new(e, reader))?;
347
348        let mut list = Vec::with_capacity(len as usize);
349
350        for i in 0..len {
351            reader
352                .seek(SeekFrom::Start(start + 5 + (i as u64 * 4)))
353                .map_err(|e| Error::new(e, reader))?;
354            let offset = reader
355                .read_u32::<LittleEndian>()
356                .map_err(|e| Error::new(e, reader))?;
357            reader
358                .seek(SeekFrom::Start(start + offset as u64))
359                .map_err(|e| Error::new(e, reader))?;
360
361            // read the type, and potentially add index to the error path
362            let child = T::read_param(reader, offsets).map_err(|mut e| {
363                e.path.insert(0, ErrorPathPart::Index(i));
364                Error {
365                    path: e.path,
366                    position: e.position,
367                    kind: e.kind,
368                }
369            })?;
370            list.push(child);
371        }
372
373        Ok(list)
374    }
375}
376
377impl<T: Prc> Prc for Option<T> {
378    fn read_param<R: Read + Seek>(_: &mut R, _: FileOffsets) -> Result<Self> {
379        unimplemented!("Option's should be read only by using the `read_from_struct` method")
380    }
381
382    fn read_from_struct<R: Read + Seek>(
383        reader: &mut R,
384        hash: Hash40,
385        offsets: FileOffsets,
386        struct_data: StructData,
387    ) -> Result<Self> {
388        struct_data
389            .search_child(reader, hash, offsets)
390            .and_then(|_| T::read_param(reader, offsets))
391            .map(|param| Some(param))
392            .or_else(|err| match &err.kind {
393                ErrorKind::ParamNotFound(_) => Ok(None),
394                _ => Err(err),
395            })
396    }
397}
398
399impl TryFrom<u8> for ParamNumber {
400    type Error = u8;
401
402    fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
403        match value {
404            1 => Ok(ParamNumber::Bool),
405            2 => Ok(ParamNumber::I8),
406            3 => Ok(ParamNumber::U8),
407            4 => Ok(ParamNumber::I16),
408            5 => Ok(ParamNumber::U16),
409            6 => Ok(ParamNumber::I32),
410            7 => Ok(ParamNumber::U32),
411            8 => Ok(ParamNumber::Float),
412            9 => Ok(ParamNumber::Hash),
413            10 => Ok(ParamNumber::String),
414            11 => Ok(ParamNumber::List),
415            12 => Ok(ParamNumber::Struct),
416            _ => Err(value),
417        }
418    }
419}
420
421impl From<ParamNumber> for u8 {
422    fn from(param_num: ParamNumber) -> Self {
423        param_num as u8
424    }
425}
426
427impl Error {
428    fn new<E: Into<ErrorKind>, S: Seek>(kind: E, seek: &mut S) -> Self {
429        Error {
430            path: vec![],
431            position: seek.stream_position(),
432            kind: kind.into(),
433        }
434    }
435
436    fn new_with_pos<E: Into<ErrorKind>>(kind: E, pos: std::io::Result<u64>) -> Self {
437        Error {
438            path: vec![],
439            position: pos,
440            kind: kind.into(),
441        }
442    }
443}
444
445impl From<std::io::Error> for ErrorKind {
446    fn from(e: std::io::Error) -> Self {
447        ErrorKind::Io(e)
448    }
449}