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, "");
}
}