Files
OpenFUT/fifa17-recon/tools/roster_server.py
T
funman300 83539e33ec fifa17-recon: take running-backend versions of 8 runtime files (direction fix)
The earlier reconcile committed the local working-tree versions of these
files, which are OLDER than the deployed backend. The running container (C)
is byte-identical to docker/fifa17-python/tools (B) and is a strict superset:
it adds profile_path_for/select_account/ensure_security_question (fut_store),
safe_header_for_log/safe_request_path/security_question_route (utas_server),
account_sync_route/_match_call/match_ready_body, plus POW balance fields and
match lifecycle support, with zero unique local functions lost.

Reconciled tree is now a strict superset of B with every shared file
byte-identical; verified via md5 map (0 missing, 0 differing).
2026-08-10 17:12:27 -07:00

80 lines
2.9 KiB
Python

#!/usr/bin/env python3
"""FUT roster-update HTTPS server for FIFA 17 (OpenFUT).
The FUT loading flow (checkFUTRostersFlow / state CheckFUTRosterUpdateXML) downloads
a roster-update XML from ROSTERUPDATE_URL (which blaze_responder_v3b.py now serves as
https://127.0.0.1:8081/fifa17/fut/rosterupdate.xml). On download SUCCESS the flow
raises `advance` -> CheckFUTSquadBinFile -> (no squad) -> EnterFUT -> CardsDLL loads.
On FAIL it raises `back` -> abort. So this must return something FIFA ACCEPTS.
We don't have (or need) EA's real roster: the base player DB is baked into CardsDLL;
the roster-update is an optional delta. Start by serving a minimal "no update" body,
LOG every request (path/headers) so we learn exactly what FIFA fetches, and iterate.
HTTPS because EA's value is https and the DirtySDK download mgr may reject http; FIFA's
ProtoSSL cert-verify is patched (autopatch), so our self-signed cert is accepted.
"""
import http.server, ssl, os, sys, datetime
HERE = os.path.dirname(os.path.abspath(__file__))
CERT = os.path.join(HERE, "redir_cert.pem")
KEY = os.path.join(HERE, "redir_key.pem")
LOG = "/tmp/roster_server.log"
ADDR = (os.environ.get("OPENFUT_BIND", "127.0.0.1"), 8081)
# Minimal "no update available" roster body. Unknown-format -> iterate from the log.
ROSTER_XML = b'<?xml version="1.0" encoding="utf-8"?>\n<rosterupdate version="0"/>\n'
def log(m):
line = "[%s] %s" % (datetime.datetime.now().strftime("%H:%M:%S"), m)
print(line, flush=True)
with open(LOG, "a") as f:
f.write(line + "\n")
class H(http.server.BaseHTTPRequestHandler):
def _handle(self, method):
log("%s %s from %s" % (method, self.path, self.client_address))
for k, v in self.headers.items():
log(" %s: %s" % (k, v))
body = ROSTER_XML
self.send_response(200)
self.send_header("Content-Type", "application/xml")
self.send_header("Content-Length", str(len(body)))
self.send_header("Connection", "close")
self.end_headers()
if method == "GET":
self.wfile.write(body)
log(" -> 200 %dB (%r)" % (len(body), body[:60]))
def do_GET(self): self._handle("GET")
def do_HEAD(self): self._handle("HEAD")
def do_POST(self):
n = int(self.headers.get("Content-Length", 0) or 0)
if n:
log(" POST body: %r" % self.rfile.read(n)[:200])
self._handle("POST")
def log_message(self, *a): # silence default stderr logging
pass
def main():
open(LOG, "a").close()
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain(CERT, KEY)
ctx.minimum_version = ssl.TLSVersion.TLSv1
try:
ctx.set_ciphers("ALL:@SECLEVEL=0")
except Exception:
pass
httpd = http.server.HTTPServer(ADDR, H)
httpd.socket = ctx.wrap_socket(httpd.socket, server_side=True)
log("=== roster_server https://%s:%d (FUT roster-update) ===" % ADDR)
httpd.serve_forever()
if __name__ == "__main__":
main()