# Q16: FutComponentServicesImpl::FutSquadServiceImpl -- get its ctor + vtable and # decompile SaveCurrentSquad(+0x50) / AddPlayerToSquad(+0x168) for real. out = open("/tmp/ghidra_fut/squad_impl.txt", "w") P = lambda *a: print(*a, file=out) P("=== all FutComponentServicesImpl::* service classes ===") for a in find_all(b"FutComponentServicesImpl::", blocks=(".rdata", ".data")): P(" %#x %s" % (a, rd_str(a, 90))) P("\n=== ctor/factory FUN_180189be0 ===") P(dec(0x180189be0)[:3000]) NAME = None for a in find_all(b"FutComponentServicesImpl::FutSquadServiceImpl", blocks=(".rdata", ".data")): NAME = a P("\nclass-name string @ %#x; xrefs:" % NAME) for frm, typ, fn, ent in xrefs_to(NAME): P(" %#x %s in %s @ %#x" % (frm, typ, fn, ent)) # The ctor writes the vtable into the object. Find vtables whose slot count is # large and that live near other Fut service vtables; verify by checking that # +0x50 / +0x168 / +0x198 / +0x1b8 / +0x1c8 are all real functions. P("\n=== candidate FutSquadServiceImpl vtables (>=58 slots) ===") TXT_LO, TXT_HI = 0x180001000, 0x1801e4fff def is_fn(v): return TXT_LO <= v <= TXT_HI and fm.getFunctionAt(addr(v)) is not None p, cur, runs = 0x1801e5000, None, [] while p < 0x2891f0 + 0x180000000: try: v = qword(p) except Exception: v = 0 if is_fn(v): if cur is None: cur = [p, 0] cur[1] += 1 else: if cur and cur[1] >= 58: runs.append(tuple(cur)) cur = None p += 8 if cur and cur[1] >= 58: runs.append(tuple(cur)) for s, n in runs: nm = rd_str(s + n * 8, 60) P(" vtable %#x %d slots trailing=%r" % (s, n, nm[:45])) P("\n=== slot decompiles for every candidate ===") for s, n in runs: nm = rd_str(s + n * 8, 60) P("\n##### vtable %#x (%d slots, %r) #####" % (s, n, nm[:40])) for off, tag in ((0x50, "SaveCurrentSquad"), (0x168, "AddPlayerToSquad")): if off // 8 >= n: continue t = qword(s + off) f = fm.getFunctionAt(addr(t)) P("--- +%#05x %s -> %#x %s ---" % (off, tag, t, f.getName() if f else "")) P(dec(t)[:3000]) out.close() print("wrote squad_impl.txt")