- master_scrape.py: live master-server (ger.hologryph.com) ClientMessage replay over the two-socket /login + /connect handshake (PlayFab ticket auth). Pulled compartment defs, shop prices, research tree, storage, characters, expedition -> extracted/master_*.json - PlayFab confirmed auth-only for this title (Economy disabled); docs corrected - trampler_hashes.py: blueprint hash algo MD5(UTF8(compact-JSON)); CompartmentsHash(#1) and ConnectionsHash(#3) verified & generatable from scratch - walkerdto_to_blueprint.py: WalkerDto(expedition) -> WalkerBlueprintDto, enum int<->name, verified by storage->WS->storage round-trip - render_trampler.py: per-floor map from CompartmentsDatabase cell footprints (rotation solved via overlap check) + doors/hatches from Connections + turret arcs + cargo C1-C8 in game order - docs/MASTER_SERVER.md, docs/TRAMPLER.md; ghidra address-offset bug fixed (no -0x1000)
67 lines
2.2 KiB
Python
67 lines
2.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Annotate ghidra/decomp.c: resolve absolute VAs (0x180........) to IL2CPP symbol names
|
|
(methods.tsv) and string literals (strings.tsv). Ghidra image base = 0x180000000.
|
|
|
|
methods.tsv : <rva-decimal>\t<symbol> (rva = scriptAddress - 0x1000)
|
|
strings.tsv : <scriptAddress-decimal>\t<str> (raw ScriptString.Address)
|
|
|
|
Usage:
|
|
venv/bin/python reverse/resolve_decomp.py # writes ghidra/decomp.annotated.c
|
|
venv/bin/python reverse/resolve_decomp.py <substr> # print only funcs whose name matches substr
|
|
"""
|
|
import re, sys, os
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
G = os.path.join(ROOT, "ghidra")
|
|
BASE = 0x180000000
|
|
|
|
def load_methods():
|
|
m = {}
|
|
for l in open(os.path.join(G, "methods.tsv")):
|
|
if "\t" in l:
|
|
rva, name = l.rstrip("\n").split("\t", 1)
|
|
m[int(rva)] = name
|
|
return m
|
|
|
|
def load_strings():
|
|
s = {}
|
|
p = os.path.join(G, "strings.tsv")
|
|
if os.path.exists(p):
|
|
for l in open(p):
|
|
if "\t" in l:
|
|
a, v = l.rstrip("\n").split("\t", 1)
|
|
s[int(a)] = v
|
|
return s
|
|
|
|
def main():
|
|
methods = load_methods()
|
|
strings = load_strings()
|
|
src = open(os.path.join(G, "decomp.c"), encoding="utf-8", errors="replace").read()
|
|
hexre = re.compile(r"0x(1[0-9a-fA-F]{8,9})")
|
|
|
|
def repl(m):
|
|
va = int(m.group(1), 16)
|
|
rva = va - BASE
|
|
if rva in methods:
|
|
return "%s/*%s*/" % (m.group(0), methods[rva])
|
|
# string literal: try raw rva, rva+0x1000 (scriptAddress), and the table conventions
|
|
for cand in (rva, rva + 0x1000, va, va - BASE + 0x1000):
|
|
if cand in strings:
|
|
return '%s/*"%s"*/' % (m.group(0), strings[cand][:60])
|
|
return m.group(0)
|
|
|
|
out = hexre.sub(repl, src)
|
|
outp = os.path.join(G, "decomp.annotated.c")
|
|
open(outp, "w", encoding="utf-8").write(out)
|
|
nres = out.count("/*")
|
|
print("wrote %s (%d annotations)" % (outp, nres))
|
|
|
|
if len(sys.argv) > 1:
|
|
sub = sys.argv[1]
|
|
blocks = re.split(r"(?=// ==== )", out)
|
|
for b in blocks:
|
|
if sub.lower() in b[:200].lower():
|
|
print(b)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|