fifa17-recon: persistent FUT profile + starter pack
Add fut_store.py: a JSON-backed profile store (coins, owned club items, saved squads, record). First run grants a starter pack -- 15000 coins + a 10-player starter club (real assetIds; identity resolves locally in-game per docs/CARD_SYSTEM.md). Wire utas_server to the store: /user/credits -> persisted coins, /club -> persisted owned items, PUT /squad -> persists the squad the user builds so it survives relaunches. Profile save file is gitignored. Foundation for pack-opening (store/purchasegroup + transaction) next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PN5bmpDVQR1aXgefyWAt7o
This commit is contained in:
@@ -15,6 +15,7 @@ import datetime, json, os, re, sys, http.server
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from fut_seed import CLUB, SQUAD, USER_LIST # forged starter squad (clean-room)
|
||||
from fut_store import STORE # persistent profile (coins/club/squads)
|
||||
|
||||
ADDR = ("127.0.0.1", 8099)
|
||||
LOG = "/tmp/utas_server.log"
|
||||
@@ -159,7 +160,7 @@ ROUTES = [
|
||||
(re.compile(G + r"/phishing/trusteddevice"), lambda m, h: (200, {"trusted": True})),
|
||||
(re.compile(G + r"/phishing/validate"), lambda m, h: (200, {"token": "OPENFUT-TRUST-0000000000000000"})),
|
||||
(re.compile(G + r"/phishing/question"), lambda m, h: (200, {"question": 0, "answer": "", "attempts": 5})),
|
||||
(re.compile(G + r"/user/credits"), lambda m, h: (200, {"credits": 15000})),
|
||||
(re.compile(G + r"/user/credits"), lambda m, h: (200, {"credits": STORE.coins()})),
|
||||
# ---- club/squad routes reverted to known-good {} stubs (2026-08-01) ----
|
||||
# The forged squad in MASSINFO/SQUAD/CLUB HANGS CardsDLL's deserializer (hard
|
||||
# freeze at boot). Re-enable only after the exact shape is reversed. The forged
|
||||
@@ -173,7 +174,7 @@ ROUTES = [
|
||||
# STEP 1 (wf_0bc80ab3): zero-resolve squad in MASSINFO; club stays {}.
|
||||
(re.compile(G + r"/userMassInfo"), lambda m, h: (200, MASSINFO)),
|
||||
(re.compile(G + r"/season"), lambda m, h: (200, {})),
|
||||
(re.compile(G + r"/club"), lambda m, h: (200, CLUB)),
|
||||
(re.compile(G + r"/club"), lambda m, h: (200, {"itemData": STORE.items()})),
|
||||
]
|
||||
|
||||
|
||||
@@ -187,9 +188,19 @@ def user_route(h):
|
||||
|
||||
|
||||
def squad_route(h):
|
||||
# GET = LoadActiveSquad, PUT = updateActiveSquad. Always echo the full canonical
|
||||
# squad (never {} — an empty body resets the client's 23 slots, 0x18013d1f0).
|
||||
return 200, SQUAD
|
||||
# GET = LoadActiveSquad, PUT = updateActiveSquad. Persist the squad the user
|
||||
# builds so it survives relaunches. Never return {} (empty body resets the 23
|
||||
# slots, 0x18013d1f0).
|
||||
if h.command == "PUT":
|
||||
try:
|
||||
sq = json.loads(h._body.decode("utf-8")) if getattr(h, "_body", b"") else None
|
||||
except Exception:
|
||||
sq = None
|
||||
if isinstance(sq, dict) and sq.get("players"):
|
||||
STORE.save_squad(sq)
|
||||
return 200, sq
|
||||
saved = STORE.active_squad()
|
||||
return 200, (saved if saved else SQUAD)
|
||||
|
||||
|
||||
class H(http.server.BaseHTTPRequestHandler):
|
||||
@@ -198,6 +209,7 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
def _handle(self):
|
||||
n = int(self.headers.get("Content-Length", 0) or 0)
|
||||
body = self.rfile.read(n) if n else b""
|
||||
self._body = body # route fns (squad PUT) read this
|
||||
log("%s %s" % (self.command, self.path))
|
||||
for k, v in self.headers.items():
|
||||
log(" %s: %s" % (k, v))
|
||||
|
||||
Reference in New Issue
Block a user