Skip to content

fix(libScePad): return measured status for unknown Pad NID - #5676

Merged
oneandonlydean merged 4 commits into
boykopovar:mainfrom
MarcZX10:codex/pad-n3ksx62fgno-status
Oct 17, 2026
Merged

oneandonlydean merged 4 commits into
boykopovar:mainfrom
MarcZX10:codex/pad-n3ksx62fgno-status

Conversation

@MarcZX10

@MarcZX10 MarcZX10 commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

What

Implements the measured behavior for libScePad NID n3kSX62fgNo: it returns 0x80920005 before scePadInit and 0x809200ff after initialization while no controller is open. A PS5 probe found no memory access through the tested argument values. The NID's name and signature remain unknown. Calls with an open controller remain unmeasured and throw rather than assuming the post-init status applies. This removes the observed startup abort without pretending the underlying API is understood. Resolves the measured portion of #3177.

Tested

  • Rebased onto current main 2fab88d10. python3 tools/check_conventions.py --base 2fab88d10 and git diff --check pass.
  • The old guest_ppsa01530_exports test only asserted that this export threw as an unimplemented stub. It is removed because this PR now implements the two measured return states; guest_pad covers the before-init, after-init/no-handle, and open-controller states.
  • Before the rebase, Linux/GCC 12.2 Release built guest_pad_tests and the then-existing guest_ppsa01530_exports_tests. CTest stopped before assertions because this host's Xeon E5-1620 v2 lacks LZCNT, BMI1/2, MOVBE, AVX2 and FMA. I have not rerun the emulator tests on a compatible host after rebasing.
  • The firmware return values and no-memory-access observations come from the probe in Exports for PPSA01530, PPSA23827 and PPSA13356 imports #3177. A collaborator's WSL run validated the measured-status behavior on an earlier head; the open-controller guard has not had a runtime test on a compatible host. I did not run the emulator change on a PS5.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt; documented in docs/dev/debt/libScePad.md
  • Unimplemented paths throw; this export returns only measured statuses, with limitations recorded in technical debt
  • No notes, investigation .md files or images added to the repository
  • New third-party code is a submodule built from source (not applicable)
  • Generic behavior, not specific to one title
  • Depends on: none
  • Unblocks: PPSA13356, Exports for PPSA01530, PPSA23827 and PPSA13356 imports #3177
  • GPUs tested: n/a
  • AI-assisted: yes

@AlexOfTheForest

Copy link
Copy Markdown
Contributor

CONTRIBUTING allows code comments only for technical debt, so the three comment lines in scePadUnknown_n3kSX62fgNo should move to the debt entry. That entry is the right place to record "firmware 13.60, before/after scePadInit, no argument pointers read or written", which is currently in the code.

0x80920005 and 0x809200ff look like the known pad error codes NOT_INITIALIZED and FATAL (the same values other PS4/PS5 projects use). Naming the constants PAD_ERROR_NOT_INITIALIZED / PAD_ERROR_FATAL instead of PAD_UNKNOWN_N3KSX62FGNO_BEFORE/AFTER_INIT would say what they mean, and the before-init value could be reused by other exports that check initialization.

@eliasbruno124-dev

eliasbruno124-dev commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Additional title evidence for the controller-open limitation: Castlevania Dominus Collection (PPSA15645, 01.003.000) calls this import after opening a pad.

On Windows with GCC 15.2, I built and tested the exact Pad change from 9e6dba8 on the frozen 0aa4cb1 base. Both guest_pad and guest_ppsa01530_exports passed; the new Pad test fails at the original unimplemented export when using the old module.

Separately, read-only debugger observation of the original title using the baseline Pad runtime established this order:

  1. scePadInit once.
  2. scePadOpen(user=0x10000000, type=0, index=0, param=NULL) once.
  3. No scePadClose before the unknown call at original executable VA 0xff.
  4. The module's g_opened byte is 1 at that call. The caller forwards its existing argument registers and does not inspect the return value afterwards.

I stopped before the unknown call rather than treating this as covered by the #3177 probe, where PadOpen failed. No return was substituted. This does not demonstrate title progress or correct input behavior, and it is not a current-main gameplay test.

Correction to my initial wording: the behavioral tests used the new unpatched module, but the title observation loaded the old packaged module. The packaging check caught this; the observation therefore establishes the baseline open-controller state, not execution of this PR.

A hardware measurement with a successfully opened controller would cover a state this title actually uses. The current measured scope is useful, but cannot yet establish the result or side effects for that state.

Prepared with AI assistance.

@MarcZX10

Copy link
Copy Markdown
Contributor Author

Moved the probe details from the code comments into docs/dev/debt/libScePad.md and recorded PPSA15645’s opened-controller call site as an unmeasured state. I kept the constants named by the observed NID and initialization state: the supplied evidence establishes the numeric returns, but does not independently confirm their semantic labels, and the title observation stopped before the call. Targeted Linux CTest (guest_pad, guest_ppsa01530_exports) passes 2/2; the hosted conventions check also passes on this revision.

@xXPBSXx

xXPBSXx commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Title evidence for this pull request: Raiden III x MIKADO MANIAX (PPSA13356), Windows 10 (19045), RTX 4080 SUPER, one 90 s run.

Without a change to scePadUnknown_n3kSX62fgNo the title stops at it: PPSA13356_rH/run_085314.err line 4 what(): scePadUnknown_n3kSX62fgNo not implemented (main e46c69505).

With this pull request (head 5dd44a89c) in a local merge tree ab5a5501c = current main (checked out 11:49:03) + this head + a local change that makes sceVideoOutSetFlipRate return its error code instead of throwing (that is the change of #5691, the title calls sceVideoOutSetFlipRate(handle, 60)): 0 of 353 lines in PPSA13356_rY1/run_115406.err contain scePadUnknown, and the title gets past it. It then stops at line 29, what(): NGS2: invalid voice handle (FATAL in line 30), which is not related to this pull request.

With #5038 (return 0 for the same function) the same title stops at the same place, so for this title both pull requests pass the pad call equally; this one has the console measurement as its source. #5038 and this pull request conflict in libScePad/Export.cpp.

Test conditions: ninja -j 4 relinker plus all 126 core/libs/libs/*.prx of the merged tree, finished 11:54:01 local time (libSceAgcDriver.prx 11:53:53, libkernel.prx 11:54:00), relinker.exe 11:53:53; relink relink_title_trace.ps1 -Repo K:\AnyPS5\wt-sockets (relinker.exe --windows --registry --trace-imports), run through run_title.ps1, 90 s, one game at a time.
Not tested: Linux, other GPUs, a second run, the title without the local flip-rate change.

AI-assisted (Claude Code).

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
@MarcZX10
MarcZX10 force-pushed the codex/pad-n3ksx62fgno-status branch from 5dd44a8 to 1c7a09d Compare October 14, 2026 14:28
@MarcZX10

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (066b58fa1) and refreshed the integration with the current not-implemented export audit.

Validation on Linux:

  • guest_pad_tests and guest_ppsa01530_exports_tests build successfully.
  • CTest could not reach the assertions on this host: both test processes abort at startup because the CPU lacks LZCNT, BMI1/2, AVX2 and FMA, which this emulator requires.
  • Hosted conventions, eligibility and report checks pass. The Linux/Windows full builds and the platform-specific suites were skipped by path filtering.

The earlier focused CTest pass was on the previous revision; this rebase's runtime result is limited by the host CPU.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 5021 core/libs/CMakeLists.txt
PR 5038 core/libs/prx/libScePad/Export.cpp
docs/dev/debt/libScePad.md
PR 6044 core/libs/CMakeLists.txt
core/libs/tests/GuestPPSA01530Exports.cpp
core/libs/tests/GuestPad.cpp
PR 6222 core/libs/CMakeLists.txt
PR 6281 core/libs/prx/libScePad/Export.cpp
docs/dev/debt/libScePad.md
PR 6447 docs/dev/debt/libScePad.md

@nikowolf123

Copy link
Copy Markdown
Contributor

The rebase's CTest run could not reach the assertions, so here is a runtime result for the current head (1c7a09d1f merged onto main e34c47921, WSL Ubuntu, GCC 15.2.0, Release). The new assertions fail before the change and pass after it:

Tree guest_pad guest_ppsa01530_exports
main passes passes
main library with this PR's two test files aborts: what(): scePadUnknown_n3kSX62fgNo not implemented passes
main + this PR passes (10 of 10 repeats) passes

One gap: the after-init assertion (GuestPad.cpp#L102-L104) runs inside CheckTiltCorrection, which main() calls after scePadOpen has returned a handle (L285, L291). So the test pins 0x809200ff with a handle open, the state @hugodearaujo's probe could not reach because scePadOpen failed in the payload with 0x809b0081 (#3177 comment). The state the probe did measure, initialized with no handle, is not asserted. The export ignores g_opened (Export.cpp#L317-L319), so in the open-handle state, where @eliasbruno124-dev traced PPSA15645's call (g_opened = 1, comment), it returns 0x809200ff instead of throwing, although the debt entry calls that state unknown. A small probe that calls the export in each state:

State main this PR
before scePadInit throws 0x80920005
after scePadInit, no handle throws 0x809200ff
after scePadOpen(0x10000000, 0, 0, NULL) returned 1 throws 0x809200ff
after scePadClose throws 0x809200ff

The change below returns the measured statuses while no handle is open and keeps throwing while one is. With it on main, guest_pad (10 of 10 repeats) and guest_ppsa01530_exports pass, its guest_pad aborts when built against this head's Export.cpp, and the conventions check passes.

diff against 1c7a09d
diff --git a/core/libs/prx/libScePad/Export.cpp b/core/libs/prx/libScePad/Export.cpp
index 802a25dc1..b2a61b864 100644
--- a/core/libs/prx/libScePad/Export.cpp
+++ b/core/libs/prx/libScePad/Export.cpp
@@ -315,6 +315,7 @@ int APS5_VABI scePadSetAngularVelocityBiasCorrectionState(int handle, bool enabl
 
 APS5_EXPORT("n3kSX62fgNo", scePadUnknown_n3kSX62fgNo);
 int APS5_VABI scePadUnknown_n3kSX62fgNo() {
+    if (g_opened) NotImplemented_nid_no_patch(__func__);
     return g_initialized.load(std::memory_order_acquire) ? PAD_UNKNOWN_N3KSX62FGNO_AFTER_INIT : PAD_UNKNOWN_N3KSX62FGNO_BEFORE_INIT;
 }
 
diff --git a/core/libs/tests/GuestPad.cpp b/core/libs/tests/GuestPad.cpp
index 37cd4f0ec..c7cdb0d1a 100644
--- a/core/libs/tests/GuestPad.cpp
+++ b/core/libs/tests/GuestPad.cpp
@@ -7,6 +7,8 @@
 #include <cstdlib>
 #include <filesystem>
 #include <fstream>
+#include <stdexcept>
+#include <string>
 #include <thread>
 
 extern "C" {
@@ -29,6 +31,15 @@ int APS5_VABI scePadIsRemoteController(int, bool*);
 
 static void Require(bool value) { if (!value) std::abort(); }
 
+static bool UnknownN3kNotImplemented() {
+    try {
+        scePadUnknown_n3kSX62fgNo();
+    } catch (const std::runtime_error& error) {
+        return std::string(error.what()) == "scePadUnknown_n3kSX62fgNo not implemented";
+    }
+    return false;
+}
+
 static void CheckInputCommands() {
     const auto path = std::filesystem::temp_directory_path() / ("anyps5-pad-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
     struct Cleanup {
@@ -101,7 +112,7 @@ static float SettleOrientationW() {
 
 static void CheckTiltCorrection(int handle) {
     Require(scePadInit_nid_postfix() == 0);
-    Require(scePadUnknown_n3kSX62fgNo() == static_cast<int>(0x809200ff));
+    Require(UnknownN3kNotImplemented());
     PadInputState tilted;
     tilted.hasMotion = true;
     tilted.accel = {9.80665f, 0.0f, 0.0f};
@@ -243,6 +254,8 @@ static void CheckInjectedState(const std::filesystem::path& path) {
 int main() {
     CheckInputCommands();
     Require(scePadUnknown_n3kSX62fgNo() == static_cast<int>(0x80920005));
+    Require(scePadInit_nid_postfix() == 0);
+    Require(scePadUnknown_n3kSX62fgNo() == static_cast<int>(0x809200ff));
     const auto path = std::filesystem::temp_directory_path() / ("anyps5-pad-state-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
     struct Cleanup {
         std::filesystem::path path;
diff --git a/docs/dev/debt/libScePad.md b/docs/dev/debt/libScePad.md
index bec36f662..a851c5301 100644
--- a/docs/dev/debt/libScePad.md
+++ b/docs/dev/debt/libScePad.md
@@ -2,7 +2,7 @@
 
 ### Unknown function info
 
-- [scePadUnknown_n3kSX62fgNo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad, NID `n3kSX62fgNo`) - firmware 13.60 returned `0x80920005` before `scePadInit` and `0x809200ff` after it in the #3177 probe; the tested call read or wrote no argument pointers. PPSA15645 calls the import after `scePadOpen`, but its return and side effects in that state are unknown. The NID name and signature are unknown, so the statuses are not named by presumed error meanings
+- [scePadUnknown_n3kSX62fgNo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad, NID `n3kSX62fgNo`) - firmware 13.60 returned `0x80920005` before `scePadInit` and `0x809200ff` after it in the #3177 probe; the tested call read or wrote no argument pointers. PPSA15645 calls the import after `scePadOpen`; the probe's `scePadOpen` failed, so the return and side effects with a handle open are unknown and that state throws. The NID name and signature are unknown, so the statuses are not named by presumed error meanings
 - [scePadSetAngularVelocityDeadbandState](../../../core/libs/prx/libScePad/src/PadState.cpp) (libScePad) - with the deadband on, each angular velocity axis whose magnitude is below 0.017453292 rad/s (1 degree per second) reads as 0, before the orientation is integrated; it starts off. Threshold, per-axis test and default follow [duaLib](https://github.com/WujekFoliarz/duaLib/blob/03bad1bea2a36561b520846776f2a07ced6773e0/src/source/duaLib.cpp#L44), which reimplements the PC `libScePad.dll`; the console's threshold is not measured, and the deadband is kept per library rather than per handle since one pad handle exists
 - [scePadOpen and scePadOpenExt](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - special ports (type 2) return `DEVICE_NOT_CONNECTED` (0x80920007) because the host has no special controller, and `scePadGetHandle` has no handle for them; whether the console opens a handle and reports the device as disconnected later is unknown. scePadOpenExt handles only special ports. PPSA01317 passes a 16-byte param that names the device by vendor and product id (`b7 0e 04 0e ...` for 0x0eb7:0x0e04) and skips the user on any negative result. Other port types throw
 - [scePadVrControllerReadState](../../../core/libs/prx/libScePad/Export.cpp) (libScePad, NID `iA-DdobUen8`) - the name hashes to the imported NID; the `(int handle, void* state)` signature follows [PS5VR](https://github.com/theghostonline/PS5VR/blob/0e408909ce7a39891a2e06eee3438d07a09e11e3/app/src/app_vr.c#L326-L369) (PPSA99177), which passes a PS VR2 Sense controller handle from `scePadOpen` type 3 or 4 and a 0x68-byte state it reads as buttons at 0, stick bytes at 4 and a connected byte at 0x40. The title calls it only after `sceHmd2Initialize` succeeds, which it does not here, so every call throws; the state layout is otherwise unknown

The trade-off: PPSA15645 keeps stopping at this call, and PPSA13356, which gets past it with this PR in @xXPBSXx's run (comment), would stop here again if it also calls it with a handle open. @xXPBSXx, would a run on main relinked with --trace-imports and APS5_TRACE_CALLS=libScePad show the order? The abort report lists the last traced calls (USAGE.md). If xk0AcarP3V4 (scePadOpen) comes before n3kSX62fgNo, the maintainers have to choose between this PR's value for that state and a probe from a game process that can open a pad.

AI-assisted: written with Claude Code; I checked the head's export and test against the #3177 probe and the PPSA15645 trace, and ran guest_pad and guest_ppsa01530_exports on main, on main with this PR's tests, on main + this PR and on main + the change above (WSL Ubuntu, GCC 15.2.0, Release), plus the state probe on each tree and tools/check_conventions.py on the change.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Please take @nikowolf123's change above: keep the measured statuses while no handle is open, and throw while one is. The #3177 probe never had an open handle, so 0x809200ff in that state is a guess. PPSA10594 calls this after scePadInit and before scePadOpen, so it still gets past the stop with that change.

@MarcZX10

Copy link
Copy Markdown
Contributor Author

I updated #5676 on current main in commits c268ba50e, e5fe01dcc and 46c0de1b7. The export keeps the firmware-measured statuses before init and after init with no open controller, and throws while a controller handle is open because that state remains unmeasured. The focused test now covers all three states, and the generic “not implemented” export test no longer treats this measured export as a stub. Both targets build on Linux; conventions and git diff --check pass. CTest could not reach the assertions on this Xeon E5-1620 v2 because it lacks LZCNT, BMI1/2, MOVBE, AVX2 and FMA. The PR description records this limitation.

@MarcZX10

Copy link
Copy Markdown
Contributor Author

The latest head (46c0de1b) includes the requested state split: it returns the firmware-measured values before init and after init with no open handle, and throws while a controller handle is open. GuestPad.cpp now asserts all three states, including the measured post-init/no-handle state. The current hosted eligibility, conventions, and report checks pass. I rebuilt both focused targets locally, but CTest still aborts before reaching assertions on this Xeon E5-1620 v2 because the host lacks the required LZCNT, BMI1/2, MOVBE, AVX2, and FMA features. @nikowolf123 provided a 10/10 Linux runtime run of the same behavioral split on the preceding head; I have not rerun CTest on this final head locally.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 16, 2026
@MarcZX10
MarcZX10 force-pushed the codex/pad-n3ksx62fgno-status branch from 46c0de1 to e35c62c Compare October 16, 2026 20:41
@MarcZX10

Copy link
Copy Markdown
Contributor Author

Rebased onto current main 2fab88d10 as head e35c62c84. I preserved main’s libSceNet debt updates, kept the Pad-specific evidence in libScePad.md, and removed the old guest_ppsa01530_exports check because it treated this now-measured export as an unimplemented stub. The existing guest_pad regression now owns the before-init, after-init/no-handle, and open-controller assertions. Conventions and git diff --check pass. The local test run is still limited by this host’s CPU-feature guard; the body now distinguishes the earlier local build/runtime evidence from this rebased head.

@github-actions github-actions Bot added build and removed needs rebase Conflicts with main, rebase needed labels Oct 16, 2026
@oneandonlydean
oneandonlydean merged commit 9070237 into boykopovar:main Oct 17, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants