diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 809dbffda..7657fcf11 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -83,6 +83,8 @@ add_library(core multitap.h negcon.cpp negcon.h + negcon_rumble.cpp + negcon_rumble.h pad.cpp pad.h pcdrv.cpp diff --git a/src/core/controller.cpp b/src/core/controller.cpp index 4c782d671..bf774d028 100644 --- a/src/core/controller.cpp +++ b/src/core/controller.cpp @@ -9,6 +9,7 @@ #include "guncon.h" #include "host.h" #include "negcon.h" +#include "negcon_rumble.h" #include "playstation_mouse.h" #include "util/state_wrapper.h" @@ -21,8 +22,8 @@ static const Controller::ControllerInfo s_none_info = {ControllerType::None, Controller::VibrationCapabilities::NoVibration}; static const Controller::ControllerInfo* s_controller_info[] = { - &s_none_info, &DigitalController::INFO, &AnalogController::INFO, &AnalogJoystick::INFO, &NeGcon::INFO, - &GunCon::INFO, &PlayStationMouse::INFO, + &s_none_info, &DigitalController::INFO, &AnalogController::INFO, &AnalogJoystick::INFO, + &NeGcon::INFO, &NeGconRumble::INFO,&GunCon::INFO, &PlayStationMouse::INFO, }; Controller::Controller(u32 index) : m_index(index) @@ -99,6 +100,9 @@ std::unique_ptr Controller::Create(ControllerType type, u32 index) case ControllerType::NeGcon: return NeGcon::Create(index); + + case ControllerType::NeGconRumble: + return NeGconRumble::Create(index); case ControllerType::None: default: diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index 9602ed4a8..c97ae6da4 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -65,6 +65,7 @@ + @@ -143,6 +144,7 @@ + diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index 945af6b54..4b075aad0 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -52,6 +52,7 @@ + @@ -122,6 +123,7 @@ + diff --git a/src/core/negcon_rumble.cpp b/src/core/negcon_rumble.cpp new file mode 100644 index 000000000..a11a7ce8a --- /dev/null +++ b/src/core/negcon_rumble.cpp @@ -0,0 +1,776 @@ +// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin and contributors. +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#include "negcon_rumble.h" +#include "IconsFontAwesome5.h" +#include "common/assert.h" +#include "common/log.h" +#include "host.h" +#include "settings.h" +#include "system.h" + +#include "util/imgui_manager.h" +#include "util/input_manager.h" +#include "util/state_wrapper.h" + +#include "common/bitutils.h" +#include "common/log.h" +#include "common/string_util.h" + +#include "IconsFontAwesome5.h" +#include "IconsPromptFont.h" + +#include + +Log_SetChannel(NeGconRumble); + + +// Mapping of Button to index of corresponding bit in m_button_state +static constexpr std::array(NeGconRumble::Button::Count)> s_button_indices = {3, 4, 5, 6, + 7, 11, 12, 13}; +NeGconRumble::NeGconRumble(u32 index) : Controller(index) +{ + m_status_byte = 0x5A; + m_axis_state.fill(0x00); + m_axis_state[static_cast(Axis::Steering)] = 0x80; + m_rumble_config.fill(0xFF); +} + +NeGconRumble::~NeGconRumble() = default; + +ControllerType NeGconRumble::GetType() const +{ + return ControllerType::NeGconRumble; +} +bool NeGconRumble::InAnalogMode() const +{ + return m_analog_mode; +} + +void NeGconRumble::Reset() +{ + m_command = Command::Idle; + m_command_step = 0; + m_rx_buffer.fill(0x00); + m_tx_buffer.fill(0x00); + m_analog_mode = false; + m_configuration_mode = false; + + for (u32 i = 0; i < NUM_MOTORS; i++) + { + if (m_motor_state[i] != 0) + SetMotorState(i, 0); + } + + m_dualshock_enabled = false; + ResetRumbleConfig(); + + m_status_byte = 0x5A; + + if (m_force_analog_on_reset) + { + if (g_settings.controller_disable_analog_mode_forcing || System::IsRunningUnknownGame()) + { + Host::AddIconOSDMessage( + fmt::format("Controller{}AnalogMode", m_index), ICON_FA_GAMEPAD, + TRANSLATE_STR("OSDMessage", + "Analog mode forcing is disabled by game settings. Controller will start in digital mode."), + 10.0f); + } + else + { + SetAnalogMode(true, false); + } + } +} + +bool NeGconRumble::DoState(StateWrapper& sw, bool apply_input_state) +{ + if (!Controller::DoState(sw, apply_input_state)) + return false; + + const bool old_analog_mode = m_analog_mode; + + sw.Do(&m_analog_mode); + sw.Do(&m_dualshock_enabled); + sw.Do(&m_configuration_mode); + sw.DoEx(&m_status_byte, 55, static_cast(0x5A)); + + u16 button_state = m_button_state; + sw.DoEx(&button_state, 44, static_cast(0xFFFF)); + if (apply_input_state) + m_button_state = button_state; + else + m_analog_mode = old_analog_mode; + + sw.Do(&m_command); + + sw.DoEx(&m_rumble_config, 45, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + sw.DoEx(&m_rumble_config_large_motor_index, 45, -1); + sw.DoEx(&m_rumble_config_small_motor_index, 45, -1); + sw.DoEx(&m_analog_toggle_queued, 45, false); + + MotorState motor_state = m_motor_state; + sw.Do(&motor_state); + + if (sw.IsReading()) + { + for (u8 i = 0; i < NUM_MOTORS; i++) + SetMotorState(i, motor_state[i]); + + if (old_analog_mode != m_analog_mode) + { + Host::AddIconOSDMessage(fmt::format("Controller{}AnalogMode", m_index), ICON_FA_GAMEPAD, + fmt::format(m_analog_mode ? + TRANSLATE_FS("AnalogController", "Controller {} switched to analog mode.") : + TRANSLATE_FS("AnalogController", "Controller {} switched to digital mode."), + m_index + 1u), + 5.0f); + } + } + return true; +} + +float NeGconRumble::GetBindState(u32 index) const +{ + if (index >= static_cast(Button::Count)) + { + const u32 sub_index = index - static_cast(Button::Count); + if (sub_index >= static_cast(m_half_axis_state.size())) + return 0.0f; + + return static_cast(m_half_axis_state[sub_index]) * (1.0f / 255.0f); + } + else if (index < static_cast(Button::Analog)) + { + return static_cast(((m_button_state >> index) & 1u) ^ 1u); + } + else + { + return 0.0f; + } +} + +void NeGconRumble::SetBindState(u32 index, float value) +{ + if (index == static_cast(Button::Analog)) + { + // analog toggle + if (value >= 0.5f) + { + if (m_command == Command::Idle) + ProcessAnalogModeToggle(); + else + m_analog_toggle_queued = true; + } + + return; + } + // Steering Axis: -1..1 -> 0..255 + else if (index == (static_cast(Button::Count) + static_cast(HalfAxis::SteeringLeft)) || + index == (static_cast(Button::Count) + static_cast(HalfAxis::SteeringRight))) + { + value *= m_steering_sensitivity; + if (value < m_steering_deadzone) + value = 0.0f; + + m_half_axis_state[index - static_cast(Button::Count)] = + static_cast(std::clamp(value * 255.0f, 0.0f, 255.0f)); + + // Merge left/right. Seems to be inverted. + m_axis_state[static_cast(Axis::Steering)] = + ((m_half_axis_state[1] != 0) ? (127u + ((m_half_axis_state[1] + 1u) / 2u)) : + (127u - (m_half_axis_state[0] / 2u))); + } + else if (index >= static_cast(Button::Count)) + { + // less one because of the two steering axes + const u32 sub_index = index - (static_cast(Button::Count) + 1); + if (sub_index >= m_axis_state.size()) + return; + + m_axis_state[sub_index] = static_cast(std::clamp(value * 255.0f, 0.0f, 255.0f)); + } + else if (index < static_cast(Button::Count)) + { + const u16 bit = u16(1) << s_button_indices[static_cast(index)]; + + if (value >= 0.5f) + { + if (m_button_state & bit) + System::SetRunaheadReplayFlag(); + + m_button_state &= ~bit; + } + else + { + if (!(m_button_state & bit)) + System::SetRunaheadReplayFlag(); + + m_button_state |= bit; + } + } +} + +u32 NeGconRumble::GetButtonStateBits() const +{ + return m_button_state ^ 0xFFFF; +} + +std::optional NeGconRumble::GetAnalogInputBytes() const +{ + return m_axis_state[static_cast(Axis::L)] << 24 | m_axis_state[static_cast(Axis::II)] << 16 | + m_axis_state[static_cast(Axis::I)] << 8 | m_axis_state[static_cast(Axis::Steering)]; +} + +void NeGconRumble::ResetTransferState() +{ + if (m_analog_toggle_queued) + { + ProcessAnalogModeToggle(); + m_analog_toggle_queued = false; + } + + m_command = Command::Idle; + m_command_step = 0; +} + +void NeGconRumble::SetAnalogMode(bool enabled, bool show_message) +{ + if (m_analog_mode == enabled) + return; + + Log_InfoPrintf("Controller %u switched to %s mode.", m_index + 1u, enabled ? "analog" : "digital"); + if (show_message) + { + Host::AddIconOSDMessage(fmt::format("Controller{}AnalogMode", m_index), ICON_FA_GAMEPAD, + fmt::format(enabled ? + TRANSLATE_FS("AnalogController", "Controller {} switched to analog mode.") : + TRANSLATE_FS("AnalogController", "Controller {} switched to digital mode."), + m_index + 1u), + 5.0f); + } + m_analog_mode = enabled; +} + +void NeGconRumble::ProcessAnalogModeToggle() +{ + if (m_analog_locked) + { + Host::AddIconOSDMessage( + fmt::format("Controller{}AnalogMode", m_index), ICON_FA_GAMEPAD, + fmt::format(m_analog_mode ? + TRANSLATE_FS("AnalogController", "Controller {} is locked to analog mode by the game.") : + TRANSLATE_FS("AnalogController", "Controller {} is locked to digital mode by the game."), + m_index + 1u), + 5.0f); + } + else + { + SetAnalogMode(!m_analog_mode, true); + ResetRumbleConfig(); + + if (m_dualshock_enabled) + m_status_byte = 0x00; + } +} + +void NeGconRumble::SetMotorState(u32 motor, u8 value) +{ + DebugAssert(motor < NUM_MOTORS); + if (m_motor_state[motor] != value) + { + m_motor_state[motor] = value; + UpdateHostVibration(); + } +} + +void NeGconRumble::UpdateHostVibration() +{ + std::array hvalues; + for (u32 motor = 0; motor < NUM_MOTORS; motor++) + { + // Curve from https://github.com/KrossX/Pokopom/blob/master/Pokopom/Input_XInput.cpp#L210 + const u8 state = m_motor_state[motor]; + const double x = static_cast(std::min(state + static_cast(m_rumble_bias), 255)); + const double strength = 0.006474549734772402 * std::pow(x, 3.0) - 1.258165252213538 * std::pow(x, 2.0) + + 156.82454281087692 * x + 3.637978807091713e-11; + + hvalues[motor] = (state != 0) ? static_cast(strength / 65535.0) : 0.0f; + } + + InputManager::SetPadVibrationIntensity(m_index, hvalues[0], hvalues[1]); +} + +u8 NeGconRumble::GetExtraButtonMaskLSB() const +{ + return 0xFF; +} + +void NeGconRumble::ResetRumbleConfig() +{ + m_rumble_config.fill(0xFF); + + m_rumble_config_large_motor_index = -1; + m_rumble_config_small_motor_index = -1; + + SetMotorState(LargeMotor, 0); + SetMotorState(SmallMotor, 0); +} + +void NeGconRumble::SetMotorStateForConfigIndex(int index, u8 value) +{ + if (m_rumble_config_small_motor_index == index) + SetMotorState(SmallMotor, ((value & 0x01) != 0) ? 255 : 0); + else if (m_rumble_config_large_motor_index == index) + SetMotorState(LargeMotor, value); +} + +u8 NeGconRumble::GetResponseNumHalfwords() const +{ + if (m_configuration_mode || m_analog_mode) + return 0x3; + + return (0x1); +} + +u8 NeGconRumble::GetModeID() const +{ + if (m_configuration_mode) + return 0xF; + + if (m_analog_mode) + return 0x2; + + return 0x4; +} + +u8 NeGconRumble::GetIDByte() const +{ + auto tteste = GetResponseNumHalfwords(); + return Truncate8((GetModeID() << 4) | GetResponseNumHalfwords()); +} + +bool NeGconRumble::Transfer(const u8 data_in, u8* data_out) +{ + bool ack; + m_rx_buffer[m_command_step] = data_in; + + switch (m_command) + { + case Command::Idle: + { + *data_out = 0xFF; + + if (data_in == 0x01) + { + Log_DebugPrintf("ACK controller access"); + m_command = Command::Ready; + return true; + } + + Log_DevPrintf("Unknown data_in = 0x%02X", data_in); + return false; + } + break; + + case Command::Ready: + { + if (data_in == 0x42) + { + Assert(m_command_step == 0); + m_response_length = (GetResponseNumHalfwords() + 1) * 2; + m_command = Command::ReadPad; + m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + } + else if (data_in == 0x43) + { + Assert(m_command_step == 0); + m_response_length = (GetResponseNumHalfwords() + 1) * 2; + m_command = Command::ConfigModeSetMode; + m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + } + else if (m_configuration_mode && data_in == 0x44) + { + Assert(m_command_step == 0); + m_response_length = (GetResponseNumHalfwords() + 1) * 2; + m_command = Command::SetAnalogMode; + m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + ResetRumbleConfig(); + } + else if (m_configuration_mode && data_in == 0x45) + { + Assert(m_command_step == 0); + m_response_length = (GetResponseNumHalfwords() + 1) * 2; + m_command = Command::GetAnalogMode; + m_tx_buffer = {GetIDByte(), m_status_byte, 0x01, 0x02, BoolToUInt8(m_analog_mode), 0x02, 0x01, 0x00}; + } + else if (m_configuration_mode && data_in == 0x46) + { + Assert(m_command_step == 0); + m_response_length = (GetResponseNumHalfwords() + 1) * 2; + m_command = Command::Command46; + m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + } + else if (m_configuration_mode && data_in == 0x47) + { + Assert(m_command_step == 0); + m_response_length = (GetResponseNumHalfwords() + 1) * 2; + m_command = Command::Command47; + m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00}; + } + else if (m_configuration_mode && data_in == 0x4C) + { + Assert(m_command_step == 0); + m_response_length = (GetResponseNumHalfwords() + 1) * 2; + m_command = Command::Command4C; + m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + } + else if (m_configuration_mode && data_in == 0x4D) + { + Assert(m_command_step == 0); + m_response_length = (GetResponseNumHalfwords() + 1) * 2; + m_command = Command::GetSetRumble; + m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + m_rumble_config_large_motor_index = -1; + m_rumble_config_small_motor_index = -1; + } + else + { + if (m_configuration_mode) + Log_ErrorPrintf("Unimplemented config mode command 0x%02X", data_in); + + *data_out = 0xFF; + return false; + } + } + break; + + case Command::ReadPad: + { + const int rumble_index = m_command_step - 2; + + switch (m_command_step) + { + case 2: + { + m_tx_buffer[m_command_step] = Truncate8(m_button_state) & GetExtraButtonMaskLSB(); + + if (m_dualshock_enabled) + SetMotorStateForConfigIndex(rumble_index, data_in); + } + break; + + case 3: + { + m_tx_buffer[m_command_step] = Truncate8(m_button_state >> 8); + + if (m_dualshock_enabled) + { + SetMotorStateForConfigIndex(rumble_index, data_in); + } + else + { + bool legacy_rumble_on = (m_rx_buffer[2] & 0xC0) == 0x40 && (m_rx_buffer[3] & 0x01) != 0; + SetMotorState(SmallMotor, legacy_rumble_on ? 255 : 0); + } + } + break; + + case 4: + { + if (m_configuration_mode || m_analog_mode) + m_tx_buffer[m_command_step] = m_axis_state[static_cast(Axis::Steering)]; + + if (m_dualshock_enabled) + SetMotorStateForConfigIndex(rumble_index, data_in); + } + break; + + case 5: + { + if (m_configuration_mode || m_analog_mode) + m_tx_buffer[m_command_step] = m_axis_state[static_cast(Axis::I)]; + + if (m_dualshock_enabled) + SetMotorStateForConfigIndex(rumble_index, data_in); + } + break; + + case 6: + { + if (m_configuration_mode || m_analog_mode) + m_tx_buffer[m_command_step] = m_axis_state[static_cast(Axis::II)]; + + if (m_dualshock_enabled) + SetMotorStateForConfigIndex(rumble_index, data_in); + } + break; + + case 7: + { + if (m_configuration_mode || m_analog_mode) + m_tx_buffer[m_command_step] = m_axis_state[static_cast(Axis::L)]; + + if (m_dualshock_enabled) + SetMotorStateForConfigIndex(rumble_index, data_in); + } + break; + + default: + { + } + break; + } + } + break; + + case Command::ConfigModeSetMode: + { + if (!m_configuration_mode) + { + switch (m_command_step) + { + case 2: + { + m_tx_buffer[m_command_step] = Truncate8(m_button_state) & GetExtraButtonMaskLSB(); + } + break; + + case 3: + { + m_tx_buffer[m_command_step] = Truncate8(m_button_state >> 8); + } + break; + + case 4: + { + if (m_configuration_mode || m_analog_mode) + m_tx_buffer[m_command_step] = m_axis_state[static_cast(Axis::Steering)]; + } + break; + + case 5: + { + if (m_configuration_mode || m_analog_mode) + m_tx_buffer[m_command_step] = m_axis_state[static_cast(Axis::I)]; + } + break; + + case 6: + { + if (m_configuration_mode || m_analog_mode) + m_tx_buffer[m_command_step] = m_axis_state[static_cast(Axis::II)]; + } + break; + + case 7: + { + if (m_configuration_mode || m_analog_mode) + m_tx_buffer[m_command_step] = m_axis_state[static_cast(Axis::L)]; + } + break; + + default: + { + } + break; + } + } + + if (m_command_step == (static_cast(m_response_length) - 1)) + { + m_configuration_mode = (m_rx_buffer[2] == 1); + + if (m_configuration_mode) + { + m_dualshock_enabled = true; + m_status_byte = 0x5A; + } + + Log_DevPrintf("0x%02x(%s) config mode", m_rx_buffer[2], m_configuration_mode ? "enter" : "leave"); + } + } + break; + + case Command::SetAnalogMode: + { + if (m_command_step == 2) + { + Log_DevPrintf("analog mode val 0x%02x", data_in); + + if (data_in == 0x00 || data_in == 0x01) + SetAnalogMode((data_in == 0x01), true); + } + else if (m_command_step == 3) + { + Log_DevPrintf("analog mode lock 0x%02x", data_in); + + if (data_in == 0x02 || data_in == 0x03) + m_analog_locked = (data_in == 0x03); + } + } + break; + + case Command::GetAnalogMode: + { + // Intentionally empty, analog mode byte is set in reply buffer when command is first received + } + break; + + case Command::Command46: + { + if (m_command_step == 2) + { + if (data_in == 0x00) + { + m_tx_buffer[4] = 0x01; + m_tx_buffer[5] = 0x02; + m_tx_buffer[6] = 0x00; + m_tx_buffer[7] = 0x0A; + } + else if (data_in == 0x01) + { + m_tx_buffer[4] = 0x01; + m_tx_buffer[5] = 0x01; + m_tx_buffer[6] = 0x01; + m_tx_buffer[7] = 0x14; + } + } + } + break; + + case Command::Command47: + { + if (m_command_step == 2 && data_in != 0x00) + { + m_tx_buffer[4] = 0x00; + m_tx_buffer[5] = 0x00; + m_tx_buffer[6] = 0x00; + m_tx_buffer[7] = 0x00; + } + } + break; + + case Command::Command4C: + { + if (m_command_step == 2) + { + if (data_in == 0x00) + m_tx_buffer[5] = 0x04; + else if (data_in == 0x01) + m_tx_buffer[5] = 0x02; + } + } + break; + + case Command::GetSetRumble: + { + int rumble_index = m_command_step - 2; + if (rumble_index >= 0) + { + m_tx_buffer[m_command_step] = m_rumble_config[rumble_index]; + m_rumble_config[rumble_index] = data_in; + + if (data_in == 0x00) + m_rumble_config_small_motor_index = rumble_index; + else if (data_in == 0x01) + m_rumble_config_large_motor_index = rumble_index; + } + + if (m_command_step == 7) + { + if (m_rumble_config_large_motor_index == -1) + SetMotorState(LargeMotor, 0); + + if (m_rumble_config_small_motor_index == -1) + SetMotorState(SmallMotor, 0); + } + } + break; + + DefaultCaseIsUnreachable(); + + } + + *data_out = m_tx_buffer[m_command_step]; + + m_command_step = (m_command_step + 1) % m_response_length; + ack = (m_command_step == 0) ? false : true; + + if (m_command_step == 0) + { + m_command = Command::Idle; + + Log_DebugPrintf("Rx: %02x %02x %02x %02x %02x %02x %02x %02x", m_rx_buffer[0], m_rx_buffer[1], m_rx_buffer[2], + m_rx_buffer[3], m_rx_buffer[4], m_rx_buffer[5], m_rx_buffer[6], m_rx_buffer[7]); + Log_DebugPrintf("Tx: %02x %02x %02x %02x %02x %02x %02x %02x", m_tx_buffer[0], m_tx_buffer[1], m_tx_buffer[2], + m_tx_buffer[3], m_tx_buffer[4], m_tx_buffer[5], m_tx_buffer[6], m_tx_buffer[7]); + + m_rx_buffer.fill(0x00); + m_tx_buffer.fill(0x00); + } + + return ack; + +} + +std::unique_ptr NeGconRumble::Create(u32 index) +{ + return std::make_unique(index); +} + +static const Controller::ControllerBindingInfo s_binding_info[] = { +#define BUTTON(name, display_name, icon_name, button, genb) \ + { \ + name, display_name, icon_name, static_cast(button), InputBindingInfo::Type::Button, genb \ + } +#define AXIS(name, display_name, icon_name, halfaxis, genb) \ + { \ + name, display_name, icon_name, static_cast(NeGconRumble::Button::Count) + static_cast(halfaxis), \ + InputBindingInfo::Type::HalfAxis, genb \ + } + + // clang-format off + BUTTON("Up", TRANSLATE_NOOP("NeGconRumble", "D-Pad Up"), ICON_PF_DPAD_UP, NeGconRumble::Button::Up, GenericInputBinding::DPadUp), + BUTTON("Right", TRANSLATE_NOOP("NeGconRumble", "D-Pad Right"), ICON_PF_DPAD_RIGHT, NeGconRumble::Button::Right, GenericInputBinding::DPadRight), + BUTTON("Down", TRANSLATE_NOOP("NeGconRumble", "D-Pad Down"), ICON_PF_DPAD_DOWN, NeGconRumble::Button::Down, GenericInputBinding::DPadDown), + BUTTON("Left", TRANSLATE_NOOP("NeGconRumble", "D-Pad Left"), ICON_PF_DPAD_LEFT, NeGconRumble::Button::Left, GenericInputBinding::DPadLeft), + BUTTON("Start", TRANSLATE_NOOP("NeGconRumble", "Start"),ICON_PF_START, NeGconRumble::Button::Start, GenericInputBinding::Start), + BUTTON("A", TRANSLATE_NOOP("NeGconRumble", "A Button"), ICON_PF_BUTTON_A, NeGconRumble::Button::A, GenericInputBinding::Circle), + BUTTON("B", TRANSLATE_NOOP("NeGconRumble", "B Button"), ICON_PF_BUTTON_B, NeGconRumble::Button::B, GenericInputBinding::Triangle), + AXIS("I", TRANSLATE_NOOP("NeGconRumble", "I Button"), ICON_PF_RIGHT_TRIGGER_R2, NeGconRumble::HalfAxis::I, GenericInputBinding::R2), + AXIS("II", TRANSLATE_NOOP("NeGconRumble", "II Button"), ICON_PF_LEFT_TRIGGER_L2, NeGconRumble::HalfAxis::II, GenericInputBinding::L2), + AXIS("L", TRANSLATE_NOOP("NeGconRumble", "Left Trigger"), ICON_PF_LEFT_ANALOG_LEFT, NeGconRumble::HalfAxis::L, GenericInputBinding::L1), + BUTTON("R", TRANSLATE_NOOP("NeGconRumble", "Right Trigger"), ICON_PF_RIGHT_SHOULDER_R1, NeGconRumble::Button::R, GenericInputBinding::R1), + AXIS("SteeringLeft", TRANSLATE_NOOP("NeGconRumble", "Steering (Twist) Left"), ICON_PF_LEFT_ANALOG_LEFT, NeGconRumble::HalfAxis::SteeringLeft, GenericInputBinding::LeftStickLeft), + AXIS("SteeringRight", TRANSLATE_NOOP("NeGconRumble", "Steering (Twist) Right"), ICON_PF_LEFT_ANALOG_LEFT, NeGconRumble::HalfAxis::SteeringRight, GenericInputBinding::LeftStickRight), + BUTTON("Analog", TRANSLATE_NOOP("NeGconRumble", "Analog Toggle"), ICON_PF_ANALOG_LEFT_RIGHT, NeGconRumble::Button::Analog, GenericInputBinding::System), +// clang-format on + +#undef AXIS +#undef BUTTON +}; + +static const SettingInfo s_settings[] = { + {SettingInfo::Type::Float, "SteeringDeadzone", TRANSLATE_NOOP("NeGconRumble", "Steering Axis Deadzone"), + TRANSLATE_NOOP("NeGconRumble", "Sets deadzone size for steering axis."), "0.00f", "0.00f", "0.99f", "0.01f", "%.0f%%", nullptr, + 100.0f}, + {SettingInfo::Type::Float, "SteeringSensitivity", TRANSLATE_NOOP("NeGconRumble", "Steering Axis Sensitivity"), + TRANSLATE_NOOP("NeGconRumble", "Sets the steering axis scaling factor."), "1.00f", "0.01f", "2.00f", "0.01f", "%.0f%%", + nullptr, 100.0f}, +}; + +const Controller::ControllerInfo NeGconRumble::INFO = { + ControllerType::NeGconRumble, "NeGconRumble", TRANSLATE_NOOP("ControllerType", "NeGconRumble"), ICON_PF_GAMEPAD, + s_binding_info, s_settings, Controller::VibrationCapabilities::LargeSmallMotors}; + +void NeGconRumble::LoadSettings(SettingsInterface& si, const char* section) +{ + Controller::LoadSettings(si, section); + m_steering_deadzone = si.GetFloatValue(section, "SteeringDeadzone", 0.10f); + m_steering_sensitivity = si.GetFloatValue(section, "SteeringSensitivity", 1.00f); + m_rumble_bias = static_cast(std::min(si.GetIntValue(section, "VibrationBias", 8), 255)); +} \ No newline at end of file diff --git a/src/core/negcon_rumble.h b/src/core/negcon_rumble.h new file mode 100644 index 000000000..14e0868a7 --- /dev/null +++ b/src/core/negcon_rumble.h @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin and contributors. +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#pragma once +#include "controller.h" +#include +#include +#include +#include + +class NeGconRumble final : public Controller +{ +public: + enum class Axis : u8 + { + Steering = 0, + I = 1, + II = 2, + L = 3, + Count + }; + + enum class Button : u8 + { + Start = 0, + Up = 1, + Right = 2, + Down = 3, + Left = 4, + R = 5, + B = 6, + A = 7, + Analog = 8, + Count + }; + + enum class HalfAxis : u8 + { + SteeringLeft, + SteeringRight, + I, + II, + L, + Count + }; + + static constexpr u8 NUM_MOTORS = 2; + + static const Controller::ControllerInfo INFO; + + NeGconRumble(u32 index); + ~NeGconRumble() override; + + static std::unique_ptr Create(u32 index); + + ControllerType GetType() const override; + bool InAnalogMode() const override; + + void Reset() override; + bool DoState(StateWrapper& sw, bool apply_input_state) override; + + float GetBindState(u32 index) const override; + void SetBindState(u32 index, float value) override; + + void ResetTransferState() override; + bool Transfer(const u8 data_in, u8* data_out) override; + + u32 GetButtonStateBits() const override; + std::optional GetAnalogInputBytes() const override; + + void LoadSettings(SettingsInterface& si, const char* section) override; + +private: + using MotorState = std::array; + + enum class Command : u8 + { + Idle, + Ready, + ReadPad, // 0x42 + ConfigModeSetMode, // 0x43 + SetAnalogMode, // 0x44 + GetAnalogMode, // 0x45 + Command46, // 0x46 + Command47, // 0x47 + Command4C, // 0x4C + GetSetRumble // 0x4D + }; + + bool m_force_analog_on_reset = true; + bool m_analog_dpad_in_digital_mode = false; + float m_analog_deadzone = 0.0f; + float m_analog_sensitivity = 1.33f; + float m_button_deadzone = 0.0f; + u8 m_rumble_bias = 8; + u8 m_invert_left_stick = 0; + u8 m_invert_right_stick = 0; + + bool m_analog_mode = false; + bool m_analog_locked = false; + bool m_dualshock_enabled = false; + bool m_configuration_mode = false; + + std::array(Axis::Count)> m_axis_state{}; + + enum : u8 + { + LargeMotor = 0, + SmallMotor = 1 + }; + + // steering, merged to m_axis_state + std::array m_half_axis_state{}; + + // buttons are active low; bits 0-2, 8-10, 14-15 are not used and are always high + u16 m_button_state = UINT16_C(0xFFFF); + + MotorState m_motor_state{}; + + Command m_command = Command::Idle; + int m_command_step = 0; + + // Transmit and receive buffers, not including the first Hi-Z/ack response byte + static constexpr u32 MAX_RESPONSE_LENGTH = 8; + std::array m_rx_buffer; + std::array m_tx_buffer; + u32 m_response_length = 0; + + std::array m_rumble_config{}; + int m_rumble_config_large_motor_index = -1; + int m_rumble_config_small_motor_index = -1; + + bool m_analog_toggle_queued = false; + u8 m_status_byte = 0; + + // Get number of response halfwords (excluding the initial controller info halfword) + u8 GetResponseNumHalfwords() const; + + u8 GetModeID() const; + u8 GetIDByte() const; + + void SetAnalogMode(bool enabled, bool show_message); + void ProcessAnalogModeToggle(); + void SetMotorState(u32 motor, u8 value); + void UpdateHostVibration(); + u8 GetExtraButtonMaskLSB() const; + void ResetRumbleConfig(); + void SetMotorStateForConfigIndex(int index, u8 value); + + float m_steering_deadzone = 0.00f; + float m_steering_sensitivity = 1.00f; +}; \ No newline at end of file diff --git a/src/core/settings.cpp b/src/core/settings.cpp index a4344d669..8ad8ff3e6 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -1629,7 +1629,7 @@ const char* Settings::GetAudioBackendDisplayName(AudioBackend backend) } static constexpr const std::array s_controller_type_names = { - "None", "DigitalController", "AnalogController", "AnalogJoystick", "GunCon", "PlayStationMouse", "NeGcon"}; + "None", "DigitalController", "AnalogController", "AnalogJoystick", "GunCon", "PlayStationMouse", "NeGcon", "NeGconRumble"}; static constexpr const std::array s_controller_display_names = { TRANSLATE_NOOP("ControllerType", "None"), TRANSLATE_NOOP("ControllerType", "Digital Controller"), @@ -1637,7 +1637,8 @@ static constexpr const std::array s_controller_display_names = { TRANSLATE_NOOP("ControllerType", "Analog Joystick"), TRANSLATE_NOOP("ControllerType", "GunCon"), TRANSLATE_NOOP("ControllerType", "PlayStation Mouse"), - TRANSLATE_NOOP("ControllerType", "NeGcon")}; + TRANSLATE_NOOP("ControllerType", "NeGcon"), + TRANSLATE_NOOP("ControllerType", "NeGcon Rumble")}; std::optional Settings::ParseControllerTypeName(std::string_view str) { diff --git a/src/core/types.h b/src/core/types.h index e3d5d337f..8f51e3e13 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -213,6 +213,7 @@ enum class ControllerType GunCon, PlayStationMouse, NeGcon, + NeGconRumble, Count };