mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
1358 lines
78 KiB
C
1358 lines
78 KiB
C
#pragma once
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "func_override.h" /* FO_CREDIT_SELF for the credit argument below */
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
typedef void (*PSXModVBlankCallback)(void);
|
|
typedef void (*PSXModActivationCallback)(void);
|
|
/* Emulation-thread stereo S16 at 44100 Hz, after SPU/shadow synthesis, before
|
|
* capture/host mute. Called for every rendered block, including discarded
|
|
* turbo/rollback output. Add with saturation; never call guest code here.
|
|
* Keep mutable cursors in allocated guest memory for save/rewind/rollback.
|
|
* Host audio/device state must not affect sample generation. */
|
|
typedef void (*PSXModPCMCallback)(int16_t* stereo, uint32_t frames);
|
|
struct CPUState;
|
|
/* A callback that runs guest code inside psx_mod_render_pass() can be
|
|
* abandoned by a watchdog longjmp; see the note at psx_mod_render_pass(). */
|
|
typedef void (*PSXModFunctionEntryCallback)(struct CPUState* cpu,
|
|
uint32_t address);
|
|
/* Return nonzero to finish this opt-in function with the callback's return
|
|
* registers. The runtime publishes pc=$ra; zero executes the original body. */
|
|
typedef int (*PSXModFunctionFilterCallback)(struct CPUState* cpu,
|
|
uint32_t address);
|
|
|
|
/*
|
|
* 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);
|
|
/* Host-only cleanup before each plan activates, including plans that omit this
|
|
* plugin, and when netplay clears the offline plan. Runs with emulation stopped
|
|
* and without a current feature/resource context. Never mutate guest state. */
|
|
int psx_mod_register_session_reset_plugin(const char* id,
|
|
PSXModActivationCallback callback);
|
|
int psx_mod_register_vblank_plugin(const char* id,
|
|
PSXModVBlankCallback callback);
|
|
int psx_mod_register_pcm_plugin(const char* id, PSXModPCMCallback callback);
|
|
/* Runs after successful guest-state restore, before the restored PC resumes.
|
|
* Rebind host hooks here; do not advance guest gameplay as a VBlank would. */
|
|
int psx_mod_register_savestate_plugin(const char* id,
|
|
PSXModActivationCallback callback);
|
|
int psx_mod_register_function_entry_plugin(
|
|
const char* id, uint32_t address, PSXModFunctionEntryCallback callback);
|
|
/* Mod-defined guest functions have no original machine-code body. Addresses
|
|
* must be aligned and in physical 0x0F000000..0x0FFFFFFF (an unused bus range),
|
|
* never hardware/BIOS/game text. Only statically linked code can register one;
|
|
* the active package plan owns its availability and resource context. Address
|
|
* aliases share one globally unique registration. The callback supplies the
|
|
* full function behavior; return publishes $ra through normal dispatch. */
|
|
int psx_mod_register_guest_function_plugin(
|
|
const char* id, uint32_t address, PSXModFunctionEntryCallback callback);
|
|
int psx_mod_dispatch_guest_function(struct CPUState* cpu, uint32_t address);
|
|
extern uint32_t g_psx_mod_guest_functions;
|
|
/* Run immediately before a configured instruction, including delay slots.
|
|
* The complete instruction word must match both registration and live RAM.
|
|
* Callbacks may update registers/data but cannot redirect PC, finish a guest
|
|
* function, or re-enter guest execution (pending load/branch state is live).
|
|
* Native emits opt in with [recompiler] mod_instruction_sites; dirty-RAM
|
|
* execution uses the same guarded table. Inactive plans are inert. */
|
|
int psx_mod_register_instruction_plugin(const char* id, uint32_t address,
|
|
uint32_t expected, PSXModFunctionEntryCallback callback);
|
|
void psx_mod_instruction(struct CPUState* cpu, uint32_t address, uint32_t instruction);
|
|
extern uint32_t g_psx_mod_instruction_hooks;
|
|
/* Redirect one audited load/store's effective address without changing its
|
|
* base register, destination, data, branch state or timing. Registration and
|
|
* live RAM must match the complete instruction. Native code instruments sites
|
|
* listed in mod_instruction_sites; dirty-RAM execution uses the same table.
|
|
* Return the original address to decline. Callbacks run in plan order, cannot
|
|
* re-enter guest execution, and must preserve the low two address bits (also
|
|
* preserving unaligned merge lanes). The CPU argument is read-only. */
|
|
typedef uint32_t (*PSXModMemoryAddressCallback)(const struct CPUState* cpu,
|
|
uint32_t pc, uint32_t instruction, uint32_t effective_address);
|
|
int psx_mod_register_memory_address_plugin(const char* id, uint32_t pc,
|
|
uint32_t expected, PSXModMemoryAddressCallback callback);
|
|
uint32_t psx_mod_memory_address(const struct CPUState* cpu, uint32_t pc,
|
|
uint32_t instruction, uint32_t effective_address);
|
|
/* Called from generated functions listed by the game config and from every
|
|
* interpreted entry, so the hook contract does not depend on the backend.
|
|
* Hooks match by code address (segment bits ignored) and run only for plugins
|
|
* the active plan resolved; the table is rebuilt at plugin activation. */
|
|
int psx_mod_register_function_filter_plugin(
|
|
const char* id, uint32_t address, PSXModFunctionFilterCallback callback);
|
|
int psx_mod_function_entry(struct CPUState* cpu, uint32_t address);
|
|
/* Trusted game code (not a mod package) may hook guest functions for netplay
|
|
* only. These are independent of the mod package plan, which every online
|
|
* match clears, and run only while netplay is active; offline execution stays
|
|
* stock. A filter returning nonzero consumes the whole call (PC <- $ra); it
|
|
* must supply any guest-visible side effects itself. */
|
|
int psx_game_register_netplay_function_entry(
|
|
uint32_t address, PSXModFunctionEntryCallback callback);
|
|
int psx_game_register_netplay_function_filter(
|
|
uint32_t address, PSXModFunctionFilterCallback filter);
|
|
/* Complete a guest function from its trusted entry callback after supplying
|
|
* its full result. Valid only for that callback's CPU. Publishes pc=$ra and
|
|
* prevents the original body from executing. Nested callbacks have separate
|
|
* completion scopes; requests outside an entry callback return zero. */
|
|
int psx_mod_finish_function(struct CPUState* cpu);
|
|
/* Active function-entry hook count (0 = none). Hot callers test it before the
|
|
* call, so a run without an active hook pays one load per interpreted entry. */
|
|
extern uint32_t g_psx_mod_function_entry_hooks;
|
|
|
|
/* Always-on named event counters for trusted plugins (observability, not
|
|
* logging): e.g. how often each guard in a hook rejected. `name` should be a
|
|
* string literal of the form "<plugin>.<event>"; up to 128 distinct names are
|
|
* kept, further names are counted in an overflow bucket. Emulation-thread only.
|
|
* TCP: {"cmd":"mod_counters"} lists every counter with its last frame. */
|
|
void psx_mod_counter_add(const char* name, uint32_t delta);
|
|
/* Named debug commands for trusted plugins (test benches, tuning): `fn` gets the
|
|
* argument string and writes a short text reply (truncated to `cap`); it runs on
|
|
* the debug-server thread, so it must only touch state it guards itself. Up to 16
|
|
* names (1..63 bytes, copied by the registry); callbacks have process lifetime.
|
|
* Re-registering a name replaces its callback, allowing restart registration.
|
|
* This debug-control registry is independent of package activation. It must
|
|
* not directly mutate guest/shared simulation state from the server thread:
|
|
* signal a guarded control request for the plugin's existing emulation hook.
|
|
* Callbacks must not throw. TCP: {"cmd":"mod_cmd","name":"<name>","args":"..."}. */
|
|
typedef int (*PSXModCommandFn)(const char* args, char* reply, int cap);
|
|
int psx_mod_register_command(const char* name, PSXModCommandFn fn);
|
|
/* Runs a registered command; -1 for unknown name/invalid output, otherwise the
|
|
* callback result. Valid replies are always terminated within cap bytes. */
|
|
int psx_mod_run_command(const char* name, const char* args, char* reply, int cap);
|
|
/* Presentation-only filtering: 0 nearest, 1 bilinear, 2 stable minification.
|
|
* Mode 2 uses a bounded palette-aware footprint for proven 3D polygons on
|
|
* OpenGL; untracked UI stays nearest. Other backends use bilinear. A session
|
|
* reset restores the player's configured filter. */
|
|
void psx_mod_set_texture_filter(int mode);
|
|
/* Entry callbacks can make nested guest calls while retaining host registers.
|
|
* Save/load and rewind must wait until that host context has returned. */
|
|
int psx_mod_function_entry_active(void);
|
|
|
|
/* [timing] guest_cycle_scale mod gate (docs/config_schema.md, Timing block).
|
|
* With guest_cycle_scale_gated = true in game.toml the scale applies only
|
|
* while a trusted plugin holds this gate open (and any declarative
|
|
* guest_cycle_scale_gate RAM predicates hold). Every mod session reset
|
|
* shuts it; open it from the activation callback. Mod plans are cleared
|
|
* online, so this gate keeps a scale that depends on a mod out of netplay.
|
|
* The gate state is part of savestates and the netplay rollback snapshot. */
|
|
void psx_mod_set_guest_cycle_scale_gate(int open);
|
|
/* Live scale: the configured one while every gate is open, else 1. */
|
|
uint32_t psx_mod_guest_cycle_scale(void);
|
|
|
|
/*
|
|
* Package-gated function override (see runtime/include/func_override.h for
|
|
* the full contract). Registration queues the override under the plugin id;
|
|
* it is ARMED into the dispatcher tier only when a resolved package plan
|
|
* selects that plugin — the same gating as vblank/activation callbacks. The
|
|
* callback obeys the guest ABI (args cpu->gpr[4..7], result cpu->gpr[2]);
|
|
* return 1 = handled (guest resumes at $ra), 0 = decline (original runs).
|
|
* The owning plugin resource context is active throughout the callback and
|
|
* restored across nested callbacks. Snapshots wait until its native stack
|
|
* unwinds. Wrap semantics via func_override_call_original(); optional residency
|
|
* guard via expected_words (NULL/0 = unguarded).
|
|
*
|
|
* `id` may carry an optional ":label" suffix ("pkg.feature:aim") that names
|
|
* the override in the `func_override` TCP inventory; gating and manifest
|
|
* matching use only the part before the ':', so several overrides can sit
|
|
* under one [[plugin]] entry and still read apart in diagnostics.
|
|
*
|
|
* `credit` is the REQUIRED guest-cycle policy (func_override.h, CYCLE
|
|
* ACCOUNTING): >= 0 cycles charged per handled call, or FO_CREDIT_SELF when
|
|
* the body charges its own. For a mod whose behavior has no hardware analog
|
|
* an explicit 0 is the honest value; a wrap that runs the original via
|
|
* func_override_call_original must pass FO_CREDIT_SELF (the original
|
|
* self-charges by executing — a fixed credit would double-count).
|
|
*/
|
|
typedef int (*PSXModFunctionOverrideFn)(struct CPUState* cpu);
|
|
int psx_mod_register_function_override(const char* id, uint32_t address,
|
|
PSXModFunctionOverrideFn fn,
|
|
const uint32_t* expected_words,
|
|
int n_words, int32_t credit);
|
|
|
|
/* Narrow guest services available to trusted plugin callbacks. */
|
|
int psx_mod_game_started(void);
|
|
/* Read an original mounted-disc file without changing guest CD state/timing.
|
|
* Emulation-thread callbacks only. NULL buffer + zero capacity queries size;
|
|
* otherwise capacity must hold the entire file. Active sector mods apply. */
|
|
int psx_mod_read_disc_file(const char* path, void* buffer, uint32_t capacity,
|
|
uint32_t* size);
|
|
/* 1-based roster index of the disc image the guest drive has mounted, or 0
|
|
* for a single-image session. It changes when the player swaps discs; the
|
|
* psx_mod_read_disc_file / psx_mod_disc_file_extent calls read that image. */
|
|
int psx_mod_mounted_disc(void);
|
|
/* Optional automatic disc changes. A trusted title provider reads its verified
|
|
* guest request state: return a 1-based roster disc throughout the request
|
|
* episode, or 0 when no request exists. Set *ready only once the guest is
|
|
* waiting for the lid, after its old-disc reads finish. Do not write guest RAM.
|
|
* The host polls at VBlank, attempts once per episode, and preserves manual
|
|
* switching after failure. A readiness change does not rearm a failed request.
|
|
* Register only from an active offline plugin's activation callback.
|
|
* NULL clears the provider; a new plan or activation clears it too.
|
|
* Providers are never called in netplay or before the game starts. */
|
|
typedef int (*PSXModDiscRequestProvider)(int *ready);
|
|
void psx_mod_set_disc_request_provider(PSXModDiscRequestProvider provider);
|
|
/* Effective-disc LBA and byte size of a file (directory records patched by
|
|
* active mods apply, so a relocated file reports its new extent). */
|
|
int psx_mod_disc_file_extent(const char* path, uint32_t* lba, uint32_t* size);
|
|
/* Experimental retained-texture service (currently OpenGL only). IDs are
|
|
* nonzero, stable game-owned identities, NOT GL names. Banks are immutable
|
|
* 16-bit PS1 texels/indices with a caller-selected row pitch (width).
|
|
* A missing bank may be reconstructed from original assets by the resolver,
|
|
* including when a restored DMA queue refers to a previously unseen level. */
|
|
typedef int (*PSXModTextureBankResolver)(uint16_t id);
|
|
int psx_mod_texture_banks_supported(void);
|
|
int psx_mod_define_texture_bank(uint16_t id, uint32_t width, uint32_t height,
|
|
const uint16_t* pixels);
|
|
void psx_mod_set_texture_bank_resolver(PSXModTextureBankResolver resolver);
|
|
/* Default-off GL optimization: batch immutable-bank semi triangles in painter
|
|
* order on the single-pass dual-source path only. Ordinary VRAM, subtractive
|
|
* blending and destination-mask checks retain per-primitive isolation. Call
|
|
* from activation or an emulation-thread render boundary. */
|
|
void psx_mod_set_texture_bank_batching(int enabled);
|
|
/* A dedicated GPU-DMA packet arena. Only GT3 commands sourced from this
|
|
* allocation interpret C1/C2's otherwise-unused high bytes as a bank ID:
|
|
* id = (C1 >> 24) | ((C2 >> 24) << 8). ID zero uses ordinary VRAM. The rest
|
|
* of the packet is standard GP0, retaining OT order, palettes and STP blend.
|
|
* Optional 40-byte suffix after the 40-byte tagged GT3: u32 magic 0x48545031,
|
|
* three IEEE float 1/z weights, six IEEE float x/y coordinates. This enables
|
|
* precise perspective rendering without transient host-pointer side tables.
|
|
* Allocate during activation; do not mix stock game packets into this arena. */
|
|
uint32_t psx_mod_alloc_texture_packet_memory(uint32_t size, uint32_t alignment);
|
|
uint8_t psx_mod_read_byte(uint32_t address);
|
|
void psx_mod_write_byte(uint32_t address, uint8_t value);
|
|
uint16_t psx_mod_read_half(uint32_t address);
|
|
void psx_mod_write_half(uint32_t address, uint16_t value);
|
|
uint32_t psx_mod_read_word(uint32_t address);
|
|
void psx_mod_write_word(uint32_t address, uint32_t value);
|
|
/*
|
|
* Replace one guest instruction and route that address through the runtime's
|
|
* executable-RAM path. Use this instead of psx_mod_write_word for code so a
|
|
* restored save state cannot leave the compiled instruction stale.
|
|
*/
|
|
void psx_mod_write_code_word(uint32_t address, uint32_t value);
|
|
|
|
/*
|
|
* Services shared by seamless-loading adapters (resident disc data: see
|
|
* mod_resident.h). Emulation-thread callbacks only.
|
|
*
|
|
* Run a guest function to completion from a hook: a0..a3 and $ra are set,
|
|
* control returns when the guest reaches return_address, and the caller's
|
|
* GPRs, PC, HI and LO are restored. COP0, GTE and timing deadlines keep the
|
|
* callee's effects, as they would after an ordinary call. Snapshots wait
|
|
* until the call returns (the host stack holds the continuation). Returns v0.
|
|
*/
|
|
uint32_t psx_mod_call_guest(struct CPUState* cpu, uint32_t function,
|
|
uint32_t return_address, uint32_t a0, uint32_t a1,
|
|
uint32_t a2, uint32_t a3);
|
|
/* psx_mod_call_guest in zero guest time, for CPU/RAM work a game spends guest
|
|
* time on while it loads (allocators, decoders, table builds). Every RAM,
|
|
* register and device effect of the callee is kept, as if it executed in an
|
|
* instant at the call's guest cycle: none of its cycles are charged, no
|
|
* device advances and no interrupt is taken while it runs, and the caller's
|
|
* pending mult/div, GTE and load-pipeline timing is as it was before the
|
|
* call. Device work the callee starts (a DMA, a CD command) proceeds in guest
|
|
* time after the return. A callee that executes more than budget_cycles
|
|
* (0 = no limit) is charged in full from then on, so one that waits on a
|
|
* device still completes; *charged (optional) is then set to 1. Inside a
|
|
* render pass or another uncharged call, time is already frozen and this is
|
|
* psx_mod_call_guest. The outcome depends only on guest state, so netplay
|
|
* peers and rollback re-simulation agree. Returns v0. */
|
|
uint32_t psx_mod_call_guest_uncharged(struct CPUState* cpu, uint32_t function,
|
|
uint32_t return_address, uint32_t a0,
|
|
uint32_t a1, uint32_t a2, uint32_t a3,
|
|
uint32_t budget_cycles, int* charged);
|
|
/* psx_mod_call_guest_uncharged that also reports the guest cycles the callee
|
|
* executed (*counted_cycles; 0 when time was already frozen, so the call ran
|
|
* inside an outer span). An adapter can then charge its own estimate, e.g. a
|
|
* share of the measured cost, instead of nothing. */
|
|
uint32_t psx_mod_call_guest_uncharged_counted(struct CPUState* cpu,
|
|
uint32_t function, uint32_t return_address, uint32_t a0, uint32_t a1,
|
|
uint32_t a2, uint32_t a3, uint32_t budget_cycles, int* charged,
|
|
uint64_t* counted_cycles);
|
|
/* Deliver disc sectors into RAM exactly as a completed CD-ROM DMA would
|
|
* (overlay capture, executable-page invalidation, CD DMA log when lba >= 0).
|
|
* Word-aligned address and length. Returns 0 when the span leaves RAM. */
|
|
int psx_mod_dma_write_ram(uint32_t address, const void* data, uint32_t bytes,
|
|
int lba);
|
|
/* Store bytes through the CPU store path (any alignment): for data the
|
|
* original code produces with CPU stores, e.g. a decompressor's output. */
|
|
int psx_mod_host_write_ram(uint32_t address, const void* data, uint32_t bytes);
|
|
/* PsyQ SpuWrite by DMA, completed synchronously: transfer address, DMA-write
|
|
* transfer mode, the words through the SPU's DMA write path (address
|
|
* advance and sample-IRQ checks), then transfer mode stop when stop_after.
|
|
* spu_address 8-aligned, guest_source/bytes word aligned, at most 512 KiB;
|
|
* the transfer address wraps at the end of SPU RAM as on hardware.
|
|
* Library bookkeeping (transfer callbacks, busy flags) stays the caller's. */
|
|
int psx_mod_spu_upload(uint32_t spu_address, uint32_t guest_source,
|
|
uint32_t bytes, int stop_after);
|
|
/* Private immutable ADPCM residency for opt-in title mods. Does not upload
|
|
* into or expand the 512 KiB hardware RAM. Bind before the original driver
|
|
* queues a voice; the bank is latched when its next KEYON reaches the SPU. */
|
|
int psx_mod_spu_sample_bank(uint32_t bank, const void *adpcm, uint32_t bytes);
|
|
int psx_mod_spu_bind_voice_bank(unsigned voice, uint32_t bank);
|
|
int psx_mod_spu_private_voice_play(unsigned slot,uint32_t bank,const uint16_t regs[8],unsigned mode);
|
|
void psx_mod_spu_private_voice_stop(uint32_t mask);
|
|
int psx_mod_spu_private_voice_volume(unsigned slot,uint16_t left,uint16_t right);
|
|
uint32_t psx_mod_spu_private_voice_active(void);
|
|
/* PsyQ LoadImage completed synchronously: texture-cache flush, GP0 A0h
|
|
* rectangle copy of w*h 16-bit pixels from guest RAM (provenance attributed
|
|
* per word), then GP1(04h) DMA direction CPU->GP0 for uploads the library
|
|
* would DMA (16 words or more). Caller drains earlier GPU work first. */
|
|
int psx_mod_psyq_load_image(uint32_t x, uint32_t y, uint32_t w, uint32_t h,
|
|
uint32_t guest_source);
|
|
|
|
/*
|
|
* Allocate opt-in enhancement memory from Expansion 1. Until the first
|
|
* allocation, the region remains hardware-faithful open bus. The returned
|
|
* KSEG0 address is accessible through normal generated guest loads.
|
|
*/
|
|
uint32_t psx_mod_alloc_guest_memory(uint32_t size, uint32_t alignment);
|
|
|
|
/*
|
|
* Allocate guest memory that is also addressable by 24-bit GPU linked-list
|
|
* tags. This is intended for opt-in enhanced primitive/ordering-table arenas;
|
|
* without an allocation the aperture remains unmapped and DMA stays faithful.
|
|
*/
|
|
uint32_t psx_mod_alloc_gpu_dma_memory(uint32_t size, uint32_t alignment);
|
|
|
|
/*
|
|
* Opt into expanded 8 MiB main RAM for this launch. Default runtime behavior
|
|
* remains stock 2 MiB mirroring unless a trusted activation plugin requests
|
|
* this before memory_init().
|
|
*/
|
|
int psx_mod_set_main_ram_8mb(int enabled);
|
|
|
|
/* World-culling envelope in native game pixels, including safety guards. */
|
|
int32_t psx_mod_widescreen_x_margin(void);
|
|
/* Configured per-side visible reveal, excluding culling guards; zero at 4:3.
|
|
* Use for screen-space layout, including the first frame of a new scene. */
|
|
int32_t psx_mod_widescreen_view_x_margin(void);
|
|
/* Frame-local presentation origin for native-wide world primitives. Positive
|
|
* shifts move the world right; HUD tags retain their physical-frame anchors.
|
|
* Call after native camera update every frame; disable outside qualified scenes.
|
|
* No guest camera, projection or packet coordinates are changed. */
|
|
void psx_mod_set_widescreen_view_anchor(int enabled, int32_t shift);
|
|
|
|
/* Enable render-only recovery of saturated horizontal GTE projections and
|
|
* camera-plane clipping in native-wide gameplay. Requires exact packet-address/
|
|
* word provenance and depth; never changes guest SXY or the 4:3 path. */
|
|
void psx_mod_set_native_wide_projection_correction(int enabled);
|
|
/* Deprecated compatibility entry point; enabled is ignored. Camera/view-plane
|
|
* clipping is automatic with projection correction above. The GL path requires
|
|
* signed projection through exact PGXP word transport; 4:3, software and
|
|
* untracked UI stay on their existing paths. No guest GTE/gameplay changes. */
|
|
void psx_mod_set_native_wide_near_clip(int enabled);
|
|
/* Bind render-only NCLIP branch consumers to full instruction words. These
|
|
* recover winding only when valid horizontal projections saturated; guest
|
|
* MAC0 and flags remain architectural. Empty registration disables the sites. */
|
|
void psx_mod_set_native_wide_nclip_sites(const uint32_t* addresses,
|
|
const uint32_t* expected, int count);
|
|
/* After registering sites, bind a verified first-of-two quad branch to the
|
|
* preceding NCLIP result. Exact address/word must match an existing site;
|
|
* registering the base site list again resets every site to the latest result. */
|
|
void psx_mod_set_native_wide_nclip_previous_site(uint32_t address, uint32_t expected);
|
|
|
|
/* Mark a guest GPU packet (P_TAG address) as persistent screen-space HUD.
|
|
* edge = -1 left, +1 right, 0 clears a reused packet's tag. The native-wide
|
|
* compositor translates it by the live reveal, excluding culling guards.
|
|
* Guest coordinates, world sprites, and native 4:3 remain unchanged. */
|
|
void psx_mod_tag_hud_primitive(uint32_t primitive, int edge);
|
|
/* Packet-guarded screen-space anchor: -1 left, +1 right, 0 stays centred.
|
|
* Unlike the legacy role tag above, zero explicitly protects centred text
|
|
* from automatic layout transforms. Native-wide translates the group;
|
|
* projection-and-stretch squashes around the selected screen anchor. This
|
|
* works across separate DMA lists and is inert at native 4:3. Title code
|
|
* owns classification and must tag each completed packet. Pass the word
|
|
* immediately before the GP0 colour command; for a compound E1+SPRT packet
|
|
* this is its E1 word (P_TAG+4). */
|
|
void psx_mod_anchor_hud_primitive(uint32_t primitive, int edge);
|
|
/* As psx_mod_anchor_hud_primitive, for a packet whose X coordinates came out
|
|
* of the GTE (so they already carry the widescreen squash about the display
|
|
* centre, and the guest wrote them verbatim). Projection-and-stretch moves the
|
|
* piece to the selected edge by translation, keeping its proportions; scaling
|
|
* it about the edge again would squash it twice. */
|
|
void psx_mod_anchor_hud_primitive_projected(uint32_t primitive, int edge);
|
|
/* Did the GTE project a vertex at screen (x, y) in this or the previous
|
|
* vblank? Under the widescreen squash a primitive whose every vertex answers
|
|
* yes was built from projected geometry (already squashed); one that does not
|
|
* carries authored screen coordinates. Always 0 while the squash is off. */
|
|
int psx_mod_gte_sxy_recent(int32_t x, int32_t y);
|
|
/* Opt in to (1) or out of (0) the recent-projection record behind
|
|
* psx_mod_gte_sxy_recent. Off by default: until a plugin enables it nothing is
|
|
* recorded and the query answers 0. Call from an activation callback. */
|
|
void psx_mod_set_gte_sxy_tracking(int enabled);
|
|
/* Screen-space masks identified by their title's producer, P_TAG addresses.
|
|
* Flat panels extend only their exterior boundaries. Radial flat/Gouraud
|
|
* quads scale around the display centre for an aspect-aware iris transition.
|
|
* Both services are render-only, word guarded and inert at native 4:3. */
|
|
void psx_mod_tag_screen_mask_quad(uint32_t primitive);
|
|
void psx_mod_tag_radial_screen_mask_quad(uint32_t primitive, float scale);
|
|
/* Exclude a known world packet from screen-space backdrop stretching, even
|
|
* if it sorts before the first shaded polygon. Zero clears a recycled tag. */
|
|
void psx_mod_tag_world_primitive(uint32_t primitive, int is_world);
|
|
/* A 15-bit sprite (P_TAG address) that copies a framebuffer 1:1, such as a
|
|
* motion trail: native-wide continues it into the bands. Tag it each frame
|
|
* after the title built it; inert at native 4:3. */
|
|
void psx_mod_tag_feedback_rect(uint32_t primitive);
|
|
/* Enable aspect-derived column selection for a title that opted into the
|
|
* auto_backdrop detector. No effect on titles that did not opt in. */
|
|
void psx_mod_set_adaptive_backdrop_preload(int enabled);
|
|
|
|
/*
|
|
* Width, in native game pixels, of the picture the guest is currently
|
|
* scanning out -- the same value the presenter uses, derived from the display
|
|
* mode and the GP1(06h) horizontal range.
|
|
*
|
|
* Why this exists: a plugin that draws its own overlay primitives needs to
|
|
* know where the right-hand edge of the screen is, and it cannot work that
|
|
* out for itself. GPUSTAT carries the horizontal-resolution bits, so a plugin
|
|
* can recover the coarse MODE width (256/320/512/640, or 368), but the
|
|
* visible width also depends on the GP1(06h) X1/X2 range, which is write-only
|
|
* and mirrored nowhere the plugin can read. Ape Escape is the worked example:
|
|
* it scans out 384 while its mode width is 368, and a plugin that assumed the
|
|
* usual 320 put its HUD row 68 pixels short of the edge.
|
|
*
|
|
* Returns 0 if the display geometry is not yet established, in which case the
|
|
* caller should skip drawing rather than substitute a guess.
|
|
*/
|
|
uint32_t psx_mod_display_width(void);
|
|
|
|
/* Height companion to psx_mod_display_width(); same conventions. */
|
|
uint32_t psx_mod_display_height(void);
|
|
|
|
/* Opt-in presentation hold for a game that retains its previous framebuffer
|
|
* while loading. The pure, cheap emulation-thread predicate returns HOLD
|
|
* only while that SAME scene remains displayed; no GPU/API recursion allowed.
|
|
* RELEASE resumes normal classification immediately. UNTIL_FLIP releases a
|
|
* prior HOLD only once the displayed VRAM origin changes: useful when drawing
|
|
* the next backbuffer finishes before it becomes visible. UNTIL_FLIP without
|
|
* a prior HOLD does nothing. Do not use it for in-place scene replacements.
|
|
* Native-wide retains its prior wide/4:3 classification, never stretches art
|
|
* and never overrides FMV. NULL removes the opt-in. Host history is discarded
|
|
* on GPU reset/savestate restore, so loading a frozen scene cannot recreate
|
|
* missing widescreen strips. This does not change guest rendering or memory. */
|
|
typedef int (*PSXModRetainedScenePredicate)(void);
|
|
enum {
|
|
PSX_MOD_SCENE_RELEASE = 0,
|
|
PSX_MOD_SCENE_HOLD = 1,
|
|
PSX_MOD_SCENE_UNTIL_FLIP = 2
|
|
};
|
|
void psx_mod_set_retained_scene_predicate(PSXModRetainedScenePredicate predicate);
|
|
|
|
/* Opt-in supplemental native-wide world classifier. A nonzero result marks a
|
|
* known, rendered 3D scene (for example a real-time intro) as world content even
|
|
* when a game's gameplay-state allowlist excludes it. Zero defers to the normal
|
|
* classifier; NULL removes the callback. Only native-wide mode consults it.
|
|
* FMV and retained-frame presentation rules still apply. The pure, cheap callback
|
|
* runs on the emulation thread: no GPU calls, allocation or guest-state writes.
|
|
* Registration is host configuration, not savestate data. No default behavior
|
|
* changes and no extra geometry is generated by this service. */
|
|
typedef int (*PSXModWorldScenePredicate)(void);
|
|
void psx_mod_set_world_scene_predicate(PSXModWorldScenePredicate predicate);
|
|
|
|
/*
|
|
* 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);
|
|
/* Read an option of the package/feature whose trusted callback is running.
|
|
* Shared framework plugins use this without hard-coding a title package id.
|
|
* The same committed-plan and buffer rules as psx_mod_option_value apply. */
|
|
int psx_mod_current_option_value(const char* option_id,
|
|
char* out, uint32_t out_size);
|
|
/*
|
|
* Live option changes (in-game overlay P5). A trusted plugin that can re-read
|
|
* an option on a running game registers an option-changed callback under its
|
|
* plugin id; it runs in that plugin's context (psx_mod_current_option_value
|
|
* already answers the new value) and returns 1 if the change took effect now.
|
|
*
|
|
* psx_mod_set_option_live records a new value for a committed feature's
|
|
* option for the rest of the session and notifies its plugin. Returns 1 when
|
|
* a plugin applied it live; 0 when the feature has no live-capable plugin (or
|
|
* is not in the plan) -- then nothing changes until the next start; -1 when
|
|
* refused: during netplay the plan is negotiated between peers and must not
|
|
* change, except a host-only feature's (psx_mod_option_live_host_only). Persisting the choice stays
|
|
* the mod provider's job (mods/state.toml).
|
|
*/
|
|
typedef int (*PSXModOptionChangedCallback)(const char* option_id, const char* value);
|
|
int psx_mod_register_option_changed_plugin(const char* id,
|
|
PSXModOptionChangedCallback callback);
|
|
int psx_mod_set_option_live(const char* package_id, const char* feature_id,
|
|
const char* option_id, const char* value);
|
|
/* 1 when the committed feature's plugin registered an option-changed callback
|
|
* (so a live set can take effect now). Read-only; any thread. */
|
|
int psx_mod_option_live_capable(const char* package_id, const char* feature_id);
|
|
/* 1 when the committed feature is host-only (every plugin it activates
|
|
* declares netplay = "host_only"): it is the player's own in a match, so its
|
|
* options may change live during netplay (psx_mod_set_option_live), unlike
|
|
* every other feature's. Read-only; any thread, like
|
|
* psx_mod_option_live_capable. */
|
|
int psx_mod_option_live_host_only(const char* package_id, const char* feature_id);
|
|
/* Write the player's current selection (feature switches and option values,
|
|
* as edited through the launcher mod provider) to mods/state.toml now,
|
|
* without re-resolving the running plan. Main thread. 1 on success. */
|
|
int psx_mod_save_selection(void);
|
|
/* Switch a feature on/off or set one of its options in the selection (not
|
|
* the committed plan; psx_mod_save_selection persists it). Main thread.
|
|
* Returns 1 on success. Used by the in-game menu host. */
|
|
int psx_mod_feature_enable(const char* package_id, const char* feature_id, int enabled);
|
|
int psx_mod_feature_set_option(const char* package_id, const char* feature_id,
|
|
const char* option_id, const char* value);
|
|
/*
|
|
* Read the committed owner-selected path for a resource declared by the
|
|
* package feature whose trusted plugin is currently running. Returns 0 when
|
|
* the feature has no selected path for that resource; plugins then leave the
|
|
* stock presentation unchanged.
|
|
*/
|
|
int psx_mod_current_resource_path(const char* resource_id,
|
|
char* out, uint32_t out_size);
|
|
/* Configure the host HD texture pack from this callback's directory resource.
|
|
* Replacements are currently OpenGL-only; dumping also works on software and
|
|
* Vulkan. Configuration is host state, never guest RAM or savestate data.
|
|
* Activation/emulation-thread only. Failure is reported to the runtime log. */
|
|
int psx_mod_set_hd_texture_pack(const char* resource_id,
|
|
int replacements_enabled, int dump_enabled);
|
|
/* Emulation-thread controls for an already configured pack. */
|
|
int psx_mod_set_hd_texture_dump(int enabled);
|
|
int psx_mod_reload_hd_texture_pack(void);
|
|
/* Read-only canonical media verified by the engine for this plugin's owning
|
|
* package/feature. The pointer lives until the committed plan is replaced or
|
|
* cleared. Available only during that plugin's callbacks; returns 0 for an
|
|
* ordinary unverified resource, an inactive feature, or a different owner. */
|
|
int psx_mod_current_resource_bytes(const char* resource_id,
|
|
const uint8_t** bytes, uint64_t* size);
|
|
|
|
/* Append raw Mode-2 sectors (2336 bytes, starting with the XA subheader)
|
|
* from a verified resource owned by this activation callback. Returns their
|
|
* native CD LBA in first_lba. Registration is deterministic, append-only and
|
|
* rejected outside activation, for invalid ranges or beyond 99:59:74.
|
|
* The immutable committed snapshot remains mounted until the plan changes;
|
|
* no host path or donor data is saved into guest RAM/savestates. */
|
|
int psx_mod_append_disc_extent(const char* resource_id, uint64_t byte_offset,
|
|
uint32_t sector_count, uint32_t* first_lba);
|
|
|
|
/* Optional CD-DA playlist, built only during activation. Audio has a separate
|
|
* TOC/timeline (track 1 is a data placeholder); data/XA LBAs are unchanged.
|
|
* Sources are verified immutable raw 2352-byte stereo PCM sectors, or audio
|
|
* tracks on the mounted disc. Playback, reports and saves use the native CD
|
|
* controller. No conversion, resampling or host clock is involved. */
|
|
typedef struct PSXModCDDATrack {
|
|
const char* resource_id; /* non-NULL: verified external raw PCM */
|
|
uint64_t byte_offset;
|
|
uint32_t sector_count;
|
|
uint32_t disc_track; /* resource_id == NULL: mounted audio track */
|
|
} PSXModCDDATrack;
|
|
/* Append the whole group atomically. Both output arrays have count elements;
|
|
* failure leaves the previous playlist intact and clears all outputs. */
|
|
int psx_mod_append_cdda_tracks(const PSXModCDDATrack* tracks, uint32_t count,
|
|
uint32_t* first_lbas, uint32_t* sector_counts);
|
|
|
|
/* Display aspects have no framework ceiling: the native-wide surfaces size
|
|
* themselves from the live width, and each title caps its own view at what
|
|
* it has validated (fixed ratio, or the adaptive maximum below). Requests
|
|
* must be at least native 4:3, with numerator and denominator in 1..99. */
|
|
|
|
/*
|
|
* 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.
|
|
* Pass (0, 0) for Fit to window with no upper aspect limit. Both modes retain
|
|
* the native 4:3 minimum; a single zero is invalid.
|
|
* 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);
|
|
/*
|
|
* HUD size for the auto-UI widescreen HUD ([widescreen] auto_ui_squash):
|
|
* 0 keeps it at the display height's scale ("original"), 1 makes it
|
|
* proportional (unchanged up to 16:9, shrinking by sqrt((16:9) / aspect)
|
|
* beyond, about each widget's anchors). Overrides game.toml
|
|
* [widescreen] auto_ui_size for the session; every session start restores the
|
|
* title's setting before activation.
|
|
*/
|
|
int psx_mod_set_widescreen_hud_size(int proportional);
|
|
/* Request an internal rendering height for this mod session (120..8192).
|
|
* The existing resolution resolver selects an integer raster scale and the
|
|
* backend applies its normal allocation limits. Zero clears the request.
|
|
* It wins over the built-in Internal Resolution mod's choice and the title's
|
|
* baseline (both left unchanged); explicit PSX_INTERNAL_RESOLUTION overrides
|
|
* win over it, and each session clears this request before activating its
|
|
* committed mod plan. */
|
|
int psx_mod_set_internal_resolution(uint32_t target_lines);
|
|
/*
|
|
* 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
|
|
* temporally blends completed guest frames at the requested output rate on its
|
|
* owning render thread/context. It does not derive motion vectors or generate
|
|
* true intermediate object positions.
|
|
* A value of zero follows the measured host-display refresh rate.
|
|
*/
|
|
int psx_mod_set_frame_interpolation(uint32_t frames_per_second);
|
|
/*
|
|
* Choose how the OpenGL presenter combines completed frames. Linear is a
|
|
* full-frame crossfade. Motion-adaptive retains temporal blending for
|
|
* small temporal changes but switches large changes cleanly to reduce the
|
|
* double-image trails produced by moving objects.
|
|
*/
|
|
enum {
|
|
PSX_MOD_FRAME_INTERPOLATION_LINEAR = 0,
|
|
PSX_MOD_FRAME_INTERPOLATION_MOTION_ADAPTIVE = 1,
|
|
/* No crossfade: every output frame repeats the newest game frame. For a
|
|
* plugin that supplies its own in-between images with render passes
|
|
* (below); wherever it has none, the output matches stock timing and
|
|
* nothing is shown later than the game shows it. */
|
|
PSX_MOD_FRAME_INTERPOLATION_HOLD = 2
|
|
};
|
|
/* Usually called from activation. It may also be called later from the
|
|
* emulation thread (a function-entry hook or VBlank callback), e.g. to swap
|
|
* HOLD for a crossfade while render passes are unavailable; the OpenGL
|
|
* presenter then uses the new mode from its next present. */
|
|
int psx_mod_set_frame_interpolation_blend(uint32_t blend_mode);
|
|
/*
|
|
* Choose what the OpenGL presenter treats as a new source frame. VBLANK (the
|
|
* default, reset at every session start) treats every guest VBlank as one,
|
|
* which suits games that flip every VBlank. FLIP rotates the blend history
|
|
* only when the guest really flips (the displayed VRAM origin moves, or the
|
|
* displayed rect is redrawn) and spreads each crossfade over the measured
|
|
* flip period (1..4 VBlanks). A 30 Hz game then blends across its whole frame
|
|
* instead of blending for one VBlank and holding for the next. Guest timing
|
|
* is unchanged either way.
|
|
*/
|
|
enum {
|
|
PSX_MOD_FRAME_SOURCE_VBLANK = 0,
|
|
PSX_MOD_FRAME_SOURCE_FLIP = 1
|
|
};
|
|
int psx_mod_set_frame_interpolation_source(uint32_t source);
|
|
|
|
/*
|
|
* Host-timed render passes: true in-between frames for a game whose logic
|
|
* runs slower than the presentation rate. Default off: nothing happens unless
|
|
* a trusted plugin calls these. OpenGL, frame interpolation enabled with the
|
|
* FLIP source, never in netplay, rollback, rewind, fast-forward (manual,
|
|
* turbo-through-loads, FMV auto-skip) or while the presenter is suspended
|
|
* (FMV); psx_mod_render_pass_plan() returns 0 then, and
|
|
* psx_mod_render_pass_status() says why.
|
|
*
|
|
* Call both from an emulation-thread function-entry hook placed where the
|
|
* game has finished its logic for game frame N+1 but the display still has to
|
|
* flip to frame N (for a PsyQ double-buffered loop: the VSync(0) that precedes
|
|
* PutDispEnv). Each pass runs `fn`, which may call guest functions with
|
|
* psx_dispatch_call() to draw an intermediate image of the scene. While it
|
|
* runs, guest time is frozen: cycles are counted but no device advances, no
|
|
* interrupt is delivered and GPU DMA completes synchronously; SPU, CD, timer
|
|
* and other device stores are dropped (counted). Afterwards CPU state
|
|
* (including the GTE), RAM, scratchpad, I-cache tags, interrupt, timer, DMA
|
|
* and GPU registers, and the VRAM rect are exactly as before: the pass's only
|
|
* product is the image of the rect, which the presenter shows at `alpha_q16`
|
|
* of the way through frame N's time on screen (Q16, 0 = frame N's own image).
|
|
* The display rect is the one the next flip shows (its DISPENV); the pass may
|
|
* draw only inside it.
|
|
*/
|
|
/* Returns nonzero to keep the image, 0 to discard it (state is restored
|
|
* either way). */
|
|
typedef int (*PSXModRenderPassFn)(struct CPUState* cpu, void* user,
|
|
uint32_t alpha_q16);
|
|
typedef struct PSXModRenderPass {
|
|
uint32_t struct_size; /* sizeof(PSXModRenderPass) */
|
|
uint32_t alpha_q16; /* a phase returned by the plan */
|
|
uint16_t x, y, w, h; /* VRAM display rect the pass draws */
|
|
} PSXModRenderPass;
|
|
/*
|
|
* Phases (Q16, ascending, excluding 0) at which the presenter will actually
|
|
* show frame N: its output deadlines during the `period_vblanks` guest VBlanks
|
|
* the frame stays on screen, starting after `shown_after_vblanks` more
|
|
* VBlank presents (R4 at its VSync(0) entry: 1 -- the next VBlank still shows
|
|
* the previous frame). When the host cannot afford them all, an evenly spread
|
|
* subset is returned and the presenter crossfades the gaps. The first
|
|
* psx_mod_render_pass() after a plan captures frame N's own image. Returns 0
|
|
* when passes are unavailable or unaffordable.
|
|
*/
|
|
uint32_t psx_mod_render_pass_plan(uint32_t period_vblanks,
|
|
uint32_t shown_after_vblanks,
|
|
uint32_t* alpha_q16, uint32_t max);
|
|
/* Returns 1 when the pass ran and its image was queued, 0 when it was refused
|
|
* or rolled back (state is restored either way).
|
|
* A watchdog abort rolls the pass back by longjmp, past every frame between
|
|
* the watchdog and psx_mod_render_pass(): plugin callbacks, guest functions
|
|
* and function-entry hooks they called. Runtime nesting state (including the
|
|
* function-entry context) is restored, but plugin-owned state, held locks and
|
|
* C++ destructors in those frames are NOT unwound. A callback run inside a
|
|
* pass must keep nothing that needs cleanup (no locks, no RAII objects, no
|
|
* partially updated plugin state) across guest code, or tolerate the abort. */
|
|
int psx_mod_render_pass(struct CPUState* cpu, const PSXModRenderPass* pass,
|
|
PSXModRenderPassFn fn, void* user);
|
|
/* Isolated dual-matte draw/readback using the stereo-capable rollback
|
|
* transaction. Draws two mattes of the same slice, the first into eye 0 of the
|
|
* stage surface and the second into eye 1, so both planes of a frame cost one
|
|
* readback instead of two. Never publishes an eye pair or changes presentation.
|
|
* Returns native-sized bottom-to-top RGBA8 pixels (alpha remains the PSX mask
|
|
* bit, not coverage). Call outside another pass. rgba_bytes must cover
|
|
* 2*rect->w*rect->h*4; the first matte arrives in the buffer's first half and
|
|
* the second in its second half. */
|
|
int psx_mod_render_capture_rgba_pair(struct CPUState *cpu,
|
|
const PSXModRenderPass *rect,
|
|
PSXModRenderPassFn fn,void *user_first,
|
|
void *user_second,uint8_t *rgba,
|
|
uint32_t rgba_bytes);
|
|
#define PSX_MOD_RENDER_CAPTURE_RGBA_PAIR 1
|
|
/*
|
|
* Why passes cannot run right now, the host-time budget aside (a plan that
|
|
* returns 0 while this says READY was shed for time). A plugin that relies on
|
|
* passes uses it to fall back, e.g. to a crossfade with
|
|
* psx_mod_set_frame_interpolation_blend(), while the reason lasts.
|
|
* NO_PRESENTER, BACKEND and DISABLED persist; the others are transient.
|
|
*/
|
|
enum {
|
|
PSX_MOD_RENDER_PASS_READY = 0,
|
|
/* Not OpenGL, interpolation off or suspended (FMV), or not the FLIP
|
|
* source. */
|
|
PSX_MOD_RENDER_PASS_NO_PRESENTER = 1,
|
|
/* The renderer declines passes in its current mode. */
|
|
PSX_MOD_RENDER_PASS_BACKEND = 2,
|
|
/* Switched off for this session after repeated faults. */
|
|
PSX_MOD_RENDER_PASS_DISABLED = 3,
|
|
/* Netplay, rollback, rewind, load/save replay or self-check resim. */
|
|
PSX_MOD_RENDER_PASS_SESSION = 4,
|
|
/* Fast-forward, turbo-through-loads or FMV auto-skip is running. */
|
|
PSX_MOD_RENDER_PASS_FAST_FORWARD = 5,
|
|
/* Inside an exception, a pass or a GPU DMA walk, or the presenter has not
|
|
* captured a frame since its history restarted (display mode change). */
|
|
PSX_MOD_RENDER_PASS_BUSY = 6
|
|
};
|
|
uint32_t psx_mod_render_pass_status(void);
|
|
/*
|
|
* Netplay local view: this peer's own image of a display rect, drawn by the
|
|
* game's code inside the render-pass sandbox, replaces what the presenter
|
|
* shows of that rect. For a title whose netplay frame draws every seat's view
|
|
* (so guest state stays identical on every peer) but whose players should
|
|
* each see their own seat's single full-screen view.
|
|
*
|
|
* `fn` runs as a render pass does (frozen guest time, sandboxed stores, the
|
|
* watchdog; CPU with the GTE, RAM, scratchpad, devices and the authoritative
|
|
* VRAM restored afterwards) and draws into rect->x/y/w/h; alpha_q16 is 0. When
|
|
* it returns nonzero the presenter's own copy of the rect keeps the image
|
|
* until the guest draws there again: the canonical frame stays in the
|
|
* authoritative VRAM, savestates, rollback snapshots and digests. A committed
|
|
* image also cancels any psx_netplay_present_local_view() crop. Call it where
|
|
* the next flip will show rect and the guest has finished drawing it.
|
|
*
|
|
* Only in a netplay session on forward frames, with the OpenGL presenter
|
|
* keeping a surface separate from the authoritative VRAM (dual raster); never
|
|
* while resimulating, in rewind, lockstep replay, fast-forward, inside an
|
|
* exception or a pass. psx_mod_render_local_view_status() says why not, with
|
|
* the PSX_MOD_RENDER_PASS_* reasons; a title then shows its canonical frame
|
|
* (for example its own view's part of it through
|
|
* psx_netplay_present_local_view). Returns 1 when the image was committed.
|
|
*/
|
|
int psx_mod_render_local_view(struct CPUState* cpu,
|
|
const PSXModRenderPass* rect,
|
|
PSXModRenderPassFn fn, void* user);
|
|
uint32_t psx_mod_render_local_view_status(void);
|
|
/* 1 while a psx_mod_render_local_view draw runs. In that scope a netplay
|
|
* match's own-view mods ([[plugin]] netplay = "local_view") run their hooks
|
|
* and the widescreen cull margin is this peer's; outside it neither touches
|
|
* the shared simulation. */
|
|
int psx_mod_local_view_scope(void);
|
|
/* Simultaneous stereo capture, independent of temporal interpolation. Each
|
|
* eye starts from the same guest state; CPU/RAM/devices/VRAM are restored
|
|
* before the other eye and on failure. Publish only after both succeed.
|
|
* The caller provides a draw-only callback at a main-thread frame boundary.
|
|
* period_vblanks (1..8) describes the game's draw cadence for whole-pair cost
|
|
* shedding. No alpha/time phase is used. Returns 1 for a published pair. */
|
|
enum { PSX_MOD_EYE_LEFT = 0, PSX_MOD_EYE_RIGHT = 1 };
|
|
typedef struct PSXModStereoFrame {
|
|
uint32_t struct_size;
|
|
uint32_t period_vblanks;
|
|
uint16_t x, y, w, h;
|
|
} PSXModStereoFrame;
|
|
typedef int (*PSXModStereoFn)(struct CPUState*, void*, uint32_t eye);
|
|
int psx_mod_render_stereo(struct CPUState *cpu, const PSXModStereoFrame *frame,
|
|
PSXModStereoFn fn, void *user);
|
|
uint32_t psx_mod_render_stereo_status(void);
|
|
/* Annotate the current frame's trace record with a short tag (scene transition,
|
|
* state change). Only meaningful while PSX_FRAME_TRACE is on and a traced frame
|
|
* is open (a headset frame, opened by psx_mod_openxr_begin); a no-op otherwise. */
|
|
void psx_mod_frame_trace_note(const char *tag);
|
|
/* Bracket one guest call of a stereo eye's replay with a frame-trace sub-stage,
|
|
* so a hitch can be aimed at the call that grew instead of at the whole eye
|
|
* (the PSX_TRACE_GAME_* stages in frame_trace.h). The runtime does the timing,
|
|
* with the same clock and conversion as the eye's own stages, so the parts sum
|
|
* against eye_guest. A no-op unless PSX_FRAME_TRACE is on AND the call is made
|
|
* inside a traced stereo eye: a HUD capture's guest draws, the flat path and
|
|
* anything outside the eye's callback are ignored. A begin without a matching
|
|
* end (or an eye aborted mid-entry) contributes nothing; the unmeasured time
|
|
* stays visible as the difference between eye_guest and the sub-stages.
|
|
* Spans may nest: each stage keeps its own start tick, so a stage bracketed
|
|
* inside another is timed on its own and reports a subset of the outer span's
|
|
* wall time rather than a sibling of it. The hook's visibility override nests
|
|
* inside the guest traversal it is part of (game_select in game_traverse). */
|
|
void psx_mod_frame_trace_stage_begin(uint32_t stage);
|
|
void psx_mod_frame_trace_stage_end(uint32_t stage);
|
|
/* Drop the stereo shedding policy's pair-time estimate and derived budget. Call
|
|
* on a scene/mode transition so a new scene does not inherit the previous one's
|
|
* timing; cheap and idempotent. Shedding itself is unchanged. */
|
|
void psx_mod_stereo_reset_budget(void);
|
|
/* Set the stereo pair budget from a real headset deadline in milliseconds,
|
|
* pushed by the OpenXR layer from the target display period (11.1 ms at 90 Hz,
|
|
* 8.3 ms at 120 Hz). 0 clears it. The guest-VBlank-derived value is only a
|
|
* fallback: a PSX redraw budget is not an OpenXR display deadline, and the two
|
|
* disagree on every non-60 Hz headset. PSX_STEREO_SHED_BUDGET_MS still wins. */
|
|
void psx_mod_stereo_set_budget_ms(double ms);
|
|
/* 0 disables stereo output; 1 shows a complete pair side by side. Capture
|
|
* itself does not enable presentation. Default 0; applies to this session. */
|
|
int psx_mod_set_stereo_presentation(uint32_t mode);
|
|
/* Camera-space addition to RT*V+TR before RTPS/RTPT perspective division.
|
|
* Valid only inside a render callback. Replaces (never accumulates) the host
|
|
* offset and is restored automatically on completion or watchdog abort.
|
|
* Units are the game's GTE camera units; IPD/world-scale calibration is game
|
|
* specific. Zero is faithful. Guest TR registers are not modified. */
|
|
int psx_mod_render_view_offset(int32_t x, int32_t y, int32_t z);
|
|
/* Rigid camera transform after guest RT*V+TR, before division. Rotation is
|
|
* row-major Q12; translation uses camera units. Optional projection supplies
|
|
* focal lengths and centre deltas from guest OFX/OFY in Q16 pixel units.
|
|
* Identity rotation with projection=0 preserves the architectural path.
|
|
* projection=1 REPLACES the guest X/Y projection (guest H, widescreen squash and
|
|
* [video] fov_scale do not apply); projection_h_ref != 0 scales the focal lengths
|
|
* by (fov-scaled guest H)/ref. Valid only inside a render callback; restored with it. */
|
|
typedef struct PSXModRenderView {
|
|
uint32_t struct_size;
|
|
int32_t rotation_q12[9], translation[3];
|
|
uint32_t projection;
|
|
uint32_t projection_h_ref; /* 0 absolute FOV; otherwise scale focal lengths by guest H/ref */
|
|
int32_t fx_q16, fy_q16, cx_delta_q16, cy_delta_q16;
|
|
/* Optional host-only pose for projective replay. The ordinary path uses
|
|
* Q12/integer values; precise 1 preserves sub-unit movement inside replay. */
|
|
uint32_t precise;
|
|
double rotation[9], translation_precise[3];
|
|
} PSXModRenderView;
|
|
int psx_mod_render_view(const PSXModRenderView *view);
|
|
/* Signed homogeneous projection and near clipping for replayed 3D packets.
|
|
* Requires intact PGXP provenance; native gameplay stays architectural. */
|
|
void psx_mod_set_render_projective(int enabled);
|
|
/* Begin locates both views at one predicted time. End submits only a fresh
|
|
* complete pair; failed/shed redraws submit zero layers. */
|
|
int psx_mod_openxr_enable(int enabled);
|
|
int psx_mod_openxr_begin(uint32_t width, uint32_t height, double units_per_meter);
|
|
int psx_mod_openxr_view(uint32_t eye, PSXModRenderView *view);
|
|
int psx_mod_openxr_end(int pair_rendered);
|
|
/* Frame-local UI surface: the fresh pair's left image is shown to both eyes
|
|
* on a head-relative quad. Call after begin, outside the draw transaction.
|
|
* Dimensions and distance are meters; zero distance restores projection.
|
|
* Requests reset at begin/end and never affect faithful guest rendering. */
|
|
int psx_mod_openxr_quad(double distance_m, double width_m, double height_m);
|
|
/* Additive, frame-local LOCAL-space surface. Called between XR begin/end and
|
|
* outside guest replay. Pixels are tightly packed bottom-to-top sRGB RGBA8,
|
|
* straight alpha, copied synchronously; caller retains ownership. IDs 0..1.
|
|
* Pose is raw XR LOCAL meters/quaternion x,y,z,w, not recentered game units.
|
|
* Missing requests disappear next frame. Returns 0 without replacing scene UI. */
|
|
typedef struct PSXModOpenXRSurface {
|
|
uint32_t struct_size, id, width_px, height_px, flags;
|
|
float position_m[3], orientation_xyzw[4], size_m[2];
|
|
const uint8_t *rgba;
|
|
} PSXModOpenXRSurface;
|
|
#define PSX_MOD_OPENXR_SURFACES 1
|
|
/* Ignore surface orientation: aim at predicted eye midpoint, upright in LOCAL
|
|
* space. Both eyes use the same plane. Position remains caller-controlled. */
|
|
#define PSX_MOD_OPENXR_SURFACE_FACE_HEAD 1u
|
|
int psx_mod_openxr_surface(const PSXModOpenXRSurface *surface);
|
|
/* Opt-in native presentation surface for boot, videos and menus. Copies the
|
|
* freshly drawn desktop content before host overlays, without guest replay.
|
|
* Zero distance disables it. Applications disable it before scene begin and
|
|
* re-enable when their scene renderer is inactive. Dimensions are meters;
|
|
* height follows the presented content aspect. No retained stereo substitution. */
|
|
int psx_mod_openxr_native_surface(double distance_m, double width_m,
|
|
double units_per_meter);
|
|
void psx_mod_openxr_recenter(void);
|
|
/* Fresh action sample at the offline input boundary, never in an eye replay.
|
|
* Positive Y is forward/up in XR. active[] refers only to thumbsticks;
|
|
* other actions have independent activity. Unavailable/unfocused actions
|
|
* return zero values. Click masks are active-high, unrelated to PSX pad bits. */
|
|
#define PSX_MOD_XR_PRIMARY 1u /* left X / right A */
|
|
#define PSX_MOD_XR_SECONDARY 2u /* left Y / right B */
|
|
#define PSX_MOD_XR_MENU 4u /* Touch left Menu */
|
|
#define PSX_MOD_XR_STICK 8u /* thumbstick click */
|
|
#define PSX_MOD_XR_CLICKS 15u
|
|
typedef struct PSXModOpenXRInput {
|
|
uint32_t struct_size, focused, active[2], synthetic;
|
|
float stick[2][2];
|
|
uint64_t sequence;
|
|
float trigger[2], squeeze[2]; /* [0,1] */
|
|
uint32_t trigger_active[2], squeeze_active[2];
|
|
uint32_t buttons[2], buttons_active[2]; /* PSX_MOD_XR_* masks */
|
|
} PSXModOpenXRInput;
|
|
int psx_mod_openxr_input(PSXModOpenXRInput *input);
|
|
|
|
/* Read-only controller snapshot from the latest located XR frame. Grip and aim
|
|
* share the eye poses' predicted time and LOCAL space; no action sync or locate
|
|
* occurs here, including during eye replay. Positions are meters; quaternions
|
|
* are x,y,z,w. The origin matches the rendered view's recenter basis. Consumers
|
|
* must check focus, activity, validity and age before using a cached pose. */
|
|
enum { PSX_MOD_XR_GRIP_POSE = 0, PSX_MOD_XR_AIM_POSE = 1 };
|
|
#define PSX_MOD_XR_ORIENTATION_VALID 1u
|
|
#define PSX_MOD_XR_POSITION_VALID 2u
|
|
#define PSX_MOD_XR_ORIENTATION_TRACKED 4u
|
|
#define PSX_MOD_XR_POSITION_TRACKED 8u
|
|
typedef struct PSXModTrackedPose {
|
|
uint32_t active, flags;
|
|
float position_m[3], orientation_xyzw[4];
|
|
} PSXModTrackedPose;
|
|
typedef struct PSXModOpenXRHands {
|
|
uint32_t struct_size, focused, synthetic, origin_valid;
|
|
uint64_t sequence, predicted_time;
|
|
uint32_t age_ms; /* UINT32_MAX when no real frame has been located */
|
|
double origin_position_m[3], origin_orientation_xyzw[4];
|
|
PSXModTrackedPose pose[2][2]; /* [left/right][grip/aim] */
|
|
} PSXModOpenXRHands;
|
|
int psx_mod_openxr_hands(PSXModOpenXRHands *hands);
|
|
|
|
|
|
|
|
/*
|
|
* When the game flips relative to the pass point. PENDING (default, reset at
|
|
* every session start): the pass rect is the one the next flip will show --
|
|
* the PsyQ VSync(0)-then-PutDispEnv loop, whose frame N is drawn and waits.
|
|
* SHOWN: the game flips each frame as soon as it is drawn (for example
|
|
* PutDispEnv in a VBlank callback armed by the DrawSync callback), so by the
|
|
* time frame N+1's logic is done frame N is already on screen. The pass rect
|
|
* is then that on-screen rect, and frame N's own image and its in-between
|
|
* images are shown from the game's next flip on: one game frame later than
|
|
* the game shows them, as a host interpolator with one frame of history
|
|
* would. Set it from activation, before the first plan.
|
|
*/
|
|
enum {
|
|
PSX_MOD_RENDER_PASS_FLIP_PENDING = 0,
|
|
PSX_MOD_RENDER_PASS_FLIP_SHOWN = 1
|
|
};
|
|
int psx_mod_set_render_pass_flip(uint32_t mode);
|
|
/* A trusted title may opt into presentation-only passes during forward
|
|
* netplay. Resimulation and lockstep replay still refuse them. The backend
|
|
* restores authoritative CPU VRAM as well as the captured display surface. */
|
|
int psx_mod_set_render_pass_netplay(int enabled);
|
|
|
|
/*
|
|
* The per-frame plumbing every frame-rate plugin repeats (render_pass_frame.c).
|
|
* Blending the game state itself: render_pass_motion.h.
|
|
*
|
|
* Activation: read a choice option holding "display" (follow the measured
|
|
* display refresh) or a frame rate, and select the FLIP source, HOLD blend,
|
|
* `flip_mode` and that rate. Returns 0 if any setting was refused.
|
|
*/
|
|
int psx_mod_activate_render_pass_rate(const char* package, const char* feature,
|
|
const char* option, uint32_t flip_mode);
|
|
typedef struct PSXModRenderPassFrame {
|
|
uint32_t struct_size; /* sizeof(PSXModRenderPassFrame) */
|
|
uint32_t period_vblanks; /* VBlanks this game frame stays on screen */
|
|
uint32_t shown_after_vblanks; /* VBlank presents before it is shown */
|
|
uint16_t x, y, w, h; /* VRAM rect the passes draw */
|
|
} PSXModRenderPassFrame;
|
|
/*
|
|
* Plan this game frame's passes and run `fn` at each phase. While passes are
|
|
* unavailable for a lasting reason (BACKEND, DISABLED) the presenter is
|
|
* switched to a motion-adaptive crossfade, and back to HOLD once they return.
|
|
* Returns how many passes produced an image.
|
|
*/
|
|
uint32_t psx_mod_render_pass_frame(struct CPUState* cpu,
|
|
const PSXModRenderPassFrame* frame,
|
|
PSXModRenderPassFn fn, void* user);
|
|
|
|
/*
|
|
* Replay part of an already-loaded guest function inside a render pass:
|
|
* run from start_pc with the CPU state given until control reaches stop_pc.
|
|
* Every PC in [start_pc, stop_pc) is interpreted from its RAM bytes, so the
|
|
* span may start anywhere in a compiled function, including just after one
|
|
* of its calls; calls the span makes run normally and return into it.
|
|
* A plugin uses this to redraw with the game's own frame code, branches and
|
|
* all, from registers it captured at start_pc during the real frame (an
|
|
* instruction hook), instead of re-implementing that code's call sequence.
|
|
* Returns 1 when stop_pc is reached; 0 when control leaves the range another
|
|
* way (a return or jump out, a nested span), after 1M interpreted
|
|
* instructions, or outside a pass. The pass restores the machine either way.
|
|
* Both PCs are 4-aligned, in one segment, start_pc < stop_pc.
|
|
*/
|
|
int psx_mod_run_guest_span(struct CPUState* cpu, uint32_t start_pc,
|
|
uint32_t stop_pc);
|
|
int psx_mod_set_auto_skip_fmv(int enabled);
|
|
/*
|
|
* Draw still artwork behind the game image in OpenGL letterbox/pillarbox
|
|
* margins. The image path is an owner-selected mod resource; with no enabled
|
|
* mod/resource path, the margins remain the historical black clear.
|
|
* An absolute path is used unchanged. A relative path (e.g. "bezels/x.png"
|
|
* for artwork a title stages beside its binary) is resolved against the
|
|
* executable's directory, never the current working directory, when the
|
|
* artwork is loaded.
|
|
*/
|
|
int psx_mod_set_bezel_artwork(const char* path);
|
|
|
|
/*
|
|
* Upper bounds for the two loading-speed knobs below. Both are generous on
|
|
* purpose: games surface them to players as free-form integers, and neither
|
|
* can corrupt guest state (see the notes on each setter). They exist to reject
|
|
* nonsense, not to curate a list of "blessed" speeds.
|
|
*/
|
|
#define PSX_MOD_LOAD_ACCEL_MAX 1024u
|
|
#define PSX_MOD_DISC_SPEED_MAX 1024u
|
|
|
|
/*
|
|
* Accelerate only the wall-clock pacing of sustained non-XA data loads while
|
|
* preserving every guest VBlank, CD deadline, interrupt, and callback.
|
|
* wall_clock_multiplier accepts 1..PSX_MOD_LOAD_ACCEL_MAX, or zero for
|
|
* uncapped host speed (1 is a no-op, i.e. authentic pacing).
|
|
* release_frames controls how many guest frames acceleration may remain active
|
|
* after the load predicate clears; zero is the precise/speedrun-safe policy.
|
|
*/
|
|
int psx_mod_set_load_acceleration(uint32_t wall_clock_multiplier,
|
|
uint32_t release_frames);
|
|
|
|
/*
|
|
* Select guest-visible CD timing for a game-owned loading feature. divisor
|
|
* divides the emulated sector delay, so it IS the speed multiplier: 1 is
|
|
* authentic timing, higher is faster, up to PSX_MOD_DISC_SPEED_MAX. Zero
|
|
* selects the bounded "instant" scheduler, and instant_max_per_frame (1..256)
|
|
* applies only in that case. cdrom.c floors the divided delay at
|
|
* CDROM_MIN_DELAY and leaves XA streaming at authentic timing, so no value
|
|
* here can produce a zero-delay storm or speed up FMV audio. Unlike host load
|
|
* acceleration this changes WHEN the guest receives CD interrupts and can
|
|
* expose game timing bugs, which is why it is a separate, opt-in knob.
|
|
*/
|
|
int psx_mod_set_disc_speed(uint32_t divisor,
|
|
uint32_t instant_max_per_frame);
|
|
|
|
/*
|
|
* Turn the title's [[draw_distance.clamp]] sites on or off for this session
|
|
* (draw_distance.h): a far primitive the game would drop past the end of its
|
|
* ordering table is kept in the farthest slot instead. Returns 1 when the
|
|
* title configured sites, 0 when it has none (the call then changes nothing
|
|
* the guest sees). Off by default and reset to off at every session start;
|
|
* it adds guest work, so call it from activation, never in netplay.
|
|
*/
|
|
int psx_mod_set_draw_distance_clamp(int enabled);
|
|
int psx_mod_draw_distance_clamp_enabled(void);
|
|
|
|
/*
|
|
* Select the immediate variant of one [[mod_immediate.sites]] group
|
|
* (mod_immediate.h). Selector 0 runs the stock instructions; N runs
|
|
* values[N-1] of every site in `group`. The instruction bytes in RAM never
|
|
* change, so static and cached native code stay valid. Returns 1 when the
|
|
* title configured sites, 0 otherwise (nothing the guest sees changes), and
|
|
* rejects a group >= 64 or a selector > 15. Reset to 0 at every session start:
|
|
* call it from activation, before the guest runs. The caller owns the memory
|
|
* layout the selector implies (a savestate or card written under another
|
|
* selector is not interchangeable). Only offline activation may select a value;
|
|
* the host seals choices before guest execution. Later setter calls fail.
|
|
*/
|
|
int psx_mod_set_immediate_selector(uint32_t group, uint32_t selector);
|
|
uint32_t psx_mod_immediate_selector(uint32_t group);
|
|
|
|
/* Controller presentation values exposed to trusted game-owned plugins. */
|
|
enum {
|
|
PSX_MOD_CONTROLLER_ANALOG = 1,
|
|
PSX_MOD_CONTROLLER_DIGITAL = 2
|
|
};
|
|
/*
|
|
* Per-sample input facts for an opt-in controller presentation policy. The
|
|
* runtime owns SDL sampling and SIO delivery; the game-owned plugin owns only
|
|
* the policy decision of whether this sample should present as DualShock
|
|
* analog or a digital pad.
|
|
*/
|
|
typedef struct PSXModControllerInput {
|
|
uint32_t struct_size;
|
|
uint32_t player;
|
|
uint32_t sio_slot;
|
|
uint32_t configured_mode;
|
|
uint32_t current_mode;
|
|
uint32_t stick_active;
|
|
uint32_t dpad_active;
|
|
uint32_t buttons;
|
|
uint32_t lx;
|
|
uint32_t ly;
|
|
uint32_t rx;
|
|
uint32_t ry;
|
|
} PSXModControllerInput;
|
|
typedef uint32_t (*PSXModControllerPresentationCallback)(
|
|
const PSXModControllerInput* input);
|
|
/* Read-only snapshot of the local host stick state for an emulation-thread
|
|
* presentation callback. Values are ordered lx, ly, rx, ry and use the
|
|
* DualShock byte range (0..255, centered at 128). This reads mapped local
|
|
* controller axes independently of the guest's current digital/analog SIO
|
|
* mode and never changes the simulation's controller sample. Returns 0 and
|
|
* centers the output for invalid/disconnected input, netplay, or rollback
|
|
* resimulation; presentation state must never follow synchronized peer input. */
|
|
int psx_mod_read_local_pad_sticks(uint32_t player, uint8_t out[4]);
|
|
/*
|
|
* Override one player's resolved controller presentation mode for this launch.
|
|
* This is intentionally a trusted-plugin API, not a generic launcher setting.
|
|
*/
|
|
int psx_mod_set_controller_mode_override(uint32_t player,
|
|
uint32_t controller_mode);
|
|
/*
|
|
* Let a game-owned plugin choose analog/digital presentation for one player on
|
|
* every input sample. The initial mode is used for boot/hotplug before the
|
|
* first sample. config_capable should be non-zero when the selected policy may
|
|
* present a DualShock, even if a later sample currently reports digital.
|
|
*/
|
|
int psx_mod_set_controller_presentation_policy(
|
|
uint32_t player,
|
|
PSXModControllerPresentationCallback callback,
|
|
uint32_t initial_mode,
|
|
int config_capable);
|
|
|
|
/* ---- External offline input: ONE ordered resolution per player ----------
|
|
* Three optional, mod-supplied inputs can act on an offline player's pad. The
|
|
* runtime resolves them in a fixed order in pad_external_input.h:
|
|
* 1. physical/local capture (keyboard, controllers: buttons, sticks, type),
|
|
* plus the host extras of that port (gamepad present, LT/RT 0..255)
|
|
* 2. offline controller source (psx_mod_set_controller_source): buttons =
|
|
* source AND physical; sticks/type come from the source, through the
|
|
* same mode override / multitap rule / presentation policy as a physical
|
|
* pad. A declined/invalid sample delivers neutral, not the last input.
|
|
* 2b. title pad transform (psx_mod_set_pad_transform): sees the pad of 1-2
|
|
* plus the host extras and may rewrite buttons, sticks and the presented
|
|
* controller type (e.g. a NeGcon fed from the triggers). Its output is
|
|
* validated; an invalid one delivers neutral.
|
|
* 3. local mouse policy (psx_mod_set_local_mouse_policy), P1 only: may
|
|
* override ONLY the right analog axes of the pad resolved by 1-2. It sees
|
|
* the final buttons/analog flag/right stick, so it composes with a
|
|
* source (a source never suppresses it) and no capture is taken to be
|
|
* discarded. A deflected source right stick, a digital pad, or Start
|
|
* held disables it exactly as for a physical pad; a source release,
|
|
* no device, or an armed input guard resets (releases) the capture.
|
|
* Gating. Neither input reaches the guest under netplay or rollback resim,
|
|
* selfcheck replay, headless, or a debug-server input override (which wins and
|
|
* is never mixed with external input). The mouse policy is additionally live
|
|
* only outside selfcheck input lock/resim, render passes, rewind, the
|
|
* savestate menu and the savestate input guard (pad_ext_live). A source is
|
|
* still sampled while the input guard is armed but its output is discarded
|
|
* (neutral). With no source and no mouse policy registered, pad bytes and
|
|
* host event handling are exactly the faithful defaults.
|
|
*
|
|
* ONE registration rule for both: call only on the main (emulation) thread;
|
|
* struct arguments are validated at registration (struct_size) and a bad one
|
|
* is rejected (return 0, logged to stderr); results of each callback are
|
|
* validated per use and fall back to neutral. These are mod-trusted APIs --
|
|
* arbitrary native code, no caller check -- not launcher settings. A source
|
|
* is per player (NULL detaches, with one neutral release frame); the mouse
|
|
* policy is one per session (a second registration returns 0). Callbacks run
|
|
* on the main thread only: sources once per frame, in the normal offline
|
|
* sampler.
|
|
* The source's `analog` is the pad capability; the final type still goes
|
|
* through the multitap rule, mod mode override and presentation policy.
|
|
*/
|
|
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);
|
|
/* Presence of an attached offline source (default connected). A diagnostic
|
|
* source may explicitly emulate an unplugged pad without falling back to a
|
|
* physical controller assigned to that port. Cleared on detach/session reset. */
|
|
int psx_mod_set_controller_source_connected(uint32_t player, int connected);
|
|
|
|
/* Title pad transform (stage 2b above). Offline local play only: never under
|
|
* netplay or rollback resim (the session is vanilla), selfcheck replay or a
|
|
* plain debug-server override; the debug host-input layer feeds stage 1 and
|
|
* so does reach it. Main thread; may run more than once per guest frame
|
|
* (low-latency resample), so keep it a function of its input plus game state.
|
|
*
|
|
* The frame is the resolved pad: active-low buttons, sticks (0x80 centred),
|
|
* type (PSX_MOD_PAD_*), and host extras of the port. A player presented as
|
|
* digital (and not driven by a controller source) is given as its host pad
|
|
* instead: the real sticks, and buttons without the stick->D-pad fold, with
|
|
* type still DIGITAL; the transform decides what the guest sees.
|
|
* host_flags bit 0 = a
|
|
* gamepad is assigned, bit 1 / bit 2 = it has a left / right trigger axis;
|
|
* host_lt / host_rt are those triggers, 0 released .. 255 fully pressed
|
|
* (0 when absent or while the savestate input guard is armed). Bit 3 marks
|
|
* axes/type supplied by an offline input source (PSX_MOD_PAD_HOST_SOURCE).
|
|
*
|
|
* The output arrives pre-filled with the stock pad (what the player gets
|
|
* without a transform, so a digital pad keeps its fold and centred sticks;
|
|
* pressures 0). Return non-zero to apply it, 0 to deliver the stock pad. The
|
|
* type must be one of allowed_types (bit per PSX_MOD_PAD_*); bytes are
|
|
* 0..255 and buttons 0..0xFFFF; anything else delivers a neutral frame.
|
|
* NeGcon uses lx as twist (0x80 centre) and negcon_i / negcon_ii / negcon_l
|
|
* as pressures. A type change reaches SIO through the deferred, idle-bus
|
|
* request; entering or leaving NeGcon is a device swap (sio.h).
|
|
*
|
|
* Registration validates struct_size, a non-NULL callback, allowed_types
|
|
* within the known types and initial_type within allowed_types; a bad one
|
|
* returns 0. initial_type is the type presented at boot/hotplug before the
|
|
* first frame. NULL detaches with one neutral release frame; mod/session
|
|
* reset detaches all (with release). Not registering keeps the faithful
|
|
* default path untouched. */
|
|
enum {
|
|
PSX_MOD_PAD_DIGITAL = 0,
|
|
PSX_MOD_PAD_DUALSHOCK = 1,
|
|
PSX_MOD_PAD_JOGCON = 2,
|
|
PSX_MOD_PAD_NEGCON = 3
|
|
};
|
|
#define PSX_MOD_PAD_TYPE_BIT(type) (1u << (type))
|
|
enum {
|
|
PSX_MOD_PAD_HOST_GAMEPAD = 1u << 0,
|
|
PSX_MOD_PAD_HOST_LT = 1u << 1,
|
|
PSX_MOD_PAD_HOST_RT = 1u << 2,
|
|
/* Axes/type came from an offline input source, rather than the host.
|
|
* Transforms can preserve already transformed diagnostic recordings. */
|
|
PSX_MOD_PAD_HOST_SOURCE = 1u << 3
|
|
};
|
|
typedef struct PSXModPadFrame {
|
|
uint32_t struct_size, player, buttons, lx, ly, rx, ry, type;
|
|
uint32_t host_flags, host_lt, host_rt;
|
|
} PSXModPadFrame;
|
|
typedef struct PSXModPadOutput {
|
|
uint32_t struct_size, buttons, type, lx, ly, rx, ry;
|
|
uint32_t negcon_i, negcon_ii, negcon_l;
|
|
} PSXModPadOutput;
|
|
typedef struct PSXModPadTransform {
|
|
uint32_t struct_size, allowed_types, initial_type;
|
|
int (*transform)(const PSXModPadFrame *frame, PSXModPadOutput *out);
|
|
} PSXModPadTransform;
|
|
int psx_mod_set_pad_transform(uint32_t player, const PSXModPadTransform *transform);
|
|
|
|
/* Let a host shortcut bound to ONE controller button act on that button
|
|
* alone. Without this, a one-button binding means Select + button (the
|
|
* legacy rule), so a title's default one-button binding is safe whether or
|
|
* not its mod is enabled. While allowed (and, for Rewind, while Rewind is
|
|
* enabled) the runtime claims that host button: it is removed from P1's
|
|
* guest pad, and a claim made while it is held lasts until it is released.
|
|
* Call at mod activation; cleared at every mod/session reset. Returns 0 for
|
|
* an unknown shortcut. Multi-button bindings are unaffected. */
|
|
enum {
|
|
PSX_MOD_SHORTCUT_REWIND = 0,
|
|
PSX_MOD_SHORTCUT_SAVE_STATE_MENU = 1,
|
|
PSX_MOD_SHORTCUT_FAST_FORWARD = 2,
|
|
PSX_MOD_SHORTCUT_FAST_FORWARD_TOGGLE = 3
|
|
};
|
|
int psx_mod_allow_direct_shortcut(uint32_t shortcut);
|
|
|
|
/* Refuse local Rewind while the title is in a mode it must not rewind (e.g.
|
|
* local split-screen multiplayer). While blocked, opening Rewind is refused
|
|
* with an OSD note, no history is captured (snapshots already in the ring are
|
|
* kept), and an allowed direct Rewind shortcut is not in force: its button is
|
|
* not claimed and reaches the guest pad / title transform as if Rewind were
|
|
* disabled. Netplay already refuses Rewind on its own. Opt-in: call with 1
|
|
* when the mode starts and 0 when it ends (setting it every frame from the
|
|
* title's state is fine). Cleared at every mod/session reset. */
|
|
void psx_mod_set_rewind_blocked(int blocked);
|
|
/* Refuse saves and loads while trusted host gameplay state cannot be restored
|
|
* by the guest serializer. Includes synchronized netplay state operations;
|
|
* pending requests are cancelled. Cleared before each session's activation. */
|
|
void psx_mod_set_savestate_blocked(int blocked);
|
|
|
|
/* Title-supplied host rumble for one guest port (0-based): DualShock motor
|
|
* values (small: 0 off / nonzero on, large: 0..255) for a title that
|
|
* computes vibration but cannot send it over SIO to the presented pad (e.g. a
|
|
* NeGcon has no motors). The runtime drives the host pad with the louder of
|
|
* this and the guest's own SIO motors. Online only the local seat's value
|
|
* is used (this peer's local port, on its pad) and calls during a rollback resim are
|
|
* ignored, so a netplay = "host_output" plugin can call it for every seat.
|
|
* Opt-in: call every frame while it applies; a value lapses 8 VBlanks after the last call, and all are cleared
|
|
* at every mod/session reset (psx_host_rumble.h). Returns 0 for a bad seat. */
|
|
int psx_mod_set_host_rumble(uint32_t player, uint32_t small, uint32_t large);
|
|
|
|
/* Local P1 mouse policy. The runtime delivers ordered events on the SDL owner
|
|
* (main) thread, owns relative capture and folds the resulting right-stick
|
|
* bytes after native input/presentation (and after any controller source),
|
|
* before normal SIO delivery. No SDL type, guest address, gesture, or game
|
|
* setting belongs in this interface. */
|
|
enum {
|
|
PSX_MOD_MOUSE_HOLD_NONE = 0,
|
|
PSX_MOD_MOUSE_HOLD_RIGHT = 1, /* Mouse3 in keybinds.ini */
|
|
PSX_MOD_MOUSE_HOLD_LEFT_ALT = 2,
|
|
PSX_MOD_MOUSE_RESET = 0,
|
|
PSX_MOD_MOUSE_ACQUIRED = 1,
|
|
PSX_MOD_MOUSE_MOTION = 2,
|
|
PSX_MOD_MOUSE_HOLD_PRESS = 3,
|
|
PSX_MOD_MOUSE_HOLD_RELEASE = 4
|
|
};
|
|
typedef struct PSXModMouseEvent {
|
|
uint32_t struct_size;
|
|
uint32_t type;
|
|
uint64_t time_ns;
|
|
double dx, dy;
|
|
} PSXModMouseEvent;
|
|
typedef struct PSXModMouseOutput {
|
|
uint32_t struct_size;
|
|
uint32_t override_right;
|
|
uint32_t rx, ry;
|
|
} PSXModMouseOutput;
|
|
typedef struct PSXModMousePolicy {
|
|
uint32_t struct_size;
|
|
uint32_t hold_control;
|
|
/* Re-read persistent guest context, including pause/menus, on every event
|
|
* and local sample. Must not infer it from a host Start toggle. */
|
|
int (*eligible)(uint32_t native_buttons);
|
|
void (*event)(const PSXModMouseEvent* event);
|
|
/* A query: must not consume motion, change the event anchor or deadlines. */
|
|
void (*sample)(uint64_t now_ns, PSXModMouseOutput* output);
|
|
} PSXModMousePolicy;
|
|
int psx_mod_set_local_mouse_policy(const PSXModMousePolicy* policy);
|
|
/* Optional action-game capture: click once to capture, Escape to release.
|
|
* Later clicks reach the normal bindings. Existing hold-to-look is default. */
|
|
int psx_mod_set_local_mouse_persistent_capture(int enabled);
|
|
/* Optional persistent-capture companion: keep existing capture through title
|
|
* eligibility changes (menus/cutscenes/loading), Start and host stalls, while
|
|
* clearing mouse motion and suspending output. Focus loss, host UI, disconnect,
|
|
* native right-stick takeover and Escape still release. Never auto-acquires. */
|
|
int psx_mod_set_local_mouse_capture_across_transitions(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
|