diff --git a/rpcs3/Crypto/unpkg.cpp b/rpcs3/Crypto/unpkg.cpp index 1e3e7dbb78..35e8edc4f6 100644 --- a/rpcs3/Crypto/unpkg.cpp +++ b/rpcs3/Crypto/unpkg.cpp @@ -4,27 +4,19 @@ #include "sha1.h" #include "key_vault.h" #include "Utilities/StrFmt.h" +#include "Emu/System.h" #include "Emu/VFS.h" #include "unpkg.h" -bool pkg_install(const fs::file& pkg_f, const std::string& dir, atomic_t& sync, const std::string& pkg_filepath) +bool pkg_install(const std::string& path, atomic_t& sync) { const std::size_t BUF_SIZE = 8192 * 1024; // 8 MB + std::vector filelist; - u32 cur_file=0; - - fs::file original_file(pkg_filepath); - original_file.seek(pkg_f.pos()); - - filelist.push_back(std::move(original_file)); - - // Save current file offset (probably zero) - const u64 start_offset = pkg_f.pos(); - u64 cur_offset = start_offset; - u64 cur_file_offset = start_offset; - - // Get basic PKG information - PKGHeader header; + filelist.emplace_back(fs::file{path}); + u32 cur_file = 0; + u64 cur_offset = 0; + u64 cur_file_offset = 0; auto archive_seek = [&](const s64 new_offset, const fs::seek_mode damode = fs::seek_set) { @@ -73,6 +65,8 @@ bool pkg_install(const fs::file& pkg_f, const std::string& dir, atomic_t return num_read; }; + // Get basic PKG information + PKGHeader header; if (archive_read(&header, sizeof(header)) != sizeof(header)) { @@ -108,18 +102,18 @@ bool pkg_install(const fs::file& pkg_f, const std::string& dir, atomic_t } } - if (header.pkg_size > pkg_f.size()) + if (header.pkg_size > filelist[0].size()) { - //Check if multi-files pkg - if (pkg_filepath.length() < 7 || pkg_filepath.substr(pkg_filepath.length() - 7).compare("_00.pkg") != 0) + // Check if multi-files pkg + if (path.size() < 7 || path.compare(path.size() - 7, 7, "_00.pkg", 7) != 0) { LOG_ERROR(LOADER, "PKG file size mismatch (pkg_size=0x%llx)", header.pkg_size); return false; } - std::string name_wo_number = pkg_filepath.substr(0, pkg_filepath.length() - 7); - u64 cursize = pkg_f.size(); - while (cursize != header.pkg_size) + std::string name_wo_number = path.substr(0, path.size() - 7); + u64 cursize = filelist[0].size(); + while (cursize < header.pkg_size) { std::string archive_filename = fmt::format("%s_%02d.pkg", name_wo_number, filelist.size()); @@ -131,7 +125,7 @@ bool pkg_install(const fs::file& pkg_f, const std::string& dir, atomic_t } cursize += archive_file.size(); - filelist.push_back(std::move(archive_file)); + filelist.emplace_back(std::move(archive_file)); } } @@ -143,6 +137,12 @@ bool pkg_install(const fs::file& pkg_f, const std::string& dir, atomic_t be_t drm_type{0}; be_t content_type{0}; + std::string install_id; + + // Read title ID and use it as an installation directory + install_id.resize(9); + archive_seek(55); + archive_read(&install_id.front(), install_id.size()); archive_seek(header.pkg_info_off); @@ -179,18 +179,50 @@ bool pkg_install(const fs::file& pkg_f, const std::string& dir, atomic_t break; } + case 0xA: + { + if (packet.size > 8) + { + // Read an actual installation directory (DLC) + install_id.resize(packet.size); + archive_read(&install_id.front(), packet.size); + install_id = install_id.c_str() + 8; + continue; + } + + break; + } } archive_seek(packet.size, fs::seek_cur); } + // If false, an existing directory is being overwritten: cannot cancel the operation + bool was_null = true; + + // Get full path and create the directory + const std::string dir = Emu.GetHddDir() + "game/" + install_id + '/'; + + if (!fs::create_dir(dir)) + { + if (fs::g_tls_error == fs::error::exist) + { + was_null = false; + } + else + { + LOG_ERROR(LOADER, "PKG: Could not create the installation directory %s", dir); + return false; + } + } + // Allocate buffer with BUF_SIZE size or more if required const std::unique_ptr buf(new u128[std::max(BUF_SIZE, sizeof(PKGEntry) * header.file_count) / sizeof(u128)]); // Define decryption subfunction (`psp` arg selects the key for specific block) auto decrypt = [&](u64 offset, u64 size, const uchar* key) -> u64 { - archive_seek(start_offset + header.data_offset + offset); + archive_seek(header.data_offset + offset); // Read the data and set available size const u64 read = archive_read(buf.get(), size); @@ -334,8 +366,16 @@ bool pkg_install(const fs::file& pkg_f, const std::string& dir, atomic_t if (sync.fetch_add((block_size + 0.0) / header.data_size) < 0.) { - LOG_ERROR(LOADER, "Package installation cancelled: %s", dir); - return false; + if (was_null) + { + LOG_ERROR(LOADER, "Package installation cancelled: %s", dir); + out.close(); + fs::remove_all(dir, true); + return false; + } + + // Cannot cancel the installation + sync += 1.; } } diff --git a/rpcs3/Crypto/unpkg.h b/rpcs3/Crypto/unpkg.h index 69453bd858..1b4b67aefb 100644 --- a/rpcs3/Crypto/unpkg.h +++ b/rpcs3/Crypto/unpkg.h @@ -57,4 +57,4 @@ struct PKGEntry be_t pad; // Padding (zeros) }; -bool pkg_install(const class fs::file& pkg_f, const std::string& dir, atomic_t&, const std::string& pkg_filepath); +bool pkg_install(const std::string& path, atomic_t&); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 848f226425..87a9319c3b 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -342,62 +342,17 @@ void main_window::InstallPkg(const QString& dropPath) } } - if (filePath == NULL) + if (filePath.isEmpty()) { return; } + Emu.Stop(); guiSettings->SetValue(gui::fd_install_pkg, QFileInfo(filePath).path()); const std::string fileName = sstr(QFileInfo(filePath).fileName()); const std::string path = sstr(filePath); - // Open PKG file - fs::file pkg_f(path); - - if (!pkg_f || pkg_f.size() < 64) - { - LOG_ERROR(LOADER, "PKG: Failed to open %s", path); - return; - } - - //Check header - u32 pkg_signature; - pkg_f.seek(0); - pkg_f.read(pkg_signature); - if (pkg_signature != "\x7FPKG"_u32) - { - LOG_ERROR(LOADER, "PKG: %s is not a pkg file", fileName); - return; - } - - // Get title ID - std::vector title_id(9); - pkg_f.seek(55); - pkg_f.read(title_id); - pkg_f.seek(0); - - // Get full path - const auto& local_path = Emu.GetHddDir() + "game/" + std::string(std::begin(title_id), std::end(title_id)); - - if (!fs::create_dir(local_path)) - { - if (fs::is_dir(local_path)) - { - if (QMessageBox::question(this, tr("PKG Decrypter / Installer"), tr("Another installation found. Do you want to overwrite it?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) - { - LOG_ERROR(LOADER, "PKG: Cancelled installation to existing directory %s", local_path); - return; - } - } - else - { - LOG_ERROR(LOADER, "PKG: Could not create the installation directory %s", local_path); - return; - } - } - QProgressDialog pdlg(tr("Installing package ... please wait ..."), tr("Cancel"), 0, 1000, this); pdlg.setWindowTitle(tr("RPCS3 Package Installer")); pdlg.setWindowModality(Qt::WindowModal); @@ -405,12 +360,11 @@ void main_window::InstallPkg(const QString& dropPath) pdlg.show(); #ifdef _WIN32 - QWinTaskbarButton *taskbar_button = new QWinTaskbarButton(); + std::unique_ptr taskbar_button = std::make_unique(); taskbar_button->setWindow(windowHandle()); - QWinTaskbarProgress *taskbar_progress = taskbar_button->progress(); + QWinTaskbarProgress* taskbar_progress = taskbar_button->progress(); taskbar_progress->setRange(0, 1000); taskbar_progress->setVisible(true); - #endif // Synchronization variable @@ -419,39 +373,29 @@ void main_window::InstallPkg(const QString& dropPath) // Run PKG unpacking asynchronously scope_thread worker("PKG Installer", [&] { - if (pkg_install(pkg_f, local_path + '/', progress, path)) + if (pkg_install(path, progress)) { progress = 1.; return; } - // TODO: Ask user to delete files on cancellation/failure? progress = -1.; }); + // Wait for the completion while (std::this_thread::sleep_for(5ms), std::abs(progress) < 1.) { if (pdlg.wasCanceled()) { progress -= 1.; -#ifdef _WIN32 - taskbar_progress->hide(); - taskbar_button->~QWinTaskbarButton(); -#endif - if (QMessageBox::question(this, tr("PKG Decrypter / Installer"), tr("Remove incomplete folder?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) - { - fs::remove_all(local_path); - m_gameListFrame->Refresh(true); - LOG_SUCCESS(LOADER, "PKG: removed incomplete installation in %s", local_path); - return; - } - break; } + // Update progress window - pdlg.setValue(static_cast(progress * pdlg.maximum())); + double pval = progress; + pval < 0 ? pval += 1. : pval; + pdlg.setValue(static_cast(pval * pdlg.maximum())); #ifdef _WIN32 - taskbar_progress->setValue(static_cast(progress * taskbar_progress->maximum())); + taskbar_progress->setValue(static_cast(pval * taskbar_progress->maximum())); #endif QCoreApplication::processEvents(); } @@ -471,11 +415,6 @@ void main_window::InstallPkg(const QString& dropPath) m_gameListFrame->Refresh(true); LOG_SUCCESS(GENERAL, "Successfully installed %s.", fileName); guiSettings->ShowInfoBox(gui::ib_pkg_success, tr("Success!"), tr("Successfully installed software from package!"), this); - -#ifdef _WIN32 - taskbar_progress->hide(); - taskbar_button->~QWinTaskbarButton(); -#endif } }