From 63f16d7a469569b20568c2ec735672c67a32331b Mon Sep 17 00:00:00 2001 From: Eladash Date: Sun, 26 Sep 2021 20:45:24 +0300 Subject: [PATCH] GUI Utilities: Implement PS3 SDAT/EDAT decryption --- rpcs3/Crypto/unedat.cpp | 60 ++++++++++++++++------------------- rpcs3/Crypto/unedat.h | 4 +-- rpcs3/Crypto/unpkg.cpp | 4 +-- rpcs3/Emu/system_utils.cpp | 17 +++------- rpcs3/rpcs3qt/main_window.cpp | 45 ++++++++++++++++++++++---- 5 files changed, 73 insertions(+), 57 deletions(-) diff --git a/rpcs3/Crypto/unedat.cpp b/rpcs3/Crypto/unedat.cpp index 756e5e7293..de0513ecb2 100644 --- a/rpcs3/Crypto/unedat.cpp +++ b/rpcs3/Crypto/unedat.cpp @@ -6,6 +6,7 @@ #include "ec.h" #include "Utilities/mutex.h" +#include "Emu/system_utils.hpp" #include #include "util/asm.hpp" @@ -649,7 +650,7 @@ void read_npd_edat_header(const fs::file* input, NPD_HEADER& NPD, EDAT_HEADER& E EDAT.file_size = swap64(*reinterpret_cast(&edat_header[8])); } -bool extract_all_data(const fs::file* input, const fs::file* output, const char* input_file_name, unsigned char* devklic, unsigned char* rifkey, bool verbose) +bool extract_all_data(const fs::file* input, const fs::file* output, const char* input_file_name, unsigned char* devklic, bool verbose) { // Setup NPD and EDAT/SDAT structs. NPD_HEADER NPD; @@ -658,8 +659,7 @@ bool extract_all_data(const fs::file* input, const fs::file* output, const char* // Read in the NPD and EDAT/SDAT headers. read_npd_edat_header(input, NPD, EDAT); - unsigned char npd_magic[4] = {0x4E, 0x50, 0x44, 0x00}; //NPD0 - if (memcmp(&NPD.magic, npd_magic, 4)) + if (NPD.magic != "NPD\0"_u32) { edat_log.error("%s has invalid NPD header or already decrypted.", input_file_name); return true; @@ -671,6 +671,7 @@ bool extract_all_data(const fs::file* input, const fs::file* output, const char* edat_log.notice("NPD version: %d", NPD.version); edat_log.notice("NPD license: %d", NPD.license); edat_log.notice("NPD type: %d", NPD.type); + edat_log.notice("NPD content_id: %s", NPD.content_id); } // Set decryption key. @@ -715,27 +716,28 @@ bool extract_all_data(const fs::file* input, const fs::file* output, const char* // Select EDAT key. if ((NPD.license & 0x3) == 0x3) // Type 3: Use supplied devklic. - memcpy(&key, devklic, 0x10); - else if ((NPD.license & 0x2) == 0x2) // Type 2: Use key from RAP file (RIF key). { - memcpy(&key, rifkey, 0x10); + std::memcpy(&key, devklic, 0x10); + } + else // Type 2: Use key from RAP file (RIF key). (also used for type 1 at the moment) + { + const std::string rap_path = rpcs3::utils::get_rap_file_path(NPD.content_id); + + if (fs::file rap{rap_path}; rap && rap.size() >= sizeof(key)) + { + key = GetEdatRifKeyFromRapFile(rap); + } // Make sure we don't have an empty RIF key. if (!key) { - edat_log.error("A valid RAP file is needed for this EDAT file! (local activation)"); + edat_log.error("A valid RAP file is needed for this EDAT file! (license=%d)", NPD.license); return true; } - } - else if ((NPD.license & 0x1) == 0x1) // Type 1: Use network activation. - { - memcpy(&key, rifkey, 0x10); - // Make sure we don't have an empty RIF key. - if (!key) + if (verbose) { - edat_log.error("A valid RAP file is needed for this EDAT file! (network activation)"); - return true; + edat_log.notice("RIFKEY: %s", std::bit_cast>(key)); } } @@ -745,8 +747,6 @@ bool extract_all_data(const fs::file* input, const fs::file* output, const char* std::memcpy(&data, devklic, sizeof(data)); edat_log.notice("DEVKLIC: %s", data); - std::memcpy(&data, rifkey, sizeof(data)); - edat_log.notice("RIF KEY: %s", data); } } @@ -793,8 +793,7 @@ bool VerifyEDATHeaderWithKLicense(const fs::file& input, const std::string& inpu // Read in the NPD and EDAT/SDAT headers. read_npd_edat_header(&input, NPD, EDAT); - unsigned char npd_magic[4] = { 0x4E, 0x50, 0x44, 0x00 }; //NPD0 - if (memcmp(&NPD.magic, npd_magic, 4)) + if (NPD.magic != "NPD\0"_u32) { edat_log.error("%s has invalid NPD header or already decrypted.", input_file_name); return false; @@ -824,13 +823,17 @@ bool VerifyEDATHeaderWithKLicense(const fs::file& input, const std::string& inpu } // Decrypts full file -fs::file DecryptEDAT(const fs::file& input, const std::string& input_file_name, int mode, const std::string& rap_file_name, u8 *custom_klic, bool verbose) +fs::file DecryptEDAT(const fs::file& input, const std::string& input_file_name, int mode, u8 *custom_klic, bool verbose) { + if (!input) + { + return {}; + } + // Prepare the files. input.seek(0); - // Set keys (RIF and DEVKLIC). - u128 rifKey{}; + // Set DEVKLIC u128 devklic{}; // Select the EDAT key mode. @@ -875,17 +878,9 @@ fs::file DecryptEDAT(const fs::file& input, const std::string& input_file_name, return fs::file{}; } - // Read the RAP file, if provided. - if (!rap_file_name.empty()) - { - const fs::file rap(rap_file_name); - - rifKey = GetEdatRifKeyFromRapFile(rap); - } - // Delete the bad output file if any errors arise. fs::file output = fs::make_stream>(); - if (extract_all_data(&input, &output, input_file_name.c_str(), reinterpret_cast(&devklic), reinterpret_cast(&rifKey), verbose)) + if (extract_all_data(&input, &output, input_file_name.c_str(), reinterpret_cast(&devklic), verbose)) { output.release(); return fs::file{}; @@ -901,8 +896,7 @@ bool EDATADecrypter::ReadHeader() // Read in the NPD and EDAT/SDAT headers. read_npd_edat_header(&edata_file, npdHeader, edatHeader); - unsigned char npd_magic[4] = { 0x4E, 0x50, 0x44, 0x00 }; //NPD0 - if (memcmp(&npdHeader.magic, npd_magic, 4)) + if (npdHeader.magic != "NPD\0"_u32) { return false; } diff --git a/rpcs3/Crypto/unedat.h b/rpcs3/Crypto/unedat.h index ef351cac04..a273ecf588 100644 --- a/rpcs3/Crypto/unedat.h +++ b/rpcs3/Crypto/unedat.h @@ -27,7 +27,7 @@ struct NPD_HEADER s32 version; s32 license; s32 type; - u8 content_id[0x30]; + char content_id[0x30]; u8 digest[0x10]; u8 title_hash[0x10]; u8 dev_hash[0x10]; @@ -43,7 +43,7 @@ struct EDAT_HEADER }; // Decrypts full file, or null/empty file -extern fs::file DecryptEDAT(const fs::file& input, const std::string& input_file_name, int mode, const std::string& rap_file_name, u8 *custom_klic, bool verbose); +extern fs::file DecryptEDAT(const fs::file& input, const std::string& input_file_name, int mode, u8 *custom_klic, bool verbose); extern bool VerifyEDATHeaderWithKLicense(const fs::file& input, const std::string& input_file_name, const u8* custom_klic, std::string* contentID); diff --git a/rpcs3/Crypto/unpkg.cpp b/rpcs3/Crypto/unpkg.cpp index 77b53aa491..494f80c02d 100644 --- a/rpcs3/Crypto/unpkg.cpp +++ b/rpcs3/Crypto/unpkg.cpp @@ -686,7 +686,7 @@ package_error package_reader::check_target_app_version() const return package_error::app_version; } -fs::file DecryptEDAT(const fs::file& input, const std::string& input_file_name, int mode, const std::string& rap_file_name, u8 *custom_klic, bool verbose = false); +fs::file DecryptEDAT(const fs::file& input, const std::string& input_file_name, int mode, u8 *custom_klic, bool verbose = false); bool package_reader::extract_data(atomic_t& sync) { @@ -839,7 +839,7 @@ bool package_reader::extract_data(atomic_t& sync) if (is_buffered) { - out = DecryptEDAT(out, name, 1, "", reinterpret_cast(&m_header.klicensee), true); + out = DecryptEDAT(out, name, 1, reinterpret_cast(&m_header.klicensee), true); if (!out || !fs::write_file(path, fs::rewrite, static_cast>*>(out.release().get())->obj)) { num_failures++; diff --git a/rpcs3/Emu/system_utils.cpp b/rpcs3/Emu/system_utils.cpp index 3909a0f4c1..9db6b98e87 100644 --- a/rpcs3/Emu/system_utils.cpp +++ b/rpcs3/Emu/system_utils.cpp @@ -185,13 +185,14 @@ namespace rpcs3::utils const std::string edat_path = rpcs3::utils::get_c00_unlock_edat_path(content_id); // Check if user has unlock EDAT installed - if (!fs::is_file(edat_path)) + fs::file enc_file(edat_path); + + if (!enc_file) { 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; @@ -207,18 +208,8 @@ namespace rpcs3::utils 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); + fs::file dec_file = DecryptEDAT(enc_file, edat_path, 8, reinterpret_cast(&k_licensee), false); if (!dec_file) { sys_log.error("verify_c00_unlock_edat(): Failed to decrypt '%s'", edat_path); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 896bb3bdbb..b2fe8cefb2 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -1235,7 +1235,8 @@ void main_window::DecryptSPRXLibraries() path_last_sprx = qstr(g_cfg_vfs.get_dev_flash() + "sys/external"); } - const QStringList modules = QFileDialog::getOpenFileNames(this, tr("Select binary files"), path_last_sprx, tr("All Binaries (*.bin *.BIN *.self *.SELF *.sprx *.SPRX);;BIN files (*.bin *.BIN);;SELF files (*.self *.SELF);;SPRX files (*.sprx *.SPRX);;All files (*.*)")); + const QStringList modules = QFileDialog::getOpenFileNames(this, tr("Select binary files"), path_last_sprx, tr("All Binaries (*.bin *.BIN *.self *.SELF *.sprx *.SPRX *.sdat *.SDAT *.edat *.EDAT);;" + "BIN files (*.bin *.BIN);;SELF files (*.self *.SELF);;SPRX files (*.sprx *.SPRX);;SDAT/EDAT files (*.sdat *.SDAT *.edat *.EDAT);;All files (*.*)")); if (modules.isEmpty()) { @@ -1267,12 +1268,20 @@ void main_window::DecryptSPRXLibraries() bool tried = false; bool invalid = false; usz key_it = 0; + u32 file_magic{}; while (true) { for (; key_it < klics.size(); key_it++) { - if (elf_file.open(old_path) && elf_file.size() >= 4 && elf_file.read() == "SCE\0"_u32) + if (!elf_file.open(old_path) || !elf_file.read(file_magic)) + { + file_magic = 0; + } + + switch (file_magic) + { + case "SCE\0"_u32: { // First KLIC is no KLIC elf_file = decrypt_self(std::move(elf_file), key_it != 0 ? reinterpret_cast(&klics[key_it]) : nullptr); @@ -1282,11 +1291,32 @@ void main_window::DecryptSPRXLibraries() // Try another key continue; } + + break; } - else + case "NPD\0"_u32: + { + // EDAT / SDAT + elf_file = DecryptEDAT(elf_file, old_path, key_it != 0 ? 8 : 1, reinterpret_cast(&klics[key_it]), true); + + if (!elf_file) + { + // Try another key + continue; + } + + break; + } + default: { - elf_file = {}; invalid = true; + break; + } + } + + if (invalid) + { + elf_file.close(); } break; @@ -1294,13 +1324,14 @@ void main_window::DecryptSPRXLibraries() if (elf_file) { - const std::string bin_ext = _module.toLower().endsWith(".sprx") ? ".prx" : ".elf"; - const std::string new_path = old_path.substr(0, old_path.find_last_of('.')) + bin_ext; + const std::string exec_ext = _module.toLower().endsWith(".sprx") ? ".prx" : ".elf"; + const std::string new_path = file_magic == "NPD\0"_u32 ? old_path + ".unedat" : + old_path.substr(0, old_path.find_last_of('.')) + exec_ext; if (fs::file new_file{new_path, fs::rewrite}) { new_file.write(elf_file.to_string()); - gui_log.success("Decrypted %s", old_path); + gui_log.success("Decrypted %s -> %s", old_path, new_path); } else {