# -*- coding: utf-8 -*- """ q_pack_inv_7 -- DIMENSION 1 pass 7: who READS unopened(0xcd) and the unopenedPacks total. HYPOTHESES (a) The store pack element built by FUN_18013af30 is 0x158 bytes; `unopened` (atom 0x35d) lands at byte offset 0xcd and `useDefaultImage` at 0xcc. Any x86 access with disp32 == 0xcd inside .text is a candidate reader. (b) The FUT model singleton (DAT_1802e6398, written by FUN_18011d780) has a vtable whose slot +0x4e0 is the unopenedPacks-total setter. Find the vtable by locating the ctor that both calls FUN_18011d780 and stores a vtable ptr. (c) displayGroup(0xd9) is an OBJECT {priority(0x250):int, value(0x377):string}; value lands at element offset 0x00 and priority at 0x34, and the My Packs UI FUN_1800150d0 compares offset 0 against the literal "mypacks". CONTROLS * The disp32==0xcc scan MUST return FUN_1800150d0 (hand-verified: it reads *(char *)((longlong)puVar10 + 0xcc) to pick the pack background image). * The disp32 scanner is also run for 0x34 and must return FUN_1800150d0 too. * class_deser("FutSquadSaveServerResponse") must still be 0x180171a60. """ import struct import traceback OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres" def disp32_readers(disp): """functions containing a modrm with mod=10 and this disp32.""" pat = struct.pack(" %s %s" % ([hex(g) for g in got], "PASS" if 0x180171A60 in got else "FAIL")) for disp, tag, ctl in ((0xCC, "useDefaultImage (CONTROL)", 0x1800150D0), (0xCD, "unopened", None), (0x34, "displayGroup.priority (CONTROL)", 0x1800150D0)): r = disp32_readers(disp) print("\n### disp32 %#x %s : %d functions" % (disp, tag, len(r))) if ctl is not None: print(" CONTROL %#x present: %s" % (ctl, "PASS" if ctl in r else "FAIL")) for e in sorted(r): f = fm.getFunctionContaining(addr(e)) print(" %#x %-30s sites=%s" % (e, f.getName(), [hex(x) for x in r[e][:6]])) print("\n########## MODEL SINGLETON CTOR / VTABLE ##########") ctors = set() for (fr, ty, fn, en) in xrefs_to(0x18011D780): print(" caller of FUN_18011d780: %#x %s @%#x" % (fr, fn, en)) if en: ctors.add(en) for e in sorted(ctors): s = dec(e) print("\n--- ctor candidate %#x len=%d" % (e, len(s))) print(s[:4000]) open(OUT + "/d1_modelctor_%x.txt" % e, "w").write(s) print("\n########## VTABLE SCAN in .rdata for tables >= 0x950 bytes ##########") LO, HI = 0x180001000, 0x1801E4F62 blk = None for b in mem.getBlocks(): if b.getName() == ".rdata": blk = b start = int(blk.getStart().getOffset()) end = int(blk.getEnd().getOffset()) data = read_bytes(start, end - start + 1) n = len(data) // 8 qs = struct.unpack_from("<%dQ" % n, data, 0) i = 0 found = [] while i < n: if LO <= qs[i] <= HI: j = i while j < n and LO <= qs[j] <= HI: j += 1 if (j - i) * 8 >= 0x950: found.append((start + i * 8, (j - i) * 8)) i = j else: i += 1 print("candidate vtables >= 0x950 bytes: %d" % len(found)) for va, sz in found: f4e0 = qword(va + 0x4E0) f940 = qword(va + 0x940) if sz > 0x940 else 0 n4e0 = fm.getFunctionAt(addr(f4e0)) n940 = fm.getFunctionAt(addr(f940)) if f940 else None print(" vtable %#x size %#x [+0x4e0]=%#x %s [+0x940]=%#x %s" % (va, sz, f4e0, n4e0.getName() if n4e0 else "?", f940, n940.getName() if n940 else "?")) if n4e0 is not None: s = dec(f4e0) print(" --- [+0x4e0] len=%d\n%s" % (len(s), s)) open(OUT + "/d1_vt%x_slot4e0_%x.txt" % (va, f4e0), "w").write(s) print("\n########## pack-element consumers ##########") for va, tag in ((0x180014380, "find_group"), (0x18002C3C0, "tile_from_packelem"), (0x180012950, "group_ctor")): s = dec(va) print("\n--- %s %#x len=%d" % (tag, va, len(s))) print(s) open(OUT + "/d1_%s.txt" % tag, "w").write(s) print("\nDONE q_pack_inv_7") except Exception: traceback.print_exc()