diff --git a/Cargo.lock b/Cargo.lock index 6f108b8..5628d47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,12 +7,34 @@ name = "LauncherTweaks" version = "0.1.0" dependencies = [ "proxy-dll", + "retour", "serde", "skidscan", "toml", "windows", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "ctor" version = "0.1.26" @@ -29,6 +51,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "hashbrown" version = "0.15.2" @@ -51,12 +83,41 @@ version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +[[package]] +name = "libudis86-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139bbf9ddb1bfc90c1ac64dd2923d9c957cd433cee7315c018125d72ab08a6b0" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mmap-fixed-fixed" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0681853891801e4763dc252e843672faf32bcfee27a0aa3b19733902af450acc" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -110,6 +171,34 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "region" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" +dependencies = [ + "bitflags", + "libc", + "mach2", + "windows-sys", +] + +[[package]] +name = "retour" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9af44d40e2400b44d491bfaf8eae111b09f23ac4de6e92728e79d93e699c527" +dependencies = [ + "cfg-if", + "generic-array", + "libc", + "libudis86-sys", + "mmap-fixed-fixed", + "once_cell", + "region", + "slice-pool2", +] + [[package]] name = "serde" version = "1.0.219" @@ -139,6 +228,12 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "skidscan" version = "2.0.1" @@ -160,6 +255,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "slice-pool2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3d689654af89bdfeba29a914ab6ac0236d382eb3b764f7454dde052f2821f8" + [[package]] name = "syn" version = "1.0.109" @@ -247,12 +348,24 @@ dependencies = [ "winnow 0.7.6", ] +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "winapi" version = "0.3.9" @@ -376,6 +489,79 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "0.5.40" diff --git a/Cargo.toml b/Cargo.toml index a71d4de..d6264f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,12 @@ edition = "2024" [lib] crate-type = ["cdylib"] -name = "version" +name = "winhttp" [dependencies] skidscan = { version = "2.0", default-features = false } proxy-dll = { git = "https://github.com/redstrate/dll-proxy-rs/", default-features = false } # needed to cross-compile from Linux -windows = { version = "0.61", features = ["Win32_Foundation", "Win32_System_SystemServices", "Win32_UI_WindowsAndMessaging"], default-features = false } +windows = { version = "0.61", features = ["Win32_Foundation", "Win32_System_SystemServices", "Win32_UI_WindowsAndMessaging", "Win32_System_LibraryLoader", "Win32_Networking_WinHttp"], default-features = false } 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 } diff --git a/README.md b/README.md index bc8c390..06201e6 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Tweaks for the official FFXIV launcher. ## Features * Allows configuring the launcher URL, enabling you to write custom launcher pages. +* Force the launcher/boot executable to use the system proxy. (The web browser portions of the launcher already use the system proxy.) ## Usage @@ -41,7 +42,9 @@ error occurred in cc-rs: failed to find tool "x86_64-w64-mingw32-gcc": No such f Then you need to install the MinGW toolchain, as one of our dependencies has to compile C code. -## Launcher Functions +## Tips & Tricks + +### Launcher Functions Here are some interesting native functions that are callable from JavaScript. To call these, use `window.external.user`. @@ -49,6 +52,10 @@ Here are some interesting native functions that are callable from JavaScript. To * `requestExit`: Despite the name, exits the launcher immediately. * `requestReboot`: Exits the launcher immediately, doesn't seem to come back though? +### Logging Proxy + +You can use the `winhttp_proxy` config option to sniff the non-web browser traffic from the launcher. You need a HTTP proxy capable of logging of course, I personally use [mitmproxy](https://mitmproxy.org). + ## Credits Initially based off of [NotNite](https://github.com/NotNite)'s [benchtweaks](https://github.com/NotNite/benchtweaks/) and also inspiration for the name! diff --git a/src/config.rs b/src/config.rs index 3737a63..e08c416 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,23 +1,9 @@ use serde::Deserialize; -#[derive(Deserialize)] +#[derive(Deserialize, Default)] pub struct Config { - #[serde(default = "Config::default_launcher_url")] - pub launcher_url: String, -} - -impl Default for Config { - fn default() -> Self { - Self { - launcher_url: Self::default_launcher_url(), - } - } -} - -impl Config { - fn default_launcher_url() -> String { - "about:blank".to_string() - } + pub launcher_url: Option, + pub winhttp_proxy: Option, } pub fn get_config() -> Config { diff --git a/src/lib.rs b/src/lib.rs index c0459f9..b66d771 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,10 +4,13 @@ mod config; use config::get_config; mod utilities; -use utilities::{find_signature, get_utf16_bytes, show_message}; +use utilities::{find_signature, find_symbol, get_utf16_bytes, show_message}; use proxy_dll::proxy; +use retour::static_detour; use skidscan::Signature; +use std::ffi::c_void; +use windows::{Win32::Networking::WinHttp::*, core::*}; /// So we can have a static address that won't change. static mut OVERRIDE_URL: Vec = Vec::new(); @@ -15,10 +18,18 @@ static mut OVERRIDE_URL: Vec = Vec::new(); /// The first string in the launcher URLs array. const ABOUT_BLANK_URL: &str = "about:blank"; -/// Filename of the launcher. +/// Filename of the launcher executable. const LAUNCHER_FILENAME: &str = "ffxivlauncher64.exe"; +/// Filename of the boot executable. +const BOOT_FILENAME: &str = "ffxivboot64.exe"; + fn overwrite_launcher_url() { + let config = get_config(); + let Some(launcher_url) = config.launcher_url else { + return; + }; + unsafe { // We are going to overwrite a portion of memory that contains the two main launcher URLs. // @@ -43,13 +54,61 @@ fn overwrite_launcher_url() { (url_array_address + std::mem::size_of::() as u64) as *mut u64; // Override!!!! - OVERRIDE_URL.extend_from_slice(&get_utf16_bytes(&get_config().launcher_url)); + OVERRIDE_URL.extend_from_slice(&get_utf16_bytes(&launcher_url)); *launcher_url_launcher_page = OVERRIDE_URL.as_ptr() as u64; } } -fn tweak_launcher() { - overwrite_launcher_url(); +static_detour! { + static WinHttpOpen: fn(PCWSTR, WINHTTP_ACCESS_TYPE, PCWSTR, PCWSTR, u32) -> *mut c_void; +} + +fn winhttpopen_detour( + pszagentw: PCWSTR, + _dwaccesstype: WINHTTP_ACCESS_TYPE, + _pszproxyw: PCWSTR, + pszproxybypassw: PCWSTR, + dwflags: u32, +) -> *mut c_void { + let config = get_config(); + let http_proxy = config + .winhttp_proxy + .expect("Failed to get winhttp_proxy config value"); + let http_proxy: Vec = http_proxy + .encode_utf16() + .chain(std::iter::once(0)) + .collect(); + + // See https://learn.microsoft.com/en-us/windows/win32/api/winhttp/nf-winhttp-winhttpopen + WinHttpOpen.call( + pszagentw, + WINHTTP_ACCESS_TYPE_NAMED_PROXY, + PCWSTR(http_proxy.as_ptr() as _), + pszproxybypassw, + dwflags, + ) +} + +fn use_system_proxy() { + let config = get_config(); + if config.winhttp_proxy.is_none() { + return; + } + + unsafe { + let winhttpopen_addr = + find_symbol("WinHttpOpen", "winhttp.dll").expect("Failed to find WinHttpOpen address"); + let winhttpopen_fn = std::mem::transmute::< + *mut u8, + fn(PCWSTR, WINHTTP_ACCESS_TYPE, PCWSTR, PCWSTR, u32) -> *mut c_void, + >(winhttpopen_addr); + WinHttpOpen + .initialize(winhttpopen_fn, winhttpopen_detour) + .expect("Failed to initialize WinHttpOpen hook"); + WinHttpOpen + .enable() + .expect("Failed to hook into WinHttpOpen"); + } } #[proxy] @@ -72,8 +131,17 @@ fn main() { .file_name() .expect("Failed to get executable filename"); - // We only care about the launcher for now - if current_exe == LAUNCHER_FILENAME { - tweak_launcher(); + match current_exe + .to_str() + .expect("Failed to parse executable filename") + { + LAUNCHER_FILENAME => { + use_system_proxy(); + overwrite_launcher_url(); + } + BOOT_FILENAME => { + use_system_proxy(); + } + _ => {} } } diff --git a/src/utilities.rs b/src/utilities.rs index 7b44003..d8ec156 100644 --- a/src/utilities.rs +++ b/src/utilities.rs @@ -1,7 +1,11 @@ use std::ffi::CString; use skidscan::Signature; -use windows::{Win32::UI::WindowsAndMessaging::MessageBoxA, core::*}; +use windows::{ + Win32::System::LibraryLoader::{GetModuleHandleW, GetProcAddress}, + Win32::UI::WindowsAndMessaging::MessageBoxA, + core::*, +}; use crate::LAUNCHER_FILENAME; @@ -9,6 +13,15 @@ pub fn find_signature(sig: Signature) -> Option<*mut u8> { unsafe { sig.scan_module(LAUNCHER_FILENAME).ok() } } +pub fn find_symbol(symbol: &str, module: &str) -> Option<*mut u8> { + let module: Vec = module.encode_utf16().chain(std::iter::once(0)).collect(); + let symbol = CString::new(symbol).expect("Failed to parse symbol!"); + unsafe { + let handle = GetModuleHandleW(PCWSTR(module.as_ptr() as _)).expect("Module not loaded!"); + GetProcAddress(handle, PCSTR(symbol.as_ptr() as _)).map(|func| func as *mut u8) + } +} + pub fn show_message(message: &str) { unsafe { let message = CString::new(message).unwrap();