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}