696386a9c1
The old check was `grep easw /etc/hosts && echo ok`. It passed on ANY matching line -- including a line that shadows ours. glibc returns the first match, and the sed above only deletes lines this script wrote (`# openfut`), so a foreign entry earlier in the file wins forever and re-running the script never helps. Observed today: a leftover `127.0.0.1 easw.easports.com` from the single-machine era, before the backend moved to its own host. Every arm reported "/etc/hosts ok" while the name resolved to loopback. Now it resolves the name -- the same call the game makes -- and compares address to address, so a server given as a hostname is handled too. On a mismatch it prints the offending lines with line numbers and says how to fix them. It does NOT delete them. This script writes one tagged line and owns only that line; silently removing entries a user put there by hand is a bigger hazard than the shadowing it would cure. Reported as a warning, not an error, because it is survivable: the responders advertise the server address, so the game stops using this hostname after the first redirected contact. FIFA reached the FUT hub today with this exact misconfiguration in place. Claiming it is fatal would be wrong, and a check that overstates its findings gets ignored. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
103 lines
5.3 KiB
Bash
103 lines
5.3 KiB
Bash
#!/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=<SERVER_IP>, so
|
|
# after these first redirected contacts the game is handed <SERVER_IP> 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."
|