diff --git a/src/core/frontend/applets/controller.h b/src/core/frontend/applets/controller.h index dff71d8d98..b0626a0f9c 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 dbf1983454..70b9f38248 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( + button_state[ZL - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0); + trigger_entry.r_analog = static_cast( + 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 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 48bab988c6..bc2e6779d4 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 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 npad_pad_states{}; + std::array 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 75486554b6..a59f5d4612 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 c680fd2c21..b92cd6886f 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 c9d19c948c..21d0d34499 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(&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 layout_show = { + const std::array 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 layout_enable = { + ui->buttonHome, + ui->buttonLStickPressedGroup, + ui->groupRStickPressed, + ui->buttonShoulderButtonsButtonLGroup, + }; + + for (auto* widget : layout_enable) { + widget->setEnabled(true); + } + + std::vector 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 da2b891368..efe953fbc9 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();