# -*- coding: utf-8 -*-
r"""Liga a janela da segunda senha no cliente.

JANELA_DO_CADASTRO_NAO_PRESTA. A 30037 ("Criar uma segunda senha", dois campos) abre
e o teclado dela responde, mas os campos nao desenham: o OnOpen nao apanha os subctrl
41 e 42, deixando +0x1d4 e +0x1d8 a zero, e FUN_005a2b13 -- que escreve os asteriscos
-- sai sem fazer nada. Reconstruir esse pedaco (monta_cave_campos, que fica no modulo
mas desligado) fez o jogo fechar noutro sitio: 0xc0000005 em 0x004600f4, ja dentro do
desenho do EditBox, com [ESI+0xa8] a zero. Ou seja, o EditBox precisa de mais
inicializacao que tambem foi arrancada, e cada peca reposta descobre a seguinte.

Por isso a janela que abre e SEMPRE a 30039, que funciona de ponta a ponta, e o
cadastro faz-se com ela: o servidor toma o primeiro PIN de uma conta sem PIN como
cadastro. Perde-se a confirmacao em dois campos.

    python tools/patch_segunda_senha.py             mostra o estado
    python tools/patch_segunda_senha.py --aplicar   patcha
    python tools/patch_segunda_senha.py --auto      idem, e a janela abre sozinha
    python tools/patch_segunda_senha.py --reverter  volta ao original

O QUE ESTAVA ERRADO NO DIAGNOSTICO ANTERIOR. Eu tinha concluido que faltavam os
DADOS da janela -- que nenhum script definia a GUI 57676. Estava errado duas vezes:

  1. 57676 (0xe14c) nao e um id de janela. E o identificador que o cliente compara
     em FUN_0043b905 para saber se um clique caiu dentro da janela 30037. Os tres
     sao 0xe14c/0xe14d/0xe14e, para 30037/30038/30039.

  2. As janelas EXISTEM, nos dois clientes, em Contents_Enter.Sct:
        30037  UserCtrl.332  43 subctrl   registar
        30038  UserCtrl.333  44 subctrl   alterar
        30039  UserCtrl.334  40 subctrl   login    <- a que interessa
     A 30039 bate ao controlo com o que CGui2ndPassLogin::OnOpen (FUN_005a3f09) le:
        0..0x22  (35)  BtnConfirm BtnCancel BtnBlank BtnBackspace
                       Arabia0-9 china0-9 rome0-9 FindBtn
        0x23..0x26 (4) os quatro txtTitle
        0x27       (1) pass1, o campo da senha
     E os textos estao todos traduzidos no string.lod_pt: 31984 "Confirmar segunda
     senha", 31990 "Por favor, digite sua senha clicando nos numeros a esquerda",
     31992 "Obter senha", 31821 "2a senha". Nao falta dado nenhum.

COMO FOI DESLIGADA. Chamando Hide onde devia chamar Show. As duas sao vizinhas:

    0x00455617   MOV byte [ECX+0x5f],1   <- Show
    0x00455620   MOV byte [ECX+0x5f],0   <- Hide

  A) 0x005abf06, no OnOpen da tela de personagem (FUN_005abd4c): os botoes 0..3
     recebem texto, o 4 -- Btn_2ndPass, que esta no script -- recebe Hide e mais nada.
  B) 0x00446a67, em FUN_004469c5, o handler do estado 4 (a tela de personagem, pela
     jump table em 0x0044092e): apanha as tres janelas por id e esconde as tres.

E TAMBEM ARRANCARAM O CLIQUE. FUN_005ad49c trata os subctrl 0,1,2,3 e nao tem caso
nenhum para o 4. Trocar Hide por Show poe o botao no ecra mas ele nao faz nada --
por isso os dois code caves abaixo, no fim da .text (3046 bytes de zeros a partir
de 0x007f541a).

PORQUE O --auto NAO E O PADRAO. O patch B faz a janela aparecer sozinha, que e o
que o Arena faz. So que os botoes dela ainda nao tem handler: se aparecer por cima
e nao houver como a fechar, fica no caminho. Primeiro poe-se o botao a funcionar,
que e reversivel com um clique; o automatico vem depois de se saber que a janela
desenha e responde.
"""
import argparse
import os
import shutil
import struct

EXE = r"C:\Games\Axion\Axn.exe"
BAK = EXE + ".bak_2asenha"

SHOW = 0x00455617          # MOV byte [ECX+0x5f],1
HIDE = 0x00455620          # MOV byte [ECX+0x5f],0
SUBCTRL = 0x006F4AE1       # __thiscall (indice) -> &subctrl[i]
CARREGA_TXT = 0x0045139C   # carrega a cadeia por id
FORMATA_TXT = 0x0044FC66
POE_TXT = 0x0045D57C       # SetText
ACHA_GUI = 0x004521C1      # __thiscall (id) -> janela, so procura
ABRE_GUI = 0x00453123      # __thiscall (id, camada) -> cria pelo descritor, OnOpen, Show
GESTOR_GUI = 0x008DB7EC    # o gestor esta em [GESTOR_GUI] + 0x8a78
VISIVEL = 0x00455612       # le +0x5f, o IsVisible
CAMPO_SENHA = 0x0045F228   # descritor -> controlo de campo de senha
POE_RECT = 0x00456B95      # __thiscall (rect, 1) -- poe o rectangulo
ORIGINAL_3234 = 0x005A3234 # o que o OnOpen da 30037 chamava em 0x005a39b0
POE_TOPO = 0x004530AE      # __thiscall (&janela) -- reordena para a frente
BLOQUEADO = 0x0042BAEF     # __thiscall () -> 1 quando a tela esta trancada
VOLTAR = 0x005AD3E1        # o que o botao Voltar chama
JANELA_CADASTRO = 0x7555   # 30037, dois campos: cadastrar
JANELA_ALTERAR = 0x7556    # 30038, tres campos: alterar
JANELA_LOGIN = 0x7557      # 30039, um campo: conferir
TXT_BOTAO = 0x7C4D         # 31821 "2a senha"

CAVE = 0x007F5420          # zeros no fim da .text
# Um byte NOSSO, na .AMoShar -- a unica seccao gravavel com espaco de sobra: declara
# 4 bytes usados num raw de 4096, tudo a zero. E o canal por onde o servidor diz ao
# client se a conta ja tem PIN. Ver monta_cave_sinal.
SINAL = 0x00941010
# 1 enquanto a janela da senha estiver visivel. Escrito pelo cave do Update a cada
# frame, lido pela tranca dos cliques.
ABERTA = 0x00941011
# O ponteiro da janela, guardado pelo mesmo cave. FUN_004521c1 nao serve para o ir
# buscar -- ver monta_cave_pick.
PONTEIRO = 0x00941014
# Contadores de diagnostico: quantas vezes cada tranca correu. Servem para saber se
# a funcao e sequer chamada no caminho do clique -- ver tools/ler_memoria.py.
CONTA_PICK = 0x00941018
CONTA_TRANCA = 0x00941019
CONTA_BLOQ = 0x0094101A    # vezes que a tranca decidiu BLOQUEAR
CONTA_CLIQUE = 0x0094101B  # vezes que o cave dos botoes 3/4 correu
# 0 = ainda nao mostrei a janela nesta visita a tela de personagem. Zerado no
# OnOpen da tela, posto a 1 quando o Update a mostra.
MOSTRADA = 0x0094101C
# Diagnostico da janela de cadastro: os dois controlos que o cave dos campos cria,
# guardados SEM os ligar a janela. Assim ela abre sem rebentar e da para comparar
# na memoria com um EditBox que funciona (o da 30039, em +0x1cc).
CTRL1 = 0x00941020
CTRL2 = 0x00941024
FIM_HANDLER = 0x005AD740   # onde o handler de cliques converge

SITIO_TEXTO = (0x005ABEFB, 16)     # PUSH 4 / MOV ECX,ESI / CALL subctrl / MOV ECX,[EAX] / CALL Hide
SITIO_CLIQUE = (0x005AD728, 24)    # o teste do subctrl 3 inteiro
SITIO_AUTO = (0x005ADA3A, 8)       # prologo do Update da tela de personagem
SITIO_B13 = (0x005A2B13, 6)        # prologo de FUN_005a2b13, a que rebenta na 30037
SITIO_CAMPOS = (0x005A39B0, 5)     # o CALL 0x005a3234 no fim do OnOpen da 30037
SITIO_TRANCA = (0x005AD4B6, 5)     # o CALL 0x0042baef no inicio de FUN_005ad49c
SITIO_SINAL = (0x00648379, 7)      # MOV [ESI+0xc],ECX + CMP [EDI+8],0 no parser do 02 0b
SITIO_SELECAO = (0x005AB9C9, 5)    # prologo de FUN_005ab9c9, quem selecciona
SITIO_PICK = (0x0043D79D, 48)      # a procura da janela em FUN_0043b905
DEPOIS_DO_PICK = 0x0043D7E3        # para onde ela saltava quando nao bloqueava


def seccoes(d):
    """[(va_inicial, tam_virtual, offset, tam_bruto)] das seccoes do PE."""
    pe = struct.unpack_from("<I", d, 0x3C)[0]
    n = struct.unpack_from("<H", d, pe + 6)[0]
    tam_opt = struct.unpack_from("<H", d, pe + 20)[0]
    base = struct.unpack_from("<I", d, pe + 24 + 28)[0]
    tab = pe + 24 + tam_opt
    out = []
    for i in range(n):
        vs, va, rs, ro = struct.unpack_from("<4I", d, tab + i * 40 + 8)
        out.append((base + va, vs, ro, rs))
    return out


def va_para_off(d, va):
    for ini, vs, ro, rs in seccoes(d):
        if ini <= va < ini + max(vs, rs):
            return ro + (va - ini)
    raise ValueError("VA 0x%08x fora das seccoes" % va)


def chamada(destino, va):
    return b"\xe8" + struct.pack("<i", destino - (va + 5))


def salto(destino, va):
    return b"\xe9" + struct.pack("<i", destino - (va + 5))


def monta_cave_texto(va, auto=False):
    """Mostra o Btn_2ndPass e poe-lhe texto, copiando o que os botoes 0..3 fazem.

    Com auto, abre tambem a janela aqui. Este e o OnOpen da tela de personagem
    (FUN_005abd4c) -- corre de certeza, ao contrario de FUN_004469c5, que e um
    caso da jump table de 0x0044092e e cujo indice nao e o 4 so por estar no
    quinto bloco: a tabela em 0x440aa9 nao segue a ordem do codigo.
    """
    b = bytearray()
    b += b"\x6a\x04"                                    # PUSH 4
    b += b"\x8b\xce"                                    # MOV ECX,ESI
    b += chamada(SUBCTRL, va + len(b))                  # CALL subctrl -> EAX
    b += b"\x8b\xe8"                                    # MOV EBP,EAX
    b += b"\x8b\x4d\x00"                                # MOV ECX,[EBP]
    b += chamada(SHOW, va + len(b))                     # CALL Show
    b += b"\x6a\x00"                                    # PUSH 0
    b += b"\x68" + struct.pack("<I", TXT_BOTAO)         # PUSH 31821
    b += chamada(CARREGA_TXT, va + len(b))
    b += b"\x8b\xc8"                                    # MOV ECX,EAX
    b += chamada(FORMATA_TXT, va + len(b))
    b += b"\x8b\x4d\x00"                                # MOV ECX,[EBP]
    b += b"\x50"                                        # PUSH EAX
    b += chamada(POE_TXT, va + len(b))                  # CALL SetText
    # Cada entrada na tela de personagem volta a pedir a senha: e aqui, no OnOpen dela,
    # que se zera a marca que o cave do Update le. Sem isto, quem cancelasse e voltasse
    # ao canal entrava sem senha na segunda visita.
    b += b"\xc6\x05" + struct.pack("<I", MOSTRADA) + b"\x00"   # MOV byte [MOSTRADA],0
    b += b"\xc3"                                        # RET
    return bytes(b)


def monta_cave_campos(va):
    """Apanha os subctrl 41 e 42 (pass1 e pass2) e guarda-os em +0x1d4 e +0x1d8.

    E o pedaco que falta no OnOpen da 30037: ele converte os subctrl 0..40 e para ai,
    deixando a zero os dois ponteiros dos campos de senha. Sem eles, FUN_005a2b13 --
    que e quem monta a cadeia de asteriscos e a escreve no campo -- nao tem onde
    escrever, e por isso os cliques nos numeros nao aparecem na tela.

    O padrao e o mesmo que a 30039 usa para o campo dela (FUN_005a3f09, subctrl 0x27):
        descritor = subctrl[i]
        se descritor e descritor[0] nao forem nulos:
            controlo = FUN_0045f228(descritor[0])
            guarda o controlo
            FUN_00456b95(controlo, &descritor[1], 1)   <- o rectangulo

    Substitui o CALL 0x005a3234 em 0x005a39b0, onde EBX ja e o this, e acaba a saltar
    para la -- o RET de FUN_005a3234 volta para 0x005a39b5 como antes.
    """
    def apanha(indice, campo, saida_se_falhar, global_=False):
        return [
            b"\x6a" + bytes([indice]),                       # PUSH indice
            b"\x8b\xce",                                     # MOV ECX,ESI (o vector)
            ("call", SUBCTRL),                               # EAX = &subctrl[i]
            b"\x8b\x38",                                     # MOV EDI,[EAX]   (o descritor)
            b"\x85\xff",                                     # TEST EDI,EDI
            ("jz", saida_se_falhar),
            b"\x8b\x07",                                     # MOV EAX,[EDI]
            b"\x85\xc0",                                     # TEST EAX,EAX
            ("jz", saida_se_falhar),
            # As duas chamadas seguem o que o client faz em 0x005a4073 e 0x005a4089:
            # o descritor vai na PILHA (e o chamador limpa), e o PoeRect leva TRES
            # argumentos com ECX no this da JANELA -- nao no controlo.
            b"\x50",                                         # PUSH EAX
            ("call", CAMPO_SENHA),                           # EAX = o controlo
            b"\x59",                                         # POP ECX  (limpa o arg)
            b"\x89\x83" + struct.pack("<I", campo),          # MOV [EBX+campo],EAX
            b"\x85\xc0",                                     # TEST EAX,EAX
            ("jz", saida_se_falhar),
            b"\x6a\x01",                                     # PUSH 1
            b"\x8d\x57\x04",                                 # LEA EDX,[EDI+4]  (o rect)
            b"\x52",                                         # PUSH EDX
            b"\x50",                                         # PUSH EAX         (o controlo)
            b"\x8b\xcb",                                     # MOV ECX,EBX      (a janela)
            ("call", POE_RECT),
        ]

    itens = [
        b"\x53", b"\x56", b"\x57",                           # PUSH EBX/ESI/EDI
        b"\x8b\xd9",                                         # MOV EBX,ECX  (this)
        b"\x8b\x43\x04",                                     # MOV EAX,[EBX+4] (descritor)
        b"\x85\xc0",                                         # TEST EAX,EAX
        ("jz", "fim"),
        b"\x8d\x70\x50",                                     # LEA ESI,[EAX+0x50] (vector)
    ]
    itens += apanha(0x29, CTRL1 - 0, "campo2", global_=True)  # pass1 -> CTRL1
    itens += [("rotulo", "campo2")]
    itens += apanha(0x2A, CTRL2 - 0, "fim", global_=True)     # pass2 -> CTRL2
    itens += [
        ("rotulo", "fim"),
        b"\x8b\xcb",                                         # MOV ECX,EBX
        b"\x5f", b"\x5e", b"\x5b",                           # POP EDI/ESI/EBX
        ("jmpl", ORIGINAL_3234),                             # o que estava aqui
    ]
    return montar(va, itens)


def monta_cave_sinal(va):
    """Guarda em SINAL o byte 9 do 02 0b que vem do servidor.

    E assim que o client fica a saber se a conta ja tem PIN: o codigo que decidia isso
    foi arrancado com o resto, e nao ha no binario nada que o servidor consiga mexer.
    Entao poe-se um byte nosso, e o servidor manda-o dentro de um pacote que o client
    ja processa.

        byte 9 == 0   a conta nao tem PIN   -> abre a janela de CADASTRO (30037)
        byte 9 != 0   a conta tem PIN       -> abre a de CONFERIR (30039)

    O sitio e o parser do pacote (FUN_00648358), logo depois de ele confirmar que o
    opcode e mesmo 02 0b, com EAX ja a apontar para o corpo:

        00648373  CMP byte [EAX+1],0xb
        00648377  JNZ (sai)
        00648379  MOV [ESI+0xc],ECX     <- entra-se aqui
        0064837c  CMP dword [EDI+8],0

    O CMP fica para o fim do cave porque e ele que arma as flags do JNZ seguinte.
    """
    return montar(va, [
        b"\x89\x4e\x0c",                                    # MOV [ESI+0xc],ECX  (original)
        b"\x8a\x50\x09",                                    # MOV DL,[EAX+9]
        b"\x88\x15" + struct.pack("<I", SINAL),             # MOV [SINAL],DL
        # E TAMBEM AQUI que se pede a senha outra vez.
        #
        # Zerar a marca no OnOpen da tela nao chegava: o OnOpen so corre quando a tela
        # e CRIADA, e ao voltar do canal ela ja existe -- entao a marca ficava a 1 e a
        # janela nao reaparecia. Dava para cancelar, voltar e entrar sem senha.
        #
        # Este pacote, sim, chega a cada volta: o servidor manda o 02 0b colado a lista
        # de personagens sempre que a devolve. Quem manda pedir a senha passa a ser o
        # servidor, que e quem sabe se ela ja foi conferida.
        b"\xc6\x05" + struct.pack("<I", MOSTRADA) + b"\x00",  # MOV byte [MOSTRADA],0
        b"\x83\x7f\x08\x00",                                # CMP [EDI+8],0      (original)
        ("jmpl", SITIO_SINAL[0] + SITIO_SINAL[1]),
    ])


def monta_cave_tranca(va):
    """Faz a tela de personagem ignorar cliques enquanto a janela da senha estiver aberta.

    Assim como no Arena: os botoes continuam todos visiveis -- o Play tambem -- mas
    nada responde a nao ser a janela.

    O handler da tela (FUN_005ad49c) ja comeca por perguntar se esta bloqueado:
        005ad4b6  CALL 0x0042baef
        005ad4bb  CMP EAX,1
        005ad4be  JNZ 0x005ad4c7      <- nao bloqueado, processa
        005ad4c0  XOR EAX,EAX         <- bloqueado, sai sem tocar em nada
    Entra-se nessa pergunta em vez de esconder controlos: e o mesmo caminho que o
    proprio jogo usa, e apanha tudo de uma vez -- o Play, o Apagar e a seleccao do
    personagem, que passa pelo picking 3D do viewer e nao pelo sistema de GUI.

    O binario tem ainda um bloqueio proprio em FUN_0043b905, que compara
    descritor+8 com 0xe14e; esse nao esta a disparar, e sem runtime nao da para
    saber se o campo e outro. Este caminho nao depende disso.

    A tranca A SERIO continua a ser a do servidor, que recusa o 02 04. Isto so muda
    o que o jogo deixa fazer com este exe.
    """
    return montar(va, [
        b"\xfe\x05" + struct.pack("<I", CONTA_TRANCA),        # INC byte [CONTA_TRANCA]
        b"\x51",                                            # PUSH ECX  (o gestor)
        ("call", BLOQUEADO),                                # o teste original
        b"\x59",                                            # POP ECX
        b"\x83\xf8\x01",                                    # CMP EAX,1
        ("jz", "fim"),                                      # ja estava trancado
        # NAO se procura a janela aqui. FUN_004521c1 tem um ramo esquisito para a lista
        # da camada 1 -- em vez de devolver o ponteiro, faz aritmetica sobre um retorno
        # de 64 bits -- e era por isso que a tranca nao pegava com a janela bem aberta:
        # o clique passava para os personagens por baixo dela. Quem sabe se ela esta
        # aberta e o cave do Update, que tem o ponteiro na mao; ele deixa a resposta
        # neste byte a cada frame.
        b"\x80\x3d" + struct.pack("<I", ABERTA) + b"\x00",  # CMP byte [ABERTA],0
        ("jz", "fim"),
        b"\xfe\x05" + struct.pack("<I", CONTA_BLOQ),      # INC byte [CONTA_BLOQ]
        b"\xb8\x01\x00\x00\x00",                            # MOV EAX,1  -> trancado
        ("rotulo", "fim"),
        b"\xc3",
    ])


def monta_cave_pick(va):
    """Poe o bloqueio nativo de cliques a funcionar, no caminho do picking 3D.

    FUN_0043b905 e quem decide se um clique deve ser processado, e ja trazia a regra
    certa: com a janela da segunda senha visivel, um clique FORA dela nao vale. So que
    a regra nunca disparava, por duas razoes:

      - procurava a janela com FUN_004521c1, que para a lista da camada 1 devolve
        aritmetica de um retorno de 64 bits em vez do ponteiro;
      - e depois confirmava o tipo com `descritor+8 == 0xe14e`, que nao bate.

    Trocam-se os 48 bytes dessa procura pelo ponteiro que o cave do Update ja guardou.
    O que vem a seguir (0x0043d7cd) fica como estava: PUSH y / MOV ECX,ESI / PUSH x /
    CALL 0x0049cfbb, o teste de o ponto cair dentro da janela.

    E este o caminho que apanha o clique no MODELO do personagem: esse passa pelo
    picking 3D do viewer e nunca chega ao handler da tela, que e onde esta a outra
    tranca.
    """
    return montar(va, [
        b"\xfe\x05" + struct.pack("<I", CONTA_PICK),        # INC byte [CONTA_PICK]
        b"\x8b\x35" + struct.pack("<I", PONTEIRO),          # MOV ESI,[PONTEIRO]
        b"\x85\xf6",                                        # TEST ESI,ESI
        ("jz", "fora"),
        b"\x80\x3d" + struct.pack("<I", ABERTA) + b"\x00",  # CMP byte [ABERTA],0
        ("jz", "fora"),
        ("jmpl", SITIO_PICK[0] + SITIO_PICK[1]),            # segue o teste do ponto
        ("rotulo", "fora"),
        ("jmpl", DEPOIS_DO_PICK),                           # nao bloqueia
    ])


def monta_cave_selecao(va):
    """Nao deixa seleccionar personagem enquanto a janela da senha estiver aberta.

    A tranca vai na PROPRIA FUN_005ab9c9, que e quem selecciona, e nao nos caminhos que
    lhe chegam. Sao cinco -- 0x00446cc7, 0x005ac268, 0x005ac8c0, 0x005ac941 e
    0x005ad60c -- e eu tinha trancado so o ultimo, que e o handler da tela. Os
    contadores confirmaram o engano: FUN_005ad49c correu 11 vezes e FUN_0043b905 tres,
    ou seja as duas trancas disparavam, e o clique no modelo passava por outro lado.

    Prologo substituido: PUSH EBX / PUSH EBP / PUSH ESI / MOV ESI,ECX. A funcao e
    __thiscall com um argumento, e acaba em RET 4 (visto em 0x005abb16).
    """
    return montar(va, [
        b"\x80\x3d" + struct.pack("<I", ABERTA) + b"\x00",   # CMP byte [ABERTA],0
        ("jz", "segue"),
        b"\xc2\x04\x00",                                     # RET 4  -- nao selecciona
        ("rotulo", "segue"),
        b"\x53", b"\x55", b"\x56",                           # PUSH EBX/EBP/ESI
        b"\x8b\xf1",                                         # MOV ESI,ECX
        ("jmpl", SITIO_SELECAO[0] + SITIO_SELECAO[1]),
    ])


def monta_cave_b13(va):
    """Faz FUN_005a2b13 sair em paz quando os campos de senha estao a zero.

    E esta funcao que fecha o jogo ao abrir a janela de cadastro (30037). Medido pelo
    log de eventos do Windows: 0xc0000005 no deslocamento 0x00055603, que e
    `MOVZX EAX,byte [ECX+0x5e]`. O ECX vem daqui:

        005a2b26  MOV ECX,[ESI+0x1d4]
        005a2b2d  CALL 0x00455603      <- com [0x1d4] a zero, le o endereco 0x5e

    +0x1d4 e +0x1d8 sao os dois campos de senha da janela (pass1 e pass2, subctrl 41 e
    42). O construtor poe-os a ZERO (FUN_005a242a, em 0x005a2447) e o OnOpen da 30037
    nunca os preenche -- ele converte os subctrl 0..40 e para ai. Na 30039 o campo
    equivalente e apanhado e guardado em +0x1cc, e la o codigo ainda TESTA nulo antes
    de usar; aqui nao testa. E por isso que uma abre e a outra fechava o jogo.

    O que falta e o codigo que apanhava os subctrl 41 e 42, arrancado com o resto.
    Ate isso estar reconstruido, sair no inicio deixa a janela abrir: os asteriscos
    do campo nao se desenham, mas nada rebenta.

    Substitui os 6 primeiros bytes: PUSH EBP / MOV EBP,ESP / SUB ESP,0xc.
    """
    return montar(va, [
        b"\x83\xb9\xd4\x01\x00\x00\x00",                    # CMP dword [ECX+0x1d4],0
        ("jnz", "segue"),
        b"\x83\xb9\xd8\x01\x00\x00\x00",                    # CMP dword [ECX+0x1d8],0
        ("jnz", "segue"),
        b"\xc3",                                            # RET
        ("rotulo", "segue"),
        b"\x55",                                            # PUSH EBP     (original)
        b"\x8b\xec",                                        # MOV EBP,ESP  (original)
        b"\x83\xec\x0c",                                    # SUB ESP,0xc  (original)
        ("jmpl", SITIO_B13[0] + SITIO_B13[1]),
    ])


def monta_cave_auto(va):
    """Abre a 30039 no Update da tela de personagem (FUN_005ada3a).

    NAO no OnOpen. O OnOpen corre dentro de FUN_0044a729, ANTES de a propria tela
    ser inscrita na lista de desenho -- abrir a 30039 dali punha-a na lista antes
    da 30000, e os modelos 3D dos personagens desenhavam-se por cima dela. No
    Update a 30000 ja la esta, por isso a 30039 entra depois e fica a frente.

    Corre a cada frame, mas AbreGui devolve a janela que ja esta na lista sem
    voltar a mostra-la, por isso fechar continua a fechar.

    Substitui os 8 primeiros bytes: PUSH ESI / MOV ESI,ECX / CALL IsVisible.
    """
    LEA_SUB = b"\x8d\x8e\x98\x01\x00\x00"               # LEA ECX,[ESI+0x198]
    return montar(va, [
        b"\x56",                                        # PUSH ESI     (original)
        b"\x8b\xf1",                                    # MOV ESI,ECX  (original)
        # Qual janela abrir depende do que o servidor disse no 02 0b -- ver
        # monta_cave_sinal. Sem PIN abre a de cadastro; com PIN, a de conferir.
        # SEMPRE A 30039, tenha a conta PIN ou nao. Ver JANELA_DO_CADASTRO_NAO_PRESTA
        # no topo do modulo: a 30037 abre mas os campos dela nao desenham, e forcar a
        # inicializacao deles fecha o jogo.
        #
        # O sinal continua a ser lido e guardado (monta_cave_sinal) -- e nele que
        # assenta a escolha, no dia em que a 30037 estiver inteira. Por agora o
        # servidor e que trata o primeiro PIN como cadastro.
        _gestor(),
        b"\x6a\x01",                                    # PUSH 1
        b"\x68" + struct.pack("<I", JANELA_LOGIN),      # PUSH 30039
        ("call", ABRE_GUI),                             # EAX = a janela
        b"\xa3" + struct.pack("<I", PONTEIRO),          # MOV [PONTEIRO],EAX
        b"\x85\xc0",                                    # TEST EAX,EAX
        ("jz", "fechada"),
        # MOSTRA UMA VEZ POR VISITA a tela de personagem.
        #
        # Sem isto havia um furo: Cancelar, voltar ao canal e entrar outra vez, e a
        # janela ja nao aparecia -- dava para jogar sem a senha. E porque AbreGui
        # devolve a janela que ja esta na lista ANTES de chegar ao Show dela, e depois
        # do Cancelar ela continua la, so que escondida.
        #
        # MOSTRADA e posta a zero no OnOpen da tela (cave do texto), por isso cada
        # entrada na tela volta a mostrar, mas o Cancelar dentro da mesma visita
        # continua a fechar.
        b"\x80\x3d" + struct.pack("<I", MOSTRADA) + b"\x00",  # CMP byte [MOSTRADA],0
        ("jnz", "ja_mostrada"),
        b"\xc6\x05" + struct.pack("<I", MOSTRADA) + b"\x01",  # MOV byte [MOSTRADA],1
        b"\x8b\xc8",                                    # MOV ECX,EAX
        ("call", SHOW),
        ("rotulo", "ja_mostrada"),
        # Deixa em ABERTA se ela esta visivel. E daqui que a tranca dos cliques sabe
        # -- aqui o ponteiro vem direito de AbreGui, sem passar por FUN_004521c1.
        b"\x8b\xc8",                                    # MOV ECX,EAX
        ("call", VISIVEL),                              # AL = visivel
        b"\xa2" + struct.pack("<I", ABERTA),            # MOV [ABERTA],AL
        b"\x84\xc0",                                    # TEST AL,AL
        ("jz", "prossegue"),
        # POE A JANELA NO TOPO, a cada frame enquanto estiver aberta.
        #
        # Sem isto ela ia para tras ao primeiro clique fora: o sistema de GUI da foco
        # a quem foi clicado e reordena. E o mesmo que FUN_00453123 faz em 0x004531cb
        # logo depois de a mostrar -- recebe o ENDERECO da variavel que guarda a
        # janela, e a nossa global PONTEIRO e exactamente isso.
        b"\x68" + struct.pack("<I", PONTEIRO),          # PUSH offset PONTEIRO
        # ECX e a LISTA DA CAMADA, nao o gestor. FUN_00453123 faz LEA ESI,[EDI+0xac]
        # em 0x00453147 antes de chamar, e o 0xac e a camada 1 -- a que usamos. Passar
        # o gestor fechava o jogo em 0x004530ba, logo na dupla deferencia de this+4.
        b"\x8b\x0d" + struct.pack("<I", GESTOR_GUI),    # MOV ECX,[gestor]
        b"\x81\xc1\x24\x8b\x00\x00",                    # ADD ECX,0x8a78+0xac
        ("call", POE_TOPO),
        ("jmp", "prossegue"),
        ("rotulo", "fechada"),
        b"\xc6\x05" + struct.pack("<I", ABERTA) + b"\x00",   # MOV byte [ABERTA],0
        ("rotulo", "prossegue"),
        b"\x8b\xce",                                    # MOV ECX,ESI  (repoe o this)
        ("call", VISIVEL),                              # o que estava no prologo
        ("jmpl", SITIO_AUTO[0] + SITIO_AUTO[1]),
    ])


def montar(va, itens):
    """Monta codigo com rotulos, em duas passagens.

    Cada item e bytes cruos, ("rotulo", nome), ("jz"/"jnz"/"jmp", nome) para um salto
    curto, ("call", destino) ou ("jmpl", destino) para um endereco absoluto.

    Existe porque calcular os saltos a mao ja me custou dois enganos seguidos, e o
    segundo passou despercebido ate eu reler os bytes: um `eb 29` que devia ser `eb 3e`
    e mandava o botao Voltar para o meio do bloco do cadastro. Aqui quem conta e o
    codigo, e um salto que nao caiba em 8 bits levanta erro em vez de gerar lixo.
    """
    CURTO = {"jz": b"\x74", "jnz": b"\x75", "jmp": b"\xeb"}

    def tamanho(it):
        if isinstance(it, bytes):
            return len(it)
        if it[0] == "rotulo":
            return 0
        return 5 if it[0] in ("call", "jmpl") else 2

    rotulos, pos = {}, 0
    for it in itens:
        if not isinstance(it, bytes) and it[0] == "rotulo":
            rotulos[it[1]] = pos
        pos += tamanho(it)

    pos, saida = 0, bytearray()
    for it in itens:
        if isinstance(it, bytes):
            saida += it
        elif it[0] == "rotulo":
            continue
        elif it[0] == "call":
            saida += chamada(it[1], va + pos)
        elif it[0] == "jmpl":
            saida += salto(it[1], va + pos)
        else:
            delta = rotulos[it[1]] - (pos + 2)
            if not -128 <= delta <= 127:
                raise ValueError("salto curto de %d bytes para '%s'" % (delta, it[1]))
            saida += CURTO[it[0]] + bytes([delta & 0xFF])
        pos += tamanho(it)
    return bytes(saida)


def _gestor():
    """ECX = o gestor de GUI, como o proprio client faz antes de cada AbreGui."""
    return (b"\x8b\x0d" + struct.pack("<I", GESTOR_GUI)      # MOV ECX,[gestor]
            + b"\x81\xc1\x78\x8a\x00\x00")                   # ADD ECX,0x8a78


def monta_cave_clique_v2(va):
    """Botao Voltar como estava, e o botao 4 abre a janela de CADASTRO (30037).

    Esconde primeiro a 30039. As duas ocupam o mesmo rectangulo (500x290, centrado),
    e a de conferir ja esta aberta -- sem a esconder, a de cadastro abre atras dela e
    parece que o botao nao fez nada. Foi o que aconteceu no teste de 02/09.
    """
    LEA = b"\x8d\x8e\x98\x01\x00\x00"                        # LEA ECX,[ESI+0x198]
    return montar(va, [
        # --- subctrl 3: Voltar, tal como estava ---
        b"\xfe\x05" + struct.pack("<I", CONTA_CLIQUE),
        b"\x6a\x03", LEA, ("call", SUBCTRL), b"\x3b\x38",    # CMP EDI,[EAX]
        ("jnz", "testa4"),
        b"\x8b\xce", ("call", VOLTAR),                       # MOV ECX,ESI
        ("jmp", "fim"),
        # --- subctrl 4: o botao da segunda senha ---
        ("rotulo", "testa4"),
        b"\x6a\x04", LEA, ("call", SUBCTRL), b"\x3b\x38",
        ("jnz", "fim"),
        # esconde a de conferir, senao a de cadastro abre POR BAIXO dela: as duas tem o
        # mesmo rectangulo (500x290, centrado) e a de conferir ja esta aberta.
        _gestor(), b"\x68" + struct.pack("<I", JANELA_LOGIN),
        ("call", ACHA_GUI), b"\x85\xc0",                     # TEST EAX,EAX
        ("jz", "abre"),
        b"\x8b\xc8", ("call", HIDE),                         # MOV ECX,EAX
        # abre a de cadastro -- so nao fecha o jogo com o patch de FUN_005a2b13
        ("rotulo", "abre"),
        _gestor(), b"\x6a\x01", b"\x68" + struct.pack("<I", JANELA_CADASTRO),
        ("call", ABRE_GUI), b"\x85\xc0",
        ("jz", "fim"),
        # AQUI ESCREVIA-SE [EAX+0x35c]=2 e [EAX+0x363]=1, E ISSO FECHAVA O JOGO.
        #
        # Esses offsets sao da classe da 30038, nao da 30037. A fabrica em 0x0043829d
        # aloca tamanhos diferentes para as tres:
        #     0x35c  caso 0x8c -> 30037 (cadastrar)   CGui2ndPassword, FUN_005a2a36
        #     0x364  caso 0x8d -> 30038 (alterar)     FUN_005a2bce
        #     0x348  caso 0x8e -> 30039 (conferir)    CGui2ndPassLogin, FUN_005a2fad
        # O objeto da 30037 acaba em 0x35b. Escrever em 0x35c e 0x363 caia FORA da
        # alocacao e estragava a heap -- o jogo fechava no clique.
        #
        # FUN_005a4876, que testa [0x35c] != 2, e o Confirmar da 30038 (0x364 bytes),
        # nao da 30037. O campo equivalente na 30037 ainda esta por achar; ate la a
        # janela abre e escreve-se, mas o Confirmar dela nao envia nada.
        ("rotulo", "fim"),
        ("jmpl", FIM_HANDLER),
    ])


def monta_cave_clique(va):
    """Refaz o teste do subctrl 3 e acrescenta o do 4, que abre a janela 30039.

    EDI = quem enviou o clique, ESI = a janela. Sao os registos que o codigo
    original ja usa neste ponto do handler.
    """
    b = bytearray()
    LEA = b"\x8d\x8e\x98\x01\x00\x00"                   # LEA ECX,[ESI+0x198]
    # --- subctrl 3: Voltar, tal como estava ---
    b += b"\x6a\x03"
    b += LEA
    b += chamada(SUBCTRL, va + len(b))
    b += b"\x3b\x38"                                    # CMP EDI,[EAX]
    b += b"\x75\x09"                                    # JNZ testa4
    b += b"\x8b\xce"                                    # MOV ECX,ESI
    b += chamada(VOLTAR, va + len(b))
    b += b"\xeb\x29"                                    # JMP fim
    assert len(b) == 26, len(b)
    # --- subctrl 4: o botao da segunda senha ---
    b += b"\x6a\x04"
    b += LEA
    b += chamada(SUBCTRL, va + len(b))
    b += b"\x3b\x38"                                    # CMP EDI,[EAX]
    b += b"\x75\x2d"                                    # JNZ fim
    b += b"\x8b\x0d" + struct.pack("<I", GESTOR_GUI)    # MOV ECX,[gestor]
    b += b"\x81\xc1\x78\x8a\x00\x00"                    # ADD ECX,0x8a78
    b += b"\x6a\x01"                                    # PUSH 1  (a camada de sempre)
    b += b"\x68" + struct.pack("<I", JANELA_CADASTRO)   # PUSH 30037
    b += chamada(ABRE_GUI, va + len(b))                 # CALL AbreGui -> EAX
    b += b"\x85\xc0"                                    # TEST EAX,EAX
    b += b"\x74\x11"                                    # JZ fim
    # O MODO, sem o qual o botao Confirmar da janela nao envia nada.
    #
    # FUN_005a4876 (o Confirmar) comeca por `if (this->[0x35c] != 2) return`, e o unico
    # sitio do binario que toca nesse campo e o construtor, que o poe a zero -- foi
    # arrancado como o resto. Com 2, ele envia; com [0x363] ligado, envia tambem o
    # REGISTER, que e o 02 08 (a cadeia de debug do proprio client diz
    # MSG_LOGIN_CHAR_SECOND_PASSWD_REGISTER sent).
    b += b"\xc7\x80\x5c\x03\x00\x00\x02\x00\x00\x00"    # MOV dword [EAX+0x35c],2
    b += b"\xc6\x80\x63\x03\x00\x00\x01"                # MOV byte  [EAX+0x363],1
    assert len(b) == 88, len(b)
    b += salto(FIM_HANDLER, va + len(b))                # JMP fim do handler
    return bytes(b)


def monta_auto(va):
    """Abre a 30039 ao entrar na tela de personagem, em vez de a esconder.

    Substitui o ultimo dos tres blocos de FUN_004469c5, que fazia
    AchaGui(0x7557) e Hide. ESI e o gestor (MOV ESI,[ESP+8] no prologo) e ha um
    PUSH ESI a repor, por isso acaba em POP ESI / RET como o original.

    Nao reabre sozinha depois de fechada: AbreGui devolve a janela que ja esta na
    lista antes de chegar ao Show.
    """
    b = bytearray()
    b += b"\x6a\x01"                                    # PUSH 1
    b += b"\x68" + struct.pack("<I", JANELA_LOGIN)      # PUSH 30039
    b += b"\x8b\xce"                                    # MOV ECX,ESI
    b += chamada(ABRE_GUI, va + len(b))                 # CALL AbreGui
    b += b"\x5e"                                        # POP ESI
    b += b"\xc3"                                        # RET
    return bytes(b)


def main() -> int:
    ap = argparse.ArgumentParser(description="Liga a janela da segunda senha.")
    ap.add_argument("--aplicar", action="store_true")
    ap.add_argument("--auto", action="store_true",
                    help="tambem faz a janela abrir sozinha na tela de personagem")
    ap.add_argument("--reverter", action="store_true")
    args = ap.parse_args()

    if args.reverter:
        if not os.path.exists(BAK):
            print("nao ha backup em %s" % BAK)
            return 1
        shutil.copy2(BAK, EXE)
        print("reposto de %s" % BAK)
        return 0

    # parte-se sempre do original, para o patch poder ser repetido
    if os.path.exists(BAK):
        d = bytearray(open(BAK, "rb").read())
        print("base: %s (original)" % BAK)
    else:
        d = bytearray(open(EXE, "rb").read())
        print("base: %s (ainda sem backup)" % EXE)

    cave_t = CAVE
    corpo_t = monta_cave_texto(cave_t)
    cave_c = cave_t + len(corpo_t)
    cave_c += (4 - cave_c % 4) % 4
    corpo_c = monta_cave_clique_v2(cave_c)
    print("")
    print("cave do texto  @ 0x%08x  %d bytes" % (cave_t, len(corpo_t)))
    print("cave do clique @ 0x%08x  %d bytes" % (cave_c, len(corpo_c)))

    o_t = va_para_off(d, cave_t)
    total = (cave_c + len(corpo_c)) - cave_t
    if any(d[o_t:o_t + total]):
        print("")
        print("abortado: o cave em 0x%08x nao esta a zeros." % cave_t)
        return 1

    escritas = [(o_t, corpo_t), (va_para_off(d, cave_c), corpo_c)]

    va, tam = SITIO_TEXTO
    escritas.append((va_para_off(d, va), chamada(cave_t, va) + b"\x90" * (tam - 5)))
    print("")
    print("  0x%08x  %2d bytes -> CALL cave do texto   mostra o botao e escreve o rotulo"
          % (va, tam))

    va, tam = SITIO_CLIQUE
    escritas.append((va_para_off(d, va), salto(cave_c, va) + b"\x90" * (tam - 5)))
    print("  0x%08x  %2d bytes -> JMP  cave do clique   Voltar, e o 4 abre a 30039"
          % (va, tam))

    # A blindagem de FUN_005a2b13 vai sempre: sem ela o botao 2a senha fecha o jogo,
    # porque abre a 30037 e essa funcao dereferencia os campos a zero.
    cave_b = cave_c + len(corpo_c)
    cave_b += (4 - cave_b % 4) % 4
    corpo_b = monta_cave_b13(cave_b)
    escritas.append((va_para_off(d, cave_b), corpo_b))
    va, tam = SITIO_B13
    escritas.append((va_para_off(d, va), salto(cave_b, va) + b"\x90" * (tam - 5)))
    print("  cave da blindagem @ 0x%08x  %d bytes" % (cave_b, len(corpo_b)))
    print("  0x%08x   %d bytes -> JMP  cave da blindagem  FUN_005a2b13 nao rebenta"
          % (va, tam))

    # O cave dos campos fica DESLIGADO -- ver JANELA_DO_CADASTRO_NAO_PRESTA.
    # O cave dos campos, agora SO PARA DIAGNOSTICO: cria os dois controlos e guarda-os
    # em CTRL1/CTRL2 sem os ligar a janela, para se poder comparar na memoria com um
    # EditBox que funciona. Nao rebenta porque +0x1d4 e +0x1d8 continuam a zero e a
    # blindagem de FUN_005a2b13 continua a segurar.
    cave_f = cave_b + len(corpo_b)
    cave_f += (4 - cave_f % 4) % 4
    corpo_f = monta_cave_campos(cave_f)
    escritas.append((va_para_off(d, cave_f), corpo_f))
    va, tam = SITIO_CAMPOS
    escritas.append((va_para_off(d, va), chamada(cave_f, va)))
    print("  cave dos campos   @ 0x%08x  %d bytes  (diagnostico)" % (cave_f, len(corpo_f)))

    # O sinal do servidor: qual janela abrir.
    cave_s = cave_f + len(corpo_f)
    cave_s += (4 - cave_s % 4) % 4
    corpo_s = monta_cave_sinal(cave_s)
    escritas.append((va_para_off(d, cave_s), corpo_s))
    va, tam = SITIO_SINAL
    escritas.append((va_para_off(d, va), salto(cave_s, va) + b"\x90" * (tam - 5)))
    print("  cave do sinal     @ 0x%08x  %d bytes" % (cave_s, len(corpo_s)))
    print("  0x%08x   %d bytes -> JMP  cave do sinal      byte 9 do 02 0b -> 0x%08x"
          % (va, tam, SINAL))

    # O bloqueio no caminho do picking 3D.
    cave_p = cave_s + len(corpo_s)
    cave_p += (4 - cave_p % 4) % 4
    corpo_p = monta_cave_pick(cave_p)
    escritas.append((va_para_off(d, cave_p), corpo_p))
    va, tam = SITIO_PICK
    escritas.append((va_para_off(d, va), salto(cave_p, va) + b"\x90" * (tam - 5)))
    print("  cave do picking   @ 0x%08x  %d bytes" % (cave_p, len(corpo_p)))
    print("  0x%08x  %2d bytes -> JMP  cave do picking    clique no personagem tambem trancado"
          % (va, tam))

    # A tranca da SELECCAO, na propria funcao que selecciona.
    cave_x = cave_p + len(corpo_p)
    cave_x += (4 - cave_x % 4) % 4
    corpo_x = monta_cave_selecao(cave_x)
    escritas.append((va_para_off(d, cave_x), corpo_x))
    va, tam = SITIO_SELECAO
    escritas.append((va_para_off(d, va), salto(cave_x, va)))
    print('  cave da seleccao  @ 0x%08x  %d bytes' % (cave_x, len(corpo_x)))
    print('  0x%08x   %d bytes -> JMP  cave da seleccao   nao selecciona com a janela aberta'
          % (va, tam))

    # A tranca dos cliques: com a janela aberta, a tela de personagem nao responde.
    cave_t = cave_x + len(corpo_x)
    cave_t += (4 - cave_t % 4) % 4
    corpo_t = monta_cave_tranca(cave_t)
    escritas.append((va_para_off(d, cave_t), corpo_t))
    va, tam = SITIO_TRANCA
    escritas.append((va_para_off(d, va), chamada(cave_t, va)))
    print("  cave da tranca    @ 0x%08x  %d bytes" % (cave_t, len(corpo_t)))
    print("  0x%08x   %d bytes -> CALL cave da tranca     cliques ignorados com a janela aberta"
          % (va, tam))

    if args.auto:
        cave_a = cave_t + len(corpo_t)
        cave_a += (4 - cave_a % 4) % 4
        corpo_a = monta_cave_auto(cave_a)
        escritas.append((va_para_off(d, cave_a), corpo_a))
        va, tam = SITIO_AUTO
        escritas.append((va_para_off(d, va), salto(cave_a, va) + b"\x90" * (tam - 5)))
        print("  cave do auto   @ 0x%08x  %d bytes" % (cave_a, len(corpo_a)))
        print("  0x%08x   %d bytes -> JMP  cave do auto     a janela abre sozinha"
              % (va, tam))

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

    if not os.path.exists(BAK):
        shutil.copy2(EXE, BAK)
        print("")
        print("backup criado: %s" % BAK)
    for off, dados in escritas:
        d[off:off + len(dados)] = dados
    open(EXE, "wb").write(bytes(d))
    print("")
    print("gravado: %d bytes de codigo novo, %d sitios redireccionados."
          % (len(corpo_t) + len(corpo_c), len(escritas) - 2))
    return 0


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