112 lines
3.5 KiB
Rust
112 lines
3.5 KiB
Rust
use std::env;
|
|
use std::error::Error;
|
|
use std::fs;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use walkdir::WalkDir;
|
|
use wow_mpq::compression::flags;
|
|
use wow_mpq::{ArchiveBuilder, FormatVersion, ListfileOption};
|
|
|
|
/// Checks if a directory name looks like a WoW locale folder (e.g. ruRU, enUS, deDE).
|
|
fn is_locale_dir(name: &str) -> bool {
|
|
if name.len() != 4 {
|
|
return false;
|
|
}
|
|
let b = name.as_bytes();
|
|
b[0].is_ascii_lowercase()
|
|
&& b[1].is_ascii_lowercase()
|
|
&& b[2].is_ascii_uppercase()
|
|
&& b[3].is_ascii_uppercase()
|
|
}
|
|
|
|
fn build_mpq(input_dir: &Path, output_path: &Path) -> Result<(), Box<dyn Error>> {
|
|
let mut output = output_path.to_path_buf();
|
|
output.set_extension("MPQ");
|
|
|
|
if let Some(parent) = output.parent() {
|
|
fs::create_dir_all(parent)?;
|
|
}
|
|
|
|
let base = input_dir.canonicalize()?;
|
|
println!("Packing: {} -> {}", base.display(), output.display());
|
|
|
|
let mut builder = ArchiveBuilder::new()
|
|
.version(FormatVersion::V2)
|
|
.block_size(7) // 64 KB sectors
|
|
.default_compression(flags::ZLIB)
|
|
.listfile_option(ListfileOption::Generate);
|
|
|
|
for entry in WalkDir::new(&base)
|
|
.into_iter()
|
|
.filter_map(|e| e.ok())
|
|
.filter(|e| e.file_type().is_file())
|
|
{
|
|
let full_path = entry.path().canonicalize()?;
|
|
let rel_path = match full_path.strip_prefix(&base) {
|
|
Ok(p) => p,
|
|
Err(_) => {
|
|
eprintln!("Skipping (prefix mismatch): {}", full_path.display());
|
|
continue;
|
|
}
|
|
};
|
|
let archive_path = rel_path.to_string_lossy().replace('\\', "/");
|
|
println!(" Adding: {}", archive_path);
|
|
builder = builder.add_file(&full_path, &archive_path);
|
|
}
|
|
|
|
builder.build(&output)?;
|
|
println!("Created: {}", output.display());
|
|
Ok(())
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn Error>> {
|
|
let args: Vec<String> = env::args().collect();
|
|
|
|
if args.len() != 3 {
|
|
eprintln!("Usage: tool <src_dir> <output_dir>");
|
|
eprintln!();
|
|
eprintln!(" src_dir — root of source tree (must contain Data/)");
|
|
eprintln!(" output_dir — destination root; MPQs are placed mirroring the src structure");
|
|
std::process::exit(1);
|
|
}
|
|
|
|
let src_dir = PathBuf::from(&args[1]);
|
|
let output_dir = PathBuf::from(&args[2]);
|
|
|
|
let data_dir = src_dir.join("Data");
|
|
if !data_dir.exists() {
|
|
return Err(format!("Data/ not found inside src_dir: {}", src_dir.display()).into());
|
|
}
|
|
|
|
for entry in fs::read_dir(&data_dir)? {
|
|
let entry = entry?;
|
|
let path = entry.path();
|
|
if !path.is_dir() {
|
|
continue;
|
|
}
|
|
|
|
let name = entry.file_name().to_string_lossy().to_string();
|
|
|
|
if is_locale_dir(&name) {
|
|
// e.g. ruRU/ — iterate its immediate subdirs, each is a patch source
|
|
for sub in fs::read_dir(&path)? {
|
|
let sub = sub?;
|
|
let sub_path = sub.path();
|
|
if !sub_path.is_dir() {
|
|
continue;
|
|
}
|
|
let sub_name = sub.file_name().to_string_lossy().to_string();
|
|
let output_mpq = output_dir.join("Data").join(&name).join(&sub_name);
|
|
build_mpq(&sub_path, &output_mpq)?;
|
|
}
|
|
} else {
|
|
// Regular patch dir, e.g. patch-4/ or patch-Z/
|
|
let output_mpq = output_dir.join("Data").join(&name);
|
|
build_mpq(&path, &output_mpq)?;
|
|
}
|
|
}
|
|
|
|
println!("\nAll MPQ archives built successfully.");
|
|
Ok(())
|
|
}
|