fifa17-recon: store-enable live poke (online-readiness gate)
The FUT store 'not available' is FIFA's online-mode readiness gate (FUT::CompetitionManager), not a config flag. tools/store_enable_poke.py finds CardsDLL's live base and patches the 3 gate methods (0x1800f7fb0 IS_EASTORE_SERVICE_READY, 0x1800fb850 IS_STORE_ENABLED, 0x180100500 IS_COIN_PURCHASABLE) to 'mov eax,1; ret'. Reversible (saves originals; 'restore' subcommand; FIFA restart clears it). Needs ptrace_scope=0 + FIFA running. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PN5bmpDVQR1aXgefyWAt7o
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
#!/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
|
||||
GATES = {
|
||||
0x1800f7fb0: "IS_EASTORE_SERVICE_READY",
|
||||
0x1800fb850: "IS_STORE_ENABLED",
|
||||
0x180100500: "IS_COIN_PURCHASABLE",
|
||||
}
|
||||
RET_TRUE = bytes.fromhex("b801000000c3") # mov eax,1 ; ret
|
||||
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 in GATES.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(RET_TRUE))
|
||||
open(origf, 'wb').write(orig)
|
||||
data = RET_TRUE
|
||||
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()
|
||||
Reference in New Issue
Block a user