From a994a404676feed8c812e66edf3d055af8bc10e4 Mon Sep 17 00:00:00 2001
From: german <german@thesoftwareartisans.com>
Date: Sat, 6 Feb 2021 11:53:25 -0600
Subject: [PATCH 1/2] hid: Implement GC controller

---
 src/core/frontend/applets/controller.h        |   1 +
 src/core/hle/service/hid/controllers/npad.cpp |  63 +++++++++++
 src/core/hle/service/hid/controllers/npad.h   |  26 ++++-
 src/input_common/settings.h                   |   1 +
 src/yuzu/applets/controller.cpp               |   9 +-
 .../configuration/configure_input_player.cpp  | 106 +++++++++++++++++-
 .../configuration/configure_input_player.h    |   6 +
 7 files changed, 206 insertions(+), 6 deletions(-)

diff --git a/src/core/frontend/applets/controller.h b/src/core/frontend/applets/controller.h
index dff71d8d9..b0626a0f9 100644
--- a/src/core/frontend/applets/controller.h
+++ b/src/core/frontend/applets/controller.h
@@ -31,6 +31,7 @@ struct ControllerParameters {
     bool allow_dual_joycons{};
     bool allow_left_joycon{};
     bool allow_right_joycon{};
+    bool allow_gamecube_controller{};
 };
 
 class ControllerApplet {
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index dbf198345..70b9f3824 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -21,6 +21,7 @@
 
 namespace Service::HID {
 constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
+constexpr s32 HID_TRIGGER_MAX = 0x7fff;
 [[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
 constexpr std::size_t NPAD_OFFSET = 0x9A00;
 constexpr u32 BATTERY_FULL = 2;
@@ -48,6 +49,8 @@ Controller_NPad::NPadControllerType Controller_NPad::MapSettingsTypeToNPad(
         return NPadControllerType::JoyRight;
     case Settings::ControllerType::Handheld:
         return NPadControllerType::Handheld;
+    case Settings::ControllerType::GameCube:
+        return NPadControllerType::GameCube;
     default:
         UNREACHABLE();
         return NPadControllerType::ProController;
@@ -67,6 +70,8 @@ Settings::ControllerType Controller_NPad::MapNPadToSettingsType(
         return Settings::ControllerType::RightJoycon;
     case NPadControllerType::Handheld:
         return Settings::ControllerType::Handheld;
+    case NPadControllerType::GameCube:
+        return Settings::ControllerType::GameCube;
     default:
         UNREACHABLE();
         return Settings::ControllerType::ProController;
@@ -209,6 +214,13 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
         controller.assignment_mode = NpadAssignments::Single;
         controller.footer_type = AppletFooterUiType::JoyRightHorizontal;
         break;
+    case NPadControllerType::GameCube:
+        controller.style_set.gamecube.Assign(1);
+        // The GC Controller behaves like a wired Pro Controller
+        controller.device_type.fullkey.Assign(1);
+        controller.system_properties.is_vertical.Assign(1);
+        controller.system_properties.use_plus.Assign(1);
+        break;
     case NPadControllerType::Pokeball:
         controller.style_set.palma.Assign(1);
         controller.device_type.palma.Assign(1);
@@ -259,6 +271,7 @@ void Controller_NPad::OnInit() {
         style.joycon_right.Assign(1);
         style.joycon_dual.Assign(1);
         style.fullkey.Assign(1);
+        style.gamecube.Assign(1);
         style.palma.Assign(1);
     }
 
@@ -339,6 +352,7 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
     auto& pad_state = npad_pad_states[controller_idx].pad_states;
     auto& lstick_entry = npad_pad_states[controller_idx].l_stick;
     auto& rstick_entry = npad_pad_states[controller_idx].r_stick;
+    auto& trigger_entry = npad_trigger_states[controller_idx];
     const auto& button_state = buttons[controller_idx];
     const auto& analog_state = sticks[controller_idx];
     const auto [stick_l_x_f, stick_l_y_f] =
@@ -404,6 +418,17 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
         pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus());
         pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus());
     }
+
+    if (controller_type == NPadControllerType::GameCube) {
+        trigger_entry.l_analog = static_cast<s32>(
+            button_state[ZL - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0);
+        trigger_entry.r_analog = static_cast<s32>(
+            button_state[ZR - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0);
+        pad_state.zl.Assign(false);
+        pad_state.zr.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus());
+        pad_state.l.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus());
+        pad_state.r.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus());
+    }
 }
 
 void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
@@ -418,6 +443,11 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
             &npad.joy_left_states,  &npad.joy_right_states, &npad.palma_states,
             &npad.system_ext_states};
 
+        // There is the posibility to have more controllers with analog triggers
+        const std::array<TriggerGeneric*, 1> controller_triggers{
+            &npad.gc_trigger_states,
+        };
+
         for (auto* main_controller : controller_npads) {
             main_controller->common.entry_count = 16;
             main_controller->common.total_entry_count = 17;
@@ -435,6 +465,21 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
             cur_entry.timestamp2 = cur_entry.timestamp;
         }
 
+        for (auto* analog_trigger : controller_triggers) {
+            analog_trigger->entry_count = 16;
+            analog_trigger->total_entry_count = 17;
+
+            const auto& last_entry = analog_trigger->trigger[analog_trigger->last_entry_index];
+
+            analog_trigger->timestamp = core_timing.GetCPUTicks();
+            analog_trigger->last_entry_index = (analog_trigger->last_entry_index + 1) % 17;
+
+            auto& cur_entry = analog_trigger->trigger[analog_trigger->last_entry_index];
+
+            cur_entry.timestamp = last_entry.timestamp + 1;
+            cur_entry.timestamp2 = cur_entry.timestamp;
+        }
+
         const auto& controller_type = connected_controllers[i].type;
 
         if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) {
@@ -444,6 +489,7 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
 
         RequestPadStateUpdate(npad_index);
         auto& pad_state = npad_pad_states[npad_index];
+        auto& trigger_state = npad_trigger_states[npad_index];
 
         auto& main_controller =
             npad.fullkey_states.npad[npad.fullkey_states.common.last_entry_index];
@@ -456,6 +502,8 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
         auto& pokeball_entry = npad.palma_states.npad[npad.palma_states.common.last_entry_index];
         auto& libnx_entry =
             npad.system_ext_states.npad[npad.system_ext_states.common.last_entry_index];
+        auto& trigger_entry =
+            npad.gc_trigger_states.trigger[npad.gc_trigger_states.last_entry_index];
 
         libnx_entry.connection_status.raw = 0;
         libnx_entry.connection_status.is_connected.Assign(1);
@@ -524,6 +572,18 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
 
             libnx_entry.connection_status.is_right_connected.Assign(1);
             break;
+        case NPadControllerType::GameCube:
+            main_controller.connection_status.raw = 0;
+            main_controller.connection_status.is_connected.Assign(1);
+            main_controller.connection_status.is_wired.Assign(1);
+            main_controller.pad.pad_states.raw = pad_state.pad_states.raw;
+            main_controller.pad.l_stick = pad_state.l_stick;
+            main_controller.pad.r_stick = pad_state.r_stick;
+            trigger_entry.l_analog = trigger_state.l_analog;
+            trigger_entry.r_analog = trigger_state.r_analog;
+
+            libnx_entry.connection_status.is_wired.Assign(1);
+            break;
         case NPadControllerType::Pokeball:
             pokeball_entry.connection_status.raw = 0;
             pokeball_entry.connection_status.is_connected.Assign(1);
@@ -674,6 +734,7 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
                 right_sixaxis_entry.orientation = motion_devices[1].orientation;
             }
             break;
+        case NPadControllerType::GameCube:
         case NPadControllerType::Pokeball:
             break;
         }
@@ -1135,6 +1196,8 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const
             return style.joycon_left;
         case NPadControllerType::JoyRight:
             return style.joycon_right;
+        case NPadControllerType::GameCube:
+            return style.gamecube;
         case NPadControllerType::Pokeball:
             return style.palma;
         default:
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 48bab988c..bc2e6779d 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -51,6 +51,7 @@ public:
         JoyDual,
         JoyLeft,
         JoyRight,
+        GameCube,
         Pokeball,
     };
 
@@ -60,6 +61,7 @@ public:
         JoyconDual = 5,
         JoyconLeft = 6,
         JoyconRight = 7,
+        GameCube = 8,
         Pokeball = 9,
         MaxNpadType = 10,
     };
@@ -389,6 +391,25 @@ private:
     };
     static_assert(sizeof(SixAxisGeneric) == 0x708, "SixAxisGeneric is an invalid size");
 
+    struct TriggerState {
+        s64_le timestamp{};
+        s64_le timestamp2{};
+        s32_le l_analog{};
+        s32_le r_analog{};
+    };
+    static_assert(sizeof(TriggerState) == 0x18, "TriggerState is an invalid size");
+
+    struct TriggerGeneric {
+        INSERT_PADDING_BYTES(0x4);
+        s64_le timestamp;
+        INSERT_PADDING_BYTES(0x4);
+        s64_le total_entry_count;
+        s64_le last_entry_index;
+        s64_le entry_count;
+        std::array<TriggerState, 17> trigger{};
+    };
+    static_assert(sizeof(TriggerGeneric) == 0x1C8, "TriggerGeneric is an invalid size");
+
     struct NPadSystemProperties {
         union {
             s64_le raw{};
@@ -509,7 +530,9 @@ private:
         AppletFooterUiType footer_type;
         // nfc_states needs to be checked switchbrew does not match with HW
         NfcXcdHandle nfc_states;
-        INSERT_PADDING_BYTES(0xdef);
+        INSERT_PADDING_BYTES(0x8); // Mutex
+        TriggerGeneric gc_trigger_states;
+        INSERT_PADDING_BYTES(0xc1f);
     };
     static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size");
 
@@ -560,6 +583,7 @@ private:
     f32 sixaxis_fusion_parameter2{};
     bool sixaxis_at_rest{true};
     std::array<ControllerPad, 10> npad_pad_states{};
+    std::array<TriggerState, 10> npad_trigger_states{};
     bool is_in_lr_assignment_mode{false};
     Core::System& system;
 };
diff --git a/src/input_common/settings.h b/src/input_common/settings.h
index 75486554b..a59f5d461 100644
--- a/src/input_common/settings.h
+++ b/src/input_common/settings.h
@@ -340,6 +340,7 @@ enum class ControllerType {
     LeftJoycon,
     RightJoycon,
     Handheld,
+    GameCube,
 };
 
 struct PlayerInput {
diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp
index c680fd2c2..b92cd6886 100644
--- a/src/yuzu/applets/controller.cpp
+++ b/src/yuzu/applets/controller.cpp
@@ -67,6 +67,8 @@ bool IsControllerCompatible(Settings::ControllerType controller_type,
         return parameters.allow_right_joycon;
     case Settings::ControllerType::Handheld:
         return parameters.enable_single_mode && parameters.allow_handheld;
+    case Settings::ControllerType::GameCube:
+        return parameters.allow_gamecube_controller;
     default:
         return false;
     }
@@ -370,7 +372,7 @@ void QtControllerSelectorDialog::SetSupportedControllers() {
             QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme));
     }
 
-    if (parameters.allow_pro_controller) {
+    if (parameters.allow_pro_controller || parameters.allow_gamecube_controller) {
         ui->controllerSupported5->setStyleSheet(
             QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme));
     } else {
@@ -420,6 +422,10 @@ void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index
                            Settings::ControllerType::Handheld);
         emulated_controllers[player_index]->addItem(tr("Handheld"));
     }
+
+    pairs.emplace_back(emulated_controllers[player_index]->count(),
+                       Settings::ControllerType::GameCube);
+    emulated_controllers[player_index]->addItem(tr("GameCube Controller"));
 }
 
 Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex(
@@ -461,6 +467,7 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index)
         switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(),
                                            player_index)) {
         case Settings::ControllerType::ProController:
+        case Settings::ControllerType::GameCube:
             return QStringLiteral("image: url(:/controller/applet_pro_controller%0); ");
         case Settings::ControllerType::DualJoyconDetached:
             return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ");
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index c9d19c948..21d0d3449 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -467,10 +467,14 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
 
     UpdateControllerIcon();
     UpdateControllerAvailableButtons();
+    UpdateControllerEnabledButtons();
+    UpdateControllerButtonNames();
     UpdateMotionButtons();
     connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) {
         UpdateControllerIcon();
         UpdateControllerAvailableButtons();
+        UpdateControllerEnabledButtons();
+        UpdateControllerButtonNames();
         UpdateMotionButtons();
     });
 
@@ -558,9 +562,6 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
             &ConfigureInputPlayer::SaveProfile);
 
     LoadConfiguration();
-
-    // TODO(wwylele): enable this when we actually emulate it
-    ui->buttonHome->setEnabled(false);
     ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
     ui->controllerFrame->SetConnectedStatus(ui->groupConnectedController->isChecked());
 }
@@ -924,6 +925,12 @@ void ConfigureInputPlayer::SetConnectableControllers() {
                                                      Settings::ControllerType::Handheld);
             ui->comboControllerType->addItem(tr("Handheld"));
         }
+
+        if (enable_all || npad_style_set.gamecube == 1) {
+            index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
+                                                     Settings::ControllerType::GameCube);
+            ui->comboControllerType->addItem(tr("GameCube Controller"));
+        }
     };
 
     Core::System& system{Core::System::GetInstance()};
@@ -1014,7 +1021,7 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
 
     // List of all the widgets that will be hidden by any of the following layouts that need
     // "unhidden" after the controller type changes
-    const std::array<QWidget*, 9> layout_show = {
+    const std::array<QWidget*, 11> layout_show = {
         ui->buttonShoulderButtonsSLSR,
         ui->horizontalSpacerShoulderButtonsWidget,
         ui->horizontalSpacerShoulderButtonsWidget2,
@@ -1024,6 +1031,8 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
         ui->buttonShoulderButtonsRight,
         ui->buttonMiscButtonsPlusHome,
         ui->bottomRight,
+        ui->buttonMiscButtonsMinusGroup,
+        ui->buttonMiscButtonsScreenshotGroup,
     };
 
     for (auto* widget : layout_show) {
@@ -1056,6 +1065,14 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
             ui->bottomLeft,
         };
         break;
+    case Settings::ControllerType::GameCube:
+        layout_hidden = {
+            ui->buttonShoulderButtonsSLSR,
+            ui->horizontalSpacerShoulderButtonsWidget2,
+            ui->buttonMiscButtonsMinusGroup,
+            ui->buttonMiscButtonsScreenshotGroup,
+        };
+        break;
     }
 
     for (auto* widget : layout_hidden) {
@@ -1063,6 +1080,52 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
     }
 }
 
+void ConfigureInputPlayer::UpdateControllerEnabledButtons() {
+    auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
+    if (debug) {
+        layout = Settings::ControllerType::ProController;
+    }
+
+    // List of all the widgets that will be disabled by any of the following layouts that need
+    // "enabled" after the controller type changes
+    const std::array<QWidget*, 4> layout_enable = {
+        ui->buttonHome,
+        ui->buttonLStickPressedGroup,
+        ui->groupRStickPressed,
+        ui->buttonShoulderButtonsButtonLGroup,
+    };
+
+    for (auto* widget : layout_enable) {
+        widget->setEnabled(true);
+    }
+
+    std::vector<QWidget*> layout_disable;
+    switch (layout) {
+    case Settings::ControllerType::ProController:
+    case Settings::ControllerType::DualJoyconDetached:
+    case Settings::ControllerType::Handheld:
+    case Settings::ControllerType::LeftJoycon:
+    case Settings::ControllerType::RightJoycon:
+        // TODO(wwylele): enable this when we actually emulate it
+        layout_disable = {
+            ui->buttonHome,
+        };
+        break;
+    case Settings::ControllerType::GameCube:
+        layout_disable = {
+            ui->buttonHome,
+            ui->buttonLStickPressedGroup,
+            ui->groupRStickPressed,
+            ui->buttonShoulderButtonsButtonLGroup,
+        };
+        break;
+    }
+
+    for (auto* widget : layout_disable) {
+        widget->setEnabled(false);
+    }
+}
+
 void ConfigureInputPlayer::UpdateMotionButtons() {
     if (debug) {
         // Motion isn't used with the debug controller, hide both groupboxes.
@@ -1085,6 +1148,11 @@ void ConfigureInputPlayer::UpdateMotionButtons() {
         ui->buttonMotionLeftGroup->hide();
         ui->buttonMotionRightGroup->show();
         break;
+    case Settings::ControllerType::GameCube:
+        // Hide both "Motion 1/2".
+        ui->buttonMotionLeftGroup->hide();
+        ui->buttonMotionRightGroup->hide();
+        break;
     case Settings::ControllerType::DualJoyconDetached:
     default:
         // Show both "Motion 1/2".
@@ -1094,6 +1162,36 @@ void ConfigureInputPlayer::UpdateMotionButtons() {
     }
 }
 
+void ConfigureInputPlayer::UpdateControllerButtonNames() {
+    auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
+    if (debug) {
+        layout = Settings::ControllerType::ProController;
+    }
+
+    switch (layout) {
+    case Settings::ControllerType::ProController:
+    case Settings::ControllerType::DualJoyconDetached:
+    case Settings::ControllerType::Handheld:
+    case Settings::ControllerType::LeftJoycon:
+    case Settings::ControllerType::RightJoycon:
+        ui->buttonMiscButtonsPlusGroup->setTitle(tr("Plus"));
+        ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("ZL"));
+        ui->buttonShoulderButtonsZRGroup->setTitle(tr("ZR"));
+        ui->buttonShoulderButtonsRGroup->setTitle(tr("R"));
+        ui->LStick->setTitle(tr("Left Stick"));
+        ui->RStick->setTitle(tr("Right Stick"));
+        break;
+    case Settings::ControllerType::GameCube:
+        ui->buttonMiscButtonsPlusGroup->setTitle(tr("Start / Pause"));
+        ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("L"));
+        ui->buttonShoulderButtonsZRGroup->setTitle(tr("R"));
+        ui->buttonShoulderButtonsRGroup->setTitle(tr("Z"));
+        ui->LStick->setTitle(tr("Control Stick"));
+        ui->RStick->setTitle(tr("C-Stick"));
+        break;
+    }
+}
+
 void ConfigureInputPlayer::UpdateMappingWithDefaults() {
     if (ui->comboDevices->currentIndex() == 0) {
         return;
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index da2b89136..efe953fbc 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -143,9 +143,15 @@ private:
     /// Hides and disables controller settings based on the current controller type.
     void UpdateControllerAvailableButtons();
 
+    /// Disables controller settings based on the current controller type.
+    void UpdateControllerEnabledButtons();
+
     /// Shows or hides motion groupboxes based on the current controller type.
     void UpdateMotionButtons();
 
+    /// Alters the button names based on the current controller type.
+    void UpdateControllerButtonNames();
+
     /// Gets the default controller mapping for this device and auto configures the input to match.
     void UpdateMappingWithDefaults();
 

From bcd4e4f650a0d32b94ccf7b724de67d6ddefbee2 Mon Sep 17 00:00:00 2001
From: german <german@thesoftwareartisans.com>
Date: Tue, 9 Feb 2021 08:12:21 -0600
Subject: [PATCH 2/2] Use GC image

---
 src/yuzu/configuration/configure_input_player_widget.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp
index e3e8bde48..b8bcb44a4 100644
--- a/src/yuzu/configuration/configure_input_player_widget.cpp
+++ b/src/yuzu/configuration/configure_input_player_widget.cpp
@@ -226,6 +226,9 @@ void PlayerControlPreview::paintEvent(QPaintEvent* event) {
     case Settings::ControllerType::RightJoycon:
         DrawRightController(p, center);
         break;
+    case Settings::ControllerType::GameCube:
+        DrawGCController(p, center);
+        break;
     case Settings::ControllerType::ProController:
     default:
         DrawProController(p, center);