From 4fcc1c0f3106965fb67297d3ff0c6210db318e93 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sun, 18 Jul 2021 22:19:58 +0300 Subject: [PATCH] Fully implement sceNpDrmVerifyUpgradeLicense --- rpcs3/Emu/Cell/Modules/sceNp.cpp | 42 ++++--------- rpcs3/Emu/system_utils.cpp | 105 +++++++++++++++++++++++++++++-- rpcs3/Emu/system_utils.hpp | 3 +- 3 files changed, 113 insertions(+), 37 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/sceNp.cpp b/rpcs3/Emu/Cell/Modules/sceNp.cpp index 708e7e59d8..704eeefc29 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNp.cpp @@ -1,4 +1,4 @@ -#include "stdafx.h" +#include "stdafx.h" #include "Emu/System.h" #include "Emu/system_utils.hpp" #include "Emu/VFS.h" @@ -543,52 +543,34 @@ error_code sceNpDrmIsAvailable2(vm::cptr k_licensee_addr, vm::cptr drm return npDrmIsAvailable(k_licensee_addr, drm_path); } -error_code sceNpDrmVerifyUpgradeLicense(vm::cptr content_id) +error_code npDrmVerifyUpgradeLicense(vm::cptr content_id) { - sceNp.warning("sceNpDrmVerifyUpgradeLicense(content_id=*0x%x)", content_id); - if (!content_id) { return SCE_NP_DRM_ERROR_INVALID_PARAM; } const std::string content_str(content_id.get_ptr(), std::find(content_id.get_ptr(), content_id.get_ptr() + 0x2f, '\0')); - const std::string rap_file = rpcs3::utils::get_rap_file_path(content_str); + sceNp.warning("npDrmVerifyUpgradeLicense(): content_id='%s'", content_id); - sceNp.warning(u8"sceNpDrmVerifyUpgradeLicense(): content_id=“%s”", content_id); - - if (fs::stat_t s{}; !fs::stat(rap_file, s) || s.is_directory || s.size < 0x10) - { - // Game hasn't been purchased therefore no RAP file present + if (!rpcs3::utils::verify_c00_unlock_edat(Emu.GetTitleID(), content_str)) return SCE_NP_DRM_ERROR_LICENSE_NOT_FOUND; - } - // Game has been purchased and there's a RAP file present return CELL_OK; } +error_code sceNpDrmVerifyUpgradeLicense(vm::cptr content_id) +{ + sceNp.warning("sceNpDrmVerifyUpgradeLicense(content_id=*0x%x)", content_id); + + return npDrmVerifyUpgradeLicense(content_id); +} + error_code sceNpDrmVerifyUpgradeLicense2(vm::cptr content_id) { sceNp.warning("sceNpDrmVerifyUpgradeLicense2(content_id=*0x%x)", content_id); - if (!content_id) - { - return SCE_NP_DRM_ERROR_INVALID_PARAM; - } - - const std::string content_str(content_id.get_ptr(), std::find(content_id.get_ptr(), content_id.get_ptr() + 0x2f, '\0')); - const std::string rap_file = rpcs3::utils::get_rap_file_path(content_str); - - sceNp.warning(u8"sceNpDrmVerifyUpgradeLicense2(): content_id=“%s”", content_id); - - if (fs::stat_t s{}; !fs::stat(rap_file, s) || s.is_directory || s.size < 0x10) - { - // Game hasn't been purchased therefore no RAP file present - return SCE_NP_DRM_ERROR_LICENSE_NOT_FOUND; - } - - // Game has been purchased and there's a RAP file present - return CELL_OK; + return npDrmVerifyUpgradeLicense(content_id); } error_code sceNpDrmExecuteGamePurchase() diff --git a/rpcs3/Emu/system_utils.cpp b/rpcs3/Emu/system_utils.cpp index 5ab959ce97..b8a1709f11 100644 --- a/rpcs3/Emu/system_utils.cpp +++ b/rpcs3/Emu/system_utils.cpp @@ -7,6 +7,8 @@ #include "Utilities/StrUtil.h" #include "Utilities/Thread.h" #include "Crypto/unpkg.h" +#include "Crypto/unself.h" +#include "Crypto/unedat.h" #include #include @@ -133,9 +135,9 @@ namespace rpcs3::utils return fs::get_cache_dir() + "cache/"; } - std::string get_rap_file_path(const std::string& rap) + std::string get_rap_file_path(const std::string_view& rap) { - const std::string home_dir = get_hdd0_dir() + "/home"; + const std::string home_dir = get_hdd0_dir() + "home"; std::string rap_path; @@ -155,6 +157,95 @@ namespace rpcs3::utils return rap_path; } + std::string get_c00_unlock_edat_path(const std::string_view& title_id, const std::string_view& content_id) + { + const std::string home_dir = get_hdd0_dir() + "home"; + + std::string edat_path; + + for (auto&& entry : fs::dir(home_dir)) + { + if (entry.is_directory && check_user(entry.name)) + { + edat_path = fmt::format("%s/%s/exdata/%s/%s.edat", home_dir, entry.name, title_id, content_id); + if (fs::is_file(edat_path)) + { + return edat_path; + } + } + } + + // Return a sample path tested for logging purposes + return edat_path; + } + + bool verify_c00_unlock_edat(const std::string_view& title_id, const std::string_view& content_id) + { + const std::string edat_path = rpcs3::utils::get_c00_unlock_edat_path(title_id, content_id); + + // Check if user has unlock EDAT installed + if (!fs::is_file(edat_path)) + { + sys_log.notice("verify_c00_unlock_edat(): '%s' not found", edat_path); + return false; + } + + const fs::file enc_file(edat_path); + u128 k_licensee = get_default_self_klic(); + std::string edat_content_id; + + if (!VerifyEDATHeaderWithKLicense(enc_file, edat_path, reinterpret_cast(&k_licensee), &edat_content_id)) + { + sys_log.error("verify_c00_unlock_edat(): Failed to verify npd file '%s'", edat_path); + return false; + } + + if (edat_content_id != content_id) + { + sys_log.error("verify_c00_unlock_edat(): Content ID mismatch in npd header of '%s'", edat_path); + return false; + } + + // Check if required RAP is present + std::string rap_path = rpcs3::utils::get_rap_file_path(content_id); + + if (!fs::is_file(rap_path)) + { + // Not necessarily an error + sys_log.warning("verify_c00_unlock_edat(): RAP file not found: '%s'", rap_path); + rap_path.clear(); + } + + // Decrypt EDAT and verify its contents + fs::file dec_file = DecryptEDAT(enc_file, edat_path, 8, rap_path, reinterpret_cast(&k_licensee), false); + if (!dec_file) + { + sys_log.error("verify_c00_unlock_edat(): Failed to decrypt '%s'", edat_path); + return false; + } + + u32 magic{}; + dec_file.read(magic); + if (magic != "GOMA"_u32) + { + sys_log.error("verify_c00_unlock_edat(): Bad header magic in unlock EDAT '%s'", edat_path); + return false; + } + + // Read null-terminated string + dec_file.seek(0x10); + dec_file.read(edat_content_id, 0x30); + edat_content_id.resize(std::min(0x30, edat_content_id.find_first_of('\0'))); + if (edat_content_id != content_id) + { + sys_log.error("verify_c00_unlock_edat(): Content ID mismatch in unlock EDAT '%s'", edat_path); + return false; + } + + // Game has been purchased and EDAT is verified + return true; + } + std::string get_sfo_dir_from_game_path(const std::string& game_path, const std::string& title_id) { if (fs::is_file(game_path + "/PS3_DISC.SFB")) @@ -189,15 +280,17 @@ namespace rpcs3::utils const auto psf = psf::load_object(fs::file(game_path + "/PARAM.SFO")); const auto category = psf::get_string(psf, "CATEGORY"); - const auto content_id = std::string(psf::get_string(psf, "CONTENT_ID")); + const auto content_id = psf::get_string(psf, "CONTENT_ID"); if (category == "HG" && !content_id.empty()) { - // This is a trial game. Check if the user has a RAP file to unlock it. - if (fs::is_file(game_path + "/C00/PARAM.SFO") && fs::is_file(get_rap_file_path(content_id))) + // This is a trial game. Check if the user has EDAT file to unlock it. + const auto c00_title_id = psf::get_string(psf, "TITLE_ID"); + + if (fs::is_file(game_path + "/C00/PARAM.SFO") && verify_c00_unlock_edat(c00_title_id, content_id)) { // Load full game data. - sys_log.notice("Found RAP file %s.rap for trial game %s", content_id, title_id); + sys_log.notice("Verified EDAT file %s.edat for trial game %s", content_id, c00_title_id); return game_path + "/C00"; } } diff --git a/rpcs3/Emu/system_utils.hpp b/rpcs3/Emu/system_utils.hpp index 1d0bf255b0..09ebefbf14 100644 --- a/rpcs3/Emu/system_utils.hpp +++ b/rpcs3/Emu/system_utils.hpp @@ -22,7 +22,8 @@ namespace rpcs3::utils std::string get_hdd1_dir(); std::string get_cache_dir(); - std::string get_rap_file_path(const std::string& rap); + std::string get_rap_file_path(const std::string_view& rap); + bool verify_c00_unlock_edat(const std::string_view& title_id, const std::string_view& content_id); std::string get_sfo_dir_from_game_path(const std::string& game_path, const std::string& title_id = ""); std::string get_custom_config_dir();