GUI Utilities: Implement PS3 SDAT/EDAT decryption

This commit is contained in:
Eladash 2021-09-26 20:45:24 +03:00 committed by Megamouse
parent c765de81d4
commit 63f16d7a46
5 changed files with 73 additions and 57 deletions

View File

@ -6,6 +6,7 @@
#include "ec.h"
#include "Utilities/mutex.h"
#include "Emu/system_utils.hpp"
#include <cmath>
#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<u64*>(&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<be_t<u128>>(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<std::vector<u8>>();
if (extract_all_data(&input, &output, input_file_name.c_str(), reinterpret_cast<uchar*>(&devklic), reinterpret_cast<uchar*>(&rifKey), verbose))
if (extract_all_data(&input, &output, input_file_name.c_str(), reinterpret_cast<uchar*>(&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;
}

View File

@ -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);

View File

@ -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<double>& sync)
{
@ -839,7 +839,7 @@ bool package_reader::extract_data(atomic_t<double>& sync)
if (is_buffered)
{
out = DecryptEDAT(out, name, 1, "", reinterpret_cast<u8*>(&m_header.klicensee), true);
out = DecryptEDAT(out, name, 1, reinterpret_cast<u8*>(&m_header.klicensee), true);
if (!out || !fs::write_file(path, fs::rewrite, static_cast<fs::container_stream<std::vector<u8>>*>(out.release().get())->obj))
{
num_failures++;

View File

@ -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<u8*>(&k_licensee), false);
fs::file dec_file = DecryptEDAT(enc_file, edat_path, 8, reinterpret_cast<u8*>(&k_licensee), false);
if (!dec_file)
{
sys_log.error("verify_c00_unlock_edat(): Failed to decrypt '%s'", edat_path);

View File

@ -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<u32>() == "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<u8*>(&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<u8*>(&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
{