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
10 changes: 10 additions & 0 deletions runtime/include/fntrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ void fntrace_record(CPUState* cpu, uint32_t target);
* First dispatch into [lo, hi) calls cdrom_notify_game_started(). */
void fntrace_set_game_range(uint32_t lo, uint32_t hi);
int fntrace_is_game_started(void);
/* One-shot game-start handoff. Idempotent — safe to call from any path
* (compiled dispatch, dirty-RAM interpreter, generated entry function).
* Performs dirty-image baseline clear, low-boot scratch clear, CD speed
* switch, and boot-state capture. */
void fntrace_mark_game_started(CPUState* cpu);
/* Entry-pc-exact latch for the interpreter path: calls
* fntrace_mark_game_started() only when addr matches the armed game entry
* (fntrace_set_game_range lo). No-op before the range is armed and after
* the latch fires. */
void fntrace_maybe_mark_game_started(CPUState* cpu, uint32_t addr);

/* Arm a target filter. arm_count == 0 means "record all" (default).
* When arm_count > 0, only dispatches whose target matches one of the
Expand Down
11 changes: 11 additions & 0 deletions runtime/src/dirty_ram_interp.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "ws_backdrop_detect.h" /* shared backdrop-window detector (auto_backdrop) */
#include "lockstep.h"
#include "starvation_ring.h"
#include "fntrace.h" /* fntrace_is_game_started / fntrace_mark_game_started */

#include <stdint.h>
#include <stdlib.h>
Expand Down Expand Up @@ -2397,6 +2398,16 @@ int dirty_ram_dispatch(CPUState* cpu, uint32_t addr, uint32_t stop_addr) {
psx_fatal_halt("dispatch recursion guard tripped — runaway self-call "
"(see dispatch_depth + dirty_block cycle for the recursing PC)");
}
/* Game-start detection for the dirty-RAM interpreter path.
* When dispatch_count == 0 (all code runs interpreted), the native
* dispatch path's fntrace_record() never fires, so widescreen/mouselook/
* CD-speed-switch are never engaged. This one-shot check closes the gap
* with the SAME semantics as the native path: latch only on the exact
* game entry PC. A broader match (any phys >= 0x10000) fires during
* BIOS boot — the shell/kernel run relocated RAM code above 0x10000 —
* and the handoff's baseline/scratch clears then corrupt the boot
* (observed: MoH SLUS-00974 garbage-jump/VBLANK-wedge, 2026-08-06). */
fntrace_maybe_mark_game_started(cpu, addr);
if (addr == 0x8001A954u) site_note(&g_site_dd954); /* loop head re-dispatch */
else if (addr == 0x80046264u) site_note(&g_site_dd264); /* loop tail re-dispatch */
int prev = g_dirty_interp_active;
Expand Down
43 changes: 30 additions & 13 deletions runtime/src/fntrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "text_xlate.h" /* on-the-fly string translation hook (framework) */
#include "parity_trace.h" /* general control-flow parity ring (native producer) */
#include "mod_runtime.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

Expand Down Expand Up @@ -44,6 +45,34 @@ void fntrace_set_game_range(uint32_t lo, uint32_t hi) {

int fntrace_is_game_started(void) { return s_game_started; }

/* Centralised game-start transition. Idempotent — safe to call from both
* the dispatcher (fntrace_record) and the generated entry-point function.
* Performs the complete handoff side effects: dirty-image baseline clear,
* low-boot scratch clear, CD speed switch, and boot-state capture. */
void fntrace_mark_game_started(CPUState* cpu) {
if (s_game_started) return;
s_game_started = 1;
extern void dirty_ram_clear_image_baseline(void);
extern void memory_clear_low_boot_scratch(void);
dirty_ram_clear_image_baseline();
memory_clear_low_boot_scratch();
cdrom_notify_game_started();
boot_state_trigger_capture(cpu);
}

/* Range-guarded latch for the dirty-RAM interpreter path. Mirrors the
* native path's semantics: latch ONLY on the exact game entry PC set via
* fntrace_set_game_range(). Never latch on a broad address heuristic —
* the BIOS shell/kernel execute relocated RAM code well above the game
* load address during boot, and a premature handoff (baseline/scratch
* clears, CD speed switch) corrupts the boot sequence. */
void fntrace_maybe_mark_game_started(CPUState* cpu, uint32_t addr) {
if (s_game_started) return;
if (s_game_entry_phys == 0) return; /* no game range armed */
if ((addr & 0x1FFFFFFFu) == s_game_entry_phys)
fntrace_mark_game_started(cpu);
}

static inline int armed_match(uint32_t target) {
/* PSX_FNTRACE_ALL=1 records every dispatch from power-on — the ring
* then holds the earliest boot execution (before any TCP client can
Expand Down Expand Up @@ -184,19 +213,7 @@ void fntrace_record(CPUState* cpu, uint32_t target) {
if (_tphys == s_game_entry_phys ||
(dirty_ram_text_image_registered() &&
psx_game_address_in_text(target) && psx_game_text_native_ok(target))) {
s_game_started = 1;
/* Establish the clean compiled-image baseline now: the boot EXE is fully
* loaded into the game-text region (== compiled image) and no gameplay
* overlay has run yet. The EXE load marked the whole text dirty (false
* positive); clearing it makes dirty_ram_is_dirty() true ONLY for pages a
* later overlay overwrites, so the dispatch runs clean text compiled and
* interprets only true overlays (Tomba 2 boot-text loader overlay). */
extern void dirty_ram_clear_image_baseline(void);
extern void memory_clear_low_boot_scratch(void);
dirty_ram_clear_image_baseline();
memory_clear_low_boot_scratch();
cdrom_notify_game_started();
boot_state_trigger_capture(cpu);
fntrace_mark_game_started(cpu);
}
}
/* Honor the one-shot capture freeze (insn_freeze): once latched, the ring
Expand Down