From 86a832da29316f7b6c15ca9b0f2639e23e74a9e0 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Fri, 21 Feb 2025 13:55:19 +0200 Subject: [PATCH] unpkg: Add support for IDU packages --- rpcs3/Crypto/unpkg.cpp | 84 ++++++++++++++++++++++++++++++------------ rpcs3/Crypto/unpkg.h | 3 +- 2 files changed, 63 insertions(+), 24 deletions(-) diff --git a/rpcs3/Crypto/unpkg.cpp b/rpcs3/Crypto/unpkg.cpp index baf612657d..94a260b006 100644 --- a/rpcs3/Crypto/unpkg.cpp +++ b/rpcs3/Crypto/unpkg.cpp @@ -39,6 +39,13 @@ package_reader::package_reader(const std::string& path) return; } + m_is_valid = set_decryption_key(); + + if (!m_is_valid) + { + return; + } + const bool param_sfo_found = read_param_sfo(); if (!param_sfo_found) @@ -66,7 +73,7 @@ bool package_reader::read_header() } pkg_log.notice("Path: '%s'", m_path); - pkg_log.notice("Header: pkg_magic = 0x%x = \"%s\"", +m_header.pkg_magic, std::string_view(reinterpret_cast(&m_header.pkg_magic + 1), 3)); // Skip 0x7F + pkg_log.notice("Header: pkg_magic = 0x%x = \"%s\"", +m_header.pkg_magic, std::string_view(reinterpret_cast(&m_header.pkg_magic), 4).substr(1)); // Skip 0x7F pkg_log.notice("Header: pkg_type = 0x%x = %d", m_header.pkg_type, m_header.pkg_type); pkg_log.notice("Header: pkg_platform = 0x%x = %d", m_header.pkg_platform, m_header.pkg_platform); pkg_log.notice("Header: meta_offset = 0x%x = %d", m_header.meta_offset, m_header.meta_offset); @@ -93,7 +100,7 @@ bool package_reader::read_header() return false; } - pkg_log.notice("Extended header: magic = 0x%x = \"%s\"", +ext_header.magic, std::string_view(reinterpret_cast(&ext_header.magic + 1), 3)); + pkg_log.notice("Extended header: magic = 0x%x = \"%s\"", +ext_header.magic, std::string_view(reinterpret_cast(&ext_header.magic), 4).substr(1)); pkg_log.notice("Extended header: unknown_1 = 0x%x = %d", ext_header.unknown_1, ext_header.unknown_1); pkg_log.notice("Extended header: ext_hdr_size = 0x%x = %d", ext_header.ext_hdr_size, ext_header.ext_hdr_size); pkg_log.notice("Extended header: ext_data_size = 0x%x = %d", ext_header.ext_data_size, ext_header.ext_data_size); @@ -112,6 +119,12 @@ bool package_reader::read_header() return false; } + if (u64{umax} / sizeof(PKGEntry) < m_header.file_count) + { + pkg_log.error("PKG file count is too large! (0x%x)", m_header.file_count); + return false; + } + switch (const u16 type = m_header.pkg_type) { case PKG_RELEASE_TYPE_DEBUG: break; @@ -187,11 +200,6 @@ bool package_reader::read_header() bool package_reader::read_metadata() { - if (!decrypt_data()) - { - return false; - } - // Read title ID and use it as an installation directory m_install_dir.resize(9); archive_read_block(55, &m_install_dir.front(), m_install_dir.size()); @@ -306,7 +314,7 @@ bool package_reader::read_metadata() if (packet.size == sizeof(m_metadata.qa_digest)) { archive_read(&m_metadata.qa_digest, sizeof(m_metadata.qa_digest)); - pkg_log.notice("Metadata: QA Digest = 0x%x", m_metadata.qa_digest); + pkg_log.notice("Metadata: QA Digest = %s", std::span(m_metadata.qa_digest, sizeof(m_metadata.qa_digest))); continue; } else @@ -478,7 +486,7 @@ bool package_reader::read_metadata() return true; } -bool package_reader::decrypt_data() +bool package_reader::set_decryption_key() { if (!m_is_valid) { @@ -493,27 +501,59 @@ bool package_reader::decrypt_data() aes_context ctx; aes_setkey_enc(&ctx, m_metadata.content_type == 0x15u ? PKG_AES_KEY_VITA_1 : m_metadata.content_type == 0x16u ? PKG_AES_KEY_VITA_2 : PKG_AES_KEY_VITA_3, 128); aes_crypt_ecb(&ctx, AES_ENCRYPT, reinterpret_cast(&m_header.klicensee), m_dec_key.data()); - decrypt(0, m_header.file_count * sizeof(PKGEntry), m_dec_key.data()); + return true; } - else + + switch (m_metadata.package_revision.data.make_package_npdrm_ver[0]) + { + case 0x15: + { + if (!!(read_from_ptr(m_metadata.qa_digest) | read_from_ptr(m_metadata.qa_digest + 16))) + { + pkg_log.error("IDU PKG contains QA Digest!"); + } + + std::memcpy(m_dec_key.data(), PKG_AES_KEY_IDU, m_dec_key.size()); + break; + } + default: + { + pkg_log.error("Unknown NPDRM package version: 0x%x", read_from_ptr>(m_metadata.package_revision.data.make_package_npdrm_ver)); + [[fallthrough]]; + } + case 0x19: { std::memcpy(m_dec_key.data(), PKG_AES_KEY, m_dec_key.size()); - decrypt(0, m_header.file_count * sizeof(PKGEntry), m_header.pkg_platform == PKG_PLATFORM_TYPE_PSP_PSVITA ? PKG_AES_KEY2 : m_dec_key.data()); + break; + } } return true; } -bool package_reader::read_param_sfo() +bool package_reader::read_entries(std::vector& entries) { - if (!decrypt_data()) + const std::span data = decrypt(0, m_header.file_count * sizeof(PKGEntry), m_dec_key.data()); + + if (data.size() < m_header.file_count * sizeof(PKGEntry)) { return false; } - std::vector entries(m_header.file_count); + entries.resize(m_header.file_count); - std::memcpy(entries.data(), m_bufs.back().get(), entries.size() * sizeof(PKGEntry)); + std::memcpy(entries.data(), data.data(), data.size()); + return true; +} + +bool package_reader::read_param_sfo() +{ + std::vector entries; + + if (!read_entries(entries)) + { + return false; + } for (const PKGEntry& entry : entries) { @@ -757,17 +797,15 @@ bool package_reader::fill_data(std::map& all_instal m_entry_indexer = 0; m_written_bytes = 0; - if (!decrypt_data()) + usz num_failures = 0; + + std::vector entries; + + if (!read_entries(entries)) { return false; } - usz num_failures = 0; - - std::vector entries(m_header.file_count); - - std::memcpy(entries.data(), m_bufs.back().get(), entries.size() * sizeof(PKGEntry)); - // Create directories first for (const auto& entry : entries) { diff --git a/rpcs3/Crypto/unpkg.h b/rpcs3/Crypto/unpkg.h index 0f5c8faba1..993f2d0705 100644 --- a/rpcs3/Crypto/unpkg.h +++ b/rpcs3/Crypto/unpkg.h @@ -365,7 +365,8 @@ private: bool read_header(); bool read_metadata(); bool read_param_sfo(); - bool decrypt_data(); + bool set_decryption_key(); + bool read_entries(std::vector& entries); 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();