From a8887a42330459b2a9149988d701dd8c2ff9118a Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 10 Jan 2023 20:58:19 +0100 Subject: [PATCH 1/4] package_reader: fix some warnings --- rpcs3/Crypto/unpkg.cpp | 59 +++++++++++++++++++++++++++++++----------- rpcs3/Crypto/unpkg.h | 23 +++------------- 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/rpcs3/Crypto/unpkg.cpp b/rpcs3/Crypto/unpkg.cpp index b4888a2829ef..5ebac9e4f786 100644 --- a/rpcs3/Crypto/unpkg.cpp +++ b/rpcs3/Crypto/unpkg.cpp @@ -515,7 +515,7 @@ bool package_reader::read_param_sfo() std::memcpy(entries.data(), m_bufs.back().get(), entries.size() * sizeof(PKGEntry)); - for (const auto& entry : entries) + for (const PKGEntry& entry : entries) { if (entry.name_size > 256) { @@ -567,11 +567,9 @@ bool package_reader::read_param_sfo() return true; } - else - { - pkg_log.error("Failed to create temporary PARAM.SFO file"); - return false; - } + + pkg_log.error("Failed to create temporary PARAM.SFO file"); + return false; } return false; @@ -780,7 +778,9 @@ bool package_reader::fill_data(std::map& all_instal (log_error ? pkg_log.error : pkg_log.notice)("Entry 0x%08x: %s (pad=0x%x)", entry.type, name, entry.pad); - switch (const u8 entry_type = entry.type & 0xff) + const u8 entry_type = entry.type & 0xff; + + switch (entry_type) { case PKG_FILE_ENTRY_FOLDER: case 0x12: @@ -807,7 +807,14 @@ bool package_reader::fill_data(std::map& all_instal const std::string true_path = std::filesystem::weakly_canonical(std::filesystem::u8path(path)).string(); auto map_ptr = &*all_install_entries.try_emplace(true_path).first; - m_install_entries.push_back({ map_ptr, name, entry.file_offset, entry.file_size, entry.type, entry.pad }); + m_install_entries.push_back({ + .weak_reference = map_ptr, + .name = name, + .file_offset = entry.file_offset, + .file_size = entry.file_size, + .type = entry.type, + .pad = entry.pad + }); if (map_ptr->second && !(entry.type & PKG_FILE_ENTRY_OVERWRITE)) { @@ -833,7 +840,7 @@ 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, std::map& all_install_entries) +usz package_reader::extract_worker(thread_key thread_data_key) { usz num_failures = 0; @@ -989,7 +996,7 @@ bool package_reader::extract_data(std::deque& readers, std::dequ usz num_failures = 0; - for (auto& reader : readers) + for (package_reader& reader : readers) { reader.m_bufs.resize(std::min(utils::get_thread_count(), reader.m_install_entries.size())); @@ -997,10 +1004,10 @@ bool package_reader::extract_data(std::deque& readers, std::dequ named_thread_group workers("PKG Installer "sv, std::max(reader.m_bufs.size(), 1) - 1, [&]() { - num_failures += reader.extract_worker(thread_key{thread_indexer++}, all_install_entries); + num_failures += reader.extract_worker(thread_key{thread_indexer++}); }); - num_failures += reader.extract_worker(thread_key{thread_indexer++}, all_install_entries); + num_failures += reader.extract_worker(thread_key{thread_indexer++}); workers.join(); reader.m_bufs.clear(); @@ -1016,7 +1023,8 @@ bool package_reader::extract_data(std::deque& readers, std::dequ pkg_log.success("Package failed to install ('%s')", reader.m_install_path); break; } - else if (reader.get_progress(1) != 1) + + 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); reader.m_written_bytes = reader.m_header.data_size; // Mark as completed anyway @@ -1032,7 +1040,7 @@ bool package_reader::extract_data(std::deque& readers, std::dequ void package_reader::archive_seek(const s64 new_offset, const fs::seek_mode damode) { if (m_file) m_file.seek(new_offset, damode); -}; +} u64 package_reader::archive_read(void* data_ptr, const u64 num_bytes) { @@ -1128,4 +1136,25 @@ std::span package_reader::decrypt(u64 offset, u64 size, const uchar* // Return the amount of data written in buf return data_span; -}; +} + +int package_reader::get_progress(int maximum) const +{ + const usz wr = m_written_bytes; + + return wr >= m_header.data_size ? maximum : ::narrow(wr * maximum / m_header.data_size); +} + +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; + }); +} diff --git a/rpcs3/Crypto/unpkg.h b/rpcs3/Crypto/unpkg.h index d9d1045e22c6..14bfa2af3431 100644 --- a/rpcs3/Crypto/unpkg.h +++ b/rpcs3/Crypto/unpkg.h @@ -337,26 +337,9 @@ class package_reader static bool extract_data(std::deque& readers, std::deque& bootable_paths); psf::registry get_psf() const { return m_psf; } - int get_progress(int maximum = 100) const - { - const usz wr = m_written_bytes; - - return wr >= m_header.data_size ? maximum : ::narrow(wr * maximum / m_header.data_size); - } + int get_progress(int maximum = 100) const; - void 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; - }); - } + void abort_extract(); private: bool read_header(); @@ -368,7 +351,7 @@ class package_reader 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, std::map& all_install_entries); + usz extract_worker(thread_key thread_data_key); std::deque m_install_entries; std::string m_install_path; From bfc85b17ff05c952ca337c919626f0c57476dd5c Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 10 Jan 2023 21:26:17 +0100 Subject: [PATCH 2/4] Qt: fix some package install cancellation issues - Abort installation if any thread has errors - Only clean the directories of packages that actually had errors - Additionally clean the directories of packages that were cancelled before they could finish - Clear boot path in case of error or cancelation - Propagate result to caller - Skip success message if the installation was canceled --- rpcs3/Crypto/unpkg.cpp | 86 +++++++++++------- rpcs3/Crypto/unpkg.h | 16 +++- rpcs3/rpcs3qt/main_window.cpp | 163 +++++++++++++++++++++------------- 3 files changed, 171 insertions(+), 94 deletions(-) diff --git a/rpcs3/Crypto/unpkg.cpp b/rpcs3/Crypto/unpkg.cpp index 5ebac9e4f786..40a7112d3f74 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; + } + + return false; + }).first; - if (maybe_index >= m_install_entries.size()) + 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 14bfa2af3431..4ad248d46a33 100644 --- a/rpcs3/Crypto/unpkg.h +++ b/rpcs3/Crypto/unpkg.h @@ -332,10 +332,21 @@ class package_reader 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 @@ class package_reader 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 @@ class package_reader 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 c875a23ba1f8..739a3d9a6ad0 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); - - QDialogButtonBox* btn_box = new QDialogButtonBox(QDialogButtonBox::Ok); + 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); - 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(); + }); + + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->exec(); - std::set locations; + 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; } } From f20e5ebc130bbeaa1b1d41c8ce8c97a758ad9878 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 11 Jan 2023 03:06:52 +0100 Subject: [PATCH 3/4] Qt: more package install fixes - Clean directories if fill_path fails - Fix check_target_app_version when installing multiple packages (compromise: no more optimized singular file installs for now) --- rpcs3/Crypto/unpkg.cpp | 81 ++++++++++++++++++++++++++--------- rpcs3/Crypto/unpkg.h | 4 +- rpcs3/Emu/system_utils.cpp | 4 +- rpcs3/rpcs3qt/main_window.cpp | 18 +++----- 4 files changed, 71 insertions(+), 36 deletions(-) diff --git a/rpcs3/Crypto/unpkg.cpp b/rpcs3/Crypto/unpkg.cpp index 40a7112d3f74..63821189e42a 100644 --- a/rpcs3/Crypto/unpkg.cpp +++ b/rpcs3/Crypto/unpkg.cpp @@ -689,20 +689,16 @@ package_error package_reader::check_target_app_version() const return package_error::app_version; } -bool package_reader::fill_data(std::map& all_install_entries) +bool package_reader::set_install_path() { if (!m_is_valid) { return false; } - m_install_entries.clear(); m_install_path.clear(); - m_bootable_file_path.clear(); - m_entry_indexer = 0; - m_written_bytes = 0; - // Get full path and create the directory + // Get full path std::string dir = rpcs3::utils::get_hdd0_dir(); // Based on https://www.psdevwiki.com/ps3/PKG_files#ContentType @@ -739,13 +735,27 @@ bool package_reader::fill_data(std::map& all_instal // If false, an existing directory is being overwritten: cannot cancel the operation m_was_null = !fs::is_dir(dir); - if (!fs::create_path(dir)) + m_install_path = dir; + return true; +} + +bool package_reader::fill_data(std::map& all_install_entries) +{ + if (!m_is_valid) { - pkg_log.error("Could not create the installation directory %s", dir); return false; } - m_install_path = dir; + if (!fs::create_path(m_install_path)) + { + pkg_log.error("Could not create the installation directory %s (error=%s)", m_install_path, fs::g_tls_error); + return false; + } + + m_install_entries.clear(); + m_bootable_file_path.clear(); + m_entry_indexer = 0; + m_written_bytes = 0; if (!decrypt_data()) { @@ -772,7 +782,7 @@ bool package_reader::fill_data(std::map& all_instal decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data()); const std::string name{reinterpret_cast(m_bufs.back().get()), entry.name_size}; - std::string path = dir + vfs::escape(name); + std::string path = m_install_path + vfs::escape(name); const bool log_error = entry.pad || (entry.type & ~PKG_FILE_ENTRY_KNOWN_BITS); @@ -831,7 +841,7 @@ bool package_reader::fill_data(std::map& all_instal if (num_failures != 0) { - pkg_log.error("Package installation failed: %s", dir); + pkg_log.error("Package installation failed: %s", m_install_path); return false; } @@ -988,29 +998,53 @@ void package_reader::extract_worker(thread_key thread_data_key) } } -bool package_reader::extract_data(std::deque& readers, std::deque& bootable_paths) +package_error package_reader::extract_data(std::deque& readers, std::deque& bootable_paths) { - std::map all_install_entries; + package_error error = package_error::no_error; + usz num_failures = 0; + // Set paths first in order to know if the install dir was empty before starting any installations. + // This will also allow us to remove all the new packages in one path at once if any of them fail. for (package_reader& reader : readers) { - if (!reader.fill_data(all_install_entries)) + reader.m_result = result::not_started; + + if (!reader.set_install_path()) { - return false; + error = package_error::other; + reader.m_result = result::error; + break; } } - usz num_failures = 0; - for (package_reader& reader : readers) { + // Use a seperate map for each reader. We need to check if the target app version exists for each package in sequence. + std::map all_install_entries; + + if (error == package_error::no_error) + { + // Check if this package is allowed to be installed on top of the existing data + error = reader.check_target_app_version(); + } + + if (error == package_error::no_error) + { + reader.m_result = result::started; + + // Parse the files to be installed and create all paths. + if (!reader.fill_data(all_install_entries)) + { + error = package_error::other; + } + } + 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; + reader.m_num_failures = error == package_error::no_error ? 0 : 1; atomic_t thread_indexer = 0; - named_thread_group workers("PKG Installer "sv, std::max(reader.m_bufs.size(), 1) - 1, [&]() + named_thread_group workers("PKG Installer "sv, std::max(::narrow(reader.m_bufs.size()), 1) - 1, [&]() { reader.extract_worker(thread_key{thread_indexer++}); }); @@ -1067,7 +1101,12 @@ bool package_reader::extract_data(std::deque& readers, std::dequ bootable_paths.emplace_back(std::move(reader.m_bootable_file_path)); } - return num_failures == 0; + if (num_failures > 0) + { + error = package_error::other; + } + + return error; } void package_reader::archive_seek(const s64 new_offset, const fs::seek_mode damode) diff --git a/rpcs3/Crypto/unpkg.h b/rpcs3/Crypto/unpkg.h index 4ad248d46a33..03a1b1300825 100644 --- a/rpcs3/Crypto/unpkg.h +++ b/rpcs3/Crypto/unpkg.h @@ -335,6 +335,7 @@ class package_reader enum result { not_started, + started, success, aborted, aborted_cleaned, @@ -344,7 +345,7 @@ class package_reader 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); + static package_error extract_data(std::deque& readers, std::deque& bootable_paths); psf::registry get_psf() const { return m_psf; } result get_result() const { return m_result; }; @@ -359,6 +360,7 @@ class package_reader bool decrypt_data(); void archive_seek(s64 new_offset, const fs::seek_mode damode = fs::seek_set); u64 archive_read(void* data_ptr, u64 num_bytes); + bool set_install_path(); 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}); diff --git a/rpcs3/Emu/system_utils.cpp b/rpcs3/Emu/system_utils.cpp index 1cd7d62aace2..6e2d35740fc8 100644 --- a/rpcs3/Emu/system_utils.cpp +++ b/rpcs3/Emu/system_utils.cpp @@ -92,8 +92,8 @@ namespace rpcs3::utils named_thread worker("PKG Installer", [&] { std::deque bootables; - - return package_reader::extract_data(reader, bootables); + const package_error error = package_reader::extract_data(reader, bootables); + return error == package_error::no_error; }); // Wait for the completion diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 739a3d9a6ad0..3ea1a3f7d770 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -944,10 +944,9 @@ void main_window::HandlePackageInstallation(QStringList file_paths) std::deque readers; - for (usz i = 0; error == package_error::no_error && i < packages.size(); i++) + for (const compat::package_info& info : packages) { - readers.emplace_back(sstr(packages[i].path)); - error = readers.back().check_target_app_version(); + readers.emplace_back(sstr(info.path)); } std::deque bootable_paths; @@ -955,15 +954,8 @@ void main_window::HandlePackageInstallation(QStringList file_paths) // Run PKG unpacking asynchronously named_thread worker("PKG Installer", [&readers, &error, &bootable_paths] { - if (error == package_error::no_error) - { - if (package_reader::extract_data(readers, bootable_paths)) - { - return true; - } - } - - return false; + error = package_reader::extract_data(readers, bootable_paths); + return error == package_error::no_error; }); pdlg.show(); @@ -1021,6 +1013,7 @@ void main_window::HandlePackageInstallation(QStringList file_paths) break; } case package_reader::result::not_started: + case package_reader::result::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)); @@ -1146,6 +1139,7 @@ void main_window::HandlePackageInstallation(QStringList file_paths) { case package_reader::result::success: case package_reader::result::not_started: + case package_reader::result::started: case package_reader::result::aborted: case package_reader::result::aborted_cleaned: break; From a291869cb56570064626ac9532993af2b86f8021 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 11 Jan 2023 06:32:21 +0100 Subject: [PATCH 4/4] Qt: Allow users to create shortcuts for existing bootable entries even after canceling the installation --- rpcs3/rpcs3qt/main_window.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 3ea1a3f7d770..7c67b3802d2d 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -1035,22 +1035,22 @@ void main_window::HandlePackageInstallation(QStringList file_paths) m_game_list_frame->Refresh(true); - pdlg.hide(); + std::map bootable_paths_installed; // -> title id - if (!cancelled) + for (usz index = 0; index < bootable_paths.size(); index++) { - std::map bootable_paths_installed; // -> title id - - for (usz index = 0; index < bootable_paths.size(); index++) + if (bootable_paths[index].empty()) { - if (bootable_paths[index].empty()) - { - continue; - } - - bootable_paths_installed[bootable_paths[index]] = packages[index].title_id; + continue; } + bootable_paths_installed[bootable_paths[index]] = packages[index].title_id; + } + + pdlg.hide(); + + if (!cancelled || !bootable_paths_installed.empty()) + { if (bootable_paths_installed.empty()) { m_gui_settings->ShowInfoBox(tr("Success!"), tr("Successfully installed software from package(s)!"), gui::ib_pkg_success, this);