# -*- coding: utf-8 -*-
"""Tira do stream de entrada tudo que a gente ja gera sozinho, deixando so o resto.

    python tools/filtrar_stream.py            mostra o que sairia, sem gravar
    python tools/filtrar_stream.py --aplicar  grava o stream novo

O `enterworld_nivel1.bin` e o ultimo pedaco grande de replay: 171 frames gravados de uma
sessao ALHEIA, mandados a todo jogador que entra. Medindo o conteudo:

    148 frames (86%)  a gente ja gera, e melhor:
        51x 04 02   aparicoes de entidades que NAO existem no nosso mundo
        60x 04 01   os movimentos dessas entidades fantasma
        28x 04 03   e o sumico delas
         9x 08 00/01/02, 02 05, 06 05  -- estes a gente monta do banco

     23 frames (14%)  so existem aqui, e nunca foram implementados:
        05 05  lista de skills do personagem
        04 04  troca de zona
        08 05, 08 07, 08 08, 08 09, 08 0f, 09 02, 09 04,
        0a 0a, 0a 0c, 0a 0f, 0a 13, 0d 0f, 0e 0c, 0f 11, 16 01, 05 0c, 06 25, 06 28

Apagar o arquivo inteiro levaria os 23 junto, e nao se sabe o que cada um faz -- se um
deles for o que faz a barra de skill aparecer, quebra sem aviso. Entao o filtro tira so o
que e comprovadamente redundante e guarda o resto, que vira a lista de trabalho.
"""
import os
import struct
import sys
import shutil
import collections

STREAM = r"D:\RoshEmulator\captures\enterworld_nivel1.bin"

# opcodes que o servidor ja monta por conta propria, do banco ou do estado do mundo
GERAMOS = {
    (4, 2): "aparicao -- entity_sim gera as nossas, do banco",
    (4, 1): "movimento -- os mobs do mundo se movem sozinhos",
    (4, 3): "sumico -- mandado quando um mob morre ou um jogador sai",
    (8, 0): "barra -- build_gage, do personagem do banco",
    (8, 1): "barra do heroi -- build_hero_gage",
    (8, 2): "ficha -- build_stats, com as 11 formulas derivadas",
    (8, 6): "HP de entidade -- build_hp",
    (2, 5): "charinfo -- montado por classe",
    (6, 5): "inventario -- build_inventario, do banco",
    (5, 2): "dano -- build_dano_skill",
}


def frames(dados):
    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
        yield dados[pos:pos + 4 + t], dados[pos + 4:pos + 4 + t]
        pos += 4 + t


def main() -> int:
    aplicar = "--aplicar" in sys.argv
    if not os.path.exists(STREAM):
        print("nao achei %s" % STREAM)
        return 1
    d = open(STREAM, "rb").read()

    manter, tirar = [], collections.Counter()
    mantidos_op = collections.Counter()
    for bruto, corpo in frames(d):
        op = (corpo[0], corpo[1])
        if op in GERAMOS:
            tirar[op] += 1
        else:
            manter.append(bruto)
            mantidos_op[op] += 1

    novo = b"".join(manter)
    print("stream atual : %6d bytes, %d frames" % (len(d), sum(tirar.values()) + len(manter)))
    print("stream novo  : %6d bytes, %d frames   (-%.0f%%)"
          % (len(novo), len(manter), 100.0 * (len(d) - len(novo)) / len(d)))
    print()
    print("SAI (ja geramos):")
    for op, n in tirar.most_common():
        print("   %02x %02x  x%-4d %s" % (op[0], op[1], n, GERAMOS[op]))
    print()
    print("FICA (nunca implementado -- e a lista de trabalho):")
    for op, n in sorted(mantidos_op.items()):
        print("   %02x %02x  x%d" % (op[0], op[1], n))

    if not aplicar:
        print()
        print("nada gravado. rode com --aplicar")
        return 0

    bkp = STREAM + ".completo_backup"
    if not os.path.exists(bkp):
        shutil.copy2(STREAM, bkp)
        print()
        print("original guardado em %s" % os.path.basename(bkp))
    open(STREAM, "wb").write(novo)
    print("gravado: %d bytes" % len(novo))
    return 0


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