diff --git a/.gitignore b/.gitignore index 46dfb26..192b53d 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ Thumbs.db # Frozen baseline archives / inspects / manifests /docker-backups/ + +# local dev screenshots (not versioned) +fifa17-recon/.screens/ diff --git a/Cargo.lock b/Cargo.lock index bb28c4b..bc202b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3156,6 +3156,10 @@ dependencies = [ "uuid", ] +[[package]] +name = "openfut-common" +version = "0.1.0" + [[package]] name = "openfut-core" version = "0.1.0" @@ -3184,6 +3188,7 @@ dependencies = [ name = "openfut-hook" version = "0.1.0" dependencies = [ + "openfut-common", "windows-sys 0.59.0", ] diff --git a/OPENFUT_HANDOFF.md b/OPENFUT_HANDOFF.md index 20d9d4e..9ef9ba3 100644 --- a/OPENFUT_HANDOFF.md +++ b/OPENFUT_HANDOFF.md @@ -263,3 +263,48 @@ Both matter beyond themselves, because they are the only two routes into a match Useful framing: this project's failures have almost always come from proposing a fix before testing the assumption under it. Hypotheses that come with a cheap way to disconfirm them are worth far more than plausible ones. + +## FIFA 17 network-redirect milestone (2026-08-09) + +Hook now installs a GENERIC network redirect on the fifa17 feature path (fifa17.rs +install_network_redirect): getaddrinfo IAT patch + inline connect detour + WSAConnect +IAT, with a configurable destination (connect_hook::set_target_ipv4) read from +openfut.cfg (single-line IP). Deployed DLL md5 bc9e0bc6, cfg=10.10.0.120. + +RESULT of live launch (client 105 -> server 120): +- Error changed: "servers shut down" -> "Unable to connect to EA servers / check + network". Redirect IS firing (progress). +- BLOCKER A: getaddrinfo IAT patched 0+0 -> FIFA 17 does NOT resolve via IAT + getaddrinfo in the main exe or EAWebKit.dll. Names resolved via another path + (gethostbyname or internal DirtySDK resolver). So no hostname reached 120. +- BLOCKER B (architectural): FIFA 17 online = Blaze binary TCP on high ports. Log + shows connect 20.51.153.159:42230 sock_type=1 -> wsa_err=10035 (WOULDBLOCK->dead). + Port 42230 is NOT in the remap set (443,10041,42127,3216) so it was not redirected. + Even if redirected, the Docker bridge only speaks HTTPS on 8443 -- no Blaze + listener exists for FIFA 17. This is a server-side build, not a hook tweak. + +NEXT (evidence-first): add gethostbyname (and possibly a DirtySDK resolver) capture +to learn the hostname behind 20.51.153.159; widen Blaze port remap; then scope a +Blaze-speaking bridge listener before expecting the error to clear. + +## DNS/getaddrinfo fix — RESOLVED (2026-08-09, hook md5 67e3639b) + +Added src/resolver_hook.rs: INLINE detours at ws2_32 export addresses for +getaddrinfo + GetAddrInfoW + gethostbyname (same unhook/rehook pattern as +connect_hook). Replaces the IAT approach that patched 0 slots on FIFA 17. +Wired into fifa17.rs install_network_redirect; hooks.rs gained redirect_ip_cstr() +and redirect_ip_str() helpers. + +LIVE RESULT (client 105 -> server 120): +- resolver detours 3/3 installed. +- getaddrinfo(winter15.gosredirector.ea.com) -> redirect. Game now dials + 10.10.0.120 (was 20.51.153.159 before). DNS BLOCKER A = SOLVED. + +REMAINING BLOCKER B (architectural, NOT DNS): FIFA 17 online = EA Blaze binary +TCP. Game connects 10.10.0.120:42230 (gosredirector/Blaze redirector) -> +wsa_err=10035 (nothing listening). Two gaps: (1) connect_hook remap set lacks +42230; (2) even remapped, the Docker bridge only serves HTTPS on 8443 — no Blaze +listener exists. Clearing Unable to connect requires a Blaze redirector+main +server on the bridge side (real server build), not a hook change. +NOTE: the 3s TLS-handshake-EOF spam in bridge logs on :8443 is the LAUNCHER health +poller, not the game. diff --git a/fifa17-recon/docker/fifa17-python/client_arm.sh b/fifa17-recon/docker/fifa17-python/client_arm.sh index 9b39700..565eb1e 100644 --- a/fifa17-recon/docker/fifa17-python/client_arm.sh +++ b/fifa17-recon/docker/fifa17-python/client_arm.sh @@ -20,13 +20,15 @@ # 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=10.10.0.120 ./client_arm.sh +# 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. 10.10.0.120}" +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 @@ -55,13 +57,46 @@ 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:]]easw\.easports\.com\b.*# openfut$/d' /etc/hosts -printf '%s\teasw.easports.com\t# openfut\n' "$SERVER" >> /etc/hosts +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!)" -grep 'easw.easports.com' /etc/hosts && echo " /etc/hosts ok" || echo " (/etc/hosts easw 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." diff --git a/fifa17-recon/tools/autopatch.py b/fifa17-recon/tools/autopatch.py index 49dcf62..ed649e0 100755 --- a/fifa17-recon/tools/autopatch.py +++ b/fifa17-recon/tools/autopatch.py @@ -24,6 +24,45 @@ STORE_PATCHES = { 0x1800175aa: NOP2, } +# Store resolver crash-guard for the empty "My Packs" case (bug 6c; PROVEN R1 on the +# tested FIFA 17 build -- see docs/plans/FIFA17_EMPTY_MYPACKS_CLIENT_FIX.md PART IV and +# docs/evidence/FIFA17_EMPTY_MYPACKS_CLIENT_CONTRACT.md). +# +# When no `mypacks` group exists, FIFA's Store resolver receives category id -1. CardsDLL +# FUN_1800147f0 @ 0x180014858 is `JNZ 0x14869` (75 0f): the original treats every non-zero +# category (including -1) as resolvable, calls FUN_180014420, gets NULL, and crashes at the +# [NULL+0x48] deref in FUN_1800147f0 (0x180014882). Changing JNZ->JG (7f 0f) preserves +# positive-category resolution (EDI>0 branch) while routing zero/negative categories through +# the existing Browse/list-all path -> no NULL lookup, no crash, Store opens on Browse Packs. +# +# CAVEAT: this guards the category SIGN only. It does NOT protect a stale *positive* invalid +# ordinal produced by changing the Store group topology (sentinel-present <-> sentinel-absent) +# DURING one running FIFA process -- that reproduced the same crash in the confounded run F3. +# The empty-My-Packs representation MUST stay stable for a FIFA session (see the SESSION-STABLE +# invariant in the client-fix plan). +# +# Orig-verified / fail-closed: applied only when the live bytes are the known original (75 0f); +# already-patched (7f 0f) is a no-op; anything else is logged and SKIPPED (never blindly +# overwritten), so an unrecognised CardsDLL build is not patched. +STORE_PATCHES_GUARDED = { + 0x180014858: (bytes.fromhex("750f"), bytes.fromhex("7f0f")), # JNZ 0x14869 -> JG 0x14869 +} + +# Capability advertised to the launcher/backend once the resolver guard is VERIFIED +# live in a specific FIFA process (docs/plans/FIFA17_PATCHED_CLIENT_CAPABILITY.md #3/#4). +EMPTY_MYPACKS_RESOLVER_CAPABILITY = "fifa17.empty_mypacks_resolver" +EMPTY_MYPACKS_RESOLVER_VERSION = 1 + +# The guarded site whose verified enforcement backs the capability above. +RESOLVER_GUARD_VA = 0x180014858 + +# Per-FIFA-pid guard status (fail-closed; FIFA17_PATCHED_CLIENT_CAPABILITY.md #4). +GUARD_NOT_ATTEMPTED = "NOT_ATTEMPTED" # CardsDLL not mapped / guard not yet evaluated +GUARD_VERIFIED = "VERIFIED" # live bytes == patch after enforcement (patch or noop) +GUARD_UNSUPPORTED_BUILD = "UNSUPPORTED_BUILD" # neither original nor patched (guarded_action -> skip) +GUARD_WRITE_FAILED = "WRITE_FAILED" # /proc//mem write raised +GUARD_VERIFY_FAILED = "VERIFY_FAILED" # post-write re-read != patch + LOG=os.environ.get("OPENFUT_AUTOPATCH_LOG", f"/tmp/openfut-autopatch-{os.getuid()}.log") def log(m): @@ -52,48 +91,112 @@ def wr(pid,va,b): with open(f'/proc/{pid}/mem','r+b') as f: f.seek(va); f.write(b) +def guarded_action(cur, orig, patch): + """Fail-closed decision for a guarded byte patch (see STORE_PATCHES_GUARDED). + + Returns "noop" when the live bytes are already patched, "patch" when they are the + known original (safe to apply), or "skip" for anything else -- an unrecognised + CardsDLL build that must never be blindly overwritten. + """ + if cur == patch: + return "noop" + if cur == orig: + return "patch" + return "skip" + +def guard_state_after(cur_before, orig, patch, wrote_ok, cur_after): + """Map a guarded-patch enforcement outcome to a per-pid guard STATE (pure). + + Mirrors guarded_action's decision, extended with post-write verification so the + caller advertises the capability only on VERIFIED. No /proc access -- unit-testable. + + - cur_before == patch -> VERIFIED (already patched; guarded_action "noop") + - cur_before == orig -> WRITE_FAILED if the write raised, else VERIFIED when the + re-read is patch, else VERIFY_FAILED (guarded_action "patch") + - otherwise -> UNSUPPORTED_BUILD (guarded_action "skip") + """ + if cur_before == patch: + return GUARD_VERIFIED + if cur_before == orig: + if not wrote_ok: + return GUARD_WRITE_FAILED + if cur_after == patch: + return GUARD_VERIFIED + return GUARD_VERIFY_FAILED + return GUARD_UNSUPPORTED_BUILD + patched=set() store_patched=set() +guard_reported=set() -launcher_pid = None -if "--launcher-pid" in sys.argv: - try: launcher_pid = int(sys.argv[sys.argv.index("--launcher-pid") + 1]) - except (ValueError, IndexError): raise SystemExit("invalid --launcher-pid") +if __name__ == "__main__": + launcher_pid = None + if "--launcher-pid" in sys.argv: + try: launcher_pid = int(sys.argv[sys.argv.index("--launcher-pid") + 1]) + except (ValueError, IndexError): raise SystemExit("invalid --launcher-pid") -log("=== AUTOPATCH watching for FIFA17.exe ===") -while True: - if launcher_pid and not os.path.exists(f"/proc/{launcher_pid}"): - log(f"launcher pid {launcher_pid} exited; stopping autopatch") - break - for pid in find_pids(): - if pid not in patched: - try: - g2=rd(pid,GATE2,3); g1=rd(pid,GATE1,6) - except Exception: - continue # code not mapped yet - if g2==GATE2_PATCH and g1==GATE1_PATCH: - log(f"pid {pid}: cert gates already patched"); patched.add(pid) - elif g2==GATE2_ORIG and g1==GATE1_ORIG: + log("=== AUTOPATCH watching for FIFA17.exe ===") + while True: + if launcher_pid and not os.path.exists(f"/proc/{launcher_pid}"): + log(f"launcher pid {launcher_pid} exited; stopping autopatch") + break + for pid in find_pids(): + if pid not in patched: try: - wr(pid,GATE2,GATE2_PATCH); wr(pid,GATE1,GATE1_PATCH) - log(f"pid {pid}: PATCHED cert gates") - patched.add(pid) + g2=rd(pid,GATE2,3); g1=rd(pid,GATE1,6) + except Exception: + continue # code not mapped yet + if g2==GATE2_PATCH and g1==GATE1_PATCH: + log(f"pid {pid}: cert gates already patched"); patched.add(pid) + elif g2==GATE2_ORIG and g1==GATE1_ORIG: + try: + wr(pid,GATE2,GATE2_PATCH); wr(pid,GATE1,GATE1_PATCH) + log(f"pid {pid}: PATCHED cert gates") + patched.add(pid) + except Exception as e: + log(f"pid {pid}: cert patch write failed: {e}") + + # Continuously enforce store patches every tick + cbase = cardsdll_base(pid) + if cbase is not None: + try: + for va, data in STORE_PATCHES.items(): + live = cbase + (va - IMG_BASE) + if rd(pid, live, len(data)) != data: + wr(pid, live, data) + log(f"pid {pid}: ENFORCED store patch @ {live:#x}") + for va, (orig, patch) in STORE_PATCHES_GUARDED.items(): + live = cbase + (va - IMG_BASE) + cur = rd(pid, live, len(patch)) + action = guarded_action(cur, orig, patch) + wrote_ok = True + cur_after = cur + if action == "patch": + try: + wr(pid, live, patch) + log(f"pid {pid}: ENFORCED guarded store patch @ {live:#x} (JNZ->JG, empty My Packs)") + except Exception as e: + wrote_ok = False + log(f"pid {pid}: guarded patch write failed @ {live:#x}: {e}") + if wrote_ok: + try: + cur_after = rd(pid, live, len(patch)) + except Exception: + cur_after = b"" + elif action == "skip": + log(f"pid {pid}: SKIP guarded patch @ {live:#x}: unexpected {cur.hex()} (build mismatch)") + # action == "noop": already patched; nothing to write. + if va == RESOLVER_GUARD_VA and pid not in guard_reported: + state = guard_state_after(cur, orig, patch, wrote_ok, cur_after) + if state == GUARD_VERIFIED: + log(f"[store-guard] verified capability {EMPTY_MYPACKS_RESOLVER_CAPABILITY}={EMPTY_MYPACKS_RESOLVER_VERSION} fifa_pid={pid}") + else: + log(f"[store-guard] guard status={state} fifa_pid={pid} (no capability advertised)") + guard_reported.add(pid) + if pid not in store_patched: + log(f"pid {pid}: PATCHED store gates in CardsDLL @ {cbase:#x}") + store_patched.add(pid) except Exception as e: - log(f"pid {pid}: cert patch write failed: {e}") + log(f"pid {pid}: store patch write failed: {e}") - # Continuously enforce store patches every tick - cbase = cardsdll_base(pid) - if cbase is not None: - try: - for va, data in STORE_PATCHES.items(): - live = cbase + (va - IMG_BASE) - if rd(pid, live, len(data)) != data: - wr(pid, live, data) - log(f"pid {pid}: ENFORCED store patch @ {live:#x}") - if pid not in store_patched: - log(f"pid {pid}: PATCHED store gates in CardsDLL @ {cbase:#x}") - store_patched.add(pid) - except Exception as e: - log(f"pid {pid}: store patch write failed: {e}") - - time.sleep(1) + time.sleep(1) diff --git a/fifa17-recon/tools/fifa17-hook-m1.sh b/fifa17-recon/tools/fifa17-hook-m1.sh index ab57506..c16814b 100755 --- a/fifa17-recon/tools/fifa17-hook-m1.sh +++ b/fifa17-recon/tools/fifa17-hook-m1.sh @@ -55,6 +55,34 @@ verify_exports() { done } +# Refuse any DLL that is not a FIFA-17-profile build. +# +# openfut-hook builds TWO mutually exclusive injection paths from one crate: the +# default (FIFA 23) path installs getaddrinfo/connect/ProtoSSL/origin hooks, while +# `--features fifa17` installs ONLY the FIFA-17-safe logic (module map, FIFA 17 +# cert-verify, SBC dispatch, store tab bind). Deploying a default-feature build +# into FIFA 17 hijacks the login transport and the client reports "Unable to +# connect to the EA servers", with none of the FIFA 17 repairs present. +# +# That exact mistake happened on 2026-08-19 (artifact 1c71a17a, hand-built without +# the feature): two failed launches, diagnosed only by comparing embedded strings. +# `build` below passes the feature, but a hand-built DLL can reach `stage`/`deploy` +# via OPENFUT_FIFA17_HOOK_DLL, so assert the profile on the bytes themselves. +verify_fifa17_profile() { + local dll=$1 marker + # Markers that MUST be present: the FIFA 17 target module and its repairs. + for marker in 'CardsDLL_Win64_retail.dll' 'SBC_DISPATCH'; do + grep -qaF -- "$marker" "$dll" || + die "$dll is not a --features fifa17 build (missing $marker); refusing to stage/deploy" + done + # Markers that MUST be absent: the FIFA-23-only transport hooking. + for marker in 'getaddrinfo IAT patched' 'connect: inline-hooked' 'origin_spy'; do + if grep -qaF -- "$marker" "$dll"; then + die "$dll contains FIFA-23-only hook '$marker'; build with --features fifa17" + fi + done +} + verify_inputs() { command -v sha256sum >/dev/null || die "sha256sum is required" command -v x86_64-w64-mingw32-objdump >/dev/null || @@ -62,6 +90,7 @@ verify_inputs() { need_file "$hook_dll" need_file "$system_version" verify_pe64 "$hook_dll" + verify_fifa17_profile "$hook_dll" } inspect() { @@ -129,6 +158,7 @@ deploy() { need_file "$manifest" verify_pe64 "$staged" verify_exports "$staged" + verify_fifa17_profile "$staged" local recorded actual recorded="$(awk -F= '$1=="artifact_sha256"{print $2}' "$manifest")" actual="$(sha256 "$staged")" @@ -158,7 +188,7 @@ launch() { local trace_enabled=0 local request_trace_enabled=0 local notifier_trace_enabled=0 - local commit_enabled=0 + local dispatch_enabled=0 case "$mode" in baseline) [[ "${OPENFUT_FIFA17_LAUNCH:-}" == "I_ACCEPT_M1_BASELINE_LAUNCH" ]] || @@ -177,14 +207,11 @@ launch() { request_trace_enabled=1 notifier_trace_enabled=1 ;; - commit) - [[ "${OPENFUT_FIFA17_COMMIT:-}" == "I_ACCEPT_POST_PARSE_READY_BYTE" ]] || - die "launch-commit requires OPENFUT_FIFA17_COMMIT=I_ACCEPT_POST_PARSE_READY_BYTE" - hook_enabled=1 - trace_enabled=1 + dispatch) + [[ "${OPENFUT_FIFA17_DISPATCH:-}" == "I_ACCEPT_GUARDED_NATIVE_DISPATCH" ]] || + die "launch-dispatch requires OPENFUT_FIFA17_DISPATCH=I_ACCEPT_GUARDED_NATIVE_DISPATCH" request_trace_enabled=1 - notifier_trace_enabled=1 - commit_enabled=1 + dispatch_enabled=1 ;; *) die "unknown launch mode: $mode" ;; esac @@ -207,7 +234,7 @@ launch() { done mkdir -p "${wine_prefix}/dosdevices" ln -sfn /mnt "${wine_prefix}/dosdevices/w:" - note "Launching $mode mode (SBC_HOOK=$hook_enabled; SBC_TRACE=$trace_enabled; SBC_REQUEST_TRACE=$request_trace_enabled; SBC_NOTIFIER_TRACE=$notifier_trace_enabled; SBC_COMMIT=$commit_enabled); log=/tmp/fifa17-hook-m1-launch.log" + note "Launching $mode mode (SBC_HOOK=$hook_enabled; SBC_TRACE=$trace_enabled; SBC_REQUEST_TRACE=$request_trace_enabled; SBC_NOTIFIER_TRACE=$notifier_trace_enabled; SBC_DISPATCH=$dispatch_enabled); log=/tmp/fifa17-hook-m1-launch.log" cd "$game_dir" env \ GAMEID=fifa17 \ @@ -218,8 +245,8 @@ launch() { OPENFUT_SBC_TRACE="$trace_enabled" \ OPENFUT_SBC_REQUEST_TRACE="$request_trace_enabled" \ OPENFUT_SBC_NOTIFIER_TRACE="$notifier_trace_enabled" \ - OPENFUT_SBC_DISPATCH=0 \ - OPENFUT_SBC_COMMIT="$commit_enabled" \ + OPENFUT_SBC_DISPATCH="$dispatch_enabled" \ + OPENFUT_SBC_DISPATCH_TRACE=0 \ OPENFUT_SBC_ARM_ONLY=0 \ OPENFUT_SBC_POPULATE=0 \ umu-run _fifa17.exe 2>&1 | tee /tmp/fifa17-hook-m1-launch.log @@ -227,7 +254,7 @@ launch() { usage() { cat <<'EOF' -Usage: fifa17-hook-m1.sh [inspect|build|stage|deploy|launch|launch-resolve|launch-trace|launch-commit] +Usage: fifa17-hook-m1.sh [inspect|build|stage|deploy|launch|launch-resolve|launch-trace|launch-dispatch] inspect Read-only PE/hash/export preflight (default). build Cross-build the inert FIFA17 hook, then run inspect. @@ -240,11 +267,11 @@ Usage: fifa17-hook-m1.sh [inspect|build|stage|deploy|launch|launch-resolve|launc Start M2 resolve-only mode (guarded reads/logging, no detours/writes); requires: OPENFUT_FIFA17_RESOLVE=I_ACCEPT_M2_RESOLVE_LAUNCH launch-trace - Start the single M3 passive factory/deserializer trace; requires: + Start the M3-M6 passive parser/request/notifier trace; requires: OPENFUT_FIFA17_TRACE=I_ACCEPT_M3_PASSIVE_TRACE - launch-commit - Trace and arm the SBC cache only after a validated native parse; requires: - OPENFUT_FIFA17_COMMIT=I_ACCEPT_POST_PARSE_READY_BYTE + launch-dispatch + Trace and repair only a fully validated native status-999 completion; requires: + OPENFUT_FIFA17_DISPATCH=I_ACCEPT_GUARDED_NATIVE_DISPATCH Optional path overrides: OPENFUT_FIFA17_HOOK_DLL, OPENFUT_FIFA17_GAME_DIR, @@ -260,7 +287,7 @@ case "${1:-inspect}" in launch) launch baseline ;; launch-resolve) launch resolve ;; launch-trace) launch trace ;; - launch-commit) launch commit ;; + launch-dispatch) launch dispatch ;; -h|--help|help) usage ;; *) usage >&2; die "unknown command: $1" ;; esac diff --git a/fifa17-recon/tools/lsx_responder_v2.py b/fifa17-recon/tools/lsx_responder_v2.py index addd83d..f9a4265 100755 --- a/fifa17-recon/tools/lsx_responder_v2.py +++ b/fifa17-recon/tools/lsx_responder_v2.py @@ -162,6 +162,19 @@ def log(*a): print("[lsx]", *a, flush=True) +def spawn_parent_watchdog(): + parent = os.getppid() + + def _watch(): + while True: + time.sleep(1) + if os.getppid() != parent: + log(f"launcher pid {parent} exited; stopping lsx") + os._exit(0) + + threading.Thread(target=_watch, daemon=True).start() + + _SECRET_ATTR_RE = re.compile( r'(?i)\b(AuthCode|AuthToken|SessionKey|Token|Sid)="[^"]*"') _AUTH_CODE_ATTR_RE = re.compile(r'(?i)\b(value|Code|Return)="[^"]*"') @@ -572,6 +585,7 @@ def serve(sock, addr): def main(): + spawn_parent_watchdog() s = socket.socket() s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((os.environ.get("OPENFUT_BIND", "127.0.0.1"), 4216))