infinite_rs/module/file.rs
1//! Module file entry containing metadata relating to tags and functions to read them.
2
3use bitflags::bitflags;
4use byteorder::{LE, ReadBytesExt};
5use std::collections::HashMap;
6use std::fmt::Debug;
7use std::{
8 fs::File,
9 io::{BufReader, Cursor, Read, Seek, SeekFrom},
10};
11
12use super::header::ModuleVersion;
13use super::{block::ModuleBlockEntry, kraken::decompress};
14use crate::common::errors::{ModuleError, TagError};
15use crate::tag::datablock::TagDataBlock;
16use crate::tag::structure::TagStructType;
17use crate::{Error, Result};
18use crate::{common::extensions::BufReaderExt, tag::loader::TagFile};
19
20/// Trait for defining tag structures.
21///
22/// This trait is meant to be used with its derive macro, available in the `derive` feature.
23/// It allows the [`read_metadata<T>`](`ModuleFileEntry::read_metadata`) function to be called on a [`ModuleFileEntry`] to read the tag data.
24///
25/// Each struct that implements this trait should have the following attributes:
26/// - `#[data(size())]` - The size of the tag structure in bytes.
27///
28/// For each of its fields, the following attributes are required:
29/// - `#[data(offset())]` - The offset in bytes from the start of the tag structure.
30///
31/// Any padding between fields should be accounted for in the offset.
32///
33/// # Examples
34///
35/// ```rust
36/// use infinite_rs::module::file::ModuleFileEntry;
37/// use infinite_rs_derive::TagStructure;
38/// use infinite_rs::tag::types::common_types::AnyTag;
39/// use infinite_rs::module::file::TagStructure;
40///
41/// #[derive(Default, TagStructure)]
42/// #[data(size(0x30))]
43/// struct MaterialTag {
44/// #[data(offset(0x00))]
45/// any_tag: AnyTag,
46/// }
47///
48/// fn load_tag() {
49/// let mut file_entry = ModuleFileEntry::default(); // In actual module, use reference to file.
50/// let mut material = file_entry.read_metadata::<MaterialTag>().unwrap();
51///
52/// assert_eq!(material.size(), 0x30);
53/// assert_eq!(material.offsets().get("any_tag"), Some(&0x00));
54/// }
55pub trait TagStructure {
56 /// Returns the size of the tag structure in bytes.
57 /// Determined by the [data(size())] attribute.
58 fn size(&mut self) -> u64;
59 /// Function that calls all [`read`](`crate::common::extensions::Enumerable::read`) functions for each field in the tag structure.
60 fn read<R: BufReaderExt>(&mut self, reader: &mut R) -> Result<()>;
61 /// Returns a map of field names to their offsets in the tag structure.
62 fn offsets(&self) -> HashMap<&'static str, u64>;
63 /// Function that loads all field blocks for the tag structure, if any.
64 fn load_field_blocks<R: BufReaderExt>(
65 &mut self,
66 source_index: i32,
67 parent_index: usize,
68 adjusted_base: u64,
69 reader: &mut R,
70 tag_file: &TagFile,
71 ) -> Result<()>;
72}
73
74bitflags! {
75 #[derive(Debug, Default, PartialEq, Eq)]
76 /// Flags for the last 2 bytes of the data offset.
77 pub struct DataOffsetType : u16 {
78 /// No additional HD1 module is required.
79 const USE_SELF = 0;
80 /// Additional HD1 module is required.
81 const USE_HD1 = 1 << 0;
82 /// Indicates that this file is present in a Debug module.
83 const DEBUG = 1 << 1;
84 }
85}
86
87bitflags! {
88 #[derive(Debug, Default, PartialEq, Eq)]
89 /// Flags that determine how a tag should be read.
90 pub struct FileEntryFlags : u8 {
91 /// If tag is compressed or not.
92 const COMPRESSED = 1 << 0;
93 /// Indicates that tag is made up of "tag blocks" which need to be joined to assemble the
94 /// entire file entry.
95 const HAS_BLOCKS = 1 << 1;
96 /// "Raw tag" that does not contain a tag header.
97 const RAW_FILE = 1 << 2;
98 }
99}
100
101#[derive(Default, Debug)]
102/// Module file entry structure containing metadata relating to file and required buffer sizes and offsets for the decompressor, as well as global tag ID, resource references and class.
103pub struct ModuleFileEntry {
104 /// Unknown, some sort of size?
105 unknown: u8,
106 /// Determine how the file should be read.
107 pub flags: FileEntryFlags,
108 /// Number of blocks that make up the file.
109 block_count: u16,
110 /// Index of the first block in the module.
111 block_index: i32,
112 /// Index of the first resource in the module's resource list.
113 pub resource_index: i32,
114 /// 4 byte-long string for tag group, stored as big endian. This determines how the rest of the tag is read.
115 /// Example:
116 /// * `bitm`: Bitmap
117 /// * `mat `: Material
118 pub tag_group: String,
119 /// Offset of compressed/uncompressed data in from the start of compressed data in the module.
120 data_offset: u64,
121 /// Where the offset is located.
122 pub data_offset_flags: DataOffsetType,
123 /// Size in bytes of compressed buffer in module.
124 pub total_compressed_size: u32,
125 /// Size in bytes of buffer to decompress into.
126 pub total_uncompressed_size: u32,
127 /// `MurmurHash3_x86_64` 32 bit hash of tag path.
128 /// Referred to in-memory as "global tag id"
129 /// Is set to -1 if file is resource.
130 pub tag_id: i32,
131 /// Size in bytes of header in decompressed buffer.
132 pub uncompressed_header_size: u32,
133 /// Size in bytes of actual tag data in decompressed buffer.
134 pub uncompressed_tag_data_size: u32,
135 /// Size in bytes of resource data in decompressed buffer.
136 pub uncompressed_resource_data_size: u32,
137 /// Size in bytes of "external" resource data in decompressed buffer. (for instance, havok data or bitmaps)
138 pub uncompressed_actual_resource_size: u32,
139 /// Power of 2 to align the header buffer to (ex w. 4 = align to a multiple of 16 bytes).
140 header_alignment: u8,
141 /// Power of 2 to align the tag data buffer to.
142 tag_data_alignment: u8,
143 /// Power of 2 to align the resource data buffer to.
144 resource_data_alignment: u8,
145 /// Power of 2 to align the actual resource data buffer to.
146 actual_resource_data_alignment: u8,
147 /// Offset where the name of the file is located in the string table.
148 /// This is not read after [`ModuleVersion::Season3`].
149 pub(crate) name_offset: u32,
150 /// Used with resources to point back to the parent file. -1 = none
151 pub parent_index: i32,
152 /// `Murmur3_x64_128` hash of (what appears to be) the original file that this file was built from.
153 /// This is not always the same thing as the file stored in the module.
154 /// Only verified if the `HasBlocks` flag is not set.
155 pub asset_hash: i128,
156 /// Number of resources owned by the file.
157 pub resource_count: i32,
158 /// Data stream containing a buffer of bytes to read/seek.
159 pub data_stream: Option<BufReader<Cursor<Vec<u8>>>>,
160 /// The actual tag file read from the contents (including header), only valid if file is not a resource.
161 pub tag_info: Option<TagFile>,
162 /// Indicates if file is cached (has data stream) or not.
163 pub is_loaded: bool,
164 /// Name of the tag as specified in the module string list.
165 /// Set to tag id if module version does not support names.
166 pub tag_name: String,
167}
168
169impl ModuleFileEntry {
170 /// Reads module file entry data from a reader based on the module version.
171 ///
172 /// # Arguments
173 ///
174 /// * `reader` - A mutable reference to a reader implementing [`BufReaderExt`]
175 /// * `is_flight1` - Whether the module is a Flight1 module
176 ///
177 /// # Errors
178 /// - If the reader fails to read the structure [`ReadError`](`crate::Error::ReadError`)
179 pub(super) fn read<R: BufReaderExt>(&mut self, reader: &mut R, is_flight1: bool) -> Result<()> {
180 if is_flight1 {
181 self.name_offset = reader.read_u32::<LE>()?;
182 self.parent_index = reader.read_i32::<LE>()?;
183 self.resource_count = reader.read_u16::<LE>()?.into();
184 self.block_count = reader.read_u16::<LE>()?;
185 self.resource_index = reader.read_i32::<LE>()?;
186 self.block_index = reader.read_i32::<LE>()?;
187 } else {
188 self.unknown = reader.read_u8()?;
189 self.flags = FileEntryFlags::from_bits_truncate(reader.read_u8()?);
190 self.block_count = reader.read_u16::<LE>()?;
191 self.block_index = reader.read_i32::<LE>()?;
192 self.resource_index = reader.read_i32::<LE>()?;
193 }
194
195 self.tag_group = reader.read_fixed_string(4)?.chars().rev().collect(); // Reverse string
196 let data_offset = reader.read_u64::<LE>()?;
197 self.data_offset = data_offset & 0x0000_FFFF_FFFF_FFFF; // Mask first 6 bytes
198 self.data_offset_flags = DataOffsetType::from_bits_retain((data_offset >> 48) as u16); // Read last 2 bytes
199 self.total_compressed_size = reader.read_u32::<LE>()?;
200 self.total_uncompressed_size = reader.read_u32::<LE>()?;
201
202 if is_flight1 {
203 self.asset_hash = reader.read_i128::<LE>()?;
204 }
205
206 self.tag_id = reader.read_i32::<LE>()?;
207 self.uncompressed_header_size = reader.read_u32::<LE>()?;
208 self.uncompressed_tag_data_size = reader.read_u32::<LE>()?;
209 self.uncompressed_resource_data_size = reader.read_u32::<LE>()?;
210 self.uncompressed_actual_resource_size = reader.read_u32::<LE>()?;
211 self.header_alignment = reader.read_u8()?;
212 self.tag_data_alignment = reader.read_u8()?;
213 self.resource_data_alignment = reader.read_u8()?;
214 self.actual_resource_data_alignment = reader.read_u8()?;
215
216 if is_flight1 {
217 reader.seek_relative(1)?;
218 self.unknown = reader.read_u8()?;
219 self.flags = FileEntryFlags::from_bits_truncate(reader.read_u8()?);
220 reader.seek_relative(1)?;
221 } else {
222 self.name_offset = reader.read_u32::<LE>()?;
223 self.parent_index = reader.read_i32::<LE>()?;
224 self.asset_hash = reader.read_i128::<LE>()?;
225 self.resource_count = reader.read_i32::<LE>()?;
226 }
227 reader.seek_relative(4)?; // Skip some padding
228 Ok(())
229 }
230
231 /// Reads and loads tag data from a file.
232 ///
233 /// # Arguments
234 ///
235 /// * `reader` - A mutable reference to a [`BufReader<File>`] from which to read the data.
236 /// * `data_offset` - Starting offset in bytes of the data in the file.
237 /// * `blocks` - Metadata for data blocks.
238 /// * `module_version` - Version of the module being read
239 ///
240 /// # Errors
241 /// - If the reader fails to read [`ReadError`](`crate::Error::ReadError`)
242 /// - If any issues arise while reading non-raw tags: [`TagError`](`crate::common::errors::TagError`)
243 pub(super) fn read_tag(
244 &mut self,
245 reader: &mut BufReader<File>,
246 data_offset: u64,
247 blocks: &[ModuleBlockEntry],
248 module_version: &ModuleVersion,
249 uses_hd1: bool,
250 ) -> Result<()> {
251 if self.is_loaded {
252 return Ok(());
253 }
254 let file_offset = if uses_hd1 {
255 self.data_offset - data_offset
256 } else {
257 data_offset + self.data_offset
258 };
259 let mut data = vec![0u8; self.total_uncompressed_size as usize];
260
261 // Set position to start as we are already adding the file offset to it.
262 reader.rewind()?;
263
264 if self.block_count != 0 {
265 self.read_multiple_blocks(reader, blocks, file_offset, &mut data)?;
266 } else {
267 read_single_block(reader, self, file_offset, &mut data)?;
268 }
269 let data_stream = BufReader::new(Cursor::new(data));
270 self.data_stream = Some(data_stream);
271 if !self.flags.contains(FileEntryFlags::RAW_FILE) {
272 let mut tagfile = TagFile::default();
273 if let Some(ref mut stream) = self.data_stream {
274 if self.tag_group == "psod" {
275 // HACK: "psod" tags do not have string tables in any version.
276 tagfile.read(stream, &ModuleVersion::Season3)?;
277 } else {
278 tagfile.read(stream, module_version)?;
279 }
280 }
281 self.tag_info = Some(tagfile);
282 }
283
284 self.is_loaded = true;
285 Ok(())
286 }
287
288 /// Reads multiple blocks of data from the file.
289 ///
290 /// This function reads multiple blocks of data, which can be either compressed or uncompressed,
291 /// from the file and stores them in the provided data buffer.
292 ///
293 /// # Arguments
294 ///
295 /// * `reader` - A mutable reference to a [`BufReader<File>`] from which to read the data.
296 /// * `blocks` - A slice of [`ModuleBlockEntry`] containing metadata about each block.
297 /// * `file_offset` - The offset in the file where the data blocks start.
298 /// * `data` - A mutable slice where the (decompressed) data will be stored.
299 ///
300 /// # Errors
301 /// - If the reader fails to read the exact number of bytes [`ReadError`](`crate::Error::ReadError`)
302 /// - If the block index is negative [`ModuleError::NegativeBlockIndex`]
303 ///
304 /// # Safety
305 /// - This function has an unsafe component because it can call the [`read_compressed_block`] function, which uses [`decompress`] which is unsafe.
306 #[allow(clippy::cast_sign_loss)]
307 fn read_multiple_blocks(
308 &self,
309 reader: &mut BufReader<File>,
310 blocks: &[ModuleBlockEntry],
311 file_offset: u64,
312 data: &mut [u8],
313 ) -> Result<()> {
314 if self.block_index < 0 {
315 return Err(ModuleError::NegativeBlockIndex(self.block_index).into());
316 }
317 let first_block_index = self.block_index as usize;
318 reader.seek(SeekFrom::Start(file_offset))?;
319
320 let initial_block_offset = reader.stream_position()?;
321 for block in &blocks[first_block_index..(first_block_index + self.block_count as usize)] {
322 // even though blocks are sequential, we still should seek to the correct position.
323 reader.seek(SeekFrom::Start(
324 initial_block_offset + u64::from(block.compressed_offset),
325 ))?;
326 if block.is_compressed {
327 unsafe { read_compressed_block(reader, block, data)? };
328 } else {
329 read_uncompressed_block(reader, block, data)?;
330 }
331 }
332 Ok(())
333 }
334
335 /// Reads a specified structure implementing [`TagStructure`] from the tag data.
336 ///
337 /// This function exhausts the inner [`data_stream`](`ModuleFileEntry::data_stream`) buffer to read the contents of the specified
338 /// struct. It first looks for the main struct definition of the file, then gets the referenced
339 /// data block and creates a reader for it. The initial contents of the struct are read, and
340 /// field block definitions are loaded recursively.
341 ///
342 ///
343 /// # Generic Arguments
344 ///
345 /// * `T` - The type of the struct implementing [`TagStructure`] to read the data into.
346 ///
347 /// # Errors
348 /// - If the tag data is not loaded [`TagError::NotLoaded`]
349 /// - If the tag info is not present [`TagError::NoTagInfo`]
350 /// - If the main struct definition is not found [`TagError::MainStructNotFound`]
351 /// - If the reader fails to read the exact number of bytes [`ReadError`](`crate::Error::ReadError`)
352 pub fn read_metadata<T: Default + TagStructure>(&mut self) -> Result<T> {
353 let mut struct_type = T::default();
354 let mut full_tag = Vec::with_capacity(
355 self.total_uncompressed_size as usize - self.uncompressed_header_size as usize,
356 );
357 self.data_stream
358 .as_mut()
359 .ok_or(TagError::NotLoaded)?
360 .read_to_end(&mut full_tag)?;
361
362 let tag_info = self.tag_info.as_ref().ok_or(TagError::NoTagInfo)?;
363
364 let main_struct = tag_info
365 .struct_definitions
366 .iter()
367 .find(|s| s.struct_type == TagStructType::MainStruct)
368 .ok_or(TagError::MainStructNotFound)?;
369
370 #[allow(clippy::cast_sign_loss)]
371 let main_block: &TagDataBlock =
372 &tag_info.datablock_definitions[main_struct.target_index as usize];
373 let full_tag_buffer = &full_tag[0..];
374 let mut full_tag_reader = BufReader::new(Cursor::new(full_tag_buffer));
375 full_tag_reader.seek(SeekFrom::Current(i64::try_from(main_block.offset)?))?;
376 struct_type.read(&mut full_tag_reader)?;
377 struct_type.load_field_blocks(
378 main_struct.target_index,
379 0,
380 0,
381 &mut full_tag_reader,
382 tag_info,
383 )?;
384 Ok(struct_type)
385 }
386
387 /// Reads data from internal buffer into a [`Vec<u8>`].
388 ///
389 /// # Arguments
390 /// - `include_header`: Whether to also include header info making up [`TagFile`]
391 ///
392 /// # Errors
393 /// - If the tag data is not loaded [`TagError::NotLoaded`]
394 /// - If the reader fails to read [`ReadError`](`crate::Error::ReadError`)
395 pub fn get_raw_data(&mut self, include_header: bool) -> Result<Vec<u8>> {
396 if let Some(ref mut data_stream) = self.data_stream {
397 let mut size = self.total_uncompressed_size as usize;
398 if include_header {
399 data_stream.rewind()?;
400 } else {
401 data_stream.seek(SeekFrom::Start(u64::from(self.uncompressed_header_size)))?;
402 size -= self.uncompressed_header_size as usize;
403 }
404 let mut buffer = Vec::with_capacity(size);
405 data_stream.read_to_end(&mut buffer)?;
406 Ok(buffer)
407 } else {
408 Err(Error::TagError(TagError::NotLoaded))
409 }
410 }
411}
412
413/// Reads an uncompressed block of data from the file.
414///
415/// This function reads an uncompressed block directly from the file and copies it
416/// into the appropriate section of the output buffer.
417///
418/// # Arguments
419///
420/// * `reader` - A mutable reference to a [`BufReader<File>`] from which to read the data.
421/// * `block` - A reference to the [`ModuleBlockEntry`] containing metadata about the block.
422/// * `data` - A mutable slice where the uncompressed data will be stored.
423///
424/// # Errors
425/// - If the reader fails to read the exact number of bytes [`ReadError`](`crate::Error::ReadError`)
426fn read_uncompressed_block(
427 reader: &mut BufReader<File>,
428 block: &ModuleBlockEntry,
429 data: &mut [u8],
430) -> Result<()> {
431 reader.read_exact(
432 &mut data[block.decompressed_offset as usize
433 ..(block.decompressed_offset + block.compressed_size) as usize],
434 )?;
435 Ok(())
436}
437
438/// Reads and decompresses a compressed block of data.
439///
440/// This function reads a compressed block from the file, decompresses it,
441/// and then copies the decompressed data into the appropriate section of the output buffer.
442///
443/// # Arguments
444///
445/// * `reader` - A mutable reference to a [`BufReader<File>`] from which to read the data.
446/// * `block` - A reference to the [`ModuleBlockEntry`] containing metadata about the block.
447/// * `data` - A mutable slice where the decompressed data will be stored.
448///
449/// # Errors
450/// - If the reader fails to read the exact number of bytes [`ReadError`](`crate::Error::ReadError`)
451/// - If the decompression operation fails [`Error::DecompressionError`]
452///
453/// # Safety
454/// - This function is unsafe because it calls the [`decompress`] function, which is unsafe.
455unsafe fn read_compressed_block(
456 reader: &mut BufReader<File>,
457 block: &ModuleBlockEntry,
458 data: &mut [u8],
459) -> Result<()> {
460 unsafe {
461 let mut compressed_data = vec![0u8; block.compressed_size as usize];
462 reader.read_exact(&mut compressed_data)?;
463 let mut decompressed_data = vec![0u8; block.decompressed_size as usize];
464 decompress(
465 &compressed_data,
466 &mut decompressed_data,
467 block.decompressed_size as usize,
468 )?;
469 data[block.decompressed_offset as usize
470 ..(block.decompressed_offset + block.decompressed_size) as usize]
471 .copy_from_slice(&decompressed_data);
472 Ok(())
473 }
474}
475
476/// Reads a single block of data from the file.
477///
478/// This function is used when the file entry contains only one block of data.
479/// It reads the entire block, and then either copies it directly to the output
480/// if it's not compressed, or decompresses it if necessary.
481///
482/// # Arguments
483///
484/// * `reader` - A mutable reference to a [`BufReader<File>`] from which to read the data.
485/// * `file_entry` - A reference to the [`ModuleFileEntry`] containing metadata about the file.
486/// * `file_offset` - The offset in the file where the data block starts.
487/// * `data` - A mutable reference to the [`Vec<u8>`] where the (decompressed) data will be stored.
488///
489/// # Errors
490/// - If the reader fails to read the exact number of bytes [`ReadError`](`crate::Error::ReadError`)
491/// - If the decompression operation fails [`Error::DecompressionError`]
492///
493/// # Safety
494/// - This function can be unsafe because it may call the [`decompress`] function, which is unsafe.
495fn read_single_block(
496 reader: &mut BufReader<File>,
497 file_entry: &ModuleFileEntry,
498 file_offset: u64,
499 data: &mut Vec<u8>,
500) -> Result<()> {
501 reader.seek(SeekFrom::Start(file_offset))?;
502 let compressed_size = file_entry.total_compressed_size as usize;
503 let mut block = vec![0u8; compressed_size];
504 reader.read_exact(&mut block)?;
505
506 if compressed_size == file_entry.total_uncompressed_size as usize {
507 data.copy_from_slice(&block);
508 } else {
509 unsafe { decompress(&block, data, file_entry.total_uncompressed_size as usize)? };
510 }
511 Ok(())
512}