"""Compara os moldes de 02 05 salvos por classe e destaca os campos que variam.

    python comparar_classes.py            -- campos que mudam entre as classes
    python comparar_classes.py 200        -- procura o valor 200 em todos os moldes

Ideia: campos IGUAIS em todas as classes sao molde (aparencia base, flags, padding). Campos
que VARIAM sao atributos dependentes de classe -- forca, destreza, ataque, defesa, etc.
Quanto mais classes capturadas, mais limpo fica o sinal.

Para NOMEAR um campo nao basta ver que ele varia: e preciso saber o valor que o jogo mostra
na tela. Foi assim que HP, MP, EXP e nivel foram amarrados -- valor conhecido, busca no
pacote. Abra a ficha do personagem no jogo, me diga os numeros, e eu acho.
"""
import glob
import json
import os
import struct
import sys

DIR = r"D:\RoshEmulator\captures\classes"


def carregar():
    moldes = {}
    for cam in sorted(glob.glob(os.path.join(DIR, "charinfo_classe_*.bin"))):
        classe = int(os.path.basename(cam).split("_")[-1].split(".")[0])
        with open(cam, "rb") as f:
            moldes[classe] = f.read()
    return moldes


def procurar(moldes, alvo):
    print(f"procurando o valor {alvo} em cada molde:\n")
    for classe, b in sorted(moldes.items()):
        achados = []
        for off in range(0, len(b) - 4):
            for rot, fmt in (("BE32", ">I"), ("LE32", "<I"), ("BE16", ">H"), ("LE16", "<H")):
                try:
                    if struct.unpack_from(fmt, b, off)[0] == alvo:
                        achados.append(f"{off}:{rot}")
                except struct.error:
                    pass
        print(f"  classe {classe}: {achados[:12]}")


def comparar(moldes):
    classes = sorted(moldes)
    if len(classes) < 2:
        print("preciso de pelo menos 2 classes capturadas. Tenho:", classes)
        return
    tam = min(len(b) for b in moldes.values())
    print(f"{len(classes)} classes: {classes}   ({tam} bytes cada)\n")

    variam = []
    for off in range(6, tam - 4):
        vals = {c: struct.unpack_from("<I", moldes[c], off)[0] for c in classes}
        if len(set(vals.values())) > 1 and all(v < 100000 for v in vals.values()):
            variam.append((off, vals))

    # agrupa offsets vizinhos: um u32 lido em 4 posicoes seguidas gera 4 "achados"
    print(f"{len(variam)} posicoes com valor diferente entre classes.")
    print("Mostrando as que parecem inicio de campo (alinhadas em 2 bytes):\n")
    mostrados = 0
    for off, vals in variam:
        if off % 2:
            continue
        # ignora se o offset anterior ja mostrou os mesmos valores deslocados
        linha = "  ".join(f"c{c}={vals[c]}" for c in classes)
        print(f"  [{off:4d}] LE32  {linha}")
        mostrados += 1
        if mostrados >= 60:
            print("  ... (corta aqui)")
            break

    conhecidos = {22: "HP maximo", 30: "MP maximo", 10: "classe", 6: "id"}
    print("\ncampos ja identificados:")
    for off, rot in sorted(conhecidos.items()):
        vals = {c: struct.unpack_from("<I", moldes[c], off)[0] for c in classes}
        print(f"  [{off:4d}] {rot:12s} " + "  ".join(f"c{c}={vals[c]}" for c in classes))


def main():
    moldes = carregar()
    if not moldes:
        print(f"nenhum molde em {DIR}")
        return
    if len(sys.argv) > 1:
        procurar(moldes, int(sys.argv[1]))
    else:
        comparar(moldes)


if __name__ == "__main__":
    main()
