# -*- coding: utf-8 -*-
"""Tira o opcode de cada classe de pacote do AMo.exe decompilado.

    python tools/opcodes_do_binario.py            todas
    python tools/opcodes_do_binario.py Item Loot  so as que contem estas palavras

COMO FUNCIONA

Cada classe AMOPacket::CXxxPacket tem duas funcoes seguidas no binario:

    FUN_00651fcd   o construtor, que so poe a vftable:
                       *param_1 = AMOPacket::CItemDropPacket::vftable;

    FUN_00651fda   o RECONHECEDOR, que confere os dois bytes de assinatura:
                       if ((*pcVar1 == '06') && (pcVar1[1] == '04'))

O reconhecedor vem sempre a seguir ao construtor, e e ele que da o opcode. Basta andar
pelos blocos do decompilado em ordem: quando um bloco nomeia uma vftable, o bloco seguinte
tem o if com os dois bytes.

POR QUE ISTO E DE CONFIANCA

Nao e leitura de bytes a olho nem palpite de offset -- e o proprio codigo do client a dizer
que assinatura aceita. Os opcodes que ja tinhamos medidos por captura servem de prova: se os
que sairem daqui baterem com esses, o metodo esta certo para os restantes.
"""
import glob
import os
import re
import sys

RAIZ = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DECOMP = os.path.join(RAIZ, "ghidra", "decomp")
BARRA = chr(92)

# um char no decompilado aparece como 'A' ou como uma sequencia de escape hexadecimal
CHAR = "(" + BARRA + BARRA + "x[0-9a-fA-F]{2}|.)"
RE_VFTABLE = re.compile(r"AMOPacket::(C" + r"\w+)::vftable")
RE_ASSINATURA = re.compile(
    r"\*pcVar\d+ == '" + CHAR + r"'.{0,60}?pcVar\d+\[1\] == '" + CHAR + r"'", re.S)


def valor(s):
    return int(s[2:], 16) if s.startswith(BARRA + "x") else ord(s)


def extrair():
    texto = []
    for f in sorted(glob.glob(os.path.join(DECOMP, "lote_*.c"))):
        texto.append(open(f, encoding="utf-8", errors="replace").read())
    blocos = re.split("// ===== ", "".join(texto))
    achados = {}
    pendente = None
    for b in blocos:
        m = RE_VFTABLE.search(b)
        if m:
            pendente = m.group(1)
            continue
        if pendente:
            m2 = RE_ASSINATURA.search(b)
            if m2:
                achados[pendente] = (valor(m2.group(1)), valor(m2.group(2)))
        pendente = None
    return achados


def main() -> int:
    filtros = [a for a in sys.argv[1:] if not a.startswith("-")]
    achados = extrair()
    print("classes de pacote com opcode extraido: %d" % len(achados))
    print()
    linhas = []
    for nome, (a, b) in achados.items():
        if filtros and not any(f.lower() in nome.lower() for f in filtros):
            continue
        linhas.append((a, b, nome))
    linhas.sort()
    print("%-8s %s" % ("opcode", "classe"))
    for a, b, nome in linhas:
        print("%02x %02x    %s" % (a, b, nome))
    print()
    print("mostradas %d de %d" % (len(linhas), len(achados)))
    return 0


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