ttmp-rs/examples/deduplicate.rs

88 lines
2.8 KiB
Rust

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<Vec<u8>> = 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(())
}