mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-17 08:11:51 +00:00
GUI Utilities: Implement PS3 SDAT/EDAT decryption
This commit is contained in:
parent
c765de81d4
commit
63f16d7a46
@ -6,6 +6,7 @@
|
|||||||
#include "ec.h"
|
#include "ec.h"
|
||||||
|
|
||||||
#include "Utilities/mutex.h"
|
#include "Utilities/mutex.h"
|
||||||
|
#include "Emu/system_utils.hpp"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
#include "util/asm.hpp"
|
#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]));
|
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.
|
// Setup NPD and EDAT/SDAT structs.
|
||||||
NPD_HEADER NPD;
|
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 in the NPD and EDAT/SDAT headers.
|
||||||
read_npd_edat_header(input, NPD, EDAT);
|
read_npd_edat_header(input, NPD, EDAT);
|
||||||
|
|
||||||
unsigned char npd_magic[4] = {0x4E, 0x50, 0x44, 0x00}; //NPD0
|
if (NPD.magic != "NPD\0"_u32)
|
||||||
if (memcmp(&NPD.magic, npd_magic, 4))
|
|
||||||
{
|
{
|
||||||
edat_log.error("%s has invalid NPD header or already decrypted.", input_file_name);
|
edat_log.error("%s has invalid NPD header or already decrypted.", input_file_name);
|
||||||
return true;
|
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 version: %d", NPD.version);
|
||||||
edat_log.notice("NPD license: %d", NPD.license);
|
edat_log.notice("NPD license: %d", NPD.license);
|
||||||
edat_log.notice("NPD type: %d", NPD.type);
|
edat_log.notice("NPD type: %d", NPD.type);
|
||||||
|
edat_log.notice("NPD content_id: %s", NPD.content_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set decryption key.
|
// Set decryption key.
|
||||||
@ -715,27 +716,28 @@ bool extract_all_data(const fs::file* input, const fs::file* output, const char*
|
|||||||
|
|
||||||
// Select EDAT key.
|
// Select EDAT key.
|
||||||
if ((NPD.license & 0x3) == 0x3) // Type 3: Use supplied devklic.
|
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.
|
// Make sure we don't have an empty RIF key.
|
||||||
if (!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;
|
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 (verbose)
|
||||||
if (!key)
|
|
||||||
{
|
{
|
||||||
edat_log.error("A valid RAP file is needed for this EDAT file! (network activation)");
|
edat_log.notice("RIFKEY: %s", std::bit_cast<be_t<u128>>(key));
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -745,8 +747,6 @@ bool extract_all_data(const fs::file* input, const fs::file* output, const char*
|
|||||||
|
|
||||||
std::memcpy(&data, devklic, sizeof(data));
|
std::memcpy(&data, devklic, sizeof(data));
|
||||||
edat_log.notice("DEVKLIC: %s", 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 in the NPD and EDAT/SDAT headers.
|
||||||
read_npd_edat_header(&input, NPD, EDAT);
|
read_npd_edat_header(&input, NPD, EDAT);
|
||||||
|
|
||||||
unsigned char npd_magic[4] = { 0x4E, 0x50, 0x44, 0x00 }; //NPD0
|
if (NPD.magic != "NPD\0"_u32)
|
||||||
if (memcmp(&NPD.magic, npd_magic, 4))
|
|
||||||
{
|
{
|
||||||
edat_log.error("%s has invalid NPD header or already decrypted.", input_file_name);
|
edat_log.error("%s has invalid NPD header or already decrypted.", input_file_name);
|
||||||
return false;
|
return false;
|
||||||
@ -824,13 +823,17 @@ bool VerifyEDATHeaderWithKLicense(const fs::file& input, const std::string& inpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decrypts full file
|
// 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.
|
// Prepare the files.
|
||||||
input.seek(0);
|
input.seek(0);
|
||||||
|
|
||||||
// Set keys (RIF and DEVKLIC).
|
// Set DEVKLIC
|
||||||
u128 rifKey{};
|
|
||||||
u128 devklic{};
|
u128 devklic{};
|
||||||
|
|
||||||
// Select the EDAT key mode.
|
// 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{};
|
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.
|
// Delete the bad output file if any errors arise.
|
||||||
fs::file output = fs::make_stream<std::vector<u8>>();
|
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();
|
output.release();
|
||||||
return fs::file{};
|
return fs::file{};
|
||||||
@ -901,8 +896,7 @@ bool EDATADecrypter::ReadHeader()
|
|||||||
// Read in the NPD and EDAT/SDAT headers.
|
// Read in the NPD and EDAT/SDAT headers.
|
||||||
read_npd_edat_header(&edata_file, npdHeader, edatHeader);
|
read_npd_edat_header(&edata_file, npdHeader, edatHeader);
|
||||||
|
|
||||||
unsigned char npd_magic[4] = { 0x4E, 0x50, 0x44, 0x00 }; //NPD0
|
if (npdHeader.magic != "NPD\0"_u32)
|
||||||
if (memcmp(&npdHeader.magic, npd_magic, 4))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ struct NPD_HEADER
|
|||||||
s32 version;
|
s32 version;
|
||||||
s32 license;
|
s32 license;
|
||||||
s32 type;
|
s32 type;
|
||||||
u8 content_id[0x30];
|
char content_id[0x30];
|
||||||
u8 digest[0x10];
|
u8 digest[0x10];
|
||||||
u8 title_hash[0x10];
|
u8 title_hash[0x10];
|
||||||
u8 dev_hash[0x10];
|
u8 dev_hash[0x10];
|
||||||
@ -43,7 +43,7 @@ struct EDAT_HEADER
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Decrypts full file, or null/empty file
|
// 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);
|
extern bool VerifyEDATHeaderWithKLicense(const fs::file& input, const std::string& input_file_name, const u8* custom_klic, std::string* contentID);
|
||||||
|
|
||||||
|
@ -686,7 +686,7 @@ package_error package_reader::check_target_app_version() const
|
|||||||
return package_error::app_version;
|
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)
|
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)
|
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))
|
if (!out || !fs::write_file(path, fs::rewrite, static_cast<fs::container_stream<std::vector<u8>>*>(out.release().get())->obj))
|
||||||
{
|
{
|
||||||
num_failures++;
|
num_failures++;
|
||||||
|
@ -185,13 +185,14 @@ namespace rpcs3::utils
|
|||||||
const std::string edat_path = rpcs3::utils::get_c00_unlock_edat_path(content_id);
|
const std::string edat_path = rpcs3::utils::get_c00_unlock_edat_path(content_id);
|
||||||
|
|
||||||
// Check if user has unlock EDAT installed
|
// 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);
|
sys_log.notice("verify_c00_unlock_edat(): '%s' not found", edat_path);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fs::file enc_file(edat_path);
|
|
||||||
u128 k_licensee = get_default_self_klic();
|
u128 k_licensee = get_default_self_klic();
|
||||||
std::string edat_content_id;
|
std::string edat_content_id;
|
||||||
|
|
||||||
@ -207,18 +208,8 @@ namespace rpcs3::utils
|
|||||||
return false;
|
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
|
// 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)
|
if (!dec_file)
|
||||||
{
|
{
|
||||||
sys_log.error("verify_c00_unlock_edat(): Failed to decrypt '%s'", edat_path);
|
sys_log.error("verify_c00_unlock_edat(): Failed to decrypt '%s'", edat_path);
|
||||||
|
@ -1235,7 +1235,8 @@ void main_window::DecryptSPRXLibraries()
|
|||||||
path_last_sprx = qstr(g_cfg_vfs.get_dev_flash() + "sys/external");
|
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())
|
if (modules.isEmpty())
|
||||||
{
|
{
|
||||||
@ -1267,12 +1268,20 @@ void main_window::DecryptSPRXLibraries()
|
|||||||
bool tried = false;
|
bool tried = false;
|
||||||
bool invalid = false;
|
bool invalid = false;
|
||||||
usz key_it = 0;
|
usz key_it = 0;
|
||||||
|
u32 file_magic{};
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
for (; key_it < klics.size(); key_it++)
|
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
|
// First KLIC is no KLIC
|
||||||
elf_file = decrypt_self(std::move(elf_file), key_it != 0 ? reinterpret_cast<u8*>(&klics[key_it]) : nullptr);
|
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
|
// Try another key
|
||||||
continue;
|
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;
|
invalid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invalid)
|
||||||
|
{
|
||||||
|
elf_file.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -1294,13 +1324,14 @@ void main_window::DecryptSPRXLibraries()
|
|||||||
|
|
||||||
if (elf_file)
|
if (elf_file)
|
||||||
{
|
{
|
||||||
const std::string bin_ext = _module.toLower().endsWith(".sprx") ? ".prx" : ".elf";
|
const std::string exec_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 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})
|
if (fs::file new_file{new_path, fs::rewrite})
|
||||||
{
|
{
|
||||||
new_file.write(elf_file.to_string());
|
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
|
else
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user