From ee532e5c01f97f30078202ae87a5b2a71e9ce1e4 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Fri, 4 Mar 2022 11:47:13 -0600
Subject: [PATCH] input_common: Map sticks correctly when mapped sideways

---
 src/input_common/drivers/gc_adapter.cpp       | 16 +++++++++
 src/input_common/drivers/gc_adapter.h         |  2 ++
 src/input_common/drivers/sdl_driver.cpp       | 33 +++++++++++++++++++
 src/input_common/drivers/sdl_driver.h         |  2 ++
 src/input_common/drivers/udp_client.cpp       | 16 +++++++++
 src/input_common/drivers/udp_client.h         |  2 ++
 src/input_common/input_engine.h               |  5 +++
 src/input_common/main.cpp                     | 29 ++++++++++++++++
 src/input_common/main.h                       |  3 ++
 .../configuration/configure_input_player.cpp  | 19 +++++++++++
 10 files changed, 127 insertions(+)

diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp
index 155caae424..8abd41726e 100644
--- a/src/input_common/drivers/gc_adapter.cpp
+++ b/src/input_common/drivers/gc_adapter.cpp
@@ -524,4 +524,20 @@ Common::Input::ButtonNames GCAdapter::GetUIName(const Common::ParamPackage& para
     return Common::Input::ButtonNames::Invalid;
 }
 
+bool GCAdapter::IsStickInverted(const Common::ParamPackage& params) {
+    if (!params.Has("port")) {
+        return false;
+    }
+
+    const auto x_axis = static_cast<PadAxes>(params.Get("axis_x", 0));
+    const auto y_axis = static_cast<PadAxes>(params.Get("axis_y", 0));
+    if (x_axis != PadAxes::StickY && x_axis != PadAxes::SubstickY) {
+        return false;
+    }
+    if (y_axis != PadAxes::StickX && y_axis != PadAxes::SubstickX) {
+        return false;
+    }
+    return true;
+}
+
 } // namespace InputCommon
diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h
index 43ad58c85f..894ab65a4a 100644
--- a/src/input_common/drivers/gc_adapter.h
+++ b/src/input_common/drivers/gc_adapter.h
@@ -35,6 +35,8 @@ public:
     AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
     Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
 
+    bool IsStickInverted(const Common::ParamPackage& params) override;
+
 private:
     enum class PadButton {
         Undefined = 0x0000,
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index c17ea305ef..64edf53e4f 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -934,4 +934,37 @@ u8 SDLDriver::GetHatButtonId(const std::string& direction_name) const {
     return direction;
 }
 
+bool SDLDriver::IsStickInverted(const Common::ParamPackage& params) {
+    if (!params.Has("guid") || !params.Has("port")) {
+        return false;
+    }
+    const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
+    if (joystick == nullptr) {
+        return false;
+    }
+    auto* controller = joystick->GetSDLGameController();
+    if (controller == nullptr) {
+        return false;
+    }
+
+    const auto& axis_x = params.Get("axis_x", 0);
+    const auto& axis_y = params.Get("axis_y", 0);
+    const auto& binding_left_x =
+        SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
+    const auto& binding_right_x =
+        SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
+    const auto& binding_left_y =
+        SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
+    const auto& binding_right_y =
+        SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
+
+    if (axis_x != binding_left_y.value.axis && axis_x != binding_right_y.value.axis) {
+        return false;
+    }
+    if (axis_y != binding_left_x.value.axis && axis_y != binding_right_x.value.axis) {
+        return false;
+    }
+    return true;
+}
+
 } // namespace InputCommon
diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h
index 4cde3606f9..dcd0d1e64d 100644
--- a/src/input_common/drivers/sdl_driver.h
+++ b/src/input_common/drivers/sdl_driver.h
@@ -58,6 +58,8 @@ public:
     std::string GetHatButtonName(u8 direction_value) const override;
     u8 GetHatButtonId(const std::string& direction_name) const override;
 
+    bool IsStickInverted(const Common::ParamPackage& params) override;
+
     Common::Input::VibrationError SetRumble(
         const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
 
diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp
index 9780ead10d..825262a07f 100644
--- a/src/input_common/drivers/udp_client.cpp
+++ b/src/input_common/drivers/udp_client.cpp
@@ -547,6 +547,22 @@ Common::Input::ButtonNames UDPClient::GetUIName(const Common::ParamPackage& para
     return Common::Input::ButtonNames::Invalid;
 }
 
+bool UDPClient::IsStickInverted(const Common::ParamPackage& params) {
+    if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
+        return false;
+    }
+
+    const auto x_axis = static_cast<PadAxes>(params.Get("axis_x", 0));
+    const auto y_axis = static_cast<PadAxes>(params.Get("axis_y", 0));
+    if (x_axis != PadAxes::LeftStickY && x_axis != PadAxes::RightStickY) {
+        return false;
+    }
+    if (y_axis != PadAxes::LeftStickX && y_axis != PadAxes::RightStickX) {
+        return false;
+    }
+    return true;
+}
+
 void TestCommunication(const std::string& host, u16 port,
                        const std::function<void()>& success_callback,
                        const std::function<void()>& failure_callback) {
diff --git a/src/input_common/drivers/udp_client.h b/src/input_common/drivers/udp_client.h
index c7cc7d8466..dece2a45b6 100644
--- a/src/input_common/drivers/udp_client.h
+++ b/src/input_common/drivers/udp_client.h
@@ -64,6 +64,8 @@ public:
     MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
     Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
 
+    bool IsStickInverted(const Common::ParamPackage& params) override;
+
 private:
     enum class PadButton {
         Undefined = 0x0000,
diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h
index f44e0799b8..f94b7669cb 100644
--- a/src/input_common/input_engine.h
+++ b/src/input_common/input_engine.h
@@ -157,6 +157,11 @@ public:
         return 0;
     }
 
+    /// Returns true if axis of a stick aren't mapped in the correct direction
+    virtual bool IsStickInverted([[maybe_unused]] const Common::ParamPackage& params) {
+        return false;
+    }
+
     void PreSetController(const PadIdentifier& identifier);
     void PreSetButton(const PadIdentifier& identifier, int button);
     void PreSetHatButton(const PadIdentifier& identifier, int button);
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 28769c6d88..21834fb6b3 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -241,6 +241,28 @@ struct InputSubsystem::Impl {
         return Common::Input::ButtonNames::Invalid;
     }
 
+    bool IsStickInverted(const Common::ParamPackage& params) {
+        const std::string engine = params.Get("engine", "");
+        if (engine == mouse->GetEngineName()) {
+            return mouse->IsStickInverted(params);
+        }
+        if (engine == gcadapter->GetEngineName()) {
+            return gcadapter->IsStickInverted(params);
+        }
+        if (engine == udp_client->GetEngineName()) {
+            return udp_client->IsStickInverted(params);
+        }
+        if (engine == tas_input->GetEngineName()) {
+            return tas_input->IsStickInverted(params);
+        }
+#ifdef HAVE_SDL2
+        if (engine == sdl->GetEngineName()) {
+            return sdl->IsStickInverted(params);
+        }
+#endif
+        return false;
+    }
+
     bool IsController(const Common::ParamPackage& params) {
         const std::string engine = params.Get("engine", "");
         if (engine == mouse->GetEngineName()) {
@@ -384,6 +406,13 @@ bool InputSubsystem::IsController(const Common::ParamPackage& params) const {
     return impl->IsController(params);
 }
 
+bool InputSubsystem::IsStickInverted(const Common::ParamPackage& params) const {
+    if (params.Has("axis_x") && params.Has("axis_y")) {
+        return impl->IsStickInverted(params);
+    }
+    return false;
+}
+
 void InputSubsystem::ReloadInputDevices() {
     impl->udp_client.get()->ReloadSockets();
 }
diff --git a/src/input_common/main.h b/src/input_common/main.h
index baf107e0f7..147c310c4e 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -119,6 +119,9 @@ public:
     /// Returns true if device is a controller.
     [[nodiscard]] bool IsController(const Common::ParamPackage& params) const;
 
+    /// Returns true if axis of a stick aren't mapped in the correct direction
+    [[nodiscard]] bool IsStickInverted(const Common::ParamPackage& device) const;
+
     /// Reloads the input devices.
     void ReloadInputDevices();
 
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 8ef3596dd6..ac0a9d7de5 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -471,6 +471,25 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
                     [=, this](const Common::ParamPackage& params) {
                         Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
                         SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]);
+                        // Correct axis direction for inverted sticks
+                        if (input_subsystem->IsStickInverted(param)) {
+                            switch (analog_id) {
+                            case Settings::NativeAnalog::LStick: {
+                                const bool invert_value = param.Get("invert_x", "+") == "-";
+                                const std::string invert_str = invert_value ? "+" : "-";
+                                param.Set("invert_x", invert_str);
+                                break;
+                            }
+                            case Settings::NativeAnalog::RStick: {
+                                const bool invert_value = param.Get("invert_y", "+") == "-";
+                                const std::string invert_str = invert_value ? "+" : "-";
+                                param.Set("invert_y", invert_str);
+                                break;
+                            }
+                            default:
+                                break;
+                            }
+                        }
                         emulated_controller->SetStickParam(analog_id, param);
                     },
                     InputCommon::Polling::InputType::Stick);