feat: replace hosts file with DLL injection hook
Adds openfut-hook/, a Windows DLL (cdylib, x86_64-pc-windows-gnu) that patches the IAT of FIFA 23 at load time to redirect getaddrinfo calls for fut.ea.com / utas.*.fut.ea.com to 127.0.0.1, sending all FUT traffic to the local bridge — no /etc/hosts changes needed. Deployment: the launcher copies openfut_hook.dll into the FIFA 23 game folder as version.dll (a DLL FIFA loads but delegates to system). Proton picks up the local copy automatically when you set: WINEDLLOVERRIDES="version=n,b" %command% in Steam launch options. Also updates cert install to try the Wine/Proton cert store (wine certutil) before falling back to the Linux system CA store, and removes all hosts file code from setup.rs / app.rs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
[target.x86_64-pc-windows-gnu]
|
||||
linker = "x86_64-w64-mingw32-gcc"
|
||||
ar = "x86_64-w64-mingw32-ar"
|
||||
Generated
+83
@@ -0,0 +1,83 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "openfut-hook"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
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"
|
||||
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "openfut-hook"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
windows-sys = { version = "0.59", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Memory",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_Networking_WinSock",
|
||||
] }
|
||||
|
||||
[profile.release]
|
||||
opt-level = "s"
|
||||
strip = true
|
||||
panic = "abort"
|
||||
@@ -0,0 +1,44 @@
|
||||
/// Network hooks — redirect EA FUT hostnames to localhost.
|
||||
use std::{ffi::CStr, sync::OnceLock};
|
||||
|
||||
use windows_sys::Win32::Networking::WinSock::{ADDRINFOA, getaddrinfo as sys_getaddrinfo};
|
||||
|
||||
const INTERCEPT: &[&str] = &[
|
||||
"fut.ea.com",
|
||||
"utas.mob.v4.fut.ea.com",
|
||||
"utas.s2.fut.ea.com",
|
||||
];
|
||||
|
||||
type GetaddrinfoFn = unsafe extern "system" fn(
|
||||
*const u8,
|
||||
*const u8,
|
||||
*const ADDRINFOA,
|
||||
*mut *mut ADDRINFOA,
|
||||
) -> i32;
|
||||
|
||||
static REAL: OnceLock<GetaddrinfoFn> = OnceLock::new();
|
||||
|
||||
pub fn set_real(f: GetaddrinfoFn) {
|
||||
let _ = REAL.set(f);
|
||||
}
|
||||
|
||||
pub unsafe extern "system" fn hooked_getaddrinfo(
|
||||
node_name: *const u8,
|
||||
service_name: *const u8,
|
||||
hints: *const ADDRINFOA,
|
||||
result: *mut *mut ADDRINFOA,
|
||||
) -> i32 {
|
||||
if !node_name.is_null() {
|
||||
if let Ok(host) = CStr::from_ptr(node_name as *const i8).to_str() {
|
||||
for target in INTERCEPT {
|
||||
if host.eq_ignore_ascii_case(target) {
|
||||
let local = b"127.0.0.1\0";
|
||||
let real = REAL.get().copied().unwrap_or(sys_getaddrinfo);
|
||||
return real(local.as_ptr(), service_name, hints, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let real = REAL.get().copied().unwrap_or(sys_getaddrinfo);
|
||||
real(node_name, service_name, hints, result)
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/// IAT (Import Address Table) patching.
|
||||
///
|
||||
/// We define the PE structs ourselves rather than pulling in windows-sys PE
|
||||
/// headers (which are in a different crate / feature path).
|
||||
use windows_sys::Win32::{
|
||||
Foundation::HMODULE,
|
||||
System::{
|
||||
LibraryLoader::{GetModuleHandleA, GetProcAddress},
|
||||
Memory::{VirtualProtect, PAGE_EXECUTE_READWRITE},
|
||||
},
|
||||
};
|
||||
|
||||
// ── Minimal PE struct definitions ─────────────────────────────────────────────
|
||||
|
||||
#[repr(C)]
|
||||
struct ImageDosHeader {
|
||||
e_magic: u16,
|
||||
_pad: [u16; 29],
|
||||
e_lfanew: i32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct ImageFileHeader {
|
||||
machine: u16,
|
||||
number_of_sections: u16,
|
||||
time_date_stamp: u32,
|
||||
pointer_to_symbol_table: u32,
|
||||
number_of_symbols: u32,
|
||||
size_of_optional_header: u16,
|
||||
characteristics: u16,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct ImageDataDirectory {
|
||||
virtual_address: u32,
|
||||
size: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct ImageOptionalHeader64 {
|
||||
magic: u16,
|
||||
_pad: [u8; 106],
|
||||
data_directory: [ImageDataDirectory; 16],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct ImageNtHeaders64 {
|
||||
signature: u32,
|
||||
file_header: ImageFileHeader,
|
||||
optional_header: ImageOptionalHeader64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct ImageImportDescriptor {
|
||||
original_first_thunk: u32,
|
||||
time_date_stamp: u32,
|
||||
forwarder_chain: u32,
|
||||
name: u32,
|
||||
first_thunk: u32,
|
||||
}
|
||||
|
||||
// ── IAT patching ──────────────────────────────────────────────────────────────
|
||||
|
||||
/// Replace every IAT slot in the main module that currently holds
|
||||
/// `original_fn` with `hook_fn`.
|
||||
///
|
||||
/// # Safety
|
||||
/// Caller must ensure hook_fn has the same calling convention and signature.
|
||||
pub unsafe fn patch_iat(original_fn: *const (), hook_fn: *const ()) -> usize {
|
||||
let module = GetModuleHandleA(std::ptr::null());
|
||||
patch_module(module, original_fn, hook_fn)
|
||||
}
|
||||
|
||||
unsafe fn patch_module(
|
||||
module: HMODULE,
|
||||
original_fn: *const (),
|
||||
hook_fn: *const (),
|
||||
) -> usize {
|
||||
if module.is_null() {
|
||||
return 0;
|
||||
}
|
||||
let base = module as usize;
|
||||
let dos = base as *const ImageDosHeader;
|
||||
if (*dos).e_magic != 0x5A4D {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let nt = (base + (*dos).e_lfanew as usize) as *const ImageNtHeaders64;
|
||||
|
||||
let import_rva = (*nt).optional_header.data_directory[1].virtual_address as usize;
|
||||
if import_rva == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut desc = (base + import_rva) as *const ImageImportDescriptor;
|
||||
let mut count = 0usize;
|
||||
|
||||
while (*desc).name != 0 {
|
||||
let ft = (*desc).first_thunk as usize;
|
||||
let iat_slot = (base + ft) as *mut usize;
|
||||
let mut i = 0usize;
|
||||
|
||||
loop {
|
||||
let val = *iat_slot.add(i);
|
||||
if val == 0 {
|
||||
break;
|
||||
}
|
||||
if val == original_fn as usize {
|
||||
let target = iat_slot.add(i) as *const std::ffi::c_void;
|
||||
let mut old: u32 = 0;
|
||||
VirtualProtect(target, std::mem::size_of::<usize>(), PAGE_EXECUTE_READWRITE, &mut old);
|
||||
*iat_slot.add(i) = hook_fn as usize;
|
||||
VirtualProtect(target, std::mem::size_of::<usize>(), old, &mut old);
|
||||
count += 1;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
desc = desc.add(1);
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
|
||||
/// Resolve the address of an exported function from an already-loaded DLL.
|
||||
pub unsafe fn resolve(dll: &[u8], fn_name: &[u8]) -> *const () {
|
||||
let module = GetModuleHandleA(dll.as_ptr());
|
||||
if module.is_null() {
|
||||
return std::ptr::null();
|
||||
}
|
||||
match GetProcAddress(module, fn_name.as_ptr()) {
|
||||
Some(f) => f as *const (),
|
||||
None => std::ptr::null(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
mod hooks;
|
||||
mod iat;
|
||||
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{BOOL, HMODULE, TRUE},
|
||||
System::SystemServices::DLL_PROCESS_ATTACH,
|
||||
Networking::WinSock::ADDRINFOA,
|
||||
};
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn DllMain(
|
||||
_module: HMODULE,
|
||||
reason: u32,
|
||||
_reserved: *mut (),
|
||||
) -> BOOL {
|
||||
if reason == DLL_PROCESS_ATTACH {
|
||||
install_hooks();
|
||||
}
|
||||
TRUE
|
||||
}
|
||||
|
||||
unsafe fn install_hooks() {
|
||||
let real_ptr = iat::resolve(b"ws2_32.dll\0", b"getaddrinfo\0");
|
||||
if real_ptr.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let real_fn: unsafe extern "system" fn(
|
||||
*const u8,
|
||||
*const u8,
|
||||
*const ADDRINFOA,
|
||||
*mut *mut ADDRINFOA,
|
||||
) -> i32 = std::mem::transmute(real_ptr);
|
||||
|
||||
hooks::set_real(real_fn);
|
||||
iat::patch_iat(real_ptr, hooks::hooked_getaddrinfo as *const ());
|
||||
}
|
||||
Reference in New Issue
Block a user