Files
project-solana/games/rac1/dev/crash_lookup.py
T

76 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""crash_lookup.py: names the function at a crash offset in rac1_game.dll.
Builds from kit 2026.10.14 on name crashes in rac1_log.txt themselves (rac1_game.sym);
this is for older builds, or offsets from elsewhere.
usage: python crash_lookup.py rac1_game.dll rac1_game.pdb 0x0c4f6807 [more offsets]
The offset is the "Fault offset" of a Windows Application Error event (or a crash
address minus the DLL's base). The .pdb must come from the same build as the .dll:
the launcher deletes it after building, so rebuild by hand to keep it (see below).
A player's crash can be looked up with your own build as long as both were built
from the same kit version: the build is reproducible.
To keep the .pdb, run the build yourself from the game's folder (games\\rac1):
..\\..\\toolchain\\python\\python.exe -u build.py --platform windows --iso "C:\\path\\disc.iso" --repo decomp --levels 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18
with ..\\..\\toolchain\\zig on PATH. It reads only the PDB's public symbols."""
import bisect, struct, sys
def sections(dll):
d = open(dll, "rb").read(4096)
pe = struct.unpack_from("<I", d, 0x3C)[0]
n, opt = struct.unpack_from("<H", d, pe + 6)[0], struct.unpack_from("<H", d, pe + 20)[0]
o = pe + 24 + opt
return [struct.unpack_from("<8sII", d, o + i * 40) for i in range(n)] # name, vsize, rva
def pdb_streams(path):
f = open(path, "rb").read()
if not f.startswith(b"Microsoft C/C++ MSF 7.00\r\n\x1aDS\0\0\0"): sys.exit("not a PDB: " + path)
bs, _, nblocks, dirbytes, _, dirmap = struct.unpack_from("<6I", f, 32)
nb = lambda n: (n + bs - 1) // bs
blk = lambda i: f[i * bs:(i + 1) * bs]
mapblocks = struct.unpack_from("<%dI" % nb(dirbytes), blk(dirmap))
dirdata = b"".join(blk(b) for b in mapblocks)[:dirbytes]
ns = struct.unpack_from("<I", dirdata)[0]
sizes = struct.unpack_from("<%dI" % ns, dirdata, 4)
pos, out = 4 + 4 * ns, []
for s in sizes:
s = 0 if s == 0xFFFFFFFF else s
bl = struct.unpack_from("<%dI" % nb(s), dirdata, pos); pos += 4 * nb(s)
out.append(b"".join(blk(b) for b in bl)[:s])
return out
def publics(pdb):
st = pdb_streams(pdb)
dbi = st[3]
symrec = struct.unpack_from("<H", dbi, 20)[0] # DBI header: SymRecordStream
rec, p, syms = st[symrec], 0, []
while p + 4 <= len(rec):
ln, kind = struct.unpack_from("<HH", rec, p)
if kind == 0x110E: # S_PUB32: flags, offset, segment, name
_, off, seg = struct.unpack_from("<IIH", rec, p + 4)
name = rec[p + 14:rec.index(b"\0", p + 14)].decode("utf-8", "replace")
syms.append((seg, off, name))
p += 2 + ln
return syms
def main():
if len(sys.argv) < 4: sys.exit(__doc__)
secs = sections(sys.argv[1])
table = sorted((secs[seg - 1][2] + off, name) for seg, off, name in publics(sys.argv[2]) if 0 < seg <= len(secs))
addrs = [a for a, _ in table]
for arg in sys.argv[3:]:
rva = int(arg, 16)
i = bisect.bisect_right(addrs, rva) - 1
sec = next((s[0].rstrip(b"\0").decode() for s in secs if s[2] <= rva < s[2] + s[1]), "?")
if i < 0: print("%s: before every symbol (section %s)" % (arg, sec)); continue
print("%s: %s+0x%X (section %s)" % (arg, table[i][1], rva - table[i][0], sec))
main()