diff --git a/externals/catch b/externals/catch
index de6fe184a..15cf3caac 160000
--- a/externals/catch
+++ b/externals/catch
@@ -1 +1 @@
-Subproject commit de6fe184a9ac1a06895cdd1c9b437f0a0bdf14ad
+Subproject commit 15cf3caaceb21172ea42a24e595a2eb58c3ec960
diff --git a/externals/dynarmic b/externals/dynarmic
index 358cf6f03..f9d84871f 160000
--- a/externals/dynarmic
+++ b/externals/dynarmic
@@ -1 +1 @@
-Subproject commit 358cf6f0357baae3e3bb5788431acf1068f897b5
+Subproject commit f9d84871fb6dd41c47945d649dc9017aa3762125
diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp
index 60d30c599..388d700f7 100644
--- a/src/citra_qt/configuration/configure_input.cpp
+++ b/src/citra_qt/configuration/configure_input.cpp
@@ -274,7 +274,8 @@ ConfigureInput::ConfigureInput(QWidget* parent)
         });
         connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, [=] {
             const int slider_value = analog_map_deadzone_and_modifier_slider[analog_id]->value();
-            if (analogs_param[analog_id].Get("engine", "") == "sdl") {
+            const auto engine = analogs_param[analog_id].Get("engine", "");
+            if (engine == "sdl" || engine == "gcpad") {
                 analog_map_deadzone_and_modifier_slider_label[analog_id]->setText(
                     tr("Deadzone: %1%").arg(slider_value));
                 analogs_param[analog_id].Set("deadzone", slider_value / 100.0f);
@@ -461,16 +462,14 @@ void ConfigureInput::MapFromButton(const Common::ParamPackage& params) {
     Common::ParamPackage aux_param;
     bool mapped = false;
     for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
-        aux_param = InputCommon::GetSDLControllerButtonBindByGUID(params.Get("guid", "0"),
-                                                                  params.Get("port", 0), button_id);
+        aux_param = InputCommon::GetControllerButtonBinds(params, button_id);
         if (aux_param.Has("engine")) {
             buttons_param[button_id] = aux_param;
             mapped = true;
         }
     }
     for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
-        aux_param = InputCommon::GetSDLControllerAnalogBindByGUID(params.Get("guid", "0"),
-                                                                  params.Get("port", 0), analog_id);
+        aux_param = InputCommon::GetControllerAnalogBinds(params, analog_id);
         if (aux_param.Has("engine")) {
             analogs_param[analog_id] = aux_param;
             mapped = true;
diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp
index b0fb2d95a..e0345cb55 100644
--- a/src/input_common/gcadapter/gc_poller.cpp
+++ b/src/input_common/gcadapter/gc_poller.cpp
@@ -12,7 +12,27 @@
 #include "input_common/gcadapter/gc_poller.h"
 
 namespace InputCommon {
-
+namespace {
+constexpr std::array<GCAdapter::PadButton, Settings::NativeButton::NumButtons> gc_to_3ds_mapping{{
+    GCAdapter::PadButton::ButtonA,
+    GCAdapter::PadButton::ButtonB,
+    GCAdapter::PadButton::ButtonX,
+    GCAdapter::PadButton::ButtonY,
+    GCAdapter::PadButton::ButtonUp,
+    GCAdapter::PadButton::ButtonDown,
+    GCAdapter::PadButton::ButtonLeft,
+    GCAdapter::PadButton::ButtonRight,
+    GCAdapter::PadButton::TriggerL,
+    GCAdapter::PadButton::TriggerR,
+    GCAdapter::PadButton::ButtonStart,
+    GCAdapter::PadButton::TriggerZ,
+    GCAdapter::PadButton::Undefined,
+    GCAdapter::PadButton::Undefined,
+    GCAdapter::PadButton::Undefined,
+    GCAdapter::PadButton::Undefined,
+    GCAdapter::PadButton::Undefined,
+}};
+}
 class GCButton final : public Input::ButtonDevice {
 public:
     explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter)
@@ -126,6 +146,17 @@ Common::ParamPackage GCButtonFactory::GetNextInput() {
     return params;
 }
 
+Common::ParamPackage GCButtonFactory::GetGcTo3DSMappedButton(
+    int port, Settings::NativeButton::Values button) {
+    Common::ParamPackage params({{"engine", "gcpad"}});
+    params.Set("port", port);
+    auto mapped_button = gc_to_3ds_mapping[static_cast<int>(button)];
+    if (mapped_button != GCAdapter::PadButton::Undefined) {
+        params.Set("button", static_cast<u16>(mapped_button));
+    }
+    return params;
+}
+
 void GCButtonFactory::Start() {
     polling = true;
     adapter->BeginConfiguration();
@@ -269,4 +300,24 @@ Common::ParamPackage GCAnalogFactory::GetNextInput() {
     return params;
 }
 
+Common::ParamPackage GCAnalogFactory::GetGcTo3DSMappedAnalog(
+    int port, Settings::NativeAnalog::Values analog) {
+    int x_axis, y_axis;
+    Common::ParamPackage params({{"engine", "gcpad"}});
+    params.Set("port", port);
+    if (analog == Settings::NativeAnalog::Values::CirclePad) {
+        x_axis = static_cast<s32>(GCAdapter::PadAxes::StickX);
+        y_axis = static_cast<s32>(GCAdapter::PadAxes::StickY);
+    } else if (analog == Settings::NativeAnalog::Values::CStick) {
+        x_axis = static_cast<s32>(GCAdapter::PadAxes::SubstickX);
+        y_axis = static_cast<s32>(GCAdapter::PadAxes::SubstickY);
+    } else {
+        LOG_WARNING(Input, "analog value out of range {}", analog);
+        return {{}};
+    }
+    params.Set("axis_x", x_axis);
+    params.Set("axis_y", y_axis);
+    return params;
+}
+
 } // namespace InputCommon
diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h
index 3e0382d30..113e200f9 100644
--- a/src/input_common/gcadapter/gc_poller.h
+++ b/src/input_common/gcadapter/gc_poller.h
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include "core/frontend/input.h"
+#include "core/settings.h"
 #include "input_common/gcadapter/gc_adapter.h"
 #include "input_common/main.h"
 
@@ -24,6 +25,7 @@ public:
     std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
 
     Common::ParamPackage GetNextInput() override;
+    Common::ParamPackage GetGcTo3DSMappedButton(int port, Settings::NativeButton::Values button);
 
     /// For device input configuration/polling
     void Start() override;
@@ -47,6 +49,7 @@ public:
     std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
 
     Common::ParamPackage GetNextInput() override;
+    Common::ParamPackage GetGcTo3DSMappedAnalog(int port, Settings::NativeAnalog::Values analog);
 
     /// For device input configuration/polling
     void Start() override;
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 4d0671fb6..68a430f43 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -91,16 +91,30 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
     return circle_pad_param.Serialize();
 }
 
-Common::ParamPackage GetSDLControllerButtonBindByGUID(const std::string& guid, int port,
-                                                      int button) {
-    return dynamic_cast<SDL::SDLState*>(sdl.get())->GetSDLControllerButtonBindByGUID(
-        guid, port, static_cast<Settings::NativeButton::Values>(button));
+Common::ParamPackage GetControllerButtonBinds(const Common::ParamPackage& params, int button) {
+    const auto native_button{static_cast<Settings::NativeButton::Values>(button)};
+    const auto engine{params.Get("engine", "")};
+    if (engine == "sdl") {
+        return dynamic_cast<SDL::SDLState*>(sdl.get())->GetSDLControllerButtonBindByGUID(
+            params.Get("guid", "0"), params.Get("port", 0), native_button);
+    }
+    if (engine == "gcpad") {
+        return gcbuttons->GetGcTo3DSMappedButton(params.Get("port", 0), native_button);
+    }
+    return {};
 }
 
-Common::ParamPackage GetSDLControllerAnalogBindByGUID(const std::string& guid, int port,
-                                                      int analog) {
-    return dynamic_cast<SDL::SDLState*>(sdl.get())->GetSDLControllerAnalogBindByGUID(
-        guid, port, static_cast<Settings::NativeAnalog::Values>(analog));
+Common::ParamPackage GetControllerAnalogBinds(const Common::ParamPackage& params, int analog) {
+    const auto native_analog{static_cast<Settings::NativeAnalog::Values>(analog)};
+    const auto engine{params.Get("engine", "")};
+    if (engine == "sdl") {
+        return dynamic_cast<SDL::SDLState*>(sdl.get())->GetSDLControllerAnalogBindByGUID(
+            params.Get("guid", "0"), params.Get("port", 0), native_analog);
+    }
+    if (engine == "gcpad") {
+        return gcanalog->GetGcTo3DSMappedAnalog(params.Get("port", 0), native_analog);
+    }
+    return {};
 }
 
 void ReloadInputDevices() {
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 606b198a8..48ecd26ba 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -37,10 +37,8 @@ std::string GenerateKeyboardParam(int key_code);
 std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
                                         int key_modifier, float modifier_scale);
 
-Common::ParamPackage GetSDLControllerButtonBindByGUID(const std::string& guid, int port,
-                                                      int button);
-Common::ParamPackage GetSDLControllerAnalogBindByGUID(const std::string& guid, int port,
-                                                      int analog);
+Common::ParamPackage GetControllerButtonBinds(const Common::ParamPackage& params, int button);
+Common::ParamPackage GetControllerAnalogBinds(const Common::ParamPackage& params, int analog);
 
 /// Reloads the input devices
 void ReloadInputDevices();