- 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
203 lines
7 KiB
Python
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()
|