70a64e3709
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.
80 lines
2.9 KiB
Python
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()
|