KAZIK/tools/build_setup.py
Artvasil09 23dc40ee5c feat: implement native darts mini-game, AI observer mode, and fortune session assets
- Darts: native 500-point match against Max with sway/fatigue mouse aim, parabolic throw camera, raster arm animations, and 16 camera-relative flying dart views

- AI Observer: autonomous spectator mode for Hold'em, Blackjack, Durak, and Billiards with profile isolation and playback controls

- Fortune Session: raster sprite packs, mist/orb animations, tarot divination spreads, and pack tools

- Hall Integration: wall dartboard in lounge, observer terminal dispatch, and bot turn hooks

- Packaging & Verification: updated installer streaming hash, synchronized launcher, passing check suites and release smoke checks
2026-10-24 22:10:25 +03:00

203 lines
7 KiB
Python

#!/usr/bin/env python3
"""Build and package the complete modern Assembler Casino into PokerGameSetup.exe."""
import hashlib
import os
import re
import subprocess
import sys
import zipfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
MARKER = b"<<POKER_GAME_GUI_PAYLOAD_ZIP>>"
def get_sha256(data_or_path):
if isinstance(data_or_path, (str, Path)):
h = hashlib.sha256()
with open(data_or_path, "rb") as f:
while chunk := f.read(65536):
h.update(chunk)
return h.hexdigest().upper()
return hashlib.sha256(data_or_path).hexdigest().upper()
def collect_payload_files():
entries = []
# Executables
for src, dst in [
(ROOT / "dist/Assembler Casino.exe", "Assembler Casino.exe"),
(ROOT / "dist/Uninstall Assembler Casino.exe", "Uninstall Assembler Casino.exe"),
(ROOT / "bin/poker.exe", "bin/poker.exe"),
(ROOT / "poker.exe", "poker.exe"),
]:
if not src.exists():
raise FileNotFoundError(f"Required binary not found: {src}")
entries.append((src, dst))
# Assets (excluding raw sprites used only for asset generation)
assets_dir = ROOT / "assets"
for r, dirs, files in os.walk(assets_dir):
if "sprites" in Path(r).parts:
continue
for f in sorted(files):
fp = Path(r) / f
rel = fp.relative_to(ROOT).as_posix()
entries.append((fp, rel))
# Docs
docs_dir = ROOT / "docs"
for r, dirs, files in os.walk(docs_dir):
for f in sorted(files):
if f.endswith((".txt", ".md")):
fp = Path(r) / f
rel = fp.relative_to(ROOT).as_posix()
entries.append((fp, rel))
# Tools
tool_files = [
"tools/previews/preview_dbd_hook.bat",
"tools/previews/preview_hakari.bat",
"tools/previews/preview_intro.bat",
"tools/previews/preview_casino3d.bat",
"tools/tests/achievement_popup.bat",
"tools/tests/release_smoke_check.ps1",
]
for rel in tool_files:
fp = ROOT / rel
if fp.exists():
entries.append((fp, rel))
# Backwards compatibility paths for tests / older scripts
if (ROOT / "tools/previews/preview_dbd_hook.bat").exists():
entries.append((ROOT / "tools/previews/preview_dbd_hook.bat", "tools/preview_dbd_hook.bat"))
if (ROOT / "tools/tests/release_smoke_check.ps1").exists():
entries.append((ROOT / "tools/tests/release_smoke_check.ps1", "tools/release_smoke_check.ps1"))
# Runtime configuration and state
for rel in ["runtime/casino3d-progress.dat", "runtime/display.cfg"]:
fp = ROOT / rel
if fp.exists():
entries.append((fp, rel))
# AI worker & metadata (without the 2.5 GB model per docs/FORTUNE_AI.md)
ai_dir = ROOT / "ai"
if ai_dir.exists():
for r, dirs, files in os.walk(ai_dir):
if "models" in Path(r).parts:
continue
for f in sorted(files):
fp = Path(r) / f
rel = fp.relative_to(ROOT).as_posix()
entries.append((fp, rel))
# Root documentation
for rel in ["AGENTS.md", "README.md"]:
fp = ROOT / rel
if fp.exists():
entries.append((fp, rel))
return entries
def build_payload_zip(zip_path, entries):
print(f"Creating payload archive: {zip_path} ({len(entries)} files)...")
zip_path.parent.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=6) as zf:
for src, dst in entries:
zf.write(src, dst)
size_mb = zip_path.stat().st_size / (1024 * 1024)
print(f"Payload archive created: {size_mb:.2f} MB")
def compile_setup_base(source_path, base_exe_path):
csc = Path(os.environ.get("WINDIR", r"C:\Windows")) / r"Microsoft.NET\Framework\v4.0.30319\csc.exe"
if not csc.exists():
raise RuntimeError(f"csc.exe not found at {csc}")
icon = ROOT / "assets/icons/casino.ico"
cmd = [
str(csc),
"/nologo",
"/target:winexe",
"/platform:x86",
"/optimize+",
"/reference:System.Windows.Forms.dll",
"/reference:System.Drawing.dll",
"/reference:System.IO.Compression.dll",
"/reference:System.IO.Compression.FileSystem.dll",
"/reference:System.Core.dll",
f"/win32icon:{icon}",
f"/out:{base_exe_path}",
str(source_path),
]
print(f"Compiling installer base: {' '.join(cmd)}")
res = subprocess.run(cmd, capture_output=True, text=True)
if res.returncode != 0:
raise RuntimeError(f"Compilation failed:\n{res.stdout}\n{res.stderr}")
print(f"Compiled: {base_exe_path} ({base_exe_path.stat().st_size} bytes)")
def main():
print("=== Building Assembler Casino PokerGameSetup ===")
# 1. Collect files
entries = collect_payload_files()
payload_zip = ROOT / "build/payload_release_final.zip"
build_payload_zip(payload_zip, entries)
# 2. Hash payload
payload_hash = get_sha256(payload_zip)
print(f"Payload SHA-256: {payload_hash}")
# 3. Update PokerGameSetupGui.cs with new payload hash
setup_cs = ROOT / "build/PokerGameSetupGui.cs"
cs_content = setup_cs.read_text(encoding="utf-8")
hash_pattern = r'const string ExpectedPayloadSha256 = "([A-Fa-f0-9]{64}|TO_BE_FILLED_BY_RELEASE_BUILD)";'
if not re.search(hash_pattern, cs_content):
raise ValueError("Could not find ExpectedPayloadSha256 constant in PokerGameSetupGui.cs")
new_cs_content = re.sub(
hash_pattern,
f'const string ExpectedPayloadSha256 = "{payload_hash}";',
cs_content,
count=1
)
setup_cs.write_text(new_cs_content, encoding="utf-8")
print("Updated ExpectedPayloadSha256 in PokerGameSetupGui.cs")
# 4. Compile base setup executable
base_exe = ROOT / "build/PokerGameSetup_base.exe"
compile_setup_base(setup_cs, base_exe)
# 5. Assemble final PokerGameSetup.exe
setup_exe = ROOT / "PokerGameSetup.exe"
print(f"Assembling {setup_exe}...")
with open(setup_exe, "wb") as out_f:
with open(base_exe, "rb") as in_base:
out_f.write(in_base.read())
out_f.write(MARKER)
with open(payload_zip, "rb") as in_zip:
while chunk := in_zip.read(65536):
out_f.write(chunk)
total_mb = setup_exe.stat().st_size / (1024 * 1024)
print(f"=== Successfully built {setup_exe} ({total_mb:.2f} MB) ===")
# 6. Verify built setup
with open(setup_exe, "rb") as f:
head = f.read(4 * 1024 * 1024)
idx = head.find(MARKER)
assert idx != -1, "Marker not found in generated setup!"
f.seek(idx + len(MARKER))
h = hashlib.sha256()
while chunk := f.read(65536):
h.update(chunk)
actual_hash = h.hexdigest().upper()
assert actual_hash == payload_hash, f"Hash mismatch: {actual_hash} != {payload_hash}"
print(f"[VERIFIED] Marker and SHA-256 match perfectly ({actual_hash})")
if __name__ == "__main__":
main()