# -*- coding: utf-8 -*-
r"""Quem chama um endereco, varrendo os bytes do executavel.

    python tools/quem_chama.py 0x004469c5 [0x...]

Existe porque o QuemChama.java do Ghidra devolve "total: 0" mesmo para funcoes
com dezenas de chamadores -- as referencias nao estao no programa analisado. Isto
nao depende de analise nenhuma: procura E8 (call) e E9 (jmp) cujo rel32 aponte
para o alvo. Apanha falsos positivos dentro de dados, poucos e faceis de ver.
"""
import struct
import sys

EXE = r"C:\Games\Axion\Axn.exe"


def seccoes(d):
    pe = struct.unpack_from("<I", d, 0x3C)[0]
    n = struct.unpack_from("<H", d, pe + 6)[0]
    tam_opt = struct.unpack_from("<H", d, pe + 20)[0]
    base = struct.unpack_from("<I", d, pe + 24 + 28)[0]
    tab = pe + 24 + tam_opt
    out = []
    for i in range(n):
        o = tab + i * 40
        vs, va, rs, ro = struct.unpack_from("<4I", d, o + 8)
        out.append((base + va, vs, ro, rs))
    return out


def main() -> int:
    if len(sys.argv) < 2:
        print(__doc__)
        return 1
    d = open(EXE, "rb").read()
    secs = seccoes(d)
    for arg in sys.argv[1:]:
        alvo = int(arg, 16)
        print("=== quem salta para 0x%08x ===" % alvo)
        n = 0
        for ini, vs, ro, rs in secs:
            for i in range(ro, ro + rs - 5):
                if d[i] not in (0xE8, 0xE9):
                    continue
                va = ini + (i - ro)
                rel = struct.unpack_from("<i", d, i + 1)[0]
                if va + 5 + rel == alvo:
                    print("   %-12s VA 0x%08x  offset 0x%06x"
                          % ("CALL" if d[i] == 0xE8 else "JMP", va, i))
                    n += 1
        print("   total: %d" % n)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
