From ace27b7e6c6e4c287dabd62761979436cd0a1580 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sat, 3 May 2025 16:33:21 -0400 Subject: [PATCH] Add an option to inject new command line arguments for the game This can be used to add custom lobbies to the game, otherwise there's no way to normally. (The retail client will use the built-in URLs in the executable, the launcher doesn't pass any.) --- Cargo.lock | 220 ++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 + README.md | 1 + src/blowfish.rs | 50 +++++++++++ src/config.rs | 1 + src/lib.rs | 103 +++++++++++++++++++++++ 6 files changed, 377 insertions(+) create mode 100644 src/blowfish.rs 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();