#!/usr/bin/env python3
"""Localise une chaîne dans un minidump et l'attribue à un module chargé."""
import struct, sys, mmap

MODULE_LIST, MEMORY64_LIST = 4, 9

def u32(b, o): return struct.unpack_from("<I", b, o)[0]
def u64(b, o): return struct.unpack_from("<Q", b, o)[0]

def parse(path, needle):
    f = open(path, "rb")
    m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
    assert m[:4] == b"MDMP", "pas un minidump"
    nstreams, dir_rva = u32(m, 8), u32(m, 12)
    streams = {}
    for i in range(nstreams):
        o = dir_rva + i * 12
        streams[u32(m, o)] = (u32(m, o + 4), u32(m, o + 8))  # size, rva

    # --- modules ---
    mods = []
    if MODULE_LIST in streams:
        _, rva = streams[MODULE_LIST]
        n = u32(m, rva)
        for i in range(n):
            o = rva + 4 + i * 108
            base, size, name_rva = u64(m, o), u32(m, o + 8), u32(m, o + 20)
            ln = u32(m, name_rva)
            name = m[name_rva + 4: name_rva + 4 + ln].decode("utf-16-le", "replace")
            mods.append((base, size, name))
    mods.sort()

    # --- régions mémoire ---
    _, rva = streams[MEMORY64_LIST]
    nranges, base_rva = u64(m, rva), u64(m, rva + 8)
    ranges, off = [], base_rva
    for i in range(nranges):
        o = rva + 16 + i * 16
        start, sz = u64(m, o), u64(m, o + 8)
        ranges.append((off, sz, start))   # offset fichier, taille, VA
        off += sz

    print(f"{path} : {nranges} régions mémoire, {len(mods)} modules")

    # --- recherche ---
    pos, hits = 0, 0
    while True:
        pos = m.find(needle, pos)
        if pos < 0 or hits >= 8:
            break
        va = None
        for fo, sz, start in ranges:
            if fo <= pos < fo + sz:
                va = start + (pos - fo)
                break
        owner = "<hors module — allocation privée>"
        if va is not None:
            for b, s, nm in mods:
                if b <= va < b + s:
                    owner = f"{nm.split(chr(92))[-1]}  (base 0x{b:x}, +0x{va-b:x})"
                    break
        print(f"  offset 0x{pos:x}  VA {'0x%x' % va if va else '?'}  → {owner}")
        pos += 1
        hits += 1
    if not hits:
        print("  chaîne absente")
    return mods

mods = parse(sys.argv[1], sys.argv[2].encode())
print("\nmodules principaux :")
for b, s, nm in mods[:6]:
    print(f"  0x{b:012x}  {s/1024:9.0f} Ko  {nm.split(chr(92))[-1]}")
