fix(sio): plain digital pad ignores DualShock config commands (Tomba 2 menu input)

Tomba 2's title menu was unresponsive and the idle attract demo never
started. Root cause (proven against the Beetle oracle): the SIO pad
state machine answered the DualShock config-mode commands (0x43 enter-
config, 0x4D rumble-map, etc.) for EVERY pad, including a plain digital
controller. Tomba 2's pad driver probes the controller with 0x43 every
frame to detect a DualShock; on real hardware a digital pad (SCPH-1080,
id 0x41) ignores 0x43 (hi-z, no ACK), so the driver classifies it as
digital-only and just polls with 0x42. Our SM answered 0x43, so the
driver went down the DualShock config path and read the 0x00 config-
response data bytes as button data -> phantom "all buttons pressed"
(pad word 0x0000). That made the menu's edge-detected navigation see
nothing newly-pressed (unresponsive) AND reset the idle/attract timer
every frame (no attract).

Fix: model a plain digital controller. New per-slot pad_supports_config
flag (sio_set_pad_config_capable), set from the player pad mode
(PAD_MODE_DIGITAL => 0, ANALOG/HYBRID => 1, default 1). When 0, all
config-command branches (0x43 / legacy / modern 0x44-0x4F) fall through
to the hi-z "no response" path, so a digital pad answers ONLY the 0x42
poll -- byte-for-byte matching the oracle.

Validated live: SIO now shows only 0x42 polls (id 0x41, buttons 0xFFFF),
idle pad word is clean (no 0x0000 phantom), every injected button reaches
the menu var, and START navigates the title menu and starts the game.

Gated so only DIGITAL-mode pads change behaviour; HYBRID/ANALOG pads
(MMX6, Tomba 1, Ape) are unaffected (config-capable as before) -- but a
T1/MMX6/Ape regression pass is still required before any master merge.

NEW failure point (next bug in the chain, now reachable): with input
fixed, the idle attract demo triggers and loads a gameplay level, which
FREEZES -- guest spins in compiled kernel/dispatch code, dirty-interp
idle (insns_run flat), vblank stalled, level textures corrupted. This is
the untested gameplay-overlay stage, a separate issue.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-06-23 09:57:47 -07:00
parent 4f12f14bc7
commit aeef4b554e
3 changed files with 44 additions and 3 deletions
+10
View File
@@ -80,6 +80,16 @@ void sio_request_pad_type(int slot, int analog);
void sio_connect_pad(int slot);
void sio_set_pad_connected(int slot, int connected);
/* Declare whether the pad on a slot is a config-capable DualShock (1) or a
* plain digital controller (0). A real digital controller (SCPH-1080, poll id
* 0x41) does NOT answer the config-mode commands (0x43/0x44/0x45/0x46/0x47/
* 0x4C/0x4D/0x4F) — it returns hi-z / no ACK, so a game's pad driver classifies
* it as digital-only and just polls with 0x42. A DualShock answers them. Set
* from the per-player pad mode (DIGITAL => 0, ANALOG/HYBRID => 1) at boot/
* hotplug. Default is 1 (config-capable) so existing analog/hybrid behaviour is
* unchanged. */
void sio_set_pad_config_capable(int slot, int capable);
/* Return current pad button state (for debug server). _slot targets either. */
uint16_t sio_get_pad_buttons(void);
uint16_t sio_get_pad_buttons_slot(int slot);
+5
View File
@@ -1037,6 +1037,11 @@ static void refresh_player_devices(void) {
else open_player(p, g_players[s ^ 1]);
sio_set_pad_connected(s, p.kind != 0 ? 1 : 0);
sio_set_pad_analog(s, pad_mode_boot_analog(p.mode), 0x80, 0x80, 0x80, 0x80);
/* DIGITAL mode == a plain digital controller that ignores the DualShock
* config-mode commands (real SCPH-1080 behaviour); ANALOG/HYBRID == a
* config-capable DualShock. A digital pad that wrongly answered 0x43
* sent Tomba 2's pad driver down the config path -> phantom 0x00 reads. */
sio_set_pad_config_capable(s, p.mode != PSXRecompV4::PAD_MODE_DIGITAL);
}
}
+29 -3
View File
@@ -74,6 +74,18 @@ static uint8_t pad_current_cmd = 0;
* reaches 0x42. (MMX6 ISSUES.md #2.) */
static uint8_t pad_in_config[2] = { 0, 0 };
/* Whether the pad on a slot is a config-capable DualShock (1) or a plain
* digital controller (0). A real SCPH-1080 digital pad (poll id 0x41) does NOT
* answer the config-mode commands (0x43/0x44/.../0x4F): it returns hi-z and the
* transaction ends. A game's pad driver that probes with 0x43 to detect a
* DualShock therefore classifies a digital pad as digital-only and just polls
* it with 0x42. Tomba 2's driver probes this way every frame; when the SM
* (wrongly) answered 0x43 for its digital pad it went down the DualShock config
* path and read the 0x00 config-response bytes as buttons -> phantom "all
* pressed" input. Default 1 keeps analog/hybrid pads unchanged; main.cpp sets 0
* for PAD_MODE_DIGITAL. */
static uint8_t pad_supports_config[2] = { 1, 1 };
/* Coherent-DualShock model (Tomba phantom-input fix). A real controller never
* changes its reported type (0x41 digital <-> 0x73 analog) in the middle of a
* transaction, nor while the host is mid config-handshake: the type only flips
@@ -567,6 +579,14 @@ void sio_set_pad_connected(int slot, int connected) {
else pad_connected &= (uint8_t)~(1 << slot);
}
void sio_set_pad_config_capable(int slot, int capable) {
if (slot < 0 || slot > 1) return;
pad_supports_config[slot] = capable ? 1 : 0;
/* A plain digital pad can never be in config mode; clear any stale latch so
* the next poll reports the digital id (0x41), not the config id (0xF3). */
if (!capable) pad_in_config[slot] = 0;
}
void sio_set_pad_state(uint16_t buttons) {
pad_buttons[0] = buttons;
}
@@ -718,6 +738,12 @@ static void pad_process_byte(uint8_t tx_byte) {
{
const uint8_t cur_id = pad_in_config[selected_slot] ? 0xF3
: (pad_analog[selected_slot] ? 0x73 : 0x41);
/* A plain digital controller (SCPH-1080) answers ONLY the 0x42 poll; it
* ignores every config-mode command (returns hi-z, no ACK). A driver
* that probes with 0x43 to detect a DualShock then classifies it as
* digital-only and just polls. Gate all config branches on this so a
* digital-mode pad behaves like real hardware (see pad_supports_config). */
const int ds = pad_supports_config[selected_slot];
if (tx_byte == 0x42) {
/* Read poll. Analog (or in-config) uses the 8-byte format with the
* four stick axes; a plain digital pad uses the 4-byte format. */
@@ -738,7 +764,7 @@ static void pad_process_byte(uint8_t tx_byte) {
pad_state = PAD_SEND_RESPONSE;
sio_rx_data = pad_response[0];
sio_stat |= SIO_STAT_ACK;
} else if (tx_byte == 0x43) {
} else if (ds && tx_byte == 0x43) {
/* Enter/exit config mode. The ID byte reflects the CURRENT mode; the
* enter(0x01)/exit(0x00) flag is the second data byte, latched in
* PAD_SEND_RESPONSE so it takes effect after this transaction.
@@ -753,7 +779,7 @@ static void pad_process_byte(uint8_t tx_byte) {
pad_state = PAD_SEND_RESPONSE;
sio_rx_data = pad_response[0];
sio_stat |= SIO_STAT_ACK;
} else if (g_pad_legacy_cfg &&
} else if (ds && g_pad_legacy_cfg &&
(tx_byte == 0x45 || tx_byte == 0x46 || tx_byte == 0x47 ||
tx_byte == 0x4C || tx_byte == 0x4D)) {
/* LEGACY config answers (pre-98aa688): canned 0xF3 responses given
@@ -774,7 +800,7 @@ static void pad_process_byte(uint8_t tx_byte) {
pad_state = PAD_SEND_RESPONSE;
sio_rx_data = pad_response[0];
sio_stat |= SIO_STAT_ACK;
} else if (!g_pad_legacy_cfg && pad_in_config[selected_slot] &&
} else if (ds && !g_pad_legacy_cfg && pad_in_config[selected_slot] &&
(tx_byte == 0x44 || tx_byte == 0x45 || tx_byte == 0x46 ||
tx_byte == 0x47 || tx_byte == 0x4C || tx_byte == 0x4D ||
tx_byte == 0x4F)) {