KAZIK/tools/tests/check_card_exit_build.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

95 lines
4.6 KiB
Python

"""Verify that shipping card handlers match the native regression-test handlers."""
import struct
from pathlib import Path
from capstone import Cs, CS_ARCH_X86, CS_MODE_32
from capstone.x86_const import X86_OP_IMM
class Coff:
def __init__(self, path):
self.data = path.read_bytes()
_, count, _, symbol_at, total, optional, _ = struct.unpack_from("<HHIIIHH", self.data)
strings = symbol_at + total * 18
self.symbols = {}
self.functions = {}
index = 0
while index < total:
at = symbol_at + index * 18
zero, offset = struct.unpack_from("<II", self.data, at)
if zero == 0:
end = self.data.index(0, strings + offset)
name = self.data[strings + offset:end].decode("ascii")
else:
name = self.data[at:at + 8].split(b"\0")[0].decode("ascii")
value, section, kind, storage, aux = struct.unpack_from("<IhHBB", self.data, at + 8)
self.symbols[index] = name
if kind == 0x20 and storage == 2 and section > 0:
self.functions[name] = (section, value)
index += 1 + aux
self.sections = {}
for section in range(1, count + 1):
at = 20 + optional + (section - 1) * 40
size, raw, reloc, _, reloc_count = struct.unpack_from("<IIIIH", self.data, at + 16)
entries = [struct.unpack_from("<IIH", self.data, reloc + i * 10)
for i in range(reloc_count)]
self.sections[section] = (raw, size, entries)
def signature(self, name):
section, start = self.functions[name]
raw, size, entries = self.sections[section]
end = min([value for sec, value in self.functions.values()
if sec == section and value > start] or [size])
relocations = {at: (self.symbols[symbol], kind) for at, symbol, kind in entries
if start <= at < end}
targets = {value: symbol for symbol, (sec, value) in self.functions.items()
if sec == section}
decoder = Cs(CS_ARCH_X86, CS_MODE_32)
decoder.detail = True
result = []
for ins in decoder.disasm(self.data[raw + start:raw + end], start):
code = bytearray(ins.bytes)
refs = []
for at, reference in relocations.items():
if ins.address <= at < ins.address + ins.size:
offset = at - ins.address
code[offset:offset + 4] = b"\0" * 4
refs.append((offset, reference))
target = None
if ins.mnemonic == "call" and not refs and ins.operands[0].type == X86_OP_IMM:
target = targets[ins.operands[0].imm]
code[ins.imm_offset:ins.imm_offset + ins.imm_size] = b"\0" * ins.imm_size
result.append((bytes(code), tuple(refs), target))
assert sum(len(code) for code, _, _ in result) == end - start, name
return result
def main():
root = Path(__file__).resolve().parents[2]
tested = Coff(root / "build/actions/actions-test.obj")
shipped = Coff(root / "build/graphics/poker.obj")
functions = ("CardTableMouse3D", "LeaveCardTable3D", "RequestCardExit3D",
"CardTableEscape3D", "HandleCardExit3D", "RenderCardExit3D",
"HandleNativePoker3D", "HandleNativeBlackjack3D",
"RenderNativePoker3D", "RenderNativeBlackjack3D", "StartCasino3D",
"InitNativeDurak3D", "StartNativeDurak3D", "DurakBeginBout3D",
"DurakCommitFlight3D", "DurakResolveBout3D", "DurakAITurn3D",
"DurakClampSelection3D", "DurakMouse3D",
"HandleNativeDurak3D", "RenderNativeDurak3D")
for name in functions:
suffix = "@4" if name == "CardTableMouse3D" else "@0"
symbol = "_" + name + suffix
assert tested.signature(symbol) == shipped.signature(symbol), name
for name, size in (("NativeDurakSettle3D", 4), ("DurakCanTransfer3D", 8),
("DurakCanPlay3D", 12), ("DurakPlay3D", 12),
("DurakTake3D", 4), ("DurakFinish3D", 4),
("DurakRefill3D", 4), ("DurakCardValue3D", 4),
("DurakFindMove3D", 8), ("DurakMoveSelection3D", 4),
("DurakFlightPoint3D", 8), ("DrawNativeCard3D", 20)):
symbol = f"_{name}@{size}"
assert tested.signature(symbol) == shipped.signature(symbol), name
print("PASS: shipping engine loop and all poker/blackjack/Durak input, rules, animation and exit handlers match native-tested code")
if __name__ == "__main__":
main()