diff --git a/src/graphics/presentation/window/window.cpp b/src/graphics/presentation/window/window.cpp index dfc3e57c4..ca40fefa0 100644 --- a/src/graphics/presentation/window/window.cpp +++ b/src/graphics/presentation/window/window.cpp @@ -814,6 +814,7 @@ static void WindowCreate(WindowContext& context) { int width = static_cast(context.graphic_ctx.screen_width); int height = static_cast(context.graphic_ctx.screen_height); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "0"); #endif diff --git a/src/libs/controller.cpp b/src/libs/controller.cpp index 14ff5f6ad..abb9fe79b 100644 --- a/src/libs/controller.cpp +++ b/src/libs/controller.cpp @@ -39,11 +39,42 @@ struct PadControllerInformation { uint8_t reserve[8]; }; +struct PadLightBarParam { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t reserve; +}; + struct PadVibrationParam { uint8_t large_motor; uint8_t small_motor; }; +struct PadTriggerEffectCommand { + uint32_t mode; + uint8_t reserve[4]; + uint8_t data[48]; +}; + +struct PadTriggerEffectParam { + uint8_t trigger_mask; + uint8_t reserve[7]; + PadTriggerEffectCommand command[2]; +}; + +static_assert(sizeof(PadTriggerEffectCommand) == 56); +static_assert(sizeof(PadTriggerEffectParam) == 120); + +struct DualSenseEffects { + uint8_t enable_bits; + uint8_t reserve[9]; + uint8_t right_trigger[11]; + uint8_t left_trigger[11]; +}; + +static_assert(sizeof(DualSenseEffects) == 32); + struct ControllerState { struct Touch { uint8_t id = 0; @@ -73,6 +104,8 @@ public: void ResetInputState(); void GetConnectionInfo(bool* flag, int* count); void SetVibration(uint8_t large_motor, uint8_t small_motor); + void SetLightBar(uint8_t r, uint8_t g, uint8_t b); + bool SetTriggerEffect(const PadTriggerEffectParam& param); void ReadState(ControllerState* state, bool* flag, int* count); int ReadStates(ControllerState* states, int states_num, bool* flag, int* count); @@ -125,6 +158,92 @@ static void pad_fill_data(PadData* data, const ControllerState& state, bool conn data->device_unique_data_len = 0; } +static bool trigger_effect_zones(uint8_t* effect, const uint8_t* strengths, uint8_t type, + uint8_t frequency = 0) { + uint16_t active = 0; + uint32_t packed = 0; + for (int i = 0; i < 10; i++) { + if (strengths[i] > 8) { + return false; + } + if (strengths[i] != 0) { + active |= static_cast(1u << i); + packed |= static_cast(strengths[i] - 1u) << (3 * i); + } + } + + effect[0] = active != 0 && (type != 0x26 || frequency != 0) ? type : 0x05; + effect[1] = static_cast(active); + effect[2] = static_cast(active >> 8u); + effect[3] = static_cast(packed); + effect[4] = static_cast(packed >> 8u); + effect[5] = static_cast(packed >> 16u); + effect[6] = static_cast(packed >> 24u); + effect[9] = frequency; + return true; +} + +static bool trigger_effect_to_dualsense(const PadTriggerEffectCommand& command, uint8_t* effect) { + std::memset(effect, 0, 11); + effect[0] = 0x05; + + uint8_t strengths[10] = {}; + switch (command.mode) { + case 0: return true; + case 1: + if (command.data[0] > 9 || command.data[1] > 8) { + return false; + } + std::fill(strengths + command.data[0], strengths + 10, command.data[1]); + return trigger_effect_zones(effect, strengths, 0x21); + case 2: { + const auto start = command.data[0]; + const auto end = command.data[1]; + const auto strength = command.data[2]; + if (start < 2 || start > 7 || end <= start || end > 8 || strength > 8) { + return false; + } + if (strength == 0) { + return true; + } + const uint16_t zones = static_cast((1u << start) | (1u << end)); + effect[0] = 0x25; + effect[1] = static_cast(zones); + effect[2] = static_cast(zones >> 8u); + effect[3] = strength - 1u; + return true; + } + case 3: + if (command.data[0] > 9 || command.data[1] > 8) { + return false; + } + std::fill(strengths + command.data[0], strengths + 10, command.data[1]); + return trigger_effect_zones(effect, strengths, 0x26, command.data[2]); + case 4: return trigger_effect_zones(effect, command.data, 0x21); + case 5: { + const int start = command.data[0]; + const int end = command.data[1]; + const int start_strength = command.data[2]; + const int end_strength = command.data[3]; + if (start > 8 || end <= start || end > 9 || start_strength < 1 || start_strength > 8 || + end_strength < 1 || end_strength > 8) { + return false; + } + for (int i = start; i < 10; i++) { + const int delta = (end_strength - start_strength) * (i - start); + const int length = end - start; + strengths[i] = static_cast( + i >= end ? end_strength + : start_strength + + (delta + (delta < 0 ? -length / 2 : length / 2)) / length); + } + return trigger_effect_zones(effect, strengths, 0x21); + } + case 6: return trigger_effect_zones(effect, command.data + 1, 0x26, command.data[0]); + default: return false; + } +} + void Initialize() { EXIT_IF(g_controller != nullptr); @@ -304,6 +423,44 @@ void GameController::SetVibration(uint8_t large_motor, uint8_t small_motor) { } } +void GameController::SetLightBar(uint8_t r, uint8_t g, uint8_t b) { + Common::LockGuard lock(m_mutex); + if (auto* pad = SDL_GameControllerFromInstanceID(static_cast(m_active_id)); + pad != nullptr) { + (void)SDL_GameControllerSetLED(pad, r, g, b); + } +} + +bool GameController::SetTriggerEffect(const PadTriggerEffectParam& param) { + if ((param.trigger_mask & ~0x03u) != 0) { + return false; + } + + DualSenseEffects effect {}; + if ((param.trigger_mask & 0x01u) != 0) { + effect.enable_bits |= 0x08; + if (!trigger_effect_to_dualsense(param.command[0], effect.left_trigger)) { + return false; + } + } + if ((param.trigger_mask & 0x02u) != 0) { + effect.enable_bits |= 0x04; + if (!trigger_effect_to_dualsense(param.command[1], effect.right_trigger)) { + return false; + } + } + if (effect.enable_bits == 0) { + return true; + } + + Common::LockGuard lock(m_mutex); + auto* pad = SDL_GameControllerFromInstanceID(static_cast(m_active_id)); + if (pad != nullptr && SDL_GameControllerGetType(pad) == SDL_CONTROLLER_TYPE_PS5) { + (void)SDL_GameControllerSendEffect(pad, &effect, sizeof(effect)); + } + return true; +} + void GameController::GetConnectionInfo(bool* flag, int* count) { EXIT_IF(flag == nullptr); EXIT_IF(count == nullptr); @@ -617,7 +774,24 @@ int KYTY_SYSV_ABI PadSetLightBar(int handle, const PadLightBarParam* param) { return PAD_ERROR_INVALID_ARG; } + EXIT_IF(g_controller == nullptr); + g_controller->SetLightBar(param->r, param->g, param->b); + return OK; } +int KYTY_SYSV_ABI PadSetTriggerEffect(int handle, const PadTriggerEffectParam* param) { + PRINT_NAME(); + + if (handle != 1) { + return PAD_ERROR_INVALID_HANDLE; + } + if (param == nullptr) { + return PAD_ERROR_INVALID_ARG; + } + + EXIT_IF(g_controller == nullptr); + return g_controller->SetTriggerEffect(*param) ? OK : PAD_ERROR_INVALID_ARG; +} + } // namespace Libs::Controller diff --git a/src/libs/controller.h b/src/libs/controller.h index 9c01a880d..74a09fa8a 100644 --- a/src/libs/controller.h +++ b/src/libs/controller.h @@ -48,6 +48,7 @@ struct PadControllerInformation; struct PadData; struct PadVibrationParam; struct PadLightBarParam; +struct PadTriggerEffectParam; inline int controller_get_axis(int min, int max, int value) { int v = (255 * (value - min)) / (max - min); @@ -78,6 +79,7 @@ int KYTY_SYSV_ABI PadGetControllerInformation(int handle, PadControllerInformati int KYTY_SYSV_ABI PadReadState(int handle, PadData* data); int KYTY_SYSV_ABI PadRead(int handle, PadData* data, int num); int KYTY_SYSV_ABI PadSetVibration(int handle, const PadVibrationParam* param); +int KYTY_SYSV_ABI PadSetTriggerEffect(int handle, const PadTriggerEffectParam* param); int KYTY_SYSV_ABI PadResetLightBar(int handle); int KYTY_SYSV_ABI PadSetLightBar(int handle, const PadLightBarParam* param); diff --git a/src/libs/libPad.cpp b/src/libs/libPad.cpp index b48a5a5f6..0f34f8161 100644 --- a/src/libs/libPad.cpp +++ b/src/libs/libPad.cpp @@ -42,23 +42,6 @@ static int KYTY_SYSV_ABI PadGetTriggerEffectState(int return 0; } -static int KYTY_SYSV_ABI PadSetTriggerEffect(int handle, const void* param) { - PRINT_NAME(); - - LOGF("\t handle = %d\n" - "\t param = 0x%016" PRIx64 "\n", - handle, reinterpret_cast(param)); - - if (handle != 1) { - return -2137915389; /* 0x80920003 */ - } - if (param == nullptr) { - return -2137915391; /* 0x80920001 */ - } - - return OK; -} - static int KYTY_SYSV_ABI PadSetVibrationTriggerEffectWeakWhileEmbeddedMicInUse(bool enabled) { PRINT_NAME(); @@ -198,7 +181,7 @@ LIB_DEFINE(InitPad_1) { LIB_FUNC("rIZnR6eSpvk", Controller::PadResetOrientation); LIB_FUNC("vDLMoJLde8I", PadSetTiltCorrectionState); LIB_FUNC("gjP9-KQzoUk", Controller::PadGetControllerInformation); - LIB_FUNC("2JgFB2n9oUM", PadSetTriggerEffect); + LIB_FUNC("2JgFB2n9oUM", Controller::PadSetTriggerEffect); LIB_FUNC("YndgXqQVV7c", Controller::PadReadState); LIB_FUNC("q1cHNfGycLI", Controller::PadRead); LIB_FUNC("yFVnOdGxvZY", Controller::PadSetVibration);