Files
OpenFUT/fifa17-recon/docker/fifa17-python/tools/roster_server.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

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()