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> { 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> { let args: Vec = env::args().collect(); if args.len() != 3 { eprintln!("Usage: tool "); 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(()) }