mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
runtime: fix game-owned display and controller mods
This commit is contained in:
@@ -384,7 +384,9 @@ if(BUILD_TESTING)
|
||||
default_renderer_guards
|
||||
fmv_quiet_guards
|
||||
fps_telemetry_opt_in
|
||||
frame_interpolation_display_rate
|
||||
launcher_vulkan_option
|
||||
mod_controller_override
|
||||
overlay_candidate_capacity
|
||||
overlay_decodable_fallback
|
||||
overlay_init_guard
|
||||
|
||||
@@ -73,11 +73,25 @@ int psx_mod_set_native_vblank_rate(uint32_t frames_per_second);
|
||||
* Enable presentation-only frame interpolation while leaving guest VBlank,
|
||||
* game logic, timers, and audio at their stock cadence. The OpenGL presenter
|
||||
* blends between completed guest frames at the requested output rate.
|
||||
* A value of zero selects an uncapped presentation loop.
|
||||
* A value of zero follows the measured host-display refresh rate.
|
||||
*/
|
||||
int psx_mod_set_frame_interpolation(uint32_t frames_per_second);
|
||||
int psx_mod_set_auto_skip_fmv(int enabled);
|
||||
|
||||
/*
|
||||
* Override one player's resolved controller presentation mode for this launch.
|
||||
* This is intentionally a trusted-plugin API, not a generic launcher setting:
|
||||
* games may hide Hybrid from their normal selector while offering it as an
|
||||
* explicit game-owned mod.
|
||||
*/
|
||||
enum {
|
||||
PSX_MOD_CONTROLLER_HYBRID = 0,
|
||||
PSX_MOD_CONTROLLER_ANALOG = 1,
|
||||
PSX_MOD_CONTROLLER_DIGITAL = 2
|
||||
};
|
||||
int psx_mod_set_controller_mode_override(uint32_t player,
|
||||
uint32_t controller_mode);
|
||||
|
||||
/*
|
||||
* Register a C plugin before main() on the compilers supported by the runtime.
|
||||
* The registry itself uses function-local initialization, so constructor order
|
||||
|
||||
+33
-2
@@ -57,6 +57,7 @@ extern "C" void psx_event_step_conservative_env_init(void);
|
||||
#include "freeze_heartbeat.h"
|
||||
#include "config_loader.h"
|
||||
#include "game_options.h"
|
||||
#include "mod_plugins.h"
|
||||
#include "mod_runtime.h"
|
||||
#include "crc32.h"
|
||||
#include "disc_identity.h"
|
||||
@@ -537,6 +538,13 @@ static int g_low_latency_input = 1;
|
||||
static int g_video_vsync = 1;
|
||||
static int g_frame_interpolation = 0;
|
||||
static int g_frame_interpolation_fps = 0;
|
||||
static int g_mod_controller_mode_override[2] = { -1, -1 };
|
||||
static_assert((int)PSX_MOD_CONTROLLER_HYBRID ==
|
||||
(int)PSXRecompV4::PAD_MODE_HYBRID);
|
||||
static_assert((int)PSX_MOD_CONTROLLER_ANALOG ==
|
||||
(int)PSXRecompV4::PAD_MODE_ANALOG);
|
||||
static_assert((int)PSX_MOD_CONTROLLER_DIGITAL ==
|
||||
(int)PSXRecompV4::PAD_MODE_DIGITAL);
|
||||
static double g_host_refresh_hz = 0.0;
|
||||
static constexpr double PSX_FRAME_PERIOD_MS = 1000.0 / 59.94;
|
||||
static double g_frame_period_ms = PSX_FRAME_PERIOD_MS;
|
||||
@@ -677,7 +685,7 @@ extern "C" int psx_mod_set_frame_interpolation(
|
||||
g_video_vsync = 0;
|
||||
g_frame_interpolation = 1;
|
||||
g_frame_interpolation_fps =
|
||||
frames_per_second ? (int)frames_per_second : -1;
|
||||
frames_per_second ? (int)frames_per_second : 0;
|
||||
|
||||
if (frames_per_second) {
|
||||
std::fprintf(stdout,
|
||||
@@ -686,7 +694,7 @@ extern "C" int psx_mod_set_frame_interpolation(
|
||||
(unsigned)frames_per_second);
|
||||
} else {
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: mod selected uncapped presentation-only "
|
||||
"psxrecomp: mod selected display-refresh presentation-only "
|
||||
"interpolation; guest timing remains stock\n");
|
||||
}
|
||||
return 1;
|
||||
@@ -705,6 +713,20 @@ extern "C" int psx_mod_set_auto_skip_fmv(int enabled) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
extern "C" int psx_mod_set_controller_mode_override(
|
||||
uint32_t player, uint32_t controller_mode) {
|
||||
if (player >= 2 ||
|
||||
controller_mode > (uint32_t)PSXRecompV4::PAD_MODE_DIGITAL) {
|
||||
std::fprintf(stderr,
|
||||
"psxrecomp: mod rejected controller-mode override "
|
||||
"(player %u, mode %u)\n",
|
||||
(unsigned)player, (unsigned)controller_mode);
|
||||
return 0;
|
||||
}
|
||||
g_mod_controller_mode_override[player] = (int)controller_mode;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* [widescreen] per-game hooks (see config_loader.h): anchor scratch addr for
|
||||
* tagged sprite prims + HUD SPRT center-squash. Inert at 0/false. */
|
||||
static uint32_t g_ws_anchor_addr = 0;
|
||||
@@ -6123,7 +6145,16 @@ int main(int argc, char** argv) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
/* Activation callbacks are re-run after every launcher session. Clear
|
||||
* game-owned controller overrides first so disabling a package cannot
|
||||
* leave its prior mode latched across a soft return to the launcher. */
|
||||
g_mod_controller_mode_override[0] = -1;
|
||||
g_mod_controller_mode_override[1] = -1;
|
||||
mod_runtime_activate_plugins();
|
||||
if (g_mod_controller_mode_override[0] >= 0)
|
||||
p1_mode = g_mod_controller_mode_override[0];
|
||||
if (g_mod_controller_mode_override[1] >= 0)
|
||||
p2_mode = g_mod_controller_mode_override[1];
|
||||
|
||||
/* Re-apply the resolved language to the translation layer. text_xlate_init
|
||||
* (at config load) only saw the game.toml default; this folds in the
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard the mod API's zero sentinel: follow the display, never busy-loop."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
MAIN = (ROOT / "runtime" / "src" / "main.cpp").read_text(encoding="utf-8")
|
||||
HEADER = (ROOT / "runtime" / "include" / "mod_plugins.h").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
EXPECTED_ASSIGNMENT = """g_frame_interpolation_fps =
|
||||
frames_per_second ? (int)frames_per_second : 0;"""
|
||||
|
||||
assert EXPECTED_ASSIGNMENT in MAIN, (
|
||||
"psx_mod_set_frame_interpolation(0) must preserve the 0 sentinel so "
|
||||
"gl_renderer_set_interpolation resolves it to the measured host refresh"
|
||||
)
|
||||
assert "frames_per_second ? (int)frames_per_second : -1" not in MAIN, (
|
||||
"the -1 sentinel selects the uncapped interpolation busy loop"
|
||||
)
|
||||
assert "zero follows the measured host-display refresh rate" in HEADER, (
|
||||
"the public plugin contract must document the display-rate sentinel"
|
||||
)
|
||||
|
||||
print("frame interpolation display-rate sentinel guard passed")
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard the game-owned controller-mode override lifecycle."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
MAIN = (ROOT / "runtime" / "src" / "main.cpp").read_text(encoding="utf-8")
|
||||
HEADER = (ROOT / "runtime" / "include" / "mod_plugins.h").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
for symbol in (
|
||||
"PSX_MOD_CONTROLLER_HYBRID",
|
||||
"PSX_MOD_CONTROLLER_ANALOG",
|
||||
"PSX_MOD_CONTROLLER_DIGITAL",
|
||||
"psx_mod_set_controller_mode_override",
|
||||
):
|
||||
assert symbol in HEADER, f"missing trusted-plugin controller API: {symbol}"
|
||||
|
||||
reset = """g_mod_controller_mode_override[0] = -1;
|
||||
g_mod_controller_mode_override[1] = -1;
|
||||
mod_runtime_activate_plugins();"""
|
||||
apply = """if (g_mod_controller_mode_override[0] >= 0)
|
||||
p1_mode = g_mod_controller_mode_override[0];
|
||||
if (g_mod_controller_mode_override[1] >= 0)
|
||||
p2_mode = g_mod_controller_mode_override[1];"""
|
||||
|
||||
assert reset in MAIN, "controller overrides must reset before every activation pass"
|
||||
assert apply in MAIN, "controller overrides must replace the resolved launch modes"
|
||||
assert MAIN.index(reset) < MAIN.index(apply), "reset/activate must precede application"
|
||||
|
||||
print("mod controller override lifecycle guard passed")
|
||||
Reference in New Issue
Block a user