diff --git a/.ci/license-header.sh b/.ci/license-header.sh index d98f42fc97..3d4929d1c1 100755 --- a/.ci/license-header.sh +++ b/.ci/license-header.sh @@ -15,7 +15,6 @@ FILES=`git diff --name-only $BASE` #FILES=$(git diff --name-only master) -echo $FILES echo "Done" check_header() { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index 98f342c274..b383cc147f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt @@ -232,8 +232,6 @@ class GameAdapter(private val activity: AppCompatActivity) : binding.root.findNavController().navigate(action) } - val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) - if (NativeLibrary.gameRequiresFirmware(game.programId) && !NativeLibrary.isFirmwareAvailable()) { MaterialAlertDialogBuilder(activity) .setTitle(R.string.loader_requires_firmware) @@ -248,23 +246,6 @@ class GameAdapter(private val activity: AppCompatActivity) : } .setNegativeButton(android.R.string.cancel) { _, _ -> } .show() - } else if (BooleanSetting.DISABLE_NCA_VERIFICATION.getBoolean(false) && !preferences.getBoolean( - Settings.PREF_HIDE_NCA_POPUP, false)) { - MaterialAlertDialogBuilder(activity) - .setTitle(R.string.nca_verification_disabled) - .setMessage(activity.getString(R.string.nca_verification_disabled_description)) - .setPositiveButton(android.R.string.ok) { _, _ -> - launch() - } - .setNeutralButton(R.string.dont_show_again) { _, _ -> - preferences.edit { - putBoolean(Settings.PREF_HIDE_NCA_POPUP, true) - } - - launch() - } - .setNegativeButton(android.R.string.cancel) { _, _ -> } - .show() } else { launch() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index 6d4bfd97ac..3c5b9003de 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -35,7 +35,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { RENDERER_SAMPLE_SHADING("sample_shading"), PICTURE_IN_PICTURE("picture_in_picture"), USE_CUSTOM_RTC("custom_rtc_enabled"), - DISABLE_NCA_VERIFICATION("disable_nca_verification"), BLACK_BACKGROUNDS("black_backgrounds"), JOYSTICK_REL_CENTER("joystick_rel_center"), DPAD_SLIDE("dpad_slide"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 883d8efaef..1f2ba81a73 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -297,13 +297,6 @@ abstract class SettingsItem( descriptionId = R.string.use_custom_rtc_description ) ) - put( - SwitchSetting( - BooleanSetting.DISABLE_NCA_VERIFICATION, - titleId = R.string.disable_nca_verification, - descriptionId = R.string.disable_nca_verification_description - ) - ) put( StringInputSetting( StringSetting.WEB_TOKEN, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 630bcb0d74..14d62ceec3 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -210,7 +210,6 @@ class SettingsFragmentPresenter( add(IntSetting.LANGUAGE_INDEX.key) add(BooleanSetting.USE_CUSTOM_RTC.key) add(LongSetting.CUSTOM_RTC.key) - add(BooleanSetting.DISABLE_NCA_VERIFICATION.key) add(HeaderSetting(R.string.network)) add(StringSetting.WEB_TOKEN.key) diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 2c7923d5a3..6e428a1f7f 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -784,9 +784,6 @@ Game Requires Firmware dump and install firmware, or press "OK" to launch anyways.]]> - NCA Verification Disabled - This is required to run new games and updates, but may cause instability or crashes if NCA files are corrupt, modified, or tampered with. If unsure, re-enable verification in Advanced Settings -> System, and use firmware versions of 19.0.1 or below. - Searching for game... Game not found for Title ID: %1$s diff --git a/src/common/settings.h b/src/common/settings.h index 9d448a2b38..fafd765804 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -626,10 +626,6 @@ struct Values { true, true, &rng_seed_enabled}; Setting device_name{ linkage, "Eden", "device_name", Category::System, Specialization::Default, true, true}; - SwitchableSetting disable_nca_verification{linkage, true, "disable_nca_verification", - Category::System, Specialization::Default}; - Setting hide_nca_verification_popup{ - linkage, false, "hide_nca_verification_popup", Category::System, Specialization::Default}; Setting current_user{linkage, 0, "current_user", Category::System}; diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp index 615a624f4f..71ba458cef 100644 --- a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp @@ -238,9 +238,7 @@ void BucketTree::Initialize(size_t node_size, s64 end_offset) { ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); ASSERT(Common::IsPowerOfTwo(node_size)); - if (!Settings::values.disable_nca_verification.GetValue()) { - ASSERT(end_offset > 0); - } + ASSERT(end_offset > 0); ASSERT(!this->IsInitialized()); m_node_size = node_size; diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp index 4cfa5c58f8..4e624ad3f4 100644 --- a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -1296,91 +1299,65 @@ Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl( ASSERT(base_storage != nullptr); ASSERT(layer_info_offset >= 0); - if (!Settings::values.disable_nca_verification.GetValue()) { - // Define storage types. - using VerificationStorage = HierarchicalIntegrityVerificationStorage; - using StorageInfo = VerificationStorage::HierarchicalStorageInformation; + // Define storage types. + using VerificationStorage = HierarchicalIntegrityVerificationStorage; + using StorageInfo = VerificationStorage::HierarchicalStorageInformation; - // Validate the meta info. - HierarchicalIntegrityVerificationInformation level_hash_info; - std::memcpy(std::addressof(level_hash_info), std::addressof(meta_info.level_hash_info), - sizeof(level_hash_info)); + // Validate the meta info. + HierarchicalIntegrityVerificationInformation level_hash_info; + std::memcpy(std::addressof(level_hash_info), + std::addressof(meta_info.level_hash_info), + sizeof(level_hash_info)); - R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers, - ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); - R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount, - ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); + R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers, + ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); + R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount, + ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); - // Get the base storage size. - s64 base_storage_size = base_storage->GetSize(); + // Get the base storage size. + s64 base_storage_size = base_storage->GetSize(); - // Create storage info. - StorageInfo storage_info; - for (s32 i = 0; i < static_cast(level_hash_info.max_layers - 2); ++i) { - const auto& layer_info = level_hash_info.info[i]; - R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size, - ResultNcaBaseStorageOutOfRangeD); - - storage_info[i + 1] = std::make_shared( - base_storage, layer_info.size, layer_info_offset + layer_info.offset); - } - - // Set the last layer info. - const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2]; - const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get(); - R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size, + // Create storage info. + StorageInfo storage_info; + for (s32 i = 0; i < static_cast(level_hash_info.max_layers - 2); ++i) { + const auto& layer_info = level_hash_info.info[i]; + R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size, ResultNcaBaseStorageOutOfRangeD); - if (layer_info_offset > 0) { - R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset, - ResultRomNcaInvalidIntegrityLayerInfoOffset); - } - storage_info[level_hash_info.max_layers - 1] = std::make_shared( - std::move(base_storage), layer_info.size, last_layer_info_offset); - // Make the integrity romfs storage. - auto integrity_storage = std::make_shared(); - R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); - - // Initialize the integrity storage. - R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info, - max_data_cache_entries, max_hash_cache_entries, - buffer_level)); - - // Set the output. - *out = std::move(integrity_storage); - R_SUCCEED(); - } else { - // Read IVFC layout - HierarchicalIntegrityVerificationInformation lhi{}; - std::memcpy(std::addressof(lhi), std::addressof(meta_info.level_hash_info), sizeof(lhi)); - - R_UNLESS(IntegrityMinLayerCount <= lhi.max_layers, - ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); - R_UNLESS(lhi.max_layers <= IntegrityMaxLayerCount, - ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); - - const auto& data_li = lhi.info[lhi.max_layers - 2]; - - const s64 base_size = base_storage->GetSize(); - - // Compute the data layer window - const s64 data_off = (layer_info_offset > 0) ? 0LL : data_li.offset.Get(); - R_UNLESS(data_off + data_li.size <= base_size, ResultNcaBaseStorageOutOfRangeD); - if (layer_info_offset > 0) { - R_UNLESS(data_off + data_li.size <= layer_info_offset, - ResultRomNcaInvalidIntegrityLayerInfoOffset); - } - - // TODO: Passthrough (temporary compatibility: integrity disabled) - auto data_view = std::make_shared(base_storage, data_li.size, data_off); - R_UNLESS(data_view != nullptr, ResultAllocationMemoryFailedAllocateShared); - - auto passthrough = std::make_shared(std::move(data_view)); - R_UNLESS(passthrough != nullptr, ResultAllocationMemoryFailedAllocateShared); - - *out = std::move(passthrough); - R_SUCCEED(); + storage_info[i + 1] = std::make_shared(base_storage, + layer_info.size, + layer_info_offset + layer_info.offset); } + + // Set the last layer info. + const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2]; + const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get(); + R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size, + ResultNcaBaseStorageOutOfRangeD); + if (layer_info_offset > 0) { + R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset, + ResultRomNcaInvalidIntegrityLayerInfoOffset); + } + storage_info[level_hash_info.max_layers - 1] + = std::make_shared(std::move(base_storage), + layer_info.size, + last_layer_info_offset); + + // Make the integrity romfs storage. + auto integrity_storage = std::make_shared(); + R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the integrity storage. + R_TRY(integrity_storage->Initialize(level_hash_info, + meta_info.master_hash, + storage_info, + max_data_cache_entries, + max_hash_cache_entries, + buffer_level)); + + // Set the output. + *out = std::move(integrity_storage); + R_SUCCEED(); } Result NcaFileSystemDriver::CreateRegionSwitchStorage(VirtualFile* out, diff --git a/src/qt_common/CMakeLists.txt b/src/qt_common/CMakeLists.txt index 1acf8344a1..9d292da401 100644 --- a/src/qt_common/CMakeLists.txt +++ b/src/qt_common/CMakeLists.txt @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # SPDX-FileCopyrightText: 2023 yuzu Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later +find_package(Qt6 REQUIRED COMPONENTS Core) find_package(Qt6 REQUIRED COMPONENTS Core) add_library(qt_common STATIC @@ -22,6 +26,8 @@ add_library(qt_common STATIC qt_content_util.h qt_content_util.cpp qt_rom_util.h qt_rom_util.cpp qt_applet_util.h qt_applet_util.cpp + qt_progress_dialog.h qt_progress_dialog.cpp + ) create_target_directory_groups(qt_common) @@ -33,7 +39,8 @@ endif() add_subdirectory(externals) -target_link_libraries(qt_common PRIVATE core Qt6::Core SimpleIni::SimpleIni QuaZip::QuaZip) +target_link_libraries(qt_common PRIVATE core Qt6::Core SimpleIni::SimpleIni QuaZip::QuaZip frozen::frozen) +target_link_libraries(qt_common PRIVATE Qt6::Core) if (NOT WIN32) target_include_directories(qt_common PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) diff --git a/src/qt_common/externals/CMakeLists.txt b/src/qt_common/externals/CMakeLists.txt index 50594a741f..189a52c0a6 100644 --- a/src/qt_common/externals/CMakeLists.txt +++ b/src/qt_common/externals/CMakeLists.txt @@ -14,3 +14,7 @@ set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON) # QuaZip AddJsonPackage(quazip) + +# frozen +# TODO(crueter): Qt String Lookup +AddJsonPackage(frozen) diff --git a/src/qt_common/externals/cpmfile.json b/src/qt_common/externals/cpmfile.json index e3590d0f7f..0b464b95b2 100644 --- a/src/qt_common/externals/cpmfile.json +++ b/src/qt_common/externals/cpmfile.json @@ -8,5 +8,12 @@ "options": [ "QUAZIP_INSTALL OFF" ] + }, + "frozen": { + "package": "frozen", + "repo": "serge-sans-paille/frozen", + "sha": "61dce5ae18", + "hash": "1ae3d073e659c1f24b2cdd76379c90d6af9e06bc707d285a4fafce05f7a4c9e592ff208c94a9ae0f0d07620b3c6cec191f126b03d70ad4dfa496a86ed5658a6d", + "bundled": true } } diff --git a/src/qt_common/qt_common.cpp b/src/qt_common/qt_common.cpp index 9c20f1e6d9..6be241c740 100644 --- a/src/qt_common/qt_common.cpp +++ b/src/qt_common/qt_common.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "qt_common.h" +#include "common/fs/fs.h" #include #include @@ -22,9 +23,15 @@ namespace QtCommon { -QObject *rootObject = nullptr; +#ifdef YUZU_QT_WIDGETS +QWidget* rootObject = nullptr; +#else +QObject* rootObject = nullptr; +#endif + std::unique_ptr system = nullptr; std::shared_ptr vfs = nullptr; +std::unique_ptr provider = nullptr; Core::Frontend::WindowSystemType GetWindowSystemType() { @@ -81,11 +88,35 @@ const QString tr(const std::string& str) return QGuiApplication::tr(str.c_str()); } +#ifdef YUZU_QT_WIDGETS +void Init(QWidget* root) +#else void Init(QObject* root) +#endif { system = std::make_unique(); rootObject = root; vfs = std::make_unique(); + provider = std::make_unique(); +} + +std::filesystem::path GetEdenCommand() { + std::filesystem::path command; + + QString appimage = QString::fromLocal8Bit(getenv("APPIMAGE")); + if (!appimage.isEmpty()) { + command = std::filesystem::path{appimage.toStdString()}; + } else { + const QStringList args = QGuiApplication::arguments(); + command = args[0].toStdString(); + } + + // If relative path, make it an absolute path + if (command.c_str()[0] == '.') { + command = Common::FS::GetCurrentDir() / command; + } + + return command; } } // namespace QtCommon diff --git a/src/qt_common/qt_common.h b/src/qt_common/qt_common.h index 5f9b5d7de9..a2700427ab 100644 --- a/src/qt_common/qt_common.h +++ b/src/qt_common/qt_common.h @@ -4,18 +4,25 @@ #ifndef QT_COMMON_H #define QT_COMMON_H -#include #include #include "core/core.h" +#include "core/file_sys/registered_cache.h" #include +#include #include namespace QtCommon { +#ifdef YUZU_QT_WIDGETS +extern QWidget *rootObject; +#else extern QObject *rootObject; +#endif + extern std::unique_ptr system; extern std::shared_ptr vfs; +extern std::unique_ptr provider; typedef std::function QtProgressCallback; @@ -23,10 +30,15 @@ Core::Frontend::WindowSystemType GetWindowSystemType(); Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow *window); +#ifdef YUZU_QT_WIDGETS +void Init(QWidget *root); +#else void Init(QObject *root); +#endif const QString tr(const char *str); const QString tr(const std::string &str); +std::filesystem::path GetEdenCommand(); } // namespace QtCommon #endif diff --git a/src/qt_common/qt_content_util.cpp b/src/qt_common/qt_content_util.cpp index 5f8ca14504..e4625aa423 100644 --- a/src/qt_common/qt_content_util.cpp +++ b/src/qt_common/qt_content_util.cpp @@ -6,6 +6,7 @@ #include "frontend_common/content_manager.h" #include "frontend_common/firmware_manager.h" #include "qt_common/qt_common.h" +#include "qt_common/qt_progress_dialog.h" #include "qt_frontend_util.h" #include @@ -31,24 +32,34 @@ bool CheckGameFirmware(u64 program_id, QObject* parent) return true; } -FirmwareInstallResult InstallFirmware(const QString& location, - bool recursive, - QtProgressCallback callback, - FileSys::VfsFilesystem* vfs) +void InstallFirmware(const QString& location, bool recursive) { - static constexpr const char* failedTitle = "Firmware Install Failed"; - static constexpr const char* successTitle = "Firmware Install Failed"; - static constexpr QMessageBox::StandardButtons buttons = QMessageBox::Ok; + QtCommon::Frontend::QtProgressDialog progress(tr("Installing Firmware..."), + tr("Cancel"), + 0, + 100, + rootObject); + progress.setWindowModality(Qt::WindowModal); + progress.setMinimumDuration(100); + progress.setAutoClose(false); + progress.setAutoReset(false); + progress.show(); + // Declare progress callback. + auto callback = [&](size_t total_size, size_t processed_size) { + progress.setValue(static_cast((processed_size * 100) / total_size)); + return progress.wasCanceled(); + }; + + static constexpr const char* failedTitle = "Firmware Install Failed"; + static constexpr const char* successTitle = "Firmware Install Succeeded"; QMessageBox::Icon icon; FirmwareInstallResult result; const auto ShowMessage = [&]() { QtCommon::Frontend::ShowMessage(icon, failedTitle, - GetFirmwareInstallResultString(result), - buttons, - rootObject); + GetFirmwareInstallResultString(result)); }; LOG_INFO(Frontend, "Installing firmware from {}", location.toStdString()); @@ -57,7 +68,7 @@ FirmwareInstallResult InstallFirmware(const QString& location, // there.) std::filesystem::path firmware_source_path = location.toStdString(); if (!Common::FS::IsDir(firmware_source_path)) { - return FirmwareInstallResult::NoOp; + return; } std::vector out; @@ -86,7 +97,7 @@ FirmwareInstallResult InstallFirmware(const QString& location, result = FirmwareInstallResult::NoNCAs; icon = QMessageBox::Warning; ShowMessage(); - return result; + return; } // Locate and erase the content of nand/system/Content/registered/*.nca, if any. @@ -96,7 +107,7 @@ FirmwareInstallResult InstallFirmware(const QString& location, result = FirmwareInstallResult::FailedDelete; icon = QMessageBox::Critical; ShowMessage(); - return result; + return; } LOG_INFO(Frontend, @@ -127,7 +138,7 @@ FirmwareInstallResult InstallFirmware(const QString& location, result = FirmwareInstallResult::FailedCorrupted; icon = QMessageBox::Warning; ShowMessage(); - return result; + return; } } @@ -135,12 +146,34 @@ FirmwareInstallResult InstallFirmware(const QString& location, result = FirmwareInstallResult::FailedCopy; icon = QMessageBox::Critical; ShowMessage(); - return result; + return; } // Re-scan VFS for the newly placed firmware files. system->GetFileSystemController().CreateFactories(*vfs); + auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) { + progress.setValue(90 + static_cast((processed_size * 10) / total_size)); + return progress.wasCanceled(); + }; + + auto results = ContentManager::VerifyInstalledContents(*QtCommon::system, + *QtCommon::provider, + VerifyFirmwareCallback, + true); + + if (results.size() > 0) { + const auto failed_names = QString::fromStdString( + fmt::format("{}", fmt::join(results, "\n"))); + progress.close(); + QtCommon::Frontend::Critical(tr("Firmware integrity verification failed!"), + tr("Verification failed for the following files:\n\n%1") + .arg(failed_names)); + return; + } + + progress.close(); + const auto pair = FirmwareManager::GetFirmwareVersion(*system); const auto firmware_data = pair.first; const std::string display_version(firmware_data.display_version.data()); @@ -149,9 +182,7 @@ FirmwareInstallResult InstallFirmware(const QString& location, QtCommon::Frontend::Information(rootObject, tr(successTitle), tr(GetFirmwareInstallResultString(result)) - .arg(QString::fromStdString(display_version)), - buttons); - return result; + .arg(QString::fromStdString(display_version))); } QString UnzipFirmwareToTmp(const QString& location) @@ -179,8 +210,23 @@ QString UnzipFirmwareToTmp(const QString& location) } // Content // -void VerifyGameContents(const std::string& game_path, QtProgressCallback callback) +void VerifyGameContents(const std::string& game_path) { + QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."), + tr("Cancel"), + 0, + 100, + rootObject); + progress.setWindowModality(Qt::WindowModal); + progress.setMinimumDuration(100); + progress.setAutoClose(false); + progress.setAutoReset(false); + + const auto callback = [&](size_t total_size, size_t processed_size) { + progress.setValue(static_cast((processed_size * 100) / total_size)); + return progress.wasCanceled(); + }; + const auto result = ContentManager::VerifyGameContents(*system, game_path, callback); switch (result) { @@ -234,4 +280,34 @@ void InstallKeys() } } +void VerifyInstalledContents() { + // Initialize a progress dialog. + QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, QtCommon::rootObject); + progress.setWindowModality(Qt::WindowModal); + progress.setMinimumDuration(100); + progress.setAutoClose(false); + progress.setAutoReset(false); + + // Declare progress callback. + auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { + progress.setValue(static_cast((processed_size * 100) / total_size)); + return progress.wasCanceled(); + }; + + const std::vector result = + ContentManager::VerifyInstalledContents(*QtCommon::system, *QtCommon::provider, QtProgressCallback); + progress.close(); + + if (result.empty()) { + QtCommon::Frontend::Information(tr("Integrity verification succeeded!"), + tr("The operation completed successfully.")); + } else { + const auto failed_names = + QString::fromStdString(fmt::format("{}", fmt::join(result, "\n"))); + QtCommon::Frontend::Critical( + tr("Integrity verification failed!"), + tr("Verification failed for the following files:\n\n%1").arg(failed_names)); + } +} + } // namespace QtCommon::Content diff --git a/src/qt_common/qt_content_util.h b/src/qt_common/qt_content_util.h index b401bdf1e3..b572c1c4a3 100644 --- a/src/qt_common/qt_content_util.h +++ b/src/qt_common/qt_content_util.h @@ -6,8 +6,6 @@ #include #include "common/common_types.h" -#include "core/core.h" -#include "qt_common/qt_common.h" namespace QtCommon::Content { @@ -37,11 +35,7 @@ inline constexpr const char *GetFirmwareInstallResultString(FirmwareInstallResul return FIRMWARE_RESULTS.at(static_cast(result)); } -FirmwareInstallResult InstallFirmware( - const QString &location, - bool recursive, - QtProgressCallback callback, - FileSys::VfsFilesystem *vfs); +void InstallFirmware(const QString &location, bool recursive); QString UnzipFirmwareToTmp(const QString &location); @@ -49,6 +43,7 @@ QString UnzipFirmwareToTmp(const QString &location); void InstallKeys(); // Content // -void VerifyGameContents(const std::string &game_path, QtProgressCallback callback); +void VerifyGameContents(const std::string &game_path); +void VerifyInstalledContents(); } #endif // QT_CONTENT_UTIL_H diff --git a/src/qt_common/qt_frontend_util.cpp b/src/qt_common/qt_frontend_util.cpp index d357c11338..d519669ad5 100644 --- a/src/qt_common/qt_frontend_util.cpp +++ b/src/qt_common/qt_frontend_util.cpp @@ -10,11 +10,8 @@ namespace QtCommon::Frontend { -QMessageBox::StandardButton ShowMessage(QMessageBox::Icon icon, - const QString &title, - const QString &text, - QMessageBox::StandardButtons buttons, - QObject *parent) +StandardButton ShowMessage( + Icon icon, const QString &title, const QString &text, StandardButtons buttons, QObject *parent) { #ifdef YUZU_QT_WIDGETS QMessageBox *box = new QMessageBox(icon, title, text, buttons, (QWidget *) parent); diff --git a/src/qt_common/qt_frontend_util.h b/src/qt_common/qt_frontend_util.h index 7f43286430..f86b9e1357 100644 --- a/src/qt_common/qt_frontend_util.h +++ b/src/qt_common/qt_frontend_util.h @@ -5,12 +5,12 @@ #define QT_FRONTEND_UTIL_H #include -#include #include "qt_common/qt_common.h" #ifdef YUZU_QT_WIDGETS #include #include +#include #endif /** @@ -23,6 +23,11 @@ Q_NAMESPACE #ifdef YUZU_QT_WIDGETS using Options = QFileDialog::Options; using Option = QFileDialog::Option; + +using StandardButton = QMessageBox::StandardButton; +using StandardButtons = QMessageBox::StandardButtons; + +using Icon = QMessageBox::Icon; #else enum Option { ShowDirsOnly = 0x00000001, @@ -36,44 +41,96 @@ enum Option { Q_ENUM_NS(Option) Q_DECLARE_FLAGS(Options, Option) Q_FLAG_NS(Options) + +enum StandardButton { + // keep this in sync with QDialogButtonBox::StandardButton and QPlatformDialogHelper::StandardButton + NoButton = 0x00000000, + Ok = 0x00000400, + Save = 0x00000800, + SaveAll = 0x00001000, + Open = 0x00002000, + Yes = 0x00004000, + YesToAll = 0x00008000, + No = 0x00010000, + NoToAll = 0x00020000, + Abort = 0x00040000, + Retry = 0x00080000, + Ignore = 0x00100000, + Close = 0x00200000, + Cancel = 0x00400000, + Discard = 0x00800000, + Help = 0x01000000, + Apply = 0x02000000, + Reset = 0x04000000, + RestoreDefaults = 0x08000000, + + FirstButton = Ok, // internal + LastButton = RestoreDefaults, // internal + + YesAll = YesToAll, // obsolete + NoAll = NoToAll, // obsolete + + Default = 0x00000100, // obsolete + Escape = 0x00000200, // obsolete + FlagMask = 0x00000300, // obsolete + ButtonMask = ~FlagMask // obsolete +}; +Q_ENUM_NS(StandardButton) + +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) +typedef StandardButton Button; +#endif +Q_DECLARE_FLAGS(StandardButtons, StandardButton) +Q_FLAG_NS(StandardButtons) + +enum Icon { + // keep this in sync with QMessageDialogOptions::StandardIcon + NoIcon = 0, + Information = 1, + Warning = 2, + Critical = 3, + Question = 4 +}; +Q_ENUM_NS(Icon) + #endif // TODO(crueter) widgets-less impl, choices et al. -QMessageBox::StandardButton ShowMessage(QMessageBox::Icon icon, +StandardButton ShowMessage(Icon icon, const QString &title, const QString &text, - QMessageBox::StandardButtons buttons = QMessageBox::NoButton, + StandardButtons buttons = StandardButton::NoButton, QObject *parent = nullptr); #define UTIL_OVERRIDES(level) \ - inline QMessageBox::StandardButton level(QObject *parent, \ + inline StandardButton level(QObject *parent, \ const QString &title, \ const QString &text, \ - QMessageBox::StandardButtons buttons = QMessageBox::Ok) \ + StandardButtons buttons = StandardButton::Ok) \ { \ - return ShowMessage(QMessageBox::level, title, text, buttons, parent); \ + return ShowMessage(Icon::level, title, text, buttons, parent); \ } \ - inline QMessageBox::StandardButton level(QObject *parent, \ + inline StandardButton level(QObject *parent, \ const char *title, \ const char *text, \ - QMessageBox::StandardButtons buttons \ - = QMessageBox::Ok) \ + StandardButtons buttons \ + = StandardButton::Ok) \ { \ - return ShowMessage(QMessageBox::level, tr(title), tr(text), buttons, parent); \ + return ShowMessage(Icon::level, tr(title), tr(text), buttons, parent); \ } \ - inline QMessageBox::StandardButton level(const char *title, \ + inline StandardButton level(const char *title, \ const char *text, \ - QMessageBox::StandardButtons buttons \ - = QMessageBox::Ok) \ + StandardButtons buttons \ + = StandardButton::Ok) \ { \ - return ShowMessage(QMessageBox::level, tr(title), tr(text), buttons, rootObject); \ + return ShowMessage(Icon::level, tr(title), tr(text), buttons, rootObject); \ } \ - inline QMessageBox::StandardButton level(const QString title, \ + inline StandardButton level(const QString title, \ const QString &text, \ - QMessageBox::StandardButtons buttons \ - = QMessageBox::Ok) \ + StandardButtons buttons \ + = StandardButton::Ok) \ { \ - return ShowMessage(QMessageBox::level, title, text, buttons, rootObject); \ + return ShowMessage(Icon::level, title, text, buttons, rootObject); \ } UTIL_OVERRIDES(Information) diff --git a/src/qt_common/qt_game_util.cpp b/src/qt_common/qt_game_util.cpp index 488eb8c971..292eefa224 100644 --- a/src/qt_common/qt_game_util.cpp +++ b/src/qt_common/qt_game_util.cpp @@ -5,12 +5,15 @@ #include "common/fs/fs.h" #include "common/fs/path_util.h" #include "core/file_sys/savedata_factory.h" +#include "core/hle/service/am/am_types.h" #include "frontend_common/content_manager.h" #include "qt_common.h" #include "qt_common/uisettings.h" #include "qt_frontend_util.h" +#include "yuzu/util/util.h" #include +#include #include #ifdef _WIN32 @@ -397,4 +400,178 @@ void ResetMetadata() } } +// Uhhh // + +// Messages in pre-defined message boxes for less code spaghetti +inline constexpr bool CreateShortcutMessagesGUI(ShortcutMessages imsg, const QString& game_title) +{ + int result = 0; + QMessageBox::StandardButtons buttons; + switch (imsg) { + case ShortcutMessages::Fullscreen: + buttons = QMessageBox::Yes | QMessageBox::No; + result + = QtCommon::Frontend::Information(tr("Create Shortcut"), + tr("Do you want to launch the game in fullscreen?"), + buttons); + return result == QMessageBox::Yes; + case ShortcutMessages::Success: + QtCommon::Frontend::Information(tr("Shortcut Created"), + tr("Successfully created a shortcut to %1").arg(game_title)); + return false; + case ShortcutMessages::Volatile: + buttons = QMessageBox::StandardButton::Ok | QMessageBox::StandardButton::Cancel; + result = QtCommon::Frontend::Warning( + tr("Shortcut may be Volatile!"), + tr("This will create a shortcut to the current AppImage. This may " + "not work well if you update. Continue?"), + buttons); + return result == QMessageBox::Ok; + default: + buttons = QMessageBox::Ok; + QtCommon::Frontend::Critical(tr("Failed to Create Shortcut"), + tr("Failed to create a shortcut to %1").arg(game_title), + buttons); + return false; + } +} + +void CreateShortcut(const std::string& game_path, + const u64 program_id, + const std::string& game_title_, + const ShortcutTarget &target, + std::string arguments_, + const bool needs_title) +{ + // Get path to Eden executable + std::filesystem::path command = GetEdenCommand(); + + // Shortcut path + std::filesystem::path shortcut_path = GetShortcutPath(target); + + if (!std::filesystem::exists(shortcut_path)) { + CreateShortcutMessagesGUI(ShortcutMessages::Failed, + QString::fromStdString(shortcut_path.generic_string())); + LOG_ERROR(Frontend, "Invalid shortcut target {}", shortcut_path.generic_string()); + return; + } + + const FileSys::PatchManager pm{program_id, QtCommon::system->GetFileSystemController(), + QtCommon::system->GetContentProvider()}; + const auto control = pm.GetControlMetadata(); + const auto loader = + Loader::GetLoader(*QtCommon::system, QtCommon::vfs->OpenFile(game_path, FileSys::OpenMode::Read)); + + std::string game_title{game_title_}; + + // Delete illegal characters from title + if (needs_title) { + game_title = fmt::format("{:016X}", program_id); + if (control.first != nullptr) { + game_title = control.first->GetApplicationName(); + } else { + loader->ReadTitle(game_title); + } + } + + const std::string illegal_chars = "<>:\"/\\|?*."; + for (auto it = game_title.rbegin(); it != game_title.rend(); ++it) { + if (illegal_chars.find(*it) != std::string::npos) { + game_title.erase(it.base() - 1); + } + } + + const QString qgame_title = QString::fromStdString(game_title); + + // Get icon from game file + std::vector icon_image_file{}; + if (control.second != nullptr) { + icon_image_file = control.second->ReadAllBytes(); + } else if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) { + LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); + } + + QImage icon_data = + QImage::fromData(icon_image_file.data(), static_cast(icon_image_file.size())); + std::filesystem::path out_icon_path; + if (QtCommon::Game::MakeShortcutIcoPath(program_id, game_title, out_icon_path)) { + if (!SaveIconToFile(out_icon_path, icon_data)) { + LOG_ERROR(Frontend, "Could not write icon to file"); + } + } else { + QtCommon::Frontend::Critical( + tr("Create Icon"), + tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.") + .arg(QString::fromStdString(out_icon_path.string()))); + } + +#if defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) + // Special case for AppImages + // Warn once if we are making a shortcut to a volatile AppImage + if (command.string().ends_with(".AppImage") && !UISettings::values.shortcut_already_warned) { + if (!CreateShortcutMessagesGUI(ShortcutMessages::Volatile, qgame_title)) { + return; + } + UISettings::values.shortcut_already_warned = true; + } +#endif + + // Create shortcut + std::string arguments{arguments_}; + if (CreateShortcutMessagesGUI(ShortcutMessages::Fullscreen, qgame_title)) { + arguments = "-f " + arguments; + } + const std::string comment = fmt::format("Start {:s} with the Eden Emulator", game_title); + const std::string categories = "Game;Emulator;Qt;"; + const std::string keywords = "Switch;Nintendo;"; + + if (QtCommon::Game::CreateShortcutLink(shortcut_path, comment, out_icon_path, command, + arguments, categories, keywords, game_title)) { + CreateShortcutMessagesGUI(ShortcutMessages::Success, + qgame_title); + return; + } + CreateShortcutMessagesGUI(ShortcutMessages::Failed, + qgame_title); +} + +constexpr std::string GetShortcutPath(ShortcutTarget target) { + { + std::string shortcut_path{}; + if (target == ShortcutTarget::Desktop) { + shortcut_path = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) + .toStdString(); + } else if (target == ShortcutTarget::Applications) { + shortcut_path = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + .toStdString(); + } + + return shortcut_path; + } +} + +void CreateHomeMenuShortcut(ShortcutTarget target) { + constexpr u64 QLaunchId = static_cast(Service::AM::AppletProgramId::QLaunch); + auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); + if (!bis_system) { + QtCommon::Frontend::Warning(tr("No firmware available"), + tr("Please install firmware to use the home menu.")); + return; + } + + auto qlaunch_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program); + if (!qlaunch_nca) { + QtCommon::Frontend::Warning(tr("Home Menu Applet"), + tr("Home Menu is not available. Please reinstall firmware.")); + return; + } + + auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program); + const auto game_path = qlaunch_applet_nca->GetFullPath(); + + // TODO(crueter): Make this use the Eden icon + CreateShortcut(game_path, QLaunchId, "Switch Home Menu", target, "-qlaunch", false); +} + + } // namespace QtCommon::Game diff --git a/src/qt_common/qt_game_util.h b/src/qt_common/qt_game_util.h index 9861910833..2872734390 100644 --- a/src/qt_common/qt_game_util.h +++ b/src/qt_common/qt_game_util.h @@ -5,6 +5,7 @@ #define QT_GAME_UTIL_H #include +#include #include "common/fs/path_util.h" #include @@ -24,6 +25,18 @@ enum class GameListRemoveTarget { CacheStorage, }; +enum class ShortcutTarget { + Desktop, + Applications, +}; + +enum class ShortcutMessages{ + Fullscreen = 0, + Success = 1, + Volatile = 2, + Failed = 3 +}; + bool CreateShortcutLink(const std::filesystem::path& shortcut_path, const std::string& comment, const std::filesystem::path& icon_path, @@ -57,6 +70,17 @@ void RemoveCacheStorage(u64 program_id, FileSys::VfsFilesystem* vfs); // Metadata // void ResetMetadata(); +// Shortcuts // +void CreateShortcut(const std::string& game_path, + const u64 program_id, + const std::string& game_title_, + const ShortcutTarget& target, + std::string arguments_, + const bool needs_title); + +constexpr std::string GetShortcutPath(ShortcutTarget target); +void CreateHomeMenuShortcut(ShortcutTarget target); + } #endif // QT_GAME_UTIL_H diff --git a/src/qt_common/qt_progress_dialog.cpp b/src/qt_common/qt_progress_dialog.cpp new file mode 100644 index 0000000000..b4bf74c8bd --- /dev/null +++ b/src/qt_common/qt_progress_dialog.cpp @@ -0,0 +1,4 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "qt_progress_dialog.h" diff --git a/src/qt_common/qt_progress_dialog.h b/src/qt_common/qt_progress_dialog.h new file mode 100644 index 0000000000..17f6817ffa --- /dev/null +++ b/src/qt_common/qt_progress_dialog.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef QT_PROGRESS_DIALOG_H +#define QT_PROGRESS_DIALOG_H + +#include + +#ifdef YUZU_QT_WIDGETS +#include +#endif + +namespace QtCommon::Frontend { +#ifdef YUZU_QT_WIDGETS + +using QtProgressDialog = QProgressDialog; + +// TODO(crueter): QML impl +#else +class QtProgressDialog +{ +public: + QtProgressDialog(const QString &labelText, + const QString &cancelButtonText, + int minimum, + int maximum, + QObject *parent = nullptr, + Qt::WindowFlags f = Qt::WindowFlags()); + + bool wasCanceled() const; + void setWindowModality(Qt::WindowModality modality); + void setMinimumDuration(int durationMs); + void setAutoClose(bool autoClose); + void setAutoReset(bool autoReset); + +public slots: + void setLabelText(QString &text); + void setRange(int min, int max); + void setValue(int progress); + bool close(); + + void show(); +}; +#endif // YUZU_QT_WIDGETS + +} +#endif // QT_PROGRESS_DIALOG_H diff --git a/src/qt_common/shared_translation.cpp b/src/qt_common/shared_translation.cpp index 81ec35b6d8..8f31e07154 100644 --- a/src/qt_common/shared_translation.cpp +++ b/src/qt_common/shared_translation.cpp @@ -29,10 +29,10 @@ std::unique_ptr InitializeTranslations(QObject* parent) #define INSERT(SETTINGS, ID, NAME, TOOLTIP) \ translations->insert(std::pair{SETTINGS::values.ID.Id(), std::pair{(NAME), (TOOLTIP)}}) - // A setting can be ignored by giving it a blank name + // A setting can be ignored by giving it a blank name - // Applets - INSERT(Settings, cabinet_applet_mode, tr("Amiibo editor"), QString()); + // Applets + INSERT(Settings, cabinet_applet_mode, tr("Amiibo editor"), QString()); INSERT(Settings, controller_applet_mode, tr("Controller configuration"), QString()); INSERT(Settings, data_erase_applet_mode, tr("Data erase"), QString()); INSERT(Settings, error_applet_mode, tr("Error"), QString()); @@ -407,12 +407,6 @@ std::unique_ptr InitializeTranslations(QObject* parent) "their resolution, details and supported controllers and depending on this setting.\n" "Setting to Handheld can help improve performance for low end systems.")); INSERT(Settings, current_user, QString(), QString()); - INSERT(Settings, disable_nca_verification, tr("Disable NCA Verification"), - tr("Disables integrity verification of NCA content archives." - "\nThis may improve loading speed but risks data corruption or invalid files going " - "undetected.\n" - "Is necessary to make games and updates work that needs firmware 20+.")); - INSERT(Settings, hide_nca_verification_popup, QString(), QString()); // Controls diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 7c89730651..fa61cdfb1f 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -650,10 +650,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri // TODO: Implement shortcut creation for macOS #if !defined(__APPLE__) connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() { - emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop); + emit CreateShortcut(program_id, path, QtCommon::Game::ShortcutTarget::Desktop); }); connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() { - emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications); + emit CreateShortcut(program_id, path, QtCommon::Game::ShortcutTarget::Applications); }); #endif connect(properties, &QAction::triggered, diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 367d047c40..94e7b2dc42 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -52,11 +52,6 @@ enum class DumpRomFSTarget { SDMC, }; -enum class GameListShortcutTarget { - Desktop, - Applications, -}; - class GameList : public QWidget { Q_OBJECT @@ -113,7 +108,7 @@ signals: void VerifyIntegrityRequested(const std::string& game_path); void CopyTIDRequested(u64 program_id); void CreateShortcut(u64 program_id, const std::string& game_path, - GameListShortcutTarget target); + const QtCommon::Game::ShortcutTarget target); void NavigateToGamedbEntryRequested(u64 program_id, const CompatibilityList& compatibility_list); void OpenPerGameGeneralRequested(const std::string& file); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 728ed2068c..0d7a08d63d 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1,21 +1,22 @@ // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +#include "core/hle/service/am/applet_manager.h" +#include "core/loader/nca.h" +#include "core/tools/renderdoc.h" +#include "frontend_common/firmware_manager.h" +#include "qt_common/qt_common.h" +#include "qt_common/qt_content_util.h" +#include "qt_common/qt_game_util.h" +#include "qt_common/qt_meta.h" +#include "qt_common/qt_path_util.h" +#include "qt_common/qt_progress_dialog.h" #include #include #include #include #include #include -#include "core/hle/service/am/applet_manager.h" -#include "core/loader/nca.h" -#include "core/tools/renderdoc.h" -#include "frontend_common/firmware_manager.h" -#include "qt_common/qt_common.h" -#include "qt_common/qt_game_util.h" -#include "qt_common/qt_path_util.h" -#include "qt_common/qt_meta.h" -#include "qt_common/qt_content_util.h" #ifdef __APPLE__ #include // for chdir @@ -398,8 +399,7 @@ inline static bool isDarkMode() { GMainWindow::GMainWindow(bool has_broken_vulkan) : ui{std::make_unique()}, - input_subsystem{std::make_shared()}, user_data_migrator{this}, - provider{std::make_unique()} { + input_subsystem{std::make_shared()}, user_data_migrator{this} { QtCommon::Init(this); Common::FS::CreateEdenPaths(); @@ -543,7 +543,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) QtCommon::system->SetContentProvider(std::make_unique()); QtCommon::system->RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual, - provider.get()); + QtCommon::provider.get()); QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs); // Remove cached contents generated during the previous session @@ -1091,7 +1091,7 @@ void GMainWindow::InitializeWidgets() { render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *QtCommon::system); render_window->hide(); - game_list = new GameList(QtCommon::vfs, provider.get(), *play_time_manager, *QtCommon::system, this); + game_list = new GameList(QtCommon::vfs, QtCommon::provider.get(), *play_time_manager, *QtCommon::system, this); ui->horizontalLayout->addWidget(game_list); game_list_placeholder = new GameListPlaceholder(this); @@ -1913,10 +1913,6 @@ bool GMainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPa return false; } - if (!OnCheckNcaVerification()) { - return false; - } - /** Exec */ const Core::SystemResultStatus result{ QtCommon::system->Load(*render_window, filename.toStdString(), params)}; @@ -2023,7 +2019,7 @@ void GMainWindow::ConfigureFilesystemProvider(const std::string& filepath) { 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, + QtCommon::provider->AddEntry(FileSys::TitleType::Application, FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), program_id, file); } else if (res2 == Loader::ResultStatus::Success && @@ -2033,7 +2029,7 @@ void GMainWindow::ConfigureFilesystemProvider(const std::string& filepath) { : 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, + QtCommon::provider->AddEntry(entry.first.first, entry.first.second, title.first, entry.second->GetBaseFile()); } } @@ -2761,18 +2757,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa // END void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) { - 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 total_size, size_t processed_size) { - progress.setValue(static_cast((processed_size * 100) / total_size)); - return progress.wasCanceled(); - }; - - QtCommon::Content::VerifyGameContents(game_path, QtProgressCallback); + QtCommon::Content::VerifyGameContents(game_path); } void GMainWindow::OnGameListCopyTID(u64 program_id) { @@ -2793,44 +2778,12 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, QUrl(QStringLiteral("https://eden-emulator.github.io/game/") + directory)); } -// Messages in pre-defined message boxes for less code spaghetti -// TODO(crueter): Still need to decide what to do re: message boxes w/ qml -bool GMainWindow::CreateShortcutMessagesGUI(QWidget* parent, int imsg, const QString& game_title) { - int result = 0; - QMessageBox::StandardButtons buttons; - switch (imsg) { - case GMainWindow::CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES: - buttons = QMessageBox::Yes | QMessageBox::No; - result = - QMessageBox::information(parent, tr("Create Shortcut"), - tr("Do you want to launch the game in fullscreen?"), buttons); - return result == QMessageBox::Yes; - case GMainWindow::CREATE_SHORTCUT_MSGBOX_SUCCESS: - QMessageBox::information(parent, tr("Create Shortcut"), - tr("Successfully created a shortcut to %1").arg(game_title)); - return false; - case GMainWindow::CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING: - buttons = QMessageBox::StandardButton::Ok | QMessageBox::StandardButton::Cancel; - result = - QMessageBox::warning(this, tr("Create Shortcut"), - tr("This will create a shortcut to the current AppImage. This may " - "not work well if you update. Continue?"), - buttons); - return result == QMessageBox::Ok; - default: - buttons = QMessageBox::Ok; - QMessageBox::critical(parent, tr("Create Shortcut"), - tr("Failed to create a shortcut to %1").arg(game_title), buttons); - return false; - } -} - void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path, - GameListShortcutTarget target) { + const QtCommon::Game::ShortcutTarget target) { // Create shortcut std::string arguments = fmt::format("-g \"{:s}\"", game_path); - CreateShortcut(game_path, program_id, "", target, arguments, true); + QtCommon::Game::CreateShortcut(game_path, program_id, "", target, arguments, true); } void GMainWindow::OnGameListOpenDirectory(const QString& directory) { @@ -3884,74 +3837,11 @@ void GMainWindow::OnOpenLogFolder() } void GMainWindow::OnVerifyInstalledContents() { - // 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 progress callback. - auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { - progress.setValue(static_cast((processed_size * 100) / total_size)); - return progress.wasCanceled(); - }; - - const std::vector result = - ContentManager::VerifyInstalledContents(*QtCommon::system, *provider, QtProgressCallback); - progress.close(); - - if (result.empty()) { - QMessageBox::information(this, tr("Integrity verification succeeded!"), - tr("The operation completed successfully.")); - } else { - const auto failed_names = - QString::fromStdString(fmt::format("{}", fmt::join(result, "\n"))); - QMessageBox::critical( - this, tr("Integrity verification failed!"), - tr("Verification failed for the following files:\n\n%1").arg(failed_names)); - } + QtCommon::Content::VerifyInstalledContents(); } void GMainWindow::InstallFirmware(const QString& location, bool recursive) { - QProgressDialog progress(tr("Installing Firmware..."), tr("Cancel"), 0, 100, this); - progress.setWindowModality(Qt::WindowModal); - progress.setMinimumDuration(100); - progress.setAutoClose(false); - progress.setAutoReset(false); - progress.show(); - - // Declare progress callback. - auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { - progress.setValue(static_cast((processed_size * 100) / total_size)); - return progress.wasCanceled(); - }; - - auto result = QtCommon::Content::InstallFirmware(location, recursive, QtProgressCallback, QtCommon::vfs.get()); - - progress.close(); - - if (result != QtCommon::Content::FirmwareInstallResult::Success) return; - - auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) { - progress.setValue(90 + static_cast((processed_size * 10) / total_size)); - return progress.wasCanceled(); - }; - - auto results = - ContentManager::VerifyInstalledContents(*QtCommon::system, *provider, VerifyFirmwareCallback, true); - - if (results.size() > 0) { - const auto failed_names = - QString::fromStdString(fmt::format("{}", fmt::join(results, "\n"))); - progress.close(); - QMessageBox::critical( - this, tr("Firmware integrity verification failed!"), - tr("Verification failed for the following files:\n\n%1").arg(failed_names)); - return; - } - - progress.close(); + QtCommon::Content::InstallFirmware(location, recursive); OnCheckFirmwareDecryption(); } @@ -4190,8 +4080,6 @@ void GMainWindow::OnHomeMenu() { break; } - // TODO(crueter): So much of this crap is common to qt that I should just move it all tbh - constexpr u64 QLaunchId = static_cast(Service::AM::AppletProgramId::QLaunch); auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); @@ -4233,164 +4121,11 @@ void GMainWindow::OnInitialSetup() { } void GMainWindow::OnCreateHomeMenuDesktopShortcut() { - OnCreateHomeMenuShortcut(GameListShortcutTarget::Desktop); + QtCommon::Game::CreateHomeMenuShortcut(QtCommon::Game::ShortcutTarget::Desktop); } void GMainWindow::OnCreateHomeMenuApplicationMenuShortcut() { - OnCreateHomeMenuShortcut(GameListShortcutTarget::Applications); -} - -std::filesystem::path GMainWindow::GetEdenCommand() { - std::filesystem::path command; - - QString appimage = QString::fromLocal8Bit(getenv("APPIMAGE")); - if (!appimage.isEmpty()) { - command = std::filesystem::path{appimage.toStdString()}; - } else { - const QStringList args = QApplication::arguments(); - command = args[0].toStdString(); - } - - // If relative path, make it an absolute path - if (command.c_str()[0] == '.') { - command = Common::FS::GetCurrentDir() / command; - } - - return command; -} - -std::filesystem::path GMainWindow::GetShortcutPath(GameListShortcutTarget target) { - std::filesystem::path shortcut_path{}; - if (target == GameListShortcutTarget::Desktop) { - shortcut_path = - QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toStdString(); - } else if (target == GameListShortcutTarget::Applications) { - shortcut_path = - QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation).toStdString(); - } - - return shortcut_path; -} - -// TODO(crueter): Migrate -void GMainWindow::CreateShortcut(const std::string &game_path, const u64 program_id, const std::string& game_title_, GameListShortcutTarget target, std::string arguments_, const bool needs_title) { - // Get path to Eden executable - std::filesystem::path command = GetEdenCommand(); - - // Shortcut path - std::filesystem::path shortcut_path = GetShortcutPath(target); - - if (!std::filesystem::exists(shortcut_path)) { - GMainWindow::CreateShortcutMessagesGUI( - this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR, - QString::fromStdString(shortcut_path.generic_string())); - LOG_ERROR(Frontend, "Invalid shortcut target {}", shortcut_path.generic_string()); - return; - } - - const FileSys::PatchManager pm{program_id, QtCommon::system->GetFileSystemController(), - QtCommon::system->GetContentProvider()}; - const auto control = pm.GetControlMetadata(); - const auto loader = - Loader::GetLoader(*QtCommon::system, QtCommon::vfs->OpenFile(game_path, FileSys::OpenMode::Read)); - - std::string game_title{game_title_}; - - // Delete illegal characters from title - if (needs_title) { - game_title = fmt::format("{:016X}", program_id); - if (control.first != nullptr) { - game_title = control.first->GetApplicationName(); - } else { - loader->ReadTitle(game_title); - } - } - - const std::string illegal_chars = "<>:\"/\\|?*."; - for (auto it = game_title.rbegin(); it != game_title.rend(); ++it) { - if (illegal_chars.find(*it) != std::string::npos) { - game_title.erase(it.base() - 1); - } - } - - const QString qgame_title = QString::fromStdString(game_title); - - // Get icon from game file - std::vector icon_image_file{}; - if (control.second != nullptr) { - icon_image_file = control.second->ReadAllBytes(); - } else if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) { - LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); - } - - QImage icon_data = - QImage::fromData(icon_image_file.data(), static_cast(icon_image_file.size())); - std::filesystem::path out_icon_path; - if (QtCommon::Game::MakeShortcutIcoPath(program_id, game_title, out_icon_path)) { - if (!SaveIconToFile(out_icon_path, icon_data)) { - LOG_ERROR(Frontend, "Could not write icon to file"); - } - } else { - QMessageBox::critical( - this, tr("Create Icon"), - tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.") - .arg(QString::fromStdString(out_icon_path.string())), - QMessageBox::StandardButton::Ok); - } - -#if defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) - // Special case for AppImages - // Warn once if we are making a shortcut to a volatile AppImage - if (command.string().ends_with(".AppImage") && !UISettings::values.shortcut_already_warned) { - if (!GMainWindow::CreateShortcutMessagesGUI( - this, GMainWindow::CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING, qgame_title)) { - return; - } - UISettings::values.shortcut_already_warned = true; - } -#endif - - // Create shortcut - std::string arguments{arguments_}; - if (GMainWindow::CreateShortcutMessagesGUI( - this, GMainWindow::CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES, qgame_title)) { - arguments = "-f " + arguments; - } - const std::string comment = fmt::format("Start {:s} with the Eden Emulator", game_title); - const std::string categories = "Game;Emulator;Qt;"; - const std::string keywords = "Switch;Nintendo;"; - - if (QtCommon::Game::CreateShortcutLink(shortcut_path, comment, out_icon_path, command, - arguments, categories, keywords, game_title)) { - GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_SUCCESS, - qgame_title); - return; - } - GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR, - qgame_title); -} - -void GMainWindow::OnCreateHomeMenuShortcut(GameListShortcutTarget target) { - constexpr u64 QLaunchId = static_cast(Service::AM::AppletProgramId::QLaunch); - auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); - if (!bis_system) { - QMessageBox::warning(this, tr("No firmware available"), - tr("Please install firmware to use the home menu.")); - return; - } - - auto qlaunch_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program); - if (!qlaunch_nca) { - QMessageBox::warning(this, tr("Home Menu Applet"), - tr("Home Menu is not available. Please reinstall firmware.")); - return; - } - - auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program); - const auto game_path = qlaunch_applet_nca->GetFullPath(); - - // TODO(crueter): Make this use the Eden icon - CreateShortcut(game_path, QLaunchId, "Switch Home Menu", target, "-qlaunch", false); + QtCommon::Game::CreateHomeMenuShortcut(QtCommon::Game::ShortcutTarget::Applications); } void GMainWindow::OnCaptureScreenshot() { @@ -4718,7 +4453,6 @@ void GMainWindow::OnMouseActivity() { } void GMainWindow::OnCheckFirmwareDecryption() { - QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs); if (!ContentManager::AreKeysPresent()) { QMessageBox::warning(this, tr("Derivation Components Missing"), tr("Encryption keys are missing.")); @@ -4727,41 +4461,6 @@ void GMainWindow::OnCheckFirmwareDecryption() { UpdateMenuState(); } -bool GMainWindow::OnCheckNcaVerification() { - if (!Settings::values.disable_nca_verification.GetValue()) - return true; - - const bool currently_hidden = Settings::values.hide_nca_verification_popup.GetValue(); - LOG_INFO(Frontend, "NCA Verification is disabled. Popup State={}", currently_hidden); - if (currently_hidden) - return true; - - QMessageBox msgbox(this); - msgbox.setWindowTitle(tr("NCA Verification Disabled")); - msgbox.setText(tr("NCA Verification is disabled.\n" - "This is required to run new games and updates.\n" - "Running without verification can cause instability or crashes if NCA files " - "are corrupt, modified, or tampered.\n" - "If unsure, re-enable verification in Eden's Settings and use firmware " - "version 19.0.1 or below.")); - msgbox.setIcon(QMessageBox::Warning); - msgbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); - msgbox.setDefaultButton(QMessageBox::Ok); - - QCheckBox* cb = new QCheckBox(tr("Don't show again"), &msgbox); - cb->setChecked(currently_hidden); - msgbox.setCheckBox(cb); - - int result = msgbox.exec(); - - const bool hide = cb->isChecked(); - if (hide != currently_hidden) { - Settings::values.hide_nca_verification_popup.SetValue(hide); - } - - return result == static_cast(QMessageBox::Ok); -} - bool GMainWindow::CheckFirmwarePresence() { return FirmwareManager::CheckFirmwarePresence(*QtCommon::system.get()); } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 0bc5913acc..e3922759b0 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -54,7 +54,6 @@ class QSlider; class QHBoxLayout; class WaitTreeWidget; enum class GameListOpenTarget; -enum class GameListShortcutTarget; enum class DumpRomFSTarget; class GameListPlaceholder; @@ -161,13 +160,6 @@ class GMainWindow : public QMainWindow { /// Max number of recently loaded items to keep track of static const int max_recent_files_item = 10; - enum { - CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES, - CREATE_SHORTCUT_MSGBOX_SUCCESS, - CREATE_SHORTCUT_MSGBOX_ERROR, - CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING, - }; - public: void filterBarSetChecked(bool state); void UpdateUITheme(); @@ -177,7 +169,7 @@ public: bool DropAction(QDropEvent* event); void AcceptDropEvent(QDropEvent* event); - std::filesystem::path GetShortcutPath(GameListShortcutTarget target); + std::filesystem::path GetShortcutPath(QtCommon::Game::ShortcutTarget target); signals: @@ -358,8 +350,9 @@ private slots: void OnGameListCopyTID(u64 program_id); void OnGameListNavigateToGamedbEntry(u64 program_id, const CompatibilityList& compatibility_list); - void OnGameListCreateShortcut(u64 program_id, const std::string& game_path, - GameListShortcutTarget target); + void OnGameListCreateShortcut(u64 program_id, + const std::string& game_path, + const QtCommon::Game::ShortcutTarget target); void OnGameListOpenDirectory(const QString& directory); void OnGameListAddDirectory(); void OnGameListShowList(bool show); @@ -416,7 +409,6 @@ private slots: void OnInitialSetup(); void OnCreateHomeMenuDesktopShortcut(); void OnCreateHomeMenuApplicationMenuShortcut(); - void OnCreateHomeMenuShortcut(GameListShortcutTarget target); void OnCaptureScreenshot(); void OnCheckFirmwareDecryption(); void OnLanguageChanged(const QString& locale); @@ -537,9 +529,6 @@ private: QString startup_icon_theme; - // FS - std::unique_ptr provider; - // Debugger panes WaitTreeWidget* waitTreeWidget; ControllerDialog* controller_dialog; @@ -586,7 +575,7 @@ private: void CreateShortcut(const std::string& game_path, const u64 program_id, const std::string& game_title, - GameListShortcutTarget target, + QtCommon::Game::ShortcutTarget target, std::string arguments, const bool needs_title);