diff --git a/core/libs/prx/libScePad/Export.cpp b/core/libs/prx/libScePad/Export.cpp index fd760958da26..59d0fad26760 100644 --- a/core/libs/prx/libScePad/Export.cpp +++ b/core/libs/prx/libScePad/Export.cpp @@ -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; @@ -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)) { @@ -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)); diff --git a/core/libs/prx/libScePad/src/PadState.cpp b/core/libs/prx/libScePad/src/PadState.cpp index 7c168b377299..767e363bdc38 100644 --- a/core/libs/prx/libScePad/src/PadState.cpp +++ b/core/libs/prx/libScePad/src/PadState.cpp @@ -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; @@ -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(now - slot.lastFuseTime) * 1e-6f : 0.0f; + float dt = slot.lastFuseTime != 0 && fuseTime > slot.lastFuseTime ? static_cast(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; @@ -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; diff --git a/core/libs/tests/GuestPad.cpp b/core/libs/tests/GuestPad.cpp index e2774adef350..ee17576ae1d6 100644 --- a/core/libs/tests/GuestPad.cpp +++ b/core/libs/tests/GuestPad.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -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; @@ -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(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(); @@ -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{}; @@ -1098,6 +1139,7 @@ int main() { CheckExtControllerInformation(handle); CheckPartialTrigger(); CheckReadQueue(handle); + CheckQueuedMotionSampleTimes(handle); CheckTapBetweenReads(); CheckCaptureHidesEarlierInput(); CheckLatchedPressLifetime(handle); @@ -1296,4 +1338,5 @@ int main() { Require(scePadSetAngularVelocityDeadbandState(handle2, false) == PAD_OK); Require(scePadSetAngularVelocityDeadbandState(handle2 + 1, false) == PAD_ERROR_INVALID_HANDLE); CheckPortZeroWithdrawn(); + CheckSecondPortConnection(user); } diff --git a/docs/dev/debt/libScePad.md b/docs/dev/debt/libScePad.md index af2070478839..d2e9bd040fae 100644 --- a/docs/dev/debt/libScePad.md +++ b/docs/dev/debt/libScePad.md @@ -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.