From 5b8098a9d997424cbd57829b8ae11369ab06935a Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Mon, 13 Jul 2026 21:41:31 -0700 Subject: [PATCH] runtime: guard overlay loader before initialization Adapt only the fail-closed overlay lifecycle checks from mstan/psxrecomp#16 commit e6809ccf7d778a4c2f32d9e27c0ec31a44cbd2ba. Add a focused structural regression test and record the excluded dirty-interpreter and game/runtime work. Co-authored-by: Martin Penkava Co-authored-by: Claude Fable 5 --- .../shaneomac-pr16-overlay-init-guard.md | 33 +++++++ runtime/src/overlay_loader.c | 5 +- runtime/tests/test_overlay_init_guard.py | 85 +++++++++++++++++++ 3 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 docs/internal/upstream/shaneomac-pr16-overlay-init-guard.md create mode 100644 runtime/tests/test_overlay_init_guard.py diff --git a/docs/internal/upstream/shaneomac-pr16-overlay-init-guard.md b/docs/internal/upstream/shaneomac-pr16-overlay-init-guard.md new file mode 100644 index 000000000..685f9ed94 --- /dev/null +++ b/docs/internal/upstream/shaneomac-pr16-overlay-init-guard.md @@ -0,0 +1,33 @@ +# Shaneomac PR #16: overlay-loader initialization guard + +| Field | Value | +|---|---| +| Upstream PR | | +| Source commit | [`e6809ccf7d778a4c2f32d9e27c0ec31a44cbd2ba`](https://github.com/mstan/psxrecomp/commit/e6809ccf7d778a4c2f32d9e27c0ec31a44cbd2ba) | +| Source author | Martin Penkava (`shaneomac1337`) `` | +| Source co-author | Claude Fable 5 `` | +| Port base | `dde268dc0fb9daf8fe6529f4aebfe80995350334` | +| Evaluated/ported | 2026-07-13 (America/Los_Angeles) | + +## Included + +Only the overlay-loader lifecycle guards from the source commit were adapted. +`overlay_loader_dispatch()`, `overlay_loader_is_candidate()`, and +`overlay_loader_call_native()` now return without consulting overlay indexes +until `overlay_loader_init()` finishes constructing them and sets `s_active`. + +The focused regression test is +`runtime/tests/test_overlay_init_guard.py`. + +## Explicitly excluded + +- The source commit's decodable-word/high-RAM dirty-interpreter fallback in + `runtime/src/dirty_ram_interp.c`. +- All SmackDown-specific behavior and data. +- Every other PR #16 change, including Vulkan, audio/SPU, GPU primitive-size, + MSVC portability, and helper-script work. + +The source commit combined the lifecycle guard with a separate discovery +fallback. This port intentionally does not cherry-pick that commit; it adapts +only the three fail-closed `s_active` checks so the framework change remains +game-agnostic and reviewable in isolation. diff --git a/runtime/src/overlay_loader.c b/runtime/src/overlay_loader.c index b0efd5625..2c8f56871 100644 --- a/runtime/src/overlay_loader.c +++ b/runtime/src/overlay_loader.c @@ -2390,6 +2390,7 @@ static int overlay_find_by_range(uint32_t phys) { int overlay_loader_dispatch(CPUState *cpu, uint32_t addr) { uint32_t phys = addr & 0x1FFFFFFFu; + if (!s_active) return 0; int lazy_loaded = 0; retry_candidates: int head = idx_head(phys); @@ -2883,6 +2884,7 @@ static FpEnt s_fp[FP_CAP]; static uint64_t s_fp_seq = 0; int overlay_loader_is_candidate(uint32_t phys) { + if (!s_active) return 0; phys &= 0x1FFFFFFFu; return idx_head(phys) >= 0 || lazy_has_exact_entry(phys); } @@ -3229,7 +3231,8 @@ void overlay_fp_log(uint32_t addr, const uint32_t *in_regs, * leaks (root cause of the dwarf->overworld native blue screen). * Returns 1 iff a native candidate ran. */ int overlay_loader_call_native(CPUState *cpu, uint32_t addr) { - if (!s_native_exec) return 0; /* interp mode: keep the legacy inline path */ + if (!s_active || !s_native_exec) + return 0; /* inactive/interp mode: keep the legacy inline path */ uint32_t phys = addr & 0x1FFFFFFFu; if (idx_head(phys) < 0 && !lazy_has_exact_entry(phys)) return 0; /* neither a registered nor an exact cached entry */ diff --git a/runtime/tests/test_overlay_init_guard.py b/runtime/tests/test_overlay_init_guard.py new file mode 100644 index 000000000..c14442ef0 --- /dev/null +++ b/runtime/tests/test_overlay_init_guard.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +"""Structural regression test for pre-init overlay-loader entry points. + +The public dispatch/candidate/native-call entry points can be reached during +early BIOS initialization, before overlay_loader_init() constructs their index +tables. They must fail closed on s_active before consulting those tables. + +Usage: python runtime/tests/test_overlay_init_guard.py +Exit 0 = PASS. +""" + +from pathlib import Path +import re +import sys + + +ROOT = Path(__file__).resolve().parents[2] +SOURCE = ROOT / "runtime" / "src" / "overlay_loader.c" + + +def function_body(source: str, name: str) -> str: + match = re.search(rf"\b(?:int|void)\s+{re.escape(name)}\s*\([^;]*?\)\s*\{{", source, re.S) + if not match: + raise AssertionError(f"missing function definition: {name}") + + start = match.end() + depth = 1 + for pos in range(start, len(source)): + if source[pos] == "{": + depth += 1 + elif source[pos] == "}": + depth -= 1 + if depth == 0: + return source[start:pos] + raise AssertionError(f"unterminated function definition: {name}") + + +def require_before(body: str, guard_pattern: str, state_pattern: str, name: str) -> None: + guard = re.search(guard_pattern, body) + state = re.search(state_pattern, body) + if not guard: + raise AssertionError(f"{name}: missing pre-init s_active guard") + if not state: + raise AssertionError(f"{name}: test could not find protected index access") + if guard.start() > state.start(): + raise AssertionError(f"{name}: s_active guard occurs after index access") + + +def main() -> int: + source = SOURCE.read_text(encoding="utf-8") + + dispatch = function_body(source, "overlay_loader_dispatch") + require_before( + dispatch, + r"if\s*\(\s*!s_active\s*\)\s*return\s+0\s*;", + r"idx_head\s*\(", + "overlay_loader_dispatch", + ) + + candidate = function_body(source, "overlay_loader_is_candidate") + require_before( + candidate, + r"if\s*\(\s*!s_active\s*\)\s*return\s+0\s*;", + r"idx_head\s*\(", + "overlay_loader_is_candidate", + ) + + call_native = function_body(source, "overlay_loader_call_native") + require_before( + call_native, + r"if\s*\(\s*!s_active\s*\|\|\s*!s_native_exec\s*\)\s*return\s+0\s*;", + r"idx_head\s*\(", + "overlay_loader_call_native", + ) + + print("PASS: overlay loader entry points fail closed before initialization") + return 0 + + +if __name__ == "__main__": + try: + sys.exit(main()) + except AssertionError as exc: + print(f"FAIL: {exc}") + sys.exit(1)