mirror of
				https://git.suyu.dev/suyu/suyu
				synced 2025-11-04 00:49:02 -06:00 
			
		
		
		
	Merge pull request #13047 from anpilley/import-firmware
Import firmware from folder of loose NCA files
This commit is contained in:
		@@ -251,11 +251,12 @@ inline InstallResult InstallNCA(FileSys::VfsFilesystem& vfs, const std::string&
 | 
			
		||||
 * \param callback Callback to report the progress of the installation. The first size_t
 | 
			
		||||
 * parameter is the total size of the installed contents and the second is the current progress. If
 | 
			
		||||
 * you return true to the callback, it will cancel the installation as soon as possible.
 | 
			
		||||
 * \param firmware_only Set to true to only scan system nand NCAs (firmware), post firmware install.
 | 
			
		||||
 * \return A list of entries that failed to install. Returns an empty vector if successful.
 | 
			
		||||
 */
 | 
			
		||||
inline std::vector<std::string> VerifyInstalledContents(
 | 
			
		||||
    Core::System& system, FileSys::ManualContentProvider& provider,
 | 
			
		||||
    const std::function<bool(size_t, size_t)>& callback) {
 | 
			
		||||
    const std::function<bool(size_t, size_t)>& callback, bool firmware_only = false) {
 | 
			
		||||
    // Get content registries.
 | 
			
		||||
    auto bis_contents = system.GetFileSystemController().GetSystemNANDContents();
 | 
			
		||||
    auto user_contents = system.GetFileSystemController().GetUserNANDContents();
 | 
			
		||||
@@ -264,7 +265,7 @@ inline std::vector<std::string> VerifyInstalledContents(
 | 
			
		||||
    if (bis_contents) {
 | 
			
		||||
        content_providers.push_back(bis_contents);
 | 
			
		||||
    }
 | 
			
		||||
    if (user_contents) {
 | 
			
		||||
    if (user_contents && !firmware_only) {
 | 
			
		||||
        content_providers.push_back(user_contents);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1603,6 +1603,7 @@ 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_Install_Firmware, &GMainWindow::OnInstallFirmware);
 | 
			
		||||
    connect_menu(ui->action_About, &GMainWindow::OnAbout);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1631,6 +1632,8 @@ void GMainWindow::UpdateMenuState() {
 | 
			
		||||
        action->setEnabled(emulation_running);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ui->action_Install_Firmware->setEnabled(!emulation_running);
 | 
			
		||||
 | 
			
		||||
    for (QAction* action : applet_actions) {
 | 
			
		||||
        action->setEnabled(is_firmware_available && !emulation_running);
 | 
			
		||||
    }
 | 
			
		||||
@@ -4150,6 +4153,146 @@ 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 yuzu before attempting to install firmware."));
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    QString firmware_source_location =
 | 
			
		||||
        QFileDialog::getExistingDirectory(this, tr("Select Dumped Firmware Source Location"),
 | 
			
		||||
                                          QString::fromStdString(""), QFileDialog::ShowDirsOnly);
 | 
			
		||||
    if (firmware_source_location.isEmpty()) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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<int>((processed_size * 100) / total_size));
 | 
			
		||||
        return progress.wasCanceled();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    LOG_INFO(Frontend, "Installing firmware from {}", firmware_source_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();
 | 
			
		||||
    if (!Common::FS::IsDir(firmware_source_path)) {
 | 
			
		||||
        progress.close();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::vector<std::filesystem::path> out;
 | 
			
		||||
    const Common::FS::DirEntryCallable callback =
 | 
			
		||||
        [&out](const std::filesystem::directory_entry& entry) {
 | 
			
		||||
            if (entry.path().has_extension() && entry.path().extension() == ".nca")
 | 
			
		||||
                out.emplace_back(entry.path());
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    QtProgressCallback(100, 10);
 | 
			
		||||
 | 
			
		||||
    Common::FS::IterateDirEntries(firmware_source_path, callback, Common::FS::DirEntryFilter::File);
 | 
			
		||||
    if (out.size() <= 0) {
 | 
			
		||||
        progress.close();
 | 
			
		||||
        QMessageBox::warning(this, tr("Firmware install failed"),
 | 
			
		||||
                             tr("Unable to locate potential firmware NCA files"));
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Locate and erase the content of nand/system/Content/registered/*.nca, if any.
 | 
			
		||||
    auto sysnand_content_vdir = system->GetFileSystemController().GetSystemNANDContentDirectory();
 | 
			
		||||
    if (!sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) {
 | 
			
		||||
        progress.close();
 | 
			
		||||
        QMessageBox::critical(this, tr("Firmware install failed"),
 | 
			
		||||
                              tr("Failed to delete one or more firmware file."));
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    LOG_INFO(Frontend,
 | 
			
		||||
             "Cleaned nand/system/Content/registered folder in preparation for new firmware.");
 | 
			
		||||
 | 
			
		||||
    QtProgressCallback(100, 20);
 | 
			
		||||
 | 
			
		||||
    auto firmware_vdir = sysnand_content_vdir->GetDirectoryRelative("registered");
 | 
			
		||||
 | 
			
		||||
    bool success = true;
 | 
			
		||||
    bool cancelled = false;
 | 
			
		||||
    int i = 0;
 | 
			
		||||
    for (const auto& firmware_src_path : out) {
 | 
			
		||||
        i++;
 | 
			
		||||
        auto firmware_src_vfile =
 | 
			
		||||
            vfs->OpenFile(firmware_src_path.generic_string(), FileSys::OpenMode::Read);
 | 
			
		||||
        auto firmware_dst_vfile =
 | 
			
		||||
            firmware_vdir->CreateFileRelative(firmware_src_path.filename().string());
 | 
			
		||||
 | 
			
		||||
        if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) {
 | 
			
		||||
            LOG_ERROR(Frontend, "Failed to copy firmware file {} to {} in registered folder!",
 | 
			
		||||
                      firmware_src_path.generic_string(), firmware_src_path.filename().string());
 | 
			
		||||
            success = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (QtProgressCallback(100, 20 + (int)(((float)(i) / (float)out.size()) * 70.0))) {
 | 
			
		||||
            success = false;
 | 
			
		||||
            cancelled = true;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!success && !cancelled) {
 | 
			
		||||
        progress.close();
 | 
			
		||||
        QMessageBox::critical(this, tr("Firmware install failed"),
 | 
			
		||||
                              tr("One or more firmware files failed to copy into NAND."));
 | 
			
		||||
        return;
 | 
			
		||||
    } else if (cancelled) {
 | 
			
		||||
        progress.close();
 | 
			
		||||
        QMessageBox::warning(this, tr("Firmware install failed"),
 | 
			
		||||
                             tr("Firmware installation cancelled, firmware may be in bad state, "
 | 
			
		||||
                                "restart yuzu or re-install firmware."));
 | 
			
		||||
        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<int>((processed_size * 10) / total_size));
 | 
			
		||||
        return progress.wasCanceled();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    auto result =
 | 
			
		||||
        ContentManager::VerifyInstalledContents(*system, *provider, VerifyFirmwareCallback, true);
 | 
			
		||||
 | 
			
		||||
    if (result.size() > 0) {
 | 
			
		||||
        const auto failed_names =
 | 
			
		||||
            QString::fromStdString(fmt::format("{}", fmt::join(result, "\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();
 | 
			
		||||
    OnCheckFirmwareDecryption();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GMainWindow::OnAbout() {
 | 
			
		||||
    AboutDialog aboutDialog(this);
 | 
			
		||||
    aboutDialog.exec();
 | 
			
		||||
 
 | 
			
		||||
@@ -380,6 +380,7 @@ private slots:
 | 
			
		||||
    void OnLoadAmiibo();
 | 
			
		||||
    void OnOpenYuzuFolder();
 | 
			
		||||
    void OnVerifyInstalledContents();
 | 
			
		||||
    void OnInstallFirmware();
 | 
			
		||||
    void OnAbout();
 | 
			
		||||
    void OnToggleFilterBar();
 | 
			
		||||
    void OnToggleStatusBar();
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,16 @@
 | 
			
		||||
  </property>
 | 
			
		||||
  <widget class="QWidget" name="centralwidget">
 | 
			
		||||
   <layout class="QHBoxLayout" name="horizontalLayout">
 | 
			
		||||
    <property name="margin" stdset="0">
 | 
			
		||||
    <property name="leftMargin">
 | 
			
		||||
     <number>0</number>
 | 
			
		||||
    </property>
 | 
			
		||||
    <property name="topMargin">
 | 
			
		||||
     <number>0</number>
 | 
			
		||||
    </property>
 | 
			
		||||
    <property name="rightMargin">
 | 
			
		||||
     <number>0</number>
 | 
			
		||||
    </property>
 | 
			
		||||
    <property name="bottomMargin">
 | 
			
		||||
     <number>0</number>
 | 
			
		||||
    </property>
 | 
			
		||||
   </layout>
 | 
			
		||||
@@ -156,8 +165,8 @@
 | 
			
		||||
     <addaction name="separator"/>
 | 
			
		||||
     <addaction name="action_Configure_Tas"/>
 | 
			
		||||
    </widget>
 | 
			
		||||
    <addaction name="action_Rederive"/>
 | 
			
		||||
    <addaction name="action_Verify_installed_contents"/>
 | 
			
		||||
    <addaction name="action_Install_Firmware"/>
 | 
			
		||||
    <addaction name="separator"/>
 | 
			
		||||
    <addaction name="menu_cabinet_applet"/>
 | 
			
		||||
    <addaction name="action_Load_Album"/>
 | 
			
		||||
@@ -455,6 +464,11 @@
 | 
			
		||||
    <string>Open &Controller Menu</string>
 | 
			
		||||
   </property>
 | 
			
		||||
  </action>
 | 
			
		||||
  <action name="action_Install_Firmware">
 | 
			
		||||
   <property name="text">
 | 
			
		||||
    <string>Install Firmware</string>
 | 
			
		||||
   </property>
 | 
			
		||||
  </action>
 | 
			
		||||
 </widget>
 | 
			
		||||
 <resources>
 | 
			
		||||
  <include location="yuzu.qrc"/>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user