mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Merge pull request #253 from mstan/feat/rewind-default-off
runtime: make local rewind opt-in, and give it a settings key (recomp-ui #38 adds on/off toggle)
This commit is contained in:
@@ -404,6 +404,34 @@ Settings surface. A game migrating Skip FMVs into its built-in mod catalog sets
|
||||
it to false. The runtime then hides the Settings row, ignores stale persisted
|
||||
values, and leaves activation to the selected trusted plugin.
|
||||
|
||||
### Local rewind (`settings.toml`)
|
||||
|
||||
Rewind is a player setting, not a game one: it lives in the user's
|
||||
`settings.toml` beside the runtime executable, under `[video]`.
|
||||
|
||||
```toml
|
||||
[video]
|
||||
rewind = false # off by default — see below
|
||||
rewind_depth = 50 # snapshots kept: 50 / 100 / 150 / 200
|
||||
rewind_interval = 15 # frames between snapshots: 1 / 4 / 8 / 12 / 15
|
||||
```
|
||||
|
||||
**`rewind` defaults to `false`.** The ring holds whole *machine* snapshots —
|
||||
2 MB main RAM + 1 MB VRAM + 512 KB SPU RAM, stored uncompressed — and captures
|
||||
one every `rewind_interval` frames (denser, toward 4, while an FMV runs). At
|
||||
the default depth that is a few hundred MB of resident memory plus a periodic
|
||||
multi-megabyte copy, which is not a cost to charge every host for a feature a
|
||||
session may never open. Turning it on is one click in the launcher's Display
|
||||
card; nothing is allocated until it is.
|
||||
|
||||
`PSX_REWIND=1` / `PSX_REWIND=0` override the setting either way, and
|
||||
`PSX_REWIND_DEPTH` / `PSX_REWIND_INTERVAL` / `PSX_REWIND_FMV_INTERVAL` override
|
||||
the tuning. A build configured with `-DPSX_REWIND=OFF` has no rewind at all and
|
||||
ignores all of these.
|
||||
|
||||
Netplay rollback is a separate subsystem with its own ring and is unaffected by
|
||||
this setting; rewind is in fact suppressed while a netplay session is active.
|
||||
|
||||
Bezel artwork is intentionally not a `[video]` key. It is exposed as the
|
||||
disabled-by-default `psx.presentation.bezel` mod package, which draws a
|
||||
user-selected image resource behind the game image in OpenGL letterbox or
|
||||
|
||||
@@ -438,6 +438,21 @@ if(BUILD_TESTING)
|
||||
add_test(NAME video_enhancement_settings_test
|
||||
COMMAND video_enhancement_settings_test)
|
||||
|
||||
# [video] rewind opt-in plumbing (default off + settings round trip).
|
||||
add_executable(rewind_settings_test
|
||||
tests/rewind_settings_test.cpp
|
||||
src/config_loader.cpp
|
||||
src/ps1_exe_parser.cpp
|
||||
)
|
||||
target_include_directories(rewind_settings_test PRIVATE
|
||||
include
|
||||
src
|
||||
lib/fmt/include
|
||||
lib/toml11
|
||||
)
|
||||
target_link_libraries(rewind_settings_test PRIVATE fmt)
|
||||
add_test(NAME rewind_settings_test COMMAND rewind_settings_test)
|
||||
|
||||
# Needs bios/SCPH1001.toml — run from the framework root.
|
||||
add_test(NAME bios_address_model_test
|
||||
COMMAND bios_address_model_test
|
||||
|
||||
@@ -2338,6 +2338,10 @@ UserSettings load_user_settings(const fs::path& path) {
|
||||
s.adaptive_view = toml::find<bool>(v, "adaptive_view");
|
||||
s.has_adaptive_view = true;
|
||||
});
|
||||
if (v.contains("rewind")) try_get([&]{
|
||||
s.rewind = toml::find<bool>(v, "rewind");
|
||||
s.has_rewind = true;
|
||||
});
|
||||
if (v.contains("rewind_depth")) try_get([&]{
|
||||
int d = toml::find<int>(v, "rewind_depth");
|
||||
static const int opts[4] = {50, 100, 150, 200};
|
||||
@@ -2611,6 +2615,8 @@ bool save_user_settings(const fs::path& path, const UserSettings& s) {
|
||||
f << "aspect_ratio = \"" << s.aspect_num << ":" << s.aspect_den << "\"\n";
|
||||
if (s.has_adaptive_view)
|
||||
f << "adaptive_view = " << (s.adaptive_view ? "true" : "false") << "\n";
|
||||
if (s.has_rewind)
|
||||
f << "rewind = " << (s.rewind ? "true" : "false") << "\n";
|
||||
if (s.has_rewind_depth)
|
||||
f << "rewind_depth = " << s.rewind_depth << "\n";
|
||||
if (s.has_rewind_interval)
|
||||
|
||||
@@ -1215,6 +1215,11 @@ struct UserSettings {
|
||||
bool has_aspect_ratio = false; int aspect_num = 4; // display aspect W:H
|
||||
int aspect_den = 3; // (4:3 = native)
|
||||
bool has_adaptive_view = false; bool adaptive_view = false;
|
||||
// [video] rewind: local rewind ring on/off. OFF by default — the ring
|
||||
// holds whole-machine snapshots (2 MB RAM + 1 MB VRAM + 512 KB SPU RAM
|
||||
// each) and captures on a frame cadence, so it is opt-in rather than a
|
||||
// cost every host pays. Env PSX_REWIND still outranks this.
|
||||
bool has_rewind = false; bool rewind = false;
|
||||
// [video] rewind_depth: local rewind snap-ring capacity. UI offers
|
||||
// 50 / 100 / 150 / 200 (default 50). Runtime clamps + snaps to those steps.
|
||||
bool has_rewind_depth = false; int rewind_depth = 50;
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/* rewind_settings_test — [video] rewind opt-in plumbing.
|
||||
*
|
||||
* Local rewind keeps whole-machine snapshots (2 MB main RAM + 1 MB VRAM +
|
||||
* 512 KB SPU RAM, uncompressed) in a ring and captures one every
|
||||
* rewind_interval frames. At the default depth that is a few hundred MB
|
||||
* resident plus a periodic multi-megabyte copy, so the feature is opt-in and
|
||||
* nothing is allocated until a player asks for it.
|
||||
*
|
||||
* What this pins is the decision layer, not the ring itself:
|
||||
*
|
||||
* 1. `rewind` defaults OFF, and stays off when settings.toml says nothing;
|
||||
* 2. settings.toml turns it on, and can spell an explicit off;
|
||||
* 3. an absent key stays absent, so layering cannot force a value;
|
||||
* 4. save_user_settings round-trips it — a launcher save that dropped the key
|
||||
* would read as "the toggle does nothing";
|
||||
* 5. the tuning keys stay independent of the enable.
|
||||
*/
|
||||
#include "config_loader.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static int failures = 0;
|
||||
|
||||
static void check(bool cond, const char* what) {
|
||||
if (!cond) {
|
||||
std::fprintf(stderr, "FAIL: %s\n", what);
|
||||
++failures;
|
||||
}
|
||||
}
|
||||
|
||||
static fs::path write_temp(const std::string& name, const std::string& body) {
|
||||
fs::path p = fs::temp_directory_path() / name;
|
||||
std::ofstream f(p, std::ios::binary | std::ios::trunc);
|
||||
f << body;
|
||||
return p;
|
||||
}
|
||||
|
||||
/* The default is the whole point of the change: a host that has never opened
|
||||
* the settings window must not be paying for the ring. */
|
||||
static void test_default_off() {
|
||||
PSXRecompV4::UserSettings fresh;
|
||||
check(!fresh.rewind, "rewind defaults OFF in UserSettings");
|
||||
check(!fresh.has_rewind, "a fresh UserSettings has no opinion on rewind");
|
||||
|
||||
fs::path p = write_temp("psxrecomp_rewind_absent.toml",
|
||||
"[video]\n"
|
||||
"supersampling = 2\n");
|
||||
auto us = PSXRecompV4::load_user_settings(p);
|
||||
check(!us.parse_error, "settings.toml parses");
|
||||
check(!us.has_rewind, "absent rewind leaves has_rewind false");
|
||||
check(!us.rewind, "absent rewind leaves the value off");
|
||||
fs::remove(p);
|
||||
}
|
||||
|
||||
static void test_opt_in_and_explicit_off() {
|
||||
fs::path on = write_temp("psxrecomp_rewind_on.toml",
|
||||
"[video]\n"
|
||||
"rewind = true\n");
|
||||
auto us_on = PSXRecompV4::load_user_settings(on);
|
||||
check(us_on.has_rewind && us_on.rewind,
|
||||
"settings.toml rewind = true is read");
|
||||
fs::remove(on);
|
||||
|
||||
/* Explicit false must be distinguishable from absent: one is a player who
|
||||
* turned it off, the other is a file that never mentioned it. */
|
||||
fs::path off = write_temp("psxrecomp_rewind_off.toml",
|
||||
"[video]\n"
|
||||
"rewind = false\n");
|
||||
auto us_off = PSXRecompV4::load_user_settings(off);
|
||||
check(us_off.has_rewind, "explicit rewind = false still sets has_rewind");
|
||||
check(!us_off.rewind, "explicit rewind = false reads as off");
|
||||
fs::remove(off);
|
||||
}
|
||||
|
||||
static void test_round_trip() {
|
||||
PSXRecompV4::UserSettings out;
|
||||
out.rewind = true; out.has_rewind = true;
|
||||
out.rewind_depth = 100; out.has_rewind_depth = true;
|
||||
out.rewind_interval = 4; out.has_rewind_interval = true;
|
||||
|
||||
fs::path p = fs::temp_directory_path() / "psxrecomp_rewind_roundtrip.toml";
|
||||
check(PSXRecompV4::save_user_settings(p, out), "save_user_settings writes");
|
||||
|
||||
auto back = PSXRecompV4::load_user_settings(p);
|
||||
check(!back.parse_error, "written settings.toml re-parses");
|
||||
check(back.has_rewind && back.rewind,
|
||||
"rewind survives a save/load round trip");
|
||||
check(back.has_rewind_depth && back.rewind_depth == 100,
|
||||
"rewind_depth survives a save/load round trip");
|
||||
check(back.has_rewind_interval && back.rewind_interval == 4,
|
||||
"rewind_interval survives a save/load round trip");
|
||||
fs::remove(p);
|
||||
}
|
||||
|
||||
/* Depth/interval describe a ring that only exists once rewind is on, but they
|
||||
* are still their own keys: a player may tune them while it is off and expect
|
||||
* the values to be there when they turn it on. */
|
||||
static void test_tuning_independent_of_enable() {
|
||||
fs::path p = write_temp("psxrecomp_rewind_tuning_only.toml",
|
||||
"[video]\n"
|
||||
"rewind_depth = 150\n"
|
||||
"rewind_interval = 8\n");
|
||||
auto us = PSXRecompV4::load_user_settings(p);
|
||||
check(!us.has_rewind, "tuning keys alone do not imply an enable");
|
||||
check(us.has_rewind_depth && us.rewind_depth == 150,
|
||||
"rewind_depth is read without rewind being set");
|
||||
check(us.has_rewind_interval && us.rewind_interval == 8,
|
||||
"rewind_interval is read without rewind being set");
|
||||
fs::remove(p);
|
||||
}
|
||||
|
||||
int main() {
|
||||
test_default_off();
|
||||
test_opt_in_and_explicit_off();
|
||||
test_round_trip();
|
||||
test_tuning_independent_of_enable();
|
||||
|
||||
if (failures) {
|
||||
std::fprintf(stderr, "rewind_settings_test: %d failure(s)\n", failures);
|
||||
return 1;
|
||||
}
|
||||
std::printf("PASS: [video] rewind opt-in plumbing\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -10,10 +10,16 @@
|
||||
* Keyboard: F8 (config.ini [KeyMap] Rewind). Controller: View/Back or L3.
|
||||
* While open: D-pad / left-stick Left/Right, Cross load, Circle/Back close.
|
||||
*
|
||||
* Env: PSX_REWIND=0 disables; PSX_REWIND_INTERVAL (1/4/8/12/15, default 15);
|
||||
* OFF by default. The ring holds up to 200 whole-machine snapshots (2 MB RAM +
|
||||
* 1 MB VRAM + 512 KB SPU RAM each) and captures one every rewind_interval
|
||||
* frames, which is not a cost to hand a low-end host for a feature a session
|
||||
* may never open. Turn it on in settings.toml or the launcher's Display card.
|
||||
*
|
||||
* Env: PSX_REWIND=1 enables, PSX_REWIND=0 disables; either outranks the UI.
|
||||
* PSX_REWIND_INTERVAL (1/4/8/12/15, default 15);
|
||||
* PSX_REWIND_FMV_INTERVAL (frames while depth24/MDEC/XA, default 4);
|
||||
* PSX_REWIND_DEPTH (50/100/150/200, default 50, max 200).
|
||||
* settings.toml [video] rewind_depth / rewind_interval (env still wins).
|
||||
* settings.toml [video] rewind / rewind_depth / rewind_interval (env still wins).
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
@@ -26,6 +32,9 @@ 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);
|
||||
/* Settings/launcher preference. Call before psx_rewind_configure(); to change
|
||||
* it mid-session, call this then configure() (on) or shutdown() (off). */
|
||||
void psx_rewind_set_enabled(int enabled);
|
||||
|
||||
void psx_rewind_configure(uint32_t bios_checksum, uint32_t entry_pc);
|
||||
void psx_rewind_shutdown(void);
|
||||
|
||||
@@ -1163,6 +1163,9 @@ static bool g_video_win_w_explicit = false; /* user chose a width */
|
||||
static bool g_audio_spu_hq = false; /* SPU float-shadow (env overrides) */
|
||||
static int g_audio_freq = 44100; /* host device request */
|
||||
static int g_auto_skip_fmv = 0; /* skip FMVs the instant they're detected */
|
||||
/* Local rewind is opt-in: the snap ring is whole-machine state on a frame
|
||||
* cadence, too much to charge every host for a feature many never open. */
|
||||
static int g_rewind_enabled = 0;
|
||||
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_hotkey_pad_rewind = 1272; /* select + r3 */
|
||||
@@ -11447,6 +11450,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
if (us.has_audio_freq) g_audio_freq = us.audio_freq;
|
||||
if (us.has_spu_hq) g_audio_spu_hq = us.spu_hq;
|
||||
if (us.has_rewind) g_rewind_enabled = us.rewind ? 1 : 0;
|
||||
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_hotkey_pad_rewind)
|
||||
@@ -11903,6 +11907,7 @@ int main(int argc, char** argv) {
|
||||
seed.aspect_den = g_video_aspect_den; seed.has_aspect_ratio = true;
|
||||
seed.audio_freq = g_audio_freq; seed.has_audio_freq = true;
|
||||
seed.spu_hq = g_audio_spu_hq; seed.has_spu_hq = true;
|
||||
seed.rewind = g_rewind_enabled != 0; seed.has_rewind = true;
|
||||
seed.rewind_depth = g_rewind_depth; seed.has_rewind_depth = true;
|
||||
seed.rewind_interval = g_rewind_interval; seed.has_rewind_interval = true;
|
||||
seed.hotkey_pad_rewind = g_hotkey_pad_rewind;
|
||||
@@ -12075,6 +12080,7 @@ int main(int argc, char** argv) {
|
||||
ls.frame_interp = seed.frame_interpolation ? 1 : 0;
|
||||
ls.frame_interp_fps = seed.frame_interpolation_fps;
|
||||
ls.spu_hq = seed.spu_hq ? 1 : 0;
|
||||
ls.rewind_enabled = seed.rewind ? 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.assist_pad_bind[PSX_ASSIST_BIND_REWIND] =
|
||||
@@ -12315,6 +12321,8 @@ int main(int argc, char** argv) {
|
||||
seed.frame_interpolation_fps = ls.frame_interp_fps; seed.has_frame_interpolation_fps = true;
|
||||
seed.audio_freq = ls.audio_freq; seed.has_audio_freq = true;
|
||||
seed.spu_hq = ls.spu_hq != 0; seed.has_spu_hq = true;
|
||||
seed.rewind = ls.rewind_enabled != 0;
|
||||
seed.has_rewind = 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;
|
||||
@@ -12528,6 +12536,7 @@ int main(int argc, char** argv) {
|
||||
g_video_aspect_den = seed.aspect_den;
|
||||
g_audio_freq = seed.audio_freq;
|
||||
g_audio_spu_hq = seed.spu_hq;
|
||||
g_rewind_enabled = seed.has_rewind ? (seed.rewind ? 1 : 0) : 0;
|
||||
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
|
||||
@@ -13628,6 +13637,7 @@ session_reboot:
|
||||
savestate_configure(memcard_dir.string().c_str(),
|
||||
memory_get_bios_checksum(), game_entry_pc,
|
||||
bios_token, openbios_ws);
|
||||
psx_rewind_set_enabled(g_rewind_enabled);
|
||||
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);
|
||||
@@ -13979,6 +13989,7 @@ soft_return_lobby:
|
||||
ls.spu_hq = g_audio_spu_hq ? 1 : 0;
|
||||
ls.auto_skip_fmv = (skip_fmv_offered && g_auto_skip_fmv) ? 1 : 0;
|
||||
ls.turbo_loads = (turbo_loads_offered && g_turbo_loads_enabled) ? 1 : 0;
|
||||
ls.rewind_enabled = g_rewind_enabled;
|
||||
ls.rewind_depth = g_rewind_depth;
|
||||
ls.rewind_interval = g_rewind_interval;
|
||||
ls.assist_pad_bind[PSX_ASSIST_BIND_REWIND] =
|
||||
@@ -14239,6 +14250,8 @@ soft_return_lobby:
|
||||
us.has_audio_freq = true;
|
||||
us.spu_hq = ls.spu_hq != 0;
|
||||
us.has_spu_hq = true;
|
||||
us.rewind = ls.rewind_enabled != 0;
|
||||
us.has_rewind = 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;
|
||||
@@ -14306,6 +14319,18 @@ soft_return_lobby:
|
||||
g_rewind_interval = ls.rewind_interval;
|
||||
psx_rewind_set_interval((uint32_t)g_rewind_interval);
|
||||
}
|
||||
/* Applied live so turning rewind off frees the ring now rather
|
||||
* than next launch — reclaiming it is the point of the setting.
|
||||
* shutdown() also closes the overlay and drops a pending load. */
|
||||
if ((ls.rewind_enabled ? 1 : 0) != g_rewind_enabled) {
|
||||
g_rewind_enabled = ls.rewind_enabled ? 1 : 0;
|
||||
psx_rewind_set_enabled(g_rewind_enabled);
|
||||
if (g_rewind_enabled)
|
||||
psx_rewind_configure(memory_get_bios_checksum(),
|
||||
game_entry_pc);
|
||||
else
|
||||
psx_rewind_shutdown();
|
||||
}
|
||||
g_hotkey_pad_rewind = normalize_hotkey_pad_binding(
|
||||
ls.assist_pad_bind[PSX_ASSIST_BIND_REWIND],
|
||||
PSX_HOTKEY_PAD_SELECT_R3);
|
||||
|
||||
@@ -104,6 +104,7 @@ static const uint8_t FONT8[59][8] = {
|
||||
|
||||
void psx_rewind_set_depth(uint32_t depth) { (void)depth; }
|
||||
void psx_rewind_set_interval(uint32_t interval) { (void)interval; }
|
||||
void psx_rewind_set_enabled(int enabled) { (void)enabled; }
|
||||
void psx_rewind_configure(uint32_t bios_checksum, uint32_t entry_pc)
|
||||
{
|
||||
(void)bios_checksum;
|
||||
@@ -154,6 +155,7 @@ 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 int s_enabled_pref = -1; /* -1 = unset; else from settings.toml / launcher */
|
||||
static uint32_t s_frame;
|
||||
static uint32_t s_last_capture_frame = 0xffffffffu;
|
||||
static int s_capture_due;
|
||||
@@ -242,15 +244,35 @@ void psx_rewind_set_interval(uint32_t interval)
|
||||
s_interval = (uint32_t)s_interval_pref;
|
||||
}
|
||||
|
||||
void psx_rewind_set_enabled(int enabled)
|
||||
{
|
||||
const char *e = getenv("PSX_REWIND");
|
||||
s_enabled_pref = enabled ? 1 : 0;
|
||||
/* PSX_REWIND is the operator's override and outranks the UI, so once it
|
||||
* has spoken the preference only records intent. Otherwise apply now; the
|
||||
* caller re-arms (configure) or tears down (shutdown) to match. */
|
||||
if (e && e[0])
|
||||
return;
|
||||
if (s_enabled >= 0)
|
||||
s_enabled = s_enabled_pref;
|
||||
}
|
||||
|
||||
static int rewind_wanted(void)
|
||||
{
|
||||
const char *e;
|
||||
if (s_enabled < 0) {
|
||||
/* Off unless asked for. The ring holds up to RW_MAX_DEPTH machine
|
||||
* snapshots (2 MB RAM + 1 MB VRAM + 512 KB SPU RAM each) and captures
|
||||
* one every s_interval frames, which is real cost on a low-end host
|
||||
* for a feature most sessions never open. Order: env wins, then the
|
||||
* settings.toml / launcher preference, then off. */
|
||||
e = getenv("PSX_REWIND");
|
||||
if (e && e[0] == '0')
|
||||
s_enabled = 0;
|
||||
if (e && e[0])
|
||||
s_enabled = (e[0] == '0') ? 0 : 1;
|
||||
else if (s_enabled_pref >= 0)
|
||||
s_enabled = s_enabled_pref;
|
||||
else
|
||||
s_enabled = 1;
|
||||
s_enabled = 0;
|
||||
{
|
||||
uint32_t iv_def = s_interval_pref > 0 ? (uint32_t)s_interval_pref
|
||||
: RW_DEF_INTERVAL;
|
||||
@@ -532,8 +554,13 @@ void psx_rewind_poll(CPUState *cpu, uint32_t resume_pc)
|
||||
|
||||
int psx_rewind_toggle(void)
|
||||
{
|
||||
if (!psx_rewind_enabled())
|
||||
if (!psx_rewind_enabled()) {
|
||||
/* Rewind is off by default, so this is now the common case: say why
|
||||
* rather than leaving F8 / Select+R3 looking broken. Silent only when
|
||||
* the feature was compiled out, where there is nothing to turn on. */
|
||||
host_osd_push("Rewind is off — enable it in Settings", 1800);
|
||||
return 0;
|
||||
}
|
||||
if (psx_netplay_active()) {
|
||||
host_osd_push("Rewind off during netplay", 1500);
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user