Files
psxrecomp/runtime/tests/test_mod_pad_transform.c
T
Shokunin 82bc90dff2 Give a title pad transform the host pad of a digital-presented player
A digital presentation folds the sticks onto the D-pad and centres them,
so a transform on a digital player (the default for many titles) never
saw the stick. The transform decides what the guest sees, so for a
digital stage-1 pad with no controller source its frame is now the host
pad: real sticks and buttons without the stick->D-pad fold, type still
DIGITAL (new PadExtHooks.host_pad, capture_pad_slot_view). The output is
pre-filled with the stock pad and a decline delivers it, so pass-through
stays the stock folded digital bytes (mod_pad_transform_run takes the
stock pad). Analog pads, sources and guarded frames are unchanged.

Tests: pad_external_input_test (digital player: frame has the real lx and
unfolded buttons, decline and pass-through deliver the stock digital
bytes, no host-pad query for analog / guarded / source ports),
mod_pad_transform_test (decline returns the stock pad, output pre-filled
from it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:24:54 -07:00

125 lines
5.2 KiB
C

/* psx_mod_set_pad_transform registration rules and per-frame validation. */
#include "mod_pad_transform.h"
#include "sio.h"
#include <stdio.h>
#include <string.h>
#define CHECK(expr) do { if (!(expr)) { \
fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #expr); return 1; } } while (0)
static int mode, calls;
static PSXModPadFrame seen;
static int xf(const PSXModPadFrame *f, PSXModPadOutput *o) {
calls++;
seen = *f;
if (mode == 1) return 0; /* decline: pass through */
o->type = PSX_MOD_PAD_NEGCON;
o->buttons = f->buttons | 0x0400u; /* drop L1 */
o->negcon_i = f->host_rt;
o->negcon_ii = f->host_lt;
if (mode == 2) o->negcon_i = 256; /* out of range */
if (mode == 3) o->type = PSX_MOD_PAD_JOGCON; /* not allowed */
if (mode == 4) o->struct_size = 4;
return 1;
}
static PSXModPadFrame frame(void) {
PSXModPadFrame f;
memset(&f, 0, sizeof f);
f.struct_size = sizeof f;
f.buttons = 0xfbffu; /* L1 held */
f.lx = 0x40; f.ly = 0x80; f.rx = 0x90; f.ry = 0x70;
f.type = PSX_MOD_PAD_DUALSHOCK;
f.host_flags = 7; f.host_lt = 30; f.host_rt = 200;
return f;
}
int main(void) {
PSXModPadTransform t;
PSXModPadFrame f = frame();
PSXModPadOutput o;
memset(&t, 0, sizeof t);
t.struct_size = sizeof t;
t.allowed_types = PSX_MOD_PAD_TYPE_BIT(PSX_MOD_PAD_DUALSHOCK) |
PSX_MOD_PAD_TYPE_BIT(PSX_MOD_PAD_NEGCON);
t.initial_type = PSX_MOD_PAD_DUALSHOCK;
t.transform = xf;
/* Nothing registered: no stage, nothing owed. */
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 0);
CHECK(mod_pad_transform_initial_type(0) == -1);
/* Registration validation. */
CHECK(!psx_mod_set_pad_transform(PSX_MAX_PLAYERS, &t));
{ PSXModPadTransform b = t; b.struct_size = 8; CHECK(!psx_mod_set_pad_transform(0, &b)); }
{ PSXModPadTransform b = t; b.transform = NULL; CHECK(!psx_mod_set_pad_transform(0, &b)); }
{ PSXModPadTransform b = t; b.allowed_types = 0; CHECK(!psx_mod_set_pad_transform(0, &b)); }
{ PSXModPadTransform b = t; b.allowed_types |= 1u << 4; CHECK(!psx_mod_set_pad_transform(0, &b)); }
{ PSXModPadTransform b = t; b.initial_type = PSX_MOD_PAD_JOGCON; CHECK(!psx_mod_set_pad_transform(0, &b)); }
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 0);
CHECK(psx_mod_set_pad_transform(0, &t));
CHECK(mod_pad_transform_initial_type(0) == PSX_MOD_PAD_DUALSHOCK);
CHECK(mod_pad_transform_initial_type(1) == -1);
/* Applied output; the frame carries pad + host extras. */
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 1);
CHECK(seen.player == 0 && seen.host_rt == 200 && seen.type == PSX_MOD_PAD_DUALSHOCK);
CHECK(o.type == PSX_MOD_PAD_NEGCON && o.buttons == 0xffffu &&
o.negcon_i == 200 && o.negcon_ii == 30 && o.negcon_l == 0);
CHECK(o.lx == 0x40 && o.rx == 0x90); /* pre-filled pass-through */
/* Decline -> exact pass-through. */
mode = 1;
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 1);
CHECK(o.type == PSX_MOD_PAD_DUALSHOCK && o.buttons == 0xfbffu &&
o.lx == 0x40 && o.ry == 0x70 && o.negcon_i == 0);
/* Decline with a stock pad -> that stock pad, not the frame (a digital
* player's frame is its host pad; the stock pad keeps the fold). */
{
PSXModPadOutput stock;
memset(&stock, 0, sizeof stock);
stock.struct_size = sizeof stock;
stock.buttons = 0xff7fu; stock.type = PSX_MOD_PAD_DIGITAL;
stock.lx = stock.ly = stock.rx = stock.ry = 0x80;
CHECK(mod_pad_transform_run(0, &f, &stock, &o) == 1);
CHECK(memcmp(&o, &stock, sizeof o) == 0);
mode = 0; /* applied: pre-filled from the stock pad */
CHECK(mod_pad_transform_run(0, &f, &stock, &o) == 1);
CHECK(o.type == PSX_MOD_PAD_NEGCON && o.lx == 0x80 && o.negcon_i == 200);
mode = 1;
}
/* Invalid output (range, disallowed type, struct size) -> neutral of the
* frame's type. */
for (mode = 2; mode <= 4; mode++) {
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 1);
CHECK(o.type == PSX_MOD_PAD_DUALSHOCK && o.buttons == 0xffffu &&
o.lx == 0x80 && o.ry == 0x80 && o.negcon_i == 0);
}
/* Detach: one neutral release frame, then nothing; the callback is not
* called for the release. */
mode = 0;
CHECK(psx_mod_set_pad_transform(0, NULL));
calls = 0;
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 1);
CHECK(calls == 0 && o.buttons == 0xffffu && o.type == PSX_MOD_PAD_DUALSHOCK &&
o.lx == 0x80);
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 0);
CHECK(mod_pad_transform_initial_type(0) == -1);
/* Session reset: same release for every registered player. */
CHECK(psx_mod_set_pad_transform(0, &t) && psx_mod_set_pad_transform(1, &t));
mod_pad_transform_reset();
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 1 && o.buttons == 0xffffu);
CHECK(mod_pad_transform_run(1, &f, NULL, &o) == 1 && o.buttons == 0xffffu);
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 0 && mod_pad_transform_run(1, &f, NULL, &o) == 0);
/* A reset with nothing registered owes nothing. */
mod_pad_transform_reset();
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 0);
fprintf(stderr, "mod_pad_transform: passed\n");
return 0;
}