Files
psxrecomp/runtime/include/mod_plugins.h
T
Matthew Stanley eb262c4b23 feat(mods): let a trusted plugin read its own option values
The manifest schema already carries typed, validated, launcher-rendered,
persisted options ([[option]] boolean/choice/integer), and the launcher
provider already reads them. A trusted activation plugin could not: the
callback takes no arguments, so a plugin was only ever an on/off switch.

That forced a parameterised feature to be modelled as one feature per
value, and `constraint` only expresses ordered_integer WITHIN a feature, so
those pseudo-features could not even be made mutually exclusive. A game
wanting "Widescreen: 16:9 / 21:9" or "Interpolation: 90/120/144/165/240"
had no way to express it as one entry.

psx_mod_option_value(package, feature, option, out, out_size) closes the
gap against the value the player actually committed. It returns 0 with an
empty string when the plan is not committed or the ids do not resolve, so a
caller applies its own default rather than reading "" as a selection. Ids
are explicit because registration is by plugin id alone and the callback
carries no package/feature context.

Also in this commit, from the same investigation:

* Sustained overlay-autocompile failure is now LOUD. After 3 consecutive
  failed runs the runtime prints once to stdout: what failed, the exit
  code, that overlays are staying interpreted and frame times will be far
  worse than the build is capable of, what to check, and the compiler's own
  output tail. Previously the only trace was a `fails` counter reachable
  over TCP, so a wholly-interpreted title presented as "why is this slow"
  rather than as a message. Threshold is 3, not 1, because isolated
  failures (a shard racing a capture rewrite, a transient lock) are
  ordinary and self-heal. Measured cost of the silent case on Tomba!:
  p50 frame period 105ms, vs 3.1ms once shards existed.

* The autocompile child's working directory is printed at startup beside
  cache and captures. Every relative path in overlay_autocompile_cmd
  resolves against it and it comes from game.toml's detected project root,
  so a game.toml staged outside the real root silently repoints the entire
  compile command with no other symptom.

* A build tree whose cache still carries recomp-ui's OFF for
  RECOMP_UI_ENABLE_MODS now says so at configure time. 610234a's seed only
  fires on a fresh cache, and a stale OFF is indistinguishable from a
  deliberate one, so such trees kept producing Mods-less builds across
  reconfigures - the same "reads as a stale build" failure one level up,
  and invisible. The explicit-OFF contract is deliberate so this does not
  override it; it makes the state audible.

Runtime ctest 16/16. Warning verified to fire on a tree configured with
-DRECOMP_UI_ENABLE_MODS=OFF and to stay silent on a fresh configure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 17:33:44 -07:00

104 lines
4.5 KiB
C

#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*PSXModVBlankCallback)(void);
typedef void (*PSXModActivationCallback)(void);
/*
* Register a trusted, statically linked plugin implementation. Package
* manifests select implementations by this stable id; archives never provide
* native code or symbol names.
*/
int psx_mod_register_activation_plugin(const char* id,
PSXModActivationCallback callback);
int psx_mod_register_vblank_plugin(const char* id,
PSXModVBlankCallback callback);
/* Narrow guest services available to trusted plugin callbacks. */
int psx_mod_game_started(void);
uint8_t psx_mod_read_byte(uint32_t address);
void psx_mod_write_byte(uint32_t address, uint8_t value);
/*
* Read the committed value of one of this package's declared options, as the
* player left it in the launcher (or the manifest default when untouched).
* Writes a NUL-terminated string into `out` and returns 1; returns 0 with
* out[0] = '\0' when the plan is not committed, the ids do not resolve, or the
* value does not fit — the caller then applies its own default rather than
* treating an empty string as a selection.
*
* Why this exists: the manifest schema already carries typed, validated,
* launcher-rendered, persisted options ([[option]] boolean/choice/integer), but
* an activation callback takes no arguments and had no way to read them, so a
* trusted plugin could only ever be an on/off switch. A parameterised feature
* then had to be modelled as one feature per value — and `constraint` only
* expresses ordered_integer WITHIN a feature, so those pseudo-features could
* not even be made mutually exclusive. This closes that gap: one feature, one
* option, the plugin reads what was chosen.
*
* Ids are passed explicitly because registration is by plugin id alone and the
* callback carries no package/feature context.
*/
int psx_mod_option_value(const char* package_id, const char* feature_id,
const char* option_id, char* out, uint32_t out_size);
/*
* Request a fixed host display aspect before renderer/window initialization.
* Intended for activation callbacks that move a game's widescreen enhancement
* out of generic Settings and into its mod catalog.
*/
int psx_mod_set_fixed_display_aspect(uint32_t numerator,
uint32_t denominator);
/*
* Request resize-driven widescreen, capped at the supplied maximum aspect.
* The current fixed aspect continues to shape the initial game window, so a
* plugin may select that first with psx_mod_set_fixed_display_aspect().
*/
int psx_mod_set_adaptive_display_aspect(uint32_t max_numerator,
uint32_t max_denominator);
/*
* Set the wall-clock cadence of simulated guest VBlanks. A value of zero
* removes frontend pacing; 60 and higher request that many native guest
* update opportunities per host second. This intentionally changes whole-
* machine realtime speed and is for experimental game-owned frame-rate mods.
*/
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.
*/
int psx_mod_set_frame_interpolation(uint32_t frames_per_second);
int psx_mod_set_auto_skip_fmv(int enabled);
/*
* Register a C plugin before main() on the compilers supported by the runtime.
* The registry itself uses function-local initialization, so constructor order
* between game sources and the framework is safe.
*/
#if defined(_MSC_VER)
#pragma section(".CRT$XCU", read)
#define PSX_MOD_CONSTRUCTOR(name) \
static void __cdecl name(void); \
__declspec(allocate(".CRT$XCU")) \
static void (__cdecl* name##_constructor)(void) = name; \
static void __cdecl name(void)
#elif defined(__GNUC__) || defined(__clang__)
#define PSX_MOD_CONSTRUCTOR(name) \
static void name(void) __attribute__((constructor)); \
static void name(void)
#else
#error "PSX mod plugin registration needs a supported constructor mechanism"
#endif
#ifdef __cplusplus
}
#endif