# -*- coding: utf-8 -*-
"""Compara a entrada NO MUNDO: a nossa contra a do oficial, alinhadas no mesmo ponto.

    python tools/comparar_mundo.py [captura_oficial.log]

Por que existe, se ja ha o comparar_entrada.py: aquele compara o nosso saida_servidor.log
(que so tem a saida do hop3) contra a sequencia oficial INTEIRA, que comeca no login. O
resultado sai deslocado em seis pacotes e parece que tudo difere, quando na verdade as duas
batem a partir do ponto em que se referem a mesma coisa. Aqui os dois lados sao cortados no
mesmo marco -- o primeiro 02 05, que e o charinfo -- e comparados de verdade.

O corte do fim tambem muda: a entrada termina quando o jogador comeca a AGIR. No oficial
isso e o primeiro 04 01 (movimento) vindo do client; como o nosso log so tem saida, aqui o
fim e o primeiro 04 02 de terceiro (outra entidade aparecendo), ou o fim do arquivo.
"""
import collections
import glob
import os
import re
import struct
import sys

SAIDA = r"D:\RoshEmulator\captures\saida_servidor.log"
MARCO = (2, 5)          # 02 05, o charinfo: o primeiro pacote que os dois lados tem


def frames_captura(caminho):
    """(direcao, corpo) de cada frame de uma captura do hook."""
    atual, blocos = None, []
    for linha in open(caminho, encoding="latin-1", errors="replace"):
        m = re.match(r"\[[\d:.]+\] (SEND|RECV) sock=", linha)
        if m:
            atual = [m.group(1), bytearray()]
            blocos.append(atual)
            continue
        if atual is not None and re.fullmatch(r"(?:[0-9A-Fa-f]{2} )+\s*", linha):
            atual[1].extend(bytes.fromhex(linha.strip()))
    for direcao, dados in blocos:
        pos = 0
        while pos + 4 <= len(dados):
            (t,) = struct.unpack_from(">I", dados, pos)
            if t == 0 or t > 16384 or pos + 4 + t > len(dados):
                break
            corpo = bytes(dados[pos + 4:pos + 4 + t])
            if len(corpo) >= 2:
                yield direcao, corpo
            pos += 4 + t


def sequencia_oficial(caminho):
    """Opcodes que o servidor manda, do charinfo ate o jogador se mexer."""
    seq, ligado = [], False
    for direcao, corpo in frames_captura(caminho):
        op = (corpo[0], corpo[1])
        if direcao == "SEND" and op == (4, 1) and ligado:
            break                       # o jogador andou: acabou a entrada
        if direcao != "RECV":
            continue
        if not ligado:
            if op != MARCO:
                continue
            ligado = True
        seq.append(op)
    return seq


def sequencia_nossa(caminho):
    if not os.path.exists(caminho):
        return []
    seq, ligado = [], False
    for linha in open(caminho, encoding="latin-1", errors="replace"):
        if linha.startswith("==="):
            continue
        h = linha.strip().replace(" ", "")
        if len(h) < 12:
            continue
        try:
            dados = bytes.fromhex(h)
        except ValueError:
            continue
        pos = 0
        while pos + 4 <= len(dados):
            (t,) = struct.unpack_from(">I", dados, pos)
            if t == 0 or t > 16384 or pos + 4 + t > len(dados):
                break
            corpo = dados[pos + 4:pos + 4 + t]
            if len(corpo) >= 2:
                op = (corpo[0], corpo[1])
                if not ligado and op == MARCO:
                    ligado = True
                if ligado:
                    seq.append(op)
            pos += 4 + t
    return seq


def main() -> int:
    if len(sys.argv) > 1:
        oficial = sys.argv[1]
    else:
        candidatos = sorted(glob.glob(r"C:\RoshHook\traffic*.log"),
                            key=os.path.getsize, reverse=True)
        oficial, melhor = None, 0
        for c in candidatos[:6]:
            n = len(sequencia_oficial(c))
            if n > melhor:
                oficial, melhor = c, n
    if not oficial:
        print("nenhuma captura utilizavel")
        return 1

    of = sequencia_oficial(oficial)
    no = sequencia_nossa(SAIDA)
    print("oficial: %-34s %d pacotes na entrada" % (os.path.basename(oficial), len(of)))
    print("nosso  : %-34s %d pacotes" % ("saida_servidor.log", len(no)))
    print()

    print("=" * 74)
    print("LADO A LADO, a partir do charinfo")
    print("=" * 74)
    print("%-5s %-10s %-10s" % ("#", "OFICIAL", "NOSSO"))
    iguais = 0
    for i in range(min(30, max(len(of), len(no)))):
        a = "%02x %02x" % of[i] if i < len(of) else "--"
        b = "%02x %02x" % no[i] if i < len(no) else "--"
        if a == b:
            iguais += 1
        print("%-5d %-10s %-10s %s" % (i, a, b, "" if a == b else "<-- difere"))
    print()
    print("   %d dos primeiros %d batem na ordem" % (iguais, min(30, max(len(of), len(no)))))

    c_of, c_no = collections.Counter(of), collections.Counter(no)
    print()
    print("=" * 74)
    print("OPCODES QUE O OFICIAL MANDA NA ENTRADA E NOS NAO")
    print("=" * 74)
    faltam = [(op, n) for op, n in c_of.most_common() if op not in c_no]
    if not faltam:
        print("   nenhum")
    for op, n in faltam:
        print("   %02x %02x   x%d" % (op[0], op[1], n))

    print()
    print("=" * 74)
    print("OPCODES QUE NOS MANDAMOS E O OFICIAL NAO")
    print("=" * 74)
    sobram = [(op, n) for op, n in c_no.most_common() if op not in c_of]
    if not sobram:
        print("   nenhum")
    for op, n in sobram:
        print("   %02x %02x   x%d" % (op[0], op[1], n))
    return 0


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