#!/usr/bin/env python3 """Prepare a hash-verified, pinned public ARM test outside tracked source 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 LOCK = Path(__file__).with_name("gba-tests-arm.lock.json") 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): output = destination(output) lock = json.loads(LOCK.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 branch here with r12 set; successful fallthrough keeps r12=0. # Hash pinning is authoritative; this check also catches adapter-offset mistakes. pc = int(lock["completion_pc"], 16) offset = pc - 0x08000000 rom = files[lock["rom"]] if offset < 0 or offset + 4 > len(rom) or struct.unpack_from("