Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs/internal/upstream/martin-pr16-overlay-decodable-fallback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# PR #16 decodable overlay fallback provenance

This branch isolates the reusable discovery fallback from Martin Penkava's
[`mstan/psxrecomp` PR #16](https://github.com/mstan/psxrecomp/pull/16), exact
source commit
[`e6809ccf7d778a4c2f32d9e27c0ec31a44cbd2ba`](https://github.com/mstan/psxrecomp/commit/e6809ccf7d778a4c2f32d9e27c0ec31a44cbd2ba).

Last evaluated: 2026-07-14

| Item | Value |
| --- | --- |
| Head repository/branch | `shaneomac1337/psxrecomp`, `smackdown2-fixes` |
| Pull request/source commit | `mstan/psxrecomp` PR #16, `e6809ccf7d778a4c2f32d9e27c0ec31a44cbd2ba` |
| Local base | `7085721afe338a03cb114321a3576cdff420b732` (`origin/master`) |
| Local branch | `feat/pr16-overlay-decodable-fallback-mpenkava` |

The fallback handles executable RAM populated by bulk host transfers that do
not traverse ordinary RAM write hooks. A control transfer above the configured
boot-EXE text end is admitted only when its first word passes the existing MIPS
decoder check; the interpreter then marks that word executable. Invalid, data,
out-of-RAM, and below-floor targets remain rejected.

The source commit also added pre-initialization guards to the overlay loader.
Those guards were reviewed and merged separately in PR #23 and are explicitly
excluded here. The source commit's game name, disc ID, addresses, and validation
claims are evidence only and are not embedded in framework code.

Source authorship is retained with:

```text
Co-authored-by: Martin Penkava <mpenkava1337@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
```
16 changes: 15 additions & 1 deletion runtime/src/dirty_ram_interp.c
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ extern void psx_dispatch_call(CPUState* cpu, uint32_t addr, uint32_t return_addr

/* Forward decls from memory.c — used to read instruction bytes. */
extern uint8_t *memory_get_ram_ptr(void);
extern void dirty_ram_mark_executable_range(uint32_t phys, uint32_t len);

/* MIPS instruction field decoders. */
static inline uint32_t op_field (uint32_t i) { return (i >> 26) & 0x3Fu; }
Expand Down Expand Up @@ -2211,7 +2212,20 @@ static int dirty_ram_dispatch_inner(CPUState* cpu, uint32_t addr, uint32_t stop_
}
#define OV_FPLOG_RET1() do { if (_ovfp) overlay_fp_log(addr, _in_regs, cpu, 0); return 1; } while (0)

if (!dirty_ram_is_dirty(phys) && !clean_game_text_miss) return 0;
if (!dirty_ram_is_dirty(phys) && !clean_game_text_miss) {
/* Bulk host transfers can populate post-EXE executable RAM without
* passing through the write hooks that mark dirty pages. A real
* control transfer to a decodable word above the configured boot-EXE
* text end is enough evidence to admit that word to the interpreter.
* Data and invalid targets still fail closed. */
if (phys < (2u * 1024u * 1024u) &&
phys >= g_overlay_region_floor &&
dirty_ram_word_looks_decodable(fetch_word(phys))) {
dirty_ram_mark_executable_range(phys, 4u);
} else {
return 0;
}
}

/* Interp-pressure signal for variant-capture automation (step 2.8):
* counts dispatches the interpreter actually handles inside a capture
Expand Down
56 changes: 56 additions & 0 deletions runtime/tests/test_overlay_decodable_fallback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""Structural regression test for unmarked post-EXE code discovery."""

from pathlib import Path
import re
import sys


ROOT = Path(__file__).resolve().parents[2]
SOURCE = ROOT / "runtime" / "src" / "dirty_ram_interp.c"


def function_body(source: str, name: str) -> str:
match = re.search(rf"\bstatic\s+int\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 main() -> int:
source = SOURCE.read_text(encoding="utf-8")
body = function_body(source, "dirty_ram_dispatch_inner")

dirty_gate = body.find("!dirty_ram_is_dirty(phys) && !clean_game_text_miss")
floor_gate = body.find("phys >= g_overlay_region_floor", dirty_gate)
ram_gate = body.find("phys < (2u * 1024u * 1024u)", dirty_gate)
decode_gate = body.find("dirty_ram_word_looks_decodable(fetch_word(phys))", dirty_gate)
mark = body.find("dirty_ram_mark_executable_range(phys, 4u)", dirty_gate)
if min(dirty_gate, floor_gate, ram_gate, decode_gate, mark) < 0:
raise AssertionError("missing dirty/floor/RAM/decode/mark fallback chain")
if not (dirty_gate < floor_gate < decode_gate < mark and dirty_gate < ram_gate < mark):
raise AssertionError("fallback checks or executable marking are out of order")

fallback = body[dirty_gate:mark + 200]
if "else {\n return 0;" not in fallback:
raise AssertionError("invalid/data targets do not fail closed")

print("PASS: decodable post-EXE dispatch fallback is guarded and fail-closed")
return 0


if __name__ == "__main__":
try:
sys.exit(main())
except AssertionError as exc:
print(f"FAIL: {exc}")
sys.exit(1)