#!/usr/bin/env bash # ============================================================================ # OpenFUT FIFA-17 — CLIENT-side arming (runs on the GAME machine, e.g. 105). # # Companion to the dev container on the SERVER (120). The server runs the heavy # responders (Blaze / UTAS / roster / POW). Two pieces are inherently local to # the game and therefore stay here: # # * autopatch.py — patches FIFA17.exe process memory (ProtoSSL cert-verify). # Must run where the game runs; cannot be containerised. # * lsx_responder — the Origin/EADesktop emulator the game dials on the # hardcoded loopback 127.0.0.1:4216. Loopback IPC can't be # cleanly redirected to a remote host, so it lives here. # # Everything the game reaches by a routable address is redirected to the server: # * winter15.gosredirector.ea.com (hardcoded EA IP 159.153.51.20) -> SERVER:42127 # * easw.easports.com (dead hardcoded UTAS host) -> SERVER (:8099) # # The server's responders were started with OPENFUT_ADVERTISE=, so # after these first redirected contacts the game is handed for every # later hop (Blaze main, roster, UTAS, telemetry) and dials the server directly. # # Usage: sudo OPENFUT_SERVER=203.0.113.10 ./client_arm.sh # (re-run after every reboot; the sysctl/iptables state is volatile) # ============================================================================ set -euo pipefail SERVER="${OPENFUT_SERVER:?set OPENFUT_SERVER to the backend host IP, e.g. 203.0.113.10}" GOS_EA_IP="159.153.51.20" # winter15.gosredirector.ea.com (hardcoded in FIFA17) UTAS_HOST="easw.easports.com" # dead UTAS host baked into CardsDLL UTAS_RE="${UTAS_HOST//./\\.}" # same, safe to embed in a regex if [ "$(id -u)" -ne 0 ]; then echo "!! must run as root (sudo). Re-run: sudo OPENFUT_SERVER=$SERVER $0" >&2 exit 1 fi echo "[client_arm] backend server = $SERVER" # 1) allow /proc/PID/mem writes (autopatch's ProtoSSL cert-verify patch) sysctl -q kernel.yama.ptrace_scope=0 # 2) Redirect the hardcoded Blaze redirector IP to the server's redirector. # (Replace any stale rule first so re-runs and IP changes are clean.) while iptables -t nat -D OUTPUT -p tcp -d "$GOS_EA_IP" -j DNAT \ --to-destination "$SERVER:42127" 2>/dev/null; do :; done iptables -t nat -A OUTPUT -p tcp -d "$GOS_EA_IP" -j DNAT --to-destination "$SERVER:42127" # 2b) DNAT from OUTPUT to a REMOTE host needs a matching source-NAT on the way # out, or the server's replies (from its own IP) won't match the game's # conntrack entry. MASQUERADE the redirected flow so it is SNAT'd to this # host's outbound IP. (Harmless duplicate-guarded like the DNAT above.) while iptables -t nat -D POSTROUTING -p tcp -d "$SERVER" --dport 42127 \ -j MASQUERADE 2>/dev/null; do :; done iptables -t nat -A POSTROUTING -p tcp -d "$SERVER" --dport 42127 -j MASQUERADE # 3) Point the dead hardcoded UTAS host at the server. The port (8099) is carried # in the game's own URL, so only the name needs redirecting. Remove any prior # OpenFUT-managed line (loopback or other server) and write the current one. sed -i "/[[:space:]]${UTAS_RE}\b.*# openfut\$/d" /etc/hosts printf '%s\t%s\t# openfut\n' "$SERVER" "$UTAS_HOST" >> /etc/hosts echo "[client_arm] --- armed ---" sysctl kernel.yama.ptrace_scope iptables -t nat -L OUTPUT -n | grep -i "$GOS_EA_IP" || echo " (DNAT missing!)" # Verify the hosts entry by EFFECT, not by presence. # # glibc returns the FIRST match in /etc/hosts, so our line can be written # correctly and still lose to an earlier one -- and the sed above only removes # lines this script wrote (`# openfut`), so re-running never clears a foreign # one. The old check here was `grep easw /etc/hosts && echo ok`, which passed on # the shadowing line itself and reported success while resolution was wrong. # # Observed on 2026-08-11: a leftover `127.0.0.1 easw.easports.com` from the # single-machine era shadowed the OpenFUT line, and every re-run said "ok". resolved="$(getent ahosts "$UTAS_HOST" 2>/dev/null | awk '{print $1}' | sort -u | tr '\n' ' ')" # SERVER may be a hostname, so compare address-to-address rather than comparing # the literal string against resolved IPs (which would warn spuriously). server_ips="$(getent ahosts "$SERVER" 2>/dev/null | awk '{print $1}' | sort -u)" [ -n "$server_ips" ] || server_ips="$SERVER" match=0 for ip in $server_ips; do printf '%s' "$resolved" | grep -qw -- "$ip" && match=1 done if [ "$match" -eq 1 ]; then echo " /etc/hosts ok ($UTAS_HOST -> $resolved)" else echo echo " !! WARNING: $UTAS_HOST resolves to [$resolved], not $SERVER." echo " An earlier /etc/hosts line is shadowing the OpenFUT one:" grep -nE "^[[:space:]]*[^#].*[[:space:]]${UTAS_RE}([[:space:]]|\$)" /etc/hosts \ | grep -v '# openfut$' | sed 's/^/ /' || true echo echo " Not fatal: the responders advertise $SERVER, so the game stops using" echo " this name after the first hop. Worth removing the line above anyway." echo " Lines are listed rather than deleted -- this script will not remove" echo " /etc/hosts entries it did not write." fi echo echo "[client_arm] Next: start the LOCAL pieces (LSX + autopatch) with client_local.sh," echo " ensure the container is up on $SERVER, then launch FIFA 17."