# Q9: FUN_18004c180 builds the squad-service object registered into DAT_1802dfd18. # Recover its vtable, then decompile AddPlayerToSquad(+0xc8) and SaveCurrentSquad # (+0x28) -- the two slots that decide whether PUT /squad is ever issued. out = open("/tmp/ghidra_fut/squad_service.txt", "w") P = lambda *a: print(*a, file=out) P("=== factory FUN_18004c180 ===") P(dec(0x18004c180)) # find vtable pointers written by the factory f = func(0x18004c180) cands = {} for a in f.getBody().getAddresses(True): ins = listing.getInstructionAt(a) if ins is None: continue for r in ins.getReferencesFrom(): t = int(r.getToAddress().getOffset()) if 0x1801e5000 <= t <= 0x1802891ff: cands[t] = cands.get(t, 0) + 1 P("\n=== vtable candidates referenced by the factory ===") best = None for t, n in sorted(cands.items()): try: fns = [fm.getFunctionAt(addr(qword(t + i * 8))) for i in range(6)] except Exception: continue nf = sum(1 for x in fns if x) if nf >= 5: P(" %#x (%d/6 fnptrs, %d refs)" % (t, nf, n)) if best is None: best = t SLOTS = {0x20: "SaveSquad", 0x28: "SaveCurrentSquad", 0x30: "RemovePlayer", 0x40: "GetCurrentSquadID?", 0x48: "GetCurrentSquadChemistry", 0x58: "GetCurrentSquadData", 0x80: "GetSquadList", 0x88: "GetSquads", 0x90: "SelectSquadById", 0xa8: "IsCardInSquad", 0xb8: "GetSquadLineup", 0xc0: "LoadActiveSquad", 0xc8: "AddPlayerToSquad"} for t in sorted(cands): try: fns = [fm.getFunctionAt(addr(qword(t + i * 8))) for i in range(6)] except Exception: continue if sum(1 for x in fns if x) < 5: continue P("\n=== VTABLE %#x ===" % t) for off, tgt, name in vtable(t, 40): tag = SLOTS.get(off, "") P(" +%#04x -> %#x %-16s %s" % (off, tgt, name, tag)) P("\n--- key slot decompiles ---") for off in (0x28, 0xc8, 0x88, 0x80, 0x90, 0xc0): try: tgt = qword(t + off) except Exception: continue if not fm.getFunctionAt(addr(tgt)): continue P("### +%#04x %s -> %#x" % (off, SLOTS.get(off, ""), tgt)) P(dec(tgt)) break out.close() print("wrote squad_service.txt")