diff --git a/bncsutil-node/src/lib.rs b/bncsutil-node/src/lib.rs index 8c9313d..874c573 100644 --- a/bncsutil-node/src/lib.rs +++ b/bncsutil-node/src/lib.rs @@ -52,10 +52,10 @@ fn version() -> f64 { bncs::version() as f64 } -#[napi] -fn version_string() -> String { - bncs::version_string() -} +// #[napi] +// fn version_string() -> String { +// bncs::version_string() +// } #[napi(object)] pub struct ExeInfo { @@ -76,10 +76,10 @@ fn get_exe_info(path_string: String) -> ExeInfo { // value: String, files: Vec<&Path>, mpqNumber: i32 #[napi] -fn check_revision(value: String, files: Vec, mpq_number: i32) -> u32 { +fn check_revision(value: String, files: Vec, mpq_number: u32) -> i64 { let files_ref = files.iter().map(|val| Path::new(val.as_str())).collect(); - bncs::check_revision(value, files_ref, mpq_number as i32) + bncs::check_revision(value, files_ref, mpq_number) as i64 } // value: String, file1: &Path, file2: &Path, file3: &Path, mpq_number: i32 @@ -89,10 +89,10 @@ fn check_revision_flat( file1: String, file2: String, file3: String, - mpq_number: i32, -) -> u32 { + mpq_number: u32, +) -> i64 { let files = vec![file1, file2, file3]; let files_ref = files.iter().map(|val| Path::new(val.as_str())).collect(); - bncs::check_revision(value, files_ref, mpq_number as i32) + bncs::check_revision(value, files_ref, mpq_number as u32) as i64 } diff --git a/bncsutil-sys/src/lib.rs b/bncsutil-sys/src/lib.rs index a38a13a..e4ec8b9 100644 --- a/bncsutil-sys/src/lib.rs +++ b/bncsutil-sys/src/lib.rs @@ -1,5 +1,8 @@ #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] +#![allow(unused_imports)] +#![allow(unused_mut)] +#![allow(improper_ctypes)] include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/bncsutil/src/auth.rs b/bncsutil/src/auth.rs new file mode 100644 index 0000000..d08a8bd --- /dev/null +++ b/bncsutil/src/auth.rs @@ -0,0 +1,150 @@ +use std::path::Path; + +use crate::*; + +pub fn auth_check( + is_tft: bool, + war3_version: u32, + war3_path: &Path, + key_roc: String, + key_tft: String, + value_string_formula: String, + mpq_file_name: String, + client_token: u32, + server_token: u32, +) -> Option<(u32, u64, Vec, Vec, String)> { + let w3exe = war3_path.join("Warcraft III.exe"); + let w3exe2 = war3_path.join("warcraft.exe"); + let w3exe3 = war3_path.join("war3.exe"); + + let file_war3_exe = if w3exe.exists() { + w3exe + } else if w3exe2.exists() { + w3exe2 + } else { + w3exe3 + }; + + let is_exist = file_war3_exe.exists(); + + dbg!(is_exist); + + if !is_exist { + println!("[BNCSUI] unable to open [{}]", file_war3_exe.display()); + + return None; + } + + // todotodo: check getExeInfo return value to ensure 1024 bytes was enough + + let (_, exe_info, exe_version) = get_exe_info(file_war3_exe.as_path()); + let mpq_ver = extract_mpq_number(mpq_file_name.to_string()).unwrap(); + + // for war3version <= 28, we use war3.exe, storm.dll, and game.dll + // for war3version == 29, we use Warcraft III.exe only + let exe_version_hash = if war3_version <= 28 { + let file_storm_dll = if war3_path.join("Storm.dll").exists() { + war3_path.join("Storm.dll") + } else { + war3_path.join("storm.dll") + }; + + let file_game_dll = war3_path.join("game.dll"); + + if !file_storm_dll.exists() { + println!("[BNCSUI] unable to open [{}]", file_storm_dll.display()); + } + + if !file_game_dll.exists() { + println!("[BNCSUI] unable to open [{}]", file_game_dll.display()); + } + + check_revision_flat( + value_string_formula, + file_war3_exe.as_path(), + file_storm_dll.as_path(), + file_game_dll.as_path(), + mpq_ver, + ) + } else { + let files = vec![file_war3_exe.as_path()]; + check_revision(value_string_formula, files, mpq_ver) + }; + + let key_info_roc = create_key_info(key_roc, client_token, server_token); + + if is_tft { + let key_info_tft = create_key_info(key_tft, client_token, server_token); + + if key_info_roc.len() == 36 && key_info_tft.len() == 36 { + return Some(( + exe_version, + exe_version_hash, + key_info_roc, + key_info_tft, + exe_info, + )); + } else { + println!("[BNCSUI] unable to create ROC key info - invalid ROC key"); + println!("[BNCSUI] unable to create TFT key info - invalid TFT key"); + } + } + + if key_info_roc.len() == 36 { + return Some(( + exe_version, + exe_version_hash, + key_info_roc, + vec![], + exe_info, + )); + } else { + println!("[BNCSUI] unable to create ROC key info - invalid ROC key"); + } + + return None; +} + +#[cfg(test)] +mod auth_tests { + use super::*; + + #[test] + fn test_auth_check() { + // logonType: '2 0 0 0', + // serverToken: '23 6 226 51', + // mpqFileTime: '128 105 207 135 198 27 214 1', + // ix86VerFileName: 'ver-IX86-1.mpq', + // valueString: 'A=1239576727 C=1604096186 B=4198521212 4 A=A+S B=B-C C=C^A A=A+B' + let is_tft = true; + let key_roc = "FFFFFFFFFFFFFFFFFFFFFFFFFF".to_string(); + let key_tft = "FFFFFFFFFFFFFFFFFFFFFFFFFF".to_string(); + let client_token: u32 = 130744796; + let server_token: u32 = 2115470359; + let war3_version = 28; + let war3_path = Path::new("../mock/"); + let value_string_formula = + "A=1239576727 C=1604096186 B=4198521212 4 A=A+S B=B-C C=C^A A=A+B".to_string(); + let mpq_file_name = "ver-IX86-1.mpq".to_string(); + + let (exe_version, exe_version_hash, key_info_roc, key_info_tft, exe_info) = auth_check( + is_tft, + war3_version, + war3_path, + key_roc, + key_tft, + value_string_formula, + mpq_file_name, + client_token, + server_token, + ) + .unwrap(); + + dbg!(exe_version_hash); + dbg!(key_info_roc); + dbg!(key_info_tft); + + assert_eq!(exe_version, 18613504); + assert_eq!(exe_info, "war3.exe 02/02/24 12:39:34 562152".to_string()); + } +} diff --git a/bncsutil/src/lib.rs b/bncsutil/src/lib.rs index 03ead54..dc7c3a7 100644 --- a/bncsutil/src/lib.rs +++ b/bncsutil/src/lib.rs @@ -6,25 +6,26 @@ use std::ffi::CString; use std::os::raw::c_char; use std::path::Path; +mod auth; mod nls; pub fn version() -> u64 { unsafe { bncsutil::bncsutil_getVersion() } } -pub fn version_string() -> String { - unsafe { - let mut exe_info_vec: Vec = vec![0i8; 1024]; - let exe_info_slice = exe_info_vec.as_mut_slice(); - let exe_info_ptr = exe_info_slice.as_mut_ptr(); - let length = bncsutil::bncsutil_getVersionString(exe_info_ptr); - let exe_info_string = - String::from_utf8(exe_info_slice.iter().map(|&c| c as u8).collect()).unwrap(); - let exe_info: String = exe_info_string.chars().take(length as usize).collect(); +// pub fn version_string() -> String { +// unsafe { +// let mut ver = CString::new("").unwrap(); +// let mut raw_ver = ver.into_raw(); +// let length = bncsutil::bncsutil_getVersionString(raw_ver); - exe_info - } -} +// dbg!(length); + +// let version = CString::from_raw(raw_ver).to_string_lossy().to_string(); + +// version +// } +// } pub fn get_exe_info(path: &Path) -> (i32, String, u32) { unsafe { @@ -46,7 +47,7 @@ pub fn get_exe_info(path: &Path) -> (i32, String, u32) { } } -pub fn check_revision(value: String, files: Vec<&Path>, mpq_number: i32) -> u32 { +pub fn check_revision(value: String, files: Vec<&Path>, mpq_number: u32) -> u64 { unsafe { let files_str = files .iter() @@ -65,13 +66,17 @@ pub fn check_revision(value: String, files: Vec<&Path>, mpq_number: i32) -> u32 value_cstr.as_ptr(), files_ptr.as_mut_ptr(), files_str.len() as i32, - mpq_number, + (mpq_number as i32).try_into().unwrap(), &mut result, ); - println!("result {:?}", result as u32); + println!("check_revision result {:?} err: {}", result, error_code); + + if error_code != 0 { + panic!("check_revision error: {}", error_code); + } - result as u32 + result } } @@ -80,12 +85,22 @@ pub fn check_revision_flat( file1: &Path, file2: &Path, file3: &Path, - mpq_number: i32, -) -> u32 { + mpq_number: u32, +) -> u64 { unsafe { - let file1_str = CString::new(file1.to_str().unwrap()).unwrap(); - let file2_str = CString::new(file2.to_str().unwrap()).unwrap(); - let file3_str = CString::new(file3.to_str().unwrap()).unwrap(); + if !file1.exists() { + panic!("File not found {}", file1.display()); + } + if !file2.exists() { + panic!("File2 not found {}", file2.display()); + } + if !file3.exists() { + panic!("File3 not found {}", file3.display()); + } + + let file1_str = CString::new(file1.to_str().expect("File1 error")).unwrap(); + let file2_str = CString::new(file2.to_str().expect("File2 error")).unwrap(); + let file3_str = CString::new(file3.to_str().expect("File3 error")).unwrap(); let value_cstr = CString::new(value).unwrap(); let mut result: u64 = 0; @@ -95,15 +110,19 @@ pub fn check_revision_flat( file1_str.as_ptr(), file2_str.as_ptr(), file3_str.as_ptr(), - mpq_number, + (mpq_number as i32).try_into().unwrap(), &mut result, ); - result as u32 + println!( + "check_revision_flat result {:?} err: {}", + result, error_code + ); + + result } } -// { CDKey: 'FFFFFFFFFFFFFFFFFFFFFFFFFF', clientToken: 130744796, serverToken: 1655005115 } { publicValue: 10992493, product: 5650, hash: '0 0 0 0 0 0 0 0' } pub fn keydecode_quick( cd_key: String, client_token: u32, @@ -114,22 +133,11 @@ pub fn keydecode_quick( panic!("Invalid Warcraft 3 Key provided"); } - let cd_key_str = CString::new(cd_key).unwrap(); + let cd_key_str = CString::new(cd_key).expect("Failed cd_key c_string"); let mut public_value = 0 as u32; let mut product = 0 as u32; let mut hash_buf = [0i8; 20]; - /* - MEXP(int) kd_quick( - const char* cd_key, - uint32_t client_token, - uint32_t server_token, - uint32_t* public_value, - uint32_t* product, - char* hash_buffer, - size_t buffer_len - ) - */ let status = bncsutil::kd_quick( cd_key_str.as_ptr(), client_token, @@ -140,15 +148,15 @@ pub fn keydecode_quick( hash_buf.len(), ); - if status == 0 { + if status != 1 { panic!("Failed to kd_quick") } - ( + return ( public_value.clone(), product.clone(), Vec::from(hash_buf.map(|c| c as u8)), - ) + ); } } @@ -179,6 +187,26 @@ pub fn pvpgn_password_hash(password: String) -> Vec { Vec::from(out_buf.map(|c| c as u8)) } } + +/** + * extracts number from file name "ver-IX86-1.mpq" + */ +pub fn extract_mpq_number(name: String) -> Option { + if name.len() == 0 || !name.contains(".") { + return None; + } + + let parts: Vec<&str> = name.split(".").collect(); + let first = parts.first().unwrap(); + let first_parts: Vec<&str> = first.split("-").collect(); + let last_num = first_parts.last().unwrap(); + let num: u32 = last_num + .parse() + .expect("expect number in extract_MPQ_number"); + + Some(num) +} + #[cfg(test)] mod bncs_tests { use super::*; @@ -188,10 +216,10 @@ mod bncs_tests { assert_eq!(version(), 10300); } - #[test] - fn test_version_string() { - assert_eq!(version_string(), "1.3.0"); - } + // #[test] + // fn test_version_string() { + // assert_eq!(version_string(), "1.3.0"); + // } #[test] fn test_get_exe_info() { @@ -211,7 +239,7 @@ mod bncs_tests { assert_eq!( check_revision_flat(value, file1, file2, file3, 1), - 2392268693 as u32 + 2392268693 as u64 ) } @@ -221,7 +249,7 @@ mod bncs_tests { let file1 = Path::new("../mock/war3.exe"); let files = vec![file1]; - assert_eq!(check_revision(value, files, 1), 1397123850 as u32) + assert_eq!(check_revision(value, files, 1), 1397123850 as u64) } // { @@ -273,4 +301,11 @@ mod bncs_tests { ] ) } + + #[test] + fn test_extract_mpq_number() { + let ver = extract_mpq_number("ver-IX86-1.mpq".to_string()); + + assert_eq!(ver, Some(1)); + } }