mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Disc speed: post-BIOS switch via entry_pc, XA guard for FMVs
Boot stays at authentic 1x. Speed switch fires once when the game's entry_pc is first dispatched — safely after the BIOS disc init and Sony/PlayStation logo sequences complete. Uses entry_pc exact-match rather than text range to avoid false-triggering on the BIOS shell (RAM 0x30000-0x5B000 overlaps the game text range). XA guard in apply_speed(): reverts to 1x whenever xa_stream_active, protecting FMV playback from speed-up regardless of disc_speed setting. CDROM_MIN_DELAY floor of 500 cycles prevents instant mode from firing IRQs faster than the interrupt handler can process them. Tomba game.toml: disc_speed = "4x" (instant proved too aggressive for early game disc reads; 4x safe with XA guard protecting FMVs). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,10 +9,12 @@ extern "C" {
|
||||
|
||||
void cdrom_init(const char* cue_path);
|
||||
|
||||
/* Set disc speed multiplier. divisor=0 → instant (all delays collapse to
|
||||
* 1 cycle, correct INT sequence fires as fast as host can process).
|
||||
* divisor=1 → authentic 1x. divisor=2 → 2x. Call before first cdrom_tick. */
|
||||
/* Set disc speed multiplier immediately. divisor=0 → instant, 1 → 1x, 2 → 2x. */
|
||||
void cdrom_set_speed(int divisor);
|
||||
/* Store configured game speed (applied post-BIOS via cdrom_notify_game_started). */
|
||||
void cdrom_set_game_speed(int divisor);
|
||||
/* Called by fntrace on first game-range dispatch; switches to game speed. */
|
||||
void cdrom_notify_game_started(void);
|
||||
|
||||
/* MMIO read/write (0x1F801800-0x1F801803) */
|
||||
uint32_t cdrom_read(uint32_t addr);
|
||||
|
||||
@@ -48,6 +48,9 @@ extern uint64_t g_fntrace_seq; /* monotonic; index into ring = seq % CAP
|
||||
|
||||
/* Hot path. Called from psx_dispatch on every entry. Inlinable. */
|
||||
void fntrace_record(CPUState* cpu, uint32_t target);
|
||||
/* Register the game's text range for one-shot game-start detection.
|
||||
* First dispatch into [lo, hi) calls cdrom_notify_game_started(). */
|
||||
void fntrace_set_game_range(uint32_t lo, uint32_t hi);
|
||||
|
||||
/* Arm a target filter. arm_count == 0 means "record all" (default).
|
||||
* When arm_count > 0, only dispatches whose target matches one of the
|
||||
|
||||
+26
-4
@@ -86,18 +86,40 @@ static uint8_t xa_stream_file;
|
||||
static uint8_t xa_stream_channel;
|
||||
static int xa_stream_active;
|
||||
|
||||
/* 0 = instant (every delay collapses to 1 cycle), >0 = divisor applied to
|
||||
* all timing constants. Set via cdrom_set_speed() from main before first use. */
|
||||
/* Operating divisor: 1x during BIOS boot, switches to g_game_divisor
|
||||
* when the game's entry point first fires (via cdrom_notify_game_started). */
|
||||
static int g_disc_speed_divisor = 1;
|
||||
/* Configured target speed — applied post-BIOS. */
|
||||
static int g_game_divisor = 1;
|
||||
|
||||
void cdrom_set_speed(int divisor) {
|
||||
g_disc_speed_divisor = divisor;
|
||||
}
|
||||
|
||||
/* Store the configured speed for post-BIOS application. Boot stays at 1x. */
|
||||
void cdrom_set_game_speed(int divisor) {
|
||||
g_game_divisor = divisor;
|
||||
}
|
||||
|
||||
/* Called by fntrace_record on first game-range dispatch. */
|
||||
void cdrom_notify_game_started(void) {
|
||||
g_disc_speed_divisor = g_game_divisor;
|
||||
}
|
||||
|
||||
/* Minimum cycles between CD-ROM IRQs in fast modes. Must be enough for the
|
||||
* interrupt handler to save state, check the IRQ flag, process data, and
|
||||
* return. Too low → interrupt fires before the previous one is processed →
|
||||
* game hangs. 500 cycles ≈ 15µs at 33MHz, still ~900x faster than authentic. */
|
||||
#define CDROM_MIN_DELAY 500
|
||||
|
||||
static int apply_speed(int delay) {
|
||||
if (g_disc_speed_divisor == 0) return 1;
|
||||
/* XA streaming (FMV / CDDA background music): preserve authentic timing.
|
||||
* FMVs interleave XA audio + MDEC video — speeding up sector delivery
|
||||
* would cause both to play faster than the display refresh rate. */
|
||||
if (xa_stream_active) return delay;
|
||||
if (g_disc_speed_divisor == 0) return CDROM_MIN_DELAY;
|
||||
int d = delay / g_disc_speed_divisor;
|
||||
return d < 1 ? 1 : d;
|
||||
return d < CDROM_MIN_DELAY ? CDROM_MIN_DELAY : d;
|
||||
}
|
||||
|
||||
static int sector_delay_cycles(void) {
|
||||
|
||||
@@ -13,6 +13,21 @@ static uint32_t s_arm_targets[FNTRACE_ARM_MAX];
|
||||
static uint32_t s_arm_count = 0;
|
||||
static uint32_t s_arm_record_all = 0; /* opt-in via fntrace_arm(0xFFFFFFFF) */
|
||||
|
||||
/* One-shot game-start detection: fire cdrom_notify_game_started() the first
|
||||
* time the game's entry_pc is dispatched. Using entry_pc (not a range) avoids
|
||||
* false-triggering on the BIOS shell, which runs from RAM 0x30000-0x5B000 —
|
||||
* overlapping the game text range but never at entry_pc. */
|
||||
static uint32_t s_game_entry_phys = 0;
|
||||
static int s_game_started = 0;
|
||||
extern void cdrom_notify_game_started(void);
|
||||
|
||||
void fntrace_set_game_range(uint32_t lo, uint32_t hi) {
|
||||
/* lo is treated as entry_pc; hi is ignored (kept for API compat). */
|
||||
(void)hi;
|
||||
s_game_entry_phys = lo & 0x1FFFFFFFu;
|
||||
s_game_started = 0;
|
||||
}
|
||||
|
||||
static inline int armed_match(uint32_t target) {
|
||||
if (s_arm_record_all) return 1;
|
||||
/* Hot path: when nothing is armed, record nothing. Recording every
|
||||
@@ -27,6 +42,12 @@ static inline int armed_match(uint32_t target) {
|
||||
}
|
||||
|
||||
void fntrace_record(CPUState* cpu, uint32_t target) {
|
||||
if (!s_game_started && s_game_entry_phys != 0) {
|
||||
if ((target & 0x1FFFFFFFu) == s_game_entry_phys) {
|
||||
s_game_started = 1;
|
||||
cdrom_notify_game_started();
|
||||
}
|
||||
}
|
||||
if (!armed_match(target)) return;
|
||||
uint64_t idx = g_fntrace_seq++ & (FNTRACE_RING_CAP - 1);
|
||||
FntraceEntry* e = &g_fntrace_ring[idx];
|
||||
|
||||
+12
-3
@@ -8,6 +8,7 @@
|
||||
#include "cpu_state.h"
|
||||
#include "psx_interpreter.h"
|
||||
#include "cdrom.h"
|
||||
#include "fntrace.h"
|
||||
#include "gpu.h"
|
||||
#include "sio.h"
|
||||
#include "spu.h"
|
||||
@@ -1073,6 +1074,7 @@ int main(int argc, char** argv) {
|
||||
std::string game_name;
|
||||
std::string game_id;
|
||||
std::string disc_speed; /* "1x" | "2x" | "4x" | "instant" */
|
||||
uint32_t game_entry_pc = 0;
|
||||
|
||||
if (game_config_path) {
|
||||
try {
|
||||
@@ -1084,6 +1086,7 @@ int main(int argc, char** argv) {
|
||||
if (gc.runtime.has_window_title) window_title = gc.runtime.window_title;
|
||||
if (gc.runtime.has_debug_port) debug_port = gc.runtime.debug_port;
|
||||
if (gc.runtime.has_disc_speed) disc_speed = gc.runtime.disc_speed;
|
||||
game_entry_pc = gc.entry_pc;
|
||||
std::fprintf(stdout, "psxrecomp: loaded game config %s (%s, %s)\n",
|
||||
game_config_path, game_name.c_str(), game_id.c_str());
|
||||
} catch (const std::exception& ex) {
|
||||
@@ -1130,10 +1133,12 @@ int main(int argc, char** argv) {
|
||||
if (disc_speed == "instant") divisor = 0;
|
||||
else if (disc_speed == "4x") divisor = 4;
|
||||
else if (disc_speed == "2x") divisor = 2;
|
||||
/* Store for post-BIOS application; boot always runs at 1x so the
|
||||
* BIOS disc-init sequence sees correct timing. */
|
||||
cdrom_set_game_speed(divisor);
|
||||
if (divisor != 1)
|
||||
std::fprintf(stdout, "psxrecomp: disc_speed=%s (divisor=%d)\n",
|
||||
disc_speed.c_str(), divisor);
|
||||
cdrom_set_speed(divisor);
|
||||
std::fprintf(stdout, "psxrecomp: disc_speed=%s (applied post-BIOS)\n",
|
||||
disc_speed.c_str());
|
||||
}
|
||||
memcard_init(memcard_dir_str.c_str());
|
||||
std::atexit(memcard_flush_all);
|
||||
@@ -1144,6 +1149,10 @@ int main(int argc, char** argv) {
|
||||
#endif
|
||||
/* Heartbeat always on — see freeze_heartbeat.c rationale. */
|
||||
freeze_heartbeat_start("psx-runtime");
|
||||
/* Register game entry_pc for post-BIOS disc speed switch. Fires once when
|
||||
* the BIOS hands control to the game EXE — not on the BIOS shell. */
|
||||
if (game_entry_pc != 0)
|
||||
fntrace_set_game_range(game_entry_pc, 0);
|
||||
|
||||
/* ---- SDL init ---- */
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
|
||||
|
||||
Reference in New Issue
Block a user