From 855176796bf4e8cec2b6087a8eaf67fc240e2a50 Mon Sep 17 00:00:00 2001 From: OverkillLabs <10800190+OverkillLabs@users.noreply.github.com> Date: Mon, 28 Sep 2026 13:43:07 +0300 Subject: [PATCH] pad: real implementations for extended pad info/lightbar/trigger NIDs Replace NotImplemented stubs with real implementations for scePadGetInfo, scePadDeviceClassGetExtendedInformation/ParseData, scePadGetExtControllerInformation, scePadSetLightBar, scePadResetLightBar, scePadResetOrientation, scePadSetMotionSensorState, scePadGetTriggerEffectState, scePadSetProcessPrivilege, and scePadSetParticularMode, wiring them to the new Pad:: output/orientation API. None of these NIDs are touched by upstream's existing keyboard/mouse binding work, so this is a clean additive change. scePadSetVibration, scePadSetVibrationMode, and scePadSetTriggerEffect are also given real implementations here, even though they were not named in the audit's NID list: they are the only guest entry points for the Pad::SetVibration/SetVibrationMode/SetTriggerCommand plumbing added in the previous commit, so leaving them NotImplemented would make the rumble/adaptive-trigger machinery unreachable from any title. Every other previously-NotImplemented NID in this file (scePadGetHandle, scePadSetAngularVelocityDeadbandState, scePadSetTiltCorrectionState, scePadSetVibrationTriggerEffectWeakWhileEmbeddedMicInUse) is left exactly as upstream had it; scePadOpen_nid_postfix, scePadRead_nid_postfix, and scePadReadState are untouched (upstream's APS5_INVALID_ARG_EX throw-based error handling is kept as-is, not replaced with plain error-code returns). Co-Authored-By: Claude Sonnet 5 --- core/libs/prx/libScePad/Export.cpp | 149 +++++++++++++++++++++-------- 1 file changed, 109 insertions(+), 40 deletions(-) diff --git a/core/libs/prx/libScePad/Export.cpp b/core/libs/prx/libScePad/Export.cpp index be2cc63e1..87e2dbc79 100644 --- a/core/libs/prx/libScePad/Export.cpp +++ b/core/libs/prx/libScePad/Export.cpp @@ -20,18 +20,21 @@ int APS5_VABI scePadClose_nid_postfix(int handle) { } int APS5_VABI scePadDeviceClassGetExtendedInformation(int handle, PadDeviceClassExtendedInformation* info) { - (void)handle; - (void)info; - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (info == nullptr) return PAD_ERROR_INVALID_ARG; + // Standard digital pad: device class only, every class-specific field zero. + std::memset(info, 0, sizeof(*info)); + info->deviceClass = PAD_DEVICE_CLASS_STANDARD; + return PAD_OK; } int APS5_VABI scePadDeviceClassParseData(int handle, const PadData* data, PadDeviceClassData* class_data) { - (void)handle; - (void)data; - (void)class_data; - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (data == nullptr || class_data == nullptr) return PAD_ERROR_INVALID_ARG; + std::memset(class_data, 0, sizeof(*class_data)); + class_data->deviceClass = PAD_DEVICE_CLASS_STANDARD; + class_data->dataValid = data->connected; // no steering/guitar/drum payload for a standard pad + return PAD_OK; } int APS5_VABI scePadGetControllerInformation(int handle, PadControllerInformation* info) { @@ -62,11 +65,56 @@ int APS5_VABI scePadGetHandle(int user_id, int type, int index) { return 0; } +// Port 0 holds the virtual DualSense while the host has no pad open; fields per the PadInfo layout. +int APS5_VABI scePadGetInfo_nid_postfix(PadInfo* info) { + if (info == nullptr) return PAD_ERROR_INVALID_ARG; + std::memset(info, 0, sizeof(*info)); + info->maxConnectCount = 4; + info->connectedCount[0] = 1; // standard ports + info->connectedCount[1] = 0; // special ports + PadInfo::PadTypeInfo& slot = info->padTypeInfo[0]; + slot.connectPort = 0; + slot.status = 1; + slot.deviceType = PAD_DEVICE_TYPE_DUAL_SENSE; + slot.connectType = PAD_CONNECT_TYPE_BLUETOOTH; + std::memcpy(slot.bluetoothMacAddress, PAD_BD_ADDRESS, sizeof(slot.bluetoothMacAddress)); + return PAD_OK; +} + int APS5_VABI scePadGetTriggerEffectState(int handle, PadTriggerEffectStateInformation* info) { - (void)handle; - (void)info; - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (info == nullptr) return PAD_ERROR_INVALID_ARG; + // Both triggers report the neutral "no feedback engaged" state; the host pad has no trigger readback. + std::memset(info, 0, sizeof(*info)); + return PAD_OK; +} + +int APS5_VABI scePadGetExtControllerInformation(int handle, void* info) { + if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (info == nullptr) return PAD_ERROR_INVALID_ARG; + // ScePadExtendedControllerInformation: base information followed by the class extension (all zero for a standard pad). + std::memset(info, 0, 0x2c); + PadControllerInformation* base = static_cast(info); + base->touchPadInfo.pixelDensity = 44.86f; + base->touchPadInfo.resolution.x = 1920; + base->touchPadInfo.resolution.y = 943; + base->stickInfo.deadZoneLeft = 2; + base->stickInfo.deadZoneRight = 2; + base->connectionType = PAD_CONNECTION_TYPE_LOCAL; + base->connectedCount = 1; + base->connected = true; + base->deviceClass = PAD_DEVICE_CLASS_STANDARD; + return PAD_OK; +} + +int APS5_VABI scePadSetProcessPrivilege(int privilege) { + (void)privilege; + return PAD_OK; +} + +int APS5_VABI scePadSetParticularMode(bool mode) { + (void)mode; + return PAD_OK; } int APS5_VABI scePadInit_nid_postfix(void) { @@ -107,15 +155,15 @@ int APS5_VABI scePadReadState(int handle, PadData* data) { } int APS5_VABI scePadResetLightBar(int handle) { - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + Pad::SetLightBar(false, 0, 0, 0); + return PAD_OK; } int APS5_VABI scePadResetOrientation(int handle) { - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + Pad::ResetOrientation(); + return PAD_OK; } int APS5_VABI scePadSetAngularVelocityDeadbandState(int handle, bool enable) { @@ -125,19 +173,17 @@ int APS5_VABI scePadSetAngularVelocityDeadbandState(int handle, bool enable) { return 0; } +// ScePadLightBarParam: u8 r, g, b at +0..2. int APS5_VABI scePadSetLightBar(int handle, const PadLightBarParam* param) { - (void)handle; - (void)param; - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (param == nullptr) return PAD_ERROR_INVALID_ARG; + Pad::SetLightBar(true, param->r, param->g, param->b); + return PAD_OK; } int APS5_VABI scePadSetMotionSensorState(int handle, bool enable) { - (void)handle; - (void)enable; - // if (enable) { - // throw std::runtime_error("scePadSetMotionSensorState: motion sensor not supported"); - // } + if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + Pad::SetMotionEnabled(enable); return PAD_OK; } @@ -148,25 +194,48 @@ int APS5_VABI scePadSetTiltCorrectionState(int handle, bool enabled) { return 0; } +// ScePadTriggerEffectParam: u8 triggerMask (bit 0 = L2, bit 1 = R2), u8 padding[7], ScePadTriggerEffectCommandData command[2] (56 bytes each). +// Not one of the extended-info NIDs the audit called out by name, but wired to a real implementation here +// because it is the guest entry point for the adaptive-trigger plumbing added alongside it (Pad::SetTriggerCommand); +// leaving it NotImplemented would make the trigger effect state/output machinery unreachable from the guest. int APS5_VABI scePadSetTriggerEffect(int handle, const void* param) { - (void)handle; - (void)param; - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (param == nullptr) return PAD_ERROR_INVALID_ARG; + const std::uint8_t* bytes = static_cast(param); + const std::uint8_t mask = bytes[0]; + if ((mask & ~0x3u) != 0) { + return PAD_ERROR_INVALID_ARG; + } + std::uint32_t mode[2] = {}; + for (int trigger = 0; trigger < 2; ++trigger) std::memcpy(&mode[trigger], bytes + 8 + 56 * trigger, 4); + // Modes 0..6 are the SDK ScePadTriggerEffectMode values; anything else means the struct was misread. + if (mode[0] > 6 || mode[1] > 6) { + return PAD_ERROR_INVALID_ARG; + } + for (int trigger = 0; trigger < 2; ++trigger) { + if ((mask & (1u << trigger)) != 0) Pad::SetTriggerCommand(trigger, bytes + 8 + 56 * trigger); + } + return PAD_OK; } +// ScePadVibrationParam: u8 largeMotor at +0, u8 smallMotor at +1 (vibration-mode motors only; the trigger +// motors are driven through scePadSetTriggerEffect). +// Also not in the audit's named list, but wired here for the same reason as scePadSetTriggerEffect: it is +// the only guest entry point for Pad::SetVibration, which the rumble plumbing added alongside it needs. int APS5_VABI scePadSetVibration(int handle, const PadVibrationParam* param) { - (void)handle; - (void)param; - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (param == nullptr) return PAD_ERROR_INVALID_ARG; + Pad::SetVibration(param->large_motor, param->small_motor); + return PAD_OK; } +// mode: 0 = desktop (USB) vibration layout, 1 = embedded controller layout. Both drive the same host rumble. +// Same rationale as scePadSetVibration: the only guest entry point for Pad::SetVibrationMode. int APS5_VABI scePadSetVibrationMode(int handle, int mode) { - (void)handle; - (void)mode; - NotImplemented_nid_no_patch(__func__); - return 0; + if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; + if (mode != 0 && mode != 1) return PAD_ERROR_INVALID_ARG; + Pad::SetVibrationMode(mode); + return PAD_OK; } int APS5_VABI scePadSetVibrationTriggerEffectWeakWhileEmbeddedMicInUse(bool enabled) {