#!/usr/bin/env python3
"""Did clubPlayers actually land in the client, or was it eaten as the envelope key?

Read-only. The argument cannot settle this; the client's own memory can.

Chain, from the comment at utas_server.py:1076 (established earlier by two independent
agents and two reviewers, so this probe TESTS that chain rather than assuming it):
    R = <model> + 0x1fd70          (FUN_18011a810 is `lea rax,[rcx+0x1fd70]; ret`)
    clubPlayers  -> R + 0x3c
    auctionCount -> R + 0x38
The server logged "HUB: clubPlayers=205 auctionCount=0" for this session.

PREDICTIONS, stated before reading so this cannot be rationalised after the fact:
  * if R+0x3c reads 205, clubPlayers reached its arm. The flat two-key hub body is
    fine and the envelope worry does not apply to this root.
  * if R+0x3c reads 0 while R+0x38 reads 0 too, the result is ambiguous, because
    auctionCount is legitimately 0 this session. Say so rather than claiming a result.
  * if R+0x3c reads 0 and some other plausible field is populated, clubPlayers was
    eaten as the first key/value pair and the MY CLUB tile is showing a wrong number.
"""
import os
import struct

pid = None
for d in os.listdir('/proc'):
    if d.isdigit():
        try:
            if open('/proc/%s/comm' % d).read().strip() == 'FIFA17.exe':
                pid = int(d)
                break
        except Exception:
            pass
if not pid:
    raise SystemExit("FIFA17.exe not running")

base = None
for ln in open('/proc/%d/maps' % pid):
    if 'CardsDLL' in ln:
        base = int(ln.split('-')[0], 16)
if not base:
    raise SystemExit("CardsDLL not mapped: the client has not reached Ultimate Team")
slide = base - 0x180000000
print("pid %d  cardsdll %#x  slide %#x" % (pid, base, slide))

fd = os.open('/proc/%d/mem' % pid, os.O_RDONLY)


def rd(va, n):
    return os.pread(fd, n, va)


# Prove the slide before trusting any address derived from it.
pe = open('/mnt/games/FIFA 17/CardsDLL_Win64_retail.dll', 'rb').read()
off = 0x180180D00 - 0x180000000 - 0x1000 + 0x400
ok = pe[off:off + 32] == rd(0x180180D00 + slide, 32)
print("slide control (FNV prologue): %s" % ("MATCH" if ok else "MISMATCH -- STOP"))
if not ok:
    raise SystemExit(1)

model = struct.unpack('<Q', rd(0x1802E6398 + slide, 8))[0]
print("model singleton %#x" % model)
R = model + 0x1FD70
club, auction = struct.unpack('<i', rd(R + 0x3C, 4))[0], struct.unpack('<i', rd(R + 0x38, 4))[0]
print("\n  R = model+0x1fd70 = %#x" % R)
print("  R+0x3c clubPlayers  = %d   (server sent 205)" % club)
print("  R+0x38 auctionCount = %d   (server sent 0)" % auction)

print("\nVERDICT:")
if club == 205:
    print("  clubPlayers REACHED its arm. The flat hub body parses correctly and the")
    print("  envelope concern does not apply to FutGetHubData.")
elif club == 0:
    print("  clubPlayers is 0. Either it was eaten as the first key/value pair, or the")
    print("  hub has not been loaded this session. Check the tile in game before")
    print("  concluding: auctionCount is legitimately 0, so it cannot break the tie.")
else:
    print("  clubPlayers = %d, which is neither 205 nor 0. The chain above is wrong"
          " somewhere." % club)
os.close(fd)
