#!/usr/bin/env python3 """Prepare a pinned public ARM, Thumb, or memory test outside tracked paths. This adapter is original. Upstream sources, license, and ROM bytes are downloaded only to the requested local directory. They are not embedded in this repository. """ import argparse import hashlib import json from pathlib import Path import struct import sys from urllib.request import Request, urlopen ROOT = Path(__file__).resolve().parent.parent LOCKS = {suite: Path(__file__).with_name(f"gba-tests-{suite}.lock.json") for suite in ("arm", "thumb", "memory")} MAX_FILE_BYTES = 1024 * 1024 def fetch(url): request = Request(url, headers={"User-Agent": "gameboy-rust-public-test-adapter"}) with urlopen(request, timeout=30) as response: return response.read(MAX_FILE_BYTES + 1) def verify(name, data, expected): if len(data) > MAX_FILE_BYTES or len(data) != expected["bytes"]: raise ValueError(f"size mismatch for {name}") if hashlib.sha256(data).hexdigest() != expected["sha256"]: raise ValueError(f"SHA-256 mismatch for {name}") def destination(path): path = Path(path).resolve() # Public ROM headers can contain third-party assets. Keep them untracked. if path.is_relative_to(ROOT) and not path.is_relative_to(ROOT / "roms"): raise ValueError("output must be outside the repository or inside ignored roms/") if path.exists(): raise ValueError(f"output directory already exists: {path}") if not path.parent.is_dir(): raise ValueError("output parent directory must already exist") return path def prepare(output, source=None, download=fetch, suite="arm"): if suite not in LOCKS: raise ValueError(f"unsupported public suite: {suite}") output = destination(output) lock = json.loads(LOCKS[suite].read_text(encoding="utf-8")) base = f"https://raw.githubusercontent.com/{lock['repository']}/{lock['revision']}/" files = {} for name, expected in lock["files"].items(): if source is None: data = download(base + name) else: path = Path(source) / name if not path.is_file() or path.stat().st_size > MAX_FILE_BYTES: raise ValueError(f"source must be a bounded regular file: {name}") with path.open("rb") as stream: data = stream.read(MAX_FILE_BYTES + 1) verify(name, data, expected) files[name] = data # The source-defined eval label starts with m_vsync's register save. All # failure exits set the result register (ARM/memory r12, Thumb r7). # Thumb tests switch back to ARM before eval. Success leaves the result zero. # Hash pinning is authoritative; these checks catch adapter-offset mistakes. pc = int(lock["completion_pc"], 16) offset = pc - 0x08000000 rom = files[lock["rom"]] if offset < 0 or offset % 4 or offset + 4 > len(rom) or struct.unpack_from("