KAZIK/tools/tests/check_card_exit_build.py
Artvasil09 51c6512675 feat: implement secret fortune teller room, lived-in den v3, and bar camera stabilization
- Secret Fortune Teller Room: arcade lantern toggle, sliding timber panel, dedicated staircase and compact cellar chamber

- Lived-in den pass v3: cloth hangings, draped workbench, props/clutter, centered entrance, and custom light sources

- Bar camera stabilization: remove alcohol horizon sway while retaining drink animations

- Synchronize binaries and update launcher hash to EADBA9C8C9BD5B7E3CC4CE486ED647C1CE9383D1DCB6EBC94FE755EA1F4BF927
2026-10-20 17:29:52 +03:00

101 lines
5 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, CS_GRP_JUMP
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, tail_branches=False):
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
elif (tail_branches and ins.group(CS_GRP_JUMP) and not refs
and ins.operands[0].type == X86_OP_IMM
and ins.operands[0].imm in targets):
# A tail branch relocates with its callee, just like a direct call.
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()