Merge pull request #307 from kerokline/feat/fast-forward-pad

runtime: controller host shortcut for fast-forward ([hotkeys] fast_forward_pad)
This commit is contained in:
Matt Stanley
2026-09-03 07:30:35 -07:00
committed by GitHub
5 changed files with 73 additions and 4 deletions
+4 -1
View File
@@ -755,7 +755,10 @@ The runtime models **authentic 1× CD-ROM timing by default** — the same read
seek delays as real hardware. On top of that faithful baseline, load-time
acceleration is **opt-in**, per game, so the accurate path is never compromised:
- **Turbo** — a hold-to-fast-forward key that compresses loads on demand.
- **Turbo** — a hold-to-fast-forward key that compresses loads on demand
(keyboard `[KeyMap] Turbo`, default Tab; controller `[hotkeys]
fast_forward_pad`, default Select+L1, rebindable under the launcher's
Controller → Host Shortcuts alongside Rewind and Save states).
- **The "Fast Loading (host pacing)" and "CD Speed" mods** — automatic
acceleration during load waits, shipped with every title and **off by
default**, with `turbo_audio_sink` keeping the SPU timeline coherent through
+11 -1
View File
@@ -2429,6 +2429,13 @@ UserSettings load_user_settings(const fs::path& path) {
s.has_hotkey_pad_save_state_menu = true;
}
});
if (h.contains("fast_forward_pad")) try_get([&]{
const auto n = toml::find<int64_t>(h, "fast_forward_pad");
if (pad_bind_value_ok(n)) {
s.hotkey_pad_fast_forward = (int)n;
s.has_hotkey_pad_fast_forward = true;
}
});
}
if (doc.contains("launcher")) {
const toml::value& l = toml::find(doc, "launcher");
@@ -2672,13 +2679,16 @@ bool save_user_settings(const fs::path& path, const UserSettings& s) {
f << "frequency = " << s.audio_freq << "\n";
if (s.has_spu_hq)
f << "spu_hq = " << (s.spu_hq ? "true" : "false") << "\n";
if (s.has_hotkey_pad_rewind || s.has_hotkey_pad_save_state_menu) {
if (s.has_hotkey_pad_rewind || s.has_hotkey_pad_save_state_menu ||
s.has_hotkey_pad_fast_forward) {
f << "\n[hotkeys]\n";
if (s.has_hotkey_pad_rewind)
f << "rewind_pad = " << s.hotkey_pad_rewind << "\n";
if (s.has_hotkey_pad_save_state_menu)
f << "save_state_menu_pad = "
<< s.hotkey_pad_save_state_menu << "\n";
if (s.has_hotkey_pad_fast_forward)
f << "fast_forward_pad = " << s.hotkey_pad_fast_forward << "\n";
}
if (s.has_skip_launcher)
f << "\n[launcher]\nskip_launcher = " << (s.skip_launcher ? "true" : "false") << "\n";
+3
View File
@@ -1262,6 +1262,9 @@ struct UserSettings {
// RECOMP_LAUNCHER_PAD_* encoding (0 = unbound, 1+button, 100+axis).
bool has_hotkey_pad_rewind = false; int hotkey_pad_rewind = 1272; /* select+r3 */
bool has_hotkey_pad_save_state_menu = false; int hotkey_pad_save_state_menu = 2040; /* select+r1 */
// fast_forward_pad: hold-to-fast-forward, the controller twin of the
// keyboard [KeyMap] Turbo (Tab). 0 = unbound.
bool has_hotkey_pad_fast_forward = false; int hotkey_pad_fast_forward = 1528; /* select+l1 */
// [audio]
bool has_spu_hq = false; bool spu_hq = false;
bool has_audio_freq = false; int audio_freq = 44100;
+40 -2
View File
@@ -1217,6 +1217,7 @@ static int g_rewind_depth = 50; /* local rewind snap count (50/100/1
static int g_rewind_interval = 15; /* frames between snaps (1/4/8/12/15) */
static int g_hotkey_pad_rewind = 1272; /* select + r3 */
static int g_hotkey_pad_save_state_menu = 2040;/* select + r1 */
static int g_hotkey_pad_fast_forward = 1528; /* select + l1 (hold) */
static uint32_t g_savestate_input_guard_min_until = 0;
static uint32_t g_savestate_input_guard_max_until = 0;
static int g_headless = 0; /* debug/CI frontend: no SDL window/audio */
@@ -5968,6 +5969,7 @@ static void depth24_stage_scanout(const GpuDisplayInfo *di, uint32_t *buf,
enum {
PSX_ASSIST_BIND_REWIND = 0,
PSX_ASSIST_BIND_SAVE_STATE_MENU,
PSX_ASSIST_BIND_FAST_FORWARD, /* hold-to-fast-forward; pad twin of [KeyMap] Turbo */
PSX_ASSIST_BIND_COUNT
};
@@ -5986,6 +5988,9 @@ enum {
#define PSX_HOTKEY_PAD_SELECT_R1 \
PSX_HOTKEY_PAD_BUTTON_COMBO(((uint32_t)1u << SDL_CONTROLLER_BUTTON_BACK) | \
((uint32_t)1u << SDL_CONTROLLER_BUTTON_RIGHTSHOULDER))
#define PSX_HOTKEY_PAD_SELECT_L1 \
PSX_HOTKEY_PAD_BUTTON_COMBO(((uint32_t)1u << SDL_CONTROLLER_BUTTON_BACK) | \
((uint32_t)1u << SDL_CONTROLLER_BUTTON_LEFTSHOULDER))
static int normalize_hotkey_pad_binding(int binding, int fallback) {
if (PSX_HOTKEY_PAD_IS_BUTTON(binding)) {
@@ -6859,8 +6864,13 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) {
const Uint8* keys = SDL_GetKeyboardState(NULL);
static int turbo_skip = 0;
static int turbo_was_down = 0;
if (host_hotkey_input_focused() &&
host_keymap_down(HOST_KEYMAP_TURBO, keys, (int)SDL_GetModState())) {
/* Keyboard ([KeyMap] Turbo, default Tab) or the controller host
* shortcut ([hotkeys] fast_forward_pad, default select+L1). Both are
* hold-to-run; the pad chord goes through the same combo matcher as
* Rewind / Save states so the launcher's binding editor covers it. */
const bool kb_turbo = host_hotkey_input_focused() &&
host_keymap_down(HOST_KEYMAP_TURBO, keys, (int)SDL_GetModState());
if (kb_turbo || hotkey_pad_binding_down(g_hotkey_pad_fast_forward)) {
const int mult = manual_fast_forward_multiplier();
const int present_every = (mult < 0) ? 4 : (mult <= 4 ? 2 : 4);
manual_turbo_active = true;
@@ -10850,6 +10860,7 @@ namespace {
static const char* const kPsxHostShortcutLabels[] = {
"Rewind",
"Save states",
"Fast-forward",
};
void ae_rui_set_sidecar_paths(const char* argv0) {
@@ -11784,6 +11795,10 @@ int main(int argc, char** argv) {
g_hotkey_pad_save_state_menu = normalize_hotkey_pad_binding(
us.hotkey_pad_save_state_menu,
PSX_HOTKEY_PAD_SELECT_R1);
if (us.has_hotkey_pad_fast_forward)
g_hotkey_pad_fast_forward = normalize_hotkey_pad_binding(
us.hotkey_pad_fast_forward,
PSX_HOTKEY_PAD_SELECT_L1);
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();
@@ -12298,6 +12313,8 @@ int main(int argc, char** argv) {
seed.has_hotkey_pad_rewind = true;
seed.hotkey_pad_save_state_menu = g_hotkey_pad_save_state_menu;
seed.has_hotkey_pad_save_state_menu = true;
seed.hotkey_pad_fast_forward = g_hotkey_pad_fast_forward;
seed.has_hotkey_pad_fast_forward = true;
seed.skip_launcher = skip_launcher_setting; seed.has_skip_launcher = true;
if (has_netplay_player_name) {
seed.netplay_player_name = netplay_player_name;
@@ -12482,6 +12499,9 @@ int main(int argc, char** argv) {
ls.assist_pad_bind[PSX_ASSIST_BIND_SAVE_STATE_MENU] =
normalize_hotkey_pad_binding(seed.hotkey_pad_save_state_menu,
PSX_HOTKEY_PAD_SELECT_R1);
ls.assist_pad_bind[PSX_ASSIST_BIND_FAST_FORWARD] =
normalize_hotkey_pad_binding(seed.hotkey_pad_fast_forward,
PSX_HOTKEY_PAD_SELECT_L1);
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
@@ -12780,6 +12800,10 @@ int main(int argc, char** argv) {
ls.assist_pad_bind[PSX_ASSIST_BIND_SAVE_STATE_MENU],
PSX_HOTKEY_PAD_SELECT_R1);
seed.has_hotkey_pad_save_state_menu = true;
seed.hotkey_pad_fast_forward = normalize_hotkey_pad_binding(
ls.assist_pad_bind[PSX_ASSIST_BIND_FAST_FORWARD],
PSX_HOTKEY_PAD_SELECT_L1);
seed.has_hotkey_pad_fast_forward = 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;
@@ -12997,6 +13021,9 @@ int main(int argc, char** argv) {
g_hotkey_pad_save_state_menu = seed.has_hotkey_pad_save_state_menu
? seed.hotkey_pad_save_state_menu
: PSX_HOTKEY_PAD_SELECT_R1;
g_hotkey_pad_fast_forward = seed.has_hotkey_pad_fast_forward
? seed.hotkey_pad_fast_forward
: PSX_HOTKEY_PAD_SELECT_L1;
skip_launcher_setting = seed.skip_launcher;
if (seed.has_bios_path) {
settings_bios_storage = seed.bios_path.string();
@@ -14483,6 +14510,10 @@ soft_return_lobby:
normalize_hotkey_pad_binding(
g_hotkey_pad_save_state_menu,
PSX_HOTKEY_PAD_SELECT_R1);
ls.assist_pad_bind[PSX_ASSIST_BIND_FAST_FORWARD] =
normalize_hotkey_pad_binding(
g_hotkey_pad_fast_forward,
PSX_HOTKEY_PAD_SELECT_L1);
ls.aspect_index = (g_video_aspect_num * 9 == g_video_aspect_den * 21) ? 2
: (g_video_aspect_num == 16 && g_video_aspect_den == 9) ? 1 : 0;
ls.language_index = 0;
@@ -14782,6 +14813,10 @@ soft_return_lobby:
ls.assist_pad_bind[PSX_ASSIST_BIND_SAVE_STATE_MENU],
PSX_HOTKEY_PAD_SELECT_R1);
us.has_hotkey_pad_save_state_menu = true;
us.hotkey_pad_fast_forward = normalize_hotkey_pad_binding(
ls.assist_pad_bind[PSX_ASSIST_BIND_FAST_FORWARD],
PSX_HOTKEY_PAD_SELECT_L1);
us.has_hotkey_pad_fast_forward = 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;
@@ -14862,6 +14897,9 @@ soft_return_lobby:
g_hotkey_pad_save_state_menu = normalize_hotkey_pad_binding(
ls.assist_pad_bind[PSX_ASSIST_BIND_SAVE_STATE_MENU],
PSX_HOTKEY_PAD_SELECT_R1);
g_hotkey_pad_fast_forward = normalize_hotkey_pad_binding(
ls.assist_pad_bind[PSX_ASSIST_BIND_FAST_FORWARD],
PSX_HOTKEY_PAD_SELECT_L1);
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;
+15
View File
@@ -40,4 +40,19 @@ assert "bool manual_turbo_active = false;" in MAIN
assert "Fast forward: %dx" in MAIN
assert "g_frame_period_ms / (double)mult" in MAIN
# Fast-forward has a controller host shortcut alongside the keyboard Turbo key:
# a select+L1 chord by default, routed through the same combo matcher, offered
# to the launcher as the third host-shortcut row, and persisted as
# [hotkeys] fast_forward_pad.
assert "static int g_hotkey_pad_fast_forward = 1528;" in MAIN
assert "PSX_HOTKEY_PAD_SELECT_L1" in MAIN
assert "PSX_ASSIST_BIND_FAST_FORWARD" in MAIN
assert "if (kb_turbo || hotkey_pad_binding_down(g_hotkey_pad_fast_forward)) {" in MAIN
assert MAIN.count("ls.assist_pad_bind[PSX_ASSIST_BIND_FAST_FORWARD]") == \
MAIN.count("ls.assist_pad_bind[PSX_ASSIST_BIND_SAVE_STATE_MENU]")
assert '"Fast-forward",' in MAIN
CFG = (ROOT / "recompiler" / "src" / "config_loader.cpp").read_text(encoding="utf-8")
assert 'h.contains("fast_forward_pad")' in CFG # settings.toml read
assert 'f << "fast_forward_pad = "' in CFG # settings.toml write
print("host shortcut guard passed")