feat(fifa17-hook): redirect FIFA17 sockets from shared config
The fifa17 hook path installed no connection redirect (only a module-map dump), so FIFA17 relied entirely on Linux iptables DNAT / hosts (and had no Windows equivalent). Add an in-process, config-driven redirect for the fifa17 build: - openfut-common gains ResolvedServer::redirect_for_ea_port(): the single shared decision (EA source-port signature -> configured OpenFUT host+port, network byte order), reused by the hook so it and openfut.cfg agree by construction. Covers 443->https, 10041/42230->blaze_redirector, 42127->blaze_main. - connect_hook: redirect_if_ea now dispatches to a config-driven rewrite when armed (rewrites to the CONFIGURED, possibly remote, server -- not hardcoded 127.0.0.1), matching by EA source port so a hardcoded EA IP (159.153.51.20 redirector) and a DNS-resolved one both land on the server. Legacy loopback path retained only for the not-yet-retired FIFA23 build. - fifa17::install() reads openfut.cfg next to FIFA17.exe via openfut-common and installs connect + WSAConnect (IAT) + ConnectEx (shared redirect). Fail-safe: missing/invalid config installs NO redirect (traffic untouched), never a corrupt sockaddr. Tests: openfut-common redirect map/sockaddr/endian/remote-host/unknown-port. Cross-builds x86_64-pc-windows-gnu --features fifa17; clippy -D warnings clean. No Linux fallback removed (migration gate).
This commit is contained in:
@@ -97,7 +97,7 @@ unsafe fn restore_original(target: *mut u8) {
|
||||
/// The returned buffer is 28 bytes (enough for a `sockaddr_in6`); the second value is
|
||||
/// how many of those bytes are meaningful (16 for v4, 28 for v6). `pub(crate)` so the
|
||||
/// ConnectEx path can share this one implementation.
|
||||
pub(crate) unsafe fn redirect_if_ea(name: *const u8, namelen: i32) -> Option<([u8; 28], i32)> {
|
||||
unsafe fn redirect_loopback(name: *const u8, namelen: i32) -> Option<([u8; 28], i32)> {
|
||||
if namelen < 8 || name.is_null() {
|
||||
return None;
|
||||
}
|
||||
@@ -172,6 +172,89 @@ pub(crate) unsafe fn redirect_if_ea(name: *const u8, namelen: i32) -> Option<([u
|
||||
}
|
||||
}
|
||||
|
||||
/// The armed FIFA17 redirect target, resolved once from `openfut.cfg` via
|
||||
/// `openfut-common`. When set, `redirect_if_ea` rewrites matched EA connections
|
||||
/// to this configured server; when unset, the legacy loopback path is used.
|
||||
static REDIRECT: OnceLock<openfut_common::ResolvedServer> = OnceLock::new();
|
||||
|
||||
/// Arm the config-driven redirect (FIFA17). Idempotent: the first call wins.
|
||||
pub fn set_redirect(server: openfut_common::ResolvedServer) {
|
||||
let _ = REDIRECT.set(server);
|
||||
}
|
||||
|
||||
/// Dispatch: config-driven (FIFA17, shared `openfut-common` map + configured
|
||||
/// host) when armed, else the legacy hardcoded-loopback rewrite.
|
||||
pub(crate) unsafe fn redirect_if_ea(name: *const u8, namelen: i32) -> Option<([u8; 28], i32)> {
|
||||
if namelen < 8 || name.is_null() {
|
||||
return None;
|
||||
}
|
||||
match REDIRECT.get() {
|
||||
Some(server) => redirect_configured(server, name, namelen),
|
||||
None => redirect_loopback(name, namelen),
|
||||
}
|
||||
}
|
||||
|
||||
/// FIFA17 config-driven rewrite. Destination host+port come from `openfut.cfg`
|
||||
/// through `openfut-common`, so the hook and the launcher agree by construction.
|
||||
/// Matching is by EA source-port signature only (see `openfut_common::ea_ports`),
|
||||
/// so a hardcoded EA IP (e.g. the `159.153.51.20:42230` redirector) and a
|
||||
/// DNS-resolved one both land on the configured — possibly remote — server. An
|
||||
/// unrecognised port returns `None` (connection left untouched). Never corrupts
|
||||
/// the sockaddr: it only writes into a fresh 28-byte buffer.
|
||||
unsafe fn redirect_configured(
|
||||
server: &openfut_common::ResolvedServer,
|
||||
name: *const u8,
|
||||
namelen: i32,
|
||||
) -> Option<([u8; 28], i32)> {
|
||||
let family = *(name as *const u16);
|
||||
let mut buf = [0u8; 28];
|
||||
match family {
|
||||
AF_INET => {
|
||||
let sa = &*(name as *const SockaddrIn);
|
||||
let redir = server.redirect_for_ea_port(sa.sin_port)?;
|
||||
crate::write_log(&format!(
|
||||
"connect_hook: v4 :{} → {}:{}\n",
|
||||
u16::from_be(sa.sin_port),
|
||||
redir.redirect_ip,
|
||||
u16::from_be(redir.port_nbo)
|
||||
));
|
||||
let out = &mut *(buf.as_mut_ptr() as *mut SockaddrIn);
|
||||
out.sin_family = AF_INET;
|
||||
out.sin_port = redir.port_nbo;
|
||||
out.sin_addr = redir.addr_nbo;
|
||||
Some((buf, 16))
|
||||
}
|
||||
AF_INET6 => {
|
||||
if namelen < 28 {
|
||||
return None;
|
||||
}
|
||||
let sa6 = &*(name as *const SockaddrIn6);
|
||||
let redir = server.redirect_for_ea_port(sa6.sin6_port)?;
|
||||
// ::ffff:<redirect_ip> — a v4-mapped v6 target so a v6 socket sends
|
||||
// real IPv4 packets to the configured server.
|
||||
let o = redir.redirect_ip.octets();
|
||||
let mut v4mapped = [0u8; 16];
|
||||
v4mapped[10] = 0xff;
|
||||
v4mapped[11] = 0xff;
|
||||
v4mapped[12..16].copy_from_slice(&o);
|
||||
crate::write_log(&format!(
|
||||
"connect_hook: v6 :{} → ::ffff:{}:{}\n",
|
||||
u16::from_be(sa6.sin6_port),
|
||||
redir.redirect_ip,
|
||||
u16::from_be(redir.port_nbo)
|
||||
));
|
||||
let out = &mut *(buf.as_mut_ptr() as *mut SockaddrIn6);
|
||||
out.sin6_family = AF_INET6;
|
||||
out.sin6_port = redir.port_nbo;
|
||||
out.sin6_flowinfo = 0;
|
||||
out.sin6_addr = v4mapped;
|
||||
out.sin6_scope_id = 0;
|
||||
Some((buf, 28))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe extern "system" fn hooked_connect(s: usize, name: *const u8, namelen: i32) -> i32 {
|
||||
let addr = CONNECT_ADDR.load(Ordering::Relaxed) as *mut u8;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user