Repository navigation
Expand file tree
/
Copy pathmod_plugins.h
More file actions
1357 lines (1310 loc) · 77.9 KB
/
Copy pathmod_plugins.h
File metadata and controls
1357 lines (1310 loc) · 77.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#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.