Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 26 additions & 24 deletions rpcs3/Crypto/unpkg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -577,13 +589,14 @@ bool package_reader::read_entries(std::vector<PKGEntry>& entries)
return true;
}

bool package_reader::read_param_sfo()
fs::file package_reader::read_file(std::string_view relative_path)
{
std::vector<PKGEntry> entries;
fs::file tmp;

if (!read_entries(entries))
{
return false;
return tmp;
}

std::vector<u8> data_buf;
Expand All @@ -609,13 +622,13 @@ bool package_reader::read_param_sfo()
fmt::trim_back(name, "\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<std::vector<uchar>>();
tmp = fs::make_stream<std::vector<uchar>>();
{
for (u64 pos = 0; pos < entry.file_size; pos += BUF_SIZE)
{
Expand All @@ -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
Expand Down
8 changes: 7 additions & 1 deletion rpcs3/Crypto/unpkg.h
Original file line number Diff line number Diff line change
Expand Up @@ -361,10 +361,15 @@ class package_reader
return m_file;
}

const std::string& gep_needed_rap_file_path() const
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const std::string& gep_needed_rap_file_path() const
const std::string& get_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<PKGEntry>& entries);
void archive_seek(s64 new_offset, const fs::seek_mode damode = fs::seek_set);
Expand Down Expand Up @@ -400,4 +405,5 @@ class package_reader

// Expose bootable file installed (if installed such)
std::string m_bootable_file_path;
std::string m_rap_file_path;
};
71 changes: 71 additions & 0 deletions rpcs3/Crypto/unself.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switch

{
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.
Expand Down Expand Up @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

snake_case


// 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)
Expand Down
2 changes: 2 additions & 0 deletions rpcs3/Crypto/unself.h
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ class SELFDecrypter
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<typename EHdr, typename SHdr, typename PHdr>
Expand Down Expand Up @@ -562,5 +563,6 @@ class SELFDecrypter
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();
60 changes: 55 additions & 5 deletions rpcs3/rpcs3qt/main_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,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)))
Expand All @@ -902,6 +902,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++;
}
Expand Down Expand Up @@ -1005,10 +1012,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)
Expand Down Expand Up @@ -1047,6 +1050,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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be !fs::is_file ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is a debug change that sleeped 👍

{
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of casting to stringview here?

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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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));
const 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<std::string> bootable_paths;

// Run PKG unpacking asynchronously
Expand Down
3 changes: 3 additions & 0 deletions rpcs3/rpcs3qt/main_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ class main_window : public QMainWindow
int m_other_slider_pos = 0;
std::function<void()> m_notify_batch_game_action_cb;

QString last_pkg_path_postponed_due_to_missing_rap;
QString last_rap_file_needed_for_pkg;
Comment on lines +54 to +55
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this do ? too lazy

Suggested change
QString last_pkg_path_postponed_due_to_missing_rap;
QString last_rap_file_needed_for_pkg;
QString m_last_pkg_path_postponed_due_to_missing_rap;
QString m_last_rap_file_needed_for_pkg;


QIcon m_app_icon;
QIcon m_icon_play;
QIcon m_icon_pause;
Expand Down
Loading