runtime: guard overlay loader before initialization

Adapt only the fail-closed overlay lifecycle checks from mstan/psxrecomp#16 commit e6809ccf7d. Add a focused structural regression test and record the excluded dirty-interpreter and game/runtime work.

Co-authored-by: Martin Penkava <mpenkava1337@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-07-13 21:41:31 -07:00
parent dde268dc0f
commit 5b8098a9d9
3 changed files with 122 additions and 1 deletions
@@ -0,0 +1,33 @@
# Shaneomac PR #16: overlay-loader initialization guard
| Field | Value |
|---|---|
| Upstream PR | <https://github.com/mstan/psxrecomp/pull/16> |
| Source commit | [`e6809ccf7d778a4c2f32d9e27c0ec31a44cbd2ba`](https://github.com/mstan/psxrecomp/commit/e6809ccf7d778a4c2f32d9e27c0ec31a44cbd2ba) |
| Source author | Martin Penkava (`shaneomac1337`) `<mpenkava1337@gmail.com>` |
| Source co-author | Claude Fable 5 `<noreply@anthropic.com>` |
| 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.
+4 -1
View File
@@ -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 */
+85
View File
@@ -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)