KAZIK/tools/tests/diagnose_wheel_assert.py
Artvasil09 4f8ae0893b Implement native slots, coin toss, European roulette and hall furniture
Add native assembly scenes and assets for Slots (777), Coin flip, and European Roulette; add 3D hall furniture packs and prompts; improve Russian UI strings in CP1251; update launcher hash and rebuild shipping binaries.
2026-10-10 14:43:30 +03:00

94 lines
4.4 KiB
Python

"""Offline assertion breakpoint in the native test executable, not game code."""
import ctypes as c
import struct
from capstone import Cs, CS_ARCH_X86, CS_MODE_32
from debug_native_crash import K, ROOT, Startup, Process, Event
from check_card_exit_build import Coff
def main():
obj = Coff(ROOT / "build/actions/actions-test.obj")
sec, start = obj.functions["_RunWheelSceneTest3D@0"]
raw, size, _ = obj.sections[sec]
end = min(v for s, v in obj.functions.values() if s == sec and v > start)
instructions = list(Cs(CS_ARCH_X86, CS_MODE_32).disasm(obj.data[raw + start:raw + end], start))
trap_offset = instructions[-6].address
assert instructions[-6].mnemonic == "mov"
symbols = {}
_, _, _, table, total, _, _ = struct.unpack_from("<HHIIIHH", obj.data)
index = 0
while index < total:
value, section, _, _, aux = struct.unpack_from("<IhHBB", obj.data, table + index * 18 + 8)
if section == 2:
symbols[obj.symbols[index]] = value
index += aux + 1
pe = (ROOT / "build/actions/actions-test.exe").read_bytes()
header = struct.unpack_from("<I", pe, 60)[0]
count = struct.unpack_from("<H", pe, header + 6)[0]
opt = struct.unpack_from("<H", pe, header + 20)[0]
sections = header + 24 + opt
data_rva = None
for i in range(count):
at = sections + i * 40
if pe[at:at + 8].rstrip(b"\0") == b".data":
data_rva = struct.unpack_from("<I", pe, at + 12)[0]
assert data_rva
K.WriteProcessMemory.argtypes = [c.c_void_p, c.c_void_p, c.c_void_p, c.c_size_t, c.c_void_p]
K.TerminateProcess.argtypes = [c.c_void_p, c.c_uint32]
start_info, process = Startup(), Process()
start_info.cb, start_info.flags, start_info.show = c.sizeof(start_info), 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_info), c.byref(process)):
raise c.WinError(c.get_last_error())
base = trap = 0
def memory(address, size):
buffer, read = c.create_string_buffer(size), c.c_size_t()
assert K.ReadProcessMemory(process.process, address, buffer, size, c.byref(read))
return buffer.raw[:read.value]
while True:
event = Event()
assert K.WaitForDebugEvent(c.byref(event), 120000)
data, status = bytes(event.data), 0x10002
if event.code == 3:
base = struct.unpack_from("<Q", data, 24)[0]
trap = base + 0x1000 + trap_offset
assert K.WriteProcessMemory(process.process, trap, b"\xcc", 1, None)
elif event.code == 1:
exception = struct.unpack_from("<I", data)[0]
address = struct.unpack_from("<Q", data, 16)[0]
if exception in (0x80000003, 0x4000001F) and address == trap:
thread = K.OpenThread(0x48, False, event.tid)
context = c.create_string_buffer(716)
struct.pack_into("<I", context, 0, 0x10007)
assert 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("ASSERTION REGISTERS:", regs, flush=True)
print("LOCALS:", struct.unpack("<7I", memory(regs["ebp"] - 28, 28)), flush=True)
for name in ("wheelArtReady3D", "wheelStake3D", "wheelReturn3D", "wheelResult3D",
"wheelResultSlot3D", "wheelBallAngle3D", "bankroll", "rngSeed3D", "roulState"):
print(name, struct.unpack("<I", memory(base + data_rva + symbols[name], 4))[0], flush=True)
K.CloseHandle(thread)
K.TerminateProcess(process.process, 1)
elif exception not in (0x80000003, 0x4000001F):
status = 0x80010001
elif event.code == 5:
print("PROCESS EXIT:", 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()