#!/usr/bin/env python3
"""Force the FIFA 17 FUT store OPEN by patching CardsDLL's online-readiness gate.

The "store is not available" screen is gated by FIFA's online-mode manager
(FUT::CompetitionManager). Three CardsDLL methods report store-enabled and only
return true once that manager reaches the online "service ready" state -- an
online/Blaze wall we can't reach offline. This pokes them to return true.

Reversible: originals are saved to /tmp/orig_<va>.bin; `restore` puts them back;
a FIFA restart also clears the patch (live-memory only, per session).

Needs ptrace_scope=0 (tools/root_arm.sh) and FIFA running. Run as YOUR action:
    !python3 tools/store_enable_poke.py          # patch (open the store)
    !python3 tools/store_enable_poke.py restore  # undo

Static gate methods (CardsDLL image base 0x180000000), patched to `mov eax,1; ret`:
  0x1800f7fb0  IS_EASTORE_SERVICE_READY  (mgr[0x6d4]==0x1fbd0)
  0x1800fb850  IS_STORE_ENABLED          (online-mode state stack non-empty)
  0x180100500  IS_COIN_PURCHASABLE       (commerce config)
"""
import sys, glob, os

IMG_BASE = 0x180000000
RET_TRUE = bytes.fromhex("b801000000c3")  # mov eax,1 ; ret
NOP2 = bytes.fromhex("9090")

PATCHES = {
    0x1800f7fb0: ("IS_EASTORE_SERVICE_READY", RET_TRUE),
    0x1800fb850: ("IS_STORE_ENABLED",         RET_TRUE),
    0x180100500: ("IS_COIN_PURCHASABLE",      RET_TRUE),
    0x180013cf0: ("IS_STORE_AVAILABLE",       RET_TRUE),
    0x180017543: ("JMP_BYPASS_RESOLUTION",    bytes.fromhex("eb3f")),
    0x180017487: ("NOP_JE_STORE_AVAILABLE",   NOP2),
    0x180017490: ("NOP_JNE_STORE_CACHED",      NOP2),
    0x1800175aa: ("NOP_JE_STORE_ENTITLEMENT", NOP2),
}
DLL_MATCH = "CardsDLL"


def find_pid():
    for d in glob.glob('/proc/[0-9]*'):
        try:
            if open(d + '/comm').read().strip() == 'FIFA17.exe':
                return int(d.split('/')[-1])
        except Exception:
            pass
    raise SystemExit("FIFA17.exe not running")


def cardsdll_base(pid):
    for line in open(f'/proc/{pid}/maps'):
        if DLL_MATCH in line:
            return int(line.split('-')[0], 16)   # lowest mapping = module base
    raise SystemExit("CardsDLL not mapped in FIFA process")


def main():
    restore = len(sys.argv) > 1 and sys.argv[1] == "restore"
    pid = find_pid()
    base = cardsdll_base(pid)
    print(f"FIFA pid={pid}  CardsDLL base={base:#x}  ({'RESTORE' if restore else 'PATCH'})")
    mem = f'/proc/{pid}/mem'
    for va, (name, patch_bytes) in PATCHES.items():
        live = base + (va - IMG_BASE)
        origf = f'/tmp/orig_{live:x}.bin'
        if restore:
            if not os.path.exists(origf):
                print(f"  {name}: no saved original, skip"); continue
            data = open(origf, 'rb').read()
        else:
            with open(mem, 'rb') as f:
                f.seek(live); orig = f.read(len(patch_bytes))
            open(origf, 'wb').write(orig)
            data = patch_bytes
        with open(mem, 'r+b') as f:
            f.seek(live); f.write(data)
            f.seek(live); chk = f.read(len(data))
        print(f"  {name:26s} @ {live:#x} -> {chk.hex()}")
    print("Done." + ("" if restore else "  Now open the FUT Store in-game."))


if __name__ == "__main__":
    main()
