# -*- coding: utf-8 -*-
r"""Procura, numa captura, os pares (codigo de opcao, valor) que o servidor manda por item.

    python tools/achar_opcoes_no_fio.py <log>
    python tools/achar_opcoes_no_fio.py            usa a captura corrente do oficial

O QUE PROCURA. As "Caracteristicas especiais" de um item -- "Multiplicador de critico +0,15",
"Classificacao de ataque +2,00" -- nao pertencem ao item, pertencem a INSTANCIA: sao sorteadas
quando ele cai e ficam guardadas do lado do servidor. Nenhuma tabela do client as liga ao
item, portanto so se veem no fio.

Os codigos ja sao conhecidos: `grupo * 100 + grau`, com a lista completa em
Data/Items/UniqueOptionType.ini e os nomes decifrados do executavel. Aqui varre-se cada
pacote a procura desses numeros e mostra-se o que vem a seguir a cada um -- que e o candidato
a valor.

ONDE GRAVAR. A LOJA DE CONSIGNACAO e o melhor sitio: mostra dezenas de itens de outros
jogadores de uma vez, cada um com as suas opcoes, e o servidor tem de mandar todas para o
client conseguir desenhar as caixas de texto. Nao e preciso possuir nada. Basta abrir a loja
com o gravador ligado e percorrer algumas paginas -- de preferencia filtrando por armas de
nivel alto, que sao as que trazem mais opcoes.
"""
import collections
import os
import struct
import sys

RAIZ = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(RAIZ, "tools", "analise"))
sys.path.insert(0, os.path.join(RAIZ, "server"))
from ler_hook import pacotes                                        # noqa: E402
import data_files as D                                              # noqa: E402

OFICIAL = "C:" + os.sep + "RoshHook" + os.sep + "traffic.log"


def catalogo():
    """{codigo: (grupo, grau, nome)} de todas as opcoes que o client declara."""
    D.carregar()
    saida = {}
    for r in D.tabela("UniqueOptionType"):
        try:
            saida[int(r["codigo"])] = (int(r["grupo"]), int(r["grau"]),
                                       r.get("descricao") or "?")
        except (TypeError, ValueError):
            continue
    return saida


def varrer(caminho, cat):
    """Cada ocorrencia de um codigo conhecido, com o que vem imediatamente a seguir."""
    achados = collections.defaultdict(list)
    por_opcode = collections.Counter()
    total = 0
    for _t, op, c in pacotes(caminho, direcao="RECV"):
        total += 1
        for i in range(0, len(c) - 8, 2):        # 2 em 2: o campo pode nao estar alinhado
            for fmt in ("<I", ">I"):
                try:
                    v = struct.unpack_from(fmt, c, i)[0]
                except struct.error:
                    continue
                if v not in cat:
                    continue
                seguintes = []
                for j, f2 in ((i + 4, "<I"), (i + 4, "<f"), (i + 4, "<H")):
                    try:
                        seguintes.append((f2, struct.unpack_from(f2, c, j)[0]))
                    except struct.error:
                        pass
                achados[v].append((op.hex(" "), fmt, i, len(c), seguintes))
                por_opcode[op.hex(" ")] += 1
    return achados, por_opcode, total


def main() -> int:
    caminho = sys.argv[1] if len(sys.argv) > 1 else OFICIAL
    if not os.path.exists(caminho):
        print("captura nao existe: %s" % caminho)
        return 1
    cat = catalogo()
    print("codigos de opcao conhecidos: %d" % len(cat))
    print("a varrer %s (%.1f MB)" % (os.path.basename(caminho),
                                     os.path.getsize(caminho) / 1e6))
    achados, por_opcode, total = varrer(caminho, cat)
    print("pacotes lidos: %d" % total)
    print()
    if not achados:
        print("Nenhum codigo de opcao apareceu nesta captura.")
        print()
        print("Ou nao ha itens com opcoes nela, ou o codigo vai no fio de outra forma")
        print("(so o grupo, so o indice, ou comprimido). Ver o cabecalho deste arquivo")
        print("para o que gravar.")
        return 1

    print("EM QUE PACOTES APARECEM:")
    for op, n in por_opcode.most_common(10):
        print("   opcode %s   %d ocorrencias" % (op, n))
    print()
    print("CODIGOS ENCONTRADOS (o mais provavel primeiro):")
    print("%9s %6s %5s  %-28s %s" % ("codigo", "grupo", "grau", "nome", "vezes"))
    for cod, ocs in sorted(achados.items(), key=lambda kv: -len(kv[1]))[:30]:
        g, grau, nome = cat[cod]
        print("%9d %6d %5d  %-28s %d" % (cod, g, grau, nome[:28], len(ocs)))
    print()
    print("PRIMEIRA OCORRENCIA DE CADA, com o que vem a seguir:")
    for cod, ocs in sorted(achados.items(), key=lambda kv: -len(kv[1]))[:8]:
        op, fmt, i, tam, seg = ocs[0]
        print("   %d (%s) em %s, offset %d de %d bytes, %s" %
              (cod, cat[cod][2][:24], op, i, tam, fmt))
        for f2, v in seg:
            print("        seguinte como %-4s %s" % (f2, v))
    return 0


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