duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

negcon_rumble.cpp (24241B)


      1 // SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com> and contributors.
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "negcon_rumble.h"
      5 #include "IconsFontAwesome5.h"
      6 #include "common/assert.h"
      7 #include "common/log.h"
      8 #include "host.h"
      9 #include "settings.h"
     10 #include "system.h"
     11 
     12 #include "util/imgui_manager.h"
     13 #include "util/input_manager.h"
     14 #include "util/state_wrapper.h"
     15 
     16 #include "common/bitutils.h"
     17 #include "common/log.h"
     18 #include "common/string_util.h"
     19 
     20 #include "IconsFontAwesome5.h"
     21 #include "IconsPromptFont.h"
     22 
     23 #include <cmath>
     24 
     25 Log_SetChannel(NeGconRumble);
     26 
     27 // Mapping of Button to index of corresponding bit in m_button_state
     28 static constexpr std::array<u8, static_cast<size_t>(NeGconRumble::Button::Count)> s_button_indices = {3, 4,  5,  6,
     29                                                                                                       7, 11, 12, 13};
     30 NeGconRumble::NeGconRumble(u32 index) : Controller(index)
     31 {
     32   m_status_byte = 0x5A;
     33   m_axis_state.fill(0x00);
     34   m_axis_state[static_cast<u8>(Axis::Steering)] = 0x80;
     35   m_rumble_config.fill(0xFF);
     36 }
     37 
     38 NeGconRumble::~NeGconRumble() = default;
     39 
     40 ControllerType NeGconRumble::GetType() const
     41 {
     42   return ControllerType::NeGconRumble;
     43 }
     44 bool NeGconRumble::InAnalogMode() const
     45 {
     46   return m_analog_mode;
     47 }
     48 
     49 void NeGconRumble::Reset()
     50 {
     51   m_command = Command::Idle;
     52   m_command_step = 0;
     53   m_rx_buffer.fill(0x00);
     54   m_tx_buffer.fill(0x00);
     55   m_analog_mode = false;
     56   m_configuration_mode = false;
     57 
     58   for (u32 i = 0; i < NUM_MOTORS; i++)
     59   {
     60     if (m_motor_state[i] != 0)
     61       SetMotorState(i, 0);
     62   }
     63 
     64   m_dualshock_enabled = false;
     65   ResetRumbleConfig();
     66 
     67   m_status_byte = 0x5A;
     68 
     69   if (m_force_analog_on_reset)
     70   {
     71     if (!CanStartInAnalogMode(ControllerType::AnalogController))
     72     {
     73       Host::AddIconOSDMessage(
     74         fmt::format("Controller{}AnalogMode", m_index), ICON_FA_GAMEPAD,
     75         TRANSLATE_STR("OSDMessage",
     76                       "Analog mode forcing is disabled by game settings. Controller will start in digital mode."),
     77         10.0f);
     78     }
     79     else
     80     {
     81       SetAnalogMode(true, false);
     82     }
     83   }
     84 }
     85 
     86 bool NeGconRumble::DoState(StateWrapper& sw, bool apply_input_state)
     87 {
     88   if (!Controller::DoState(sw, apply_input_state))
     89     return false;
     90 
     91   const bool old_analog_mode = m_analog_mode;
     92 
     93   sw.Do(&m_analog_mode);
     94   sw.Do(&m_dualshock_enabled);
     95   sw.Do(&m_configuration_mode);
     96   sw.DoEx(&m_status_byte, 55, static_cast<u8>(0x5A));
     97 
     98   u16 button_state = m_button_state;
     99   sw.DoEx(&button_state, 44, static_cast<u16>(0xFFFF));
    100   if (apply_input_state)
    101     m_button_state = button_state;
    102   else
    103     m_analog_mode = old_analog_mode;
    104 
    105   sw.Do(&m_command);
    106 
    107   sw.DoEx(&m_rumble_config, 45, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
    108   sw.DoEx(&m_rumble_config_large_motor_index, 45, -1);
    109   sw.DoEx(&m_rumble_config_small_motor_index, 45, -1);
    110   sw.DoEx(&m_analog_toggle_queued, 45, false);
    111 
    112   MotorState motor_state = m_motor_state;
    113   sw.Do(&motor_state);
    114 
    115   if (sw.IsReading())
    116   {
    117     for (u8 i = 0; i < NUM_MOTORS; i++)
    118       SetMotorState(i, motor_state[i]);
    119 
    120     if (old_analog_mode != m_analog_mode)
    121     {
    122       Host::AddIconOSDMessage(fmt::format("Controller{}AnalogMode", m_index), ICON_FA_GAMEPAD,
    123                               fmt::format(m_analog_mode ?
    124                                             TRANSLATE_FS("AnalogController", "Controller {} switched to analog mode.") :
    125                                             TRANSLATE_FS("AnalogController", "Controller {} switched to digital mode."),
    126                                           m_index + 1u),
    127                               5.0f);
    128     }
    129   }
    130   return true;
    131 }
    132 
    133 float NeGconRumble::GetBindState(u32 index) const
    134 {
    135   if (index >= static_cast<u32>(Button::Count))
    136   {
    137     const u32 sub_index = index - static_cast<u32>(Button::Count);
    138     if (sub_index >= static_cast<u32>(m_half_axis_state.size()))
    139       return 0.0f;
    140 
    141     return static_cast<float>(m_half_axis_state[sub_index]) * (1.0f / 255.0f);
    142   }
    143   else if (index < static_cast<u32>(Button::Analog))
    144   {
    145     return static_cast<float>(((m_button_state >> index) & 1u) ^ 1u);
    146   }
    147   else
    148   {
    149     return 0.0f;
    150   }
    151 }
    152 
    153 void NeGconRumble::SetBindState(u32 index, float value)
    154 {
    155   if (index == static_cast<s32>(Button::Analog))
    156   {
    157     // analog toggle
    158     if (value >= 0.5f)
    159     {
    160       if (m_command == Command::Idle)
    161         ProcessAnalogModeToggle();
    162       else
    163         m_analog_toggle_queued = true;
    164     }
    165 
    166     return;
    167   }
    168   // Steering Axis: -1..1 -> 0..255
    169   else if (index == (static_cast<u32>(Button::Count) + static_cast<u32>(HalfAxis::SteeringLeft)) ||
    170            index == (static_cast<u32>(Button::Count) + static_cast<u32>(HalfAxis::SteeringRight)))
    171   {
    172     value *= m_steering_sensitivity;
    173     if (value < m_steering_deadzone)
    174       value = 0.0f;
    175 
    176     m_half_axis_state[index - static_cast<u32>(Button::Count)] =
    177       static_cast<u8>(std::clamp(value * 255.0f, 0.0f, 255.0f));
    178 
    179     // Merge left/right. Seems to be inverted.
    180     m_axis_state[static_cast<u32>(Axis::Steering)] =
    181       ((m_half_axis_state[1] != 0) ? (127u + ((m_half_axis_state[1] + 1u) / 2u)) :
    182                                      (127u - (m_half_axis_state[0] / 2u)));
    183   }
    184   else if (index >= static_cast<u32>(Button::Count))
    185   {
    186     // less one because of the two steering axes
    187     const u32 sub_index = index - (static_cast<u32>(Button::Count) + 1);
    188     if (sub_index >= m_axis_state.size())
    189       return;
    190 
    191     m_axis_state[sub_index] = static_cast<u8>(std::clamp(value * 255.0f, 0.0f, 255.0f));
    192   }
    193   else if (index < static_cast<u32>(Button::Count))
    194   {
    195     const u16 bit = u16(1) << s_button_indices[static_cast<u8>(index)];
    196 
    197     if (value >= 0.5f)
    198     {
    199       if (m_button_state & bit)
    200         System::SetRunaheadReplayFlag();
    201 
    202       m_button_state &= ~bit;
    203     }
    204     else
    205     {
    206       if (!(m_button_state & bit))
    207         System::SetRunaheadReplayFlag();
    208 
    209       m_button_state |= bit;
    210     }
    211   }
    212 }
    213 
    214 u32 NeGconRumble::GetButtonStateBits() const
    215 {
    216   return m_button_state ^ 0xFFFF;
    217 }
    218 
    219 std::optional<u32> NeGconRumble::GetAnalogInputBytes() const
    220 {
    221   return m_axis_state[static_cast<size_t>(Axis::L)] << 24 | m_axis_state[static_cast<size_t>(Axis::II)] << 16 |
    222          m_axis_state[static_cast<size_t>(Axis::I)] << 8 | m_axis_state[static_cast<size_t>(Axis::Steering)];
    223 }
    224 
    225 void NeGconRumble::ResetTransferState()
    226 {
    227   if (m_analog_toggle_queued)
    228   {
    229     ProcessAnalogModeToggle();
    230     m_analog_toggle_queued = false;
    231   }
    232 
    233   m_command = Command::Idle;
    234   m_command_step = 0;
    235 }
    236 
    237 void NeGconRumble::SetAnalogMode(bool enabled, bool show_message)
    238 {
    239   if (m_analog_mode == enabled)
    240     return;
    241 
    242   INFO_LOG("Controller {} switched to {} mode.", m_index + 1u, m_analog_mode ? "analog" : "digital");
    243   if (show_message)
    244   {
    245     Host::AddIconOSDMessage(
    246       fmt::format("analog_mode_toggle_{}", m_index), ICON_FA_GAMEPAD,
    247       enabled ? fmt::format(TRANSLATE_FS("Controller", "Controller {} switched to analog mode."), m_index + 1u) :
    248                 fmt::format(TRANSLATE_FS("Controller", "Controller {} switched to digital mode."), m_index + 1u));
    249   }
    250 
    251   m_analog_mode = enabled;
    252 }
    253 
    254 void NeGconRumble::ProcessAnalogModeToggle()
    255 {
    256   if (m_analog_locked)
    257   {
    258     Host::AddIconOSDMessage(
    259       fmt::format("Controller{}AnalogMode", m_index), ICON_FA_GAMEPAD,
    260       fmt::format(m_analog_mode ?
    261                     TRANSLATE_FS("AnalogController", "Controller {} is locked to analog mode by the game.") :
    262                     TRANSLATE_FS("AnalogController", "Controller {} is locked to digital mode by the game."),
    263                   m_index + 1u),
    264       5.0f);
    265   }
    266   else
    267   {
    268     SetAnalogMode(!m_analog_mode, true);
    269     ResetRumbleConfig();
    270 
    271     if (m_dualshock_enabled)
    272       m_status_byte = 0x00;
    273   }
    274 }
    275 
    276 void NeGconRumble::SetMotorState(u32 motor, u8 value)
    277 {
    278   DebugAssert(motor < NUM_MOTORS);
    279   if (m_motor_state[motor] != value)
    280   {
    281     m_motor_state[motor] = value;
    282     UpdateHostVibration();
    283   }
    284 }
    285 
    286 void NeGconRumble::UpdateHostVibration()
    287 {
    288   std::array<float, NUM_MOTORS> hvalues;
    289   for (u32 motor = 0; motor < NUM_MOTORS; motor++)
    290   {
    291     // Curve from https://github.com/KrossX/Pokopom/blob/master/Pokopom/Input_XInput.cpp#L210
    292     const u8 state = m_motor_state[motor];
    293     const double x = static_cast<double>(std::min<u32>(state + static_cast<u32>(m_rumble_bias), 255));
    294     const double strength = 0.006474549734772402 * std::pow(x, 3.0) - 1.258165252213538 * std::pow(x, 2.0) +
    295                             156.82454281087692 * x + 3.637978807091713e-11;
    296 
    297     hvalues[motor] = (state != 0) ? static_cast<float>(strength / 65535.0) : 0.0f;
    298   }
    299 
    300   InputManager::SetPadVibrationIntensity(m_index, hvalues[0], hvalues[1]);
    301 }
    302 
    303 u8 NeGconRumble::GetExtraButtonMaskLSB() const
    304 {
    305   return 0xFF;
    306 }
    307 
    308 void NeGconRumble::ResetRumbleConfig()
    309 {
    310   m_rumble_config.fill(0xFF);
    311 
    312   m_rumble_config_large_motor_index = -1;
    313   m_rumble_config_small_motor_index = -1;
    314 
    315   SetMotorState(LargeMotor, 0);
    316   SetMotorState(SmallMotor, 0);
    317 }
    318 
    319 void NeGconRumble::SetMotorStateForConfigIndex(int index, u8 value)
    320 {
    321   if (m_rumble_config_small_motor_index == index)
    322     SetMotorState(SmallMotor, ((value & 0x01) != 0) ? 255 : 0);
    323   else if (m_rumble_config_large_motor_index == index)
    324     SetMotorState(LargeMotor, value);
    325 }
    326 
    327 u8 NeGconRumble::GetResponseNumHalfwords() const
    328 {
    329   if (m_configuration_mode || m_analog_mode)
    330     return 0x3;
    331 
    332   return (0x1);
    333 }
    334 
    335 u8 NeGconRumble::GetModeID() const
    336 {
    337   if (m_configuration_mode)
    338     return 0xF;
    339 
    340   if (m_analog_mode)
    341     return 0x2;
    342 
    343   return 0x4;
    344 }
    345 
    346 u8 NeGconRumble::GetIDByte() const
    347 {
    348   return Truncate8((GetModeID() << 4) | GetResponseNumHalfwords());
    349 }
    350 
    351 bool NeGconRumble::Transfer(const u8 data_in, u8* data_out)
    352 {
    353   bool ack;
    354   m_rx_buffer[m_command_step] = data_in;
    355 
    356   switch (m_command)
    357   {
    358     case Command::Idle:
    359     {
    360       *data_out = 0xFF;
    361 
    362       if (data_in == 0x01)
    363       {
    364         DEBUG_LOG("ACK controller access");
    365         m_command = Command::Ready;
    366         return true;
    367       }
    368 
    369       DEV_LOG("Unknown data_in = 0x{:02X}", data_in);
    370       return false;
    371     }
    372     break;
    373 
    374     case Command::Ready:
    375     {
    376       if (data_in == 0x42)
    377       {
    378         Assert(m_command_step == 0);
    379         m_response_length = (GetResponseNumHalfwords() + 1) * 2;
    380         m_command = Command::ReadPad;
    381         m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    382       }
    383       else if (data_in == 0x43)
    384       {
    385         Assert(m_command_step == 0);
    386         m_response_length = (GetResponseNumHalfwords() + 1) * 2;
    387         m_command = Command::ConfigModeSetMode;
    388         m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    389       }
    390       else if (m_configuration_mode && data_in == 0x44)
    391       {
    392         Assert(m_command_step == 0);
    393         m_response_length = (GetResponseNumHalfwords() + 1) * 2;
    394         m_command = Command::SetAnalogMode;
    395         m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    396 
    397         ResetRumbleConfig();
    398       }
    399       else if (m_configuration_mode && data_in == 0x45)
    400       {
    401         Assert(m_command_step == 0);
    402         m_response_length = (GetResponseNumHalfwords() + 1) * 2;
    403         m_command = Command::GetAnalogMode;
    404         m_tx_buffer = {GetIDByte(), m_status_byte, 0x01, 0x02, BoolToUInt8(m_analog_mode), 0x02, 0x01, 0x00};
    405       }
    406       else if (m_configuration_mode && data_in == 0x46)
    407       {
    408         Assert(m_command_step == 0);
    409         m_response_length = (GetResponseNumHalfwords() + 1) * 2;
    410         m_command = Command::Command46;
    411         m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    412       }
    413       else if (m_configuration_mode && data_in == 0x47)
    414       {
    415         Assert(m_command_step == 0);
    416         m_response_length = (GetResponseNumHalfwords() + 1) * 2;
    417         m_command = Command::Command47;
    418         m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00};
    419       }
    420       else if (m_configuration_mode && data_in == 0x4C)
    421       {
    422         Assert(m_command_step == 0);
    423         m_response_length = (GetResponseNumHalfwords() + 1) * 2;
    424         m_command = Command::Command4C;
    425         m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    426       }
    427       else if (m_configuration_mode && data_in == 0x4D)
    428       {
    429         Assert(m_command_step == 0);
    430         m_response_length = (GetResponseNumHalfwords() + 1) * 2;
    431         m_command = Command::GetSetRumble;
    432         m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    433 
    434         m_rumble_config_large_motor_index = -1;
    435         m_rumble_config_small_motor_index = -1;
    436       }
    437       else
    438       {
    439         if (m_configuration_mode)
    440           ERROR_LOG("Unimplemented config mode command 0x{:02X}", data_in);
    441 
    442         *data_out = 0xFF;
    443         return false;
    444       }
    445     }
    446     break;
    447 
    448     case Command::ReadPad:
    449     {
    450       const int rumble_index = m_command_step - 2;
    451 
    452       switch (m_command_step)
    453       {
    454         case 2:
    455         {
    456           m_tx_buffer[m_command_step] = Truncate8(m_button_state) & GetExtraButtonMaskLSB();
    457 
    458           if (m_dualshock_enabled)
    459             SetMotorStateForConfigIndex(rumble_index, data_in);
    460         }
    461         break;
    462 
    463         case 3:
    464         {
    465           m_tx_buffer[m_command_step] = Truncate8(m_button_state >> 8);
    466 
    467           if (m_dualshock_enabled)
    468           {
    469             SetMotorStateForConfigIndex(rumble_index, data_in);
    470           }
    471           else
    472           {
    473             bool legacy_rumble_on = (m_rx_buffer[2] & 0xC0) == 0x40 && (m_rx_buffer[3] & 0x01) != 0;
    474             SetMotorState(SmallMotor, legacy_rumble_on ? 255 : 0);
    475           }
    476         }
    477         break;
    478 
    479         case 4:
    480         {
    481           if (m_configuration_mode || m_analog_mode)
    482             m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::Steering)];
    483 
    484           if (m_dualshock_enabled)
    485             SetMotorStateForConfigIndex(rumble_index, data_in);
    486         }
    487         break;
    488 
    489         case 5:
    490         {
    491           if (m_configuration_mode || m_analog_mode)
    492             m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::I)];
    493 
    494           if (m_dualshock_enabled)
    495             SetMotorStateForConfigIndex(rumble_index, data_in);
    496         }
    497         break;
    498 
    499         case 6:
    500         {
    501           if (m_configuration_mode || m_analog_mode)
    502             m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::II)];
    503 
    504           if (m_dualshock_enabled)
    505             SetMotorStateForConfigIndex(rumble_index, data_in);
    506         }
    507         break;
    508 
    509         case 7:
    510         {
    511           if (m_configuration_mode || m_analog_mode)
    512             m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::L)];
    513 
    514           if (m_dualshock_enabled)
    515             SetMotorStateForConfigIndex(rumble_index, data_in);
    516         }
    517         break;
    518 
    519         default:
    520         {
    521         }
    522         break;
    523       }
    524     }
    525     break;
    526 
    527     case Command::ConfigModeSetMode:
    528     {
    529       if (!m_configuration_mode)
    530       {
    531         switch (m_command_step)
    532         {
    533           case 2:
    534           {
    535             m_tx_buffer[m_command_step] = Truncate8(m_button_state) & GetExtraButtonMaskLSB();
    536           }
    537           break;
    538 
    539           case 3:
    540           {
    541             m_tx_buffer[m_command_step] = Truncate8(m_button_state >> 8);
    542           }
    543           break;
    544 
    545           case 4:
    546           {
    547             if (m_configuration_mode || m_analog_mode)
    548               m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::Steering)];
    549           }
    550           break;
    551 
    552           case 5:
    553           {
    554             if (m_configuration_mode || m_analog_mode)
    555               m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::I)];
    556           }
    557           break;
    558 
    559           case 6:
    560           {
    561             if (m_configuration_mode || m_analog_mode)
    562               m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::II)];
    563           }
    564           break;
    565 
    566           case 7:
    567           {
    568             if (m_configuration_mode || m_analog_mode)
    569               m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::L)];
    570           }
    571           break;
    572 
    573           default:
    574           {
    575           }
    576           break;
    577         }
    578       }
    579 
    580       if (m_command_step == (static_cast<s32>(m_response_length) - 1))
    581       {
    582         m_configuration_mode = (m_rx_buffer[2] == 1);
    583 
    584         if (m_configuration_mode)
    585         {
    586           m_dualshock_enabled = true;
    587           m_status_byte = 0x5A;
    588         }
    589 
    590         DEV_LOG("0x{:02x}({}) config mode", m_rx_buffer[2], m_configuration_mode ? "enter" : "leave");
    591       }
    592     }
    593     break;
    594 
    595     case Command::SetAnalogMode:
    596     {
    597       if (m_command_step == 2)
    598       {
    599         DEV_LOG("analog mode val 0x{:02x}", data_in);
    600 
    601         if (data_in == 0x00 || data_in == 0x01)
    602           SetAnalogMode((data_in == 0x01), true);
    603       }
    604       else if (m_command_step == 3)
    605       {
    606         DEV_LOG("analog mode lock 0x{:02x}", data_in);
    607 
    608         if (data_in == 0x02 || data_in == 0x03)
    609           m_analog_locked = (data_in == 0x03);
    610       }
    611     }
    612     break;
    613 
    614     case Command::GetAnalogMode:
    615     {
    616       // Intentionally empty, analog mode byte is set in reply buffer when command is first received
    617     }
    618     break;
    619 
    620     case Command::Command46:
    621     {
    622       if (m_command_step == 2)
    623       {
    624         if (data_in == 0x00)
    625         {
    626           m_tx_buffer[4] = 0x01;
    627           m_tx_buffer[5] = 0x02;
    628           m_tx_buffer[6] = 0x00;
    629           m_tx_buffer[7] = 0x0A;
    630         }
    631         else if (data_in == 0x01)
    632         {
    633           m_tx_buffer[4] = 0x01;
    634           m_tx_buffer[5] = 0x01;
    635           m_tx_buffer[6] = 0x01;
    636           m_tx_buffer[7] = 0x14;
    637         }
    638       }
    639     }
    640     break;
    641 
    642     case Command::Command47:
    643     {
    644       if (m_command_step == 2 && data_in != 0x00)
    645       {
    646         m_tx_buffer[4] = 0x00;
    647         m_tx_buffer[5] = 0x00;
    648         m_tx_buffer[6] = 0x00;
    649         m_tx_buffer[7] = 0x00;
    650       }
    651     }
    652     break;
    653 
    654     case Command::Command4C:
    655     {
    656       if (m_command_step == 2)
    657       {
    658         if (data_in == 0x00)
    659           m_tx_buffer[5] = 0x04;
    660         else if (data_in == 0x01)
    661           m_tx_buffer[5] = 0x02;
    662       }
    663     }
    664     break;
    665 
    666     case Command::GetSetRumble:
    667     {
    668       int rumble_index = m_command_step - 2;
    669       if (rumble_index >= 0)
    670       {
    671         m_tx_buffer[m_command_step] = m_rumble_config[rumble_index];
    672         m_rumble_config[rumble_index] = data_in;
    673 
    674         if (data_in == 0x00)
    675           m_rumble_config_small_motor_index = rumble_index;
    676         else if (data_in == 0x01)
    677           m_rumble_config_large_motor_index = rumble_index;
    678       }
    679 
    680       if (m_command_step == 7)
    681       {
    682         if (m_rumble_config_large_motor_index == -1)
    683           SetMotorState(LargeMotor, 0);
    684 
    685         if (m_rumble_config_small_motor_index == -1)
    686           SetMotorState(SmallMotor, 0);
    687       }
    688     }
    689     break;
    690 
    691       DefaultCaseIsUnreachable();
    692   }
    693 
    694   *data_out = m_tx_buffer[m_command_step];
    695 
    696   m_command_step = (m_command_step + 1) % m_response_length;
    697   ack = (m_command_step == 0) ? false : true;
    698 
    699   if (m_command_step == 0)
    700   {
    701     m_command = Command::Idle;
    702 
    703     DEBUG_LOG("Rx: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", m_rx_buffer[0], m_rx_buffer[1],
    704               m_rx_buffer[2], m_rx_buffer[3], m_rx_buffer[4], m_rx_buffer[5], m_rx_buffer[6], m_rx_buffer[7]);
    705     DEBUG_LOG("Tx: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", m_tx_buffer[0], m_tx_buffer[1],
    706               m_tx_buffer[2], m_tx_buffer[3], m_tx_buffer[4], m_tx_buffer[5], m_tx_buffer[6], m_tx_buffer[7]);
    707 
    708     m_rx_buffer.fill(0x00);
    709     m_tx_buffer.fill(0x00);
    710   }
    711 
    712   return ack;
    713 }
    714 
    715 std::unique_ptr<NeGconRumble> NeGconRumble::Create(u32 index)
    716 {
    717   return std::make_unique<NeGconRumble>(index);
    718 }
    719 
    720 static const Controller::ControllerBindingInfo s_binding_info[] = {
    721 #define BUTTON(name, display_name, icon_name, button, genb)                                                            \
    722   {                                                                                                                    \
    723     name, display_name, icon_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb                      \
    724   }
    725 #define AXIS(name, display_name, icon_name, halfaxis, genb)                                                            \
    726   {                                                                                                                    \
    727     name, display_name, icon_name, static_cast<u32>(NeGconRumble::Button::Count) + static_cast<u32>(halfaxis),         \
    728       InputBindingInfo::Type::HalfAxis, genb                                                                           \
    729   }
    730 
    731   // clang-format off
    732   BUTTON("Up", TRANSLATE_NOOP("NeGconRumble", "D-Pad Up"), ICON_PF_DPAD_UP, NeGconRumble::Button::Up, GenericInputBinding::DPadUp),
    733   BUTTON("Right", TRANSLATE_NOOP("NeGconRumble", "D-Pad Right"), ICON_PF_DPAD_RIGHT, NeGconRumble::Button::Right, GenericInputBinding::DPadRight),
    734   BUTTON("Down", TRANSLATE_NOOP("NeGconRumble", "D-Pad Down"), ICON_PF_DPAD_DOWN, NeGconRumble::Button::Down, GenericInputBinding::DPadDown),
    735   BUTTON("Left", TRANSLATE_NOOP("NeGconRumble", "D-Pad Left"), ICON_PF_DPAD_LEFT, NeGconRumble::Button::Left, GenericInputBinding::DPadLeft),
    736   BUTTON("Start", TRANSLATE_NOOP("NeGconRumble", "Start"),ICON_PF_START, NeGconRumble::Button::Start, GenericInputBinding::Start),
    737   BUTTON("A", TRANSLATE_NOOP("NeGconRumble", "A Button"), ICON_PF_BUTTON_A, NeGconRumble::Button::A, GenericInputBinding::Circle),
    738   BUTTON("B", TRANSLATE_NOOP("NeGconRumble", "B Button"), ICON_PF_BUTTON_B, NeGconRumble::Button::B, GenericInputBinding::Triangle),
    739   AXIS("I", TRANSLATE_NOOP("NeGconRumble", "I Button"), ICON_PF_RIGHT_TRIGGER_R2, NeGconRumble::HalfAxis::I, GenericInputBinding::R2),
    740   AXIS("II", TRANSLATE_NOOP("NeGconRumble", "II Button"), ICON_PF_LEFT_TRIGGER_L2, NeGconRumble::HalfAxis::II, GenericInputBinding::L2),
    741   AXIS("L", TRANSLATE_NOOP("NeGconRumble", "Left Trigger"), ICON_PF_LEFT_ANALOG_LEFT, NeGconRumble::HalfAxis::L, GenericInputBinding::L1),
    742   BUTTON("R", TRANSLATE_NOOP("NeGconRumble", "Right Trigger"), ICON_PF_RIGHT_SHOULDER_R1, NeGconRumble::Button::R, GenericInputBinding::R1),
    743   AXIS("SteeringLeft", TRANSLATE_NOOP("NeGconRumble", "Steering (Twist) Left"), ICON_PF_LEFT_ANALOG_LEFT, NeGconRumble::HalfAxis::SteeringLeft, GenericInputBinding::LeftStickLeft),
    744   AXIS("SteeringRight", TRANSLATE_NOOP("NeGconRumble", "Steering (Twist) Right"), ICON_PF_LEFT_ANALOG_LEFT, NeGconRumble::HalfAxis::SteeringRight, GenericInputBinding::LeftStickRight),
    745   BUTTON("Analog", TRANSLATE_NOOP("NeGconRumble", "Analog Toggle"), ICON_PF_ANALOG_LEFT_RIGHT, NeGconRumble::Button::Analog, GenericInputBinding::System),
    746 // clang-format on
    747 
    748 #undef AXIS
    749 #undef BUTTON
    750 };
    751 
    752 static const SettingInfo s_settings[] = {
    753   {SettingInfo::Type::Float, "SteeringDeadzone", TRANSLATE_NOOP("NeGconRumble", "Steering Axis Deadzone"),
    754    TRANSLATE_NOOP("NeGconRumble", "Sets deadzone size for steering axis."), "0.00f", "0.00f", "0.99f", "0.01f",
    755    "%.0f%%", nullptr, 100.0f},
    756   {SettingInfo::Type::Float, "SteeringSensitivity", TRANSLATE_NOOP("NeGconRumble", "Steering Axis Sensitivity"),
    757    TRANSLATE_NOOP("NeGconRumble", "Sets the steering axis scaling factor."), "1.00f", "0.01f", "2.00f", "0.01f",
    758    "%.0f%%", nullptr, 100.0f},
    759 };
    760 
    761 const Controller::ControllerInfo NeGconRumble::INFO = {ControllerType::NeGconRumble,
    762                                                        "NeGconRumble",
    763                                                        TRANSLATE_NOOP("ControllerType", "NeGcon with Rumble"),
    764                                                        ICON_PF_GAMEPAD,
    765                                                        s_binding_info,
    766                                                        s_settings,
    767                                                        Controller::VibrationCapabilities::LargeSmallMotors};
    768 
    769 void NeGconRumble::LoadSettings(SettingsInterface& si, const char* section, bool initial)
    770 {
    771   Controller::LoadSettings(si, section, initial);
    772   m_steering_deadzone = si.GetFloatValue(section, "SteeringDeadzone", 0.10f);
    773   m_steering_sensitivity = si.GetFloatValue(section, "SteeringSensitivity", 1.00f);
    774   m_rumble_bias = static_cast<u8>(std::min<u32>(si.GetIntValue(section, "VibrationBias", 8), 255));
    775 }