diff --git a/rpcs3/Crypto/unpkg.cpp b/rpcs3/Crypto/unpkg.cpp index de23f5f1ba..a22676fa64 100644 --- a/rpcs3/Crypto/unpkg.cpp +++ b/rpcs3/Crypto/unpkg.cpp @@ -9,6 +9,7 @@ #include "Emu/system_utils.hpp" #include "Emu/VFS.h" #include "unpkg.h" +#include "unself.h" #include "util/sysinfo.hpp" #include "Loader/PSF.h" @@ -47,12 +48,23 @@ package_reader::package_reader(const std::string& path, fs::file file) return; } - const bool param_sfo_found = read_param_sfo(); + m_psf = psf::load_object(read_file("PARAM.SFO"), path + ":PARAM.SFO"); - if (!param_sfo_found) + if (m_psf.empty()) { pkg_log.notice("PKG does not contain a PARAM.SFO"); } + + m_rap_file_path = get_rap_file_path_of_self(read_file("USRDIR/EBOOT.BIN")); + + if (m_rap_file_path.empty()) + { + pkg_log.notice("PKG needs a RAP file: %s", m_rap_file_path); + } + else + { + pkg_log.notice("PKG does not need a license file."); + } } package_reader::~package_reader() @@ -577,13 +589,14 @@ bool package_reader::read_entries(std::vector& entries) return true; } -bool package_reader::read_param_sfo() +fs::file package_reader::read_file(std::string_view relative_path) { std::vector entries; + fs::file tmp; if (!read_entries(entries)) { - return false; + return tmp; } std::vector data_buf; @@ -609,13 +622,13 @@ bool package_reader::read_param_sfo() std::string_view name = fmt::trim_back_sv(name_buf, "\0"sv); // We're looking for the PARAM.SFO file, if there is any - if (usz ndelim = name.find_first_not_of('/'); ndelim == umax || name.substr(ndelim) != "PARAM.SFO") + if (usz ndelim = name.find_first_not_of('/'); ndelim == umax || name.substr(ndelim) != relative_path) { continue; } // Read the package's PARAM.SFO - fs::file tmp = fs::make_stream>(); + tmp = fs::make_stream>(); { for (u64 pos = 0; pos < entry.file_size; pos += BUF_SIZE) { @@ -625,32 +638,21 @@ bool package_reader::read_param_sfo() if (decrypt(entry.file_offset + pos, block_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), data_buf.data()) != block_size) { - pkg_log.error("Failed to decrypt PARAM.SFO file"); - return false; + pkg_log.error("Failed to decrypt %s file", relative_path); + tmp.close(); + return tmp; } - if (tmp.write(data_buf.data(), block_size) != block_size) - { - pkg_log.error("Failed to write to temporary PARAM.SFO file"); - return false; - } + ensure(tmp.write(data_buf.data(), block_size) == block_size); } tmp.seek(0); - - m_psf = psf::load_object(tmp, name); - - if (m_psf.empty()) - { - // Invalid - continue; - } - - return true; + return tmp; } } - return false; + tmp.close(); + return tmp; } // TODO: maybe also check if VERSION matches diff --git a/rpcs3/Crypto/unpkg.h b/rpcs3/Crypto/unpkg.h index 0b7971c07e..2c4233b47d 100644 --- a/rpcs3/Crypto/unpkg.h +++ b/rpcs3/Crypto/unpkg.h @@ -362,10 +362,15 @@ public: return m_file; } + const std::string& gep_needed_rap_file_path() const + { + return m_rap_file_path; + } + private: bool read_header(); bool read_metadata(); - bool read_param_sfo(); + fs::file read_file(std::string_view relative_path); bool set_decryption_key(); bool read_entries(std::vector& entries); void archive_seek(s64 new_offset, const fs::seek_mode damode = fs::seek_set); @@ -401,4 +406,5 @@ private: // Expose bootable file installed (if installed such) std::string m_bootable_file_path; + std::string m_rap_file_path; }; diff --git a/rpcs3/Crypto/unself.cpp b/rpcs3/Crypto/unself.cpp index 3c187400cb..b5df4d1c70 100644 --- a/rpcs3/Crypto/unself.cpp +++ b/rpcs3/Crypto/unself.cpp @@ -1068,6 +1068,37 @@ bool SELFDecrypter::DecryptNPDRM(u8 *metadata, u32 metadata_size) return true; } +std::string SELFDecrypter::GetRapFilePath() +{ + // Check if we have a valid NPDRM control info structure. + // If not, the data has no NPDRM layer. + const NPD_HEADER* npd = GetNPDHeader(); + if (!npd) + { + self_log.trace("No NPDRM control info found!"); + return {}; + } + + if (npd->license == 1) // Network license. + { + return rpcs3::utils::get_rap_file_path(npd->content_id); + } + else if (npd->license == 2) // Local license. + { + return rpcs3::utils::get_rap_file_path(npd->content_id); + } + else if (npd->license == 3) // Free license. + { + // + } + else + { + self_log.error("Invalid NPDRM license type!"); + } + + return {}; +} + const NPD_HEADER* SELFDecrypter::GetNPDHeader() const { // Parse the control info structures to find the NPDRM control info. @@ -1435,6 +1466,46 @@ fs::file decrypt_self(const fs::file& elf_or_self, const u8* klic_key, SelfAddit return {}; } +std::string get_rap_file_path_of_self(const fs::file& elf_or_self) +{ + if (!elf_or_self) + { + return {}; + } + + elf_or_self.seek(0); + + // Check SELF header first. Check for a debug SELF. + u32 file_type = umax; + elf_or_self.read_at(0, &file_type, sizeof(file_type)); + + if (file_type == "SCE\0"_u32) + { + if (fs::file res = CheckDebugSelf(elf_or_self)) + { + return {}; + } + + // Check the ELF file class (32 or 64 bit). + const bool isElf32 = IsSelfElf32(elf_or_self); + + // Start the decrypter on this SELF file. + SELFDecrypter self_dec(elf_or_self); + + // Load the SELF file headers. + if (!self_dec.LoadHeaders(isElf32, nullptr)) + { + self_log.error("Failed to load SELF file headers!"); + return {}; + } + + // Load and decrypt the SELF file metadata. + return self_dec.GetRapFilePath(); + } + + return {}; +} + bool verify_npdrm_self_headers(const fs::file& self, u8* klic_key, NPD_HEADER* npd_out) { if (!self) diff --git a/rpcs3/Crypto/unself.h b/rpcs3/Crypto/unself.h index 1279bc961d..0869fb4eca 100644 --- a/rpcs3/Crypto/unself.h +++ b/rpcs3/Crypto/unself.h @@ -481,6 +481,7 @@ public: bool DecryptNPDRM(u8 *metadata, u32 metadata_size); const NPD_HEADER* GetNPDHeader() const; static bool GetKeyFromRap(const char *content_id, u8 *npdrm_key); + std::string GetRapFilePath(); private: template @@ -562,5 +563,6 @@ private: fs::file decrypt_self(const fs::file& elf_or_self, const u8* klic_key = nullptr, SelfAdditionalInfo* additional_info = nullptr); bool verify_npdrm_self_headers(const fs::file& self, u8* klic_key = nullptr, NPD_HEADER* npd_out = nullptr); bool get_npdrm_self_header(const fs::file& self, NPD_HEADER& npd); +std::string get_rap_file_path_of_self(const fs::file& elf_or_self); u128 get_default_self_klic(); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 80d71a44c2..b5e3591f7d 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -909,7 +909,7 @@ bool main_window::InstallPackages(QStringList file_paths, bool from_boot) // Install rap files if available int installed_rap_and_edat_count = 0; - const auto install_filetype = [&installed_rap_and_edat_count, &file_paths](const std::string extension) + const auto install_filetype = [this, &installed_rap_and_edat_count, &file_paths](const std::string extension) { const QString pattern = QString(".*\\.%1").arg(QString::fromStdString(extension)); for (const QString& file : file_paths.filter(QRegularExpression(pattern, QRegularExpression::PatternOption::CaseInsensitiveOption))) @@ -919,6 +919,13 @@ bool main_window::InstallPackages(QStringList file_paths, bool from_boot) if (InstallFileInExData(extension, file, filename)) { + if (filename == last_rap_file_needed_for_pkg) + { + file_paths.append(last_pkg_path_postponed_due_to_missing_rap); + last_rap_file_needed_for_pkg = {}; + last_pkg_path_postponed_due_to_missing_rap = {}; + } + gui_log.success("Successfully copied %s file: %s", extension, filename); installed_rap_and_edat_count++; } @@ -1022,10 +1029,6 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo } 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(); - package_install_result result = {}; auto get_app_info = [](compat::package_info& package) @@ -1064,6 +1067,53 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo readers.emplace_back(info.path.toStdString()); } + std::string missing_licenses; + std::string missing_licenses_short; + + for (const auto& reader : readers) + { + if (std::string filepath = reader.gep_needed_rap_file_path(); !filepath.empty() && fs::is_file(filepath)) + { + missing_licenses += filepath; + missing_licenses += "\n"; + + if (std::count(missing_licenses_short.begin(), missing_licenses_short.end(), '\n') < 5) + { + missing_licenses_short += std::string_view(filepath).substr(filepath.find_last_of(fs::delim) + 1); + missing_licenses_short += "\n"; + } + } + } + + if (!missing_licenses.empty()) + { + gui_log.fatal("Failed to locate the game license file(s):\n%s" + "\nEnsure the .rap license file is placed in the dev_hdd0/home/%s/exdata folder with a lowercase file extension." + "\nIf you need assistance on dumping the license file from your PS3, read our quickstart guide: https://rpcs3.net/quickstart", missing_licenses, Emu.GetUsr()); + + QString error = tr("Failed to locate the game license file(s):\n\n%1\nEnsure the .rap license file(s) are placed in the dev_hdd0 folder with a lowercase file extension.").arg(QString::fromStdString(missing_licenses_short)); + + QMessageBox* mb = new QMessageBox(QMessageBox::Warning, tr("PKG Installer: Missing License(s) For PKG(s)"), error, QMessageBox::Ok, this, Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint | Qt::WindowStaysOnTopHint); + mb->setAttribute(Qt::WA_DeleteOnClose); + mb->open(); + + if (readers.size() == 1) + { + last_pkg_path_postponed_due_to_missing_rap = packages[0].path; + missing_licenses_short.pop_back(); // Remove newline + last_rap_file_needed_for_pkg = QString::fromStdString(missing_licenses_short); + } + + return false; + } + + last_pkg_path_postponed_due_to_missing_rap = {}; + last_rap_file_needed_for_pkg = {}; + + progress_dialog pdlg(tr("RPCS3 Package Installer"), tr("Installing package, please wait..."), tr("Cancel"), 0, 1000, false, this); + pdlg.setAutoClose(false); + pdlg.show(); + std::deque bootable_paths; // Run PKG unpacking asynchronously diff --git a/rpcs3/rpcs3qt/main_window.h b/rpcs3/rpcs3qt/main_window.h index 960a70c722..cc9eb24399 100644 --- a/rpcs3/rpcs3qt/main_window.h +++ b/rpcs3/rpcs3qt/main_window.h @@ -51,6 +51,9 @@ class main_window : public QMainWindow int m_other_slider_pos = 0; std::function m_notify_batch_game_action_cb; + QString last_pkg_path_postponed_due_to_missing_rap; + QString last_rap_file_needed_for_pkg; + QIcon m_app_icon; QIcon m_icon_play; QIcon m_icon_pause;