use std::{ ffi::CStr, sync::{ atomic::{AtomicBool, Ordering}, OnceLock, }, }; use windows_sys::Win32::Networking::WinSock::{getaddrinfo as sys_getaddrinfo, ADDRINFOA}; type GetaddrinfoFn = unsafe extern "system" fn(*const u8, *const u8, *const ADDRINFOA, *mut *mut ADDRINFOA) -> i32; static REAL: OnceLock = OnceLock::new(); // NUL-terminated dotted-quad of the resolved OpenFUT server, built once at init // from the SAME shared config the socket hooks use. getaddrinfo redirects EA // hostnames here so DNS resolves to the configured server. If configuration was // missing/invalid this stays empty and EA hostnames are NOT redirected (no // loopback fallback). static REDIRECT_HOST: OnceLock> = OnceLock::new(); // Flipped to true the first time we successfully apply the runtime cert patch. // The patch is deferred to here (rather than DllMain) because EAWebKit.dll may // not be loaded yet when the hook DLL is injected. static CERT_PATCHED: AtomicBool = AtomicBool::new(false); pub fn set_real(f: GetaddrinfoFn) { let _ = REAL.set(f); } /// Install the resolved redirect IPv4 (dotted-quad) getaddrinfo will hand back /// for EA hostnames. Called once at init from the shared resolved server. pub fn set_redirect_ip(ip: std::net::Ipv4Addr) { let mut bytes = ip.to_string().into_bytes(); bytes.push(0); let _ = REDIRECT_HOST.set(bytes); } /// Returns true if `host` is an EA / EA-Sports domain that should be redirected /// to the local OpenFUT bridge. fn is_ea_host(host: &str) -> bool { let h = host.to_ascii_lowercase(); h.ends_with(".ea.com") || h == "ea.com" || h.ends_with(".easports.com") || h == "easports.com" || h.ends_with(".ugc.footapi.com") || h.ends_with(".footapi.com") } 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() { crate::write_log(&format!("openfut_hook: getaddrinfo({host})\n")); if is_ea_host(host) { // Apply the ProtoSSL cert-verify bypass the first time we see an EA // hostname — EAWebKit.dll must be loaded by now because it's calling us. if !CERT_PATCHED.load(Ordering::Relaxed) { if crate::ssl_patch::patch_eawebkit_cert_verify() { CERT_PATCHED.store(true, Ordering::Relaxed); crate::write_log( "openfut_hook: ProtoSSL cert-verify patched (lazy, from getaddrinfo)\n", ); } else { crate::write_log( "openfut_hook: ProtoSSL cert-verify patch FAILED in getaddrinfo\n", ); } } // Only redirect when a server was configured & resolved. If not, // fall through to the real resolver — we never invent a loopback // destination here. match REDIRECT_HOST.get() { Some(redirect) => { let real = REAL.get().copied().unwrap_or(sys_getaddrinfo); return real(redirect.as_ptr(), service_name, hints, result); } None => { crate::write_log( "openfut_hook: EA host seen but no OpenFUT server configured — NOT redirecting\n", ); } } } } } let real = REAL.get().copied().unwrap_or(sys_getaddrinfo); real(node_name, service_name, hints, result) }