diff --git a/rpcs3/Crypto/unpkg.cpp b/rpcs3/Crypto/unpkg.cpp index 5ebac9e4f7..40a7112d3f 100644 --- a/rpcs3/Crypto/unpkg.cpp +++ b/rpcs3/Crypto/unpkg.cpp @@ -619,7 +619,7 @@ package_error package_reader::check_target_app_version() const if (!target_app_ver.empty()) { // We are unable to compare anything with the target app version - pkg_log.error("A target app version is required (%s), but no PARAM.SFO was found for %s", target_app_ver, title_id); + pkg_log.error("A target app version is required (%s), but no PARAM.SFO was found for %s. (path='%s', error=%s)", target_app_ver, title_id, sfo_path, fs::g_tls_error); return package_error::app_version; } @@ -840,20 +840,28 @@ bool package_reader::fill_data(std::map& all_instal fs::file DecryptEDAT(const fs::file& input, const std::string& input_file_name, int mode, u8 *custom_klic, bool verbose = false); -usz package_reader::extract_worker(thread_key thread_data_key) +void package_reader::extract_worker(thread_key thread_data_key) { - usz num_failures = 0; - - while (true) + while (m_num_failures == 0 && !m_aborted) { - const usz maybe_index = m_entry_indexer++; + // Make sure m_entry_indexer does not exceed m_install_entries + const usz index = m_entry_indexer.fetch_op([this](usz& v) + { + if (v < m_install_entries.size()) + { + v++; + return true; + } - if (maybe_index >= m_install_entries.size()) + return false; + }).first; + + if (index >= m_install_entries.size()) { break; } - const install_entry& entry = ::at32(m_install_entries, maybe_index); + const install_entry& entry = ::at32(m_install_entries, index); if (!entry.is_dominating()) { @@ -934,7 +942,7 @@ usz package_reader::extract_worker(thread_key thread_data_key) out = DecryptEDAT(out, name, 1, reinterpret_cast(&m_header.klicensee), true); if (!out || !fs::write_file(path, fs::rewrite, static_cast>*>(out.release().get())->obj)) { - num_failures++; + m_num_failures++; pkg_log.error("Failed to create file %s", path); break; } @@ -959,12 +967,12 @@ usz package_reader::extract_worker(thread_key thread_data_key) } else { - num_failures++; + m_num_failures++; } } else { - num_failures++; + m_num_failures++; pkg_log.error("Failed to create file %s", path); } @@ -972,21 +980,19 @@ usz package_reader::extract_worker(thread_key thread_data_key) } default: { - num_failures++; + m_num_failures++; pkg_log.error("Unknown PKG entry type (0x%x) %s", entry.type, name); break; } } } - - return num_failures; } bool package_reader::extract_data(std::deque& readers, std::deque& bootable_paths) { std::map all_install_entries; - for (auto& reader : readers) + for (package_reader& reader : readers) { if (!reader.fill_data(all_install_entries)) { @@ -999,31 +1005,58 @@ bool package_reader::extract_data(std::deque& readers, std::dequ for (package_reader& reader : readers) { reader.m_bufs.resize(std::min(utils::get_thread_count(), reader.m_install_entries.size())); + reader.m_num_failures = 0; + reader.m_result = result::not_started; atomic_t thread_indexer = 0; named_thread_group workers("PKG Installer "sv, std::max(reader.m_bufs.size(), 1) - 1, [&]() { - num_failures += reader.extract_worker(thread_key{thread_indexer++}); + reader.extract_worker(thread_key{thread_indexer++}); }); - num_failures += reader.extract_worker(thread_key{thread_indexer++}); + reader.extract_worker(thread_key{thread_indexer++}); workers.join(); + num_failures += reader.m_num_failures; + reader.m_bufs.clear(); reader.m_bufs.shrink_to_fit(); - if (num_failures) + // We don't count this package as aborted if all entries were processed. + if (reader.m_num_failures || (reader.m_aborted && reader.m_entry_indexer < reader.m_install_entries.size())) { - if (reader.m_was_null) + // Clear boot path. We don't want to propagate potentially broken paths to the caller. + reader.m_bootable_file_path.clear(); + + bool cleaned = reader.m_was_null; + + if (reader.m_was_null && fs::is_dir(reader.m_install_path)) { - fs::remove_all(reader.m_install_path, true); + pkg_log.notice("Removing partial installation ('%s')", reader.m_install_path); + + if (!fs::remove_all(reader.m_install_path, true)) + { + pkg_log.notice("Failed to remove partial installation ('%s') (error=%s)", reader.m_install_path, fs::g_tls_error); + cleaned = false; + } } - pkg_log.success("Package failed to install ('%s')", reader.m_install_path); + if (reader.m_num_failures) + { + pkg_log.error("Package failed to install ('%s')", reader.m_install_path); + reader.m_result = cleaned ? result::error_cleaned : result::error; + } + else + { + pkg_log.warning("Package installation aborted ('%s')", reader.m_install_path); + reader.m_result = cleaned ? result::aborted_cleaned : result::aborted; + } break; } + reader.m_result = result::success; + if (reader.get_progress(1) != 1) { pkg_log.warning("Missing %d bytes from PKG total files size.", reader.m_header.data_size - reader.m_written_bytes); @@ -1147,14 +1180,5 @@ int package_reader::get_progress(int maximum) const void package_reader::abort_extract() { - m_entry_indexer.fetch_op([this](usz& v) - { - if (v < m_install_entries.size()) - { - v = m_install_entries.size(); - return true; - } - - return false; - }); + m_aborted = true; } diff --git a/rpcs3/Crypto/unpkg.h b/rpcs3/Crypto/unpkg.h index 14bfa2af34..4ad248d46a 100644 --- a/rpcs3/Crypto/unpkg.h +++ b/rpcs3/Crypto/unpkg.h @@ -332,10 +332,21 @@ public: package_reader(const std::string& path); ~package_reader(); + enum result + { + not_started, + success, + aborted, + aborted_cleaned, + error, + error_cleaned + }; + bool is_valid() const { return m_is_valid; } package_error check_target_app_version() const; static bool extract_data(std::deque& readers, std::deque& bootable_paths); psf::registry get_psf() const { return m_psf; } + result get_result() const { return m_result; }; int get_progress(int maximum = 100) const; @@ -351,10 +362,12 @@ private: bool fill_data(std::map& all_install_entries); std::span archive_read_block(u64 offset, void* data_ptr, u64 num_bytes); std::span decrypt(u64 offset, u64 size, const uchar* key, thread_key thread_data_key = {0}); - usz extract_worker(thread_key thread_data_key); + void extract_worker(thread_key thread_data_key); std::deque m_install_entries; std::string m_install_path; + atomic_t m_aborted = false; + atomic_t m_num_failures = 0; atomic_t m_entry_indexer = 0; atomic_t m_written_bytes = 0; bool m_was_null = false; @@ -362,6 +375,7 @@ private: static constexpr usz BUF_SIZE = 8192 * 1024; // 8 MB bool m_is_valid = false; + result m_result = result::not_started; std::string m_path{}; std::string m_install_dir{}; diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index c875a23ba1..739a3d9a6a 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -900,6 +900,13 @@ void main_window::HandlePackageInstallation(QStringList file_paths) Emu.GracefulShutdown(false); + std::vector path_vec; + for (const compat::package_info& pkg : packages) + { + path_vec.push_back(pkg.path.toStdString()); + } + gui_log.notice("About to install packages:\n%s", fmt::merge(path_vec, "\n")); + progress_dialog pdlg(tr("RPCS3 Package Installer"), tr("Installing package, please wait..."), tr("Cancel"), 0, 1000, false, this); pdlg.setAutoClose(false); pdlg.show(); @@ -934,7 +941,6 @@ void main_window::HandlePackageInstallation(QStringList file_paths) }; bool cancelled = false; - std::map bootable_paths_installed; // -> title id std::deque readers; @@ -971,7 +977,7 @@ void main_window::HandlePackageInstallation(QStringList file_paths) { cancelled = true; - for (auto& reader : readers) + for (package_reader& reader : readers) { reader.abort_extract(); } @@ -1002,15 +1008,45 @@ void main_window::HandlePackageInstallation(QStringList file_paths) pdlg.SetValue(pdlg.maximum()); std::this_thread::sleep_for(100ms); + for (usz i = 0; i < packages.size(); i++) { - m_game_list_frame->Refresh(true); - - for (const auto& package : packages) + const compat::package_info& package = ::at32(packages, i); + const package_reader& reader = ::at32(readers, i); + + switch (reader.get_result()) + { + case package_reader::result::success: { gui_log.success("Successfully installed %s (title_id=%s, title=%s, version=%s).", sstr(package.path), sstr(package.title_id), sstr(package.title), sstr(package.version)); + break; } + case package_reader::result::not_started: + case package_reader::result::aborted_cleaned: + { + gui_log.notice("Aborted installation of %s (title_id=%s, title=%s, version=%s).", sstr(package.path), sstr(package.title_id), sstr(package.title), sstr(package.version)); + break; + } + case package_reader::result::error_cleaned: + { + gui_log.error("Failed to install %s (title_id=%s, title=%s, version=%s).", sstr(package.path), sstr(package.title_id), sstr(package.title), sstr(package.version)); + break; + } + case package_reader::result::aborted: + case package_reader::result::error: + { + gui_log.error("Partially installed %s (title_id=%s, title=%s, version=%s).", sstr(package.path), sstr(package.title_id), sstr(package.title), sstr(package.version)); + break; + } + } + } - gui_log.success("Package(s) successfully installed!"); + m_game_list_frame->Refresh(true); + + pdlg.hide(); + + if (!cancelled) + { + std::map bootable_paths_installed; // -> title id for (usz index = 0; index < bootable_paths.size(); index++) { @@ -1022,78 +1058,73 @@ void main_window::HandlePackageInstallation(QStringList file_paths) bootable_paths_installed[bootable_paths[index]] = packages[index].title_id; } + if (bootable_paths_installed.empty()) { - pdlg.hide(); + m_gui_settings->ShowInfoBox(tr("Success!"), tr("Successfully installed software from package(s)!"), gui::ib_pkg_success, this); + return; + } - bool create_desktop_shortcuts = false; - bool create_app_shortcut = false; + auto dlg = new QDialog(this); + dlg->setWindowTitle(tr("Success!")); - if (bootable_paths_installed.empty()) - { - m_gui_settings->ShowInfoBox(tr("Success!"), tr("Successfully installed software from package(s)!"), gui::ib_pkg_success, this); - } - else - { - auto dlg = new QDialog(this); - dlg->setWindowTitle(tr("Success!")); + QVBoxLayout* vlayout = new QVBoxLayout(dlg); - QVBoxLayout* vlayout = new QVBoxLayout(dlg); - - QCheckBox* desk_check = new QCheckBox(tr("Add desktop shortcut(s)")); + QCheckBox* desk_check = new QCheckBox(tr("Add desktop shortcut(s)")); #ifdef _WIN32 - QCheckBox* quick_check = new QCheckBox(tr("Add Start menu shortcut(s)")); + QCheckBox* quick_check = new QCheckBox(tr("Add Start menu shortcut(s)")); #elif defined(__APPLE__) - QCheckBox* quick_check = new QCheckBox(tr("Add dock shortcut(s)")); + QCheckBox* quick_check = new QCheckBox(tr("Add dock shortcut(s)")); #else - QCheckBox* quick_check = new QCheckBox(tr("Add launcher shortcut(s)")); + QCheckBox* quick_check = new QCheckBox(tr("Add launcher shortcut(s)")); #endif - QLabel* label = new QLabel(tr("Successfully installed software from package(s)!\nWould you like to install shortcuts to the installed software? (%1 new software detected)\n\n").arg(bootable_paths_installed.size()), dlg); + QLabel* label = new QLabel(tr("Successfully installed software from package(s)!\nWould you like to install shortcuts to the installed software? (%1 new software detected)\n\n").arg(bootable_paths_installed.size()), dlg); - vlayout->addWidget(label); - vlayout->addStretch(10); - vlayout->addWidget(desk_check); - vlayout->addStretch(3); - vlayout->addWidget(quick_check); - vlayout->addStretch(3); + vlayout->addWidget(label); + vlayout->addStretch(10); + vlayout->addWidget(desk_check); + vlayout->addStretch(3); + vlayout->addWidget(quick_check); + vlayout->addStretch(3); - QDialogButtonBox* btn_box = new QDialogButtonBox(QDialogButtonBox::Ok); + QDialogButtonBox* btn_box = new QDialogButtonBox(QDialogButtonBox::Ok); - vlayout->addWidget(btn_box); - dlg->setLayout(vlayout); + vlayout->addWidget(btn_box); + dlg->setLayout(vlayout); - connect(btn_box, &QDialogButtonBox::accepted, this, [&]() - { - create_desktop_shortcuts = desk_check->isChecked(); - create_app_shortcut = quick_check->isChecked(); - dlg->accept(); - }); + bool create_desktop_shortcuts = false; + bool create_app_shortcut = false; - dlg->setAttribute(Qt::WA_DeleteOnClose); - dlg->exec(); - } + connect(btn_box, &QDialogButtonBox::accepted, this, [&]() + { + create_desktop_shortcuts = desk_check->isChecked(); + create_app_shortcut = quick_check->isChecked(); + dlg->accept(); + }); - std::set locations; + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->exec(); + + std::set locations; #ifdef _WIN32 - locations.insert(gui::utils::shortcut_location::rpcs3_shortcuts); + locations.insert(gui::utils::shortcut_location::rpcs3_shortcuts); #endif - if (create_desktop_shortcuts) - { - locations.insert(gui::utils::shortcut_location::desktop); - } - if (create_app_shortcut) - { - locations.insert(gui::utils::shortcut_location::applications); - } + if (create_desktop_shortcuts) + { + locations.insert(gui::utils::shortcut_location::desktop); + } + if (create_app_shortcut) + { + locations.insert(gui::utils::shortcut_location::applications); + } - for (const auto& [boot_path, title_id] : bootable_paths_installed) + for (const auto& [boot_path, title_id] : bootable_paths_installed) + { + for (const game_info& gameinfo : m_game_list_frame->GetGameInfo()) { - for (const game_info& gameinfo : m_game_list_frame->GetGameInfo()) + if (gameinfo && gameinfo->info.bootable && gameinfo->info.serial == sstr(title_id) && boot_path.starts_with(gameinfo->info.path)) { - if (gameinfo && gameinfo->info.bootable && gameinfo->info.serial == sstr(title_id) && boot_path.starts_with(gameinfo->info.path)) - { - m_game_list_frame->CreateShortcuts(gameinfo, locations); - break; - } + m_game_list_frame->CreateShortcuts(gameinfo, locations); + break; } } } @@ -1108,12 +1139,20 @@ void main_window::HandlePackageInstallation(QStringList file_paths) { const compat::package_info* package = nullptr; - for (usz i = 0; i < readers.size(); i++) + for (usz i = 0; i < readers.size() && !package; i++) { // Figure out what package failed the installation - if (readers[i].get_progress(1) != 1) + switch (readers[i].get_result()) { + case package_reader::result::success: + case package_reader::result::not_started: + case package_reader::result::aborted: + case package_reader::result::aborted_cleaned: + break; + case package_reader::result::error: + case package_reader::result::error_cleaned: package = &packages[i]; + break; } }