#!/usr/bin/env python3 """The overlay producer must DECLARE its trailing delay-slot guard words. overlay_capture.c appends one coherent guard instruction past the end of a dirty-page run so a MIPS branch at the run's final word (...FFC) has its architectural delay slot (...000). The recompiler must be TOLD how many trailing bytes that is, because the guard word is a legal delay-slot source and an illegal block leader -- the word after it does not exist in the image. Inferring the count inside the recompiler from `size % 4096 == 4` would fit every capture written by the current format and would silently shift underneath any future change to capture granularity. This pins the whole producer chain (bead beads-eio.3.100): overlay_capture.c writes "guard_bytes" (writer states intent) -> compile_overlays.capture_guard_bytes (prefer it; reconstruct for pre-field captures) -> compile_overlays.make_psxexe (tagged PS-EXE header) -> ps1_exe_parser.h exe_tag (recompiler reads it) """ import struct import sys import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "tools")) import compile_overlays # noqa: E402 sys.path.insert(0, str(ROOT / "tools" / "aot_overlay_spike")) import extract_generic # noqa: E402 class GuardByteDerivationTests(unittest.TestCase): def test_explicit_field_wins(self): self.assertEqual( compile_overlays.capture_guard_bytes({"guard_bytes": 4}, 0x2004), 4) # A page-multiple+4 capture that explicitly says it has NO guard word # must be believed: the writer knows, the size does not. self.assertEqual( compile_overlays.capture_guard_bytes({"guard_bytes": 0}, 0x2004), 0) def test_legacy_capture_reconstructs_from_the_page_run_format(self): # Current format: whole 4 KiB pages plus one guard word. self.assertEqual(compile_overlays.capture_guard_bytes({}, 0x2004), 4) self.assertEqual(compile_overlays.capture_guard_bytes({}, 0x1004), 4) # Pre-2026-07-25 captures are page-exact and have no guard word. self.assertEqual(compile_overlays.capture_guard_bytes({}, 0x2000), 0) self.assertEqual(compile_overlays.capture_guard_bytes({}, 0x1000), 0) def test_unknown_residue_falls_back_to_zero(self): # Not this tool's format. Zero is the fail-closed answer: the emitter's # mandatory-delay-slot check still refuses a transfer it cannot finish. self.assertEqual(compile_overlays.capture_guard_bytes({}, 0x2008), 0) self.assertEqual(compile_overlays.capture_guard_bytes({}, 100), 0) def test_nonsense_declaration_is_rejected(self): self.assertEqual( compile_overlays.capture_guard_bytes({"guard_bytes": 7}, 0x2004), 0) self.assertEqual( compile_overlays.capture_guard_bytes( {"guard_bytes": "four"}, 0x2004), 0) # A declaration that would consume the whole image is not a trailer. self.assertEqual( compile_overlays.capture_guard_bytes({"guard_bytes": 4}, 4), 0) class PsxExeTagTests(unittest.TestCase): PAYLOAD = b"\x00" * 0x2000 + struct.pack("