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.
108 lines
4.9 KiB
Python
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()
|