"""Runs the deployed title on the PS5 with a test plan and collects what happened. uv run --no-project --with pillow python tools/console_test.py test/console-boot.cfg [--timeout S] Writes the plan to the title folder as test.cfg (plus a capture port), launches PPSA99728 with the rct2launch payload (tools/launcher, loaded through the Payload Manager; through the PS5Upload engine's launch route when the Payload Manager does not answer), pulls the frames the plan captures from the running title over TCP and saves them as PNG under build/test/run/, receives the title's log over the same connection when it exits (build/test/run/openrct2.log), prints it and removes test.cfg again. If the title dies before that, the log is still in the title folder: tools/deploy.py cat /data/homebrew/PPSA99728/openrct2.log. The PS5Upload helper must be running. """ import argparse import base64 import socket import struct import sys import time import urllib.request from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT.parent / "tools")) import ps5 # noqa: E402 import push_ffpkg as push # noqa: E402 TITLE = "PPSA99728" PLDMGR = f"http://{ps5.PS5_IP}:8084" LAUNCHER = ROOT / "build" / "launcher" / "rct2launch.elf" LAUNCHER_DIR = "/data/pldmgr/payloads/rct2launch" TEST_PLAN = f"/data/homebrew/{TITLE}/test.cfg" CAPTURE_PORT = 9119 OUT = ROOT / "build" / "test" / "run" LOG = "openrct2.log" def get(url, timeout=10): with urllib.request.urlopen(url, timeout=timeout) as r: return r.read().decode(errors="replace") def write_remote(path, data): r = push.http("POST", "/api/ps5/fs/write-bytes", {"addr": push.XFER, "path": path, "bytes_b64": base64.b64encode(data).decode()}) if "http_error" in r: sys.exit(f"writing {path} failed: {r}") def install_launcher(): data = LAUNCHER.read_bytes() try: if any(e["name"] == LAUNCHER.name and e["size"] == len(data) for e in ps5.list_dir(LAUNCHER_DIR)["entries"]): return except Exception: pass push.http("POST", "/api/ps5/fs/mkdir", {"addr": push.XFER, "path": LAUNCHER_DIR}) write_remote(f"{LAUNCHER_DIR}/{LAUNCHER.name}", data) def receive_exactly(sock, size): data = bytearray() while len(data) < size: chunk = sock.recv(min(1 << 20, size - len(data))) if not chunk: raise ConnectionError("capture stream ended early") data += chunk return bytes(data) def pull_captures(seen): from PIL import Image try: sock = socket.create_connection((ps5.PS5_IP, CAPTURE_PORT), timeout=2) except OSError: return sock.settimeout(30) try: while True: (name_length,) = struct.unpack("