From 08ccbe19c56abaad59c9b4e7fd81eb931da96140 Mon Sep 17 00:00:00 2001 From: Whoneon Date: Tue, 17 Feb 2026 15:12:19 +0100 Subject: [PATCH 1/5] pad: implement basic input mapping and stubs Add a minimal libpad implementation backed by raylib input Provide override hooks for deterministic tests Add pad input tests and link runtime into test target --- ps2xRuntime/include/ps2_stubs.h | 4 + ps2xRuntime/src/lib/ps2_stubs.cpp | 206 ++++++++++++++ ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl | 150 +++++++++-- ps2xTest/CMakeLists.txt | 3 + ps2xTest/src/main.cpp | 2 + ps2xTest/src/pad_input_tests.cpp | 265 +++++++++++++++++++ 6 files changed, 603 insertions(+), 27 deletions(-) create mode 100644 ps2xTest/src/pad_input_tests.cpp diff --git a/ps2xRuntime/include/ps2_stubs.h b/ps2xRuntime/include/ps2_stubs.h index 963caa94..3af65b17 100644 --- a/ps2xRuntime/include/ps2_stubs.h +++ b/ps2xRuntime/include/ps2_stubs.h @@ -16,6 +16,10 @@ namespace ps2_stubs void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); void TODO_NAMED(const char *name, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + + // Test hook: override pad input for scePadRead. + void setPadOverrideState(uint16_t buttons, uint8_t lx, uint8_t ly, uint8_t rx, uint8_t ry); + void clearPadOverrideState(); } #endif // PS2_STUBS_H diff --git a/ps2xRuntime/src/lib/ps2_stubs.cpp b/ps2xRuntime/src/lib/ps2_stubs.cpp index 6468b1f6..e836af96 100644 --- a/ps2xRuntime/src/lib/ps2_stubs.cpp +++ b/ps2xRuntime/src/lib/ps2_stubs.cpp @@ -16,11 +16,199 @@ #include #include #include +#include +#include "raylib.h" #include "stubs/helpers/ps2_stubs_helpers.inl" namespace ps2_stubs { + namespace + { + constexpr uint8_t kPadModeDualShock = 0x73; + constexpr uint8_t kPadAnalogCenter = 0x80; + + constexpr uint16_t kPadBtnSelect = 1u << 0; + constexpr uint16_t kPadBtnL3 = 1u << 1; + constexpr uint16_t kPadBtnR3 = 1u << 2; + constexpr uint16_t kPadBtnStart = 1u << 3; + constexpr uint16_t kPadBtnUp = 1u << 4; + constexpr uint16_t kPadBtnRight = 1u << 5; + constexpr uint16_t kPadBtnDown = 1u << 6; + constexpr uint16_t kPadBtnLeft = 1u << 7; + constexpr uint16_t kPadBtnL2 = 1u << 8; + constexpr uint16_t kPadBtnR2 = 1u << 9; + constexpr uint16_t kPadBtnL1 = 1u << 10; + constexpr uint16_t kPadBtnR1 = 1u << 11; + constexpr uint16_t kPadBtnTriangle = 1u << 12; + constexpr uint16_t kPadBtnCircle = 1u << 13; + constexpr uint16_t kPadBtnCross = 1u << 14; + constexpr uint16_t kPadBtnSquare = 1u << 15; + + struct PadInputState + { + uint16_t buttons = 0xFFFF; // active-low + uint8_t rx = kPadAnalogCenter; + uint8_t ry = kPadAnalogCenter; + uint8_t lx = kPadAnalogCenter; + uint8_t ly = kPadAnalogCenter; + }; + + std::mutex g_padOverrideMutex; + bool g_padOverrideEnabled = false; + PadInputState g_padOverrideState{}; + bool g_padDebugCached = false; + bool g_padDebugEnabled = false; + + uint8_t axisToByte(float axis) + { + axis = std::clamp(axis, -1.0f, 1.0f); + const float mapped = (axis + 1.0f) * 127.5f; + return static_cast(std::lround(mapped)); + } + + bool padDebugEnabled() + { + if (!g_padDebugCached) + { + const char *env = std::getenv("PS2_PAD_DEBUG"); + g_padDebugEnabled = (env && *env && std::strcmp(env, "0") != 0); + g_padDebugCached = true; + } + return g_padDebugEnabled; + } + + void setButton(PadInputState &state, uint16_t mask, bool pressed) + { + if (pressed) + { + state.buttons = static_cast(state.buttons & ~mask); + } + } + + int findFirstGamepad() + { + for (int i = 0; i < 4; ++i) + { + if (IsGamepadAvailable(i)) + { + return i; + } + } + return -1; + } + + void applyGamepadState(PadInputState &state) + { + if (!IsWindowReady()) + { + return; + } + + const int gamepad = findFirstGamepad(); + if (gamepad < 0) + { + return; + } + + // Raylib mapping (PS2 -> raylib buttons/axes): + // D-Pad -> LEFT_FACE_*, Cross/Circle/Square/Triangle -> RIGHT_FACE_* + // L1/R1 -> TRIGGER_1, L2/R2 -> TRIGGER_2, L3/R3 -> THUMB + // Select/Start -> MIDDLE_LEFT/MIDDLE_RIGHT + state.lx = axisToByte(GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_LEFT_X)); + state.ly = axisToByte(GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_LEFT_Y)); + state.rx = axisToByte(GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_RIGHT_X)); + state.ry = axisToByte(GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_RIGHT_Y)); + + setButton(state, kPadBtnUp, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_FACE_UP)); + setButton(state, kPadBtnDown, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_FACE_DOWN)); + setButton(state, kPadBtnLeft, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_FACE_LEFT)); + setButton(state, kPadBtnRight, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_FACE_RIGHT)); + + setButton(state, kPadBtnCross, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_DOWN)); + setButton(state, kPadBtnCircle, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_RIGHT)); + setButton(state, kPadBtnSquare, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_LEFT)); + setButton(state, kPadBtnTriangle, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_UP)); + + setButton(state, kPadBtnL1, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_TRIGGER_1)); + setButton(state, kPadBtnR1, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_TRIGGER_1)); + setButton(state, kPadBtnL2, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_TRIGGER_2)); + setButton(state, kPadBtnR2, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_TRIGGER_2)); + + setButton(state, kPadBtnL3, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_THUMB)); + setButton(state, kPadBtnR3, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_THUMB)); + + setButton(state, kPadBtnSelect, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_MIDDLE_LEFT)); + setButton(state, kPadBtnStart, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_MIDDLE_RIGHT)); + } + + void applyKeyboardState(PadInputState &state, bool allowAnalog) + { + if (!IsWindowReady()) + { + return; + } + + // Keyboard mapping (PS2 -> keys): + // D-Pad: arrows, Square/Cross/Circle/Triangle: Z/X/C/V + // L1/R1: Q/E, L2/R2: 1/3, Start/Select: Enter/RightShift + // L3/R3: LeftCtrl/RightCtrl, Analog left: WASD + setButton(state, kPadBtnUp, IsKeyDown(KEY_UP)); + setButton(state, kPadBtnDown, IsKeyDown(KEY_DOWN)); + setButton(state, kPadBtnLeft, IsKeyDown(KEY_LEFT)); + setButton(state, kPadBtnRight, IsKeyDown(KEY_RIGHT)); + + setButton(state, kPadBtnSquare, IsKeyDown(KEY_Z)); + setButton(state, kPadBtnCross, IsKeyDown(KEY_X)); + setButton(state, kPadBtnCircle, IsKeyDown(KEY_C)); + setButton(state, kPadBtnTriangle, IsKeyDown(KEY_V)); + + setButton(state, kPadBtnL1, IsKeyDown(KEY_Q)); + setButton(state, kPadBtnR1, IsKeyDown(KEY_E)); + setButton(state, kPadBtnL2, IsKeyDown(KEY_ONE)); + setButton(state, kPadBtnR2, IsKeyDown(KEY_THREE)); + + setButton(state, kPadBtnStart, IsKeyDown(KEY_ENTER)); + setButton(state, kPadBtnSelect, IsKeyDown(KEY_RIGHT_SHIFT)); + setButton(state, kPadBtnL3, IsKeyDown(KEY_LEFT_CONTROL)); + setButton(state, kPadBtnR3, IsKeyDown(KEY_RIGHT_CONTROL)); + + if (!allowAnalog) + { + return; + } + + float ax = 0.0f; + float ay = 0.0f; + if (IsKeyDown(KEY_D)) + ax += 1.0f; + if (IsKeyDown(KEY_A)) + ax -= 1.0f; + if (IsKeyDown(KEY_S)) + ay += 1.0f; + if (IsKeyDown(KEY_W)) + ay -= 1.0f; + + if (ax != 0.0f || ay != 0.0f) + { + state.lx = axisToByte(ax); + state.ly = axisToByte(ay); + } + } + + void fillPadStatus(uint8_t *data, const PadInputState &state) + { + std::memset(data, 0, 32); + data[1] = kPadModeDualShock; + data[2] = static_cast(state.buttons & 0xFFu); + data[3] = static_cast((state.buttons >> 8) & 0xFFu); + data[4] = state.rx; + data[5] = state.ry; + data[6] = state.lx; + data[7] = state.ly; + } + } + #include "stubs/ps2_stubs_libc.inl" #include "stubs/ps2_stubs_ps2.inl" #include "stubs/ps2_stubs_misc.inl" @@ -70,4 +258,22 @@ namespace ps2_stubs setReturnS32(ctx, -1); // Return error } + void setPadOverrideState(uint16_t buttons, uint8_t lx, uint8_t ly, uint8_t rx, uint8_t ry) + { + std::lock_guard lock(g_padOverrideMutex); + g_padOverrideEnabled = true; + g_padOverrideState.buttons = buttons; + g_padOverrideState.lx = lx; + g_padOverrideState.ly = ly; + g_padOverrideState.rx = rx; + g_padOverrideState.ry = ry; + } + + void clearPadOverrideState() + { + std::lock_guard lock(g_padOverrideMutex); + g_padOverrideEnabled = false; + g_padOverrideState = PadInputState{}; + } + } diff --git a/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl b/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl index e1b2d9bd..a449233c 100644 --- a/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl +++ b/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl @@ -1096,32 +1096,47 @@ void sceOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void scePadEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadEnd", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + setReturnS32(ctx, 1); } void scePadEnterPressMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadEnterPressMode", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + setReturnS32(ctx, 1); } void scePadExitPressMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadExitPressMode", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + setReturnS32(ctx, 1); } void scePadGetButtonMask(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadGetButtonMask", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + // Report all buttons supported. + setReturnS32(ctx, 0xFFFF); } void scePadGetDmaStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadGetDmaStr", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + // No DMA structure exposed in this minimal implementation. + setReturnS32(ctx, 0); } void scePadGetFrameCount(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadGetFrameCount", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + static std::atomic frameCount{0}; + setReturnU32(ctx, frameCount++); } void scePadGetModVersion(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1165,12 +1180,18 @@ void scePadGetState(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void scePadInfoAct(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadInfoAct", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + // No actuators supported. + setReturnS32(ctx, 0); } void scePadInfoComb(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadInfoComb", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + // No combined modes reported. + setReturnS32(ctx, 0); } void scePadInfoMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1262,62 +1283,138 @@ void scePadRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) return; } - // struct padButtonStatus (32 bytes): neutral state, no buttons pressed. - std::memset(data, 0, 32); - data[1] = 0x73; // analog/dualshock mode marker - data[2] = 0xFF; // btns low (active-low) - data[3] = 0xFF; // btns high - data[4] = 0x80; // rjoy_h - data[5] = 0x80; // rjoy_v - data[6] = 0x80; // ljoy_h - data[7] = 0x80; // ljoy_v + PadInputState state; + bool useOverride = false; + { + std::lock_guard lock(g_padOverrideMutex); + if (g_padOverrideEnabled) + { + state = g_padOverrideState; + useOverride = true; + } + } + + if (!useOverride) + { + applyGamepadState(state); + applyKeyboardState(state, true); + } + + fillPadStatus(data, state); + + if (padDebugEnabled()) + { + static uint32_t logCounter = 0; + if ((logCounter++ % 60u) == 0u) + { + std::cout << "[pad] buttons=0x" << std::hex << state.buttons << std::dec + << " lx=" << static_cast(state.lx) + << " ly=" << static_cast(state.ly) + << " rx=" << static_cast(state.rx) + << " ry=" << static_cast(state.ry) + << (useOverride ? " (override)" : "") << std::endl; + } + } setReturnS32(ctx, 1); } void scePadReqIntToStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadReqIntToStr", rdram, ctx, runtime); + (void)runtime; + const uint32_t state = getRegU32(ctx, 4); + const uint32_t strAddr = getRegU32(ctx, 5); + char *buf = reinterpret_cast(getMemPtr(rdram, strAddr)); + if (!buf) + { + setReturnS32(ctx, -1); + return; + } + + const char *text = (state == 0) ? "COMPLETE" : "BUSY"; + std::strncpy(buf, text, 31); + buf[31] = '\0'; + setReturnS32(ctx, 0); } void scePadSetActAlign(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadSetActAlign", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + setReturnS32(ctx, 1); } void scePadSetActDirect(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadSetActDirect", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + setReturnS32(ctx, 1); } void scePadSetButtonInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadSetButtonInfo", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + setReturnS32(ctx, 1); } void scePadSetMainMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadSetMainMode", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + setReturnS32(ctx, 1); } void scePadSetReqState(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadSetReqState", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + setReturnS32(ctx, 1); } void scePadSetVrefParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadSetVrefParam", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + setReturnS32(ctx, 1); } void scePadSetWarningLevel(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadSetWarningLevel", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + setReturnS32(ctx, 0); } void scePadStateIntToStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadStateIntToStr", rdram, ctx, runtime); + (void)runtime; + const uint32_t state = getRegU32(ctx, 4); + const uint32_t strAddr = getRegU32(ctx, 5); + char *buf = reinterpret_cast(getMemPtr(rdram, strAddr)); + if (!buf) + { + setReturnS32(ctx, -1); + return; + } + + const char *text = "UNKNOWN"; + if (state == 6) + { + text = "STABLE"; + } + else if (state == 1) + { + text = "FINDPAD"; + } + else if (state == 0) + { + text = "DISCONNECTED"; + } + + std::strncpy(buf, text, 31); + buf[31] = '\0'; + setReturnS32(ctx, 0); } void scePrintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2360,4 +2457,3 @@ void write(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { ps2_syscalls::fioWrite(rdram, ctx, runtime); } - diff --git a/ps2xTest/CMakeLists.txt b/ps2xTest/CMakeLists.txt index 2f5fd3f6..58aa6632 100644 --- a/ps2xTest/CMakeLists.txt +++ b/ps2xTest/CMakeLists.txt @@ -10,6 +10,7 @@ add_executable(ps2x_tests src/code_generator_tests.cpp src/r5900_decoder_tests.cpp src/elf_analyzer_tests.cpp + src/pad_input_tests.cpp ) option(PRINT_GENERATED_CODE "Print generated code in tests" OFF) @@ -21,9 +22,11 @@ target_include_directories(ps2x_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/ps2xRecomp/include ${CMAKE_SOURCE_DIR}/ps2xAnalyzer/include + ${CMAKE_SOURCE_DIR}/ps2xRuntime/include ) target_link_libraries(ps2x_tests PRIVATE ps2_recomp_lib ps2_analyzer_lib + ps2_runtime ) diff --git a/ps2xTest/src/main.cpp b/ps2xTest/src/main.cpp index c0bbee84..4e324cd1 100644 --- a/ps2xTest/src/main.cpp +++ b/ps2xTest/src/main.cpp @@ -3,11 +3,13 @@ void register_code_generator_tests(); void register_r5900_decoder_tests(); void register_elf_analyzer_tests(); +void register_pad_input_tests(); int main() { register_code_generator_tests(); register_r5900_decoder_tests(); register_elf_analyzer_tests(); + register_pad_input_tests(); return MiniTest::Run(); } diff --git a/ps2xTest/src/pad_input_tests.cpp b/ps2xTest/src/pad_input_tests.cpp new file mode 100644 index 00000000..b3a18f07 --- /dev/null +++ b/ps2xTest/src/pad_input_tests.cpp @@ -0,0 +1,265 @@ +#include "MiniTest.h" +#include "ps2_stubs.h" + +#include +#include +#include + +namespace +{ + constexpr uint32_t kPadDataAddr = 0x1000; + + constexpr uint16_t kPadBtnSelect = 1u << 0; + constexpr uint16_t kPadBtnL3 = 1u << 1; + constexpr uint16_t kPadBtnR3 = 1u << 2; + constexpr uint16_t kPadBtnStart = 1u << 3; + constexpr uint16_t kPadBtnUp = 1u << 4; + constexpr uint16_t kPadBtnRight = 1u << 5; + constexpr uint16_t kPadBtnDown = 1u << 6; + constexpr uint16_t kPadBtnLeft = 1u << 7; + constexpr uint16_t kPadBtnL2 = 1u << 8; + constexpr uint16_t kPadBtnR2 = 1u << 9; + constexpr uint16_t kPadBtnL1 = 1u << 10; + constexpr uint16_t kPadBtnR1 = 1u << 11; + constexpr uint16_t kPadBtnTriangle = 1u << 12; + constexpr uint16_t kPadBtnCircle = 1u << 13; + constexpr uint16_t kPadBtnCross = 1u << 14; + constexpr uint16_t kPadBtnSquare = 1u << 15; + + void setRegU32(R5900Context &ctx, int reg, uint32_t value) + { + ctx.r[reg] = _mm_set_epi64x(0, static_cast(value)); + } + + void runPadRead(R5900Context &ctx, std::vector &rdram) + { + setRegU32(ctx, 6, kPadDataAddr); // a2 + ps2_stubs::scePadRead(rdram.data(), &ctx, nullptr); + } + + uint16_t readButtons(const std::vector &rdram) + { + const uint8_t *data = rdram.data() + kPadDataAddr; + return static_cast(data[2] | (data[3] << 8)); + } +} + +void register_pad_input_tests() +{ + MiniTest::Case("PadInput", [](TestCase &tc) + { + tc.Run("scePadRead uses override state", [](TestCase &t) + { + std::vector rdram(PS2_RAM_SIZE, 0); + R5900Context ctx; + + const uint16_t buttons = static_cast(0xFFFFu & ~kPadBtnCross & ~kPadBtnStart); + ps2_stubs::setPadOverrideState(buttons, 0x00, 0xFF, 0x10, 0xEE); + + runPadRead(ctx, rdram); + + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadRead should return 1"); + t.Equals(readButtons(rdram), buttons, "button bitmask should match override state"); + const uint8_t *data = rdram.data() + kPadDataAddr; + t.Equals(data[4], static_cast(0x10), "rx should match override"); + t.Equals(data[5], static_cast(0xEE), "ry should match override"); + t.Equals(data[6], static_cast(0x00), "lx should match override"); + t.Equals(data[7], static_cast(0xFF), "ly should match override"); + + ps2_stubs::clearPadOverrideState(); + }); + + tc.Run("scePadRead button bits are active-low", [](TestCase &t) + { + std::vector rdram(PS2_RAM_SIZE, 0); + R5900Context ctx; + + struct ButtonCase + { + uint16_t mask; + const char *name; + }; + + const ButtonCase cases[] = { + {kPadBtnSelect, "select"}, + {kPadBtnL3, "l3"}, + {kPadBtnR3, "r3"}, + {kPadBtnStart, "start"}, + {kPadBtnUp, "up"}, + {kPadBtnRight, "right"}, + {kPadBtnDown, "down"}, + {kPadBtnLeft, "left"}, + {kPadBtnL2, "l2"}, + {kPadBtnR2, "r2"}, + {kPadBtnL1, "l1"}, + {kPadBtnR1, "r1"}, + {kPadBtnTriangle, "triangle"}, + {kPadBtnCircle, "circle"}, + {kPadBtnCross, "cross"}, + {kPadBtnSquare, "square"}}; + + for (const auto &entry : cases) + { + const uint16_t buttons = static_cast(0xFFFFu & ~entry.mask); + ps2_stubs::setPadOverrideState(buttons, 0x80, 0x80, 0x80, 0x80); + runPadRead(ctx, rdram); + + const uint16_t mask = readButtons(rdram); + t.IsTrue((mask & entry.mask) == 0, std::string("button should be active-low: ").append(entry.name)); + } + + ps2_stubs::clearPadOverrideState(); + }); + + tc.Run("scePadGetButtonMask returns all buttons", [](TestCase &t) + { + R5900Context ctx; + ps2_stubs::scePadGetButtonMask(nullptr, &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(0xFFFF), "button mask should be 0xFFFF"); + }); + + tc.Run("basic pad init/port/state functions return expected values", [](TestCase &t) + { + R5900Context ctx; + + ps2_stubs::scePadInit(nullptr, &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadInit should succeed"); + + ps2_stubs::scePadInit2(nullptr, &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadInit2 should succeed"); + + ps2_stubs::scePadPortOpen(nullptr, &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadPortOpen should succeed"); + + ps2_stubs::scePadPortClose(nullptr, &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadPortClose should succeed"); + + ps2_stubs::scePadGetState(nullptr, &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(6), "scePadGetState should return STABLE"); + + ps2_stubs::scePadGetReqState(nullptr, &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(0), "scePadGetReqState should return completed"); + + ps2_stubs::scePadGetPortMax(nullptr, &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(2), "scePadGetPortMax should be 2"); + + ps2_stubs::scePadGetSlotMax(nullptr, &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadGetSlotMax should be 1"); + + ps2_stubs::scePadGetModVersion(nullptr, &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(0x0200), "scePadGetModVersion should be 0x0200"); + }); + + tc.Run("pad info and mode helpers return consistent values", [](TestCase &t) + { + std::vector rdram(PS2_RAM_SIZE, 0); + R5900Context ctx; + + ps2_stubs::scePadInfoAct(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(0), "scePadInfoAct should return 0"); + + ps2_stubs::scePadInfoComb(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(0), "scePadInfoComb should return 0"); + + setRegU32(ctx, 6, 1); + setRegU32(ctx, 7, 0); + ps2_stubs::scePadInfoMode(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(7), "scePadInfoMode CURID should return DualShock"); + + setRegU32(ctx, 6, 4); + setRegU32(ctx, 7, static_cast(-1)); + ps2_stubs::scePadInfoMode(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadInfoMode table count should be 1"); + + ps2_stubs::scePadInfoPressMode(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(0), "scePadInfoPressMode should be 0"); + }); + + tc.Run("pad setters return success", [](TestCase &t) + { + std::vector rdram(PS2_RAM_SIZE, 0); + R5900Context ctx; + + ps2_stubs::scePadSetActAlign(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadSetActAlign should succeed"); + + ps2_stubs::scePadSetActDirect(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadSetActDirect should succeed"); + + ps2_stubs::scePadSetButtonInfo(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadSetButtonInfo should succeed"); + + ps2_stubs::scePadSetMainMode(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadSetMainMode should succeed"); + + ps2_stubs::scePadSetReqState(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadSetReqState should succeed"); + + ps2_stubs::scePadSetVrefParam(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadSetVrefParam should succeed"); + + ps2_stubs::scePadSetWarningLevel(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(0), "scePadSetWarningLevel should return 0"); + + ps2_stubs::scePadEnd(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadEnd should succeed"); + + ps2_stubs::scePadEnterPressMode(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadEnterPressMode should succeed"); + + ps2_stubs::scePadExitPressMode(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadExitPressMode should succeed"); + }); + + tc.Run("pad string helpers map state codes", [](TestCase &t) + { + std::vector rdram(PS2_RAM_SIZE, 0); + R5900Context ctx; + + setRegU32(ctx, 4, 1); + setRegU32(ctx, 5, kPadDataAddr); + ps2_stubs::scePadStateIntToStr(rdram.data(), &ctx, nullptr); + t.IsTrue(std::string(reinterpret_cast(rdram.data() + kPadDataAddr)).find("FINDPAD") != std::string::npos, + "state 1 should map to FINDPAD"); + + setRegU32(ctx, 4, 0); + setRegU32(ctx, 5, kPadDataAddr + 64); + ps2_stubs::scePadStateIntToStr(rdram.data(), &ctx, nullptr); + t.IsTrue(std::string(reinterpret_cast(rdram.data() + kPadDataAddr + 64)).find("DISCONNECTED") != std::string::npos, + "state 0 should map to DISCONNECTED"); + + setRegU32(ctx, 4, 1); + setRegU32(ctx, 5, kPadDataAddr + 128); + ps2_stubs::scePadReqIntToStr(rdram.data(), &ctx, nullptr); + t.IsTrue(std::string(reinterpret_cast(rdram.data() + kPadDataAddr + 128)).find("BUSY") != std::string::npos, + "req state 1 should map to BUSY"); + }); + tc.Run("scePadGetFrameCount increments", [](TestCase &t) + { + R5900Context ctx; + ps2_stubs::scePadGetFrameCount(nullptr, &ctx, nullptr); + const uint32_t first = getRegU32(&ctx, 2); + ps2_stubs::scePadGetFrameCount(nullptr, &ctx, nullptr); + const uint32_t second = getRegU32(&ctx, 2); + t.Equals(second, first + 1, "frame count should increment"); + }); + + tc.Run("scePadStateIntToStr and scePadReqIntToStr write strings", [](TestCase &t) + { + std::vector rdram(PS2_RAM_SIZE, 0); + R5900Context ctx; + + setRegU32(ctx, 4, 6); + setRegU32(ctx, 5, kPadDataAddr); + ps2_stubs::scePadStateIntToStr(rdram.data(), &ctx, nullptr); + const char *stateStr = reinterpret_cast(rdram.data() + kPadDataAddr); + t.IsTrue(std::string(stateStr).find("STABLE") != std::string::npos, "state string should include STABLE"); + + setRegU32(ctx, 4, 0); + setRegU32(ctx, 5, kPadDataAddr + 64); + ps2_stubs::scePadReqIntToStr(rdram.data(), &ctx, nullptr); + const char *reqStr = reinterpret_cast(rdram.data() + kPadDataAddr + 64); + t.IsTrue(std::string(reqStr).find("COMPLETE") != std::string::npos, "req string should include COMPLETE"); + }); + }); +} From 72d84861e27abae3258be18fca7a4b9b26586e49 Mon Sep 17 00:00:00 2001 From: Whoneon Date: Mon, 23 Feb 2026 08:58:59 +0100 Subject: [PATCH 2/5] FIX: moved pad override test hooks to ps2_call_list.h --- ps2xRuntime/include/ps2_call_list.h | 13 +++++++++++++ ps2xRuntime/include/ps2_stubs.h | 3 --- ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ps2xRuntime/include/ps2_call_list.h b/ps2xRuntime/include/ps2_call_list.h index f57b18fe..b9562925 100644 --- a/ps2xRuntime/include/ps2_call_list.h +++ b/ps2xRuntime/include/ps2_call_list.h @@ -1,5 +1,9 @@ #pragma once +#ifdef __cplusplus +#include +#endif + // I know ugly, but will work for now. #define PS2_SYSCALL_LIST(X) \ @@ -612,3 +616,12 @@ X(syMallocInit) \ X(syRtcInit) \ /* Game/middleware */ + +// Test hooks (C++ only): override pad input for scePadRead. +#ifdef __cplusplus +namespace ps2_stubs +{ + void setPadOverrideState(uint16_t buttons, uint8_t lx, uint8_t ly, uint8_t rx, uint8_t ry); + void clearPadOverrideState(); +} +#endif diff --git a/ps2xRuntime/include/ps2_stubs.h b/ps2xRuntime/include/ps2_stubs.h index 3af65b17..d01771d0 100644 --- a/ps2xRuntime/include/ps2_stubs.h +++ b/ps2xRuntime/include/ps2_stubs.h @@ -17,9 +17,6 @@ namespace ps2_stubs void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); void TODO_NAMED(const char *name, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); - // Test hook: override pad input for scePadRead. - void setPadOverrideState(uint16_t buttons, uint8_t lx, uint8_t ly, uint8_t rx, uint8_t ry); - void clearPadOverrideState(); } #endif // PS2_STUBS_H diff --git a/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl b/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl index b656da96..a2a873d5 100644 --- a/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl +++ b/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl @@ -1626,7 +1626,7 @@ void scePadSetWarningLevel(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtim { (void)rdram; (void)runtime; - setReturnS32(ctx, 1); + setReturnS32(ctx, 0); } void scePadStateIntToStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) From 9b354f41d754c0dae1f9fab230cd6cec38501b62 Mon Sep 17 00:00:00 2001 From: Whoneon Date: Tue, 24 Feb 2026 16:08:09 +0100 Subject: [PATCH 3/5] pad: move test hooks to X macro list + removing __cplusplus ifdef --- ps2xRuntime/include/ps2_call_list.h | 13 +++++-------- ps2xRuntime/include/ps2_stubs.h | 4 ++++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/ps2xRuntime/include/ps2_call_list.h b/ps2xRuntime/include/ps2_call_list.h index b9562925..305829ab 100644 --- a/ps2xRuntime/include/ps2_call_list.h +++ b/ps2xRuntime/include/ps2_call_list.h @@ -617,11 +617,8 @@ X(syRtcInit) \ /* Game/middleware */ -// Test hooks (C++ only): override pad input for scePadRead. -#ifdef __cplusplus -namespace ps2_stubs -{ - void setPadOverrideState(uint16_t buttons, uint8_t lx, uint8_t ly, uint8_t rx, uint8_t ry); - void clearPadOverrideState(); -} -#endif +// Test hooks: override pad input for scePadRead. +#define PS2_TEST_HOOK_LIST(X) \ + X(setPadOverrideState, (uint16_t buttons, uint8_t lx, uint8_t ly, \ + uint8_t rx, uint8_t ry)) \ + X(clearPadOverrideState, (void)) diff --git a/ps2xRuntime/include/ps2_stubs.h b/ps2xRuntime/include/ps2_stubs.h index d01771d0..fc9a6071 100644 --- a/ps2xRuntime/include/ps2_stubs.h +++ b/ps2xRuntime/include/ps2_stubs.h @@ -11,6 +11,10 @@ namespace ps2_stubs PS2_STUB_LIST(PS2_DECLARE_STUB) #undef PS2_DECLARE_STUB + #define PS2_DECLARE_TEST_HOOK(name, signature) void name signature; + PS2_TEST_HOOK_LIST(PS2_DECLARE_TEST_HOOK) + #undef PS2_DECLARE_TEST_HOOK + void syMalloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); void sndr_trans_func(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); From a353ea73ec215e318df1cee18774575308666c63 Mon Sep 17 00:00:00 2001 From: Whoneon Date: Tue, 24 Feb 2026 16:15:26 +0100 Subject: [PATCH 4/5] removed one last ifdef remaining --- ps2xRuntime/include/ps2_call_list.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/ps2xRuntime/include/ps2_call_list.h b/ps2xRuntime/include/ps2_call_list.h index 305829ab..9bc1ec75 100644 --- a/ps2xRuntime/include/ps2_call_list.h +++ b/ps2xRuntime/include/ps2_call_list.h @@ -1,8 +1,6 @@ #pragma once -#ifdef __cplusplus #include -#endif // I know ugly, but will work for now. From 7ce6ce249a575fc354b89dfd5fa8c6eed4c0e272 Mon Sep 17 00:00:00 2001 From: Whoneon Date: Mon, 2 Mar 2026 08:48:04 +0100 Subject: [PATCH 5/5] refresh PR merge status