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
//! Main abstraction file for modules.
use super::{block::ModuleBlockEntry, file::ModuleFileEntry, header::ModuleHeader};
use anyhow::Result;
use byteorder::{ReadBytesExt, LE};
use std::{
fs::File,
io::{BufReader, Read, Seek, SeekFrom},
path::Path,
};
#[derive(Default, Debug)]
/// Module structure which contains the layout of the entire module file. Also stores file_path for re-use in read_tag.
pub struct ModuleFile {
/// Info relating to how the other fields should be read.
pub header: ModuleHeader,
/// Metadata regarding compression and layout of files (tags)
pub files: Vec<ModuleFileEntry>,
/// String slice of file names seperated by a null terminator.
/// Does not exist after module version 52.
pub string_list: Vec<u8>,
/// Indices (?) of resource files present in module.
pub resources: Vec<u32>,
/// Uncompressed/compressed blocks making up a file.
pub blocks: Vec<ModuleBlockEntry>,
/// Offset in BufReader where file data starts.
pub file_data_offset: u64,
/// Path stored to be re-used when reading tags.
file_path: String,
}
impl ModuleFile {
/// Allocate new ModuleFile and set it to default values.
pub fn new() -> Self {
Self::default()
}
/// Reads the module file from the given file path.
/// This function reads the entire structure of the module file.
/// It also calculates and stores important offsets within the file.
///
/// # Arguments
///
/// * `file_path` - A string slice that holds the path to the module file
///
/// # Returns
///
/// Returns `Ok(())` if the read operation is successful, or an `Err` containing
/// the I/O error if any reading operation fails/header checks fail.
pub fn read(&mut self, file_path: String) -> Result<()> {
let file = File::open(Path::new(&file_path))?;
self.file_path = file_path;
let mut reader = BufReader::new(file);
self.header.read(&mut reader)?;
self.files = (0..self.header.file_count)
.map(|_| {
let mut entry = ModuleFileEntry::new();
entry.read(&mut reader).unwrap();
entry
})
.collect();
if self.header.strings_size != 0 {
self.string_list = Vec::with_capacity(self.header.strings_size as usize);
reader.read_exact(&mut self.string_list)?;
}
self.resources = (0..self.header.resource_count)
.map(|_| reader.read_u32::<LE>().unwrap())
.collect();
self.blocks = (0..self.header.block_count)
.map(|_| {
let mut block = ModuleBlockEntry::new();
block.read(&mut reader).unwrap();
block
})
.collect();
// Align to 0x?????000
let stream_position = reader.stream_position()?;
reader.seek(SeekFrom::Start((stream_position / 0x1000 + 1) * 0x1000))?;
self.file_data_offset = reader.stream_position()?;
Ok(())
}
/// Reads a specific tag from the module file.
///
/// # Arguments
///
/// * `index` - The index of the file entry to read the tag from
///
/// # Returns
///
/// Returns `Ok(())` if the read operation is successful, or an `(anyhow) Error` containing
/// the I/O error if any reading operation fails.
pub fn read_tag(&mut self, index: usize) -> Result<()> {
// -1 indicates a resource tag.
if self.files[index].tag_id != -1 {
self.files[index].read_tag(&self.file_path, self.file_data_offset, &self.blocks)?;
}
Ok(())
}
/// Searches for the index of the tag given the global_id.
/// Reads the tag using read_tag and stores it in the index.
///
/// # Arguments
///
/// * `global_id` - The global tag ID of the file to find.
///
/// # Returns
///
/// Returns the index of the file if successful, 0 if it fails. Any I/O Error is also returned if it occurs.
pub fn read_tag_from_id(&mut self, global_id: i32) -> Result<Option<usize>> {
if let Some(index) = self.files.iter().position(|file| file.tag_id == global_id) {
self.read_tag(index)?;
Ok(Some(index))
} else {
Ok(None)
}
}
}