diff --git a/bff/src/bigfile/mod.rs b/bff/src/bigfile/mod.rs index a635022..d852240 100644 --- a/bff/src/bigfile/mod.rs +++ b/bff/src/bigfile/mod.rs @@ -6,6 +6,7 @@ mod v1_2000_77_18_pc; mod v1_2002_45_19_pc; mod v1_22_pc; mod v2_07_pc; +mod v2_0_pc; mod v2_128_52_19_pc; mod v2_128_92_19_pc; mod v2_256_38_19_pc; @@ -26,7 +27,8 @@ use crate::bigfile::v1_22_pc::{ BigFileV1_22PCNoVersionTriple, BigFileV1_22PCNoVersionTripleBlackSheep, }; -use crate::bigfile::v2_07_pc::{BigFileV2_07PCMQFEL, BigFileV2_07PCPROTO, BigFileV2_07PCSHAUN}; +use crate::bigfile::v2_07_pc::{BigFileV2_07PCPROTO, BigFileV2_07PCSHAUN}; +use crate::bigfile::v2_0_pc::BigFileV2_0PC; use crate::bigfile::v2_128_52_19_pc::BigFileV2_128_52_19PC; use crate::bigfile::v2_128_92_19_pc::BigFileV2_128_92_19PC; use crate::bigfile::v2_256_38_19_pc::BigFileV2_256_38_19PC; @@ -47,7 +49,7 @@ bigfiles! { (Kalisto(1, _), _) => BigFileV1_22PCNoVersionTriple, (BlackSheep(2, ..=7) | BlackSheep(2, 158..), _) => BigFileV2_07PCPROTO, (BlackSheep(2, _), _) => BigFileV2_07PCSHAUN, - (Ubisoft { .. }, _) => BigFileV2_07PCMQFEL, + (Ubisoft { .. }, _) => BigFileV2_0PC, (AsoboLegacy(1, ..=80), _) => BigFileV1_22PC, (AsoboLegacy(1, _) | Asobo(1, 1..=5 | 8, _, _), _) => BigFileV1_08_40_02PC, (Asobo(1, 1..=1999, _, _), _) => BigFileV1_06_63_02PC, diff --git a/bff/src/bigfile/v1_22_pc/mod.rs b/bff/src/bigfile/v1_22_pc/mod.rs index 93f2597..8051f02 100644 --- a/bff/src/bigfile/v1_22_pc/mod.rs +++ b/bff/src/bigfile/v1_22_pc/mod.rs @@ -17,17 +17,17 @@ use crate::BffResult; #[binrw] #[derive(Debug)] -pub struct Resource { +pub struct Resource { #[br(temp)] - #[bw(calc = data.len() as u32 + 12)] + #[bw(calc = data.len() as u32 + S)] data_size: u32, class_name: Name, pub name: Name, - #[br(count = data_size - 12)] + #[br(count = data_size - S)] data: Vec, } -impl Resource { +impl Resource { pub fn dump_resource( resource: &crate::bigfile::resource::Resource, writer: &mut W, @@ -61,8 +61,8 @@ impl Resource { } } -impl From for crate::bigfile::resource::Resource { - fn from(resource: Resource) -> crate::bigfile::resource::Resource { +impl From> for crate::bigfile::resource::Resource { + fn from(resource: Resource) -> crate::bigfile::resource::Resource { crate::bigfile::resource::Resource { class_name: resource.class_name, name: resource.name, @@ -216,7 +216,7 @@ impl BigFileIo for resource in block.objects.iter() { let resource = bigfile.objects.get(&resource.name).unwrap(); - Resource::dump_resource(resource, writer, endian)?; + Resource::<12>::dump_resource(resource, writer, endian)?; } write_align_to(writer, 0x20000, 0xCD)?; diff --git a/bff/src/bigfile/v2_07_pc/mod.rs b/bff/src/bigfile/v2_07_pc/mod.rs index a469b9e..f339e58 100644 --- a/bff/src/bigfile/v2_07_pc/mod.rs +++ b/bff/src/bigfile/v2_07_pc/mod.rs @@ -10,7 +10,7 @@ use crate::bigfile::BigFile; use crate::helpers::{calculated_padded, read_align_to, write_align_to, DynArray}; use crate::lz::{lzo_compress, lzo_decompress}; use crate::names::NameType; -use crate::names::NameType::{BlackSheep32, Ubisoft64}; +use crate::names::NameType::BlackSheep32; use crate::platforms::Platform; use crate::traits::BigFileIo; use crate::versions::Version; @@ -39,7 +39,6 @@ impl BinWrite for Block { const SHAUN_PROTO: usize = 0; const SHAUN: usize = 1; -const MQFEL: usize = 2; #[parser(reader, endian)] fn parse_blocks( @@ -51,11 +50,6 @@ fn parse_blocks( for block_size in block_sizes { let block_start = reader.stream_position()?; - let checksum = if GAME == MQFEL { - Some(u32::read_options(reader, endian, ())?) - } else { - None - }; let resource_count = u32::read_options(reader, endian, ())?; if *block_size != decompressed_block_size { @@ -65,7 +59,6 @@ fn parse_blocks( - match GAME { SHAUN_PROTO => 0, SHAUN => 4, - MQFEL => 8, _ => unreachable!(), }) as usize ]; @@ -75,7 +68,7 @@ fn parse_blocks( let mut decompressed = Cursor::new(decompressed); blocks.push(Block { compressed: true, - checksum, + checksum: None, resources: Vec::::read_options( &mut decompressed, endian, @@ -86,7 +79,7 @@ fn parse_blocks( } else { blocks.push(Block { compressed: false, - checksum, + checksum: None, resources: Vec::::read_options( reader, endian, @@ -128,7 +121,6 @@ pub struct BigFileV2_07PC { blocks: Vec, } -pub type BigFileV2_07PCMQFEL = BigFileV2_07PC; pub type BigFileV2_07PCSHAUN = BigFileV2_07PC; pub type BigFileV2_07PCPROTO = BigFileV2_07PC; @@ -203,7 +195,7 @@ impl BigFileIo for BigFileV2_07PC { for resource in block.objects.iter() { let resource = bigfile.objects.get(&resource.name).unwrap(); - Resource::dump_resource(resource, &mut block_writer, endian)?; + Resource::<12>::dump_resource(resource, &mut block_writer, endian)?; } let block_data = block_writer.into_inner(); @@ -222,13 +214,9 @@ impl BigFileIo for BigFileV2_07PC { let mut block_sizes = Vec::new(); let mut compression_type = CompressionType::None; - for (resource_count, checksum, compressed, mut block_data) in blocks { + for (resource_count, _, compressed, mut block_data) in blocks { let block_begin = writer.stream_position()?; - if GAME == MQFEL { - checksum.write_options(writer, endian, ())?; - } - resource_count.write_options(writer, endian, ())?; block_data.resize(decompressed_block_size as usize, 0); @@ -251,7 +239,6 @@ impl BigFileIo for BigFileV2_07PC { match GAME { SHAUN_PROTO => 0, SHAUN => 4, - MQFEL => 8, _ => unreachable!(), } } else { @@ -281,11 +268,7 @@ impl BigFileIo for BigFileV2_07PC { Ok(()) } - const NAME_TYPE: NameType = if GAME == MQFEL { - Ubisoft64 - } else { - BlackSheep32 - }; + const NAME_TYPE: NameType = BlackSheep32; type ResourceType = Resource; } diff --git a/bff/src/bigfile/v2_0_pc/mod.rs b/bff/src/bigfile/v2_0_pc/mod.rs new file mode 100644 index 0000000..00e2fbf --- /dev/null +++ b/bff/src/bigfile/v2_0_pc/mod.rs @@ -0,0 +1,250 @@ +use std::cmp::max; +use std::collections::HashMap; +use std::io::{Cursor, Read, Seek, SeekFrom, Write}; + +use binrw::{args, binread, parser, BinRead, BinResult, BinWrite, Endian}; + +use super::v1_22_pc::Resource as Resource12; +use crate::bigfile::manifest::Manifest; +use crate::bigfile::BigFile; +use crate::helpers::{calculated_padded, read_align_to, write_align_to, DynArray}; +use crate::lz::{lzo_compress, lzo_decompress}; +use crate::names::NameType; +use crate::names::NameType::Ubisoft64; +use crate::platforms::Platform; +use crate::traits::BigFileIo; +use crate::versions::Version; +use crate::BffResult; + +type Resource = Resource12<20>; + +#[derive(Debug)] +pub struct Block { + pub compressed: bool, + pub checksum: Option, + pub resources: Vec, +} + +impl BinWrite for Block { + type Args<'a> = (); + + fn write_options( + &self, + writer: &mut W, + endian: Endian, + _args: Self::Args<'_>, + ) -> BinResult<()> { + self.resources.write_options(writer, endian, ())?; + Ok(()) + } +} + +#[parser(reader, endian)] +fn parse_blocks(decompressed_block_size: u32, block_sizes: &[u32]) -> BinResult> { + let mut blocks = Vec::new(); + + for block_size in block_sizes { + let block_start = reader.stream_position()?; + + let checksum = Some(u32::read_options(reader, endian, ())?); + let resource_count = u32::read_options(reader, endian, ())?; + + if *block_size != decompressed_block_size { + let mut compressed = vec![0; (*block_size - 8) as usize]; + reader.read_exact(&mut compressed)?; + let decompressed = + lzo_decompress(&compressed, decompressed_block_size as usize).unwrap(); + let mut decompressed = Cursor::new(decompressed); + blocks.push(Block { + compressed: true, + checksum, + resources: Vec::::read_options( + &mut decompressed, + endian, + args! { count: resource_count as usize }, + )?, + }); + read_align_to(reader, 2048)?; + } else { + blocks.push(Block { + compressed: false, + checksum, + resources: Vec::::read_options( + reader, + endian, + args! { count: resource_count as usize }, + )?, + }); + reader.seek(SeekFrom::Start(block_start + *block_size as u64))?; + } + } + + Ok(blocks) +} + +#[derive(Debug, BinRead, BinWrite, Copy, Clone)] +#[brw(repr = u32)] +pub enum CompressionType { + None, + Lzo, +} + +#[derive(Debug, BinRead, BinWrite)] +pub struct Header { + pub decompressed_block_size: u32, + pub compression_type: CompressionType, + pub block_sizes: DynArray, +} + +#[binread] +#[derive(Debug)] +#[br(import(version: Version, platform: Platform))] +pub struct BigFileV2_0PC { + #[br(calc = version)] + version: Version, + #[br(calc = platform)] + platform: Platform, + #[br(temp)] + header: Header, + #[br(align_before = 2048, parse_with = parse_blocks, args(header.decompressed_block_size, &header.block_sizes.inner))] + blocks: Vec, +} + +impl From for BigFile { + fn from(bigfile: BigFileV2_0PC) -> BigFile { + let mut blocks = Vec::with_capacity(bigfile.blocks.len()); + let mut resources = HashMap::new(); + + for block in bigfile.blocks.into_iter() { + let mut objects = Vec::with_capacity(block.resources.len()); + + for resource in block.resources { + objects.push(crate::bigfile::manifest::ManifestObject { + name: resource.name, + compress: None, + }); + resources.insert(resource.name, resource.into()); + } + + blocks.push(crate::bigfile::manifest::ManifestBlock { + offset: None, + checksum: None, + compressed: Some(block.compressed), + objects, + }); + } + + BigFile { + manifest: Manifest { + version: bigfile.version, + version_xple: None, + platform: bigfile.platform, + bigfile_type: None, + pool_manifest_unused: None, + incredi_builder_string: None, + blocks, + pool: None, + }, + objects: resources, + } + } +} + +impl BigFileIo for BigFileV2_0PC { + fn read( + reader: &mut R, + version: Version, + platform: Platform, + ) -> BffResult { + let endian = platform.into(); + let bigfile: BigFileV2_0PC = + BigFileV2_0PC::read_options(reader, endian, (version, platform))?; + Ok(bigfile.into()) + } + + fn write( + bigfile: &BigFile, + writer: &mut W, + tag: Option<&str>, + ) -> BffResult<()> { + let endian: Endian = bigfile.manifest.platform.into(); + + // Remember starting position for writing header + let begin = writer.stream_position()?; + + let mut decompressed_block_size = 0; + + let mut blocks = Vec::new(); + + for block in bigfile.manifest.blocks.iter() { + let mut block_writer = Cursor::new(Vec::new()); + + for resource in block.objects.iter() { + let resource = bigfile.objects.get(&resource.name).unwrap(); + Resource::dump_resource(resource, &mut block_writer, endian)?; + } + + let block_data = block_writer.into_inner(); + + decompressed_block_size = max(decompressed_block_size, block_data.len() as u32); + + blocks.push(( + block.objects.len() as u32, + block.checksum.unwrap_or(0), + block.compressed.unwrap_or(false), + block_data, + )); + } + + decompressed_block_size = calculated_padded(decompressed_block_size as usize, 2048) as u32; + let mut block_sizes = Vec::new(); + let mut compression_type = CompressionType::None; + + for (resource_count, checksum, compressed, mut block_data) in blocks { + let block_begin = writer.stream_position()?; + + checksum.write_options(writer, endian, ())?; + + resource_count.write_options(writer, endian, ())?; + + block_data.resize(decompressed_block_size as usize, 0); + + if compressed { + compression_type = CompressionType::Lzo; + lzo_compress(&block_data, writer)?; + } else { + writer.write_all(&block_data)?; + } + + let block_end = writer.stream_position()?; + + write_align_to(writer, 2048, 0)?; + + block_sizes.push((block_end - block_begin - if compressed { 8 } else { 0 }) as u32); + } + + // Write header at the beginning of the file and restore position + let end = writer.stream_position()?; + writer.seek(SeekFrom::Start(begin))?; + + let header = Header { + decompressed_block_size, + compression_type, + block_sizes: block_sizes.into(), + }; + header.write_options(writer, endian, ())?; + + if let Some(tag) = tag { + // TODO: Make sure the tag fits + writer.write_all(tag.as_bytes())?; + } + + writer.seek(SeekFrom::Start(end))?; + + Ok(()) + } + + const NAME_TYPE: NameType = Ubisoft64; + + type ResourceType = Resource; +} diff --git a/justfile b/justfile index aa7cdaf..ed8617b 100644 --- a/justfile +++ b/justfile @@ -15,7 +15,7 @@ clippy: cargo clippy test *TEST: - cargo +nightly test --release {{ TEST }} + cmake -E env RUST_TEST_THREADS=1 cargo +nightly test --release {{ TEST }} -j 1 build: cargo build --release