88 lines
2.8 KiB
Rust
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(())
|
||
|
}
|