Skip to content

feat(libScePad): check the arguments of thirteen exports as firmware 13.42 does - #15105

Merged
oneandonlydean merged 4 commits into
boykopovar:mainfrom
zaryabzip:feat/pad-firmware-checks
Nov 3, 2026
Merged

oneandonlydean merged 4 commits into
boykopovar:mainfrom
zaryabzip:feat/pad-firmware-checks

Conversation

@zaryabzip

@zaryabzip zaryabzip commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

What

Thirteen libScePad exports get the argument checks and order of firmware 13.42's libScePad.sprx, from @BunnyFl's disassembly (module SHA-256 57eab7054e10d95ba0028dbd4808f94eb34c7663333a2b7ca9f68dc90cf62c60), not a console run. Past their checks they still throw: the console asks the pad driver there, or reads state that isn't modelled. Before this, every call threw at once.

Export Checks, in order Then
GetDeviceInfo(int32, info*), GetCapability(int32, uint32*), GetVersionInfo(int32, info*) not initialised 0x80920005; null output 0x80920001 driver ioctl
GetIdleCount(userId) before scePadInit it returns 0 with no error driver ioctl
GetControllerIdleCount(handle, uint64*) not initialised; handle 0 or null output 0x80920001 driver ioctl
GetActiveTime(int32, int32, uint64*) not initialised; null output; then it writes 0 to the output driver ioctl
SetPS4BcVibrationMode(uint8) not initialised driver ioctl
GetInfoByPortType(type, info*) null output 0x80920001; not initialised; a type other than 0 or 2 0x80920001 (BunnyFl's corrected order in review) driver ioctl
GetExtensionUnitInfo(handle, info*) not initialised; null output a second library flag (0x80920101), then a driver ioctl; not modelled
ReadHistory(handle, data*, uint32 size) not initialised; size other than 0x960 0x80920001 driver ioctl
OpenExt2(int32 userId, int32, param*) not initialised; middle argument other than 0x10, or null param, 0x80920001 the open path
ReadStateExt(handle, data*) this library's scePadReadState checks (handle, then output); the module checks the output first and initialisation before the handle, recorded as debt the extension flag's effect isn't known
GetDeviceId(handle, int32*) not initialised; null output; writes -1; unknown handle 0x80920003 the id copied from the handle's slot isn't known

A closed device fd counts as not initialised, since AnyPS5 has no pad device. Two signatures change to match the reading. ReadHistory's third argument is a byte size, not a count; 0x960 is 30 of the 72-byte history records the debt file already lists. OpenExt2 takes (int32 userId, int32, const void*), not (uint64, int32, const void*). No other code calls either.

The debt file gets two entries for these, with the module hash. No function count changes: every call still ends in NotImplemented_nid_no_patch past its checks.

GuestPadFirmwareChecks.cpp checks each rejection before and after scePadInit, and that nothing is written on the refused paths, except GetDeviceId's -1 for an unknown handle. It stops at the last check, so it doesn't require the driver paths to throw. It calls ReadHistory and OpenExt2 through casts, so it also builds on main.

Overlap: git merge-tree merges this cleanly with main and every open pull request. The new constants sit after PAD_LEONARDO_UNIQUE_DATA_SIZE, away from the lines #14124 changes.

Tested

Windows 11 (WinLibs GCC 15.2.0 r7) and Ubuntu 24.04 on WSL2 (GCC 13.3.0), at d094933 merged into main 1d91f29. That commit has the same code as the head, with the two constants defined a few lines earlier.

  • No warnings in the changed files. The 9 files that include Pad.hpp rebuild with none, on both hosts.
  • guest_pad, guest_pad_vibration_mode, guest_pad_controller_queries, guest_pad_firmware_checks, guest_pad_audio_route, pad_rumble_intensity and guest_offline_imports: 7 of 7 on both hosts. guest_pad_firmware_checks passes 3 of 3.
  • Baseline: on that main with only the new test file and its CMake block, it builds and aborts at its first call with scePadGetDeviceInfo not implemented (Windows exit 0xc0000409, Linux SIGABRT).
  • tools/check_conventions.py passes.

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
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: Fixes libScePad: thirteen exports need their argument lists and checks from the firmware module #15087
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes (Claude Code)

…13.42 does

From @BunnyFl's disassembly of the 13.42 libScePad.sprx (boykopovar#15087):
GetDeviceInfo, GetCapability, GetVersionInfo, GetIdleCount,
GetControllerIdleCount, GetActiveTime, SetPS4BcVibrationMode,
GetInfoByPortType, GetExtensionUnitInfo, ReadHistory, OpenExt2,
ReadStateExt and GetDeviceId get their checks in order, and keep
throwing where the console asks the pad driver or reads state that isn't
modelled. ReadHistory's third argument is a byte size of 0x960, and
OpenExt2 takes (userId, 0x10, param).

@BunnyFl BunnyFl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for building on the reading. One order is wrong, and that's my fault: my comment on #15087 gave GetInfoByPortType's checks without their order and missed a null check. I've corrected that row.

Firmware 13.42's libScePad.sprx (sha256 57eab7054e10d95ba0028dbd4808f94eb34c7663333a2b7ca9f68dc90cf62c60), read from disassembly only, not measured on a console:

0xeac0 scePadGetInfoByPortType: jmp 0xe8a0(info, portType)    ; arguments swapped
  e8bb: ebx = 0x80920001
  e8c7: test rdi, rdi ; je ea98         ; info == NULL -> 0x80920001 (first)
  e8d0: cmpl [0x2401c], 0 ; js ea8f     ; fd < 0     -> 0x80920005
  e8dd: test esi, 0xfffffffd ; jne ea98 ; type not 0 or 2 -> 0x80920001
  1. scePadGetInfoByPortType must check info == nullptr first, then initialisation, then the type. The PR checks the type first, so two test lines pin answers the console doesn't give:
    • GetInfoByPortType(1, data) before init returns 0x80920005 on the console, not INVALID_ARG.
    • GetInfoByPortType(2, nullptr) before init returns 0x80920001, not NOT_INITIALIZED.
  2. A note on scePadReadStateExt, not blocking, since it follows main's scePadReadState. The shared routine 0x1a70(handle, data, 1) tests data == NULL (0x80920001) before anything else, then zero-fills the record and calls 0x1e50. That one checks initialisation (0x80920005) before the handle, as I noted on #15047. So the console answers (12345, data) before init with 0x80920005, and (12345, nullptr) with 0x80920001. main's scePadReadState has the same handle-first order. If you'd rather not change both here, a debt line saying the order follows main's ReadState, not the module, would cover it.
  3. Optional: scePadGetExtensionUnitInfo also returns 0x80920101 when the flag at 0x24020 isn't 1, after the null check and before the handle lookup. Since the PR throws past its checks, leaving it out invents nothing.

Everything else matches the reading: the fd/initialisation checks, GetIdleCount's 0, the handle == 0 || count == NULL pair, ReadHistory's exact 0x960, OpenExt2's 0x10/null check, and GetDeviceId writing -1 before the lookup. Each still throws past its checks.

Build and test on Windows (WinLibs GCC, BUILD_TESTING=ON), PR merged into current main: guest_pad_firmware_checks and guest_pad pass. main's Export.cpp doesn't compile against the new header signatures, so there's no main control.

@BunnyFl's corrected reading of 0xe8a0: a null output is refused first,
then initialisation, then a type other than 0 or 2. The test no longer
pins ReadStateExt's handle-first order before init, which follows this
library's ReadState rather than the module; the debt entry says so.
@zaryabzip

Copy link
Copy Markdown
Contributor Author

@BunnyFl Thanks for the correction and the build.

  1. Fixed in b94b3fa. GetInfoByPortType now refuses a null output first, then checks initialisation, then the port type. The test rows follow that order: (1, data) before init gives 0x80920005, and a null output gives 0x80920001 before and after init.
  2. ReadStateExt: I took the debt-line route, so ReadState and ReadStateExt stay consistent here until both move to the module's order. I also dropped the test row that pinned the handle-first answer before init.
  3. GetExtensionUnitInfo: left out, as you suggest. The entry says so.

The entry is back under 1000 characters in a follow-up commit.

@BunnyFl BunnyFl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @zaryabzip. Re-reviewed at 6b856235a8. GetInfoByPortType now checks null info, then initialisation, then the type, as 0xe8a0 does. The rows pin (1, data) to 0x80920005 before init and a null output to 0x80920001 both before and after. The ReadStateExt debt route is fine.

Build and test on Windows (WinLibs GCC, BUILD_TESTING=ON), merged into current main: guest_pad_firmware_checks and guest_pad pass.

One nit: the longer debt entry is 999 characters, right at the limit, so the next edit to it will need trimming.

@github-actions github-actions Bot added the fix error Closes an issue (Fixes #N) label Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 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 5038 core/libs/prx/libScePad/Export.cpp
docs/dev/debt/libScePad.md
PR 8260 core/libs/prx/libScePad/Export.cpp
docs/dev/debt/libScePad.md
PR 10112 core/libs/prx/libScePad/CMakeLists.txt
core/libs/prx/libScePad/Export.cpp
docs/dev/debt/libScePad.md
PR 12308 core/libs/prx/libScePad/CMakeLists.txt
core/libs/prx/libScePad/Export.cpp
docs/dev/debt/libScePad.md
PR 12309 core/libs/prx/libScePad/CMakeLists.txt
core/libs/prx/libScePad/Export.cpp
docs/dev/debt/libScePad.md
PR 12310 core/libs/prx/libScePad/CMakeLists.txt
core/libs/prx/libScePad/Export.cpp
docs/dev/debt/libScePad.md
PR 12311 core/libs/prx/libScePad/CMakeLists.txt
core/libs/prx/libScePad/Export.cpp
docs/dev/debt/libScePad.md
PR 12312 core/libs/prx/libScePad/CMakeLists.txt
core/libs/prx/libScePad/Export.cpp
docs/dev/debt/libScePad.md
PR 14212 docs/dev/debt/libScePad.md

@oneandonlydean oneandonlydean self-assigned this Nov 3, 2026
@oneandonlydean
oneandonlydean merged commit 99327b0 into boykopovar:main Nov 3, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build docs fix error Closes an issue (Fixes #N) prx

Projects

None yet

Development

Successfully merging this pull request may close these issues.

libScePad: thirteen exports need their argument lists and checks from the firmware module

3 participants