diff --git a/Cargo.lock b/Cargo.lock index 6e6b17f..02d34a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,8 @@ version = 4 name = "LauncherTweaks" version = "0.1.0" dependencies = [ + "base64", + "physis", "proxy-dll", "retour", "serde", @@ -14,12 +16,53 @@ dependencies = [ "windows", ] +[[package]] +name = "array-init" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "binrw" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173901312e9850391d4d7c1318c4e099fdc037d61870fca427429830efdb4e5f" +dependencies = [ + "array-init", + "binrw_derive", + "bytemuck", +] + +[[package]] +name = "binrw_derive" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb515fdd6f8d3a357c8e19b8ec59ef53880807864329b1cb1cba5c53bf76557e" +dependencies = [ + "either", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bytemuck" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" + [[package]] name = "cc" version = "1.2.19" @@ -29,12 +72,37 @@ dependencies = [ "shlex", ] +[[package]] +name = "cfg-expr" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d4ba6e40bd1184518716a6e1a781bf9160e286d219ccdb8ab2612e74cfe4789" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + [[package]] name = "ctor" version = "0.1.26" @@ -45,6 +113,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "equivalent" version = "1.0.2" @@ -61,12 +135,28 @@ dependencies = [ "version_check", ] +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "indexmap" version = "2.9.0" @@ -93,6 +183,16 @@ dependencies = [ "libc", ] +[[package]] +name = "libz-ng-sys" +version = "1.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7118c2c2a3c7b6edc279a8b19507672b9c4d716f95e671172dfa4e23f9fd824" +dependencies = [ + "cmake", + "libc", +] + [[package]] name = "mach2" version = "0.4.2" @@ -124,12 +224,67 @@ dependencies = [ "windows", ] +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "physis" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53ea3f3a8d52b4dd21bfe3a3e0730cfdacbfa6f75ab002b60c2164fbb350c1d3" +dependencies = [ + "binrw", + "bitflags", + "half", + "libz-ng-sys", + "modular-bitfield", + "system-deps", + "texture2ddecoder", + "tracing", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "proc-macro-crate" version = "3.3.0" @@ -263,6 +418,18 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a3d689654af89bdfeba29a914ab6ac0236d382eb3b764f7454dde052f2821f8" +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "1.0.109" @@ -285,6 +452,34 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "system-deps" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "texture2ddecoder" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54f0e5ca5dceb9b44d0f376de51782277c25fb42ffe1dad08d847f3a156d26d9" +dependencies = [ + "paste", +] + [[package]] name = "toml" version = "0.8.20" @@ -319,6 +514,25 @@ dependencies = [ "winnow", ] +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + [[package]] name = "typenum" version = "1.18.0" @@ -331,6 +545,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index 89416e8..60104b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ windows = { version = "0.61", features = ["Win32_Foundation", "Win32_UI_WindowsA serde = { version = "1.0", features = ["derive"], default-features = false } toml = { version = "0.8", features = ["parse"], default-features = false } retour = { version = "0.3", features = ["static-detour"], default-features = false } +physis = { version = "0.3" } +base64 = "0.22" [replace] "mmap-fixed-fixed:0.1.3" = { git = "https://github.com/redstrate/rust-mmap-fixed-fixed" } # my fork which removes the winapi dependency diff --git a/README.md b/README.md index 205e30f..6d6046d 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Tweaks for the official FFXIV launcher. * Force the launcher/boot executable to always communicate over HTTP instead of HTTPS, to make it easier to replace with local services. * Bypasses the (soft) WebView2 requirement introduced in Patch 7.21. * Disable the boot version check. +* Add extra arguments when launching the game. ## Usage diff --git a/src/blowfish.rs b/src/blowfish.rs new file mode 100644 index 0000000..71434ff --- /dev/null +++ b/src/blowfish.rs @@ -0,0 +1,50 @@ +const checksum_table: [char; 16] = [ + 'f', 'X', '1', 'p', 'G', 't', 'd', 'S', '5', 'C', 'A', 'P', '4', '_', 'V', 'L', +]; + +use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; +use physis::blowfish::Blowfish; +use windows::Win32::System::SystemInformation::*; + +fn calculate_checksum(key: u32) -> char { + let value = key & 0x000F0000; + checksum_table[(value >> 16) as usize] +} + +fn calculate_blowfish_key() -> u32 { + let tick_count; + unsafe { + tick_count = GetTickCount(); + } + + let ticks = tick_count & 0xFFFFFFFFu32; + ticks & 0xFFFF0000u32 +} + +fn initialize_blowfish() -> Blowfish { + let buffer = format!("{:08x}", calculate_blowfish_key()); + Blowfish::new(buffer.as_bytes()) +} + +pub fn decrypt_arguments(commandline: &str) -> String { + let index = commandline.find("sqex").unwrap(); + + let base64 = &commandline[index + 8..commandline.len() - 6]; + let blowfish = initialize_blowfish(); + let decoded = URL_SAFE_NO_PAD.decode(base64.as_bytes()).unwrap(); + + let result = blowfish.decrypt(&decoded).unwrap(); + + let str = String::from_utf8(result).unwrap(); + str.trim_matches(char::from(0)).to_string() +} + +pub fn encrypt_arguments(commandline: &str) -> String { + let blowfish = initialize_blowfish(); + let encrypted = blowfish.encrypt(commandline.as_bytes()).unwrap(); + + let encoded = URL_SAFE_NO_PAD.encode(encrypted); + let checksum = calculate_checksum(calculate_blowfish_key()); + + format!("//**sqex0003{}{}**//", encoded, checksum) +} diff --git a/src/config.rs b/src/config.rs index 2eced8f..b1a18cf 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,6 +12,7 @@ pub struct Config { pub force_http: bool, pub game_patch_server: Option, pub boot_patch_server: Option, + pub extra_game_arguments: Option, } impl Config { diff --git a/src/lib.rs b/src/lib.rs index 85370b9..4513fd9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,8 @@ #![allow(static_mut_refs)] +mod blowfish; +use blowfish::{decrypt_arguments, encrypt_arguments}; + mod config; use config::get_config; @@ -323,6 +326,105 @@ fn disable_boot_version_check() { } } +static_detour! { + static CreateProcessW: fn(PCWSTR, PWSTR, *mut c_void, *mut c_void, bool, u32, *mut c_void, PCWSTR, *mut c_void, *mut c_void) -> bool; +} + +fn createprocessw_detour( + lpapplicationname: PCWSTR, + lpcommandline: PWSTR, + lpprocessattributes: *mut c_void, + lpthreadattributes: *mut c_void, + binherithandles: bool, + dwcreationflags: u32, + lpenvironment: *mut c_void, + lpcurrentdirectory: PCWSTR, + lpstartupinfo: *mut c_void, + lpprocessinformation: *mut c_void, +) -> bool { + let config = get_config(); + + unsafe { + let old_commandline = lpcommandline.to_string().unwrap(); + if !old_commandline.contains("ffxiv_dx11.exe") { + return CreateProcessW.call( + lpapplicationname, + lpcommandline, + lpprocessattributes, + lpthreadattributes, + binherithandles, + dwcreationflags, + lpenvironment, + lpcurrentdirectory, + lpstartupinfo, + lpprocessinformation, + ); + } + + let decrypted = decrypt_arguments(&old_commandline); + + let mut commandline = decrypted.to_string(); + commandline.push_str(&config.extra_game_arguments.unwrap()); + + let new_commandline = format!( + "{} \"{}\"", + &old_commandline[..old_commandline.find(" \"//**sqex").unwrap()], + encrypt_arguments(&commandline) + ); + + let new_commandline: Vec = new_commandline + .encode_utf16() + .chain(std::iter::once(0)) + .collect(); + + CreateProcessW.call( + lpapplicationname, + PWSTR(new_commandline.as_ptr() as _), + lpprocessattributes, + lpthreadattributes, + binherithandles, + dwcreationflags, + lpenvironment, + lpcurrentdirectory, + lpstartupinfo, + lpprocessinformation, + ) + } +} + +fn add_game_args() { + let config = get_config(); + if !config.extra_game_arguments.is_some() { + return; + } + + unsafe { + let createprocessw_addr = find_symbol("CreateProcessW", "Kernel32.dll") + .expect("Failed to find CreateProcessW address"); + let createprocessw_fn = std::mem::transmute::< + *mut u8, + fn( + PCWSTR, + PWSTR, + *mut c_void, + *mut c_void, + bool, + u32, + *mut c_void, + PCWSTR, + *mut c_void, + *mut c_void, + ) -> bool, + >(createprocessw_addr); + CreateProcessW + .initialize(createprocessw_fn, createprocessw_detour) + .expect("Failed to initialize CreateProcessW hook"); + CreateProcessW + .enable() + .expect("Failed to hook into CreateProcessW"); + } +} + #[proxy] fn main() { // Emit panic messages with message boxes @@ -352,6 +454,7 @@ fn main() { overwrite_launcher_url(); overwrite_patch_url(); force_http(); + add_game_args(); } BOOT_FILENAME => { use_system_proxy();