use std::fs::File; use std::io::{Seek, Write}; use std::path::Path; use zip::{CompressionMethod, ZipWriter}; use zip::write::FileOptions; use ttmp::model::ManifestKind; use ttmp::mpd_encoder::{FileInfo, MpdEncoder}; use ttmp::ttmp_extractor::TtmpExtractor; fn main() -> anyhow::Result<()> { let path = std::env::args().skip(1).next().unwrap(); let file = File::open(&path)?; let extractor = TtmpExtractor::new(file)?; let files = extractor.all_files_sorted(); let mut zip = extractor.zip().borrow_mut(); let mut data = zip.by_name("TTMPD.mpd")?; let mpd = tempfile::tempfile()?; let mut encoder = MpdEncoder::new(mpd, extractor.manifest().clone(), None); let mut staging = tempfile::tempfile()?; let mut last_offset = None; let mut last_hash: Option> = None; for file in files { let info = FileInfo { group: file.group.map(ToOwned::to_owned), option: file.option.map(ToOwned::to_owned), game_path: file.file.full_path.clone(), }; // handle deduped ttmps if Some(file.file.mod_offset) == last_offset { if let Some(hash) = &last_hash { encoder.add_file_info(hash, info); } continue; } last_offset = Some(file.file.mod_offset); staging.rewind()?; staging.set_len(0)?; TtmpExtractor::extract_one_into(&file, &mut data, &mut staging)?; let size = staging.metadata()?.len() as usize; staging.rewind()?; last_hash = if info.game_path.ends_with(".mdl") { encoder.add_model_file(info, size, &mut staging)? } else if info.game_path.ends_with(".tex") || info.game_path.ends_with(".atex") { encoder.add_texture_file(info, size, &mut staging)? } else { encoder.add_standard_file(info, size, &mut staging)? }.into(); } let (manifest, mut mpd) = encoder.finalize()?; mpd.rewind()?; let path = Path::new(&path); let extension = path.extension().and_then(|s| s.to_str()).unwrap_or("ttmp2"); let new_path = path.with_extension(format!("deduplicated.{}", extension)); let new_file = File::create(new_path)?; let mut zip = ZipWriter::new(new_file); zip.start_file("TTMPL.mpl", FileOptions::default().compression_method(CompressionMethod::Deflated))?; match manifest { ManifestKind::V1(mods) => for mod_ in mods { serde_json::to_writer(&mut zip, &mod_)?; zip.write_all(b"\n")?; } ManifestKind::V2(pack) => serde_json::to_writer(&mut zip, &pack)?, } zip.start_file("TTMPD.mpd", FileOptions::default().compression_method(CompressionMethod::Stored))?; std::io::copy(&mut mpd, &mut zip)?; zip.finish()?; Ok(()) }