From 1a4588bb8d705a4cb2b2f02969956ccb5a61f37e Mon Sep 17 00:00:00 2001 From: Anna Clemens Date: Wed, 2 Nov 2022 15:43:25 -0400 Subject: [PATCH] feat: add support for ttmp v1 --- examples/extract_dedupe_in_memory.rs | 29 --------------- src/lib.rs | 41 ++++++++++++++++++--- src/model/manifest_kind.rs | 19 ++++++++++ src/model/mod.rs | 1 + src/model/simple_mod.rs | 2 ++ src/ttmp_extractor.rs | 54 +++++++++++++++------------- 6 files changed, 88 insertions(+), 58 deletions(-) create mode 100644 src/model/manifest_kind.rs diff --git a/examples/extract_dedupe_in_memory.rs b/examples/extract_dedupe_in_memory.rs index 0d3203c..ac0d879 100644 --- a/examples/extract_dedupe_in_memory.rs +++ b/examples/extract_dedupe_in_memory.rs @@ -16,7 +16,6 @@ pub fn main() { let files = extractor.all_files_sorted(); let mut data_file = zip.by_name("TTMPD.mpd").unwrap(); - let version = &*extractor.manifest().version; std::fs::create_dir_all("files").unwrap(); @@ -69,31 +68,3 @@ pub struct SavedFile { pub group: Option, pub option: Option, } - -struct Multiplexer { - one: W1, - two: W2, -} - -impl Multiplexer { - fn new(one: W1, two: W2) -> Self { - Self { - one, - two, - } - } -} - -impl Write for Multiplexer { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - let one = self.one.write(buf); - let two = self.two.write(buf); - one.and(two) - } - - fn flush(&mut self) -> std::io::Result<()> { - let one = self.one.flush(); - let two = self.two.flush(); - one.and(two) - } -} diff --git a/src/lib.rs b/src/lib.rs index e4faa94..2a0a31b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,51 @@ use std::io::{Read, Seek}; +use serde::de::Error as _; +use serde::Deserialize; +use serde_json::de::IoRead; +use serde_json::StreamDeserializer; pub use zip::{read::ZipFile, ZipArchive}; use crate::error::{Error, Result}; -use crate::model::ModPack; +use crate::model::{ModPack, SimpleMod}; +use crate::model::manifest_kind::ManifestKind; pub mod model; pub mod error; pub(crate) mod tracking_reader; pub mod ttmp_extractor; -pub fn from_reader(reader: R) -> Result<(ModPack, ZipArchive)> { +pub fn from_reader(reader: R) -> Result<(ManifestKind, ZipArchive)> { let mut zip = ZipArchive::new(reader).map_err(Error::Zip)?; let manifest = zip.by_name("TTMPL.mpl").map_err(Error::Zip)?; - let manifest = serde_json::from_reader(manifest).map_err(Error::InvalidManifest)?; - Ok((manifest, zip)) + let mut manifest = StreamDeserializer::new(IoRead::new(manifest)) + .collect::, _>>() + .map_err(Error::InvalidManifest)?; + + #[derive(Deserialize)] + #[serde(untagged)] + enum DesManifest { + V1(SimpleMod), + V2(ModPack), + } + + let all_v1 = manifest.iter().all(|inner| matches!(inner, DesManifest::V1(_))); + let transformed = if all_v1 { + let mods: Vec = manifest.into_iter() + .map(|inner| match inner { + DesManifest::V1(inner) => inner, + _ => unreachable!(), + }) + .collect(); + ManifestKind::V1(mods) + } else if manifest.len() == 1 && matches!(&manifest[0], DesManifest::V2(_)) { + ManifestKind::V2(match manifest.remove(0) { + DesManifest::V2(pack) => pack, + _ => unreachable!(), + }) + } else { + return Err(Error::InvalidManifest(serde_json::Error::custom("unrecognised manifest"))); + }; + + Ok((transformed, zip)) } diff --git a/src/model/manifest_kind.rs b/src/model/manifest_kind.rs new file mode 100644 index 0000000..a979fce --- /dev/null +++ b/src/model/manifest_kind.rs @@ -0,0 +1,19 @@ +use crate::model::{ModPack, SimpleMod}; + +#[derive(Debug)] +pub enum ManifestKind { + V1(Vec), + V2(ModPack), +} + +impl ManifestKind { + pub fn simple_mods_list(&self) -> &[SimpleMod] { + match self { + Self::V1(mods) => mods, + Self::V2(pack) => match &pack.simple_mods_list { + Some(list) => list, + None => &[], + }, + } + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs index 59a6754..6c287ce 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -7,6 +7,7 @@ pub use self::{ simple_mod::SimpleMod, }; +pub mod manifest_kind; pub mod mod_group; pub mod mod_option; pub mod mod_pack; diff --git a/src/model/simple_mod.rs b/src/model/simple_mod.rs index e0ef587..9e903d6 100644 --- a/src/model/simple_mod.rs +++ b/src/model/simple_mod.rs @@ -7,8 +7,10 @@ pub struct SimpleMod { pub category: String, pub full_path: String, pub dat_file: String, + #[serde(default)] pub is_default: bool, pub mod_offset: usize, pub mod_size: usize, + #[serde(default)] pub mod_pack_entry: Option, } diff --git a/src/ttmp_extractor.rs b/src/ttmp_extractor.rs index 2e0e033..60c6ae5 100644 --- a/src/ttmp_extractor.rs +++ b/src/ttmp_extractor.rs @@ -8,8 +8,9 @@ use sqpack::binrw::{BinRead, BinWriterExt, VecArgs}; use sqpack::binrw::meta::ReadEndian; use zip::ZipArchive; -use crate::{Error, ModPack}; +use crate::Error; use crate::error::Result; +use crate::model::manifest_kind::ManifestKind; use crate::model::SimpleMod; use crate::tracking_reader::TrackingReader; @@ -20,7 +21,7 @@ impl WriteSeek for T where T: Write + Seek {} pub struct TtmpExtractor { - manifest: ModPack, + manifest: ManifestKind, zip: RefCell>, } @@ -34,11 +35,11 @@ impl TtmpExtractor { }) } - pub fn manifest(&self) -> &ModPack { + pub fn manifest(&self) -> &ManifestKind { &self.manifest } - pub fn into_manifest(self) -> ModPack { + pub fn into_manifest(self) -> ManifestKind { self.manifest } @@ -92,34 +93,34 @@ impl TtmpExtractor { let mut all_files = Vec::new(); let mut seen_groups: HashMap<&str, usize> = HashMap::new(); - if let Some(pages) = &self.manifest.mod_pack_pages { - for page in pages { - for group in &page.mod_groups { - let seen = seen_groups.entry(&group.group_name).or_default(); - *seen += 1; + if let ManifestKind::V2(pack) = &self.manifest { + if let Some(pages) = &pack.mod_pack_pages { + for page in pages { + for group in &page.mod_groups { + let seen = seen_groups.entry(&group.group_name).or_default(); + *seen += 1; - for option in &group.option_list { - for file in &option.mods_jsons { - all_files.push(ModFile { - group: Some(&*option.group_name), - group_occurence: *seen, - option: Some(&*option.name), - file, - }); + for option in &group.option_list { + for file in &option.mods_jsons { + all_files.push(ModFile { + group: Some(&*option.group_name), + group_occurence: *seen, + option: Some(&*option.name), + file, + }); + } } } } } } - if let Some(list) = &self.manifest.simple_mods_list { - all_files.extend(list.iter().map(|file| ModFile { - group: None, - group_occurence: 0, - option: None, - file, - })); - } + all_files.extend(self.manifest.simple_mods_list().iter().map(|file| ModFile { + group: None, + group_occurence: 0, + option: None, + file, + })); all_files.sort_unstable_by_key(|file| file.file.mod_offset); all_files @@ -293,9 +294,11 @@ impl TtmpExtractor { fn extract_texture_file(info: &SqPackFileInfoHeader, mut reader: T, mut writer: W, buf: &mut [u8]) -> Result<()> { let std_info: SqPackFileInfo = Self::read_struct(&mut reader, buf)?; + println!("{:#?}", std_info); let blocks: Vec = (0..std_info.number_of_blocks) .map(|_| Self::read_struct(&mut reader, buf)) .collect::>()?; + println!("{:#?}", blocks); let sub_block_count = blocks .iter() @@ -341,6 +344,7 @@ impl TtmpExtractor { fn read_block_into(mut reader: T, mut writer: W, buf: &mut [u8], size: usize) -> Result { let header: DatBlockHeader = Self::read_struct(&mut reader, buf)?; + println!("{:#?}", header); let (read, actual) = if header.compressed_size == 32_000 { // uncompressed