From 4e07c4b5fe6ab135f87e736bc7cb9ffa37f2bc07 Mon Sep 17 00:00:00 2001 From: crueter Date: Sat, 12 Jul 2025 17:11:13 -0400 Subject: [PATCH] [desktop] feat: install firmware from ZIP Signed-off-by: crueter --- CMakeModules/CPM.cmake | 24 ++++++++ src/yuzu/CMakeLists.txt | 13 ++++ src/yuzu/main.cpp | 128 ++++++++++++++++++++++++++++++++-------- src/yuzu/main.h | 3 + src/yuzu/main.ui | 24 ++++++-- tools/update-cpm.sh | 3 + 6 files changed, 164 insertions(+), 31 deletions(-) create mode 100644 CMakeModules/CPM.cmake create mode 100755 tools/update-cpm.sh diff --git a/CMakeModules/CPM.cmake b/CMakeModules/CPM.cmake new file mode 100644 index 0000000000..84748734ce --- /dev/null +++ b/CMakeModules/CPM.cmake @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: MIT +# +# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors + +set(CPM_DOWNLOAD_VERSION 0.42.0) +set(CPM_HASH_SUM "2020b4fc42dba44817983e06342e682ecfc3d2f484a581f11cc5731fbe4dce8a") + +if(CPM_SOURCE_CACHE) + set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +elseif(DEFINED ENV{CPM_SOURCE_CACHE}) + set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +else() + set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +endif() + +# Expand relative path. This is important if the provided path contains a tilde (~) +get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) + +file(DOWNLOAD + https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake + ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} +) + +include(${CPM_DOWNLOAD_LOCATION}) diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 0dc6f562b8..86225a6e4e 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -494,4 +494,17 @@ if (YUZU_ROOM) target_link_libraries(yuzu PRIVATE yuzu-room) endif() +# Extra deps +set(BUILD_SHARED_LIBS OFF) + +include(CPM) +CPMAddPackage("gh:stachenov/quazip@1.5") + +target_compile_options(QuaZip + PRIVATE + -Wno-error=shadow +) + +target_link_libraries(yuzu PRIVATE QuaZip::QuaZip) + create_target_directory_groups(yuzu) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 081f08cf52..634c11bdce 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -12,6 +12,8 @@ #include "core/tools/renderdoc.h" #include "frontend_common/firmware_manager.h" +#include + #ifdef __APPLE__ #include // for chdir #endif @@ -1683,7 +1685,8 @@ void GMainWindow::ConnectMenuEvents() { connect_menu(ui->action_Discord, &GMainWindow::OnOpenDiscord); connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents); - connect_menu(ui->action_Install_Firmware, &GMainWindow::OnInstallFirmware); + connect_menu(ui->action_Firmware_From_Folder, &GMainWindow::OnInstallFirmware); + connect_menu(ui->action_Firmware_From_ZIP, &GMainWindow::OnInstallFirmwareFromZIP); connect_menu(ui->action_Install_Keys, &GMainWindow::OnInstallDecryptionKeys); connect_menu(ui->action_About, &GMainWindow::OnAbout); } @@ -1714,7 +1717,8 @@ void GMainWindow::UpdateMenuState() { action->setEnabled(emulation_running); } - ui->action_Install_Firmware->setEnabled(!emulation_running); + ui->action_Firmware_From_Folder->setEnabled(!emulation_running); + ui->action_Firmware_From_ZIP->setEnabled(!emulation_running); ui->action_Install_Keys->setEnabled(!emulation_running); for (QAction* action : applet_actions) { @@ -4239,26 +4243,8 @@ void GMainWindow::OnVerifyInstalledContents() { } } -void GMainWindow::OnInstallFirmware() { - // Don't do this while emulation is running, that'd probably be a bad idea. - if (emu_thread != nullptr && emu_thread->IsRunning()) { - return; - } - - // Check for installed keys, error out, suggest restart? - if (!ContentManager::AreKeysPresent()) { - QMessageBox::information( - this, tr("Keys not installed"), - tr("Install decryption keys and restart eden before attempting to install firmware.")); - return; - } - - const QString firmware_source_location = QFileDialog::getExistingDirectory( - this, tr("Select Dumped Firmware Source Location"), {}, QFileDialog::ShowDirsOnly); - if (firmware_source_location.isEmpty()) { - return; - } - +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); @@ -4272,11 +4258,11 @@ void GMainWindow::OnInstallFirmware() { return progress.wasCanceled(); }; - LOG_INFO(Frontend, "Installing firmware from {}", firmware_source_location.toStdString()); + LOG_INFO(Frontend, "Installing firmware from {}", location.toStdString()); // Check for a reasonable number of .nca files (don't hardcode them, just see if there's some in // there.) - std::filesystem::path firmware_source_path = firmware_source_location.toStdString(); + std::filesystem::path firmware_source_path = location.toStdString(); if (!Common::FS::IsDir(firmware_source_path)) { progress.close(); return; @@ -4294,7 +4280,12 @@ void GMainWindow::OnInstallFirmware() { QtProgressCallback(100, 10); - Common::FS::IterateDirEntries(firmware_source_path, callback, Common::FS::DirEntryFilter::File); + if (recursive) { + Common::FS::IterateDirEntriesRecursively(firmware_source_path, callback, Common::FS::DirEntryFilter::File); + } else { + Common::FS::IterateDirEntries(firmware_source_path, callback, Common::FS::DirEntryFilter::File); + } + if (out.size() <= 0) { progress.close(); QMessageBox::warning(this, tr("Firmware install failed"), @@ -4377,6 +4368,93 @@ void GMainWindow::OnInstallFirmware() { OnCheckFirmware(); } +void GMainWindow::OnInstallFirmware() { + // Don't do this while emulation is running, that'd probably be a bad idea. + if (emu_thread != nullptr && emu_thread->IsRunning()) { + return; + } + + // Check for installed keys, error out, suggest restart? + if (!ContentManager::AreKeysPresent()) { + QMessageBox::information( + this, tr("Keys not installed"), + tr("Install decryption keys and restart Eden before attempting to install firmware.")); + return; + } + + const QString firmware_source_location = QFileDialog::getExistingDirectory( + this, tr("Select Dumped Firmware Source Location"), {}, QFileDialog::ShowDirsOnly); + if (firmware_source_location.isEmpty()) { + return; + } + + InstallFirmware(firmware_source_location); +} + +void GMainWindow::OnInstallFirmwareFromZIP() +{ + // Don't do this while emulation is running, that'd probably be a bad idea. + if (emu_thread != nullptr && emu_thread->IsRunning()) { + return; + } + + // Check for installed keys, error out, suggest restart? + if (!ContentManager::AreKeysPresent()) { + QMessageBox::information( + this, tr("Keys not installed"), + tr("Install decryption keys and restart Eden before attempting to install firmware.")); + return; + } + + const QString firmware_zip_location = QFileDialog::getOpenFileName( + this, tr("Select Dumped Firmware ZIP"), {}, tr("Zipped Archives (*.zip)")); + if (firmware_zip_location.isEmpty()) { + return; + } + + namespace fs = std::filesystem; + fs::path tmp{std::filesystem::temp_directory_path()}; + + if (!std::filesystem::create_directories(tmp / "eden" / "firmware")) { + goto unzipFailed; + } + + { + tmp /= "eden"; + tmp /= "firmware"; + + QString qCacheDir = QString::fromStdString(tmp.string()); + + QFile zip(firmware_zip_location); + + QStringList result = JlCompress::extractDir(&zip, qCacheDir); + if (result.isEmpty()) { + goto unzipFailed; + } + + // In this case, it has to be done recursively, since sometimes people + // will pack it into a subdirectory after dumping + InstallFirmware(qCacheDir, true); + + std::error_code ec; + std::filesystem::remove_all(tmp, ec); + + if (ec) { + QMessageBox::warning(this, tr("Firmware cleanup failed"), + tr("Failed to clean up extracted firmware cache.\n" + "Check write permissions in the system temp directory and try again.\nOS reported error: %1") + .arg(ec.message())); + } + + return; + } +unzipFailed: + QMessageBox::critical(this, tr("Firmware unzip failed"), + tr("Check write permissions in the system temp directory and try again.")); + return; + +} + void GMainWindow::OnInstallDecryptionKeys() { // Don't do this while emulation is running. if (emu_thread != nullptr && emu_thread->IsRunning()) { diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 1f2582098a..7e7c00ec0b 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -394,6 +394,7 @@ private slots: void OnOpenLogFolder(); void OnVerifyInstalledContents(); void OnInstallFirmware(); + void OnInstallFirmwareFromZIP(); void OnInstallDecryptionKeys(); void OnAbout(); void OnToggleFilterBar(); @@ -614,6 +615,8 @@ private: std::string arguments, const bool needs_title); + void InstallFirmware(const QString& location, bool recursive = false); + protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index e35b7afa5a..970f8d2901 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -182,8 +182,15 @@ + + + Install Firmware + + + + - + @@ -484,11 +491,6 @@ Open &Controller Menu - - - Install Firmware - - Install Decryption Keys @@ -545,6 +547,16 @@ &Log Folder + + + + + From Folder + + + + + From ZIP diff --git a/tools/update-cpm.sh b/tools/update-cpm.sh new file mode 100755 index 0000000000..30e400209d --- /dev/null +++ b/tools/update-cpm.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +wget -O CMakeModules/CPM.cmake https://github.com/cpm-cmake/CPM.cmake/releases/latest/download/get_cpm.cmake