From b65dc122082bab486aaf62619328c84a7e05ad9c Mon Sep 17 00:00:00 2001 From: nmzik Date: Thu, 8 Oct 2026 20:42:11 +0400 Subject: [PATCH] Track pad port ownership and reject duplicate opens --- src/libs/controller.cpp | 169 ++++++++++++++++++++---------- src/libs/controller.h | 3 + src/libs/libPad.cpp | 30 ++---- tests/ControllerSettingsTests.cpp | 51 +++++++++ 4 files changed, 176 insertions(+), 77 deletions(-) diff --git a/src/libs/controller.cpp b/src/libs/controller.cpp index ea7574f9a..4c34601a0 100644 --- a/src/libs/controller.cpp +++ b/src/libs/controller.cpp @@ -24,8 +24,12 @@ namespace Libs::Controller { LIB_NAME("Pad", "Pad"); -constexpr int PAD_ERROR_INVALID_ARG = -2137915391; /* 0x80920001 */ -constexpr int PAD_ERROR_INVALID_HANDLE = -2137915389; /* 0x80920003 */ +constexpr int PAD_ERROR_INVALID_ARG = -2137915391; /* 0x80920001 */ +constexpr int PAD_ERROR_INVALID_HANDLE = -2137915389; /* 0x80920003 */ +constexpr int PAD_ERROR_ALREADY_OPENED = -2137915388; /* 0x80920004 */ +constexpr int PAD_ERROR_NOT_INITIALIZED = -2137915387; /* 0x80920005 */ +constexpr int PAD_ERROR_NO_HANDLE = -2137915384; /* 0x80920008 */ +constexpr int PAD_HANDLE = 1; constexpr uint32_t RUMBLE_DURATION_MS = 0xffff; constexpr uint32_t RELEASE_FLUSH_MS = 50; @@ -107,6 +111,12 @@ public: KYTY_CLASS_NO_COPY(GameController); + int InitPort(); + int OpenPort(int user_id, int type, int index); + int GetPortHandle(int user_id, int type, int index); + int ClosePort(int handle); + int CheckPortHandle(int handle); + void Connect(int id); void Disconnect(int id); void Button(int id, uint32_t button, bool down); @@ -131,6 +141,9 @@ public: private: static constexpr uint32_t STATES_MAX = 64; + enum class PortState { Uninitialized, Closed, Open }; + + int CheckPortHandleLocked(int handle) const; void CheckActive(); void AddState(); @@ -140,6 +153,7 @@ private: void UpdateTriggerEffectState(); Common::Mutex m_mutex; + PortState m_port_state = PortState::Uninitialized; std::vector m_connected_ids; int m_active_id = -1; bool m_connected = false; @@ -925,21 +939,71 @@ int GetActiveControllerId() { return g_controller != nullptr ? g_controller->GetActiveControllerId() : -1; } -int KYTY_SYSV_ABI PadInit() { - PRINT_NAME(); +static bool PadOpenArgsAreValid(int user_id, int type, int index) { + // The host keyboard and gamepad feed one standard port for the configured user. + return user_id == Config::GetUserId() && type == 0 && index == 0; +} +int GameController::InitPort() { + Common::LockGuard lock(m_mutex); + if (m_port_state == PortState::Uninitialized) { + m_port_state = PortState::Closed; + } return OK; } -static bool PadOpenArgsAreValid(int user_id, int type, int index) { - constexpr int user_id_system = 0xff; - constexpr int port_type_standard = 0; - constexpr int port_type_special = 2; - constexpr int port_type_remote = 16; - const bool personal_port = - user_id == Config::GetUserId() && (type == port_type_standard || type == port_type_special); - const bool system_remote_control = user_id == user_id_system && type == port_type_remote; - return index == 0 && (personal_port || system_remote_control); +int GameController::OpenPort(int user_id, int type, int index) { + Common::LockGuard lock(m_mutex); + if (m_port_state == PortState::Uninitialized) { + return PAD_ERROR_NOT_INITIALIZED; + } + if (!PadOpenArgsAreValid(user_id, type, index)) { + return PAD_ERROR_INVALID_ARG; + } + if (m_port_state == PortState::Open) { + return PAD_ERROR_ALREADY_OPENED; + } + m_port_state = PortState::Open; + return PAD_HANDLE; +} + +int GameController::GetPortHandle(int user_id, int type, int index) { + Common::LockGuard lock(m_mutex); + if (m_port_state == PortState::Uninitialized) { + return PAD_ERROR_NOT_INITIALIZED; + } + return m_port_state == PortState::Open && PadOpenArgsAreValid(user_id, type, index) ? PAD_HANDLE + : PAD_ERROR_NO_HANDLE; +} + +int GameController::CheckPortHandleLocked(int handle) const { + if (m_port_state == PortState::Uninitialized) { + return PAD_ERROR_NOT_INITIALIZED; + } + return m_port_state == PortState::Open && handle == PAD_HANDLE ? OK : PAD_ERROR_INVALID_HANDLE; +} + +int GameController::CheckPortHandle(int handle) { + Common::LockGuard lock(m_mutex); + return CheckPortHandleLocked(handle); +} + +int GameController::ClosePort(int handle) { + Common::LockGuard lock(m_mutex); + if (const auto error = CheckPortHandleLocked(handle); error != OK) { + return error; + } + m_port_state = PortState::Closed; + return OK; +} + +int PadCheckHandle(int handle) { + return g_controller->CheckPortHandle(handle); +} + +int KYTY_SYSV_ABI PadInit() { + PRINT_NAME(); + return g_controller->InitPort(); } int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param) { @@ -951,15 +1015,7 @@ int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param) { "\t param = 0x%016" PRIx64 "\n", user_id, type, index, reinterpret_cast(param)); - constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */ - - if (!PadOpenArgsAreValid(user_id, type, index)) { - return pad_error_invalid_arg; - } - - int handle = 1; - - return handle; + return g_controller->OpenPort(user_id, type, index); } int KYTY_SYSV_ABI PadGetHandle(int user_id, int type, int index) { @@ -970,20 +1026,20 @@ int KYTY_SYSV_ABI PadGetHandle(int user_id, int type, int index) { "\t index = %d\n", user_id, type, index); - constexpr int pad_error_device_no_handle = -2137915384; /* 0x80920008 */ + return g_controller->GetPortHandle(user_id, type, index); +} - if (!PadOpenArgsAreValid(user_id, type, index)) { - return pad_error_device_no_handle; - } - - return 1; +int KYTY_SYSV_ABI PadClose(int handle) { + PRINT_NAME(); + LOGF("\t handle = %d\n", handle); + return g_controller->ClosePort(handle); } int KYTY_SYSV_ABI PadSetMotionSensorState(int handle, bool enable) { PRINT_NAME(); - if (handle != 1) { - return PAD_ERROR_INVALID_HANDLE; + if (const auto error = PadCheckHandle(handle); error != OK) { + return error; } LOGF("\t enable = %s\n", (enable ? "true" : "false")); @@ -995,8 +1051,8 @@ int KYTY_SYSV_ABI PadSetMotionSensorState(int handle, bool enable) { int KYTY_SYSV_ABI PadSetAngularVelocityDeadbandState(int handle, bool enable) { PRINT_NAME(); - if (handle != 1) { - return PAD_ERROR_INVALID_HANDLE; + if (const auto error = PadCheckHandle(handle); error != OK) { + return error; } LOGF("\t enable = %s\n", (enable ? "true" : "false")); @@ -1007,8 +1063,8 @@ int KYTY_SYSV_ABI PadSetAngularVelocityDeadbandState(int handle, bool enable) { int KYTY_SYSV_ABI PadResetOrientation(int handle) { PRINT_NAME(); - if (handle != 1) { - return PAD_ERROR_INVALID_HANDLE; + if (const auto error = PadCheckHandle(handle); error != OK) { + return error; } g_controller->ResetOrientation(); @@ -1018,18 +1074,17 @@ int KYTY_SYSV_ABI PadResetOrientation(int handle) { int KYTY_SYSV_ABI PadGetControllerInformation(int handle, PadControllerInformation* info) { PRINT_NAME(); - int connected_count = 0; - bool connected = false; - - g_controller->GetConnectionInfo(&connected, &connected_count); - - if (handle != 1) { - return PAD_ERROR_INVALID_HANDLE; + if (const auto error = PadCheckHandle(handle); error != OK) { + return error; } if (info == nullptr) { return PAD_ERROR_INVALID_ARG; } + int connected_count = 0; + bool connected = false; + g_controller->GetConnectionInfo(&connected, &connected_count); + std::memset(info, 0, sizeof(*info)); info->touch_pixel_density = 44.86f; @@ -1048,8 +1103,8 @@ int KYTY_SYSV_ABI PadGetControllerInformation(int handle, PadControllerInformati int KYTY_SYSV_ABI PadIsRemoteController(int handle, bool* is_remote) { PRINT_NAME(); - if (handle != 1) { - return PAD_ERROR_INVALID_HANDLE; + if (const auto error = PadCheckHandle(handle); error != OK) { + return error; } if (is_remote == nullptr) { return PAD_ERROR_INVALID_ARG; @@ -1062,8 +1117,8 @@ int KYTY_SYSV_ABI PadIsRemoteController(int handle, bool* is_remote) { int KYTY_SYSV_ABI PadReadState(int handle, PadData* data) { PRINT_NAME(); - if (handle != 1) { - return PAD_ERROR_INVALID_HANDLE; + if (const auto error = PadCheckHandle(handle); error != OK) { + return error; } if (data == nullptr) { return PAD_ERROR_INVALID_ARG; @@ -1084,8 +1139,8 @@ int KYTY_SYSV_ABI PadRead(int handle, PadData* data, int num) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(num < 1 || num > 64); - if (handle != 1) { - return PAD_ERROR_INVALID_HANDLE; + if (const auto error = PadCheckHandle(handle); error != OK) { + return error; } if (data == nullptr) { return PAD_ERROR_INVALID_ARG; @@ -1116,8 +1171,8 @@ int KYTY_SYSV_ABI PadRead(int handle, PadData* data, int num) { int KYTY_SYSV_ABI PadSetVibration(int handle, const PadVibrationParam* param) { PRINT_NAME(); - if (handle != 1) { - return PAD_ERROR_INVALID_HANDLE; + if (const auto error = PadCheckHandle(handle); error != OK) { + return error; } if (param == nullptr) { return PAD_ERROR_INVALID_ARG; @@ -1135,8 +1190,8 @@ int KYTY_SYSV_ABI PadSetVibration(int handle, const PadVibrationParam* param) { int KYTY_SYSV_ABI PadResetLightBar(int handle) { PRINT_NAME(); - if (handle != 1) { - return PAD_ERROR_INVALID_HANDLE; + if (const auto error = PadCheckHandle(handle); error != OK) { + return error; } return OK; @@ -1145,8 +1200,8 @@ int KYTY_SYSV_ABI PadResetLightBar(int handle) { int KYTY_SYSV_ABI PadSetLightBar(int handle, const PadLightBarParam* param) { PRINT_NAME(); - if (handle != 1) { - return PAD_ERROR_INVALID_HANDLE; + if (const auto error = PadCheckHandle(handle); error != OK) { + return error; } if (param == nullptr) { return PAD_ERROR_INVALID_ARG; @@ -1160,8 +1215,8 @@ int KYTY_SYSV_ABI PadSetLightBar(int handle, const PadLightBarParam* param) { int KYTY_SYSV_ABI PadSetTriggerEffect(int handle, const PadTriggerEffectParam* param) { PRINT_NAME(); - if (handle != 1) { - return PAD_ERROR_INVALID_HANDLE; + if (const auto error = PadCheckHandle(handle); error != OK) { + return error; } if (param == nullptr) { return PAD_ERROR_INVALID_ARG; @@ -1175,8 +1230,8 @@ int KYTY_SYSV_ABI PadGetTriggerEffectState(int handle, PadTriggerEffectStateInfo LOGF("\t handle = %d\n", handle); - if (handle != 1) { - return PAD_ERROR_INVALID_HANDLE; + if (const auto error = PadCheckHandle(handle); error != OK) { + return error; } if (info == nullptr) { return PAD_ERROR_INVALID_ARG; diff --git a/src/libs/controller.h b/src/libs/controller.h index 7d3fee34e..b84246a18 100644 --- a/src/libs/controller.h +++ b/src/libs/controller.h @@ -81,9 +81,12 @@ enum class Setting { SpeakerVolume, VibrationIntensity, TriggerEffectIntensity } void CycleSetting(Setting setting); float GetSettingScale(Setting setting); +int PadCheckHandle(int handle); + int KYTY_SYSV_ABI PadInit(); int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param); int KYTY_SYSV_ABI PadGetHandle(int user_id, int type, int index); +int KYTY_SYSV_ABI PadClose(int handle); int KYTY_SYSV_ABI PadSetMotionSensorState(int handle, bool enable); int KYTY_SYSV_ABI PadSetAngularVelocityDeadbandState(int handle, bool enable); int KYTY_SYSV_ABI PadResetOrientation(int handle); diff --git a/src/libs/libPad.cpp b/src/libs/libPad.cpp index ab9a861fe..30b82559c 100644 --- a/src/libs/libPad.cpp +++ b/src/libs/libPad.cpp @@ -19,7 +19,7 @@ static int KYTY_SYSV_ABI PadSetVibrationMode(int handle, int mode) { "\t mode = %d\n", handle, mode); - return 0; + return Controller::PadCheckHandle(handle); } static int KYTY_SYSV_ABI PadSetVibrationTriggerEffectWeakWhileEmbeddedMicInUse(bool enabled) { @@ -66,11 +66,10 @@ static int KYTY_SYSV_ABI PadDeviceClassGetExtendedInformation(int handle, PadDeviceClassExtendedInformation* info) { PRINT_NAME(); - constexpr int pad_error_invalid_handle = -2137915389; /* 0x80920003 */ - constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */ + constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */ - if (handle != 1) { - return pad_error_invalid_handle; + if (const auto error = Controller::PadCheckHandle(handle); error != OK) { + return error; } if (info == nullptr) { return pad_error_invalid_arg; @@ -85,16 +84,15 @@ static int KYTY_SYSV_ABI PadDeviceClassParseData(int handle, const Controller::P PadDeviceClassData* class_data) { PRINT_NAME(); - constexpr int pad_error_invalid_handle = -2137915389; /* 0x80920003 */ - constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */ + constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */ LOGF("\t handle = %d\n" "\t data = 0x%016" PRIx64 "\n" "\t class_data = 0x%016" PRIx64 "\n", handle, reinterpret_cast(data), reinterpret_cast(class_data)); - if (handle != 1) { - return pad_error_invalid_handle; + if (const auto error = Controller::PadCheckHandle(handle); error != OK) { + return error; } if (data == nullptr || class_data == nullptr) { return pad_error_invalid_arg; @@ -122,15 +120,7 @@ static int KYTY_SYSV_ABI PadSetTiltCorrectionState(int handle, bool enabled) { "\t enabled = %d\n", handle, enabled ? 1 : 0); - return 0; -} - -static int KYTY_SYSV_ABI PadClose(int handle) { - PRINT_NAME(); - - LOGF("\t handle = %d\n", handle); - - return OK; + return Controller::PadCheckHandle(handle); } // PPSA02385 @@ -154,7 +144,7 @@ static int KYTY_SYSV_ABI PadGetExtControllerInformationStub(int handle, void* in "\t info = 0x%016" PRIx64 "\n", handle, reinterpret_cast(info)); - return OK; + return Controller::PadCheckHandle(handle); } static int KYTY_SYSV_ABI PadUnknownN3kSX62fgNo(uint64_t arg0, uint64_t arg1, uint64_t arg2, @@ -197,7 +187,7 @@ LIB_DEFINE(InitPad_1) { LIB_FUNC("znaWI0gpuo8", Controller::PadGetTriggerEffectState); LIB_FUNC("DscD1i9HX1w", Controller::PadResetLightBar); LIB_FUNC("RR4novUEENY", Controller::PadSetLightBar); - LIB_FUNC("6ncge5+l5Qs", PadClose); + LIB_FUNC("6ncge5+l5Qs", Controller::PadClose); LIB_FUNC("n3kSX62fgNo", PadUnknownN3kSX62fgNo); } diff --git a/tests/ControllerSettingsTests.cpp b/tests/ControllerSettingsTests.cpp index 3c5e37f5f..0ab53f532 100644 --- a/tests/ControllerSettingsTests.cpp +++ b/tests/ControllerSettingsTests.cpp @@ -110,6 +110,8 @@ struct Controller { now = 1000; haptics_handles_rumble = false; Initialize(); + Check(PadInit() == 0 && PadOpen(Config::GetUserId(), 0, 0, nullptr) == 1, + "the standard controller port did not open"); Connect(1); Check(GetSettingScale(Setting::SpeakerVolume) == Config::GetControllerSpeakerVolume() / 50.0f && @@ -143,6 +145,54 @@ void SetRumble(uint8_t large, uint8_t small) { Check(PadSetVibration(1, ¶m) == 0, "vibration request failed"); } +void TestPadPortOwnership() { + Initialize(); + const int user = Config::GetUserId(); + constexpr auto not_initialized = static_cast(0x80920005u); + constexpr auto already_opened = static_cast(0x80920004u); + constexpr auto invalid_handle = static_cast(0x80920003u); + constexpr auto no_handle = static_cast(0x80920008u); + PadData data {}; + Check(PadOpen(user, 0, 0, nullptr) == not_initialized && + PadGetHandle(user, 0, 0) == not_initialized && + PadReadState(1, &data) == not_initialized, + "an uninitialized library exposed a controller port"); + Check(PadInit() == 0 && PadGetHandle(user, 0, 0) == no_handle, + "initializing the library opened a controller port"); + const int handle = PadOpen(user, 0, 0, nullptr); + Check(handle == 1 && PadInit() == 0 && PadGetHandle(user, 0, 0) == handle, + "reinitializing the library lost the open port"); + for (int duplicate = 0; duplicate < 4; duplicate++) { + const int rejected = PadOpen(user, 0, 0, nullptr); + Check(rejected == already_opened, + "opening one pad five times exposed duplicate connected controllers"); + Check(PadClose(rejected) == invalid_handle && PadGetHandle(user, 0, 0) == handle, + "closing a rejected duplicate released the original owner"); + } + Check(PadGetHandle(user + 1, 0, 0) == no_handle && + PadGetHandle(user, 2, 0) == no_handle && PadGetHandle(user, 0, 1) == no_handle, + "a different port tuple aliased the open standard pad"); + SetButton(HOST_INPUT_CONTROLLER_ID, PAD_BUTTON_CROSS, true); + Check(PadReadState(handle, &data) == 0 && data.connected && + data.buttons == PAD_BUTTON_CROSS, + "keyboard Cross did not reach the owned port"); + SetButton(HOST_INPUT_CONTROLLER_ID, PAD_BUTTON_CROSS, false); + Connect(1); + SetButton(1, PAD_BUTTON_CROSS, true); + Check(PadReadState(handle, &data) == 0 && data.buttons == PAD_BUTTON_CROSS, + "the physical controller did not share the owned port"); + Disconnect(1); + Check(PadGetHandle(user, 0, 0) == handle && PadOpen(user, 0, 0, nullptr) == already_opened, + "host disconnection released guest port ownership"); + Check(PadClose(handle) == 0 && PadGetHandle(user, 0, 0) == no_handle && + PadReadState(handle, &data) == invalid_handle && + PadRead(handle, &data, 1) == invalid_handle && PadClose(handle) == invalid_handle, + "a closed port retained a usable handle"); + Check(PadOpen(user, 0, 0, nullptr) == handle && PadReadState(handle, &data) == 0, + "a closed port could not reopen"); + Shutdown(); +} + void TestSettingCycles() { Controller controller; for (auto setting: @@ -503,6 +553,7 @@ void TestTriggerEffectState() { int main() { Config::Initialize(); + TestPadPortOwnership(); TestSettingCycles(); TestGlobalControllerLevels(); // Each following test initializes another controller and requires strong defaults.