#!/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"<>" 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()