# -*- coding: utf-8 -*-
"""Compara a SEQUENCIA de entrada no mundo: a nossa contra a do servidor oficial.

    python tools/comparar_entrada.py

De um lado, o que o oficial manda desde o login ate o jogador comecar a andar (capturas
em C:/RoshHook). Do outro, o que o nosso servidor manda no mesmo trecho
(captures/saida_servidor.log, gravado por registrar_saida).

Serve pra responder "o que falta pro client entrar no mundo" com dado em vez de bisseccao.
Em 24/08 o corte do stream de entrada quebrou a entrada: o client recebeu tudo que a gente
mandou, nao reclamou de nada, e simplesmente NAO enviou o 04 00 que dispara o resto --
ficou em keepalive pra sempre. Nenhum erro, nenhuma mensagem. A unica forma de achar o que
faltava e comparar as duas sequencias lado a lado.
"""
import collections
import glob
import os
import re
import struct
import sys

SAIDA = r"D:\RoshEmulator\captures\saida_servidor.log"


def frames_captura(cam):
    """Frames do hook, com direcao."""
    cur, out = None, []
    for linha in open(cam, encoding="latin-1", errors="replace"):
        m = re.match(r"\[([\d:.]+)\] (SEND|RECV) sock=(0x[0-9A-Fa-f]+) len=(\d+)", linha)
        if m:
            cur = [m.group(1), m.group(2), m.group(3), bytearray()]
            out.append(cur)
            continue
        if cur is not None and re.fullmatch(r"(?:[0-9A-Fa-f]{2} )+\s*", linha):
            cur[3].extend(bytes.fromhex(linha.strip()))
    for h, d, s, b in out:
        pos = 0
        while pos + 4 <= len(b):
            (t,) = struct.unpack_from(">I", b, pos)
            if t == 0 or t > 16384 or pos + 4 + t > len(b):
                break
            yield h, d, s, bytes(b[pos + 4:pos + 4 + t])
            pos += 4 + t


def sequencia_oficial(cam):
    """A ordem dos pacotes do servidor na conexao do MUNDO, ate o jogador andar."""
    por_sock = collections.defaultdict(list)
    for h, d, s, f in frames_captura(cam):
        por_sock[s].append((h, d, f))
    # o socket do mundo e o que tem 04 02
    alvo = None
    for s, lst in por_sock.items():
        if any(f[:2] == bytes([4, 2]) for _, _, f in lst):
            if alvo is None or len(lst) > len(por_sock[alvo]):
                alvo = s
    if alvo is None:
        return []
    seq = []
    for h, d, f in por_sock[alvo]:
        if d == "SEND" and f[:2] == bytes([4, 1]):
            break                      # o jogador comecou a andar: fim da entrada
        seq.append((d, (f[0], f[1]), len(f)))
    return seq


def sequencia_nossa(cam):
    """O que o nosso servidor mandou, do saida_servidor.log."""
    if not os.path.exists(cam):
        return []
    seq = []
    rotulo = None
    for linha in open(cam, encoding="latin-1", errors="replace"):
        if linha.startswith("=== "):
            rotulo = linha[4:].strip()
            continue
        h = linha.strip().replace(" ", "")
        if not h or len(h) < 12:
            continue
        try:
            b = bytes.fromhex(h)
        except ValueError:
            continue
        pos = 0
        while pos + 4 <= len(b):
            (t,) = struct.unpack_from(">I", b, pos)
            if t == 0 or t > 16384 or pos + 4 + t > len(b):
                break
            f = b[pos + 4:pos + 4 + t]
            seq.append(("RECV", (f[0], f[1]), len(f), rotulo))
            pos += 4 + t
    return seq


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

    of = sequencia_oficial(melhor)
    nosso = sequencia_nossa(SAIDA)

    of_srv = [op for d, op, _ in of if d == "RECV"]
    no_srv = [op for _, op, _, _ in nosso]

    print("oficial: %s  (%d pacotes do servidor na entrada)"
          % (os.path.basename(melhor), len(of_srv)))
    print("nosso  : saida_servidor.log  (%d pacotes)" % len(no_srv))
    print()

    c_of = collections.Counter(of_srv)
    c_no = collections.Counter(no_srv)

    print("=" * 78)
    print("O QUE O OFICIAL MANDA NA ENTRADA E NOS NAO")
    print("=" * 78)
    faltando = [(op, n) for op, n in c_of.most_common() if op not in c_no]
    if not faltando:
        print("   nada -- todos os opcodes da entrada oficial aparecem na nossa saida")
    for op, n in faltando:
        print("   %02x %02x   x%d" % (op[0], op[1], n))

    print()
    print("=" * 78)
    print("ORDEM DOS PRIMEIROS PACOTES  (o client e sensivel a isso)")
    print("=" * 78)
    print("%-28s %s" % ("OFICIAL", "NOSSO"))
    for i in range(min(28, max(len(of_srv), len(no_srv)))):
        a = "%02x %02x" % of_srv[i] if i < len(of_srv) else "--"
        b = "%02x %02x" % no_srv[i] if i < len(no_srv) else "--"
        marca = "" if a == b else "   <-- difere"
        print("%-4d %-23s %-8s%s" % (i, a, b, marca))
    return 0


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