Bulk-decompiled ~17.2k combat/system functions (Ghidra 11.1.2 headless) and confirmed via a second toolchain what the capstone analysis found: - GetDamage is a pure component read (returns *(comp+0x10), 0 if absent), no constants. - PlainDamgeDealerComponent has CopyTo -> it's network-replicated (snapshot from server); the client receives damage, never computes it. - No client producer writes a damage value (GetDamage has no real combat caller; the rich calc factory has 0 callers). Conclusion documented: per-weapon damage numbers are server-authoritative runtime Entitas component values, assigned via fully-generic dispatch with no static class/index/string anchor -> not statically extractable from GameAssembly.dll. Trackable static artifacts are the model + formula RVAs. Adds reusable pipeline: reverse/ghidra_decomp_targets.py, reverse/find_damage_writes.py.
58 lines
1.8 KiB
Python
58 lines
1.8 KiB
Python
# Ghidra headless script: decompile a target list of IL2CPP functions to C.
|
|
# @category il2cpp
|
|
# Reads ghidra/targets.txt (rva<TAB>name), creates+names+decompiles each,
|
|
# dumps all C to ghidra/decomp.c for offline grepping.
|
|
import time
|
|
from ghidra.app.decompiler import DecompInterface
|
|
from ghidra.util.task import ConsoleTaskMonitor
|
|
from ghidra.program.model.symbol import SourceType
|
|
|
|
ROOT = "/home/downloadpizza/sand_tools/ghidra"
|
|
base = currentProgram.getImageBase()
|
|
|
|
decomp = DecompInterface()
|
|
decomp.openProgram(currentProgram)
|
|
monitor = ConsoleTaskMonitor()
|
|
|
|
out = open(ROOT + "/decomp.c", "w")
|
|
n = 0
|
|
ok = 0
|
|
t0 = time.time()
|
|
fp = open(ROOT + "/targets.txt")
|
|
for line in fp:
|
|
line = line.rstrip("\n")
|
|
if not line:
|
|
continue
|
|
rva, name = line.split("\t", 1)
|
|
# ascii-sanitize: obfuscated names use Cyrillic look-alikes -> replace
|
|
name = "".join((ch if ord(ch) < 128 else "_") for ch in name)
|
|
addr = base.add(int(rva))
|
|
fn = getFunctionAt(addr)
|
|
if fn is None:
|
|
disassemble(addr)
|
|
createFunction(addr, name)
|
|
fn = getFunctionAt(addr)
|
|
n += 1
|
|
if fn is None:
|
|
continue
|
|
try:
|
|
fn.setName(name, SourceType.USER_DEFINED)
|
|
except:
|
|
pass
|
|
try:
|
|
res = decomp.decompileFunction(fn, 25, monitor)
|
|
if res is not None and res.decompileCompleted():
|
|
c = res.getDecompiledFunction().getC()
|
|
c = "".join((ch if ord(ch) < 128 else "_") for ch in c)
|
|
out.write("\n// ==== %s @ +0x%x ====\n" % (name, int(rva)))
|
|
out.write(c)
|
|
ok += 1
|
|
except Exception, e:
|
|
pass
|
|
if n % 1000 == 0:
|
|
out.flush()
|
|
print("processed %d ok %d (%.0fs)" % (n, ok, time.time() - t0))
|
|
fp.close()
|
|
out.close()
|
|
print("DONE processed %d ok %d in %.0fs" % (n, ok, time.time() - t0))
|