diff --git a/run.ps1 b/run.ps1 index e139258..3a3deda 100644 --- a/run.ps1 +++ b/run.ps1 @@ -47,7 +47,7 @@ if (!(Test-Path $TOOL)) { # --- Build MPQ archives into dist/ Write-Host "Building MPQ archives from src/ -> dist/..." -& $TOOL $SRC_DIR $DIST_DIR +& $TOOL build-mpq $SRC_DIR $DIST_DIR if ($LASTEXITCODE -ne 0) { Write-Error "MPQ build failed!" exit 1 diff --git a/src/Data/ruRU/patch-ruRU-6/DBFilesClient/TaxiNodes.dbc b/src/Data/ruRU/patch-ruRU-6/DBFilesClient/TaxiNodes.dbc new file mode 100644 index 0000000..be25e99 Binary files /dev/null and b/src/Data/ruRU/patch-ruRU-6/DBFilesClient/TaxiNodes.dbc differ diff --git a/src/Data/ruRU/patch-ruRU-6/DBFilesClient/TaxiPath.dbc b/src/Data/ruRU/patch-ruRU-6/DBFilesClient/TaxiPath.dbc new file mode 100644 index 0000000..234c21a Binary files /dev/null and b/src/Data/ruRU/patch-ruRU-6/DBFilesClient/TaxiPath.dbc differ diff --git a/src/Data/ruRU/patch-ruRU-6/DBFilesClient/TaxiPathNode.dbc b/src/Data/ruRU/patch-ruRU-6/DBFilesClient/TaxiPathNode.dbc new file mode 100644 index 0000000..7043853 Binary files /dev/null and b/src/Data/ruRU/patch-ruRU-6/DBFilesClient/TaxiPathNode.dbc differ diff --git a/tool/.gitignore b/tool/.gitignore index 9f97022..182a301 100644 --- a/tool/.gitignore +++ b/tool/.gitignore @@ -1 +1,3 @@ -target/ \ No newline at end of file +target/ +.sqlx/ +.env \ No newline at end of file diff --git a/tool/Cargo.lock b/tool/Cargo.lock index 1026b8f..c63b507 100644 --- a/tool/Cargo.lock +++ b/tool/Cargo.lock @@ -8,6 +8,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -132,9 +141,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -154,9 +163,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -297,6 +306,29 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "env_filter" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -459,6 +491,30 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jiff" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "392c70591e8749fe235ddaf513e6f58b26bce3dcc16524cecc8936f75afa161e" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b605b0c050d845fc355bb11eb3f9a8deddc218ea60c76e61aa1f2adfb2c96a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "js-sys" version = "0.3.91" @@ -519,9 +575,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "lru" @@ -568,6 +624,17 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -674,6 +741,12 @@ dependencies = [ "base64ct", ] +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "pkcs1" version = "0.7.5" @@ -712,6 +785,15 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -848,6 +930,35 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + [[package]] name = "rsa" version = "0.9.10" @@ -961,6 +1072,16 @@ dependencies = [ "digest", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "signature" version = "2.2.0" @@ -983,6 +1104,16 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "spin" version = "0.9.8" @@ -1055,19 +1186,51 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tool" version = "0.1.0" dependencies = [ + "clap", + "env_logger", + "log", + "tokio", "walkdir", "wow-mpq", ] [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "unicode-ident" diff --git a/tool/Cargo.toml b/tool/Cargo.toml index 8a24e63..e8fc834 100644 --- a/tool/Cargo.toml +++ b/tool/Cargo.toml @@ -4,5 +4,9 @@ version = "0.1.0" edition = "2024" [dependencies] +clap = { version = "4.6.1", features = ["derive"] } +env_logger = "0.11.10" +log = "0.4.30" +tokio = { version = "1.52.3", features = ["full"] } walkdir = "2.5.0" wow-mpq = "0.6.4" diff --git a/tool/src/commands/build_mpq.rs b/tool/src/commands/build_mpq.rs new file mode 100644 index 0000000..3df7300 --- /dev/null +++ b/tool/src/commands/build_mpq.rs @@ -0,0 +1,100 @@ +use clap::Args; +use log::info; +use std::{ + error::Error, + fs, + path::{Path, PathBuf}, +}; +use walkdir::WalkDir; +use wow_mpq::{ArchiveBuilder, FormatVersion, ListfileOption, compression::flags}; + +#[derive(Args, Debug)] +pub struct BuildMpqCommand { + input_dir: PathBuf, + + destination_dir: PathBuf, +} + +pub fn execute(args: BuildMpqCommand) -> Result<(), Box> { + let data_dir = args.input_dir.join("Data"); + if !data_dir.exists() { + return Err(format!("Data directory not found at {}", data_dir.display()).into()); + } + + info!("Found Data directory at {}", data_dir.display()); + + for entry in fs::read_dir(&data_dir)? { + let entry = entry?; + let path = entry.path(); + + if !path.is_dir() { + continue; + } + + let dir_name = entry.file_name().to_string_lossy().to_string(); + + if dir_name.starts_with("ruRU") { + for sub_entry in fs::read_dir(&path)? { + let sub_entry = sub_entry?; + let sub_path = sub_entry.path(); + + let sub_name = sub_entry.file_name().to_string_lossy().to_string(); + let destination = args + .destination_dir + .join("Data") + .join(&dir_name) + .join(&sub_name); + + let _ = build_mpq(&sub_path, &destination); + } + + continue; + } + + let destination = args.destination_dir.join("Data").join(&dir_name); + + let _ = build_mpq(&path, &destination)?; + } + + Ok(()) +} + +fn build_mpq(input_dir: &Path, destination: &Path) -> Result<(), Box> { + info!( + "Packing MPQ from {} to {}", + input_dir.display(), + destination.display() + ); + + let mut destination = destination.to_path_buf(); + destination.set_extension("MPQ"); + + if let Some(parent) = destination.parent() { + fs::create_dir_all(parent)?; + } + + let mut builder = ArchiveBuilder::new() + .version(FormatVersion::V2) + .block_size(7) + .default_compression(flags::ZLIB) + .listfile_option(ListfileOption::Generate); + + let base = input_dir.canonicalize()?; + + for entry in WalkDir::new(&base) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.file_type().is_file()) + { + let absolute_path = entry.path().canonicalize()?; + let relative_path = absolute_path.strip_prefix(&base)?; + + builder = builder.add_file(&absolute_path, &relative_path.to_string_lossy().to_string()); + } + + let _ = builder.build(&destination); + + info!("Successfully packed {}", destination.display()); + + Ok(()) +} diff --git a/tool/src/commands/mod.rs b/tool/src/commands/mod.rs new file mode 100644 index 0000000..2c4a737 --- /dev/null +++ b/tool/src/commands/mod.rs @@ -0,0 +1 @@ +pub mod build_mpq; diff --git a/tool/src/main.rs b/tool/src/main.rs index 0401541..7602ee6 100644 --- a/tool/src/main.rs +++ b/tool/src/main.rs @@ -1,111 +1,41 @@ -use std::env; +mod commands; + +use clap::{Parser, Subcommand}; +use log::{error, info}; 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() +#[derive(Parser, Debug)] +struct Cli { + #[command(subcommand)] + command: Commands, } -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(()) +#[derive(Subcommand, Debug)] +enum Commands { + BuildMpq(commands::build_mpq::BuildMpqCommand), } -fn main() -> Result<(), Box> { - let args: Vec = env::args().collect(); +#[tokio::main] +async fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .format_target(false) + .format_timestamp(None) + .init(); + + let args = Cli::parse(); + + if let Err(err) = run(args).await { + error!("{}", err); - 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(()) +} + +async fn run(args: Cli) -> Result<(), Box> { + info!("Starting tool..."); + + return match args.command { + Commands::BuildMpq(args) => commands::build_mpq::execute(args), + }; }