diff --git a/bff-cli/src/create.rs b/bff-cli/src/create.rs new file mode 100644 index 0000000..27dcdf7 --- /dev/null +++ b/bff-cli/src/create.rs @@ -0,0 +1,46 @@ +use std::fs::File; +use std::io::BufWriter; +use std::path::{Path, PathBuf}; + +use bff::bigfile::BigFile; +use bff::BufReader; + +use crate::error::BffCliResult; +use crate::extract::{read_names, write_names}; + +pub fn create( + directory: &Path, + bigfile_path: &Path, + in_names: &Vec, + out_names: &Option, +) -> BffCliResult<()> { + read_names(bigfile_path, in_names)?; + + let manifest_path = directory.join("manifest.json"); + let manifest_reader = BufReader::new(File::open(manifest_path)?); + let manifest = serde_json::from_reader(manifest_reader)?; + + let mut bigfile = BigFile { + manifest, + objects: Default::default(), + }; + + let resources_path = directory.join("resources"); + std::fs::create_dir_all(&resources_path)?; + + for file in std::fs::read_dir(resources_path)? { + let path = file?.path(); + if path.is_file() { + let mut file_reader = BufReader::new(File::open(&path)?); + let resource = bigfile.read_resource(&mut file_reader)?; + bigfile.objects.insert(resource.name, resource); + } + } + + let mut bigfile_writer = BufWriter::new(File::create(bigfile_path)?); + bigfile.write(&mut bigfile_writer, None)?; + + write_names(out_names)?; + + Ok(()) +} diff --git a/bff-cli/src/extract.rs b/bff-cli/src/extract.rs index b1a4b4c..acb66e6 100644 --- a/bff-cli/src/extract.rs +++ b/bff-cli/src/extract.rs @@ -63,11 +63,19 @@ pub fn extract( let bigfile = read_bigfile(bigfile_path)?; + std::fs::create_dir(directory)?; + + let manifest_path = directory.join("manifest.json"); + let manifest_writer = BufWriter::new(File::create(manifest_path)?); + serde_json::to_writer_pretty(manifest_writer, &bigfile.manifest)?; + + let resources_path = directory.join("resources"); + std::fs::create_dir(&resources_path)?; + for resource in bigfile.objects.values() { let name = resource.name; let class_name = resource.class_name; - let path = directory.join(format!("{}.{}", name, class_name)); - std::fs::create_dir_all(path.parent().unwrap())?; + let path = resources_path.join(format!("{}.{}", name, class_name)); let mut writer = BufWriter::new(File::create(path)?); bigfile.dump_resource(resource, &mut writer)?; } diff --git a/bff-cli/src/main.rs b/bff-cli/src/main.rs index 2c1dae7..9263a8e 100644 --- a/bff-cli/src/main.rs +++ b/bff-cli/src/main.rs @@ -9,6 +9,7 @@ use reverse_crc32::DEFAULT_CHARACTER_SET; use crate::lz::LzAlgorithm; mod crc; +mod create; mod csc; mod error; mod extract; @@ -37,6 +38,15 @@ enum Commands { #[arg(long)] out_names: Option, }, + #[clap(alias = "c")] + Create { + directory: PathBuf, + bigfile: PathBuf, + #[arg(long)] + in_names: Vec, + #[arg(long)] + out_names: Option, + }, #[clap(alias = "t")] Info { bigfile: PathBuf, @@ -141,6 +151,12 @@ fn main() -> BffCliResult<()> { in_names, out_names, } => extract::extract(bigfile, directory, in_names, out_names), + Commands::Create { + directory, + bigfile, + in_names, + out_names, + } => create::create(directory, bigfile, in_names, out_names), Commands::Info { bigfile, in_names } => info::info(bigfile, in_names), Commands::Crc { string, diff --git a/bff-derive/src/bigfiles.rs b/bff-derive/src/bigfiles.rs index afd4960..c0e5c89 100644 --- a/bff-derive/src/bigfiles.rs +++ b/bff-derive/src/bigfiles.rs @@ -21,13 +21,18 @@ pub fn derive_bigfiles(input: BffBigFileMacroInput) -> TokenStream { let read_bigfile = impl_read_bigfile(&input); let write_bigfile = impl_write_bigfile(&input); let dump_resource = impl_dump_resource(&input); + let read_resource = impl_read_resource(&input); + let version_into_name_type = impl_version_into_name_type(&input); quote! { impl BigFile { #read_bigfile #write_bigfile #dump_resource + #read_resource } + + #version_into_name_type } } @@ -133,7 +138,7 @@ fn impl_dump_resource(input: &BffBigFileMacroInput) -> proc_macro2::TokenStream .collect::>(); quote! { - pub fn dump_resource(&self, resource: &Resource, writer: &mut W) -> crate::BffResult<()> { + pub fn dump_resource(&self, resource: &crate::bigfile::resource::Resource, writer: &mut W) -> crate::BffResult<()> { use crate::versions::Version::*; use crate::platforms::Platform::*; use crate::traits::BigFileIo; @@ -147,3 +152,77 @@ fn impl_dump_resource(input: &BffBigFileMacroInput) -> proc_macro2::TokenStream } } } + +fn impl_read_resource(input: &BffBigFileMacroInput) -> proc_macro2::TokenStream { + let arms = input + .forms + .iter() + .map(|form| { + let attrs = &form.attrs; + let pat = &form.pat; + let guard = match &form.guard { + Some((_, guard)) => quote! { #guard }, + None => quote! {}, + }; + let body = &form.body; + quote! { + #(#attrs)* + #pat #guard => { + crate::names::names().lock().unwrap().name_type = <#body as BigFileIo>::NAME_TYPE; + Ok(<#body as BigFileIo>::ResourceType::read_resource(reader, endian)?) + } + } + }) + .collect::>(); + + quote! { + pub fn read_resource(&self, reader: &mut R) -> crate::BffResult { + use crate::versions::Version::*; + use crate::platforms::Platform::*; + use crate::traits::BigFileIo; + let platform = self.manifest.platform; + let endian: crate::Endian = platform.into(); + let version = &self.manifest.version; + match (version.clone(), platform) { + #(#arms)* + (version, platform) => Err(crate::error::UnimplementedVersionPlatformError::new(version, platform).into()), + } + } + } +} + +fn impl_version_into_name_type(input: &BffBigFileMacroInput) -> proc_macro2::TokenStream { + let arms = input + .forms + .iter() + .map(|form| { + let attrs = &form.attrs; + let pat = &form.pat; + let guard = match &form.guard { + Some((_, guard)) => quote! { #guard }, + None => quote! {}, + }; + let body = &form.body; + quote! { + #(#attrs)* + #pat #guard => Ok(<#body as BigFileIo>::NAME_TYPE), + } + }) + .collect::>(); + + quote! { + impl TryFrom<&crate::versions::Version> for crate::names::NameType { + type Error = crate::BffError; + + fn try_from(version: &crate::versions::Version) -> Result { + use crate::versions::Version::*; + use crate::platforms::Platform::*; + use crate::traits::BigFileIo; + match (version.clone(), PC) { + #(#arms)* + (version, platform) => Err(crate::error::UnimplementedVersionError::new(version).into()), + } + } + } + } +} diff --git a/bff/src/bigfile/manifest.rs b/bff/src/bigfile/manifest.rs index 9986226..a648d3e 100644 --- a/bff/src/bigfile/manifest.rs +++ b/bff/src/bigfile/manifest.rs @@ -1,36 +1,36 @@ -use serde::Serialize; +use serde::{Deserialize, Serialize}; use crate::names::Name; use crate::platforms::Platform; use crate::versions::{Version, VersionXple}; -#[derive(Serialize, Debug)] +#[derive(Serialize, Deserialize, Debug)] pub struct ManifestPoolObjectEntry { pub name: Name, pub reference_record_index: u32, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Deserialize, Debug)] pub struct ManifestPoolReferenceRecord { pub object_entries_starting_index: u32, pub object_entries_count: u16, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Deserialize, Debug)] pub struct ManifestPool { pub object_entry_indices: Vec, pub object_entries: Vec, pub reference_records: Vec, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Deserialize, Debug)] pub struct ManifestObject { pub name: Name, #[serde(skip_serializing_if = "Option::is_none")] pub compress: Option, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Deserialize, Debug)] pub struct ManifestBlock { #[serde(skip_serializing_if = "Option::is_none")] pub offset: Option, @@ -41,7 +41,7 @@ pub struct ManifestBlock { pub objects: Vec, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Deserialize, Debug)] pub struct Manifest { pub version: Version, pub platform: Platform, diff --git a/bff/src/bigfile/v1_06_63_02_pc/mod.rs b/bff/src/bigfile/v1_06_63_02_pc/mod.rs index e8bc73e..ec88859 100644 --- a/bff/src/bigfile/v1_06_63_02_pc/mod.rs +++ b/bff/src/bigfile/v1_06_63_02_pc/mod.rs @@ -49,18 +49,7 @@ pub fn blocks_parser( compress: Some(object.compress), }); - objects.insert( - object.name, - Resource { - class_name: object.class_name, - name: object.name, - compress: object.compress, - data: SplitData { - link_header: object.link_header, - body: object.body, - }, - }, - ); + objects.insert(object.name, object.into()); } blocks.push(ManifestBlock { diff --git a/bff/src/bigfile/v1_06_63_02_pc/object.rs b/bff/src/bigfile/v1_06_63_02_pc/object.rs index 5e51875..9257ba0 100644 --- a/bff/src/bigfile/v1_06_63_02_pc/object.rs +++ b/bff/src/bigfile/v1_06_63_02_pc/object.rs @@ -1,4 +1,4 @@ -use std::io::{Seek, Write}; +use std::io::{Read, Seek, Write}; use binrw::{args, binread, parser, BinRead, BinResult, BinWrite, Endian}; use derive_more::{Deref, DerefMut}; @@ -68,6 +68,24 @@ impl Object { } Ok(()) } + + pub fn read_resource(reader: &mut R, endian: Endian) -> BinResult { + Ok(Self::read_options(reader, endian, ())?.into()) + } +} + +impl From for Resource { + fn from(value: Object) -> Self { + Resource { + class_name: value.class_name, + name: value.name, + compress: value.compress, + data: SplitData { + link_header: value.link_header, + body: value.body, + }, + } + } } #[derive(BinRead, Serialize, Debug, Deref, DerefMut)] diff --git a/bff/src/bigfile/v1_08_40_02_pc/mod.rs b/bff/src/bigfile/v1_08_40_02_pc/mod.rs index 2036108..5225c6a 100644 --- a/bff/src/bigfile/v1_08_40_02_pc/mod.rs +++ b/bff/src/bigfile/v1_08_40_02_pc/mod.rs @@ -41,15 +41,7 @@ fn blocks_parser( compress: Some(object.compress), }); - objects.insert( - object.name, - Resource { - class_name: object.class_name, - name: object.name, - compress: object.compress, - data: Data(object.data), - }, - ); + objects.insert(object.name, object.into()); } blocks.push(ManifestBlock { diff --git a/bff/src/bigfile/v1_08_40_02_pc/object.rs b/bff/src/bigfile/v1_08_40_02_pc/object.rs index cd55996..2a81c44 100644 --- a/bff/src/bigfile/v1_08_40_02_pc/object.rs +++ b/bff/src/bigfile/v1_08_40_02_pc/object.rs @@ -1,6 +1,6 @@ -use std::io::{Seek, Write}; +use std::io::{Read, Seek, Write}; -use binrw::{binread, BinResult, BinWrite, Endian}; +use binrw::{binread, BinRead, BinResult, BinWrite, Endian}; use serde::Serialize; use crate::bigfile::resource::Resource; @@ -50,4 +50,19 @@ impl Object { Ok(()) } + + pub fn read_resource(reader: &mut R, endian: Endian) -> BinResult { + Ok(Self::read_options(reader, endian, ())?.into()) + } +} + +impl From for Resource { + fn from(value: Object) -> Self { + Resource { + class_name: value.class_name, + name: value.name, + compress: value.compress, + data: Data(value.data), + } + } } diff --git a/bff/src/bigfile/v1_22_pc/mod.rs b/bff/src/bigfile/v1_22_pc/mod.rs index f625420..5a2e8b3 100644 --- a/bff/src/bigfile/v1_22_pc/mod.rs +++ b/bff/src/bigfile/v1_22_pc/mod.rs @@ -52,6 +52,13 @@ impl Resource { Ok(()) } + + pub fn read_resource( + reader: &mut R, + endian: Endian, + ) -> BinResult { + Ok(Self::read_options(reader, endian, ())?.into()) + } } impl From for crate::bigfile::resource::Resource { diff --git a/bff/src/error.rs b/bff/src/error.rs index 50f1a66..139f761 100644 --- a/bff/src/error.rs +++ b/bff/src/error.rs @@ -32,6 +32,12 @@ pub struct UnimplementedVersionPlatformError { pub platform: Platform, } +#[derive(Debug, Constructor, Display, Error)] +#[display(fmt = "Unsupported BigFile version: {}", version)] +pub struct UnimplementedVersionError { + pub version: Version, +} + #[derive(Debug, Constructor, Display, Error)] #[display(fmt = "Invalid BigFile extension {:#?}", extension)] pub struct InvalidExtensionError { @@ -53,6 +59,7 @@ pub enum Error { Io(std::io::Error), ParseInt(std::num::ParseIntError), UnimplementedClass(UnimplementedClassError), + UnimplementedVersion(UnimplementedVersionError), UnimplementedVersionPlatform(UnimplementedVersionPlatformError), Utf8(std::string::FromUtf8Error), } diff --git a/bff/src/macros/platforms.rs b/bff/src/macros/platforms.rs index 1f34766..77a3a7e 100644 --- a/bff/src/macros/platforms.rs +++ b/bff/src/macros/platforms.rs @@ -46,12 +46,12 @@ macro_rules! platforms { $($platform:ident($styles:tt,$endian:ident)),* $(,)? ] ) => { - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display, serde::Serialize)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display, serde::Serialize, serde::Deserialize)] pub enum Style { $($style,)* } - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display, serde::Serialize)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display, serde::Serialize, serde::Deserialize)] pub enum Platform { $($platform,)* } diff --git a/bff/src/versions.rs b/bff/src/versions.rs index 01a3140..c96b768 100644 --- a/bff/src/versions.rs +++ b/bff/src/versions.rs @@ -3,6 +3,8 @@ use derive_more::{Display, From}; use scanf::sscanf; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use crate::names::names; + #[derive(Debug, Display, Clone)] pub enum Version { #[display( @@ -117,7 +119,9 @@ impl Serialize for Version { impl<'de> Deserialize<'de> for Version { fn deserialize>(deserializer: D) -> Result { let string = String::deserialize(deserializer)?; - Ok(string.as_str().into()) + let version: Version = string.as_str().into(); + names().lock().unwrap().name_type = (&version).try_into().unwrap(); // FIXME: name_type should not exist + Ok(version) } }