TCP input injection for port 2 (set_input/press/clear_input port=2)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 66cc81fe63)
This commit is contained in:
Matthew Stanley
2026-10-02 16:19:30 -07:00
parent e372fe2f22
commit dd64bc6611
3 changed files with 52 additions and 1 deletions
+2
View File
@@ -198,6 +198,8 @@ void debug_server_check_watchpoints(void);
/* Returns >= 0 if the debug server wants to override pad input,
* -1 if no override is active. Value is PS1 16-bit button mask. */
int debug_server_get_input_override(void);
/* Port-2 override word (set_input/press "port":2), or -1. */
int debug_server_get_input_override_port2(void);
/* Optional analog-stick override set alongside set_input (lx/ly/rx/ry,
* 0..255, 0x80 = centre). Returns 1 and fills st[lx,ly,rx,ry] when armed,
+34 -1
View File
@@ -286,6 +286,10 @@ static uint64_t s_dirty_break_hits = 0;
/* ---- Input override ---- */
static int s_input_override = -1;
static int s_input_frames = 0;
/* Port 2 (set_input / press / clear_input "port":2): digital buttons only,
* for driving two-player modes headless. */
static int s_input_override_p2 = -1;
static int s_input_frames_p2 = 0;
/* Optional analog-stick override (set_input lx/ly/rx/ry, 0..255, 0x80 =
* centre). Lets injected input drive analog-mode movement; consumed by the
* pad sampler alongside the button word. */
@@ -8004,6 +8008,12 @@ static void handle_set_input(int id, const char *json)
if (pad_type < -1 || pad_type > 2) {
send_err(id, "pad_type must be -1 (automatic), 0, 1, or 2"); return;
}
if (json_get_int(json, "port", 1) == 2) {
s_input_override_p2 = (int)(hex_to_u32(val_str) & 0xFFFFu);
s_input_frames_p2 = 0;
send_ok(id);
return;
}
s_pad_type_override = pad_type;
s_input_override = (int)hex_to_u32(val_str);
s_input_frames = 0;
@@ -8044,6 +8054,12 @@ static void handle_press(int id, const char *json)
if (pad_type < -1 || pad_type > 2) {
send_err(id, "pad_type must be -1 (automatic), 0, 1, or 2"); return;
}
if (json_get_int(json, "port", 1) == 2) {
s_input_override_p2 = buttons & 0xFFFF;
s_input_frames_p2 = frames;
send_ok(id);
return;
}
s_pad_type_override = pad_type;
s_input_override = buttons;
s_input_frames = frames;
@@ -8095,6 +8111,7 @@ static void handle_pad_status(int id, const char *json)
"\"slot1\":{\"buttons\":\"0x%04X\",\"connected\":%s,\"analog\":%s,\"type\":%d,\"sticks\":[%u,%u,%u,%u],"
"\"negcon\":[%u,%u,%u],\"mode_locked\":%s},"
"\"override\":%d,\"override_frames\":%d,\"override_pad_type\":%d,"
"\"override_p2\":%d,\"override_p2_frames\":%d,"
"\"override_axes\":[%u,%u,%u,%u],\"override_axes_valid\":%s,"
"\"host_layer\":%s,\"host_buttons\":\"0x%04X\",\"host_axes\":[%u,%u,%u,%u],"
"\"host_lt\":%u,\"host_rt\":%u}\n",
@@ -8108,6 +8125,7 @@ static void handle_pad_status(int id, const char *json)
sticks1[0], sticks1[1], sticks1[2], sticks1[3],
neg1[0], neg1[1], neg1[2], sio_get_pad_mode_locked(1) ? "true" : "false",
s_input_override, s_input_frames, s_pad_type_override,
s_input_override_p2, s_input_frames_p2,
s_axis_st[0], s_axis_st[1], s_axis_st[2], s_axis_st[3],
s_axis_override ? "true" : "false",
s_host_layer ? "true" : "false", s_host_buttons,
@@ -8117,7 +8135,12 @@ static void handle_pad_status(int id, const char *json)
static void handle_clear_input(int id, const char *json)
{
(void)json;
const int port = json_get_int(json, "port", 0); /* 0 = both */
if (port == 0 || port == 2) {
s_input_override_p2 = -1;
s_input_frames_p2 = 0;
}
if (port == 2) { send_ok(id); return; }
s_input_route_active = 0;
s_input_route_index = 0;
s_input_route_remaining = 0;
@@ -16718,6 +16741,16 @@ int debug_server_get_input_override(void)
return current;
}
int debug_server_get_input_override_port2(void)
{
int current = s_input_override_p2;
if (s_input_override_p2 >= 0 && s_input_frames_p2 > 0) {
if (--s_input_frames_p2 == 0)
s_input_override_p2 = -1;
}
return current;
}
int debug_server_get_axis_override(unsigned char st[4])
{
if (!s_axis_override) return 0;
+16
View File
@@ -6878,6 +6878,18 @@ static PadExtHooks pad_ext_main_hooks(void) {
return h;
}
/* TCP port-2 injection (set_input/press "port":2): a digital pad plugged
* into port 2 for as long as a test drives it, so headless runs can reach and
* play two-player modes. Applied after the normal sampling so it wins. */
static void apply_input_override_port2(int override_word) {
if (override_word < 0) return;
if (!sio_get_pad_connected(1)) {
sio_set_pad_connected(1, 1);
sio_set_pad_analog(1, 0, 0x80, 0x80, 0x80, 0x80);
}
sio_set_pad_state_slot(1, (uint16_t)override_word);
}
static void sample_pad_into_sio(int override) {
/* Selfcheck fighter mash owns P1 when enabled (headless-safe). */
if (override < 0) {
@@ -8098,12 +8110,14 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) {
/* Check debug server input override. */
int override = debug_server_get_input_override();
int override_p2 = debug_server_get_input_override_port2();
#else
/* Production: skip debug server. Still need to advance frame counter
* locally so anything else that reads it continues to work. */
extern uint64_t s_frame_count;
s_frame_count++;
int override = -1;
int override_p2 = -1;
#endif
psx_local_mouse_begin(sdl_window, local_mouse_live(override));
@@ -8299,6 +8313,7 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) {
sample_headless_pad_into_sio(override);
else
sample_pad_into_sio(override);
apply_input_override_port2(override_p2);
}
/* Offline vblank boundary: record/replay/compare (PSX_RB_SELFCHECK).
* Defer opening a window while multitap arming is still pending —
@@ -8629,6 +8644,7 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) {
SDL_PumpEvents(); // retain native timing when no policy exists
}
sample_pad_into_sio(override);
apply_input_override_port2(override_p2);
latency_ring_restamp_input();
}
}