criware_utf_core/
reader.rs

1use std::{
2    collections::HashMap,
3    io::{Cursor, Read},
4};
5
6use crate::{Error, IOErrorHelper, Result, Value, ValueKind, value::sealed::Primitive};
7
8#[inline(always)]
9pub(crate) fn is_valid_value_flag(half: u8) -> bool {
10    half <= 8 || half == 0xa || half == 0xb
11}
12#[inline(always)]
13pub(crate) fn is_valid_storage_flag(half: u8) -> bool {
14    half == 0x10 || half == 0x30 || half == 0x50
15}
16
17macro_rules! handle_type_flag {
18    ($type_flag:path => $expected:path) => {
19        if $type_flag != $expected as u8 {
20            if is_valid_value_flag($type_flag) {
21                return Err(Error::WrongColumnType($type_flag, $expected as u8));
22            } else {
23                return Err(Error::InvalidColumnType($type_flag));
24            }
25        }
26    };
27}
28
29/// Abstraction layer for reading UTF tables
30///
31pub struct Reader {
32    column_buffer: Cursor<Vec<u8>>,
33    column_buffer_size: usize,
34    row_buffer: Cursor<Vec<u8>>,
35    row_buffer_size: usize,
36    strings: HashMap<u32, String>,
37    blobs: Vec<u8>,
38    table_name_index: u32,
39    field_count: u16,
40}
41
42impl Reader {
43    /**
44    Creates a new `Reader`
45
46    Preliminary validity checks are performed as well.
47
48    # Example
49    ```no_run
50    # use std::fs::File;
51    # use criware_utf_core::Reader;
52    let mut file = File::open("random-table.bin")?;
53    let reader = Reader::new(&mut file)?;
54    ```
55     */
56    pub fn new(reader: &mut dyn Read) -> Result<Reader> {
57        let table_size = {
58            let mut header = [0u8; 8];
59            reader.read_exact(&mut header).io("@UTF header")?;
60            if &header[0..4] != b"@UTF" {
61                return Err(Error::MalformedHeader);
62            }
63            u32::from_be_bytes(header[4..8].try_into().unwrap())
64        };
65        if table_size < 24 {
66            return Err(Error::EOF("@UTF header".to_string()));
67        }
68        let mut header = [0u8; 24];
69        reader.read_exact(&mut header).io("@UTF header")?;
70        let row_offset = u32::from_be_bytes(header[0..4].try_into().unwrap());
71        let string_offset = u32::from_be_bytes(header[4..8].try_into().unwrap());
72        let blob_offset = u32::from_be_bytes(header[8..12].try_into().unwrap());
73        let table_name = u32::from_be_bytes(header[12..16].try_into().unwrap());
74        let field_count = u16::from_be_bytes(header[16..18].try_into().unwrap());
75        let row_size = u16::from_be_bytes(header[18..20].try_into().unwrap());
76        let row_count = u32::from_be_bytes(header[20..24].try_into().unwrap());
77        if 24 > row_offset
78            || row_offset > string_offset
79            || string_offset > blob_offset
80            || blob_offset > table_size
81            || (row_size as u32 * row_count) != string_offset - row_offset
82        {
83            return Err(Error::MalformedHeader);
84        }
85        let (column_buffer, column_buffer_size) = {
86            let mut buffer = vec![0u8; row_offset as usize - 24];
87            reader.read_exact(&mut buffer).io("UTF column data")?;
88            let len = buffer.len();
89            (Cursor::new(buffer), len)
90        };
91        let (row_buffer, row_buffer_size) = {
92            let mut buffer = vec![0u8; (string_offset - row_offset) as usize];
93            reader.read_exact(&mut buffer).io("UTF row data")?;
94            let len = buffer.len();
95            (Cursor::new(buffer), len)
96        };
97        let strings = {
98            let mut buffer = vec![0u8; (blob_offset - string_offset) as usize];
99            reader.read_exact(&mut buffer).io("UTF string data")?;
100            let mut strings = HashMap::new();
101            let mut start = 0;
102            let mut index = 0;
103            while index < buffer.len() {
104                if buffer[index] == 0 {
105                    match std::str::from_utf8(&buffer[(start as usize)..index]) {
106                        Ok(value) => strings.insert(start, value.to_owned()),
107                        Err(error) => return Err(Error::StringMalformed(error)),
108                    };
109                    start = (index + 1) as u32;
110                }
111                index += 1;
112            }
113            strings
114        };
115        if !strings.contains_key(&table_name) {
116            return Err(Error::MalformedHeader);
117        }
118        let mut blobs = vec![0u8; (table_size - blob_offset) as usize];
119        reader.read_exact(&mut blobs).io("UTF blob data")?;
120        Ok(Reader {
121            column_buffer,
122            column_buffer_size,
123            row_buffer,
124            row_buffer_size,
125            strings,
126            blobs,
127            table_name_index: table_name,
128            field_count,
129        })
130    }
131
132    /**
133    Returns the number of columns in the table being read
134
135    # Example
136    ```no_run
137    # use std::fs::File;
138    # use criware_utf_core::Reader;
139    let mut file = File::open("random-table.bin")?;
140    let reader = Reader::new(&mut file)?;
141    assert_eq!(reader.field_count(), 7u16);
142    ```
143     */
144    pub fn field_count(&self) -> u16 {
145        self.field_count
146    }
147
148    /**
149    Returns the name of the table being read
150
151    # Example
152    ```no_run
153    # use std::fs::File;
154    # use criware_utf_core::Reader;
155    let mut file = File::open("random-table.bin")?;
156    let reader = Reader::new(&mut file)?;
157    assert_eq!(reader.table_name(), "ImportantTable");
158    ```
159     */
160    pub fn table_name<'a>(&'a self) -> &'a str {
161        self.strings.get(&self.table_name_index).unwrap().as_str()
162    }
163
164    /**
165    Returns [`true`] if there is more data in the column data section, or
166    [`false`] otherwise.
167
168    # Example
169    ```no_run
170    # use std::fs::File;
171    # use criware_utf_core::Reader;
172    let mut file = std::fs::File::open("random-table.bin")?;
173    let reader = criware_utf_core::Reader::new(&mut file)?;
174    // ... column reading code ...
175    if reader.more_column_data() {
176        panic!();
177    }
178    ```
179     */
180    pub fn more_column_data(&self) -> bool {
181        (self.column_buffer.position() as usize) < self.column_buffer_size
182    }
183
184    /**
185    Returns [`true`] if there is more data in the row data section, or
186    [`false`] otherwise.
187
188    # Example
189    ```no_run
190    # use std::fs::File;
191    # use criware_utf_core::Reader;
192    let mut file = std::fs::File::open("random-table.bin")?;
193    let reader = criware_utf_core::Reader::new(&mut file)?;
194    // ... column reading code ...
195    while reader.more_row_data() {
196        // ... row reading code ...
197    }
198    ```
199     */
200    pub fn more_row_data(&self) -> bool {
201        (self.row_buffer.position() as usize) < self.row_buffer_size
202    }
203
204    fn read_constant_column_private<T: Value>(
205        &mut self,
206        name: &'static str,
207        optional: bool,
208    ) -> Result<Option<T>> {
209        let flag = self.read_primitive::<u8>(false)?;
210        let column_name = self.read_primitive::<str>(false)?;
211        if column_name != name {
212            return Err(Error::WrongColumnName(column_name, name));
213        }
214        let type_flag = flag & 0x0f;
215        let storage_flag = flag & 0xf0;
216        handle_type_flag!(type_flag => T::Primitive::TYPE_FLAG);
217        if storage_flag == 0x30 {
218            Ok(Some(self.read_value(false)?))
219        } else if optional && storage_flag == 0x10 {
220            Ok(None)
221        } else if is_valid_storage_flag(storage_flag) {
222            return Err(Error::WrongColumnStorage(storage_flag, "0x30"));
223        } else {
224            return Err(Error::InvalidColumnStorage(storage_flag));
225        }
226    }
227
228    /**
229    Attempts to read a constant column with the given name and type.
230
231    If the column matches, the column's value is returned. If the next column
232    stored does not match, an error is returned.
233
234    # Example
235    ```no_run
236    # use std::fs::File;
237    # use criware_utf_core::Reader;
238    # let mut file = std::fs::File::open("random-table.bin")?;
239    # let reader = criware_utf_core::Reader::new(&mut file)?;
240    let file_count: u64 = reader.read_constant_column("FileCount")?;
241    let version: String = reader.read_constant_column("Version")?;
242    ```
243     */
244    pub fn read_constant_column<T: Value>(&mut self, name: &'static str) -> Result<T> {
245        Ok(self.read_constant_column_private(name, false)?.unwrap())
246    }
247
248    /**
249    Attempts to read an optional constant column with the given name and type.
250
251    If the name and type of value of the next column stored does not match, this
252    function will return an error. The storage method of the column may be
253    constant or zero. If it's constant, the column's value is returned.
254
255    # Example
256    ```no_run
257    # use std::fs::File;
258    # use criware_utf_core::Reader;
259    # let mut file = std::fs::File::open("random-table.bin")?;
260    # let reader = criware_utf_core::Reader::new(&mut file)?;
261    let file_count: u64 = reader.read_constant_column("FileCount")?;
262    let version: String = reader.read_constant_column("Version")?;
263    let crc32: Option<u32> = reader.read_constant_column_opt::<u32>("Crc")?;
264    ```
265     */
266    pub fn read_constant_column_opt<T: Value>(&mut self, name: &'static str) -> Result<Option<T>> {
267        self.read_constant_column_private(name, true)
268    }
269
270    fn read_rowed_column_private(
271        &mut self,
272        name: &'static str,
273        kind: ValueKind,
274        optional: bool,
275    ) -> Result<bool> {
276        let flag = self.read_primitive::<u8>(false)?;
277        let column_name = self.read_primitive::<str>(false)?;
278        if column_name != name {
279            return Err(Error::WrongColumnName(column_name, name));
280        }
281        let type_flag = flag & 0x0f;
282        let storage_flag = flag & 0xf0;
283        handle_type_flag!(type_flag => kind);
284        if storage_flag == 0x50 {
285            Ok(true)
286        } else if optional && storage_flag == 0x10 {
287            Ok(false)
288        } else if is_valid_storage_flag(storage_flag) {
289            return Err(Error::WrongColumnStorage(storage_flag, "0x50"));
290        } else {
291            return Err(Error::InvalidColumnStorage(storage_flag));
292        }
293    }
294
295    /**
296    Attempts to read a rowed column with the given name and type.
297
298    If the next column stored does not match, an error is returned.
299
300    # Example
301    ```no_run
302    # use std::fs::File;
303    # use criware_utf_core::Reader;
304    # let mut file = std::fs::File::open("random-table.bin")?;
305    # let reader = criware_utf_core::Reader::new(&mut file)?;
306    reader.read_constant_column::<i32>("ID")?;
307    reader.read_constant_column::<String>("Name")?;
308    ```
309     */
310    pub fn read_rowed_column<T: Value>(&mut self, name: &'static str) -> Result<()> {
311        self.read_rowed_column_private(name, T::Primitive::TYPE_FLAG, false)?;
312        Ok(())
313    }
314
315    /**
316    Attempts to read an optional rowed column with the given name and type.
317
318    If the name and type of value of the next column stored does not match, this
319    function will return an error. The storage method of the column may be
320    rowed or zero. [`true`] denotes that the column is rowed, [`false`] denotes
321    the column is zero.
322
323    # Example
324    ```no_run
325    # use std::fs::File;
326    # use criware_utf_core::Reader;
327    # let mut file = std::fs::File::open("random-table.bin")?;
328    # let reader = criware_utf_core::Reader::new(&mut file)?;
329    let crc_included: bool = reader.read_rowed_column_opt::<u32>("Crc")?;
330    if crc_included {
331        println!("CRC32 checksums are included with each file!");
332    } else {
333        println!("No checksums found");
334    }
335    ```
336     */
337    pub fn read_rowed_column_opt<T: Value>(&mut self, name: &'static str) -> Result<bool> {
338        self.read_rowed_column_private(name, T::Primitive::TYPE_FLAG, true)
339    }
340
341    fn read_primitive<T: Primitive + ?Sized>(&mut self, row: bool) -> Result<T::Owned> {
342        let mut buffer: T::Buffer = Default::default();
343        let reader = if row {
344            &mut self.row_buffer
345        } else {
346            &mut self.column_buffer
347        };
348        match reader.read_exact(buffer.as_mut()) {
349            Ok(()) => (),
350            Err(error) => match error.kind() {
351                std::io::ErrorKind::UnexpectedEof => {
352                    return Err(Error::EOF(format!(
353                        "reading {} value",
354                        std::any::type_name::<T>()
355                    )));
356                }
357                _ => return Err(Error::IOError(error)),
358            },
359        };
360        match <T as Primitive>::parse(buffer, &self.strings, &self.blobs) {
361            Some(prim) => Ok(prim),
362            None => Err(Error::DataNotFound),
363        }
364    }
365
366    /**
367    Attempts to read a value from the column or row buffer.
368
369    # Example
370    ```no_run
371    # use std::fs::File;
372    # use criware_utf_core::Reader;
373    # let mut file = std::fs::File::open("random-table.bin")?;
374    # let reader = criware_utf_core::Reader::new(&mut file)?;
375    while reader.more_row_data() {
376        let name: String = reader.read_value(true)?;
377        let crc32: u32 = reader.read_value(true)?;
378        // ...
379    }
380    ```
381     */
382    pub fn read_value<T: Value>(&mut self, row: bool) -> Result<T> {
383        T::from_primitive(self.read_primitive::<T::Primitive>(row)?).map_err(|error| {
384            Error::ValueConversion(
385                std::any::type_name::<T::Primitive>(),
386                std::any::type_name::<T>(),
387                error,
388            )
389        })
390    }
391}