From 82bc90dff2a2a34fd87b24c41c352f13859419d0 Mon Sep 17 00:00:00 2001 From: Shokunin <30949000+tetrisgm@users.noreply.github.com> Date: Mon, 5 Oct 2026 20:24:54 -0700 Subject: [PATCH] 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 --- runtime/include/mod_pad_transform.h | 13 ++--- runtime/include/mod_plugins.h | 11 +++-- runtime/src/main.cpp | 32 +++++++++--- runtime/src/mod_pad_transform.c | 10 ++-- runtime/src/pad_external_input.c | 25 ++++++++-- runtime/src/pad_external_input.h | 15 ++++-- runtime/tests/test_mod_pad_transform.c | 38 ++++++++++----- runtime/tests/test_pad_external_input.c | 65 +++++++++++++++++++++++-- 8 files changed, 168 insertions(+), 41 deletions(-) diff --git a/runtime/include/mod_pad_transform.h b/runtime/include/mod_pad_transform.h index d05ae3a7..1d6642ac 100644 --- a/runtime/include/mod_pad_transform.h +++ b/runtime/include/mod_pad_transform.h @@ -6,14 +6,15 @@ extern "C" { /* Storage and per-frame validation for psx_mod_set_pad_transform (stage 2b of * pad_external_input.h). Main (emulation) thread only. * - * run(): 0 = no transform and no release pending (caller passes the frame - * through). 1 = *out is what to deliver: the transform's validated output, - * the pass-through frame when it declined, a neutral frame of the frame's type - * when its output was invalid, or the one neutral release frame owed after a - * detach/reset. reset() detaches every transform, leaving that release owed. + * run(): 0 = no transform and no release pending (caller delivers the stock + * pad). 1 = *out is what to deliver: the transform's validated output, the + * stock pad when it declined, a neutral frame of the frame's type when its + * output was invalid, or the one neutral release frame owed after a + * detach/reset. `stock` is the pad stages 1-2 resolved, which the output + * arrives pre-filled with; NULL = the frame itself. reset() detaches every transform, leaving that release owed. * initial_type(): the registered boot/hotplug type, or -1. */ int mod_pad_transform_run(uint32_t player, const PSXModPadFrame *frame, - PSXModPadOutput *out); + const PSXModPadOutput *stock, PSXModPadOutput *out); void mod_pad_transform_reset(void); int mod_pad_transform_initial_type(uint32_t player); #ifdef __cplusplus diff --git a/runtime/include/mod_plugins.h b/runtime/include/mod_plugins.h index 0e816141..d99b4e34 100644 --- a/runtime/include/mod_plugins.h +++ b/runtime/include/mod_plugins.h @@ -810,13 +810,18 @@ int psx_mod_set_controller_source(uint32_t player, PSXModControllerSource source * (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: host_flags bit 0 = a + * 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). * - * The output arrives pre-filled with the frame (pass-through, pressures 0). - * Return non-zero to apply it, 0 to pass the frame through unchanged. The + * 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 diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 51950496..568d6e38 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -5757,7 +5757,11 @@ static void apply_input_override_to_sio(int override_word) { * disconnected. `guarded` (savestate input guard) delivers neutral buttons and * sticks but still resolves presence/type. No mouse or source side effects: * those are layered on top by pad_ext_resolve(). */ -static int capture_pad_slot(int s, PsxNetPad* out, bool guarded) { +/* host_view: the port's host pad before presentation, for a title pad + * transform (PadExtHooks.host_pad): real sticks and buttons without the + * digital stick->D-pad fold, whatever the configured mode; type unchanged. */ +static int capture_pad_slot_view(int s, PsxNetPad* out, bool guarded, + bool host_view) { if (!out) return 0; out->buttons = 0xFFFFu; out->lx = out->ly = out->rx = out->ry = 0x80u; @@ -5785,7 +5789,7 @@ static int capture_pad_slot(int s, PsxNetPad* out, bool guarded) { const int mode = effective_player_mode_for_sio(p, s); uint8_t st[4] = { 0x80, 0x80, 0x80, 0x80 }; if (mode == PSXRecompV4::PAD_MODE_ANALOG || p.steering_wheel || - g_mod_controller_policy[s].callback) { + g_mod_controller_policy[s].callback || host_view) { pad_sticks_for(p, player, st); } const uint16_t policy_buttons = @@ -5818,7 +5822,8 @@ static int capture_pad_slot(int s, PsxNetPad* out, bool guarded) { * exactly as on a real DualShock. This is what stops a dual-analog game's * D-pad control (Ape Escape's camera rotate) from being spun by stick * movement or centre drift. Digital mode keeps the stick->D-pad fold. */ - const bool suppress_stick = (eff_analog != 0) || p.steering_wheel; + const bool suppress_stick = (eff_analog != 0) || p.steering_wheel || + host_view; uint16_t btn = src.device ? pad_buttons_for(p, player, suppress_stick) : (uint16_t)0xFFFF; /* kind==1 already consumed the binds inside pad_buttons_for — ANDing the @@ -5836,7 +5841,7 @@ static int capture_pad_slot(int s, PsxNetPad* out, bool guarded) { * may press a button may also steer. kind==1 already folded its binds * inside pad_sticks_for; psx_keybinds_sticks only widens a deflection, so * applying it twice is idempotent. */ - if (eff_analog) { + if (eff_analog || host_view) { if (src.keybinds) { const Uint8* keys = SDL_GetKeyboardState(NULL); psx_keybinds_sticks(keys, player, st); @@ -5844,7 +5849,7 @@ static int capture_pad_slot(int s, PsxNetPad* out, bool guarded) { if (src.all_pads) dev_any_controller_sticks(st); } - if (!eff_analog && !p.steering_wheel) { + if (!eff_analog && !p.steering_wheel && !host_view) { st[0] = st[1] = st[2] = st[3] = 0x80; } @@ -5855,6 +5860,10 @@ static int capture_pad_slot(int s, PsxNetPad* out, bool guarded) { return 1; } +static int capture_pad_slot(int s, PsxNetPad* out, bool guarded) { + return capture_pad_slot_view(s, out, guarded, false); +} + /* Netplay-only capture: assigned PlayerInput for this slot only. Never merges * keyboard-all / all-controllers (dev_any_input). Same pad-mode / stick rules * as capture_pad_slot otherwise. present_sio_slot is the delay-sync seat this @@ -6453,9 +6462,19 @@ static void pad_ext_host_extras(void*, int s, uint32_t* flags, uint32_t* lt, SDL_GameControllerGetAxis(handle, SDL_CONTROLLER_AXIS_TRIGGERRIGHT)); } } +/* (1) Host pad of port s before presentation, for the transform frame. */ +static int pad_ext_host_pad(void*, int s, uint16_t* buttons, uint8_t st[4]) { + PsxNetPad pad; + if (s < 0 || s >= PSX_MAX_PLAYERS) return 0; + if (!capture_pad_slot_view(s, &pad, false, true)) return 0; + *buttons = pad.buttons; + st[0] = pad.lx; st[1] = pad.ly; st[2] = pad.rx; st[3] = pad.ry; + return 1; +} static int pad_ext_pad_transform(void*, int s, const PSXModPadFrame* frame, + const PSXModPadOutput* stock, PSXModPadOutput* out) { - return mod_pad_transform_run((uint32_t)s, frame, out); + return mod_pad_transform_run((uint32_t)s, frame, stock, out); } static void pad_ext_mouse_fold(void*, int connected, int analog, uint16_t buttons, uint8_t* rx, uint8_t* ry) { @@ -6472,6 +6491,7 @@ static PadExtHooks pad_ext_main_hooks(void) { h.mouse_fold = pad_ext_mouse_fold; h.host_extras = pad_ext_host_extras; h.pad_transform = pad_ext_pad_transform; + h.host_pad = pad_ext_host_pad; return h; } diff --git a/runtime/src/mod_pad_transform.c b/runtime/src/mod_pad_transform.c index c0453434..d1df37e2 100644 --- a/runtime/src/mod_pad_transform.c +++ b/runtime/src/mod_pad_transform.c @@ -80,7 +80,7 @@ static int output_valid(uint32_t allowed, const PSXModPadOutput *o) { } int mod_pad_transform_run(uint32_t player, const PSXModPadFrame *frame, - PSXModPadOutput *out) { + const PSXModPadOutput *stock, PSXModPadOutput *out) { if (!frame || !out || player >= PSX_MAX_PLAYERS) return 0; if (s_release[player] && !s_active[player]) { s_release[player] = 0; @@ -88,10 +88,12 @@ int mod_pad_transform_run(uint32_t player, const PSXModPadFrame *frame, return 1; } if (!s_active[player]) return 0; - PSXModPadOutput o; - pass_through(frame, &o); + PSXModPadOutput o, base; + if (stock) base = *stock; + else pass_through(frame, &base); + o = base; if (!s_xf[player].transform(frame, &o)) { - pass_through(frame, out); + *out = base; return 1; } if (!output_valid(s_xf[player].allowed_types, &o)) { diff --git a/runtime/src/pad_external_input.c b/runtime/src/pad_external_input.c index 17097432..04c51898 100644 --- a/runtime/src/pad_external_input.c +++ b/runtime/src/pad_external_input.c @@ -11,19 +11,36 @@ int pad_ext_live(const PadExtGate *g) { /* Stage 2b: run the title transform over the resolved pad and pack its * output back into the PsxNetPad (NeGcon: lx twist, rx I, ry II, ly L). */ static void pad_ext_transform(const PadExtHooks *h, int s, int guarded, - PsxNetPad *out) { + int from_source, PsxNetPad *out) { PSXModPadFrame f; - PSXModPadOutput o; + PSXModPadOutput stock, o; if (!h->pad_transform) return; + memset(&stock, 0, sizeof stock); + stock.struct_size = sizeof stock; + stock.buttons = out->buttons; + stock.type = out->analog; + stock.lx = out->lx; stock.ly = out->ly; stock.rx = out->rx; stock.ry = out->ry; memset(&f, 0, sizeof f); f.struct_size = sizeof f; f.player = (uint32_t)s; f.buttons = out->buttons; f.lx = out->lx; f.ly = out->ly; f.rx = out->rx; f.ry = out->ry; f.type = out->analog; + /* A digital presentation folds the sticks onto the D-pad and centres + * them; the transform decides what the guest sees, so its frame is the + * host pad instead. The stock (pass-through) output keeps the fold. */ + if (out->analog == PSX_MOD_PAD_DIGITAL && !from_source && !guarded && + h->host_pad) { + uint16_t hb; + uint8_t st[4]; + if (h->host_pad(h->ctx, s, &hb, st)) { + f.buttons = hb; + f.lx = st[0]; f.ly = st[1]; f.rx = st[2]; f.ry = st[3]; + } + } if (h->host_extras) h->host_extras(h->ctx, s, &f.host_flags, &f.host_lt, &f.host_rt); if (guarded) f.host_lt = f.host_rt = 0; - if (!h->pad_transform(h->ctx, s, &f, &o)) return; + if (!h->pad_transform(h->ctx, s, &f, &stock, &o)) return; if (guarded) { o.buttons = 0xffff; o.lx = o.ly = o.rx = o.ry = 0x80; @@ -66,7 +83,7 @@ int pad_ext_resolve(const PadExtHooks *h, int s, PsxNetPad *out) { if (s == 0) h->mouse_reset(h->ctx); return 0; } - pad_ext_transform(h, s, guarded, out); + pad_ext_transform(h, s, guarded, has_source, out); if (s == 0) { if (guarded || out->analog == PSX_MOD_PAD_NEGCON) h->mouse_reset(h->ctx); else h->mouse_fold(h->ctx, out->connected, out->analog, out->buttons, diff --git a/runtime/src/pad_external_input.h b/runtime/src/pad_external_input.h index fe5f309c..6f7598fb 100644 --- a/runtime/src/pad_external_input.h +++ b/runtime/src/pad_external_input.h @@ -7,8 +7,10 @@ * buttons = source & physical; sticks/type from the source, through * the controller presentation policy (hook) * (2b) title pad transform (psx_mod_set_pad_transform), if any: sees the - * pad of (1)/(2) plus the host extras; may rewrite buttons, sticks - * and the presented type (NeGcon packed as PsxNetPad documents) + * pad of (1)/(2) plus the host extras (a digital pad with no source + * as its host pad: real sticks, no stick->D-pad fold); may rewrite + * buttons, sticks and the presented type (NeGcon packed as PsxNetPad + * documents). Declining delivers the stock pad of (1)/(2). * (3) local mouse policy (psx_mod_set_local_mouse_policy), P1 only: may * override ONLY the right analog axes of whatever (1)-(2b) produced; * a NeGcon result has no right stick, so it resets the mouse. @@ -57,9 +59,14 @@ typedef struct PadExtHooks { void (*host_extras)(void *ctx, int s, uint32_t *flags, uint32_t *lt, uint32_t *rt); /* (2b) Title transform: mod_pad_transform_run semantics (0 = none, - * 1 = deliver *out). NULL = no transform stage. */ + * 1 = deliver *out; `stock` is the pass-through). NULL = no stage. */ int (*pad_transform)(void *ctx, int s, const PSXModPadFrame *frame, - PSXModPadOutput *out); + const PSXModPadOutput *stock, PSXModPadOutput *out); + /* (1) Host pad of port `s` before presentation: buttons without the + * digital stick->D-pad fold and the real sticks, whatever the configured + * mode. Queried only for the transform frame of a digital stage-1 pad + * with no source; 0 = unavailable (the frame keeps the stock pad). */ + int (*host_pad)(void *ctx, int s, uint16_t *buttons, uint8_t st[4]); } PadExtHooks; /* Resolve one port. Returns 1 = deliver *out, 0 = no device/source here. */ diff --git a/runtime/tests/test_mod_pad_transform.c b/runtime/tests/test_mod_pad_transform.c index 451b049d..ce5e47ae 100644 --- a/runtime/tests/test_mod_pad_transform.c +++ b/runtime/tests/test_mod_pad_transform.c @@ -47,7 +47,7 @@ int main(void) { t.transform = xf; /* Nothing registered: no stage, nothing owed. */ - CHECK(mod_pad_transform_run(0, &f, &o) == 0); + CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 0); CHECK(mod_pad_transform_initial_type(0) == -1); /* Registration validation. */ @@ -57,13 +57,13 @@ int main(void) { { 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, &o) == 0); + 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, &o) == 1); + 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); @@ -71,14 +71,30 @@ int main(void) { /* Decline -> exact pass-through. */ mode = 1; - CHECK(mod_pad_transform_run(0, &f, &o) == 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, &o) == 1); + 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); } @@ -88,21 +104,21 @@ int main(void) { mode = 0; CHECK(psx_mod_set_pad_transform(0, NULL)); calls = 0; - CHECK(mod_pad_transform_run(0, &f, &o) == 1); + 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, &o) == 0); + 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, &o) == 1 && o.buttons == 0xffffu); - CHECK(mod_pad_transform_run(1, &f, &o) == 1 && o.buttons == 0xffffu); - CHECK(mod_pad_transform_run(0, &f, &o) == 0 && mod_pad_transform_run(1, &f, &o) == 0); + 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, &o) == 0); + CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 0); fprintf(stderr, "mod_pad_transform: passed\n"); return 0; } diff --git a/runtime/tests/test_pad_external_input.c b/runtime/tests/test_pad_external_input.c index 8edd5079..c36fd424 100644 --- a/runtime/tests/test_pad_external_input.c +++ b/runtime/tests/test_pad_external_input.c @@ -17,6 +17,8 @@ static struct { int fold_slot_calls[2]; int src_analog_out; int xf_ret; PSXModPadOutput xf_out; PSXModPadFrame xf_seen; int xf_calls; + PSXModPadOutput xf_stock; int xf_echo_stock; + int host_pad_ok, host_pad_calls; uint16_t host_buttons; uint8_t host_st[4]; uint32_t ex_flags, ex_lt, ex_rt; } T; @@ -56,10 +58,16 @@ static void h_mfold(void *c, int con, int an, uint16_t b, uint8_t *rx, uint8_t * static void h_extras(void *c, int s, uint32_t *f, uint32_t *lt, uint32_t *rt) { (void)c; (void)s; L('e'); *f = T.ex_flags; *lt = T.ex_lt; *rt = T.ex_rt; } -static int h_xf(void *c, int s, const PSXModPadFrame *f, PSXModPadOutput *o) { - (void)c; (void)s; L('t'); T.xf_calls++; T.xf_seen = *f; +static int h_xf(void *c, int s, const PSXModPadFrame *f, + const PSXModPadOutput *stock, PSXModPadOutput *o) { + (void)c; (void)s; L('t'); T.xf_calls++; T.xf_seen = *f; T.xf_stock = *stock; if (!T.xf_ret) return 0; - *o = T.xf_out; return 1; + *o = T.xf_echo_stock ? *stock : T.xf_out; return 1; +} +static int h_host_pad(void *c, int s, uint16_t *b, uint8_t st[4]) { + (void)c; (void)s; L('h'); T.host_pad_calls++; + if (!T.host_pad_ok) return 0; + *b = T.host_buttons; memcpy(st, T.host_st, 4); return 1; } static PadExtHooks hooks(void) { @@ -209,6 +217,57 @@ int main(void) { CHECK(o.analog == PSX_MOD_PAD_NEGCON && o.buttons == 0xffff && o.lx == 0x80 && o.rx == 0 && o.ry == 0 && o.ly == 0); + /* Digital-presented player: the frame is the host pad (real sticks, + * buttons without the stick->D-pad fold, type still digital); the + * stock output, delivered on decline, is the folded digital pad. */ + hx.host_pad = h_host_pad; + { + const uint16_t folded = 0xff7e; /* SELECT + LEFT from the stick */ + reset(); + T.local.analog = 0; T.local.buttons = folded; + T.local.lx = T.local.ly = T.local.rx = T.local.ry = 0x80; + T.host_pad_ok = 1; T.host_buttons = 0xfffe; + T.host_st[0] = 0x10; T.host_st[1] = 0x80; T.host_st[2] = 0x81; T.host_st[3] = 0x7f; + CHECK(pad_ext_resolve(&hx, 0, &o) == 1); + CHECK(T.host_pad_calls == 1 && T.xf_seen.type == PSX_MOD_PAD_DIGITAL && + T.xf_seen.lx == 0x10 && T.xf_seen.rx == 0x81 && + T.xf_seen.buttons == 0xfffe); + CHECK(T.xf_stock.buttons == folded && T.xf_stock.lx == 0x80 && + T.xf_stock.type == PSX_MOD_PAD_DIGITAL); + CHECK(o.buttons == folded && o.analog == 0 && o.lx == 0x80 && + o.ly == 0x80 && o.rx == 0x80 && o.ry == 0x80 && o.connected == 1); + /* Declined through mod_pad_transform_run (stock echoed back): + * still the stock digital bytes. */ + reset(); + T.local.analog = 0; T.local.buttons = folded; + T.local.lx = T.local.ly = T.local.rx = T.local.ry = 0x80; + T.host_pad_ok = 1; T.host_buttons = 0xfffe; T.host_st[0] = 0x10; + T.xf_ret = 1; T.xf_echo_stock = 1; + CHECK(pad_ext_resolve(&hx, 0, &o) == 1); + CHECK(o.buttons == folded && o.analog == 0 && o.lx == 0x80 && + o.ly == 0x80 && o.rx == 0x80 && o.ry == 0x80); + /* A NeGcon from the real stick. */ + T.xf_echo_stock = 0; T.xf_out.struct_size = sizeof T.xf_out; + T.xf_out.type = PSX_MOD_PAD_NEGCON; T.xf_out.buttons = 0xffff; + T.xf_out.lx = 0x10; + CHECK(pad_ext_resolve(&hx, 0, &o) == 1 && o.analog == PSX_MOD_PAD_NEGCON && + o.lx == 0x10); + /* Analog pads, guarded frames and source-driven ports keep the + * resolved pad as the frame; no host-pad query. */ + reset(); T.host_pad_ok = 1; T.host_st[0] = 0x10; + CHECK(pad_ext_resolve(&hx, 0, &o) == 1 && T.host_pad_calls == 0 && + T.xf_seen.lx == 10); + reset(); T.local.analog = 0; T.guard = 1; T.host_pad_ok = 1; + CHECK(pad_ext_resolve(&hx, 0, &o) == 1 && T.host_pad_calls == 0); + reset(); T.have_source = 1; T.src_analog_out = 0; T.host_pad_ok = 1; + CHECK(pad_ext_resolve(&hx, 0, &o) == 1 && T.host_pad_calls == 0); + /* Unavailable host pad: the frame stays the stock pad. */ + reset(); T.local.analog = 0; T.local.buttons = folded; + T.local.lx = 0x80; + CHECK(pad_ext_resolve(&hx, 0, &o) == 1 && T.host_pad_calls == 1 && + T.xf_seen.buttons == folded && T.xf_seen.lx == 0x80); + } + /* No device and no source: the transform is not consulted. */ reset(); T.have_device = 0; T.xf_ret = 1; CHECK(pad_ext_resolve(&hx, 0, &o) == 0 && T.xf_calls == 0);