Skip to content
Open
6 changes: 4 additions & 2 deletions core/libs/prx/libScePad/Export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ int APS5_VABI scePadGetControllerInformation(int handle, PadControllerInformatio
info->stickInfo.deadZoneLeft = 13;
info->stickInfo.deadZoneRight = 13;
info->connectionType = PAD_CONNECTION_TYPE_LOCAL;
const bool connected = PortOf(handle) != 0 || Pad::IsConnected(0);
const bool connected = Pad::IsConnected(PortOf(handle));
info->connectedCount = connected ? 1 : 0;
info->connected = connected;
info->deviceClass = PAD_DEVICE_CLASS_STANDARD;
Expand Down Expand Up @@ -357,6 +357,7 @@ int APS5_VABI scePadSetFeatureReport(std::int32_t handle, std::uint8_t reportId,
int APS5_VABI scePadOutputReport(std::int32_t handle, std::uint8_t reportId, const void* data, std::uint64_t size) { NotImplemented_nid_no_patch(__func__); return 0; }

int APS5_VABI scePadRead_nid_postfix(int handle, PadData* data, int num) {
if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED;
if (!KnownHandle(handle) && !RemoteHandle(handle)) return PAD_ERROR_INVALID_HANDLE;
if (data == nullptr || num <= 0) return PAD_ERROR_INVALID_ARG;
if (RemoteHandle(handle)) {
Expand All @@ -371,8 +372,9 @@ int APS5_VABI scePadRead_nid_postfix(int handle, PadData* data, int num) {
}

int APS5_VABI scePadReadState(int handle, PadData* data) {
if (!KnownHandle(handle) && !RemoteHandle(handle)) return PAD_ERROR_INVALID_HANDLE;
if (data == nullptr) return PAD_ERROR_INVALID_ARG;
if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED;
if (!KnownHandle(handle) && !RemoteHandle(handle)) return PAD_ERROR_INVALID_HANDLE;

*data = RemoteHandle(handle) ? PadData{} : SpecialHandle(handle) ? SpecialData() : Pad::ReadState(PortOf(handle));

Expand Down
9 changes: 5 additions & 4 deletions core/libs/prx/libScePad/src/PadState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ bool Pad::IsInitialized() {
}

namespace {
PadData BuildData(PadSlot& slot, const PadInputState& state, std::uint64_t changeTime, std::uint64_t now, const Pad::InputCommandFile::Input& injected, int port) {
PadData BuildData(PadSlot& slot, const PadInputState& state, std::uint64_t changeTime, std::uint64_t fuseTime, const Pad::InputCommandFile::Input& injected, int port) {
PadData data{};
const bool intercepted = state.intercepted;
data.buttons = intercepted ? kInterceptedButton : state.buttons | injected.buttons;
Expand All @@ -213,14 +213,14 @@ PadData BuildData(PadSlot& slot, const PadInputState& state, std::uint64_t chang
}
}
if (live) {
float dt = slot.lastFuseTime != 0 && now > slot.lastFuseTime ? static_cast<float>(now - slot.lastFuseTime) * 1e-6f : 0.0f;
float dt = slot.lastFuseTime != 0 && fuseTime > slot.lastFuseTime ? static_cast<float>(fuseTime - slot.lastFuseTime) * 1e-6f : 0.0f;
dt = std::min(dt, 0.1f);
fuse(slot, dt, accel, gyro);
} else {
slot.orientation = Quat{};
slot.biasIntegral[0] = slot.biasIntegral[1] = slot.biasIntegral[2] = 0.0f;
}
slot.lastFuseTime = now;
slot.lastFuseTime = std::max(slot.lastFuseTime, fuseTime);
data.orientation_x = slot.orientation.x;
data.orientation_y = slot.orientation.y;
data.orientation_z = slot.orientation.z;
Expand Down Expand Up @@ -370,7 +370,8 @@ int Pad::Read(PadData* data, int num, int port) {
int count = 0;
while (count < num && !slot.queue.empty()) {
const QueuedInput& entry = slot.queue.front();
data[count++] = BuildData(slot, entry.input, port == 0 ? std::max(entry.timestamp, injectedTimestamp) : entry.timestamp, now, injected, port);
const std::uint64_t sample = port == 0 ? std::max(entry.timestamp, injectedTimestamp) : entry.timestamp;
data[count++] = BuildData(slot, entry.input, sample, sample, injected, port);
slot.queue.pop_front();
}
return count;
Expand Down
45 changes: 44 additions & 1 deletion core/libs/tests/GuestPad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <atomic>
#include <array>
#include <chrono>
#include <cmath>
#include <cstdlib>
#include <cstdio>
#include <cstring>
Expand Down Expand Up @@ -161,7 +162,7 @@ static void CheckTouchContact(int handle) {
Require(scePadReadState(handle, nullptr) == PAD_ERROR_INVALID_ARG);
Require(scePadRead_nid_postfix(handle, nullptr, 1) == PAD_ERROR_INVALID_ARG);
for (const int invalid : {-1, 0, handle + 1, 9999}) {
Require(scePadReadState(invalid, nullptr) == PAD_ERROR_INVALID_HANDLE);
Require(scePadReadState(invalid, nullptr) == PAD_ERROR_INVALID_ARG);
Require(scePadRead_nid_postfix(invalid, nullptr, 1) == PAD_ERROR_INVALID_HANDLE);
}
PadInputState touch;
Expand Down Expand Up @@ -980,6 +981,36 @@ static void CheckPortZeroWithdrawn() {
if (!wasOpen) Require(scePadClose_nid_postfix(handle) == PAD_OK);
}

static void CheckQueuedMotionSampleTimes(int handle) {
PadData drained[64]{};
PadData queued[4]{};
Require(scePadSetMotionSensorState(handle, true) == PAD_OK);
Require(scePadResetOrientation(handle) == PAD_OK);
scePadRead_nid_postfix(handle, drained, 64);
for (int i = 0; i < 4; ++i) {
PadInputState moving;
moving.hasMotion = true;
moving.gyro = {0.0f, 0.0f, 1.0f + 0.01f * static_cast<float>(i)};
PadPublishInput_nid_postfix(moving);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
Require(scePadRead_nid_postfix(handle, queued, 4) == 4);
Require(std::fabs(queued[3].orientation_z - queued[1].orientation_z) > 1e-4f);
Require(scePadSetMotionSensorState(handle, false) == PAD_OK);
}

static void CheckSecondPortConnection(int user) {
PadControllerInformation info{};
const int second = scePadOpen_nid_postfix(user + 1, PAD_PORT_TYPE_STANDARD, 0, nullptr);
Require(second > 0);
PadSetPortConnected_nid_postfix(1, false);
Require(scePadGetControllerInformation(second, &info) == PAD_OK && !info.connected && info.connectedCount == 0);
PadSetPortConnected_nid_postfix(1, true);
Require(scePadGetControllerInformation(second, &info) == PAD_OK && info.connected && info.connectedCount == 1);
PadSetPortConnected_nid_postfix(1, false);
Require(scePadClose_nid_postfix(second) == PAD_OK);
}

int main() {
CheckConcurrentSpecialOpens();
CheckInputCommands();
Expand Down Expand Up @@ -1073,6 +1104,16 @@ int main() {
Require(scePadGetHandle(user, PAD_PORT_TYPE_SPECIAL, 0) == earlySpecial);
Require(scePadClose_nid_postfix(earlySpecial) == PAD_OK);
Require(scePadGetHandle(user, PAD_PORT_TYPE_SPECIAL, 0) == noHandle);
const int earlyPad = scePadOpen_nid_postfix(user, PAD_PORT_TYPE_STANDARD, 0, nullptr);
Require(earlyPad > 0);
PadData earlyPadData{};
Require(scePadRead_nid_postfix(0x999, &earlyPadData, 1) == PAD_ERROR_NOT_INITIALIZED);
Require(scePadRead_nid_postfix(earlyPad, &earlyPadData, 1) == PAD_ERROR_NOT_INITIALIZED);
Require(scePadReadState(earlyPad, &earlyPadData) == PAD_ERROR_NOT_INITIALIZED);
Require(scePadReadState(0x999, &earlyPadData) == PAD_ERROR_NOT_INITIALIZED);
Require(scePadReadState(0x999, nullptr) == PAD_ERROR_INVALID_ARG);
Require(scePadReadState(earlyPad, nullptr) == PAD_ERROR_INVALID_ARG);
Require(scePadClose_nid_postfix(earlyPad) == PAD_OK);
PadSetPortConnected_nid_postfix(1, true);
Require(scePadInit_nid_postfix() == 0);
PadInfo initInfo{};
Expand All @@ -1098,6 +1139,7 @@ int main() {
CheckExtControllerInformation(handle);
CheckPartialTrigger();
CheckReadQueue(handle);
CheckQueuedMotionSampleTimes(handle);
CheckTapBetweenReads();
CheckCaptureHidesEarlierInput();
CheckLatchedPressLifetime(handle);
Expand Down Expand Up @@ -1296,4 +1338,5 @@ int main() {
Require(scePadSetAngularVelocityDeadbandState(handle2, false) == PAD_OK);
Require(scePadSetAngularVelocityDeadbandState(handle2 + 1, false) == PAD_ERROR_INVALID_HANDLE);
CheckPortZeroWithdrawn();
CheckSecondPortConnection(user);
}
2 changes: 1 addition & 1 deletion docs/dev/debt/libScePad.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
- [scePadDisableVibration, scePadIsLightBarBaseBrightnessControllable](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - `scePadDisableVibration(true)` stops active running motors on the port without disabling vibration for subsequent `scePadSetVibration` calls; `scePadIsLightBarBaseBrightnessControllable` reports 1 for standard DualSense handles and 0 for special handles while the modifier is a no-op.
- [scePadGetDeviceInfo, GetCapability, GetVersionInfo, GetIdleCount, GetControllerIdleCount, GetActiveTime, SetPS4BcVibrationMode, GetInfoByPortType, GetExtensionUnitInfo, ReadHistory and OpenExt2](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - checks and their order follow @BunnyFl's [13.42 disassembly](https://github.com/boykopovar/AnyPS5/issues/15087#issuecomment-6580925450) of `libScePad.sprx` (SHA-256 `57eab7054e10d95ba0028dbd4808f94eb34c7663333a2b7ca9f68dc90cf62c60`), not a console. Past them each asks the driver, so it throws; a closed device fd counts as uninitialised. `GetInfoByPortType` checks a null output, then initialisation, then the type (0 or 2). `GetIdleCount` returns 0 before `scePadInit`, and `GetActiveTime` clears its output first. `ReadHistory`'s third argument is a byte size (0x960), and `OpenExt2(int32 userId, int32, const void*)` requires 0x10 in its unexplained middle argument. `GetExtensionUnitInfo`'s second-flag check (0x80920101) isn't modelled.

- [scePadReadStateExt and scePadGetDeviceId](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - per the same [reading](https://github.com/boykopovar/AnyPS5/issues/15087#issuecomment-6580925450), `ReadStateExt` is `scePadReadState` with an extension flag: it runs this library's `ReadState` checks (handle, then output), then throws, since what the flag changes isn't known. The module checks a null output first, zero-fills the record, then checks initialisation before the handle; neither `ReadState` follows that order yet. `GetDeviceId(handle, int32*)` writes -1, then refuses an unknown handle with 0x80920003; the console then copies an id from the handle's slot (+0x18) whose source isn't known, so a known handle throws.
- [scePadReadStateExt and scePadGetDeviceId](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - per the same [reading](https://github.com/boykopovar/AnyPS5/issues/15087#issuecomment-6580925450), `ReadStateExt` is `scePadReadState` with an extension flag: it runs this library's `ReadState` checks (handle, then output), then throws, since what the flag changes isn't known. The module checks a null output first, zero-fills the record, then checks initialisation before the handle; `scePadReadState` follows the null output, initialisation, handle order but does not write the module's defaults into the output before returning an error. `GetDeviceId(handle, int32*)` writes -1, then refuses an unknown handle with 0x80920003; the console then copies an id from the handle's slot (+0x18) whose source isn't known, so a known handle throws.

- [libScePad internal data and device control](../../../core/libs/prx/libScePad/Export.cpp) - kernel-owned version, telemetry, Lotus-button and update payload fields remain unresolved. Lotus-button parameter units and the three blinking byte meanings are unknown. Port information occupies 152 bytes; history records occupy 72 bytes; Leonardo unique data occupies 24 bytes. Their complete field semantics remain unresolved.
- [libScePad profile writes and virtual devices](../../../core/libs/prx/libScePad/Export.cpp) - profile writes take a handle, profile ID, profile pointer and a 32-bit result pointer; the profile payload direction and fields remain unresolved. Virtual-device creation takes an input record and an output ID pointer, whose width is not exposed by the wrapper. Component information copies 16 bytes; field meanings remain unknown. Virtual trigger state contains two 32-bit states and three control bytes, but the control semantics remain incomplete.
Expand Down
Loading