mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
input: add trusted offline controller sources for game plugins
Adapted from the original feature commits, retaining their author credit: [5a1264b7](https://github.com/FractalEngineer/psxrecomp/commit/5a1264b79b69b5d75f7b8507e305b1e13a911a50) Source scope and current-master validation: docs/internal/upstream/offline-plugin-controller-sources.md Co-authored-by: Yves <calibratedbeats@gmail.com>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
# Offline plugin controller sources: source and validation
|
||||
|
||||
Reconstructed from [FractalEngineer/psxrecomp](https://github.com/FractalEngineer/psxrecomp), developed on
|
||||
`vr-dev` at `9976567eb8e95510ca7eeeeccd9af3a260885de5`.
|
||||
Exact source commits:
|
||||
|
||||
- [5a1264b7](https://github.com/FractalEngineer/psxrecomp/commit/5a1264b79b69b5d75f7b8507e305b1e13a911a50)
|
||||
|
||||
Original author: Yves <calibratedbeats@gmail.com>. The feature commit retains
|
||||
that exact human author identity. The focused port consolidates the selected
|
||||
source hunks and corrections. Its commit also cites these sources and carries
|
||||
the original human contributor's `Co-authored-by: Yves <calibratedbeats@gmail.com>` trailer.
|
||||
|
||||
Adapted: Only trusted controller-source registration, validation/neutralization and the existing offline host-input boundary.
|
||||
|
||||
Deliberately excluded: OpenXR Touch action implementation, game bindings/deadzones/aiming and guest SIO replacements.
|
||||
|
||||
The feature was reconstructed on upstream master
|
||||
`db62eec15f81c88aef3d6dab51d4e566a7167d23`, retaining current upstream APIs,
|
||||
renderer/input policy and build integration. Validation after reconstruction:
|
||||
Absent/invalid/declined/analog/digital source, detach and session-reset executable checks.
|
||||
|
||||
The motivating consumer is [Medal of Honor, SLUS-00974 (NTSC-U)](https://github.com/FractalEngineer/Medal-Of-Honor-PSX-Recomp/tree/e323885958c2ae0b0df847bed6175f9e76524442).
|
||||
Game-specific integration remains in that repository. Consuming this change
|
||||
requires a deliberate framework pin update in the game repo and a rebuild.
|
||||
This is a host/runtime/tool change; it requires no BIOS or game C regeneration.
|
||||
|
||||
Historical alpha acceptance used the older framework checkpoint above. It is
|
||||
separate from current-master branch verification. Fresh game, renderer and
|
||||
headset checks, where applicable, are recorded in the PR validation notes;
|
||||
uncertain hardware/runtime behavior must not be inferred from a clean link.
|
||||
|
||||
## Current-master integration validation
|
||||
|
||||
Windows, GCC 13.2, Ninja, Release. Runtime test checks remain active with NDEBUG enabled.
|
||||
|
||||
Release controller-source checks pass for absent, invalid, declined, analog/digital, detach and session-reset samples; they remain active with NDEBUG enabled. Integrated flat input reaches gameplay. Hardware controller mappings and live headset focus/reconnect behavior remain outside this test coverage.
|
||||
|
||||
Integration: [Medal of Honor, SLUS-00974 NTSC-U](https://github.com/FractalEngineer/Medal-Of-Honor-PSX-Recomp/tree/e323885958c2ae0b0df847bed6175f9e76524442), built against the corrected framework stack with `PSXRECOMP_ROOT`. Bundled OpenBIOS LLE cold boot, menu navigation, mission gameplay, fresh savestate creation/loading and post-load input/frame advancement were checked with screenshots. An older alpha slot-5 savestate was refused; compatibility with historical states is not established. Memory-card round trips, attract-demo completion, other games and fresh headset visuals were not tested.
|
||||
|
||||
The registered recompiler suite reports 132 passes, four failures and three disabled tests. The four failures reproduce on unchanged upstream master in this Windows toolchain: `portable_settings_paths_test`, `optional_netplay_auth`, `compile_overlays_static_tail` and `aot_overlay_discovery`. The introduced BIOS-fingerprint and GL-guard failures are fixed.
|
||||
@@ -364,6 +364,9 @@ if(BUILD_TESTING)
|
||||
target_include_directories(projection_scale_config_test PRIVATE
|
||||
include ../recompiler/lib/toml11)
|
||||
add_test(NAME projection_scale_config_test COMMAND projection_scale_config_test)
|
||||
add_executable(mod_controller_source_test tests/test_mod_controller_source.c src/mod_controller_source.c)
|
||||
target_include_directories(mod_controller_source_test PRIVATE include)
|
||||
add_test(NAME mod_controller_source_test COMMAND mod_controller_source_test)
|
||||
|
||||
add_executable(gte_register_access_test
|
||||
tests/test_gte_register_access.cpp
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include "mod_plugins.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
void mod_controller_source_reset(void);
|
||||
int mod_controller_source_sample(uint32_t player, PSXModControllerState *state);
|
||||
int mod_controller_source_present(uint32_t player);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -499,6 +499,18 @@ int psx_mod_set_controller_presentation_policy(
|
||||
uint32_t initial_mode,
|
||||
int config_capable);
|
||||
|
||||
/* Trusted offline source owns a player's pad at normal input sampling. A
|
||||
* declined/invalid sample delivers neutral, not the previous held input.
|
||||
* Existing TCP overrides take priority; netplay/resim and eye redraws never
|
||||
* invoke the source. The runtime keeps coherent SIO type requests/recording.
|
||||
* Pass NULL to detach. Local keyboard/pad buttons remain merged for menus;
|
||||
* the source owns sticks and type. No source leaves faithful defaults intact. */
|
||||
typedef struct PSXModControllerState {
|
||||
uint32_t struct_size, buttons, lx, ly, rx, ry, analog;
|
||||
} PSXModControllerState;
|
||||
typedef int (*PSXModControllerSource)(PSXModControllerState *state);
|
||||
int psx_mod_set_controller_source(uint32_t player, PSXModControllerSource source);
|
||||
|
||||
/*
|
||||
* Register a C plugin before main() on the compilers supported by the runtime.
|
||||
* The registry itself uses function-local initialization, so constructor order
|
||||
|
||||
@@ -352,6 +352,7 @@ set(PSXRECOMP_RUNTIME_SOURCES
|
||||
${PSXRECOMP_ROOT}/runtime/src/gpu_render.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/gpu_gl_renderer.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/vr_pose_math.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/mod_controller_source.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/gpu_vk_renderer.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/dma_gpu_ll.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/dma.c
|
||||
|
||||
+20
-1
@@ -8,6 +8,7 @@
|
||||
#include "cpu_state.h"
|
||||
#include "projection_scale.hpp"
|
||||
#include "projection_scale_config.hpp"
|
||||
#include "mod_controller_source.h"
|
||||
#include "window_size.h" /* default game-window size */
|
||||
#include "internal_resolution.h" /* Settings -> Display -> Internal resolution */
|
||||
#include "psx_scheduler.h" /* psx_scheduler_run — deterministic TCB scheduler */
|
||||
@@ -6242,7 +6243,24 @@ static void sample_pad_into_sio(int override) {
|
||||
psx_start_consumer_enabled() ? psx_start_consumer_offline_frame() : 0u;
|
||||
for (int s = 0; s < n; s++) {
|
||||
PsxNetPad pad;
|
||||
if (!capture_pad_slot(s, &pad)) continue; /* no device in this port */
|
||||
PSXModControllerState source;
|
||||
if (mod_controller_source_sample((uint32_t)s, &source)) {
|
||||
/* Sample physical buttons separately; its axes/type cannot steer
|
||||
* the source's virtual pad. Guards release every source channel. */
|
||||
PsxNetPad local;
|
||||
const int have_local = capture_pad_slot(s, &local);
|
||||
pad.buttons = (uint16_t)source.buttons;
|
||||
if (have_local) pad.buttons &= local.buttons;
|
||||
pad.lx = (uint8_t)source.lx; pad.ly = (uint8_t)source.ly;
|
||||
pad.rx = (uint8_t)source.rx; pad.ry = (uint8_t)source.ry;
|
||||
pad.analog = (uint8_t)source.analog; pad.connected = 1;
|
||||
if (savestate_input_guard_active()) {
|
||||
pad.buttons = 0xffff;
|
||||
pad.lx = pad.ly = pad.rx = pad.ry = 128;
|
||||
}
|
||||
sio_set_pad_connected(s, 1);
|
||||
sio_set_pad_config_capable(s, 1);
|
||||
} else if (!capture_pad_slot(s, &pad)) continue; /* no device in this port */
|
||||
/* Push sticks every frame; request the pad type (digital/analog) through
|
||||
* the coherent channel so a policy switch is applied only at an idle,
|
||||
* non-config bus boundary (never mid-poll / mid-handshake). This is the
|
||||
@@ -15782,6 +15800,7 @@ int main(int argc, char** argv) {
|
||||
* choices first so disabling a package cannot leave its prior state
|
||||
* latched across a soft return. */
|
||||
g_mod_controller_mode_override.fill(-1);
|
||||
mod_controller_source_reset();
|
||||
for (auto& policy : g_mod_controller_policy)
|
||||
policy = ModControllerPresentationPolicy{};
|
||||
g_mod_load_wall_multiplier = -1;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "mod_controller_source.h"
|
||||
#include "sio.h"
|
||||
#include <string.h>
|
||||
static PSXModControllerSource s_source[PSX_MAX_PLAYERS];
|
||||
static uint8_t s_release[PSX_MAX_PLAYERS];
|
||||
int psx_mod_set_controller_source(uint32_t player, PSXModControllerSource source) {
|
||||
if (player >= PSX_MAX_PLAYERS) return 0;
|
||||
if(s_source[player] && !source)s_release[player]=1;
|
||||
s_source[player] = source; return 1;
|
||||
}
|
||||
void mod_controller_source_reset(void) { memset(s_source, 0, sizeof s_source); memset(s_release,0,sizeof s_release); }
|
||||
int mod_controller_source_present(uint32_t player) {
|
||||
return player < PSX_MAX_PLAYERS && s_source[player] != NULL;
|
||||
}
|
||||
int mod_controller_source_sample(uint32_t player, PSXModControllerState *state) {
|
||||
if (!state || player>=PSX_MAX_PLAYERS || (!s_source[player] && !s_release[player])) return 0;
|
||||
PSXModControllerState neutral = {sizeof neutral, 0xffff, 128,128,128,128,1};
|
||||
*state = neutral;
|
||||
if(s_release[player]) { s_release[player]=0; return 1; }
|
||||
if (!s_source[player](state) || state->struct_size != sizeof *state ||
|
||||
state->buttons > 0xffff || state->lx > 255 || state->ly > 255 ||
|
||||
state->rx > 255 || state->ry > 255 || state->analog > 1) *state = neutral;
|
||||
if (!state->analog) state->lx = state->ly = state->rx = state->ry = 128;
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "mod_controller_source.h"
|
||||
#include "sio.h"
|
||||
#include <stdio.h>
|
||||
static int mode;
|
||||
static int source(PSXModControllerState *p) {
|
||||
p->buttons=0xfeff;p->lx=200;p->ly=5;
|
||||
if(mode==1)return 0;
|
||||
if(mode==2)p->rx=256;
|
||||
if(mode==3)p->analog=0;
|
||||
return 1;
|
||||
}
|
||||
/* Evaluate side effects and failures even when NDEBUG is defined. */
|
||||
#define CHECK(expr) do { \
|
||||
if (!(expr)) { \
|
||||
fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #expr); \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
int main(void) {
|
||||
PSXModControllerState p;
|
||||
CHECK(!psx_mod_set_controller_source(PSX_MAX_PLAYERS,source));
|
||||
CHECK(!mod_controller_source_sample(0,&p));
|
||||
CHECK(psx_mod_set_controller_source(0,source));
|
||||
CHECK(mod_controller_source_sample(0,&p) && p.lx==200 && p.ly==5);
|
||||
mode=1;CHECK(mod_controller_source_sample(0,&p) && p.buttons==0xffff && p.lx==128);
|
||||
mode=2;CHECK(mod_controller_source_sample(0,&p) && p.buttons==0xffff && p.rx==128);
|
||||
mode=3;CHECK(mod_controller_source_sample(0,&p) && !p.analog && p.lx==128 && p.ly==128);
|
||||
CHECK(psx_mod_set_controller_source(0,0));
|
||||
CHECK(mod_controller_source_sample(0,&p) && p.buttons==0xffff && p.lx==128);
|
||||
CHECK(!mod_controller_source_sample(0,&p));
|
||||
CHECK(psx_mod_set_controller_source(0,source));
|
||||
mod_controller_source_reset();
|
||||
CHECK(!mod_controller_source_present(0) && !mod_controller_source_sample(0,&p));
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user