mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-28 17:39:16 -04:00
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:
committed by
GitHub
parent
765cbcca0f
commit
ccd17e5244
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user