#!/usr/bin/env python3
"""Minimal minidump reader: exception code/address + module list, so a FIFA 17
CrashDump can be mapped to <module>+RVA (and thence to a Ghidra address)."""
import struct, sys

p = sys.argv[1]
d = open(p, "rb").read()
assert d[:4] == b"MDMP", "not a minidump: %r" % d[:4]
ver, nstreams, dirrva = struct.unpack_from("<IiI", d, 4)[0], *struct.unpack_from("<II", d, 8)
nstreams, dirrva = struct.unpack_from("<II", d, 8)

streams = {}
for i in range(nstreams):
    st, size, rva = struct.unpack_from("<III", d, dirrva + i * 12)
    streams.setdefault(st, []).append((size, rva))
print("streams:", sorted(streams))


def mstring(rva):
    (ln,) = struct.unpack_from("<I", d, rva)
    return d[rva + 4: rva + 4 + ln].decode("utf-16-le", "replace")


mods = []
if 4 in streams:
    size, rva = streams[4][0]
    (n,) = struct.unpack_from("<I", d, rva)
    off = rva + 4
    for i in range(n):
        base, sz, csum, ts, nrva = struct.unpack_from("<QIIII", d, off)
        mods.append((base, sz, mstring(nrva)))
        off += 108
    print("modules:", len(mods))

exc_addr = None
if 6 in streams:
    size, rva = streams[6][0]
    tid, _pad = struct.unpack_from("<II", d, rva)
    code, flags, recptr, addr, nparams = struct.unpack_from("<IIQQI", d, rva + 8)
    exc_addr = addr
    NAMES = {0xC0000005: "ACCESS_VIOLATION", 0xC000001D: "ILLEGAL_INSTRUCTION",
             0xC0000094: "INT_DIVIDE_BY_ZERO", 0xC0000096: "PRIV_INSTRUCTION",
             0x80000003: "BREAKPOINT", 0xC00000FD: "STACK_OVERFLOW",
             0xC0000374: "HEAP_CORRUPTION", 0xC0000135: "DLL_NOT_FOUND"}
    print("\n=== EXCEPTION ===")
    print("  thread   : %#x" % tid)
    print("  code     : %#010x  %s" % (code, NAMES.get(code, "?")))
    print("  address  : %#018x" % addr)
    params = [struct.unpack_from("<Q", d, rva + 8 + 32 + i * 8)[0] for i in range(min(nparams, 15))]
    print("  params   : %s" % [hex(x) for x in params])
    if code == 0xC0000005 and len(params) >= 2:
        print("  -> %s at %#x" % ({0: "READ from", 1: "WRITE to", 8: "EXECUTE at"}.get(params[0], "access"),
                                  params[1]))

if exc_addr is not None:
    hit = [m for m in mods if m[0] <= exc_addr < m[0] + m[1]]
    print("\n=== FAULTING MODULE ===")
    if hit:
        base, sz, name = hit[0]
        short = name.split("\\")[-1]
        print("  %s  base=%#x  size=%#x" % (short, base, sz))
        print("  RVA = %#x" % (exc_addr - base))
        print("  ghidra (PE base 0x180000000) = %#x" % (0x180000000 + (exc_addr - base)))
    else:
        print("  address %#x is in NO loaded module (bad indirect call / corrupt ptr)" % exc_addr)

print("\n=== modules of interest ===")
for base, sz, name in mods:
    s = name.split("\\")[-1].lower()
    if any(k in s for k in ("cards", "fifa", "dbdata", "core", "game")):
        print("  %-28s base=%#014x size=%#x" % (name.split("\\")[-1], base, sz))
