#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Enumerate every UTAS URL template CardsDLL can build, from live memory. READ-ONLY: /proc/PID/mem opened 'rb'. No write path in this file. WHY --- Support level L5 ("apply endpoint") for consumables was recorded as unreversed, with an earlier note claiming there is "no training/position/chemistry/ manager-league endpoint at all" and that the only owned-item mutations upstream are quick sell and move/pile. That claim is load-bearing -- if true, applying a consumable is not a server route at all and L5/L6 cannot be implemented as one -- so it deserves to be checked against the binary rather than inherited. This scans CardsDLL's .rdata for route-shaped strings and prints them, so the full reachable surface can be read at once. Positive control: known-live routes MUST appear (e.g. a 'item' path and a 'club' path). If the control is empty the region is wrong, not the game. Usage: python3 url_template_probe.py # route-shaped strings python3 url_template_probe.py --all # every printable string >= 6 chars python3 url_template_probe.py --grep pat # substring filter (case-insensitive) """ import argparse import re import sys import watch_club_model as W RDATA_LO, RDATA_HI = 0x1801E5000, 0x18028A000 DATA_LO, DATA_HI = 0x18028A000, 0x1802F0000 # Route-ish: contains a slash and no spaces, or looks like a UTAS path fragment. ROUTE_HINTS = ("ut/", "game/", "item", "club", "squad", "purchase", "consumable", "apply", "training", "position", "chemistry", "contract", "fitness", "healing", "playstyle", "manager", "pile", "delete", "transfer", "market", "auction", "sbs", "pack", "store") PRINTABLE = re.compile(rb"[\x20-\x7e]{6,}") def strings(mem, lo, hi): buf, bad = mem.read_pages(W_live(lo), hi - lo) if not buf: return [], bad out = [] for m in PRINTABLE.finditer(bytes(buf)): out.append((lo + m.start(), m.group().decode("ascii"))) return out, bad def main(): ap = argparse.ArgumentParser() ap.add_argument("--all", action="store_true") ap.add_argument("--grep") a = ap.parse_args() pid = W.find_pid() if pid is None: print("FIFA17.exe is not running.") return 1 base = W.dll_base(pid) if base is None: print("pid %d is up but %s is not mapped yet." % (pid, W.DLL)) return 1 mem = W.Mem(pid) global W_live W_live = lambda i: base + (i - W.IMG_BASE) print("pid=%d CardsDLL live base %#x" % (pid, base)) found = [] for lo, hi, name in ((RDATA_LO, RDATA_HI, ".rdata"), (DATA_LO, DATA_HI, ".data")): ss, bad = strings(mem, lo, hi) print(" %s: %d strings (%d bad pages)" % (name, len(ss), len(bad))) found.extend(ss) if a.grep: pat = a.grep.lower() sel = [(va, s) for va, s in found if pat in s.lower()] elif a.all: sel = found else: sel = [(va, s) for va, s in found if "/" in s and " " not in s and any(h in s.lower() for h in ROUTE_HINTS)] print("\n%d matching string(s):" % len(sel)) for va, s in sel: print(" %#x %s" % (va, s)) ctrl = [s for _, s in found if "ut/game" in s.lower()] print("\nCONTROL ('ut/game' present): %s (%d)" % ("OK" if ctrl else "EMPTY -> wrong region", len(ctrl))) return 0 if __name__ == "__main__": sys.exit(main())