From 7843915b7161eb90f9e41bc4383af285984d794e Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Thu, 12 Oct 2023 19:17:44 -0600 Subject: [PATCH] rebase to latest master --- src/core/hle/service/hid/hid.cpp | 2 +- src/core/hle/service/hid/hid_server.cpp | 90 +-- .../hle/service/hid/hid_system_server.cpp | 40 +- src/core/hle/service/hid/resource_manager.cpp | 1 - .../service/hid/resource_manager/gesture.h | 3 +- .../service/hid/resource_manager/keyboard.h | 3 +- .../hle/service/hid/resource_manager/mouse.h | 3 +- .../resource_manager/npad_resource/npad.cpp | 2 +- .../hid/resource_manager/npad_resource/npad.h | 4 +- src/yuzu/applets/qt_controller.h | 2 +- src/yuzu/main.cpp | 742 ++++++++++++++---- 11 files changed, 661 insertions(+), 231 deletions(-) diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index d83c2212d..6c1337e95 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -2,13 +2,13 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "core/core.h" -#include "core/hle/service/hid/resource_manager.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/hid/hid_debug_server.h" #include "core/hle/service/hid/hid_server.h" #include "core/hle/service/hid/hid_system_server.h" #include "core/hle/service/hid/hidbus.h" #include "core/hle/service/hid/irs.h" +#include "core/hle/service/hid/resource_manager.h" #include "core/hle/service/hid/xcd.h" #include "core/hle/service/server_manager.h" diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp index 6a47d38be..b278b1413 100644 --- a/src/core/hle/service/hid/hid_server.cpp +++ b/src/core/hle/service/hid/hid_server.cpp @@ -241,7 +241,7 @@ void IHidServer::CreateAppletResource(HLERequestContext& ctx) { if (applet_resource == nullptr) { applet_resource = std::make_shared(system); - // applet_resource->Initialize(); + // applet_resource->Initialize(); } IPC::ResponseBuilder rb{ctx, 2, 0, 1}; @@ -325,8 +325,7 @@ void IHidServer::ActivateKeyboard(HLERequestContext& ctx) { rb.Push(result); } -void IHidServer::SendKeyboardLockKeyEvent(HLERequestContext& ctx) { -} +void IHidServer::SendKeyboardLockKeyEvent(HLERequestContext& ctx) {} void IHidServer::AcquireXpadIdEventHandle(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; @@ -1251,7 +1250,7 @@ void IHidServer::SetSupportedNpadStyleSet(HLERequestContext& ctx) { const auto npad = GetResourceManager()->GetNpad(); const Result result = npad->SetSupportedNpadStyleSet(parameters.applet_resource_user_id, - parameters.supported_style_set); + parameters.supported_style_set); if (result.IsSuccess()) { NpadStyleTag style_tag{parameters.supported_style_set}; @@ -2129,11 +2128,11 @@ void IHidServer::ActivateConsoleSixAxisSensor(HLERequestContext& ctx) { Result result = ResultSuccess; auto sixaxis = GetResourceManager()->GetConsoleSixAxis(); - //if (IsDeviceManaged()) { - // result = sixaxis->Activate(applet_resource_user_id); - //} else { - // result = sixaxis->Activate(); - //} + // if (IsDeviceManaged()) { + // result = sixaxis->Activate(applet_resource_user_id); + // } else { + // result = sixaxis->Activate(); + // } IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); @@ -2178,11 +2177,11 @@ void IHidServer::ActivateSevenSixAxisSensor(HLERequestContext& ctx) { Result result = ResultSuccess; auto sixaxis = GetResourceManager()->GetConsoleSixAxis(); - //if (IsDeviceManaged()) { - // result = sixaxis->Activate(applet_resource_user_id); - //} else { - // result = sixaxis->Activate(); - //} + // if (IsDeviceManaged()) { + // result = sixaxis->Activate(applet_resource_user_id); + // } else { + // result = sixaxis->Activate(); + // } IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); @@ -2195,18 +2194,18 @@ void IHidServer::StartSevenSixAxisSensor(HLERequestContext& ctx) { LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", applet_resource_user_id); - //std::shared_ptr state = nullptr; - //const auto sixaxis = GetResourceManager()->GetSevenSixAxis(); - //Result result = sixaxis->GetSensorState(state, parameters.applet_resource_user_id, - // parameters.sixaxis_handle); + // std::shared_ptr state = nullptr; + // const auto sixaxis = GetResourceManager()->GetSevenSixAxis(); + // Result result = sixaxis->GetSensorState(state, parameters.applet_resource_user_id, + // parameters.sixaxis_handle); - //if (result.IsSuccess()) { - // result = GetResourceManager()->GetConsoleSixAxis()->ResetSevenSixAxisSensorTimestamp(); - //} + // if (result.IsSuccess()) { + // result = GetResourceManager()->GetConsoleSixAxis()->ResetSevenSixAxisSensorTimestamp(); + // } - //if (result.IsSuccess()) { - // state->SetRunningState(true); - //} + // if (result.IsSuccess()) { + // state->SetRunningState(true); + // } IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -2224,14 +2223,14 @@ void IHidServer::FinalizeSevenSixAxisSensor(HLERequestContext& ctx) { std::shared_ptr state = nullptr; const auto sixaxis = GetResourceManager()->GetSevenSixAxis(); - //Result result = sixaxis->GetSensorState(state, parameters.applet_resource_user_id, - // parameters.sixaxis_handle); + // Result result = sixaxis->GetSensorState(state, parameters.applet_resource_user_id, + // parameters.sixaxis_handle); - //if (result.IsSuccess()) { - // state->SetRunningState(false); - //} + // if (result.IsSuccess()) { + // state->SetRunningState(false); + // } - // result = GetResourceManager()->GetConsoleSixAxis()->FinalizeSevenSixAxisSensor(); + // result = GetResourceManager()->GetConsoleSixAxis()->FinalizeSevenSixAxisSensor(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -2242,7 +2241,6 @@ void IHidServer::GetSevenSixAxisSensorFusionStrength(HLERequestContext& ctx) {} void IHidServer::ResetSevenSixAxisSensorTimestamp(HLERequestContext& ctx) {} - void IHidServer::IsUsbFullKeyControllerEnabled(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const bool is_enabled = false; @@ -2798,8 +2796,7 @@ void IHidServer::GetNpadCommunicationMode(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop()}; - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", - applet_resource_user_id); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); // This function has been stubbed since 2.0.0+ @@ -2825,11 +2822,11 @@ void IHidServer::SetTouchScreenConfiguration(HLERequestContext& ctx) { touchscreen_mode.mode = TouchScreenModeForNx::UseSystemSetting; } - //const Result result = - // GetResourceManager()->GetTouchScreen()->SetTouchScreenConfiguration(touchscreen_mode); + // const Result result = + // GetResourceManager()->GetTouchScreen()->SetTouchScreenConfiguration(touchscreen_mode); - //IPC::ResponseBuilder rb{ctx, 2}; - //rb.Push(result); + // IPC::ResponseBuilder rb{ctx, 2}; + // rb.Push(result); } void IHidServer::IsFirmwareUpdateNeededForNotification(HLERequestContext& ctx) { @@ -2855,9 +2852,7 @@ void IHidServer::IsFirmwareUpdateNeededForNotification(HLERequestContext& ctx) { rb.Push(needs_update); } -void IHidServer::ActivateDigitizer(HLERequestContext& ctx) { - -} +void IHidServer::ActivateDigitizer(HLERequestContext& ctx) {} bool IHidServer::IsDeviceManaged() { InitializeDebugSettings(); @@ -2903,14 +2898,13 @@ void IHidServer::InitializeDebugSettings() { is_firmware_update_failure = {}; if (is_firmware_update_failure_emulated) { - const std::size_t size = - 0; // GetSettingsItemValueSize("hid_debug", "firmware_update_failure"); - if (size != 0) { - [[maybe_unused]] const std::size_t setting_size = - std::min(size, is_firmware_update_failure.size()); - // nn::settings::fwdbg::GetSettingsItemValue(&is_firmware_update_failure, setting_size, - // "hid_debug", "firmware_update_failure"); - } + const std::size_t size = 0; // GetSettingsItemValueSize("hid_debug", + // "firmware_update_failure"); if (size != 0) { + [[maybe_unused]] const std::size_t setting_size = + std::min(size, is_firmware_update_failure.size()); + // nn::settings::fwdbg::GetSettingsItemValue(&is_firmware_update_failure, setting_size, + // "hid_debug", "firmware_update_failure"); + //} } is_ble_disabled = false; diff --git a/src/core/hle/service/hid/hid_system_server.cpp b/src/core/hle/service/hid/hid_system_server.cpp index 83696e4d7..186f6f7e8 100644 --- a/src/core/hle/service/hid/hid_system_server.cpp +++ b/src/core/hle/service/hid/hid_system_server.cpp @@ -266,7 +266,7 @@ void IHidSystemServer::GetLastActiveNpad(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(result); - // rb.PushEnum(ResultSuccess); + // rb.PushEnum(ResultSuccess); } void IHidSystemServer::ApplyNpadSystemCommonPolicyFull(HLERequestContext& ctx) { @@ -365,34 +365,34 @@ void IHidSystemServer::SetTouchScreenDefaultConfiguration(HLERequestContext& ctx break; default: touch_screen_configuration.mode = TouchScreenModeForNx::UseSystemSetting; - }/* + } /* - const Result result = GetResourceManager()->GetTouchScreen()->SetTouchScreenConfiguration( - touch_screen_configuration); + const Result result = GetResourceManager()->GetTouchScreen()->SetTouchScreenConfiguration( + touch_screen_configuration); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result);*/ + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result);*/ } void IHidSystemServer::GetTouchScreenDefaultConfiguration(HLERequestContext& ctx) { LOG_WARNING(Service_HID, "(STUBBED) called"); - //TouchScreenConfigurationForNx touch_screen_configuration{}; - //const Result result = GetResourceManager()->GetTouchScreen()->GetTouchScreenConfiguration( - // touch_screen_configuration.mode); + // TouchScreenConfigurationForNx touch_screen_configuration{}; + // const Result result = GetResourceManager()->GetTouchScreen()->GetTouchScreenConfiguration( + // touch_screen_configuration.mode); - //switch (touch_screen_configuration.mode) { - //case TouchScreenModeForNx::UseSystemSetting: - //case TouchScreenModeForNx::Finger: - //case TouchScreenModeForNx::Heat2: - // break; - //default: - // touch_screen_configuration.mode = TouchScreenModeForNx::UseSystemSetting; - //} + // switch (touch_screen_configuration.mode) { + // case TouchScreenModeForNx::UseSystemSetting: + // case TouchScreenModeForNx::Finger: + // case TouchScreenModeForNx::Heat2: + // break; + // default: + // touch_screen_configuration.mode = TouchScreenModeForNx::UseSystemSetting; + // } - //IPC::ResponseBuilder rb{ctx, 6}; - //rb.Push(ResultSuccess); - //rb.PushRaw(touch_screen_configuration); + // IPC::ResponseBuilder rb{ctx, 6}; + // rb.Push(ResultSuccess); + // rb.PushRaw(touch_screen_configuration); } std::shared_ptr IHidSystemServer::GetResourceManager() { diff --git a/src/core/hle/service/hid/resource_manager.cpp b/src/core/hle/service/hid/resource_manager.cpp index b72b4ebc1..3af7f55d2 100644 --- a/src/core/hle/service/hid/resource_manager.cpp +++ b/src/core/hle/service/hid/resource_manager.cpp @@ -194,7 +194,6 @@ std::shared_ptr ResourceManager::GetSixAxis() { return sixaxis; } - std::shared_ptr ResourceManager::GetConsoleSixAxis() { return sixaxis; } diff --git a/src/core/hle/service/hid/resource_manager/gesture.h b/src/core/hle/service/hid/resource_manager/gesture.h index 181aa7b88..a331ea121 100644 --- a/src/core/hle/service/hid/resource_manager/gesture.h +++ b/src/core/hle/service/hid/resource_manager/gesture.h @@ -11,8 +11,7 @@ namespace Core { class System; } -namespace Service::HID { -} // namespace Service::HID +namespace Service::HID {} // namespace Service::HID namespace Service::HID { diff --git a/src/core/hle/service/hid/resource_manager/keyboard.h b/src/core/hle/service/hid/resource_manager/keyboard.h index f077545c8..1ec593148 100644 --- a/src/core/hle/service/hid/resource_manager/keyboard.h +++ b/src/core/hle/service/hid/resource_manager/keyboard.h @@ -11,8 +11,7 @@ namespace Core { class System; } -namespace Service::HID { -} // namespace Service::HID +namespace Service::HID {} // namespace Service::HID namespace Service::HID { diff --git a/src/core/hle/service/hid/resource_manager/mouse.h b/src/core/hle/service/hid/resource_manager/mouse.h index 3f5ab5938..e3887bb8f 100644 --- a/src/core/hle/service/hid/resource_manager/mouse.h +++ b/src/core/hle/service/hid/resource_manager/mouse.h @@ -11,8 +11,7 @@ namespace Core { class System; } -namespace Service::HID { -} // namespace Service::HID +namespace Service::HID {} // namespace Service::HID namespace Service::HID { diff --git a/src/core/hle/service/hid/resource_manager/npad_resource/npad.cpp b/src/core/hle/service/hid/resource_manager/npad_resource/npad.cpp index b2197e51e..4ea9f6783 100644 --- a/src/core/hle/service/hid/resource_manager/npad_resource/npad.cpp +++ b/src/core/hle/service/hid/resource_manager/npad_resource/npad.cpp @@ -274,7 +274,7 @@ bool Npad::IsFirmwareUpdateAvailableForSixAxisSensor(const SixAxisSensorHandle& Result Npad::ResetIsSixAxisSensorDeviceNewlyAssigned(const u64 aruid, const SixAxisSensorHandle& handle) { - //auto npad_index = NpadIdTypeToIndex(static_cast(handle.device_index)); + // auto npad_index = NpadIdTypeToIndex(static_cast(handle.device_index)); // TODO: Implement this part diff --git a/src/core/hle/service/hid/resource_manager/npad_resource/npad.h b/src/core/hle/service/hid/resource_manager/npad_resource/npad.h index 5fa09c71a..852ca95a3 100644 --- a/src/core/hle/service/hid/resource_manager/npad_resource/npad.h +++ b/src/core/hle/service/hid/resource_manager/npad_resource/npad.h @@ -65,9 +65,7 @@ private: }; static_assert(sizeof(BatteryState) == 0x40, "BatteryState is an invalid size"); - struct Unknown0x88 { - - }; + struct Unknown0x88 {}; struct AbstractState { INSERT_PADDING_BYTES(0x88); diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h index 1132b5fed..b50c2c356 100644 --- a/src/yuzu/applets/qt_controller.h +++ b/src/yuzu/applets/qt_controller.h @@ -51,7 +51,7 @@ public: int exec() override; - void keyPressEvent(QKeyEvent* evt) override; + //void keyPressEvent(QKeyEvent* evt) override; private: // Applies the current configuration. diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 10c419ef3..d161b4f97 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -8,6 +8,8 @@ #include #include #include +#include "core/loader/nca.h" +#include "core/tools/renderdoc.h" #ifdef __APPLE__ #include // for chdir #endif @@ -16,6 +18,8 @@ #include #endif +#include + // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. #include "applets/qt_amiibo_settings.h" #include "applets/qt_controller.h" @@ -36,7 +40,6 @@ #include "core/frontend/applets/general_frontend.h" #include "core/frontend/applets/mii_edit.h" #include "core/frontend/applets/software_keyboard.h" -#include "core/hid/hid_core.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" @@ -93,6 +96,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "common/scm_rev.h" #include "common/scope_exit.h" #ifdef _WIN32 +#include #include "common/windows/timer_resolution.h" #endif #ifdef ARCHITECTURE_x86_64 @@ -145,6 +149,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/install_dialog.h" #include "yuzu/loading_screen.h" #include "yuzu/main.h" +#include "yuzu/play_time_manager.h" #include "yuzu/startup_checks.h" #include "yuzu/uisettings.h" #include "yuzu/util/clickable_label.h" @@ -333,6 +338,8 @@ GMainWindow::GMainWindow(std::unique_ptr config_, bool has_broken_vulkan SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); discord_rpc->Update(); + play_time_manager = std::make_unique(); + system->GetRoomNetwork().Init(); RegisterMetaTypes(); @@ -348,7 +355,6 @@ GMainWindow::GMainWindow(std::unique_ptr config_, bool has_broken_vulkan ConnectMenuEvents(); ConnectWidgetEvents(); - //system->HIDCore().ReloadInputDevices(); controller_dialog->refreshConfiguration(); const auto branch_name = std::string(Common::g_scm_branch); @@ -441,8 +447,13 @@ GMainWindow::GMainWindow(std::unique_ptr config_, bool has_broken_vulkan "#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>" "here for instructions to fix the issue.")); +#ifdef HAS_OPENGL Settings::values.renderer_backend = Settings::RendererBackend::OpenGL; +#else + Settings::values.renderer_backend = Settings::RendererBackend::Null; +#endif + UpdateAPIText(); renderer_status_button->setDisabled(true); renderer_status_button->setChecked(false); } else { @@ -643,27 +654,26 @@ void GMainWindow::AmiiboSettingsRequestExit() { void GMainWindow::ControllerSelectorReconfigureControllers( const Core::Frontend::ControllerParameters& parameters) { - controller_applet = - new QtControllerSelectorDialog(this, parameters, input_subsystem.get(), *system); - SCOPE_EXIT({ - controller_applet->deleteLater(); - controller_applet = nullptr; - }); + //controller_applet = + // new QtControllerSelectorDialog(this, parameters, input_subsystem.get(), *system); + //SCOPE_EXIT({ + // controller_applet->deleteLater(); + // controller_applet = nullptr; + //}); - controller_applet->setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | - Qt::WindowStaysOnTopHint | Qt::WindowTitleHint | - Qt::WindowSystemMenuHint); - controller_applet->setWindowModality(Qt::WindowModal); - bool is_success = controller_applet->exec() != QDialog::Rejected; + //controller_applet->setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | + // Qt::WindowStaysOnTopHint | Qt::WindowTitleHint | + // Qt::WindowSystemMenuHint); + //controller_applet->setWindowModality(Qt::WindowModal); + //bool is_success = controller_applet->exec() != QDialog::Rejected; // Don't forget to apply settings. - //system->HIDCore().DisableAllControllerConfiguration(); - system->ApplySettings(); - config->Save(); + //system->ApplySettings(); + //config->Save(); - UpdateStatusButtons(); + //UpdateStatusButtons(); - emit ControllerSelectorReconfigureFinished(is_success); + //emit ControllerSelectorReconfigureFinished(is_success); } void GMainWindow::ControllerSelectorRequestExit() { @@ -976,7 +986,7 @@ void GMainWindow::InitializeWidgets() { render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system); render_window->hide(); - game_list = new GameList(vfs, provider.get(), *system, this); + game_list = new GameList(vfs, provider.get(), *play_time_manager, *system, this); ui->horizontalLayout->addWidget(game_list); game_list_placeholder = new GameListPlaceholder(this); @@ -1157,9 +1167,9 @@ void GMainWindow::InitializeWidgets() { [this](const QPoint& menu_location) { QMenu context_menu; - for (auto const& docked_mode_pair : Config::use_docked_mode_texts_map) { - context_menu.addAction(docked_mode_pair.second, [this, docked_mode_pair] { - if (docked_mode_pair.first != Settings::values.use_docked_mode.GetValue()) { + for (auto const& pair : Config::use_docked_mode_texts_map) { + context_menu.addAction(pair.second, [this, &pair] { + if (pair.first != Settings::values.use_docked_mode.GetValue()) { OnToggleDockedMode(); } }); @@ -1284,7 +1294,7 @@ void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name //auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); //const auto* controller_hotkey = - // hotkey_registry.GetControllerHotkey(main_window, action_name, controller); + //// hotkey_registry.GetControllerHotkey(main_window, action_name, controller); //connect( // controller_hotkey, &ControllerShortcut::Activated, this, // [action, tas_allowed, this] { @@ -1316,11 +1326,11 @@ void GMainWindow::InitializeHotkeys() { static const QString main_window = QStringLiteral("Main Window"); const auto connect_shortcut = [&](const QString& action_name, const Fn& function) { - const auto* hotkey = hotkey_registry.GetHotkey(main_window, action_name, this); + //const auto* hotkey = hotkey_registry.GetHotkey(main_window, action_name, this); //auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); //const auto* controller_hotkey = // hotkey_registry.GetControllerHotkey(main_window, action_name, controller); - connect(hotkey, &QShortcut::activated, this, function); + //connect(hotkey, &QShortcut::activated, this, function); //connect(controller_hotkey, &ControllerShortcut::Activated, this, function, // Qt::QueuedConnection); }; @@ -1341,6 +1351,11 @@ void GMainWindow::InitializeHotkeys() { connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] { Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue()); }); + connect_shortcut(QStringLiteral("Toggle Renderdoc Capture"), [this] { + if (Settings::values.enable_renderdoc_hotkey) { + system->GetRenderdocAPI().ToggleCapture(); + } + }); connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] { if (Settings::values.mouse_enabled) { Settings::values.mouse_panning = false; @@ -1432,6 +1447,7 @@ void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) { Settings::values.audio_muted = false; auto_muted = false; } + UpdateVolumeUI(); } } @@ -1445,7 +1461,11 @@ void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::RemoveInstalledEntryRequested, this, &GMainWindow::OnGameListRemoveInstalledEntry); connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); + connect(game_list, &GameList::RemovePlayTimeRequested, this, + &GMainWindow::OnGameListRemovePlayTimeData); connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); + connect(game_list, &GameList::VerifyIntegrityRequested, this, + &GMainWindow::OnGameListVerifyIntegrity); connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, &GMainWindow::OnGameListNavigateToGamedbEntry); @@ -1536,6 +1556,16 @@ void GMainWindow::ConnectMenuEvents() { // Tools connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, ReinitializeKeyBehavior::Warning)); + connect_menu(ui->action_Load_Album, &GMainWindow::OnAlbum); + connect_menu(ui->action_Load_Cabinet_Nickname_Owner, + [this]() { OnCabinet(Service::NFP::CabinetMode::StartNicknameAndOwnerSettings); }); + connect_menu(ui->action_Load_Cabinet_Eraser, + [this]() { OnCabinet(Service::NFP::CabinetMode::StartGameDataEraser); }); + connect_menu(ui->action_Load_Cabinet_Restorer, + [this]() { OnCabinet(Service::NFP::CabinetMode::StartRestorer); }); + connect_menu(ui->action_Load_Cabinet_Formatter, + [this]() { OnCabinet(Service::NFP::CabinetMode::StartFormatter); }); + connect_menu(ui->action_Load_Mii_Edit, &GMainWindow::OnMiiEdit); connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot); // TAS @@ -1546,11 +1576,13 @@ void GMainWindow::ConnectMenuEvents() { // Help connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder); + connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents); connect_menu(ui->action_About, &GMainWindow::OnAbout); } void GMainWindow::UpdateMenuState() { const bool is_paused = emu_thread == nullptr || !emu_thread->IsRunning(); + const bool is_firmware_available = CheckFirmwarePresence(); const std::array running_actions{ ui->action_Stop, @@ -1561,10 +1593,23 @@ void GMainWindow::UpdateMenuState() { ui->action_Pause, }; + const std::array applet_actions{ + ui->action_Load_Album, + ui->action_Load_Cabinet_Nickname_Owner, + ui->action_Load_Cabinet_Eraser, + ui->action_Load_Cabinet_Restorer, + ui->action_Load_Cabinet_Formatter, + ui->action_Load_Mii_Edit, + }; + for (QAction* action : running_actions) { action->setEnabled(emulation_running); } + for (QAction* action : applet_actions) { + action->setEnabled(is_firmware_available && !emulation_running); + } + ui->action_Capture_Screenshot->setEnabled(emulation_running && !is_paused); if (emulation_running && is_paused) { @@ -1697,7 +1742,8 @@ void GMainWindow::AllowOSSleep() { #endif } -bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t program_index) { +bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t program_index, + AmLaunchType launch_type) { // Shutdown previous session if the emu thread is still active... if (emu_thread != nullptr) { ShutdownGame(); @@ -1709,6 +1755,10 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p system->SetFilesystem(vfs); + if (launch_type == AmLaunchType::UserInitiated) { + system->GetUserChannel().clear(); + } + system->SetAppletFrontendSet({ std::make_unique(*this), // Amiibo Settings (UISettings::values.controller_applet_disabled.GetValue() == true) @@ -1810,8 +1860,45 @@ bool GMainWindow::SelectAndSetCurrentUser( return true; } +void GMainWindow::ConfigureFilesystemProvider(const std::string& filepath) { + // Ensure all NCAs are registered before launching the game + const auto file = vfs->OpenFile(filepath, FileSys::Mode::Read); + if (!file) { + return; + } + + auto loader = Loader::GetLoader(*system, file); + if (!loader) { + return; + } + + const auto file_type = loader->GetFileType(); + if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { + return; + } + + u64 program_id = 0; + const auto res2 = loader->ReadProgramId(program_id); + if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { + provider->AddEntry(FileSys::TitleType::Application, + FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), program_id, + file); + } else if (res2 == Loader::ResultStatus::Success && + (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { + const auto nsp = file_type == Loader::FileType::NSP + ? std::make_shared(file) + : FileSys::XCI{file}.GetSecurePartitionNSP(); + for (const auto& title : nsp->GetNCAs()) { + for (const auto& entry : title.second) { + provider->AddEntry(entry.first.first, entry.first.second, title.first, + entry.second->GetBaseFile()); + } + } + } +} + void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t program_index, - StartGameType type) { + StartGameType type, AmLaunchType launch_type) { LOG_INFO(Frontend, "yuzu starting..."); StoreRecentFile(filename); // Put the filename on top of the list @@ -1824,6 +1911,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t last_filename_booted = filename; + ConfigureFilesystemProvider(filename.toStdString()); const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData()); const auto loader = Loader::GetLoader(*system, v_file, program_id, program_index); @@ -1836,7 +1924,6 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t ? Common::FS::PathToUTF8String(file_path.filename()) : fmt::format("{:016X}", title_id); Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig); - //system->HIDCore().ReloadInputDevices(); system->ApplySettings(); } @@ -1854,7 +1941,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t } } - if (!LoadROM(filename, program_id, program_index)) { + if (!LoadROM(filename, program_id, program_index, launch_type)) { return; } @@ -1971,8 +2058,16 @@ bool GMainWindow::OnShutdownBegin() { emit EmulationStopping(); + int shutdown_time = 1000; + + if (system->DebuggerEnabled()) { + shutdown_time = 0; + } else if (system->GetExitLocked()) { + shutdown_time = 5000; + } + shutdown_timer.setSingleShot(true); - shutdown_timer.start(system->DebuggerEnabled() ? 0 : 5000); + shutdown_timer.start(shutdown_time); connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired); connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped); @@ -2033,8 +2128,7 @@ void GMainWindow::OnEmulationStopped() { OnTasStateChanged(); render_window->FinalizeCamera(); - // Enable all controllers - // system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All}); + system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::None); render_window->removeEventFilter(render_window); render_window->setAttribute(Qt::WA_Hover, false); @@ -2059,7 +2153,6 @@ void GMainWindow::OnEmulationStopped() { game_list->setEnabled(true); Settings::RestoreGlobalState(system->IsPoweredOn()); - //system->HIDCore().ReloadInputDevices(); UpdateStatusButtons(); } @@ -2228,40 +2321,62 @@ void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_path)); } -static std::size_t CalculateRomFSEntrySize(const FileSys::VirtualDir& dir, bool full) { - std::size_t out = 0; - - for (const auto& subdir : dir->GetSubdirectories()) { - out += 1 + CalculateRomFSEntrySize(subdir, full); - } - - return out + (full ? dir->GetFiles().size() : 0); -} - -static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src, - const FileSys::VirtualDir& dest, std::size_t block_size, bool full) { +static bool RomFSRawCopy(size_t total_size, size_t& read_size, QProgressDialog& dialog, + const FileSys::VirtualDir& src, const FileSys::VirtualDir& dest, + bool full) { if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) return false; if (dialog.wasCanceled()) return false; + std::vector buffer(CopyBufferSize); + auto last_timestamp = std::chrono::steady_clock::now(); + + const auto QtRawCopy = [&](const FileSys::VirtualFile& src_file, + const FileSys::VirtualFile& dest_file) { + if (src_file == nullptr || dest_file == nullptr) { + return false; + } + if (!dest_file->Resize(src_file->GetSize())) { + return false; + } + + for (std::size_t i = 0; i < src_file->GetSize(); i += buffer.size()) { + if (dialog.wasCanceled()) { + dest_file->Resize(0); + return false; + } + + using namespace std::literals::chrono_literals; + const auto new_timestamp = std::chrono::steady_clock::now(); + + if ((new_timestamp - last_timestamp) > 33ms) { + last_timestamp = new_timestamp; + dialog.setValue( + static_cast(std::min(read_size, total_size) * 100 / total_size)); + QCoreApplication::processEvents(); + } + + const auto read = src_file->Read(buffer.data(), buffer.size(), i); + dest_file->Write(buffer.data(), read, i); + + read_size += read; + } + + return true; + }; + if (full) { for (const auto& file : src->GetFiles()) { const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName()); - if (!FileSys::VfsRawCopy(file, out, block_size)) - return false; - dialog.setValue(dialog.value() + 1); - if (dialog.wasCanceled()) + if (!QtRawCopy(file, out)) return false; } } for (const auto& dir : src->GetSubdirectories()) { const auto out = dest->CreateSubdirectory(dir->GetName()); - if (!RomFSRawCopy(dialog, dir, out, block_size, full)) - return false; - dialog.setValue(dialog.value() + 1); - if (dialog.wasCanceled()) + if (!RomFSRawCopy(total_size, read_size, dialog, dir, out, full)) return false; } @@ -2419,6 +2534,17 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ } } +void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) { + if (QMessageBox::question(this, tr("Remove Play Time Data"), tr("Reset play time?"), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No) != QMessageBox::Yes) { + return; + } + + play_time_manager->ResetProgramPlayTime(program_id); + game_list->PopulateAsync(UISettings::values.game_dirs); +} + void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) { const auto target_file_name = [target] { switch (target) { @@ -2534,16 +2660,34 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa return; } - FileSys::VirtualFile file; - if (loader->ReadRomFS(file) != Loader::ResultStatus::Success) { + FileSys::VirtualFile packed_update_raw{}; + loader->ReadUpdateRaw(packed_update_raw); + + const auto& installed = system->GetContentProvider(); + + u64 title_id{}; + u8 raw_type{}; + if (!SelectRomFSDumpTarget(installed, program_id, &title_id, &raw_type)) { failed(); return; } - const auto& installed = system->GetContentProvider(); - const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id); + const auto type = static_cast(raw_type); + const auto base_nca = installed.GetEntry(title_id, type); + if (!base_nca) { + failed(); + return; + } - if (!romfs_title_id) { + const FileSys::NCA update_nca{packed_update_raw, nullptr}; + if (type != FileSys::ContentRecordType::Program || + update_nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS || + update_nca.GetTitleId() != FileSys::GetUpdateTitleID(title_id)) { + packed_update_raw = {}; + } + + const auto base_romfs = base_nca->GetRomFS(); + if (!base_romfs) { failed(); return; } @@ -2552,26 +2696,12 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa target == DumpRomFSTarget::Normal ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir) : Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "atmosphere" / "contents"; - const auto romfs_dir = fmt::format("{:016X}/romfs", *romfs_title_id); + const auto romfs_dir = fmt::format("{:016X}/romfs", title_id); const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir); - FileSys::VirtualFile romfs; - - if (*romfs_title_id == program_id) { - const u64 ivfc_offset = loader->ReadRomFSIVFCOffset(); - const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), installed}; - romfs = - pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program, nullptr, false); - } else { - romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS(); - } - - const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); - if (extracted == nullptr) { - failed(); - return; - } + const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), installed}; + auto romfs = pm.PatchRomFS(base_nca.get(), base_romfs, type, packed_update_raw, false); const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite); @@ -2595,11 +2725,16 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa return; } - const auto full = res == selections.constFirst(); - const auto entry_size = CalculateRomFSEntrySize(extracted, full); + const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); + if (extracted == nullptr) { + failed(); + return; + } - // The minimum required space is the size of the extracted RomFS + 1 GiB - const auto minimum_free_space = extracted->GetSize() + 0x40000000; + const auto full = res == selections.constFirst(); + + // The expected required space is the size of the RomFS + 1 GiB + const auto minimum_free_space = romfs->GetSize() + 0x40000000; if (full && Common::FS::GetFreeSpaceSize(path) < minimum_free_space) { QMessageBox::warning(this, tr("RomFS Extraction Failed!"), @@ -2610,12 +2745,15 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa return; } - QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, - static_cast(entry_size), this); + QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, 100, this); progress.setWindowModality(Qt::WindowModal); progress.setMinimumDuration(100); + progress.setAutoClose(false); + progress.setAutoReset(false); - if (RomFSRawCopy(progress, extracted, out, 0x400000, full)) { + size_t read_size = 0; + + if (RomFSRawCopy(romfs->GetSize(), read_size, progress, extracted, out, full)) { progress.close(); QMessageBox::information(this, tr("RomFS Extraction Succeeded!"), tr("The operation completed successfully.")); @@ -2627,6 +2765,54 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa } } +void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) { + const auto NotImplemented = [this] { + QMessageBox::warning(this, tr("Integrity verification couldn't be performed!"), + tr("File contents were not checked for validity.")); + }; + const auto Failed = [this] { + QMessageBox::critical(this, tr("Integrity verification failed!"), + tr("File contents may be corrupt.")); + }; + + const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read)); + if (loader == nullptr) { + NotImplemented(); + return; + } + + QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); + progress.setWindowModality(Qt::WindowModal); + progress.setMinimumDuration(100); + progress.setAutoClose(false); + progress.setAutoReset(false); + + const auto QtProgressCallback = [&](size_t processed_size, size_t total_size) { + if (progress.wasCanceled()) { + return false; + } + + progress.setValue(static_cast((processed_size * 100) / total_size)); + return true; + }; + + const auto status = loader->VerifyIntegrity(QtProgressCallback); + if (progress.wasCanceled() || + status == Loader::ResultStatus::ErrorIntegrityVerificationNotImplemented) { + NotImplemented(); + return; + } + + if (status == Loader::ResultStatus::ErrorIntegrityVerificationFailed) { + Failed(); + return; + } + + progress.close(); + QMessageBox::information(this, tr("Integrity verification succeeded!"), + tr("The operation completed successfully.")); +} + void GMainWindow::OnGameListCopyTID(u64 program_id) { QClipboard* clipboard = QGuiApplication::clipboard(); clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id))); @@ -2650,7 +2836,6 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga const QStringList args = QApplication::arguments(); std::filesystem::path yuzu_command = args[0].toStdString(); -#if defined(__linux__) || defined(__FreeBSD__) // If relative path, make it an absolute path if (yuzu_command.c_str()[0] == '.') { yuzu_command = Common::FS::GetCurrentDir() / yuzu_command; @@ -2673,12 +2858,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga UISettings::values.shortcut_already_warned = true; } #endif // __linux__ -#endif // __linux__ || __FreeBSD__ std::filesystem::path target_directory{}; // Determine target directory for shortcut -#if defined(__linux__) || defined(__FreeBSD__) +#if defined(WIN32) + const char* home = std::getenv("USERPROFILE"); +#else const char* home = std::getenv("HOME"); +#endif const std::filesystem::path home_path = (home == nullptr ? "~" : home); const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); @@ -2688,7 +2875,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga QMessageBox::critical( this, tr("Create Shortcut"), tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.") - .arg(QString::fromStdString(target_directory)), + .arg(QString::fromStdString(target_directory.generic_string())), QMessageBox::StandardButton::Ok); return; } @@ -2696,15 +2883,15 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) / "applications"; if (!Common::FS::CreateDirs(target_directory)) { - QMessageBox::critical(this, tr("Create Shortcut"), - tr("Cannot create shortcut in applications menu. Path \"%1\" " - "does not exist and cannot be created.") - .arg(QString::fromStdString(target_directory)), - QMessageBox::StandardButton::Ok); + QMessageBox::critical( + this, tr("Create Shortcut"), + tr("Cannot create shortcut in applications menu. Path \"%1\" " + "does not exist and cannot be created.") + .arg(QString::fromStdString(target_directory.generic_string())), + QMessageBox::StandardButton::Ok); return; } } -#endif const std::string game_file_name = std::filesystem::path(game_path).filename().string(); // Determine full paths for icon and shortcut @@ -2726,9 +2913,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga const std::filesystem::path shortcut_path = target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name) : fmt::format("yuzu-{:016X}.desktop", program_id)); +#elif defined(WIN32) + std::filesystem::path icons_path = + Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir); + std::filesystem::path icon_path = + icons_path / ((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name) + : fmt::format("yuzu-{:016X}.ico", program_id))); #else - const std::filesystem::path icon_path{}; - const std::filesystem::path shortcut_path{}; + std::string icon_extension; #endif // Get title from game file @@ -2753,29 +2945,37 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); } - QImage icon_jpeg = + QImage icon_data = QImage::fromData(icon_image_file.data(), static_cast(icon_image_file.size())); #if defined(__linux__) || defined(__FreeBSD__) // Convert and write the icon as a PNG - if (!icon_jpeg.save(QString::fromStdString(icon_path.string()))) { + if (!icon_data.save(QString::fromStdString(icon_path.string()))) { LOG_ERROR(Frontend, "Could not write icon as PNG to file"); } else { LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string()); } +#elif defined(WIN32) + if (!SaveIconToFile(icon_path.string(), icon_data)) { + LOG_ERROR(Frontend, "Could not write icon to file"); + return; + } #endif // __linux__ -#if defined(__linux__) || defined(__FreeBSD__) +#ifdef _WIN32 + // Replace characters that are illegal in Windows filenames by a dash + const std::string illegal_chars = "<>:\"/\\|?*"; + for (char c : illegal_chars) { + std::replace(title.begin(), title.end(), c, '_'); + } + const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str(); +#endif + const std::string comment = tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString(); const std::string arguments = fmt::format("-g \"{:s}\"", game_path); const std::string categories = "Game;Emulator;Qt;"; const std::string keywords = "Switch;Nintendo;"; -#else - const std::string comment{}; - const std::string arguments{}; - const std::string categories{}; - const std::string keywords{}; -#endif + if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(), yuzu_command.string(), arguments, categories, keywords)) { QMessageBox::critical(this, tr("Create Shortcut"), @@ -2961,10 +3161,9 @@ void GMainWindow::OnMenuInstallToNAND() { QFuture future; InstallResult result; - if (file.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) || - file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { + if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { - future = QtConcurrent::run([this, &file] { return InstallNSPXCI(file); }); + future = QtConcurrent::run([this, &file] { return InstallNSP(file); }); while (!future.isFinished()) { QCoreApplication::processEvents(); @@ -3023,7 +3222,7 @@ void GMainWindow::OnMenuInstallToNAND() { ui->action_Install_File_NAND->setEnabled(true); } -InstallResult GMainWindow::InstallNSPXCI(const QString& filename) { +InstallResult GMainWindow::InstallNSP(const QString& filename) { const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, std::size_t block_size) { if (src == nullptr || dest == nullptr) { @@ -3057,9 +3256,7 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename) { return InstallResult::Failure; } } else { - const auto xci = std::make_shared( - vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); - nsp = xci->GetSecurePartitionNSP(); + return InstallResult::Failure; } if (nsp->GetStatus() != Loader::ResultStatus::Success) { @@ -3185,6 +3382,9 @@ void GMainWindow::OnStartGame() { UpdateMenuState(); OnTasStateChanged(); + play_time_manager->SetProgramId(system->GetApplicationProcessProgramID()); + play_time_manager->Start(); + discord_rpc->Update(); } @@ -3200,6 +3400,7 @@ void GMainWindow::OnRestartGame() { void GMainWindow::OnPauseGame() { emu_thread->SetRunning(false); + play_time_manager->Stop(); UpdateMenuState(); AllowOSSleep(); } @@ -3216,10 +3417,13 @@ void GMainWindow::OnPauseContinueGame() { } void GMainWindow::OnStopGame() { - if (system->GetExitLock() && !ConfirmForceLockedExit()) { + if (system->GetExitLocked() && !ConfirmForceLockedExit()) { return; } + play_time_manager->Stop(); + // Update game list to show new play time + game_list->PopulateAsync(UISettings::values.game_dirs); if (OnShutdownBegin()) { OnShutdownBeginDialog(); } else { @@ -3233,7 +3437,8 @@ void GMainWindow::OnLoadComplete() { void GMainWindow::OnExecuteProgram(std::size_t program_index) { ShutdownGame(); - BootGame(last_filename_booted, 0, program_index); + BootGame(last_filename_booted, 0, program_index, StartGameType::Normal, + AmLaunchType::ApplicationInitiated); } void GMainWindow::OnExit() { @@ -3580,10 +3785,6 @@ void GMainWindow::OnTasStartStop() { return; } - // Disable system buttons to prevent TAS from executing a hotkey - //auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); - //controller->ResetSystemButtons(); - input_subsystem->GetTas()->StartStop(); OnTasStateChanged(); } @@ -3596,10 +3797,6 @@ void GMainWindow::OnTasRecord() { return; } - // Disable system buttons to prevent TAS from recording a hotkey - //auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); - //controller->ResetSystemButtons(); - const bool is_recording = input_subsystem->GetTas()->Record(); if (!is_recording) { is_tas_recording_dialog_active = true; @@ -3629,7 +3826,7 @@ void GMainWindow::OnTasReset() { } void GMainWindow::OnToggleDockedMode() { - const bool is_docked = Settings::values.use_docked_mode.GetValue(); + const bool is_docked = Settings::IsDockedMode(); //auto* player_1 = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); //auto* handheld = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); @@ -3643,7 +3840,8 @@ void GMainWindow::OnToggleDockedMode() { // controller_dialog->refreshConfiguration(); //} - Settings::values.use_docked_mode.SetValue(!is_docked); + Settings::values.use_docked_mode.SetValue(is_docked ? Settings::ConsoleMode::Handheld + : Settings::ConsoleMode::Docked); UpdateDockedButton(); OnDockedModeChanged(is_docked, !is_docked, *system); } @@ -3712,10 +3910,14 @@ void GMainWindow::OnToggleAdaptingFilter() { void GMainWindow::OnToggleGraphicsAPI() { auto api = Settings::values.renderer_backend.GetValue(); - if (api == Settings::RendererBackend::OpenGL) { + if (api != Settings::RendererBackend::Vulkan) { api = Settings::RendererBackend::Vulkan; } else { +#ifdef HAS_OPENGL api = Settings::RendererBackend::OpenGL; +#else + api = Settings::RendererBackend::Null; +#endif } Settings::values.renderer_backend.SetValue(api); renderer_status_button->setChecked(api == Settings::RendererBackend::Vulkan); @@ -3750,7 +3952,6 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file // Do not cause the global config to write local settings into the config file const bool is_powered_on = system->IsPoweredOn(); Settings::RestoreGlobalState(is_powered_on); - //system->HIDCore().ReloadInputDevices(); UISettings::values.configuration_applied = false; @@ -3786,6 +3987,34 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st shortcut_stream << shortcut_contents; shortcut_stream.close(); + return true; +#elif defined(WIN32) + IShellLinkW* shell_link; + auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, + (void**)&shell_link); + if (FAILED(hres)) { + return false; + } + shell_link->SetPath( + Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to + shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data()); + shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data()); + shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0); + + IPersistFile* persist_file; + hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file); + if (FAILED(hres)) { + return false; + } + + hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE); + if (FAILED(hres)) { + return false; + } + + persist_file->Release(); + shell_link->Release(); + return true; #endif return false; @@ -3859,6 +4088,108 @@ void GMainWindow::OnOpenYuzuFolder() { QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::YuzuDir)))); } +void GMainWindow::OnVerifyInstalledContents() { + // Declare sizes. + size_t total_size = 0; + size_t processed_size = 0; + + // Initialize a progress dialog. + QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); + progress.setWindowModality(Qt::WindowModal); + progress.setMinimumDuration(100); + progress.setAutoClose(false); + progress.setAutoReset(false); + + // Declare a list of file names which failed to verify. + std::vector failed; + + // Declare progress callback. + auto QtProgressCallback = [&](size_t nca_processed, size_t nca_total) { + if (progress.wasCanceled()) { + return false; + } + progress.setValue(static_cast(((processed_size + nca_processed) * 100) / total_size)); + return true; + }; + + // Get content registries. + auto bis_contents = system->GetFileSystemController().GetSystemNANDContents(); + auto user_contents = system->GetFileSystemController().GetUserNANDContents(); + + std::vector content_providers; + if (bis_contents) { + content_providers.push_back(bis_contents); + } + if (user_contents) { + content_providers.push_back(user_contents); + } + + // Get associated NCA files. + std::vector nca_files; + + // Get all installed IDs. + for (auto nca_provider : content_providers) { + const auto entries = nca_provider->ListEntriesFilter(); + + for (const auto& entry : entries) { + auto nca_file = nca_provider->GetEntryRaw(entry.title_id, entry.type); + if (!nca_file) { + continue; + } + + total_size += nca_file->GetSize(); + nca_files.push_back(std::move(nca_file)); + } + } + + // Using the NCA loader, determine if all NCAs are valid. + for (auto& nca_file : nca_files) { + Loader::AppLoader_NCA nca_loader(nca_file); + + auto status = nca_loader.VerifyIntegrity(QtProgressCallback); + if (progress.wasCanceled()) { + break; + } + if (status != Loader::ResultStatus::Success) { + FileSys::NCA nca(nca_file); + const auto title_id = nca.GetTitleId(); + std::string title_name = "unknown"; + + const auto control = provider->GetEntry(FileSys::GetBaseTitleID(title_id), + FileSys::ContentRecordType::Control); + if (control && control->GetStatus() == Loader::ResultStatus::Success) { + const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), + *provider}; + const auto [nacp, logo] = pm.ParseControlNCA(*control); + if (nacp) { + title_name = nacp->GetApplicationName(); + } + } + + if (title_id > 0) { + failed.push_back( + fmt::format("{} ({:016X}) ({})", nca_file->GetName(), title_id, title_name)); + } else { + failed.push_back(fmt::format("{} (unknown)", nca_file->GetName())); + } + } + + processed_size += nca_file->GetSize(); + } + + progress.close(); + + if (failed.size() > 0) { + auto failed_names = QString::fromStdString(fmt::format("{}", fmt::join(failed, "\n"))); + QMessageBox::critical( + this, tr("Integrity verification failed!"), + tr("Verification failed for the following files:\n\n%1").arg(failed_names)); + } else { + QMessageBox::information(this, tr("Integrity verification succeeded!"), + tr("The operation completed successfully.")); + } +} + void GMainWindow::OnAbout() { AboutDialog aboutDialog(this); aboutDialog.exec(); @@ -3877,6 +4208,76 @@ void GMainWindow::OnToggleStatusBar() { statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked()); } +void GMainWindow::OnAlbum() { + constexpr u64 AlbumId = 0x010000000000100Dull; + auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); + if (!bis_system) { + QMessageBox::warning(this, tr("No firmware available"), + tr("Please install the firmware to use the Album applet.")); + return; + } + + auto album_nca = bis_system->GetEntry(AlbumId, FileSys::ContentRecordType::Program); + if (!album_nca) { + QMessageBox::warning(this, tr("Album Applet"), + tr("Album applet is not available. Please reinstall firmware.")); + return; + } + + system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::PhotoViewer); + + const auto filename = QString::fromStdString(album_nca->GetFullPath()); + UISettings::values.roms_path = QFileInfo(filename).path(); + BootGame(filename); +} + +void GMainWindow::OnCabinet(Service::NFP::CabinetMode mode) { + constexpr u64 CabinetId = 0x0100000000001002ull; + auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); + if (!bis_system) { + QMessageBox::warning(this, tr("No firmware available"), + tr("Please install the firmware to use the Cabinet applet.")); + return; + } + + auto cabinet_nca = bis_system->GetEntry(CabinetId, FileSys::ContentRecordType::Program); + if (!cabinet_nca) { + QMessageBox::warning(this, tr("Cabinet Applet"), + tr("Cabinet applet is not available. Please reinstall firmware.")); + return; + } + + system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::Cabinet); + system->GetAppletManager().SetCabinetMode(mode); + + const auto filename = QString::fromStdString(cabinet_nca->GetFullPath()); + UISettings::values.roms_path = QFileInfo(filename).path(); + BootGame(filename); +} + +void GMainWindow::OnMiiEdit() { + constexpr u64 MiiEditId = 0x0100000000001009ull; + auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); + if (!bis_system) { + QMessageBox::warning(this, tr("No firmware available"), + tr("Please install the firmware to use the Mii editor.")); + return; + } + + auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program); + if (!mii_applet_nca) { + QMessageBox::warning(this, tr("Mii Edit Applet"), + tr("Mii editor is not available. Please reinstall firmware.")); + return; + } + + system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::MiiEdit); + + const auto filename = QString::fromStdString((mii_applet_nca->GetFullPath())); + UISettings::values.roms_path = QFileInfo(filename).path(); + BootGame(filename); +} + void GMainWindow::OnCaptureScreenshot() { if (emu_thread == nullptr || !emu_thread->IsRunning()) { return; @@ -4073,10 +4474,10 @@ void GMainWindow::UpdateGPUAccuracyButton() { } void GMainWindow::UpdateDockedButton() { - const bool is_docked = Settings::values.use_docked_mode.GetValue(); - dock_status_button->setChecked(is_docked); + const auto console_mode = Settings::values.use_docked_mode.GetValue(); + dock_status_button->setChecked(Settings::IsDockedMode()); dock_status_button->setText( - Config::use_docked_mode_texts_map.find(is_docked)->second.toUpper()); + Config::use_docked_mode_texts_map.find(console_mode)->second.toUpper()); } void GMainWindow::UpdateAPIText() { @@ -4283,6 +4684,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { if (behavior == ReinitializeKeyBehavior::Warning) { game_list->PopulateAsync(UISettings::values.game_dirs); } + + UpdateMenuState(); } bool GMainWindow::CheckSystemArchiveDecryption() { @@ -4304,28 +4707,63 @@ bool GMainWindow::CheckSystemArchiveDecryption() { return mii_nca->GetRomFS().get() != nullptr; } -std::optional GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, - u64 program_id) { - const auto dlc_entries = - installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); - std::vector dlc_match; - dlc_match.reserve(dlc_entries.size()); - std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match), - [&program_id, &installed](const FileSys::ContentProviderEntry& entry) { - return FileSys::GetBaseTitleID(entry.title_id) == program_id && - installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success; - }); +bool GMainWindow::CheckFirmwarePresence() { + constexpr u64 MiiEditId = 0x0100000000001009ull; - std::vector romfs_tids; - romfs_tids.push_back(program_id); - for (const auto& entry : dlc_match) { - romfs_tids.push_back(entry.title_id); + auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); + if (!bis_system) { + return false; } - if (romfs_tids.size() > 1) { - QStringList list{QStringLiteral("Base")}; - for (std::size_t i = 1; i < romfs_tids.size(); ++i) { - list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF)); + auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program); + if (!mii_applet_nca) { + return false; + } + + return true; +} + +bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, + u64* selected_title_id, u8* selected_content_record_type) { + using ContentInfo = std::tuple; + boost::container::flat_set available_title_ids; + + const auto RetrieveEntries = [&](FileSys::TitleType title_type, + FileSys::ContentRecordType record_type) { + const auto entries = installed.ListEntriesFilter(title_type, record_type); + for (const auto& entry : entries) { + if (FileSys::GetBaseTitleID(entry.title_id) == program_id && + installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success) { + available_title_ids.insert({entry.title_id, title_type, record_type}); + } + } + }; + + RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::Program); + RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::HtmlDocument); + RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::LegalInformation); + RetrieveEntries(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); + + if (available_title_ids.empty()) { + return false; + } + + size_t title_index = 0; + + if (available_title_ids.size() > 1) { + QStringList list; + for (auto& [title_id, title_type, record_type] : available_title_ids) { + const auto hex_title_id = QString::fromStdString(fmt::format("{:X}", title_id)); + if (record_type == FileSys::ContentRecordType::Program) { + list.push_back(QStringLiteral("Program [%1]").arg(hex_title_id)); + } else if (record_type == FileSys::ContentRecordType::HtmlDocument) { + list.push_back(QStringLiteral("HTML document [%1]").arg(hex_title_id)); + } else if (record_type == FileSys::ContentRecordType::LegalInformation) { + list.push_back(QStringLiteral("Legal information [%1]").arg(hex_title_id)); + } else { + list.push_back( + QStringLiteral("DLC %1 [%2]").arg(title_id & 0x7FF).arg(hex_title_id)); + } } bool ok; @@ -4333,13 +4771,16 @@ std::optional GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProv this, tr("Select RomFS Dump Target"), tr("Please select which RomFS you would like to dump."), list, 0, false, &ok); if (!ok) { - return {}; + return false; } - return romfs_tids[list.indexOf(res)]; + title_index = list.indexOf(res); } - return program_id; + const auto& [title_id, title_type, record_type] = *available_title_ids.nth(title_index); + *selected_title_id = title_id; + *selected_content_record_type = static_cast(record_type); + return true; } bool GMainWindow::ConfirmClose() { @@ -4372,7 +4813,6 @@ void GMainWindow::closeEvent(QCloseEvent* event) { render_window->close(); multiplayer_state->Close(); - //system->HIDCore().UnloadInputDevices(); system->GetRoomNetwork().Shutdown(); QWidget::closeEvent(event); @@ -4469,6 +4909,8 @@ void GMainWindow::RequestGameExit() { auto applet_ae = sm.GetService("appletAE"); bool has_signalled = false; + system->SetExitRequested(true); + if (applet_oe != nullptr) { applet_oe->GetMessageQueue()->RequestExit(); has_signalled = true;