infinite_rs/common/
extensions.rs

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
185
186
187
188
189
//! Extensions to [`BufRead`] for reading fixed-length strings and enumerable types.
//!
//! This module provides two main extensions to the standard [`BufRead`]:
//!
//! * [`read_fixed_string`](`BufReaderExt::read_fixed_string`): Reads a fixed number of bytes and converts them to a UTF-8 string.
//!   Special handling is included for sequences of `0xFF` bytes which are treated as empty strings.
//!
//! * [`read_enumerable`](`BufReaderExt::read_enumerable`): Generic method for reading a sequence of items that implement the
//!   [`Enumerable`] trait. Reads the specified type `count` times and collects the results into a [`Vec`].
//!
//! These extensions are implemented as traits and require the reader to implement both
//! [`Read`] and [`Seek`] traits.
//!

use std::io::{BufRead, BufReader, Read, Seek};

use crate::Result;

/// Trait for types that can be read sequentially from a buffered reader.
///
/// Types implementing this trait can be read using the [`read_enumerable`](`BufReaderExt::read_enumerable`) method
/// from [`BufReaderExt`].
pub trait Enumerable {
    /// Reads data from the given reader and updates the implementing type.
    ///
    /// # Arguments
    ///
    /// * `reader` - A mutable reference to any type that implements `BufReaderExt`
    ///
    /// # Returns
    ///
    /// * `Ok(())` - If the read operation was successful
    /// * `Err(Error)` - If an error occurred during reading
    fn read<R: BufReaderExt>(&mut self, reader: &mut R) -> Result<()>;
}

/// Extension trait for [`BufRead`] to add custom reading methods.
pub trait BufReaderExt: BufRead + Seek {
    /// Reads a fixed-length UTF-8 encoded string from the reader.
    ///
    /// This function reads exactly `length` bytes and converts them to a String.
    /// If the bytes read are all 0xFF, an empty string is returned.
    ///
    /// # Arguments
    ///
    /// * `length` - The exact number of bytes to read
    ///
    /// # Returns
    ///
    /// * `Ok(String)` - The UTF-8 string read from the buffer
    /// * `Err(Error)` - If reading fails or the bytes are not valid UTF-8
    ///
    /// # Examples
    ///
    /// ```
    /// use std::io::Cursor;
    /// use std::io::BufReader;
    /// use infinite_rs::common::extensions::BufReaderExt;
    ///
    /// let data = b"I love cats!";
    /// let mut reader = BufReader::new(Cursor::new(data));
    /// let string = reader.read_fixed_string(data.len()).unwrap();
    /// assert_eq!(string, "I love cats!");
    /// ```
    fn read_fixed_string(&mut self, length: usize) -> Result<String> {
        let mut buffer = vec![0; length];
        self.read_exact(&mut buffer)?;

        if buffer == [255, 255, 255, 255] {
            return Ok(String::new()); // Return empty string if all bytes are 0xFF
        }

        let string = String::from_utf8(buffer)?;

        Ok(string)
    }

    /// Reads a null-terminated string from the reader.
    ///
    /// This function reads bytes in a reader until it hits `0x00` and converts them to a String.
    /// The null terminator is removed from the final output.
    ///
    /// # Returns
    ///
    /// * `Ok(String)` - The UTF-8 string read from the buffer
    /// * `Err(Error)` - If reading fails or the bytes are not valid UTF-8
    ///
    /// # Examples
    ///
    /// ```
    /// use std::io::Cursor;
    /// use std::io::BufReader;
    /// use infinite_rs::common::extensions::BufReaderExt;
    ///
    /// let data = [0x49, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x20, 0x63, 0x61, 0x74, 0x73, 0x21, 0x00];
    /// let mut reader = BufReader::new(Cursor::new(data));
    /// let string = reader.read_null_terminated_string().unwrap();
    /// assert_eq!(string, "I love cats!");
    /// ```
    fn read_null_terminated_string(&mut self) -> Result<String> {
        let mut buffer = Vec::with_capacity(150); // Pre-allocate around 150 bytes (typical
                                                  // filename size)
        self.read_until(0x00, &mut buffer)?;
        buffer.pop(); // remove null terminator

        let string = String::from_utf8(buffer)?;

        Ok(string)
    }

    /// Reads multiple instances of an enumerable type into a vector.
    ///
    /// Creates a vector of type T by reading the type `count` times from the buffer.
    /// Type T must implement both [`Default`] and [`Enumerable`]    traits.
    ///
    /// # Type Parameters
    ///
    /// * `T` - The type to read, must implement `Default + Enumerable`
    ///
    /// # Arguments
    ///
    /// * `count` - Number of instances to read
    ///
    /// # Returns
    ///
    /// * `Ok(Vec<T>)` - Vector containing the read instances
    /// * `Err(Error)` - If any read operation fails
    ///
    /// # Examples
    ///
    /// ```
    /// use std::io::{Cursor, BufReader,};
    /// use infinite_rs::common::extensions::{BufReaderExt, Enumerable};
    /// use infinite_rs::common::errors::Error;
    /// use byteorder::{ReadBytesExt, LE};
    ///
    /// #[derive(Default)]
    /// struct TestType {
    ///     value: u32,
    /// }
    ///
    /// impl Enumerable for TestType {
    ///     fn read<R: BufReaderExt>(&mut self, reader: &mut R) -> Result<(), Error> {
    ///         self.value = reader.read_u32::<LE>()?;
    ///         Ok(())
    ///     }
    /// }
    ///
    /// let data = b"\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00";
    /// let mut reader = BufReader::new(Cursor::new(data));
    /// let enumerables = reader.read_enumerable::<TestType>(3).unwrap();
    /// assert_eq!(enumerables.len(), 3);
    /// assert_eq!(enumerables[0].value, 1);
    /// assert_eq!(enumerables[1].value, 2);
    /// assert_eq!(enumerables[2].value, 3);
    /// ```
    fn read_enumerable<T: Default + Enumerable>(&mut self, count: u64) -> Result<Vec<T>>
    where
        Self: Sized,
        Vec<T>: FromIterator<T>,
    {
        let mut enumerables = vec![];
        enumerables.reserve_exact(usize::try_from(count)? + 1);
        for _ in 0..count {
            let mut enumerable = T::default();
            enumerable.read(self)?;
            enumerables.push(enumerable);
        }
        Ok(enumerables)
    }
}

impl<R: Read + Seek> BufReaderExt for BufReader<R> {}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::Cursor;

    #[test]
    /// Verifies that reading 0xFFFFFFFF returns an empty string, which is used
    /// to handle empty `tag_group` entries in module files.
    fn test_read_fixed_string_empty() {
        let data = [255, 255, 255, 255];
        let mut reader = BufReader::new(Cursor::new(&data));
        let string = reader.read_fixed_string(data.len()).unwrap();
        assert_eq!(string, "");
    }
}