diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 71091767da..c8871513ce 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -1,4 +1,6 @@
 add_library(input_common STATIC
+    drivers/gc_adapter.cpp
+    drivers/gc_adapter.h
     drivers/keyboard.cpp
     drivers/keyboard.h
     drivers/mouse.cpp
@@ -23,10 +25,6 @@ add_library(input_common STATIC
     motion_from_button.h
     motion_input.cpp
     motion_input.h
-    gcadapter/gc_adapter.cpp
-    gcadapter/gc_adapter.h
-    gcadapter/gc_poller.cpp
-    gcadapter/gc_poller.h
     sdl/sdl.cpp
     sdl/sdl.h
     tas/tas_input.cpp
diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp
similarity index 61%
rename from src/input_common/gcadapter/gc_adapter.cpp
rename to src/input_common/drivers/gc_adapter.cpp
index a2f1bb67c8..6721ba4f7b 100644
--- a/src/input_common/gcadapter/gc_adapter.cpp
+++ b/src/input_common/drivers/gc_adapter.cpp
@@ -2,47 +2,103 @@
 // Licensed under GPLv2+
 // Refer to the license.txt file included.
 
-#include <chrono>
-#include <thread>
-
+#include <fmt/format.h>
 #include <libusb.h>
 
 #include "common/logging/log.h"
 #include "common/param_package.h"
 #include "common/settings_input.h"
-#include "input_common/gcadapter/gc_adapter.h"
+#include "common/thread.h"
+#include "input_common/drivers/gc_adapter.h"
 
-namespace GCAdapter {
+namespace InputCommon {
 
-Adapter::Adapter() {
-    if (usb_adapter_handle != nullptr) {
+class LibUSBContext {
+public:
+    explicit LibUSBContext() {
+        init_result = libusb_init(&ctx);
+    }
+
+    ~LibUSBContext() {
+        libusb_exit(ctx);
+    }
+
+    LibUSBContext& operator=(const LibUSBContext&) = delete;
+    LibUSBContext(const LibUSBContext&) = delete;
+
+    LibUSBContext& operator=(LibUSBContext&&) noexcept = delete;
+    LibUSBContext(LibUSBContext&&) noexcept = delete;
+
+    [[nodiscard]] int InitResult() const noexcept {
+        return init_result;
+    }
+
+    [[nodiscard]] libusb_context* get() noexcept {
+        return ctx;
+    }
+
+private:
+    libusb_context* ctx;
+    int init_result{};
+};
+
+class LibUSBDeviceHandle {
+public:
+    explicit LibUSBDeviceHandle(libusb_context* ctx, uint16_t vid, uint16_t pid) noexcept {
+        handle = libusb_open_device_with_vid_pid(ctx, vid, pid);
+    }
+
+    ~LibUSBDeviceHandle() noexcept {
+        if (handle) {
+            libusb_release_interface(handle, 1);
+            libusb_close(handle);
+        }
+    }
+
+    LibUSBDeviceHandle& operator=(const LibUSBDeviceHandle&) = delete;
+    LibUSBDeviceHandle(const LibUSBDeviceHandle&) = delete;
+
+    LibUSBDeviceHandle& operator=(LibUSBDeviceHandle&&) noexcept = delete;
+    LibUSBDeviceHandle(LibUSBDeviceHandle&&) noexcept = delete;
+
+    [[nodiscard]] libusb_device_handle* get() noexcept {
+        return handle;
+    }
+
+private:
+    libusb_device_handle* handle{};
+};
+
+GCAdapter::GCAdapter(const std::string input_engine_) : InputEngine(input_engine_) {
+    if (usb_adapter_handle) {
         return;
     }
     LOG_INFO(Input, "GC Adapter Initialization started");
 
-    const int init_res = libusb_init(&libusb_ctx);
+    libusb_ctx = std::make_unique<LibUSBContext>();
+    const int init_res = libusb_ctx->InitResult();
     if (init_res == LIBUSB_SUCCESS) {
-        adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this);
+        adapter_scan_thread =
+            std::jthread([this](std::stop_token stop_token) { AdapterScanThread(stop_token); });
     } else {
         LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);
     }
 }
 
-Adapter::~Adapter() {
+GCAdapter::~GCAdapter() {
     Reset();
 }
 
-void Adapter::AdapterInputThread() {
+void GCAdapter::AdapterInputThread(std::stop_token stop_token) {
     LOG_DEBUG(Input, "GC Adapter input thread started");
+    Common::SetCurrentThreadName("yuzu:input:GCAdapter");
     s32 payload_size{};
     AdapterPayload adapter_payload{};
 
-    if (adapter_scan_thread.joinable()) {
-        adapter_scan_thread.join();
-    }
+    adapter_scan_thread = {};
 
-    while (adapter_input_thread_running) {
-        libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(),
+    while (!stop_token.stop_requested()) {
+        libusb_interrupt_transfer(usb_adapter_handle->get(), input_endpoint, adapter_payload.data(),
                                   static_cast<s32>(adapter_payload.size()), &payload_size, 16);
         if (IsPayloadCorrect(adapter_payload, payload_size)) {
             UpdateControllers(adapter_payload);
@@ -52,19 +108,20 @@ void Adapter::AdapterInputThread() {
     }
 
     if (restart_scan_thread) {
-        adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this);
+        adapter_scan_thread =
+            std::jthread([this](std::stop_token token) { AdapterScanThread(token); });
         restart_scan_thread = false;
     }
 }
 
-bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) {
+bool GCAdapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) {
     if (payload_size != static_cast<s32>(adapter_payload.size()) ||
         adapter_payload[0] != LIBUSB_DT_HID) {
         LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size,
                   adapter_payload[0]);
         if (input_error_counter++ > 20) {
             LOG_ERROR(Input, "GC adapter timeout, Is the adapter connected?");
-            adapter_input_thread_running = false;
+            adapter_input_thread.request_stop();
             restart_scan_thread = true;
         }
         return false;
@@ -74,7 +131,7 @@ bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payloa
     return true;
 }
 
-void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) {
+void GCAdapter::UpdateControllers(const AdapterPayload& adapter_payload) {
     for (std::size_t port = 0; port < pads.size(); ++port) {
         const std::size_t offset = 1 + (9 * port);
         const auto type = static_cast<ControllerTypes>(adapter_payload[offset] >> 4);
@@ -84,23 +141,21 @@ void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) {
             const u8 b2 = adapter_payload[offset + 2];
             UpdateStateButtons(port, b1, b2);
             UpdateStateAxes(port, adapter_payload);
-            if (configuring) {
-                UpdateYuzuSettings(port);
-            }
         }
     }
 }
 
-void Adapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) {
+void GCAdapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) {
     if (pads[port].type == pad_type) {
         return;
     }
     // Device changed reset device and set new type
-    ResetDevice(port);
+    pads[port] = {};
     pads[port].type = pad_type;
 }
 
-void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) {
+void GCAdapter::UpdateStateButtons(std::size_t port, [[maybe_unused]] u8 b1,
+                                   [[maybe_unused]] u8 b2) {
     if (port >= pads.size()) {
         return;
     }
@@ -116,25 +171,21 @@ void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) {
         PadButton::TriggerR,
         PadButton::TriggerL,
     };
-    pads[port].buttons = 0;
+
     for (std::size_t i = 0; i < b1_buttons.size(); ++i) {
-        if ((b1 & (1U << i)) != 0) {
-            pads[port].buttons =
-                static_cast<u16>(pads[port].buttons | static_cast<u16>(b1_buttons[i]));
-            pads[port].last_button = b1_buttons[i];
-        }
+        const bool button_status = (b1 & (1U << i)) != 0;
+        const int button = static_cast<int>(b1_buttons[i]);
+        SetButton(pads[port].identifier, button, button_status);
     }
 
     for (std::size_t j = 0; j < b2_buttons.size(); ++j) {
-        if ((b2 & (1U << j)) != 0) {
-            pads[port].buttons =
-                static_cast<u16>(pads[port].buttons | static_cast<u16>(b2_buttons[j]));
-            pads[port].last_button = b2_buttons[j];
-        }
+        const bool button_status = (b2 & (1U << j)) != 0;
+        const int button = static_cast<int>(b2_buttons[j]);
+        SetButton(pads[port].identifier, button, button_status);
     }
 }
 
-void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) {
+void GCAdapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) {
     if (port >= pads.size()) {
         return;
     }
@@ -155,134 +206,70 @@ void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_pa
             pads[port].axis_origin[index] = axis_value;
             pads[port].reset_origin_counter++;
         }
-        pads[port].axis_values[index] =
-            static_cast<s16>(axis_value - pads[port].axis_origin[index]);
+        const f32 axis_status = (axis_value - pads[port].axis_origin[index]) / 110.0f;
+        SetAxis(pads[port].identifier, static_cast<int>(index), axis_status);
     }
 }
 
-void Adapter::UpdateYuzuSettings(std::size_t port) {
-    if (port >= pads.size()) {
-        return;
-    }
-
-    constexpr u8 axis_threshold = 50;
-    GCPadStatus pad_status = {.port = port};
-
-    if (pads[port].buttons != 0) {
-        pad_status.button = pads[port].last_button;
-        pad_queue.Push(pad_status);
-    }
-
-    // Accounting for a threshold here to ensure an intentional press
-    for (std::size_t i = 0; i < pads[port].axis_values.size(); ++i) {
-        const s16 value = pads[port].axis_values[i];
-
-        if (value > axis_threshold || value < -axis_threshold) {
-            pad_status.axis = static_cast<PadAxes>(i);
-            pad_status.axis_value = value;
-            pad_status.axis_threshold = axis_threshold;
-            pad_queue.Push(pad_status);
-        }
+void GCAdapter::AdapterScanThread(std::stop_token stop_token) {
+    Common::SetCurrentThreadName("yuzu:input:ScanGCAdapter");
+    usb_adapter_handle = nullptr;
+    pads = {};
+    while (!stop_token.stop_requested() && !Setup()) {
+        std::this_thread::sleep_for(std::chrono::seconds(2));
     }
 }
 
-void Adapter::UpdateVibrations() {
-    // Use 8 states to keep the switching between on/off fast enough for
-    // a human to not notice the difference between switching from on/off
-    // More states = more rumble strengths = slower update time
-    constexpr u8 vibration_states = 8;
-
-    vibration_counter = (vibration_counter + 1) % vibration_states;
-
-    for (GCController& pad : pads) {
-        const bool vibrate = pad.rumble_amplitude > vibration_counter;
-        vibration_changed |= vibrate != pad.enable_vibration;
-        pad.enable_vibration = vibrate;
-    }
-    SendVibrations();
-}
-
-void Adapter::SendVibrations() {
-    if (!rumble_enabled || !vibration_changed) {
-        return;
-    }
-    s32 size{};
-    constexpr u8 rumble_command = 0x11;
-    const u8 p1 = pads[0].enable_vibration;
-    const u8 p2 = pads[1].enable_vibration;
-    const u8 p3 = pads[2].enable_vibration;
-    const u8 p4 = pads[3].enable_vibration;
-    std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4};
-    const int err = libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, payload.data(),
-                                              static_cast<s32>(payload.size()), &size, 16);
-    if (err) {
-        LOG_DEBUG(Input, "Adapter libusb write failed: {}", libusb_error_name(err));
-        if (output_error_counter++ > 5) {
-            LOG_ERROR(Input, "GC adapter output timeout, Rumble disabled");
-            rumble_enabled = false;
-        }
-        return;
-    }
-    output_error_counter = 0;
-    vibration_changed = false;
-}
-
-bool Adapter::RumblePlay(std::size_t port, u8 amplitude) {
-    pads[port].rumble_amplitude = amplitude;
-
-    return rumble_enabled;
-}
-
-void Adapter::AdapterScanThread() {
-    adapter_scan_thread_running = true;
-    adapter_input_thread_running = false;
-    if (adapter_input_thread.joinable()) {
-        adapter_input_thread.join();
-    }
-    ClearLibusbHandle();
-    ResetDevices();
-    while (adapter_scan_thread_running && !adapter_input_thread_running) {
-        Setup();
-        std::this_thread::sleep_for(std::chrono::seconds(1));
-    }
-}
-
-void Adapter::Setup() {
-    usb_adapter_handle = libusb_open_device_with_vid_pid(libusb_ctx, 0x057e, 0x0337);
-
-    if (usb_adapter_handle == NULL) {
-        return;
+bool GCAdapter::Setup() {
+    constexpr u16 nintendo_vid = 0x057e;
+    constexpr u16 gc_adapter_pid = 0x0337;
+    usb_adapter_handle =
+        std::make_unique<LibUSBDeviceHandle>(libusb_ctx->get(), nintendo_vid, gc_adapter_pid);
+    if (!usb_adapter_handle->get()) {
+        return false;
     }
     if (!CheckDeviceAccess()) {
-        ClearLibusbHandle();
-        return;
+        usb_adapter_handle = nullptr;
+        return false;
     }
 
-    libusb_device* device = libusb_get_device(usb_adapter_handle);
+    libusb_device* const device = libusb_get_device(usb_adapter_handle->get());
 
     LOG_INFO(Input, "GC adapter is now connected");
     // GC Adapter found and accessible, registering it
     if (GetGCEndpoint(device)) {
-        adapter_scan_thread_running = false;
-        adapter_input_thread_running = true;
         rumble_enabled = true;
         input_error_counter = 0;
         output_error_counter = 0;
-        adapter_input_thread = std::thread(&Adapter::AdapterInputThread, this);
+
+        std::size_t port = 0;
+        for (GCController& pad : pads) {
+            pad.identifier = {
+                .guid = Common::UUID{""},
+                .port = port++,
+                .pad = 0,
+            };
+            PreSetController(pad.identifier);
+        }
+
+        adapter_input_thread =
+            std::jthread([this](std::stop_token stop_token) { AdapterInputThread(stop_token); });
+        return true;
     }
+    return false;
 }
 
-bool Adapter::CheckDeviceAccess() {
+bool GCAdapter::CheckDeviceAccess() {
     // This fixes payload problems from offbrand GCAdapters
     const s32 control_transfer_error =
-        libusb_control_transfer(usb_adapter_handle, 0x21, 11, 0x0001, 0, nullptr, 0, 1000);
+        libusb_control_transfer(usb_adapter_handle->get(), 0x21, 11, 0x0001, 0, nullptr, 0, 1000);
     if (control_transfer_error < 0) {
         LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error);
     }
 
-    s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0);
+    s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle->get(), 0);
     if (kernel_driver_error == 1) {
-        kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0);
+        kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle->get(), 0);
         if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
             LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}",
                       kernel_driver_error);
@@ -290,15 +277,13 @@ bool Adapter::CheckDeviceAccess() {
     }
 
     if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
-        libusb_close(usb_adapter_handle);
         usb_adapter_handle = nullptr;
         return false;
     }
 
-    const int interface_claim_error = libusb_claim_interface(usb_adapter_handle, 0);
+    const int interface_claim_error = libusb_claim_interface(usb_adapter_handle->get(), 0);
     if (interface_claim_error) {
         LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error);
-        libusb_close(usb_adapter_handle);
         usb_adapter_handle = nullptr;
         return false;
     }
@@ -306,7 +291,7 @@ bool Adapter::CheckDeviceAccess() {
     return true;
 }
 
-bool Adapter::GetGCEndpoint(libusb_device* device) {
+bool GCAdapter::GetGCEndpoint(libusb_device* device) {
     libusb_config_descriptor* config = nullptr;
     const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config);
     if (config_descriptor_return != LIBUSB_SUCCESS) {
@@ -332,68 +317,83 @@ bool Adapter::GetGCEndpoint(libusb_device* device) {
     // This transfer seems to be responsible for clearing the state of the adapter
     // Used to clear the "busy" state of when the device is unexpectedly unplugged
     unsigned char clear_payload = 0x13;
-    libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload,
+    libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, &clear_payload,
                               sizeof(clear_payload), nullptr, 16);
     return true;
 }
 
-void Adapter::JoinThreads() {
-    restart_scan_thread = false;
-    adapter_input_thread_running = false;
-    adapter_scan_thread_running = false;
+bool GCAdapter::SetRumble(const PadIdentifier& identifier, const Input::VibrationStatus vibration) {
+    const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f;
+    const auto processed_amplitude =
+        static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8);
 
-    if (adapter_scan_thread.joinable()) {
-        adapter_scan_thread.join();
-    }
-
-    if (adapter_input_thread.joinable()) {
-        adapter_input_thread.join();
-    }
+    pads[identifier.port].rumble_amplitude = processed_amplitude;
+    return rumble_enabled;
 }
 
-void Adapter::ClearLibusbHandle() {
-    if (usb_adapter_handle) {
-        libusb_release_interface(usb_adapter_handle, 1);
-        libusb_close(usb_adapter_handle);
-        usb_adapter_handle = nullptr;
+void GCAdapter::UpdateVibrations() {
+    // Use 8 states to keep the switching between on/off fast enough for
+    // a human to feel different vibration strenght
+    // More states == more rumble strengths == slower update time
+    constexpr u8 vibration_states = 8;
+
+    vibration_counter = (vibration_counter + 1) % vibration_states;
+
+    for (GCController& pad : pads) {
+        const bool vibrate = pad.rumble_amplitude > vibration_counter;
+        vibration_changed |= vibrate != pad.enable_vibration;
+        pad.enable_vibration = vibrate;
     }
+    SendVibrations();
 }
 
-void Adapter::ResetDevices() {
-    for (std::size_t i = 0; i < pads.size(); ++i) {
-        ResetDevice(i);
+void GCAdapter::SendVibrations() {
+    if (!rumble_enabled || !vibration_changed) {
+        return;
     }
-}
-
-void Adapter::ResetDevice(std::size_t port) {
-    pads[port].type = ControllerTypes::None;
-    pads[port].enable_vibration = false;
-    pads[port].rumble_amplitude = 0;
-    pads[port].buttons = 0;
-    pads[port].last_button = PadButton::Undefined;
-    pads[port].axis_values.fill(0);
-    pads[port].reset_origin_counter = 0;
-}
-
-void Adapter::Reset() {
-    JoinThreads();
-    ClearLibusbHandle();
-    ResetDevices();
-
-    if (libusb_ctx) {
-        libusb_exit(libusb_ctx);
+    s32 size{};
+    constexpr u8 rumble_command = 0x11;
+    const u8 p1 = pads[0].enable_vibration;
+    const u8 p2 = pads[1].enable_vibration;
+    const u8 p3 = pads[2].enable_vibration;
+    const u8 p4 = pads[3].enable_vibration;
+    std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4};
+    const int err =
+        libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, payload.data(),
+                                  static_cast<s32>(payload.size()), &size, 16);
+    if (err) {
+        LOG_DEBUG(Input, "Adapter libusb write failed: {}", libusb_error_name(err));
+        if (output_error_counter++ > 5) {
+            LOG_ERROR(Input, "GC adapter output timeout, Rumble disabled");
+            rumble_enabled = false;
+        }
+        return;
     }
+    output_error_counter = 0;
+    vibration_changed = false;
 }
 
-std::vector<Common::ParamPackage> Adapter::GetInputDevices() const {
+bool GCAdapter::DeviceConnected(std::size_t port) const {
+    return pads[port].type != ControllerTypes::None;
+}
+
+void GCAdapter::Reset() {
+    adapter_scan_thread = {};
+    adapter_input_thread = {};
+    usb_adapter_handle = nullptr;
+    pads = {};
+    libusb_ctx = nullptr;
+}
+
+std::vector<Common::ParamPackage> GCAdapter::GetInputDevices() const {
     std::vector<Common::ParamPackage> devices;
     for (std::size_t port = 0; port < pads.size(); ++port) {
         if (!DeviceConnected(port)) {
             continue;
         }
-        std::string name = fmt::format("Gamecube Controller {}", port + 1);
+        const std::string name = fmt::format("Gamecube Controller {}", port + 1);
         devices.emplace_back(Common::ParamPackage{
-            {"class", "gcpad"},
+            {"engine", "gcpad"},
             {"display", std::move(name)},
             {"port", std::to_string(port)},
         });
@@ -401,8 +401,7 @@ std::vector<Common::ParamPackage> Adapter::GetInputDevices() const {
     return devices;
 }
 
-InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice(
-    const Common::ParamPackage& params) const {
+ButtonMapping GCAdapter::GetButtonMappingForDevice(const Common::ParamPackage& params) {
     // This list is missing ZL/ZR since those are not considered buttons.
     // We will add those afterwards
     // This list also excludes any button that can't be really mapped
@@ -425,7 +424,7 @@ InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice(
         return {};
     }
 
-    InputCommon::ButtonMapping mapping{};
+    ButtonMapping mapping{};
     for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) {
         Common::ParamPackage button_params({{"engine", "gcpad"}});
         button_params.Set("port", params.Get("port", 0));
@@ -434,30 +433,30 @@ InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice(
     }
 
     // Add the missing bindings for ZL/ZR
-    static constexpr std::array<std::pair<Settings::NativeButton::Values, PadAxes>, 2>
+    static constexpr std::array<std::tuple<Settings::NativeButton::Values, PadButton, PadAxes>, 2>
         switch_to_gcadapter_axis = {
-            std::pair{Settings::NativeButton::ZL, PadAxes::TriggerLeft},
-            {Settings::NativeButton::ZR, PadAxes::TriggerRight},
+            std::tuple{Settings::NativeButton::ZL, PadButton::TriggerL, PadAxes::TriggerLeft},
+            {Settings::NativeButton::ZR, PadButton::TriggerR, PadAxes::TriggerRight},
         };
-    for (const auto& [switch_button, gcadapter_axis] : switch_to_gcadapter_axis) {
+    for (const auto& [switch_button, gcadapter_buton, gcadapter_axis] : switch_to_gcadapter_axis) {
         Common::ParamPackage button_params({{"engine", "gcpad"}});
         button_params.Set("port", params.Get("port", 0));
-        button_params.Set("button", static_cast<s32>(PadButton::Stick));
+        button_params.Set("button", static_cast<s32>(gcadapter_buton));
         button_params.Set("axis", static_cast<s32>(gcadapter_axis));
         button_params.Set("threshold", 0.5f);
+        button_params.Set("range", 1.9f);
         button_params.Set("direction", "+");
         mapping.insert_or_assign(switch_button, std::move(button_params));
     }
     return mapping;
 }
 
-InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice(
-    const Common::ParamPackage& params) const {
+AnalogMapping GCAdapter::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
     if (!params.Has("port")) {
         return {};
     }
 
-    InputCommon::AnalogMapping mapping = {};
+    AnalogMapping mapping = {};
     Common::ParamPackage left_analog_params;
     left_analog_params.Set("engine", "gcpad");
     left_analog_params.Set("port", params.Get("port", 0));
@@ -473,34 +472,12 @@ InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice(
     return mapping;
 }
 
-bool Adapter::DeviceConnected(std::size_t port) const {
-    return pads[port].type != ControllerTypes::None;
+std::string GCAdapter::GetUIName(const Common::ParamPackage& params) const {
+    if (params.Has("button")) {
+        return fmt::format("Button {}", params.Get("button", 0));
+    }
+
+    return "Bad GC Adapter";
 }
 
-void Adapter::BeginConfiguration() {
-    pad_queue.Clear();
-    configuring = true;
-}
-
-void Adapter::EndConfiguration() {
-    pad_queue.Clear();
-    configuring = false;
-}
-
-Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() {
-    return pad_queue;
-}
-
-const Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() const {
-    return pad_queue;
-}
-
-GCController& Adapter::GetPadState(std::size_t port) {
-    return pads.at(port);
-}
-
-const GCController& Adapter::GetPadState(std::size_t port) const {
-    return pads.at(port);
-}
-
-} // namespace GCAdapter
+} // namespace InputCommon
diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h
new file mode 100644
index 0000000000..c0bf1ed7ae
--- /dev/null
+++ b/src/input_common/drivers/gc_adapter.h
@@ -0,0 +1,128 @@
+// Copyright 2014 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <mutex>
+#include <stop_token>
+#include <thread>
+
+#include "input_common/input_engine.h"
+
+struct libusb_context;
+struct libusb_device;
+struct libusb_device_handle;
+
+namespace InputCommon {
+
+class LibUSBContext;
+class LibUSBDeviceHandle;
+
+class GCAdapter : public InputCommon::InputEngine {
+public:
+    explicit GCAdapter(const std::string input_engine_);
+    ~GCAdapter();
+
+    bool SetRumble(const PadIdentifier& identifier,
+                   const Input::VibrationStatus vibration) override;
+
+    /// Used for automapping features
+    std::vector<Common::ParamPackage> GetInputDevices() const override;
+    ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
+    AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
+    std::string GetUIName(const Common::ParamPackage& params) const override;
+
+private:
+    enum class PadButton {
+        Undefined = 0x0000,
+        ButtonLeft = 0x0001,
+        ButtonRight = 0x0002,
+        ButtonDown = 0x0004,
+        ButtonUp = 0x0008,
+        TriggerZ = 0x0010,
+        TriggerR = 0x0020,
+        TriggerL = 0x0040,
+        ButtonA = 0x0100,
+        ButtonB = 0x0200,
+        ButtonX = 0x0400,
+        ButtonY = 0x0800,
+        ButtonStart = 0x1000,
+    };
+
+    enum class PadAxes : u8 {
+        StickX,
+        StickY,
+        SubstickX,
+        SubstickY,
+        TriggerLeft,
+        TriggerRight,
+        Undefined,
+    };
+
+    enum class ControllerTypes {
+        None,
+        Wired,
+        Wireless,
+    };
+
+    struct GCController {
+        ControllerTypes type = ControllerTypes::None;
+        PadIdentifier identifier{};
+        bool enable_vibration = false;
+        u8 rumble_amplitude{};
+        std::array<u8, 6> axis_origin{};
+        u8 reset_origin_counter{};
+    };
+
+    using AdapterPayload = std::array<u8, 37>;
+
+    void UpdatePadType(std::size_t port, ControllerTypes pad_type);
+    void UpdateControllers(const AdapterPayload& adapter_payload);
+    void UpdateStateButtons(std::size_t port, u8 b1, u8 b2);
+    void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload);
+
+    void AdapterInputThread(std::stop_token stop_token);
+
+    void AdapterScanThread(std::stop_token stop_token);
+
+    bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size);
+
+    /// For use in initialization, querying devices to find the adapter
+    bool Setup();
+
+    /// Returns true if we successfully gain access to GC Adapter
+    bool CheckDeviceAccess();
+
+    /// Captures GC Adapter endpoint address
+    /// Returns true if the endpoint was set correctly
+    bool GetGCEndpoint(libusb_device* device);
+
+    /// Returns true if there is a device connected to port
+    bool DeviceConnected(std::size_t port) const;
+
+    /// For shutting down, clear all data, join all threads, release usb
+    void Reset();
+
+    void UpdateVibrations();
+    // Updates vibration state of all controllers
+    void SendVibrations();
+    std::unique_ptr<LibUSBDeviceHandle> usb_adapter_handle;
+    std::array<GCController, 4> pads;
+
+    std::jthread adapter_input_thread;
+    std::jthread adapter_scan_thread;
+    bool restart_scan_thread{};
+
+    std::unique_ptr<LibUSBContext> libusb_ctx;
+
+    u8 input_endpoint{0};
+    u8 output_endpoint{0};
+    u8 input_error_counter{0};
+    u8 output_error_counter{0};
+    int vibration_counter{0};
+
+    bool rumble_enabled{true};
+    bool vibration_changed{true};
+};
+} // namespace InputCommon
diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp
new file mode 100644
index 0000000000..5e2101b27d
--- /dev/null
+++ b/src/input_common/drivers/tas_input.cpp
@@ -0,0 +1,320 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include <regex>
+#include <fmt/format.h>
+
+#include "common/fs/file.h"
+#include "common/fs/fs_types.h"
+#include "common/fs/path_util.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "input_common/drivers/tas_input.h"
+
+namespace InputCommon::TasInput {
+
+enum TasAxes : u8 {
+    StickX,
+    StickY,
+    SubstickX,
+    SubstickY,
+    Undefined,
+};
+
+// Supported keywords and buttons from a TAS file
+constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
+    std::pair{"KEY_A", TasButton::BUTTON_A},
+    {"KEY_B", TasButton::BUTTON_B},
+    {"KEY_X", TasButton::BUTTON_X},
+    {"KEY_Y", TasButton::BUTTON_Y},
+    {"KEY_LSTICK", TasButton::STICK_L},
+    {"KEY_RSTICK", TasButton::STICK_R},
+    {"KEY_L", TasButton::TRIGGER_L},
+    {"KEY_R", TasButton::TRIGGER_R},
+    {"KEY_PLUS", TasButton::BUTTON_PLUS},
+    {"KEY_MINUS", TasButton::BUTTON_MINUS},
+    {"KEY_DLEFT", TasButton::BUTTON_LEFT},
+    {"KEY_DUP", TasButton::BUTTON_UP},
+    {"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
+    {"KEY_DDOWN", TasButton::BUTTON_DOWN},
+    {"KEY_SL", TasButton::BUTTON_SL},
+    {"KEY_SR", TasButton::BUTTON_SR},
+    {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
+    {"KEY_HOME", TasButton::BUTTON_HOME},
+    {"KEY_ZL", TasButton::TRIGGER_ZL},
+    {"KEY_ZR", TasButton::TRIGGER_ZR},
+};
+
+Tas::Tas(const std::string input_engine_) : InputCommon::InputEngine(input_engine_) {
+    for (size_t player_index = 0; player_index < PLAYER_NUMBER; player_index++) {
+        PadIdentifier identifier{
+            .guid = Common::UUID{},
+            .port = player_index,
+            .pad = 0,
+        };
+        PreSetController(identifier);
+    }
+    ClearInput();
+    if (!Settings::values.tas_enable) {
+        needs_reset = true;
+        return;
+    }
+    LoadTasFiles();
+}
+
+Tas::~Tas() {
+    Stop();
+};
+
+void Tas::LoadTasFiles() {
+    script_length = 0;
+    for (size_t i = 0; i < commands.size(); i++) {
+        LoadTasFile(i);
+        if (commands[i].size() > script_length) {
+            script_length = commands[i].size();
+        }
+    }
+}
+
+void Tas::LoadTasFile(size_t player_index) {
+    if (!commands[player_index].empty()) {
+        commands[player_index].clear();
+    }
+    std::string file =
+        Common::FS::ReadStringFromFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) /
+                                           fmt::format("script0-{}.txt", player_index + 1),
+                                       Common::FS::FileType::BinaryFile);
+    std::stringstream command_line(file);
+    std::string line;
+    int frame_no = 0;
+    while (std::getline(command_line, line, '\n')) {
+        if (line.empty()) {
+            continue;
+        }
+        std::smatch m;
+
+        std::stringstream linestream(line);
+        std::string segment;
+        std::vector<std::string> seglist;
+
+        while (std::getline(linestream, segment, ' ')) {
+            seglist.push_back(segment);
+        }
+
+        if (seglist.size() < 4) {
+            continue;
+        }
+
+        while (frame_no < std::stoi(seglist.at(0))) {
+            commands[player_index].push_back({});
+            frame_no++;
+        }
+
+        TASCommand command = {
+            .buttons = ReadCommandButtons(seglist.at(1)),
+            .l_axis = ReadCommandAxis(seglist.at(2)),
+            .r_axis = ReadCommandAxis(seglist.at(3)),
+        };
+        commands[player_index].push_back(command);
+        frame_no++;
+    }
+    LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
+}
+
+void Tas::WriteTasFile(std::u8string file_name) {
+    std::string output_text;
+    for (size_t frame = 0; frame < record_commands.size(); frame++) {
+        const TASCommand& line = record_commands[frame];
+        output_text += fmt::format("{} {} {} {} {}\n", frame, WriteCommandButtons(line.buttons),
+                                   WriteCommandAxis(line.l_axis), WriteCommandAxis(line.r_axis));
+    }
+    const auto bytes_written = Common::FS::WriteStringToFile(
+        Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name,
+        Common::FS::FileType::TextFile, output_text);
+    if (bytes_written == output_text.size()) {
+        LOG_INFO(Input, "TAS file written to file!");
+    } else {
+        LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written,
+                  output_text.size());
+    }
+}
+
+void Tas::RecordInput(u32 buttons, TasAnalog left_axis, TasAnalog right_axis) {
+    last_input = {
+        .buttons = buttons,
+        .l_axis = FlipAxisY(left_axis),
+        .r_axis = FlipAxisY(right_axis),
+    };
+}
+
+TasAnalog Tas::FlipAxisY(TasAnalog old) {
+    return {
+        .x = old.x,
+        .y = -old.y,
+    };
+}
+
+std::tuple<TasState, size_t, size_t> Tas::GetStatus() const {
+    TasState state;
+    if (is_recording) {
+        return {TasState::Recording, 0, record_commands.size()};
+    }
+
+    if (is_running) {
+        state = TasState::Running;
+    } else {
+        state = TasState::Stopped;
+    }
+
+    return {state, current_command, script_length};
+}
+
+void Tas::UpdateThread() {
+    if (!Settings::values.tas_enable) {
+        if (is_running) {
+            Stop();
+        }
+        return;
+    }
+
+    if (is_recording) {
+        record_commands.push_back(last_input);
+    }
+    if (needs_reset) {
+        current_command = 0;
+        needs_reset = false;
+        LoadTasFiles();
+        LOG_DEBUG(Input, "tas_reset done");
+    }
+
+    if (!is_running) {
+        ClearInput();
+        return;
+    }
+    if (current_command < script_length) {
+        LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length);
+        size_t frame = current_command++;
+        for (size_t player_index = 0; player_index < commands.size(); player_index++) {
+            TASCommand command{};
+            if (frame < commands[player_index].size()) {
+                command = commands[player_index][frame];
+            }
+
+            PadIdentifier identifier{
+                .guid = Common::UUID{},
+                .port = player_index,
+                .pad = 0,
+            };
+            for (std::size_t i = 0; i < sizeof(command.buttons); ++i) {
+                const bool button_status = (command.buttons & (1U << i)) != 0;
+                const int button = static_cast<int>(i);
+                SetButton(identifier, button, button_status);
+            }
+            SetAxis(identifier, TasAxes::StickX, command.l_axis.x);
+            SetAxis(identifier, TasAxes::StickY, command.l_axis.y);
+            SetAxis(identifier, TasAxes::SubstickX, command.r_axis.x);
+            SetAxis(identifier, TasAxes::SubstickY, command.r_axis.y);
+        }
+    } else {
+        is_running = Settings::values.tas_loop.GetValue();
+        current_command = 0;
+        ClearInput();
+    }
+}
+
+void Tas::ClearInput() {
+    ResetButtonState();
+    ResetAnalogState();
+}
+
+TasAnalog Tas::ReadCommandAxis(const std::string& line) const {
+    std::stringstream linestream(line);
+    std::string segment;
+    std::vector<std::string> seglist;
+
+    while (std::getline(linestream, segment, ';')) {
+        seglist.push_back(segment);
+    }
+
+    const float x = std::stof(seglist.at(0)) / 32767.0f;
+    const float y = std::stof(seglist.at(1)) / 32767.0f;
+
+    return {x, y};
+}
+
+u32 Tas::ReadCommandButtons(const std::string& data) const {
+    std::stringstream button_text(data);
+    std::string line;
+    u32 buttons = 0;
+    while (std::getline(button_text, line, ';')) {
+        for (auto [text, tas_button] : text_to_tas_button) {
+            if (text == line) {
+                buttons |= static_cast<u32>(tas_button);
+                break;
+            }
+        }
+    }
+    return buttons;
+}
+
+std::string Tas::WriteCommandButtons(u32 buttons) const {
+    std::string returns = "";
+    for (auto [text_button, tas_button] : text_to_tas_button) {
+        if ((buttons & static_cast<u32>(tas_button)) != 0)
+            returns += fmt::format("{};", text_button.substr(4));
+    }
+    return returns.empty() ? "NONE" : returns.substr(2);
+}
+
+std::string Tas::WriteCommandAxis(TasAnalog analog) const {
+    return fmt::format("{};{}", analog.x * 32767, analog.y * 32767);
+}
+
+void Tas::StartStop() {
+    if (!Settings::values.tas_enable) {
+        return;
+    }
+    if (is_running) {
+        Stop();
+    } else {
+        is_running = true;
+    }
+}
+
+void Tas::Stop() {
+    is_running = false;
+}
+
+void Tas::Reset() {
+    if (!Settings::values.tas_enable) {
+        return;
+    }
+    needs_reset = true;
+}
+
+bool Tas::Record() {
+    if (!Settings::values.tas_enable) {
+        return true;
+    }
+    is_recording = !is_recording;
+    return is_recording;
+}
+
+void Tas::SaveRecording(bool overwrite_file) {
+    if (is_recording) {
+        return;
+    }
+    if (record_commands.empty()) {
+        return;
+    }
+    WriteTasFile(u8"record.txt");
+    if (overwrite_file) {
+        WriteTasFile(u8"script0-1.txt");
+    }
+    needs_reset = true;
+    record_commands.clear();
+}
+
+} // namespace InputCommon::TasInput
diff --git a/src/input_common/drivers/tas_input.h b/src/input_common/drivers/tas_input.h
new file mode 100644
index 0000000000..9fadc118b7
--- /dev/null
+++ b/src/input_common/drivers/tas_input.h
@@ -0,0 +1,200 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+#include "common/settings_input.h"
+#include "input_common/input_engine.h"
+#include "input_common/main.h"
+
+/*
+To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below
+Tools -> Configure TAS. The file itself has normal text format and has to be called script0-1.txt
+for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players).
+
+A script file has the same format as TAS-nx uses, so final files will look like this:
+
+1 KEY_B 0;0 0;0
+6 KEY_ZL 0;0 0;0
+41 KEY_ZL;KEY_Y 0;0 0;0
+43 KEY_X;KEY_A 32767;0 0;0
+44 KEY_A 32767;0 0;0
+45 KEY_A 32767;0 0;0
+46 KEY_A 32767;0 0;0
+47 KEY_A 32767;0 0;0
+
+After placing the file at the correct location, it can be read into Yuzu with the (default) hotkey
+CTRL+F6 (refresh). In the bottom left corner, it will display the amount of frames the script file
+has. Playback can be started or stopped using CTRL+F5.
+
+However, for playback to actually work, the correct input device has to be selected: In the Controls
+menu, select TAS from the device list for the controller that the script should be played on.
+
+Recording a new script file is really simple: Just make sure that the proper device (not TAS) is
+connected on P1, and press CTRL+F7 to start recording. When done, just press the same keystroke
+again (CTRL+F7). The new script will be saved at the location previously selected, as the filename
+record.txt.
+
+For debugging purposes, the common controller debugger can be used (View -> Debugging -> Controller
+P1).
+*/
+
+namespace InputCommon::TasInput {
+
+constexpr size_t PLAYER_NUMBER = 10;
+
+enum class TasButton : u32 {
+    BUTTON_A = 1U << 0,
+    BUTTON_B = 1U << 1,
+    BUTTON_X = 1U << 2,
+    BUTTON_Y = 1U << 3,
+    STICK_L = 1U << 4,
+    STICK_R = 1U << 5,
+    TRIGGER_L = 1U << 6,
+    TRIGGER_R = 1U << 7,
+    TRIGGER_ZL = 1U << 8,
+    TRIGGER_ZR = 1U << 9,
+    BUTTON_PLUS = 1U << 10,
+    BUTTON_MINUS = 1U << 11,
+    BUTTON_LEFT = 1U << 12,
+    BUTTON_UP = 1U << 13,
+    BUTTON_RIGHT = 1U << 14,
+    BUTTON_DOWN = 1U << 15,
+    BUTTON_SL = 1U << 16,
+    BUTTON_SR = 1U << 17,
+    BUTTON_HOME = 1U << 18,
+    BUTTON_CAPTURE = 1U << 19,
+};
+
+struct TasAnalog {
+    float x{};
+    float y{};
+};
+
+enum class TasState {
+    Running,
+    Recording,
+    Stopped,
+};
+
+class Tas final : public InputCommon::InputEngine {
+public:
+    explicit Tas(const std::string input_engine_);
+    ~Tas();
+
+    /**
+     * Changes the input status that will be stored in each frame
+     * @param buttons: bitfield with the status of the buttons
+     * @param left_axis: value of the left axis
+     * @param right_axis: value of the right axis
+     */
+    void RecordInput(u32 buttons, TasAnalog left_axis, TasAnalog right_axis);
+
+    // Main loop that records or executes input
+    void UpdateThread();
+
+    //  Sets the flag to start or stop the TAS command excecution and swaps controllers profiles
+    void StartStop();
+
+    //  Stop the TAS and reverts any controller profile
+    void Stop();
+
+    // Sets the flag to reload the file and start from the begining in the next update
+    void Reset();
+
+    /**
+     * Sets the flag to enable or disable recording of inputs
+     * @return Returns true if the current recording status is enabled
+     */
+    bool Record();
+
+    /**
+     * Saves contents of record_commands on a file
+     * @param overwrite_file: Indicates if player 1 should be overwritten
+     */
+    void SaveRecording(bool overwrite_file);
+
+    /**
+     * Returns the current status values of TAS playback/recording
+     * @return Tuple of
+     * TasState indicating the current state out of Running ;
+     * Current playback progress ;
+     * Total length of script file currently loaded or being recorded
+     */
+    std::tuple<TasState, size_t, size_t> GetStatus() const;
+
+private:
+    struct TASCommand {
+        u32 buttons{};
+        TasAnalog l_axis{};
+        TasAnalog r_axis{};
+    };
+
+    /// Loads TAS files from all players
+    void LoadTasFiles();
+
+    /** Loads TAS file from the specified player
+     * @param player_index: player number where data is going to be stored
+     */
+    void LoadTasFile(size_t player_index);
+
+    /** Writes a TAS file from the recorded commands
+     * @param file_name: name of the file to be written
+     */
+    void WriteTasFile(std::u8string file_name);
+
+    /** Inverts the Y axis polarity
+     * @param old: value of the axis
+     * @return new value of the axis
+     */
+    TasAnalog FlipAxisY(TasAnalog old);
+
+    /**
+     * Parses a string containing the axis values. X and Y have a range from -32767 to 32767
+     * @param line: string containing axis values with the following format "x;y"
+     * @return Returns a TAS analog object with axis values with range from -1.0 to 1.0
+     */
+    TasAnalog ReadCommandAxis(const std::string& line) const;
+
+    /**
+     * Parses a string containing the button values. Each button is represented by it's text format
+     * specified in text_to_tas_button array
+     * @param line: string containing button name with the following format "a;b;c;d..."
+     * @return Returns a u32 with each bit representing the status of a button
+     */
+    u32 ReadCommandButtons(const std::string& line) const;
+
+    /**
+     * Reset state of all players
+     */
+    void ClearInput();
+
+    /**
+     * Converts an u32 containing the button status into the text equivalent
+     * @param buttons: bitfield with the status of the buttons
+     * @return Returns a string with the name of the buttons to be written to the file
+     */
+    std::string WriteCommandButtons(u32 buttons) const;
+
+    /**
+     * Converts an TAS analog object containing the axis status into the text equivalent
+     * @param data: value of the axis
+     * @return A string with the value of the axis to be written to the file
+     */
+    std::string WriteCommandAxis(TasAnalog data) const;
+
+    size_t script_length{0};
+    bool is_old_input_saved{false};
+    bool is_recording{false};
+    bool is_running{false};
+    bool needs_reset{false};
+    std::array<std::vector<TASCommand>, PLAYER_NUMBER> commands{};
+    std::vector<TASCommand> record_commands{};
+    size_t current_command{0};
+    TASCommand last_input{}; // only used for recording
+};
+} // namespace InputCommon::TasInput
diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h
deleted file mode 100644
index e5de5e94fc..0000000000
--- a/src/input_common/gcadapter/gc_adapter.h
+++ /dev/null
@@ -1,168 +0,0 @@
-// Copyright 2014 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
-
-#pragma once
-#include <algorithm>
-#include <functional>
-#include <mutex>
-#include <thread>
-#include <unordered_map>
-#include "common/common_types.h"
-#include "common/threadsafe_queue.h"
-#include "input_common/main.h"
-
-struct libusb_context;
-struct libusb_device;
-struct libusb_device_handle;
-
-namespace GCAdapter {
-
-enum class PadButton {
-    Undefined = 0x0000,
-    ButtonLeft = 0x0001,
-    ButtonRight = 0x0002,
-    ButtonDown = 0x0004,
-    ButtonUp = 0x0008,
-    TriggerZ = 0x0010,
-    TriggerR = 0x0020,
-    TriggerL = 0x0040,
-    ButtonA = 0x0100,
-    ButtonB = 0x0200,
-    ButtonX = 0x0400,
-    ButtonY = 0x0800,
-    ButtonStart = 0x1000,
-    // Below is for compatibility with "AxisButton" type
-    Stick = 0x2000,
-};
-
-enum class PadAxes : u8 {
-    StickX,
-    StickY,
-    SubstickX,
-    SubstickY,
-    TriggerLeft,
-    TriggerRight,
-    Undefined,
-};
-
-enum class ControllerTypes {
-    None,
-    Wired,
-    Wireless,
-};
-
-struct GCPadStatus {
-    std::size_t port{};
-
-    PadButton button{PadButton::Undefined}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits
-
-    PadAxes axis{PadAxes::Undefined};
-    s16 axis_value{};
-    u8 axis_threshold{50};
-};
-
-struct GCController {
-    ControllerTypes type{};
-    bool enable_vibration{};
-    u8 rumble_amplitude{};
-    u16 buttons{};
-    PadButton last_button{};
-    std::array<s16, 6> axis_values{};
-    std::array<u8, 6> axis_origin{};
-    u8 reset_origin_counter{};
-};
-
-class Adapter {
-public:
-    Adapter();
-    ~Adapter();
-
-    /// Request a vibration for a controller
-    bool RumblePlay(std::size_t port, u8 amplitude);
-
-    /// Used for polling
-    void BeginConfiguration();
-    void EndConfiguration();
-
-    Common::SPSCQueue<GCPadStatus>& GetPadQueue();
-    const Common::SPSCQueue<GCPadStatus>& GetPadQueue() const;
-
-    GCController& GetPadState(std::size_t port);
-    const GCController& GetPadState(std::size_t port) const;
-
-    /// Returns true if there is a device connected to port
-    bool DeviceConnected(std::size_t port) const;
-
-    /// Used for automapping features
-    std::vector<Common::ParamPackage> GetInputDevices() const;
-    InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
-    InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
-
-private:
-    using AdapterPayload = std::array<u8, 37>;
-
-    void UpdatePadType(std::size_t port, ControllerTypes pad_type);
-    void UpdateControllers(const AdapterPayload& adapter_payload);
-    void UpdateYuzuSettings(std::size_t port);
-    void UpdateStateButtons(std::size_t port, u8 b1, u8 b2);
-    void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload);
-    void UpdateVibrations();
-
-    void AdapterInputThread();
-
-    void AdapterScanThread();
-
-    bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size);
-
-    // Updates vibration state of all controllers
-    void SendVibrations();
-
-    /// For use in initialization, querying devices to find the adapter
-    void Setup();
-
-    /// Resets status of all GC controller devices to a disconnected state
-    void ResetDevices();
-
-    /// Resets status of device connected to a disconnected state
-    void ResetDevice(std::size_t port);
-
-    /// Returns true if we successfully gain access to GC Adapter
-    bool CheckDeviceAccess();
-
-    /// Captures GC Adapter endpoint address
-    /// Returns true if the endpoint was set correctly
-    bool GetGCEndpoint(libusb_device* device);
-
-    /// For shutting down, clear all data, join all threads, release usb
-    void Reset();
-
-    // Join all threads
-    void JoinThreads();
-
-    // Release usb handles
-    void ClearLibusbHandle();
-
-    libusb_device_handle* usb_adapter_handle = nullptr;
-    std::array<GCController, 4> pads;
-    Common::SPSCQueue<GCPadStatus> pad_queue;
-
-    std::thread adapter_input_thread;
-    std::thread adapter_scan_thread;
-    bool adapter_input_thread_running;
-    bool adapter_scan_thread_running;
-    bool restart_scan_thread;
-
-    libusb_context* libusb_ctx;
-
-    u8 input_endpoint{0};
-    u8 output_endpoint{0};
-    u8 input_error_counter{0};
-    u8 output_error_counter{0};
-    int vibration_counter{0};
-
-    bool configuring{false};
-    bool rumble_enabled{true};
-    bool vibration_changed{true};
-};
-} // namespace GCAdapter
diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp
deleted file mode 100644
index 1b6ded8d60..0000000000
--- a/src/input_common/gcadapter/gc_poller.cpp
+++ /dev/null
@@ -1,356 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <atomic>
-#include <list>
-#include <mutex>
-#include <utility>
-#include "common/assert.h"
-#include "common/threadsafe_queue.h"
-#include "input_common/gcadapter/gc_adapter.h"
-#include "input_common/gcadapter/gc_poller.h"
-
-namespace InputCommon {
-
-class GCButton final : public Input::ButtonDevice {
-public:
-    explicit GCButton(u32 port_, s32 button_, const GCAdapter::Adapter* adapter)
-        : port(port_), button(button_), gcadapter(adapter) {}
-
-    ~GCButton() override;
-
-    bool GetStatus() const override {
-        if (gcadapter->DeviceConnected(port)) {
-            return (gcadapter->GetPadState(port).buttons & button) != 0;
-        }
-        return false;
-    }
-
-private:
-    const u32 port;
-    const s32 button;
-    const GCAdapter::Adapter* gcadapter;
-};
-
-class GCAxisButton final : public Input::ButtonDevice {
-public:
-    explicit GCAxisButton(u32 port_, u32 axis_, float threshold_, bool trigger_if_greater_,
-                          const GCAdapter::Adapter* adapter)
-        : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
-          gcadapter(adapter) {}
-
-    bool GetStatus() const override {
-        if (gcadapter->DeviceConnected(port)) {
-            const float current_axis_value = gcadapter->GetPadState(port).axis_values.at(axis);
-            const float axis_value = current_axis_value / 128.0f;
-            if (trigger_if_greater) {
-                // TODO: Might be worthwile to set a slider for the trigger threshold. It is
-                // currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick
-                return axis_value > threshold;
-            }
-            return axis_value < -threshold;
-        }
-        return false;
-    }
-
-private:
-    const u32 port;
-    const u32 axis;
-    float threshold;
-    bool trigger_if_greater;
-    const GCAdapter::Adapter* gcadapter;
-};
-
-GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
-    : adapter(std::move(adapter_)) {}
-
-GCButton::~GCButton() = default;
-
-std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) {
-    const auto button_id = params.Get("button", 0);
-    const auto port = static_cast<u32>(params.Get("port", 0));
-
-    constexpr s32 PAD_STICK_ID = static_cast<s32>(GCAdapter::PadButton::Stick);
-
-    // button is not an axis/stick button
-    if (button_id != PAD_STICK_ID) {
-        return std::make_unique<GCButton>(port, button_id, adapter.get());
-    }
-
-    // For Axis buttons, used by the binary sticks.
-    if (button_id == PAD_STICK_ID) {
-        const int axis = params.Get("axis", 0);
-        const float threshold = params.Get("threshold", 0.25f);
-        const std::string direction_name = params.Get("direction", "");
-        bool trigger_if_greater;
-        if (direction_name == "+") {
-            trigger_if_greater = true;
-        } else if (direction_name == "-") {
-            trigger_if_greater = false;
-        } else {
-            trigger_if_greater = true;
-            LOG_ERROR(Input, "Unknown direction {}", direction_name);
-        }
-        return std::make_unique<GCAxisButton>(port, axis, threshold, trigger_if_greater,
-                                              adapter.get());
-    }
-
-    return nullptr;
-}
-
-Common::ParamPackage GCButtonFactory::GetNextInput() const {
-    Common::ParamPackage params;
-    GCAdapter::GCPadStatus pad;
-    auto& queue = adapter->GetPadQueue();
-    while (queue.Pop(pad)) {
-        // This while loop will break on the earliest detected button
-        params.Set("engine", "gcpad");
-        params.Set("port", static_cast<s32>(pad.port));
-        if (pad.button != GCAdapter::PadButton::Undefined) {
-            params.Set("button", static_cast<u16>(pad.button));
-        }
-
-        // For Axis button implementation
-        if (pad.axis != GCAdapter::PadAxes::Undefined) {
-            params.Set("axis", static_cast<u8>(pad.axis));
-            params.Set("button", static_cast<u16>(GCAdapter::PadButton::Stick));
-            params.Set("threshold", "0.25");
-            if (pad.axis_value > 0) {
-                params.Set("direction", "+");
-            } else {
-                params.Set("direction", "-");
-            }
-            break;
-        }
-    }
-    return params;
-}
-
-void GCButtonFactory::BeginConfiguration() {
-    polling = true;
-    adapter->BeginConfiguration();
-}
-
-void GCButtonFactory::EndConfiguration() {
-    polling = false;
-    adapter->EndConfiguration();
-}
-
-class GCAnalog final : public Input::AnalogDevice {
-public:
-    explicit GCAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_,
-                      float deadzone_, float range_, const GCAdapter::Adapter* adapter)
-        : port(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_),
-          deadzone(deadzone_), range(range_), gcadapter(adapter) {}
-
-    float GetAxis(u32 axis) const {
-        if (gcadapter->DeviceConnected(port)) {
-            std::lock_guard lock{mutex};
-            const auto axis_value =
-                static_cast<float>(gcadapter->GetPadState(port).axis_values.at(axis));
-            return (axis_value) / (100.0f * range);
-        }
-        return 0.0f;
-    }
-
-    std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {
-        float x = GetAxis(analog_axis_x);
-        float y = GetAxis(analog_axis_y);
-        if (invert_x) {
-            x = -x;
-        }
-        if (invert_y) {
-            y = -y;
-        }
-        // Make sure the coordinates are in the unit circle,
-        // otherwise normalize it.
-        float r = x * x + y * y;
-        if (r > 1.0f) {
-            r = std::sqrt(r);
-            x /= r;
-            y /= r;
-        }
-
-        return {x, y};
-    }
-
-    std::tuple<float, float> GetStatus() const override {
-        const auto [x, y] = GetAnalog(axis_x, axis_y);
-        const float r = std::sqrt((x * x) + (y * y));
-        if (r > deadzone) {
-            return {x / r * (r - deadzone) / (1 - deadzone),
-                    y / r * (r - deadzone) / (1 - deadzone)};
-        }
-        return {0.0f, 0.0f};
-    }
-
-    std::tuple<float, float> GetRawStatus() const override {
-        const float x = GetAxis(axis_x);
-        const float y = GetAxis(axis_y);
-        return {x, y};
-    }
-
-    Input::AnalogProperties GetAnalogProperties() const override {
-        return {deadzone, range, 0.5f};
-    }
-
-    bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
-        const auto [x, y] = GetStatus();
-        const float directional_deadzone = 0.5f;
-        switch (direction) {
-        case Input::AnalogDirection::RIGHT:
-            return x > directional_deadzone;
-        case Input::AnalogDirection::LEFT:
-            return x < -directional_deadzone;
-        case Input::AnalogDirection::UP:
-            return y > directional_deadzone;
-        case Input::AnalogDirection::DOWN:
-            return y < -directional_deadzone;
-        }
-        return false;
-    }
-
-private:
-    const u32 port;
-    const u32 axis_x;
-    const u32 axis_y;
-    const bool invert_x;
-    const bool invert_y;
-    const float deadzone;
-    const float range;
-    const GCAdapter::Adapter* gcadapter;
-    mutable std::mutex mutex;
-};
-
-/// An analog device factory that creates analog devices from GC Adapter
-GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
-    : adapter(std::move(adapter_)) {}
-
-/**
- * Creates analog device from joystick axes
- * @param params contains parameters for creating the device:
- *     - "port": the nth gcpad on the adapter
- *     - "axis_x": the index of the axis to be bind as x-axis
- *     - "axis_y": the index of the axis to be bind as y-axis
- */
-std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) {
-    const auto port = static_cast<u32>(params.Get("port", 0));
-    const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
-    const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
-    const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
-    const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
-    const std::string invert_x_value = params.Get("invert_x", "+");
-    const std::string invert_y_value = params.Get("invert_y", "+");
-    const bool invert_x = invert_x_value == "-";
-    const bool invert_y = invert_y_value == "-";
-
-    return std::make_unique<GCAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range,
-                                      adapter.get());
-}
-
-void GCAnalogFactory::BeginConfiguration() {
-    polling = true;
-    adapter->BeginConfiguration();
-}
-
-void GCAnalogFactory::EndConfiguration() {
-    polling = false;
-    adapter->EndConfiguration();
-}
-
-Common::ParamPackage GCAnalogFactory::GetNextInput() {
-    GCAdapter::GCPadStatus pad;
-    Common::ParamPackage params;
-    auto& queue = adapter->GetPadQueue();
-    while (queue.Pop(pad)) {
-        if (pad.button != GCAdapter::PadButton::Undefined) {
-            params.Set("engine", "gcpad");
-            params.Set("port", static_cast<s32>(pad.port));
-            params.Set("button", static_cast<u16>(pad.button));
-            return params;
-        }
-        if (pad.axis == GCAdapter::PadAxes::Undefined ||
-            std::abs(static_cast<float>(pad.axis_value) / 128.0f) < 0.1f) {
-            continue;
-        }
-        // An analog device needs two axes, so we need to store the axis for later and wait for
-        // a second input event. The axes also must be from the same joystick.
-        const u8 axis = static_cast<u8>(pad.axis);
-        if (axis == 0 || axis == 1) {
-            analog_x_axis = 0;
-            analog_y_axis = 1;
-            controller_number = static_cast<s32>(pad.port);
-            break;
-        }
-        if (axis == 2 || axis == 3) {
-            analog_x_axis = 2;
-            analog_y_axis = 3;
-            controller_number = static_cast<s32>(pad.port);
-            break;
-        }
-
-        if (analog_x_axis == -1) {
-            analog_x_axis = axis;
-            controller_number = static_cast<s32>(pad.port);
-        } else if (analog_y_axis == -1 && analog_x_axis != axis &&
-                   controller_number == static_cast<s32>(pad.port)) {
-            analog_y_axis = axis;
-            break;
-        }
-    }
-    if (analog_x_axis != -1 && analog_y_axis != -1) {
-        params.Set("engine", "gcpad");
-        params.Set("port", controller_number);
-        params.Set("axis_x", analog_x_axis);
-        params.Set("axis_y", analog_y_axis);
-        params.Set("invert_x", "+");
-        params.Set("invert_y", "+");
-        analog_x_axis = -1;
-        analog_y_axis = -1;
-        controller_number = -1;
-        return params;
-    }
-    return params;
-}
-
-class GCVibration final : public Input::VibrationDevice {
-public:
-    explicit GCVibration(u32 port_, GCAdapter::Adapter* adapter)
-        : port(port_), gcadapter(adapter) {}
-
-    u8 GetStatus() const override {
-        return gcadapter->RumblePlay(port, 0);
-    }
-
-    bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high,
-                       [[maybe_unused]] f32 freq_high) const override {
-        const auto mean_amplitude = (amp_low + amp_high) * 0.5f;
-        const auto processed_amplitude =
-            static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8);
-
-        return gcadapter->RumblePlay(port, processed_amplitude);
-    }
-
-private:
-    const u32 port;
-    GCAdapter::Adapter* gcadapter;
-};
-
-/// An vibration device factory that creates vibration devices from GC Adapter
-GCVibrationFactory::GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
-    : adapter(std::move(adapter_)) {}
-
-/**
- * Creates a vibration device from a joystick
- * @param params contains parameters for creating the device:
- *     - "port": the nth gcpad on the adapter
- */
-std::unique_ptr<Input::VibrationDevice> GCVibrationFactory::Create(
-    const Common::ParamPackage& params) {
-    const auto port = static_cast<u32>(params.Get("port", 0));
-
-    return std::make_unique<GCVibration>(port, adapter.get());
-}
-
-} // namespace InputCommon
diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h
deleted file mode 100644
index d1271e3ead..0000000000
--- a/src/input_common/gcadapter/gc_poller.h
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include "core/frontend/input.h"
-#include "input_common/gcadapter/gc_adapter.h"
-
-namespace InputCommon {
-
-/**
- * A button device factory representing a gcpad. It receives gcpad events and forward them
- * to all button devices it created.
- */
-class GCButtonFactory final : public Input::Factory<Input::ButtonDevice> {
-public:
-    explicit GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
-
-    /**
-     * Creates a button device from a button press
-     * @param params contains parameters for creating the device:
-     *     - "code": the code of the key to bind with the button
-     */
-    std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
-
-    Common::ParamPackage GetNextInput() const;
-
-    /// For device input configuration/polling
-    void BeginConfiguration();
-    void EndConfiguration();
-
-    bool IsPolling() const {
-        return polling;
-    }
-
-private:
-    std::shared_ptr<GCAdapter::Adapter> adapter;
-    bool polling = false;
-};
-
-/// An analog device factory that creates analog devices from GC Adapter
-class GCAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
-public:
-    explicit GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
-
-    std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
-    Common::ParamPackage GetNextInput();
-
-    /// For device input configuration/polling
-    void BeginConfiguration();
-    void EndConfiguration();
-
-    bool IsPolling() const {
-        return polling;
-    }
-
-private:
-    std::shared_ptr<GCAdapter::Adapter> adapter;
-    int analog_x_axis = -1;
-    int analog_y_axis = -1;
-    int controller_number = -1;
-    bool polling = false;
-};
-
-/// A vibration device factory creates vibration devices from GC Adapter
-class GCVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
-public:
-    explicit GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
-
-    std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override;
-
-private:
-    std::shared_ptr<GCAdapter::Adapter> adapter;
-};
-
-} // namespace InputCommon