1
Fork 0
mirror of https://github.com/redstrate/LauncherTweaks.git synced 2025-05-20 17:37:46 +00:00

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.)
This commit is contained in:
Joshua Goins 2025-05-03 16:33:21 -04:00
parent 282a2d0fd8
commit ace27b7e6c
6 changed files with 377 additions and 0 deletions

220
Cargo.lock generated
View file

@ -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"

View file

@ -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

View file

@ -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

50
src/blowfish.rs Normal file
View file

@ -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)
}

View file

@ -12,6 +12,7 @@ pub struct Config {
pub force_http: bool,
pub game_patch_server: Option<String>,
pub boot_patch_server: Option<String>,
pub extra_game_arguments: Option<String>,
}
impl Config {

View file

@ -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<u16> = 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();