KAZIK/tools/tests/debug_native_crash.py
Artvasil09 3ad5bf4c9d Develop assembly-only casino scenes and native games
Add native Durak, poker, blackjack, roulette, Buckshot and billiards scenes with animation assets and regression tests. Improve arrival, city, hall, profiles, shops, audio and exit handling; remove the portable C game core and refresh shipping executables.
2026-10-08 22:39:19 +03:00

108 lines
4.9 KiB
Python

"""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('<Q', data, 24)[0]
elif event.code == 1:
exception = struct.unpack_from('<I', data)[0]
if exception != 0x80000003:
status = 0x80010001
if exception == 0xC0000005 and not fault:
fault = True
address = struct.unpack_from('<Q', data, 16)[0]
access, target = struct.unpack_from('<QQ', data, 32)
print(f'ACCESS VIOLATION: {symbol(address)} operation={access} target={target:x}', flush=True)
thread, context = K.OpenThread(0x48, False, event.tid), c.create_string_buffer(716)
struct.pack_into('<I', context, 0, 0x10007)
if K.Wow64GetThreadContext(thread, context):
regs = dict(zip(('edi', 'esi', 'ebx', 'edx', 'ecx', 'eax', 'ebp', 'eip', 'cs', 'flags', 'esp'),
struct.unpack_from('<11I', context, 156)))
print({name: hex(value) for name, value in regs.items()}, flush=True)
print('Stack:', [symbol(value) for (value,) in struct.iter_unpack('<I', memory(regs['esp'], 80))], flush=True)
K.CloseHandle(thread)
for ins in Cs(CS_ARCH_X86, CS_MODE_32).disasm(memory(address, 48), address):
print(f' {ins.address:x}: {ins.mnemonic} {ins.op_str}', flush=True)
elif event.code == 5:
print('PROCESS EXIT:', hex(struct.unpack_from('<I', data)[0]), flush=True)
K.ContinueDebugEvent(event.pid, event.tid, status)
break
elif event.code == 6:
handle = struct.unpack_from('<Q', data)[0]
if handle:
K.CloseHandle(handle)
K.ContinueDebugEvent(event.pid, event.tid, status)
K.CloseHandle(process.thread)
K.CloseHandle(process.process)
if __name__ == '__main__':
main()