"""Offline debugger for the headless MASM test; never shipped with the game.""" import ctypes as c from ctypes import wintypes as w from pathlib import Path import struct from capstone import Cs, CS_ARCH_X86, CS_MODE_32 from check_card_exit_build import Coff ROOT = Path(__file__).resolve().parents[2] K = c.WinDLL('kernel32', use_last_error=True) class Startup(c.Structure): _fields_ = [('cb', w.DWORD), ('reserved', w.LPWSTR), ('desktop', w.LPWSTR), ('title', w.LPWSTR), ('x', w.DWORD), ('y', w.DWORD), ('cx', w.DWORD), ('cy', w.DWORD), ('charsx', w.DWORD), ('charsy', w.DWORD), ('fill', w.DWORD), ('flags', w.DWORD), ('show', w.WORD), ('reserved2', w.WORD), ('data', c.c_void_p), ('stdin', w.HANDLE), ('stdout', w.HANDLE), ('stderr', w.HANDLE)] class Process(c.Structure): _fields_ = [('process', w.HANDLE), ('thread', w.HANDLE), ('pid', w.DWORD), ('tid', w.DWORD)] class Event(c.Structure): _fields_ = [('code', w.DWORD), ('pid', w.DWORD), ('tid', w.DWORD), ('align', w.DWORD), ('data', c.c_ubyte * 160)] K.CreateProcessW.argtypes = [w.LPCWSTR, w.LPWSTR, c.c_void_p, c.c_void_p, w.BOOL, w.DWORD, c.c_void_p, w.LPCWSTR, c.POINTER(Startup), c.POINTER(Process)] K.WaitForDebugEvent.argtypes = [c.POINTER(Event), w.DWORD] K.ContinueDebugEvent.argtypes = [w.DWORD, w.DWORD, w.DWORD] K.ReadProcessMemory.argtypes = [w.HANDLE, c.c_void_p, c.c_void_p, c.c_size_t, c.POINTER(c.c_size_t)] K.OpenThread.argtypes = [w.DWORD, w.BOOL, w.DWORD] K.OpenThread.restype = w.HANDLE K.Wow64GetThreadContext.argtypes = [w.HANDLE, c.c_void_p] K.CloseHandle.argtypes = [w.HANDLE] def main(): obj = Coff(ROOT / 'build/actions/actions-test.obj') code = [(value, name) for name, (section, value) in obj.functions.items() if section == obj.functions['_RunDurakTest3D@0'][0]] start, process = Startup(), Process() start.cb, start.flags, start.show = c.sizeof(start), 1, 0 executable = str(ROOT / 'build/actions/actions-test.exe') command = c.create_unicode_buffer('"' + executable + '"') if not K.CreateProcessW(executable, command, None, None, False, 3, None, str(ROOT), c.byref(start), c.byref(process)): raise c.WinError(c.get_last_error()) base, fault = 0, False def memory(address, size): buffer, read = c.create_string_buffer(size), c.c_size_t() K.ReadProcessMemory(process.process, address, buffer, size, c.byref(read)) return buffer.raw[:read.value] def symbol(address): candidates = [(value, name) for value, name in code if value <= address - base - 0x1000] if not candidates: return hex(address) value, name = max(candidates) return f'{name}+0x{address - base - 0x1000 - value:x}' while True: event = Event() if not K.WaitForDebugEvent(c.byref(event), 120000): raise c.WinError(c.get_last_error()) data, status = bytes(event.data), 0x10002 if event.code == 3: base = struct.unpack_from('