mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-27 18:52:11 -04:00
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:
@@ -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.
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user