Issue 13: Minimal libpad (SIO2/PAD) stubs, input mapping and tests (#69)

* 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

* FIX: moved pad override test hooks to ps2_call_list.h

* pad: move test hooks to X macro list + removing __cplusplus ifdef

* removed one last ifdef remaining

* refresh PR merge status

---------

Co-authored-by: Ranieri <ran-junior@hotmail.com>
This commit is contained in:
Antonio Guastella
2026-03-03 20:02:04 +01:00
committed by GitHub
parent 765cbcca0f
commit ccd17e5244
7 changed files with 592 additions and 35 deletions
+8
View File
@@ -1,5 +1,7 @@
#pragma once
#include <cstdint>
// I know ugly, but will work for now.
#define PS2_SYSCALL_LIST(X) \
@@ -644,3 +646,9 @@
X(syRtcInit) \
X(InitThread) \
/* Game/middleware */
// 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))
+4
View File
@@ -11,6 +11,9 @@ 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 resetGsSyncVCallbackState();
void dispatchGsSyncVCallback(uint8_t *rdram, PS2Runtime *runtime);
@@ -19,6 +22,7 @@ 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);
}
#endif // PS2_STUBS_H
+206
View File
@@ -18,12 +18,200 @@
#include <unordered_set>
#include <filesystem>
#include <mutex>
#include <atomic>
#include <limits>
#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<uint8_t>(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<uint16_t>(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<uint8_t>(state.buttons & 0xFFu);
data[3] = static_cast<uint8_t>((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"
@@ -74,4 +262,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<std::mutex> 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<std::mutex> lock(g_padOverrideMutex);
g_padOverrideEnabled = false;
g_padOverrideState = PadInputState{};
}
}
+106 -35
View File
@@ -2212,33 +2212,46 @@ void sceOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
void scePadEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
(void)runtime;
setReturnS32(ctx, 1);
}
void scePadEnterPressMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
(void)runtime;
setReturnS32(ctx, 1);
}
void scePadExitPressMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
(void)runtime;
setReturnS32(ctx, 1);
}
void scePadGetButtonMask(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
(void)runtime;
// Report all buttons supported.
setReturnS32(ctx, static_cast<int32_t>(0xFFFFu));
}
void scePadGetDmaStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
(void)runtime;
setReturnU32(ctx, getRegU32(ctx, 6));
}
void scePadGetFrameCount(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static uint32_t frameCount = 0u;
setReturnU32(ctx, ++frameCount);
(void)rdram;
(void)runtime;
static std::atomic<uint32_t> frameCount{0};
setReturnU32(ctx, frameCount++);
}
void scePadGetModVersion(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
@@ -2293,6 +2306,9 @@ void scePadInfoAct(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
void scePadInfoComb(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
(void)runtime;
// No combined modes reported.
setReturnS32(ctx, 0);
}
@@ -2384,90 +2400,145 @@ void scePadRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
setReturnS32(ctx, 0);
return;
}
if (runtime && runtime->padBackend().readState(port, slot, data, 32))
PadInputState state;
bool useOverride = false;
{
setReturnS32(ctx, 1);
return;
std::lock_guard<std::mutex> lock(g_padOverrideMutex);
if (g_padOverrideEnabled)
{
state = g_padOverrideState;
useOverride = true;
}
}
std::memset(data, 0, 32);
data[1] = 0x73;
data[2] = data[3] = 0xFF;
data[4] = data[5] = data[6] = data[7] = 0x80;
if (!useOverride)
{
if (runtime && runtime->padBackend().readState(port, slot, data, 32))
{
setReturnS32(ctx, 1);
return;
}
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<int>(state.lx)
<< " ly=" << static_cast<int>(state.ly)
<< " rx=" << static_cast<int>(state.rx)
<< " ry=" << static_cast<int>(state.ry)
<< (useOverride ? " (override)" : "") << std::endl;
}
}
setReturnS32(ctx, 1);
}
void scePadReqIntToStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t outAddr = getRegU32(ctx, 5);
if (uint8_t *out = getMemPtr(rdram, outAddr))
(void)runtime;
const uint32_t state = getRegU32(ctx, 4);
const uint32_t strAddr = getRegU32(ctx, 5);
char *buf = reinterpret_cast<char *>(getMemPtr(rdram, strAddr));
if (!buf)
{
constexpr const char *kReq = "COMPLETE";
std::memcpy(out, kReq, std::strlen(kReq) + 1u);
setReturnU32(ctx, outAddr);
setReturnS32(ctx, -1);
return;
}
setReturnU32(ctx, 0u);
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)
{
(void)rdram;
(void)runtime;
setReturnS32(ctx, 1);
}
void scePadSetActDirect(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
(void)runtime;
setReturnS32(ctx, 1);
}
void scePadSetButtonInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
(void)runtime;
setReturnS32(ctx, 1);
}
void scePadSetMainMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
(void)runtime;
setReturnS32(ctx, 1);
}
void scePadSetReqState(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
(void)runtime;
setReturnS32(ctx, 1);
}
void scePadSetVrefParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
(void)runtime;
setReturnS32(ctx, 1);
}
void scePadSetWarningLevel(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
setReturnS32(ctx, 1);
(void)rdram;
(void)runtime;
setReturnS32(ctx, 0);
}
void scePadStateIntToStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const int32_t state = static_cast<int32_t>(getRegU32(ctx, 4));
const uint32_t outAddr = getRegU32(ctx, 5);
const char *label = "UNKNOWN";
switch (state)
(void)runtime;
const uint32_t state = getRegU32(ctx, 4);
const uint32_t strAddr = getRegU32(ctx, 5);
char *buf = reinterpret_cast<char *>(getMemPtr(rdram, strAddr));
if (!buf)
{
case 0:
label = "DISCONN";
break;
case 6:
label = "STABLE";
break;
default:
break;
}
if (uint8_t *out = getMemPtr(rdram, outAddr))
{
std::memcpy(out, label, std::strlen(label) + 1u);
setReturnU32(ctx, outAddr);
setReturnS32(ctx, -1);
return;
}
setReturnU32(ctx, 0u);
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)
+1
View File
@@ -10,6 +10,7 @@ add_library(ps2_test_lib STATIC
src/code_generator_tests.cpp
src/r5900_decoder_tests.cpp
src/elf_analyzer_tests.cpp
src/pad_input_tests.cpp
src/ps2_runtime_io_tests.cpp
src/ps2_runtime_kernel_tests.cpp
src/ps2_runtime_interrupt_tests.cpp
+2
View File
@@ -3,6 +3,7 @@
void register_code_generator_tests();
void register_r5900_decoder_tests();
void register_elf_analyzer_tests();
void register_pad_input_tests();
void register_ps2_runtime_io_tests();
void register_ps2_runtime_kernel_tests();
void register_ps2_runtime_interrupt_tests();
@@ -18,6 +19,7 @@ int main()
register_code_generator_tests();
register_r5900_decoder_tests();
register_elf_analyzer_tests();
register_pad_input_tests();
register_ps2_runtime_io_tests();
register_ps2_runtime_kernel_tests();
register_ps2_runtime_interrupt_tests();
+265
View File
@@ -0,0 +1,265 @@
#include "MiniTest.h"
#include "ps2_stubs.h"
#include <vector>
#include <cstdint>
#include <string>
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<int64_t>(value));
}
void runPadRead(R5900Context &ctx, std::vector<uint8_t> &rdram)
{
setRegU32(ctx, 6, kPadDataAddr); // a2
ps2_stubs::scePadRead(rdram.data(), &ctx, nullptr);
}
uint16_t readButtons(const std::vector<uint8_t> &rdram)
{
const uint8_t *data = rdram.data() + kPadDataAddr;
return static_cast<uint16_t>(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<uint8_t> rdram(PS2_RAM_SIZE, 0);
R5900Context ctx;
const uint16_t buttons = static_cast<uint16_t>(0xFFFFu & ~kPadBtnCross & ~kPadBtnStart);
ps2_stubs::setPadOverrideState(buttons, 0x00, 0xFF, 0x10, 0xEE);
runPadRead(ctx, rdram);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(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<uint8_t>(0x10), "rx should match override");
t.Equals(data[5], static_cast<uint8_t>(0xEE), "ry should match override");
t.Equals(data[6], static_cast<uint8_t>(0x00), "lx should match override");
t.Equals(data[7], static_cast<uint8_t>(0xFF), "ly should match override");
ps2_stubs::clearPadOverrideState();
});
tc.Run("scePadRead button bits are active-low", [](TestCase &t)
{
std::vector<uint8_t> 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<uint16_t>(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<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(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<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(1), "scePadInit should succeed");
ps2_stubs::scePadInit2(nullptr, &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(1), "scePadInit2 should succeed");
ps2_stubs::scePadPortOpen(nullptr, &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(1), "scePadPortOpen should succeed");
ps2_stubs::scePadPortClose(nullptr, &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(1), "scePadPortClose should succeed");
ps2_stubs::scePadGetState(nullptr, &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(6), "scePadGetState should return STABLE");
ps2_stubs::scePadGetReqState(nullptr, &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(0), "scePadGetReqState should return completed");
ps2_stubs::scePadGetPortMax(nullptr, &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(2), "scePadGetPortMax should be 2");
ps2_stubs::scePadGetSlotMax(nullptr, &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(1), "scePadGetSlotMax should be 1");
ps2_stubs::scePadGetModVersion(nullptr, &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(0x0200), "scePadGetModVersion should be 0x0200");
});
tc.Run("pad info and mode helpers return consistent values", [](TestCase &t)
{
std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0);
R5900Context ctx;
ps2_stubs::scePadInfoAct(rdram.data(), &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(0), "scePadInfoAct should return 0");
ps2_stubs::scePadInfoComb(rdram.data(), &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(0), "scePadInfoComb should return 0");
setRegU32(ctx, 6, 1);
setRegU32(ctx, 7, 0);
ps2_stubs::scePadInfoMode(rdram.data(), &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(7), "scePadInfoMode CURID should return DualShock");
setRegU32(ctx, 6, 4);
setRegU32(ctx, 7, static_cast<uint32_t>(-1));
ps2_stubs::scePadInfoMode(rdram.data(), &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(1), "scePadInfoMode table count should be 1");
ps2_stubs::scePadInfoPressMode(rdram.data(), &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(0), "scePadInfoPressMode should be 0");
});
tc.Run("pad setters return success", [](TestCase &t)
{
std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0);
R5900Context ctx;
ps2_stubs::scePadSetActAlign(rdram.data(), &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(1), "scePadSetActAlign should succeed");
ps2_stubs::scePadSetActDirect(rdram.data(), &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(1), "scePadSetActDirect should succeed");
ps2_stubs::scePadSetButtonInfo(rdram.data(), &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(1), "scePadSetButtonInfo should succeed");
ps2_stubs::scePadSetMainMode(rdram.data(), &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(1), "scePadSetMainMode should succeed");
ps2_stubs::scePadSetReqState(rdram.data(), &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(1), "scePadSetReqState should succeed");
ps2_stubs::scePadSetVrefParam(rdram.data(), &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(1), "scePadSetVrefParam should succeed");
ps2_stubs::scePadSetWarningLevel(rdram.data(), &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(0), "scePadSetWarningLevel should return 0");
ps2_stubs::scePadEnd(rdram.data(), &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(1), "scePadEnd should succeed");
ps2_stubs::scePadEnterPressMode(rdram.data(), &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(1), "scePadEnterPressMode should succeed");
ps2_stubs::scePadExitPressMode(rdram.data(), &ctx, nullptr);
t.Equals(static_cast<uint32_t>(getRegU32(&ctx, 2)), static_cast<uint32_t>(1), "scePadExitPressMode should succeed");
});
tc.Run("pad string helpers map state codes", [](TestCase &t)
{
std::vector<uint8_t> 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<const char *>(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<const char *>(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<const char *>(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<uint8_t> 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<const char *>(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<const char *>(rdram.data() + kPadDataAddr + 64);
t.IsTrue(std::string(reqStr).find("COMPLETE") != std::string::npos, "req string should include COMPLETE");
});
});
}