"""D2 QUICK SELL, batch 2. Nail down the remaining unknowns from batch 1. BATCH 1 ESTABLISHED (raw asm, 0x180141025..0x180141140 inside FUN_18013fe00): if ([RBP+0x198] == 0) // server discardValue, JA = unsigned price = SELECT [0x1802231e4] FROM "fcc_discardcoins" WHERE "cardtype" == [RBP+0x1ac] AND [0x180207848] == [RBP+0x1b4] AND "rare" == [RBP+0x1b8] v = (byte[RBP+0x214] * price) / 100, +1 if remainder >= 0x32 [RBP+0x19c] = v REMAINING QUESTIONS 1. what are the strings at 0x1802231e4 and 0x180207848? 2. EVERY instruction in FUN_18013fe00 that WRITES [RBP+0x1b4] (the second key, the decompiler shows no atom arm writing it, which would mean it is always the initialiser value; that must be checked on instructions, not on the decompiler's merged locals). Same for [RBP+0x198], [RBP+0x19c], [RBP+0x1ac], [RBP+0x214], [RBP+0x1b8]. CONTROL: [RBP+0x214] must show exactly one write from the atom-0x274 (rating) arm, which we already know exists. 3. where do [RBP+0x198] and [RBP+0x19c] end up in the heap item object? print the tail copy-construct. 4. Q2 ASSIGN-vs-ADD. resolve the three vtables that hold the discard triple (0x1802fb340.., 0x180270580.., 0x180220470..) and 0x1801f3100.., print every slot, then decompile the response-apply methods so the consumer of resp+0x28 is read directly. 5. the FUT manager singleton DAT_1802e6398: print vtable slots 0x9c0..0xa80 so the credit setter next to the item-remove slot 0xa30 / id-touch slot 0xa48 is visible, and decompile 0xa30 and any credit-looking neighbour. 6. every function in the DLL that contains the immediate 0x326 (totalCredits) in any of the four dispatch forms, so all credit consumers are enumerated. CONTROL: the list must contain FUN_180127300 and FUN_1801279c0, both of which are already known to carry a `== 0x326` arm. Nothing is truncated; len() printed for every decompile. """ import traceback, os OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/store/qs/" os.makedirs(OUT, exist_ok=True) def dump(tag, va, path=None): try: src = dec(va) except Exception as e: src = "// decompile threw %r" % (e,) print("=" * 78) print("%s %#x fname=%s len(src)=%d (FULL)" % (tag, va, fname(va), len(src))) print("=" * 78) print(src) if path: open(OUT + path, "w").write(src) return src def insns(va): f = func(va) out = [] if f is None: return out it = listing.getInstructions(f.getBody(), True) while it.hasNext(): i = it.next() out.append((int(i.getAddress().getOffset()), str(i))) return out try: print("##### 1: the two unknown literals #####") for a in (0x1802231e4, 0x180207848, 0x180223208, 0x18022315c): print(" %#x -> %r" % (a, rd_str(a, 64))) print("\n##### 2: every reference to the six stack slots in the ITEM deser #####") ins = insns(0x18013fe00) print(" instruction count in FUN_18013fe00 = %d" % len(ins)) slots = ("0x198", "0x19c", "0x1ac", "0x1b0", "0x1b4", "0x1b8", "0x214", "0x1a0", "0x1a4", "0x1a8") for s in slots: pat = "RBP + " + s + "]" hits = [(a, t) for a, t in ins if pat in t] print(" --- [RBP + %s] : %d refs ---" % (s, len(hits))) for a, t in hits: print(" %#x %s" % (a, t)) print("\n##### 3: the tail of the ITEM deser (copy-construct into the heap item) #####") f = func(0x18013fe00) lo = int(f.getEntryPoint().getOffset()) hi = int(f.getBody().getMaxAddress().getOffset()) print(" function body %#x..%#x" % (lo, hi)) for a, t in ins: if a >= 0x180141160: print(" %#x %s" % (a, t)) print("\n##### 4: the discard action vtables #####") for base in (0x1802fb300, 0x180270560, 0x180220450, 0x1801f3100): print(" --- vtable-ish dump at %#x ---" % base) for off, tgt, nm in vtable(base, 48): extra = "" if 0x180000000 <= tgt < 0x181000000: try: extra = repr(rd_str(tgt, 40)) except Exception: extra = "" print(" +%#05x %#018x %-20s %s" % (off, tgt, nm, extra if not nm else "")) print("\n##### 4b: response-apply candidates #####") # everything referenced next to the deser in those tables seen = set() for base in (0x1802fb300, 0x180270560, 0x180220450, 0x1801f3100): for off, tgt, nm in vtable(base, 48): if nm and tgt not in seen and fm.getFunctionAt(addr(tgt)): seen.add(tgt) print(" %d distinct functions in those tables" % len(seen)) for t in sorted(seen): try: src = dec(t) except Exception as e: src = "// threw %r" % e if "0x28" in src or "0x326" in src or "credit" in src.lower(): print("=" * 78) print("CANDIDATE %#x %s len=%d" % (t, fname(t), len(src))) print("=" * 78) print(src) print("\n##### 5: FUT manager singleton vtable #####") try: mgr_vt_holder = 0x1802e6398 print(" DAT_1802e6398 is a runtime pointer; using static xrefs instead") except Exception: pass # find the vtable by looking at who writes DAT_1802e6398 for frm, typ, fn, ent in xrefs_to(0x1802e6398): print(" xref to DAT_1802e6398: %#x %s %s %#x" % (frm, typ, fn, ent)) print("\n##### 6: every function containing the 0x326 immediate #####") ATOM = 0x326 found = {} it = fm.getFunctions(True) n = 0 while it.hasNext(): fn = it.next() n += 1 try: body = listing.getInstructions(fn.getBody(), True) except Exception: continue hit = [] prev = None while body.hasNext(): i = body.next() t = str(i) if "0x326" in t: hit.append((int(i.getAddress().getOffset()), t)) if hit: found[int(fn.getEntryPoint().getOffset())] = (fn.getName(), hit) print(" scanned %d functions; %d contain 0x326" % (n, len(found))) for e in sorted(found): nm, hit = found[e] print(" %#x %s" % (e, nm)) for a, t in hit: print(" %#x %s" % (a, t)) except Exception: traceback.print_exc()