Files
OpenFUT/fifa17-recon/docker/fifa17-python/tools/uidiff.py
T
root 70a64e3709 fifa17-python: commit working FUT backend deployment (client/server split)
Freeze the running offline FUT backend into version control as
fifa17-recon/docker/fifa17-python/ - declarative and rebuildable from a
fresh checkout:

* OPENFUT_BIND / OPENFUT_ADVERTISE client/server split in the responders
  (lsx, blaze, roster, utas, pow) + entrypoint.sh; OPENFUT_ADVERTISE is
  required for remote mode (compose and entrypoint fail without it)
* docker-compose.yml reproducing the frozen baseline container exactly
  (env, ports incl. the 8085->8080 POW-content remap, /state bind, restart)
* .env.example / .env for site config - the LAN IP is never hardcoded in source
* tools/ + data/ staged from openfut-fut-backend:python-baseline-2026-08-10,
  verified byte-identical to the running container at freeze time
* client_arm.sh (the 105 client-side arming counterpart)
* Dockerfile bakes /app/SHA256SUMS.txt so any image is self-identifying
* docs/BASELINE-python-2026-08-10.md: frozen image/container/hash record,
  restore instructions and rebuild-equivalence procedure

Secrets (redir key/cert, .env) and runtime state (docker/state) stay gitignored.
The live container is untouched pending the .105 launcher audit.
2026-08-10 23:54:04 +00:00

59 lines
2.3 KiB
Python

#!/usr/bin/env python3
"""Diff the atoms the userInfo deserializer (0x18013ec10) can consume against the
keys utas_server.user_info() actually sends, and flag STRING-typed fields we omit
(a NULL string pointer is exactly the crash class seen in FUN_180084f90)."""
import re, sys, os
sys.path.insert(0, "/home/alex/Documents/OpenFUT/fifa17-recon/tools")
os.environ.setdefault("FUT_PROFILE", "/tmp/claude-1000/-home-alex-Documents-OpenFUT/"
"4cf26d25-8cee-4db3-ad9e-9fd1838020eb/scratchpad/diffprof.json")
atoms = {}
for line in open("/home/alex/Documents/OpenFUT/fifa17-recon/docs/fut_atoms.tsv"):
p = line.rstrip("\n").split("\t")
if len(p) >= 3:
atoms[int(p[0])] = p[2]
src = open("/tmp/ghidra_fut/userinfo.txt").read()
# Every comparison against the key-id variable, plus switch cases.
ids = set()
for m in re.finditer(r"iVar\d+ == (0x[0-9a-f]+|\d+)", src):
ids.add(int(m.group(1), 0))
for m in re.finditer(r"case (0x[0-9a-f]+|\d+):", src):
ids.add(int(m.group(1), 0))
for m in re.finditer(r"caseD_([0-9a-f]+)", src):
ids.add(int(m.group(1), 16))
# getter used per id -> type. 0x1801c7aa0 = STRING, 0x1801c79d0 = int,
# 0x1801c7a40/0x1801c7b40 = bool/other scalars.
GET = {"1801c7aa0": "str", "1801c79d0": "int"}
typed = {}
for m in re.finditer(r"(?:iVar\d+ == |case )(0x[0-9a-f]+|\d+)\)?:?\s*\{?\s*\n((?:.*\n){0,6})", src):
try:
i = int(m.group(1), 0)
except ValueError:
continue
blk = m.group(2)
for g, t in GET.items():
if g in blk:
typed[i] = t
break
from utas_server import user_info # noqa: E402
sent = set(user_info().keys())
known = {i: atoms.get(i, "?") for i in sorted(ids) if i in atoms}
print("userInfo deser consumes %d named atoms; user_info() sends %d keys\n"
% (len(known), len(sent)))
missing = [(i, n, typed.get(i, "")) for i, n in known.items() if n not in sent]
extra = sorted(sent - set(known.values()))
print("=== parsed by the client but NOT sent by us (%d) ===" % len(missing))
for i, n, t in sorted(missing, key=lambda x: (x[2] != "str", x[1])):
print(" %-32s atom %#-6x %s" % (n, i, ("<-- STRING" if t == "str" else t)))
print("\n=== we send but the deser does not name (harmless SKIPs) ===")
print(" " + ", ".join(extra))