rewind support

This commit is contained in:
Alex Vanderveen
2026-08-12 03:51:50 -04:00
parent 31e16f4e40
commit 554476457f
6 changed files with 84 additions and 15 deletions
+15 -1
View File
@@ -2220,7 +2220,7 @@ UserSettings load_user_settings(const fs::path& path) {
});
if (v.contains("rewind_depth")) try_get([&]{
int d = toml::find<int>(v, "rewind_depth");
static const int opts[4] = {25, 50, 75, 100};
static const int opts[4] = {50, 100, 150, 200};
int best = opts[0];
int best_d = d > best ? d - best : best - d;
for (int i = 1; i < 4; ++i) {
@@ -2230,6 +2230,18 @@ UserSettings load_user_settings(const fs::path& path) {
s.rewind_depth = best;
s.has_rewind_depth = true;
});
if (v.contains("rewind_interval")) try_get([&]{
int d = toml::find<int>(v, "rewind_interval");
static const int opts[5] = {1, 4, 8, 12, 15};
int best = opts[0];
int best_d = d > best ? d - best : best - d;
for (int i = 1; i < 5; ++i) {
int dd = d > opts[i] ? d - opts[i] : opts[i] - d;
if (dd < best_d) { best_d = dd; best = opts[i]; }
}
s.rewind_interval = best;
s.has_rewind_interval = true;
});
}
if (doc.contains("audio")) {
const toml::value& a = toml::find(doc, "audio");
@@ -2455,6 +2467,8 @@ bool save_user_settings(const fs::path& path, const UserSettings& s) {
f << "adaptive_view = " << (s.adaptive_view ? "true" : "false") << "\n";
if (s.has_rewind_depth)
f << "rewind_depth = " << s.rewind_depth << "\n";
if (s.has_rewind_interval)
f << "rewind_interval = " << s.rewind_interval << "\n";
f << "\n[audio]\n";
if (s.has_spu_hq)
f << "spu_hq = " << (s.spu_hq ? "true" : "false") << "\n";
+4 -1
View File
@@ -1104,8 +1104,11 @@ struct UserSettings {
int aspect_den = 3; // (4:3 = native)
bool has_adaptive_view = false; bool adaptive_view = false;
// [video] rewind_depth: local rewind snap-ring capacity. UI offers
// 25 / 50 / 75 / 100 (default 50). Runtime clamps + snaps to those steps.
// 50 / 100 / 150 / 200 (default 50). Runtime clamps + snaps to those steps.
bool has_rewind_depth = false; int rewind_depth = 50;
// [video] rewind_interval: frames between local rewind snaps. UI offers
// 1 / 4 / 8 / 12 / 15 (default 15).
bool has_rewind_interval = false; int rewind_interval = 15;
// [audio]
bool has_spu_hq = false; bool spu_hq = false;
// [bios] / [disc] / [memcard]
+4 -3
View File
@@ -10,10 +10,10 @@
* Keyboard: F8 (config.ini [KeyMap] Rewind). Controller: View/Back or L3.
* While open: D-pad / left-stick Left/Right, Cross/A load, Circle/B/Back close.
*
* Env: PSX_REWIND=0 disables; PSX_REWIND_INTERVAL (frames, default 15);
* Env: PSX_REWIND=0 disables; PSX_REWIND_INTERVAL (1/4/8/12/15, default 15);
* PSX_REWIND_FMV_INTERVAL (frames while depth24/MDEC/XA, default 4);
* PSX_REWIND_DEPTH (snaps; UI offers 25/50/75/100, default 50, max 100).
* settings.toml [video] rewind_depth overrides the default (env still wins).
* PSX_REWIND_DEPTH (50/100/150/200, default 50, max 200).
* settings.toml [video] rewind_depth / rewind_interval (env still wins).
*/
#include <stdint.h>
@@ -25,6 +25,7 @@ extern "C" {
/* Snap count kept in the local rewind ring. UI values: 25 / 50 / 75 / 100. */
void psx_rewind_set_depth(uint32_t depth);
void psx_rewind_set_interval(uint32_t interval);
void psx_rewind_configure(uint32_t bios_checksum, uint32_t entry_pc);
void psx_rewind_shutdown(void);
+6 -4
View File
@@ -1001,8 +1001,10 @@ function(psxrecomp_add_runtime_target target)
endif()
# Per-game netplay/local pad ceiling. Default 2 (MotK / dual-shock path).
# Games that need multitap N-player (e.g. Bomberman Party Edition) pass
# MAX_PLAYERS 5. Clamped to the framework absolute max of 5.
# Single-player titles (Tomba, Ape Escape, …) pass MAX_PLAYERS 1 so rewind
# / rbengine still link without advertising multiplayer. Multitap N-player
# (Bomberman Party Edition) uses 5; dual SCPH-1070 uses 8. Range matches
# sio.h (1..8).
if(NOT PSXRT_MAX_PLAYERS)
if(DEFINED PSX_MAX_PLAYERS AND NOT PSX_MAX_PLAYERS STREQUAL "")
set(PSXRT_MAX_PLAYERS "${PSX_MAX_PLAYERS}")
@@ -1010,9 +1012,9 @@ function(psxrecomp_add_runtime_target target)
set(PSXRT_MAX_PLAYERS 2)
endif()
endif()
if(PSXRT_MAX_PLAYERS LESS 2 OR PSXRT_MAX_PLAYERS GREATER 5)
if(PSXRT_MAX_PLAYERS LESS 1 OR PSXRT_MAX_PLAYERS GREATER 8)
message(FATAL_ERROR
"MAX_PLAYERS must be in 2..5 (got ${PSXRT_MAX_PLAYERS})")
"MAX_PLAYERS must be in 1..8 (got ${PSXRT_MAX_PLAYERS})")
endif()
message(STATUS "psxrecomp ${target}: PSX_MAX_PLAYERS=${PSXRT_MAX_PLAYERS}")
+16 -1
View File
@@ -1059,7 +1059,8 @@ static int g_video_screen = 0; /* 0=raw,1=crt,2=composite,3=trinitr
static int g_video_win_w = 1280; /* window width (height follows aspect) */
static bool g_audio_spu_hq = false; /* SPU float-shadow (env overrides) */
static int g_auto_skip_fmv = 0; /* skip FMVs the instant they're detected */
static int g_rewind_depth = 50; /* local rewind snap count (25/50/75/100) */
static int g_rewind_depth = 50; /* local rewind snap count (50/100/150/200) */
static int g_rewind_interval = 15; /* frames between snaps (1/4/8/12/15) */
static int g_headless = 0; /* debug/CI frontend: no SDL window/audio */
/* FMV instant-skip via the game's OWN end-of-movie path. Tomba's MDEC player
* (FUN_8001efe8) tears a movie down when the streamed frame number reaches that
@@ -10256,6 +10257,7 @@ int main(int argc, char** argv) {
}
if (us.has_spu_hq) g_audio_spu_hq = us.spu_hq;
if (us.has_rewind_depth) g_rewind_depth = us.rewind_depth;
if (us.has_rewind_interval) g_rewind_interval = us.rewind_interval;
if (us.has_bios_path && !bios_from_cli && !us.bios_path.empty()) {
settings_bios_storage = us.bios_path.string();
bios_path = settings_bios_storage.c_str();
@@ -10663,6 +10665,7 @@ int main(int argc, char** argv) {
seed.aspect_den = g_video_aspect_den; seed.has_aspect_ratio = true;
seed.spu_hq = g_audio_spu_hq; seed.has_spu_hq = true;
seed.rewind_depth = g_rewind_depth; seed.has_rewind_depth = true;
seed.rewind_interval = g_rewind_interval; seed.has_rewind_interval = true;
seed.skip_launcher = skip_launcher_setting; seed.has_skip_launcher = true;
if (has_netplay_player_name) {
seed.netplay_player_name = netplay_player_name;
@@ -10816,6 +10819,7 @@ int main(int argc, char** argv) {
ls.frame_interp_fps = seed.frame_interpolation_fps;
ls.spu_hq = seed.spu_hq ? 1 : 0;
ls.rewind_depth = seed.rewind_depth > 0 ? seed.rewind_depth : 50;
ls.rewind_interval = seed.rewind_interval > 0 ? seed.rewind_interval : 15;
ls.auto_skip_fmv = seed.auto_skip_fmv ? 1 : 0;
ls.turbo_loads = seed.turbo_loads ? 1 : 0;
/* Localization: index of resolved_language within lang_menu_options
@@ -11045,6 +11049,8 @@ int main(int argc, char** argv) {
seed.spu_hq = ls.spu_hq != 0; seed.has_spu_hq = true;
seed.rewind_depth = ls.rewind_depth > 0 ? ls.rewind_depth : 50;
seed.has_rewind_depth = true;
seed.rewind_interval = ls.rewind_interval > 0 ? ls.rewind_interval : 15;
seed.has_rewind_interval = true;
seed.auto_skip_fmv = ls.auto_skip_fmv != 0;
seed.has_auto_skip_fmv = skip_fmv_offered;
seed.turbo_loads = ls.turbo_loads != 0;
@@ -11244,6 +11250,8 @@ int main(int argc, char** argv) {
g_audio_spu_hq = seed.spu_hq;
g_rewind_depth = seed.has_rewind_depth && seed.rewind_depth > 0
? seed.rewind_depth : 50;
g_rewind_interval = seed.has_rewind_interval && seed.rewind_interval > 0
? seed.rewind_interval : 15;
skip_launcher_setting = seed.skip_launcher;
if (seed.has_bios_path) {
settings_bios_storage = seed.bios_path.string();
@@ -12231,6 +12239,7 @@ session_reboot:
memory_get_bios_checksum(), game_entry_pc,
bios_token, openbios_ws);
psx_rewind_set_depth((uint32_t)g_rewind_depth);
psx_rewind_set_interval((uint32_t)g_rewind_interval);
psx_rewind_configure(memory_get_bios_checksum(), game_entry_pc);
/* Headless / agent load: PSX_LOAD_SLOT=N stages F1..F12 load (0..11)
* at the next safe block boundary after boot. */
@@ -12826,6 +12835,8 @@ soft_return_lobby:
us.has_spu_hq = true;
us.rewind_depth = ls.rewind_depth > 0 ? ls.rewind_depth : 50;
us.has_rewind_depth = true;
us.rewind_interval = ls.rewind_interval > 0 ? ls.rewind_interval : 15;
us.has_rewind_interval = true;
us.auto_skip_fmv = ls.auto_skip_fmv != 0;
us.has_auto_skip_fmv = skip_fmv_offered;
us.turbo_loads = ls.turbo_loads != 0;
@@ -12875,6 +12886,10 @@ soft_return_lobby:
g_rewind_depth = ls.rewind_depth;
psx_rewind_set_depth((uint32_t)g_rewind_depth);
}
if (ls.rewind_interval > 0) {
g_rewind_interval = ls.rewind_interval;
psx_rewind_set_interval((uint32_t)g_rewind_interval);
}
switch (ls.aspect_index) {
case 2: g_video_aspect_num = 21; g_video_aspect_den = 9; break;
case 1: g_video_aspect_num = 16; g_video_aspect_den = 9; break;
+39 -5
View File
@@ -24,7 +24,7 @@
#define RW_THUMB_W 128
#define RW_THUMB_H 96
#define RW_MAX_DEPTH 100
#define RW_MAX_DEPTH 200
#define RW_DEF_DEPTH 50
#define RW_DEF_INTERVAL 15
/* Match netplay §96 FMV media snaps (default 4; MEDIA_KF uses 2). */
@@ -102,6 +102,7 @@ static const uint8_t FONT8[59][8] = {
#if !defined(PSX_HAS_RBENGINE_SNAP)
void psx_rewind_set_depth(uint32_t depth) { (void)depth; }
void psx_rewind_set_interval(uint32_t interval) { (void)interval; }
void psx_rewind_configure(uint32_t bios_checksum, uint32_t entry_pc)
{
(void)bios_checksum;
@@ -151,6 +152,7 @@ static uint32_t s_interval = RW_DEF_INTERVAL;
static uint32_t s_fmv_interval = RW_DEF_FMV_INTERVAL;
static uint32_t s_depth = RW_DEF_DEPTH;
static int s_depth_pref = -1; /* -1 = unset; else from settings.toml / launcher */
static int s_interval_pref = -1;
static uint32_t s_frame;
static uint32_t s_last_capture_frame = 0xffffffffu;
static int s_capture_due;
@@ -190,10 +192,10 @@ static uint32_t env_u32(const char *name, uint32_t def, uint32_t lo, uint32_t hi
return v;
}
/* UI offers 25/50/75/100; nearest match keeps hand-edited toml values sane. */
/* UI offers 50/100/150/200; nearest match keeps hand-edited toml values sane. */
static uint32_t normalize_rewind_depth(uint32_t v)
{
static const uint32_t opts[4] = {25u, 50u, 75u, 100u};
static const uint32_t opts[4] = {50u, 100u, 150u, 200u};
uint32_t best = opts[0];
uint32_t best_d = v > best ? v - best : best - v;
unsigned i;
@@ -207,6 +209,23 @@ static uint32_t normalize_rewind_depth(uint32_t v)
return best;
}
/* UI offers 1/4/8/12/15. */
static uint32_t normalize_rewind_interval(uint32_t v)
{
static const uint32_t opts[5] = {1u, 4u, 8u, 12u, 15u};
uint32_t best = opts[0];
uint32_t best_d = v > best ? v - best : best - v;
unsigned i;
for (i = 1; i < 5; i++) {
uint32_t d = v > opts[i] ? v - opts[i] : opts[i] - v;
if (d < best_d) {
best_d = d;
best = opts[i];
}
}
return best;
}
void psx_rewind_set_depth(uint32_t depth)
{
s_depth_pref = (int)normalize_rewind_depth(depth);
@@ -215,6 +234,13 @@ void psx_rewind_set_depth(uint32_t depth)
s_depth = (uint32_t)s_depth_pref;
}
void psx_rewind_set_interval(uint32_t interval)
{
s_interval_pref = (int)normalize_rewind_interval(interval);
if (s_enabled >= 0)
s_interval = (uint32_t)s_interval_pref;
}
static int rewind_wanted(void)
{
const char *e;
@@ -224,7 +250,12 @@ static int rewind_wanted(void)
s_enabled = 0;
else
s_enabled = 1;
s_interval = env_u32("PSX_REWIND_INTERVAL", RW_DEF_INTERVAL, 1u, 60u);
{
uint32_t iv_def = s_interval_pref > 0 ? (uint32_t)s_interval_pref
: RW_DEF_INTERVAL;
s_interval = normalize_rewind_interval(
env_u32("PSX_REWIND_INTERVAL", iv_def, 1u, 60u));
}
s_fmv_interval =
env_u32("PSX_REWIND_FMV_INTERVAL", RW_DEF_FMV_INTERVAL, 1u, 16u);
{
@@ -251,7 +282,10 @@ static int rewind_fmv_media_active(void)
static uint32_t rewind_capture_interval(void)
{
return rewind_fmv_media_active() ? s_fmv_interval : s_interval;
if (!rewind_fmv_media_active())
return s_interval;
/* FMV densifies toward s_fmv_interval but never sparser than the user pick. */
return s_interval < s_fmv_interval ? s_interval : s_fmv_interval;
}
static int resume_pc_ok(uint32_t pc)