# -*- coding: utf-8 -*-
r"""Le bytes da memoria do cliente enquanto ele corre.

    python tools/ler_memoria.py              mostra os bytes da segunda senha
    python tools/ler_memoria.py 0x941010 16  le 16 bytes desse endereco

Existe porque nesta sessao ja gastei varias rodadas a deduzir o que o cliente faz em
runtime -- se o cave correu, se o byte foi escrito, se o ponteiro e valido -- e cada
palpite errado custou um teste ao jogador. Isto responde de uma vez.

Sem escrita: so ReadProcessMemory. Precisa do jogo aberto.
"""
import ctypes
import ctypes.wintypes as w
import sys

PROCESS_VM_READ = 0x0010
PROCESS_QUERY_INFORMATION = 0x0400

k32 = ctypes.WinDLL("kernel32", use_last_error=True)
psapi = ctypes.WinDLL("psapi", use_last_error=True)

# Os enderecos que este patch usa -- ver tools/patch_segunda_senha.py
CONHECIDOS = [
    (0x00941010, 1, "SINAL   -- 0 conta sem PIN, 1 com PIN (byte 9 do 02 0b)"),
    (0x00941011, 1, "ABERTA  -- 1 enquanto a janela da senha estiver visivel"),
    (0x00941014, 4, "PONTEIRO-- a janela, guardada pelo cave do Update"),
    (0x00941018, 1, "CONTA_PICK   -- vezes que FUN_0043b905 correu"),
    (0x00941019, 1, "CONTA_TRANCA -- vezes que FUN_005ad49c correu"),
    (0x0094101A, 1, "CONTA_BLOQ   -- vezes que a tranca disse BLOQUEADO"),
    (0x0094101B, 1, "CONTA_CLIQUE -- vezes que o handler chegou aos botoes"),
    (0x0094101C, 1, "MOSTRADA -- 1 se a janela ja foi mostrada nesta visita"),
    (0x00941020, 4, "CTRL1 -- o pass1 da janela de cadastro, se foi criado"),
    (0x00941024, 4, "CTRL2 -- o pass2 da janela de cadastro"),
]


def achar_processo(nome="Axn.exe"):
    arr = (w.DWORD * 4096)()
    devolvido = w.DWORD()
    psapi.EnumProcesses(ctypes.byref(arr), ctypes.sizeof(arr), ctypes.byref(devolvido))
    for i in range(devolvido.value // ctypes.sizeof(w.DWORD)):
        pid = arr[i]
        h = k32.OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, False, pid)
        if not h:
            continue
        buf = ctypes.create_unicode_buffer(260)
        n = w.DWORD(260)
        try:
            if psapi.GetModuleBaseNameW(h, None, buf, 260) and buf.value.lower() == nome.lower():
                return pid, h
        finally:
            if buf.value.lower() != nome.lower():
                k32.CloseHandle(h)
    return None, None


def ler(h, endereco, quantos):
    buf = (ctypes.c_char * quantos)()
    lidos = ctypes.c_size_t()
    ok = k32.ReadProcessMemory(h, ctypes.c_void_p(endereco), buf, quantos,
                               ctypes.byref(lidos))
    return bytes(buf[:lidos.value]) if ok else None


def main() -> int:
    pid, h = achar_processo()
    if not h:
        print("o Axn.exe nao esta a correr.")
        return 1
    print("Axn.exe pid %d" % pid)
    if len(sys.argv) >= 2:
        endereco = int(sys.argv[1], 16)
        quantos = int(sys.argv[2]) if len(sys.argv) > 2 else 16
        d = ler(h, endereco, quantos)
        print("0x%08x: %s" % (endereco, d.hex(" ") if d else "NAO DEU PARA LER"))
        return 0
    for endereco, quantos, nome in CONHECIDOS:
        d = ler(h, endereco, quantos)
        if d is None:
            print("  0x%08x  NAO DEU PARA LER   %s" % (endereco, nome))
        elif quantos == 4:
            print("  0x%08x  = 0x%08x         %s"
                  % (endereco, int.from_bytes(d, "little"), nome))
        else:
            print("  0x%08x  = %d                  %s" % (endereco, d[0], nome))
    return 0


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