use bitflags::bitflags;
use byteorder::{LE, ReadBytesExt};
use std::collections::HashMap;
use std::fmt::Debug;
use std::{
fs::File,
io::{BufReader, Cursor, Read, Seek, SeekFrom},
};
use super::header::ModuleVersion;
use super::{block::ModuleBlockEntry, kraken::decompress};
use crate::common::errors::{ModuleError, TagError};
use crate::tag::datablock::TagDataBlock;
use crate::tag::structure::TagStructType;
use crate::{Error, Result};
use crate::{common::extensions::BufReaderExt, tag::loader::TagFile};
pub trait TagStructure {
fn size(&mut self) -> u64;
fn read<R: BufReaderExt>(&mut self, reader: &mut R) -> Result<()>;
fn offsets(&self) -> HashMap<&'static str, u64>;
fn load_field_blocks<R: BufReaderExt>(
&mut self,
source_index: i32,
parent_index: usize,
adjusted_base: u64,
reader: &mut R,
tag_file: &TagFile,
) -> Result<()>;
}
bitflags! {
#[derive(Debug, Default, PartialEq, Eq)]
pub struct DataOffsetType : u16 {
const USE_SELF = 0;
const USE_HD1 = 1 << 0;
const DEBUG = 1 << 1;
}
}
bitflags! {
#[derive(Debug, Default, PartialEq, Eq)]
pub struct FileEntryFlags : u8 {
const COMPRESSED = 1 << 0;
const HAS_BLOCKS = 1 << 1;
const RAW_FILE = 1 << 2;
}
}
#[derive(Default, Debug)]
pub struct ModuleFileEntry {
unknown: u8,
pub flags: FileEntryFlags,
block_count: u16,
block_index: i32,
pub resource_index: i32,
pub tag_group: String,
data_offset: u64,
pub data_offset_flags: DataOffsetType,
pub total_compressed_size: u32,
pub total_uncompressed_size: u32,
pub tag_id: i32,
pub uncompressed_header_size: u32,
pub uncompressed_tag_data_size: u32,
pub uncompressed_resource_data_size: u32,
pub uncompressed_actual_resource_size: u32,
header_alignment: u8,
tag_data_alignment: u8,
resource_data_alignment: u8,
actual_resource_data_alignment: u8,
pub(crate) name_offset: u32,
pub parent_index: i32,
pub asset_hash: i128,
pub resource_count: i32,
pub data_stream: Option<BufReader<Cursor<Vec<u8>>>>,
pub tag_info: Option<TagFile>,
pub is_loaded: bool,
pub tag_name: String,
}
impl ModuleFileEntry {
pub(super) fn read<R: BufReaderExt>(&mut self, reader: &mut R, is_flight1: bool) -> Result<()> {
if is_flight1 {
self.name_offset = reader.read_u32::<LE>()?;
self.parent_index = reader.read_i32::<LE>()?;
self.resource_count = reader.read_u16::<LE>()?.into();
self.block_count = reader.read_u16::<LE>()?;
self.resource_index = reader.read_i32::<LE>()?;
self.block_index = reader.read_i32::<LE>()?;
} else {
self.unknown = reader.read_u8()?;
self.flags = FileEntryFlags::from_bits_truncate(reader.read_u8()?);
self.block_count = reader.read_u16::<LE>()?;
self.block_index = reader.read_i32::<LE>()?;
self.resource_index = reader.read_i32::<LE>()?;
}
self.tag_group = reader.read_fixed_string(4)?.chars().rev().collect(); let data_offset = reader.read_u64::<LE>()?;
self.data_offset = data_offset & 0x0000_FFFF_FFFF_FFFF; self.data_offset_flags = DataOffsetType::from_bits_retain((data_offset >> 48) as u16); self.total_compressed_size = reader.read_u32::<LE>()?;
self.total_uncompressed_size = reader.read_u32::<LE>()?;
if is_flight1 {
self.asset_hash = reader.read_i128::<LE>()?;
}
self.tag_id = reader.read_i32::<LE>()?;
self.uncompressed_header_size = reader.read_u32::<LE>()?;
self.uncompressed_tag_data_size = reader.read_u32::<LE>()?;
self.uncompressed_resource_data_size = reader.read_u32::<LE>()?;
self.uncompressed_actual_resource_size = reader.read_u32::<LE>()?;
self.header_alignment = reader.read_u8()?;
self.tag_data_alignment = reader.read_u8()?;
self.resource_data_alignment = reader.read_u8()?;
self.actual_resource_data_alignment = reader.read_u8()?;
if is_flight1 {
reader.seek_relative(1)?;
self.unknown = reader.read_u8()?;
self.flags = FileEntryFlags::from_bits_truncate(reader.read_u8()?);
reader.seek_relative(1)?;
} else {
self.name_offset = reader.read_u32::<LE>()?;
self.parent_index = reader.read_i32::<LE>()?;
self.asset_hash = reader.read_i128::<LE>()?;
self.resource_count = reader.read_i32::<LE>()?;
}
reader.seek_relative(4)?; Ok(())
}
pub(super) fn read_tag(
&mut self,
reader: &mut BufReader<File>,
data_offset: u64,
blocks: &[ModuleBlockEntry],
module_version: &ModuleVersion,
uses_hd1: bool,
) -> Result<()> {
if self.is_loaded {
return Ok(());
}
let file_offset = if uses_hd1 {
self.data_offset - data_offset
} else {
data_offset + self.data_offset
};
let mut data = vec![0u8; self.total_uncompressed_size as usize];
reader.rewind()?;
if self.block_count != 0 {
self.read_multiple_blocks(reader, blocks, file_offset, &mut data)?;
} else {
read_single_block(reader, self, file_offset, &mut data)?;
}
let data_stream = BufReader::new(Cursor::new(data));
self.data_stream = Some(data_stream);
if !self.flags.contains(FileEntryFlags::RAW_FILE) {
let mut tagfile = TagFile::default();
if let Some(ref mut stream) = self.data_stream {
if self.tag_group == "psod" {
tagfile.read(stream, &ModuleVersion::Season3)?;
} else {
tagfile.read(stream, module_version)?;
}
}
self.tag_info = Some(tagfile);
}
self.is_loaded = true;
Ok(())
}
#[allow(clippy::cast_sign_loss)]
fn read_multiple_blocks(
&self,
reader: &mut BufReader<File>,
blocks: &[ModuleBlockEntry],
file_offset: u64,
data: &mut [u8],
) -> Result<()> {
if self.block_index < 0 {
return Err(ModuleError::NegativeBlockIndex(self.block_index).into());
}
let first_block_index = self.block_index as usize;
reader.seek(SeekFrom::Start(file_offset))?;
let initial_block_offset = reader.stream_position()?;
for block in &blocks[first_block_index..(first_block_index + self.block_count as usize)] {
reader.seek(SeekFrom::Start(
initial_block_offset + u64::from(block.compressed_offset),
))?;
if block.is_compressed {
unsafe { read_compressed_block(reader, block, data)? };
} else {
read_uncompressed_block(reader, block, data)?;
}
}
Ok(())
}
pub fn read_metadata<T: Default + TagStructure>(&mut self) -> Result<T> {
let mut struct_type = T::default();
let mut full_tag = Vec::with_capacity(
self.total_uncompressed_size as usize - self.uncompressed_header_size as usize,
);
self.data_stream
.as_mut()
.ok_or(TagError::NotLoaded)?
.read_to_end(&mut full_tag)?;
let tag_info = self.tag_info.as_ref().ok_or(TagError::NoTagInfo)?;
let main_struct = tag_info
.struct_definitions
.iter()
.find(|s| s.struct_type == TagStructType::MainStruct)
.ok_or(TagError::MainStructNotFound)?;
#[allow(clippy::cast_sign_loss)]
let main_block: &TagDataBlock =
&tag_info.datablock_definitions[main_struct.target_index as usize];
let full_tag_buffer = &full_tag[0..];
let mut full_tag_reader = BufReader::new(Cursor::new(full_tag_buffer));
full_tag_reader.seek(SeekFrom::Current(i64::try_from(main_block.offset)?))?;
struct_type.read(&mut full_tag_reader)?;
struct_type.load_field_blocks(
main_struct.target_index,
0,
0,
&mut full_tag_reader,
tag_info,
)?;
Ok(struct_type)
}
pub fn get_raw_data(&mut self, include_header: bool) -> Result<Vec<u8>> {
if let Some(ref mut data_stream) = self.data_stream {
let mut size = self.total_uncompressed_size as usize;
if include_header {
data_stream.rewind()?;
} else {
data_stream.seek(SeekFrom::Start(u64::from(self.uncompressed_header_size)))?;
size -= self.uncompressed_header_size as usize;
}
let mut buffer = Vec::with_capacity(size);
data_stream.read_to_end(&mut buffer)?;
Ok(buffer)
} else {
Err(Error::TagError(TagError::NotLoaded))
}
}
}
fn read_uncompressed_block(
reader: &mut BufReader<File>,
block: &ModuleBlockEntry,
data: &mut [u8],
) -> Result<()> {
reader.read_exact(
&mut data[block.decompressed_offset as usize
..(block.decompressed_offset + block.compressed_size) as usize],
)?;
Ok(())
}
unsafe fn read_compressed_block(
reader: &mut BufReader<File>,
block: &ModuleBlockEntry,
data: &mut [u8],
) -> Result<()> {
unsafe {
let mut compressed_data = vec![0u8; block.compressed_size as usize];
reader.read_exact(&mut compressed_data)?;
let mut decompressed_data = vec![0u8; block.decompressed_size as usize];
decompress(
&compressed_data,
&mut decompressed_data,
block.decompressed_size as usize,
)?;
data[block.decompressed_offset as usize
..(block.decompressed_offset + block.decompressed_size) as usize]
.copy_from_slice(&decompressed_data);
Ok(())
}
}
fn read_single_block(
reader: &mut BufReader<File>,
file_entry: &ModuleFileEntry,
file_offset: u64,
data: &mut Vec<u8>,
) -> Result<()> {
reader.seek(SeekFrom::Start(file_offset))?;
let compressed_size = file_entry.total_compressed_size as usize;
let mut block = vec![0u8; compressed_size];
reader.read_exact(&mut block)?;
if compressed_size == file_entry.total_uncompressed_size as usize {
data.copy_from_slice(&block);
} else {
unsafe { decompress(&block, data, file_entry.total_uncompressed_size as usize)? };
}
Ok(())
}