mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-04-20 11:36:13 +00:00
Fixes of PS3 filesystem EDAT/SDAT handling
* Return EFSSPEIFIC if encountered corrupted EDAT or an unmatched key in sys_fs_open and sys_fs_read. * Fix validate_dev_klic() for license-free EDAT. * Fix EDATADecrypter::ReadData() for when size + pos > file_size but pos < file_size. * Try to save up to 16 decryption keys. Educated guess based on NPDRM file descriptors count llimit. * Return LICENCE_NOT_FOUND if needed RAP file is not found in sceNpDrmIsAvailable. * Check additional sys_fs_open errors for sceNpDrmOpen. (EISDIR, ENOTMOUNTED)
This commit is contained in:
parent
933d96af5f
commit
c49ebba648
9 changed files with 141 additions and 105 deletions
|
@ -549,6 +549,11 @@ int check_data(unsigned char *key, EDAT_HEADER *edat, NPD_HEADER *npd, const fs:
|
|||
|
||||
bool validate_dev_klic(const u8* klicensee, NPD_HEADER *npd)
|
||||
{
|
||||
if ((npd->license & 0x3) != 0x3)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned char dev[0x60] = { 0 };
|
||||
|
||||
// Build the dev buffer (first 0x60 bytes of NPD header in big-endian).
|
||||
|
@ -566,12 +571,6 @@ bool validate_dev_klic(const u8* klicensee, NPD_HEADER *npd)
|
|||
u128 klic;
|
||||
std::memcpy(&klic, klicensee, sizeof(klic));
|
||||
|
||||
if (!klic)
|
||||
{
|
||||
// Allow empty dev hash.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Generate klicensee xor key.
|
||||
u128 key = klic ^ std::bit_cast<u128>(NP_OMAC_KEY_2);
|
||||
|
||||
|
@ -579,13 +578,19 @@ bool validate_dev_klic(const u8* klicensee, NPD_HEADER *npd)
|
|||
return cmac_hash_compare(reinterpret_cast<uchar*>(&key), 0x10, dev, 0x60, npd->dev_hash, 0x10);
|
||||
}
|
||||
|
||||
bool validate_npd_hashes(const char* file_name, const u8* klicensee, NPD_HEADER *npd, bool verbose)
|
||||
bool validate_npd_hashes(const char* file_name, const u8* klicensee, NPD_HEADER *npd, EDAT_HEADER* edat, bool verbose)
|
||||
{
|
||||
if (!file_name)
|
||||
{
|
||||
fmt::throw_exception("Empty filename");
|
||||
}
|
||||
|
||||
// Ignore header validation in DEBUG data.
|
||||
if (edat->flags & EDAT_DEBUG_DATA_FLAG)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const usz file_name_length = std::strlen(file_name);
|
||||
const usz buf_len = 0x30 + file_name_length;
|
||||
|
||||
|
@ -704,14 +709,10 @@ bool extract_all_data(const fs::file* input, const fs::file* output, const char*
|
|||
// Perform header validation (EDAT only).
|
||||
char real_file_name[CRYPTO_MAX_PATH];
|
||||
extract_file_name(input_file_name, real_file_name);
|
||||
if (!validate_npd_hashes(real_file_name, devklic, &NPD, verbose))
|
||||
if (!validate_npd_hashes(real_file_name, devklic, &NPD, &EDAT, verbose))
|
||||
{
|
||||
// Ignore header validation in DEBUG data.
|
||||
if ((EDAT.flags & EDAT_DEBUG_DATA_FLAG) != EDAT_DEBUG_DATA_FLAG)
|
||||
{
|
||||
edat_log.error("NPD hash validation failed!");
|
||||
return true;
|
||||
}
|
||||
edat_log.error("NPD hash validation failed!");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Select EDAT key.
|
||||
|
@ -784,7 +785,7 @@ u128 GetEdatRifKeyFromRapFile(const fs::file& rap_file)
|
|||
return rifkey;
|
||||
}
|
||||
|
||||
bool VerifyEDATHeaderWithKLicense(const fs::file& input, const std::string& input_file_name, const u8* custom_klic, std::string* contentID)
|
||||
bool VerifyEDATHeaderWithKLicense(const fs::file& input, const std::string& input_file_name, const u8* custom_klic, std::string* contentID, u32* license)
|
||||
{
|
||||
// Setup NPD and EDAT/SDAT structs.
|
||||
NPD_HEADER NPD;
|
||||
|
@ -808,17 +809,17 @@ bool VerifyEDATHeaderWithKLicense(const fs::file& input, const std::string& inpu
|
|||
// Perform header validation (EDAT only).
|
||||
char real_file_name[CRYPTO_MAX_PATH];
|
||||
extract_file_name(input_file_name.c_str(), real_file_name);
|
||||
if (!validate_npd_hashes(real_file_name, custom_klic, &NPD, false))
|
||||
if (!validate_npd_hashes(real_file_name, custom_klic, &NPD, &EDAT, false))
|
||||
{
|
||||
// Ignore header validation in DEBUG data.
|
||||
if ((EDAT.flags & EDAT_DEBUG_DATA_FLAG) != EDAT_DEBUG_DATA_FLAG)
|
||||
{
|
||||
edat_log.error("NPD hash validation failed!");
|
||||
return false;
|
||||
}
|
||||
edat_log.error("NPD hash validation failed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
*contentID = std::string(reinterpret_cast<const char*>(NPD.content_id));
|
||||
std::string_view sv{NPD.content_id, std::size(NPD.content_id)};
|
||||
sv = sv.substr(0, sv.find_first_of('\0'));
|
||||
|
||||
if (contentID) *contentID = std::string(sv);
|
||||
if (license) *license = (NPD.license & 3);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -910,33 +911,13 @@ bool EDATADecrypter::ReadHeader()
|
|||
else
|
||||
{
|
||||
// verify key
|
||||
if (!validate_dev_klic(reinterpret_cast<uchar*>(&dev_key), &npdHeader))
|
||||
if (!validate_dev_klic(reinterpret_cast<uchar*>(&dec_key), &npdHeader))
|
||||
{
|
||||
edat_log.error("Failed validating klic");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Select EDAT key.
|
||||
if ((npdHeader.license & 0x3) == 0x3) // Type 3: Use supplied devklic.
|
||||
dec_key = std::move(dev_key);
|
||||
else if ((npdHeader.license & 0x2) == 0x2) // Type 2: Use key from RAP file (RIF key).
|
||||
{
|
||||
dec_key = std::move(rif_key);
|
||||
|
||||
if (!dec_key)
|
||||
{
|
||||
edat_log.warning("Empty Dec key for local activation!");
|
||||
}
|
||||
}
|
||||
else if ((npdHeader.license & 0x1) == 0x1) // Type 1: Use network activation.
|
||||
{
|
||||
dec_key = std::move(rif_key);
|
||||
|
||||
if (!dec_key)
|
||||
{
|
||||
edat_log.warning("Empty Dec key for network activation!");
|
||||
}
|
||||
}
|
||||
// Use provided dec_key
|
||||
}
|
||||
|
||||
edata_file.seek(0);
|
||||
|
@ -952,29 +933,37 @@ bool EDATADecrypter::ReadHeader()
|
|||
file_size = edatHeader.file_size;
|
||||
total_blocks = utils::aligned_div(edatHeader.file_size, edatHeader.block_size);
|
||||
|
||||
// Try decrypting the first block instead
|
||||
u8 data_sample[1];
|
||||
|
||||
if (!ReadData(0, data_sample, 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u64 EDATADecrypter::ReadData(u64 pos, u8* data, u64 size)
|
||||
{
|
||||
if (pos > edatHeader.file_size)
|
||||
return 0;
|
||||
size = std::min<u64>(size, pos > edatHeader.file_size ? 0 : edatHeader.file_size - pos);
|
||||
|
||||
// now we need to offset things to account for the actual 'range' requested
|
||||
const u64 startOffset = pos % edatHeader.block_size;
|
||||
|
||||
const u32 num_blocks = static_cast<u32>(utils::aligned_div(startOffset + size, edatHeader.block_size));
|
||||
const u64 bufSize = num_blocks*edatHeader.block_size;
|
||||
if (data_buf_size < (bufSize))
|
||||
if (!size)
|
||||
{
|
||||
data_buf.reset(new u8[bufSize]);
|
||||
data_buf_size = bufSize;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// find and decrypt block range covering pos + size
|
||||
const u32 starting_block = static_cast<u32>(pos / edatHeader.block_size);
|
||||
const u32 ending_block = std::min(starting_block + num_blocks, total_blocks);
|
||||
// Now we need to offset things to account for the actual 'range' requested
|
||||
const u64 startOffset = pos % edatHeader.block_size;
|
||||
|
||||
const u64 num_blocks = utils::aligned_div(startOffset + size, edatHeader.block_size);
|
||||
data_buf.resize(num_blocks * edatHeader.block_size);
|
||||
|
||||
// Find and decrypt block range covering pos + size
|
||||
const u32 starting_block = ::narrow<u32>(pos / edatHeader.block_size);
|
||||
const u32 ending_block = ::narrow<u32>(std::min<u64>(starting_block + num_blocks, total_blocks));
|
||||
u64 writeOffset = 0;
|
||||
|
||||
for (u32 i = starting_block; i < ending_block; ++i)
|
||||
{
|
||||
edata_file.seek(0);
|
||||
|
@ -984,6 +973,7 @@ u64 EDATADecrypter::ReadData(u64 pos, u8* data, u64 size)
|
|||
edat_log.error("Error Decrypting data");
|
||||
return 0;
|
||||
}
|
||||
|
||||
writeOffset += res;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,9 +16,21 @@ constexpr u32 EDAT_DEBUG_DATA_FLAG = 0x80000000;
|
|||
|
||||
struct loaded_npdrm_keys
|
||||
{
|
||||
atomic_t<u128> devKlic{};
|
||||
atomic_t<u128> rifKey{};
|
||||
atomic_t<u128> dec_keys[16]{};
|
||||
atomic_t<u64> dec_keys_pos = 0;
|
||||
atomic_t<u32> npdrm_fds{0};
|
||||
|
||||
void install_decryption_key(u128 key)
|
||||
{
|
||||
dec_keys_pos.atomic_op([&](u64& pos) { dec_keys[pos++ % std::size(dec_keys)] = key; });
|
||||
}
|
||||
|
||||
// TODO: Check if correct for ELF files usage
|
||||
u128 last_key() const
|
||||
{
|
||||
const usz pos = dec_keys_pos;
|
||||
return pos ? dec_keys[(pos - 1) % std::size(dec_keys)].load() : u128{};
|
||||
}
|
||||
};
|
||||
|
||||
struct NPD_HEADER
|
||||
|
@ -45,14 +57,14 @@ 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, 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 = nullptr, u32* license = nullptr);
|
||||
|
||||
u128 GetEdatRifKeyFromRapFile(const fs::file& rap_file);
|
||||
|
||||
struct EDATADecrypter final : fs::file_base
|
||||
{
|
||||
// file stream
|
||||
const fs::file edata_file;
|
||||
fs::file edata_file;
|
||||
u64 file_size{0};
|
||||
u32 total_blocks{0};
|
||||
u64 pos{0};
|
||||
|
@ -61,25 +73,17 @@ struct EDATADecrypter final : fs::file_base
|
|||
EDAT_HEADER edatHeader{};
|
||||
|
||||
// Internal data buffers.
|
||||
std::unique_ptr<u8[]> data_buf{};
|
||||
u64 data_buf_size{0};
|
||||
std::vector<u8> data_buf{};
|
||||
|
||||
u128 dec_key{};
|
||||
|
||||
// edat usage
|
||||
u128 rif_key{};
|
||||
u128 dev_key{};
|
||||
public:
|
||||
// SdataByFd usage
|
||||
EDATADecrypter(fs::file&& input)
|
||||
: edata_file(std::move(input)) {}
|
||||
// Edat usage
|
||||
EDATADecrypter(fs::file&& input, const u128& dev_key, const u128& rif_key)
|
||||
EDATADecrypter(fs::file&& input, u128 dec_key = {})
|
||||
: edata_file(std::move(input))
|
||||
, rif_key(rif_key)
|
||||
, dev_key(dev_key) {}
|
||||
, dec_key(dec_key)
|
||||
{
|
||||
}
|
||||
|
||||
~EDATADecrypter() override {}
|
||||
// false if invalid
|
||||
bool ReadHeader();
|
||||
u64 ReadData(u64 pos, u8* data, u64 size);
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "cellSysutil.h"
|
||||
|
||||
#include "Emu/Cell/lv2/sys_time.h"
|
||||
#include "Emu/Cell/lv2/sys_fs.h"
|
||||
#include "Emu/NP/np_handler.h"
|
||||
#include "Emu/NP/np_contexts.h"
|
||||
|
||||
|
@ -476,16 +477,14 @@ error_code npDrmIsAvailable(vm::cptr<u8> k_licensee_addr, vm::cptr<char> drm_pat
|
|||
|
||||
sceNp.warning(u8"npDrmIsAvailable(): drm_path=“%s”", enc_drm_path);
|
||||
|
||||
if (!fs::is_file(vfs::get(enc_drm_path)))
|
||||
{
|
||||
sceNp.warning(u8"npDrmIsAvailable(): “%s” not found", enc_drm_path);
|
||||
return CELL_ENOENT;
|
||||
}
|
||||
|
||||
auto& npdrmkeys = g_fxo->get<loaded_npdrm_keys>();
|
||||
|
||||
const std::string& enc_drm_path_local = vfs::get(enc_drm_path);
|
||||
const fs::file enc_file(enc_drm_path_local);
|
||||
const auto [fs_error, ppath, real_path, enc_file, type] = lv2_file::open(enc_drm_path, 0, 0);
|
||||
|
||||
if (!fs_error)
|
||||
{
|
||||
return {fs_error, enc_drm_path};
|
||||
}
|
||||
|
||||
u32 magic;
|
||||
|
||||
|
@ -499,12 +498,12 @@ error_code npDrmIsAvailable(vm::cptr<u8> k_licensee_addr, vm::cptr<char> drm_pat
|
|||
|
||||
if (verify_npdrm_self_headers(enc_file, reinterpret_cast<u8*>(&k_licensee)))
|
||||
{
|
||||
npdrmkeys.devKlic = k_licensee;
|
||||
npdrmkeys.install_decryption_key(k_licensee);
|
||||
}
|
||||
else
|
||||
{
|
||||
sceNp.error(u8"npDrmIsAvailable(): Failed to verify sce file “%s”", enc_drm_path);
|
||||
return SCE_NP_DRM_ERROR_NO_ENTITLEMENT;
|
||||
return {SCE_NP_DRM_ERROR_NO_ENTITLEMENT, enc_drm_path};
|
||||
}
|
||||
}
|
||||
else if (magic == "NPD\0"_u32)
|
||||
|
@ -512,21 +511,34 @@ error_code npDrmIsAvailable(vm::cptr<u8> k_licensee_addr, vm::cptr<char> drm_pat
|
|||
// edata / sdata files
|
||||
|
||||
std::string contentID;
|
||||
u32 license_type = 0;
|
||||
|
||||
if (VerifyEDATHeaderWithKLicense(enc_file, enc_drm_path_local, reinterpret_cast<u8*>(&k_licensee), &contentID))
|
||||
if (VerifyEDATHeaderWithKLicense(enc_file, enc_drm_path, reinterpret_cast<u8*>(&k_licensee), &contentID, &license_type))
|
||||
{
|
||||
const std::string rap_file = rpcs3::utils::get_rap_file_path(contentID);
|
||||
npdrmkeys.devKlic = k_licensee;
|
||||
|
||||
if (fs::is_file(rap_file))
|
||||
npdrmkeys.rifKey = GetEdatRifKeyFromRapFile(fs::file{rap_file});
|
||||
// Check if RAP-free
|
||||
if (license_type == 3)
|
||||
{
|
||||
npdrmkeys.install_decryption_key(k_licensee);
|
||||
}
|
||||
else
|
||||
sceNp.warning(u8"npDrmIsAvailable(): Rap file not found: “%s”", rap_file.c_str());
|
||||
{
|
||||
const std::string rap_file = rpcs3::utils::get_rap_file_path(contentID);
|
||||
|
||||
if (fs::file rap_fd{rap_file}; rap_fd && rap_fd.size() >= sizeof(u128))
|
||||
{
|
||||
npdrmkeys.install_decryption_key(GetEdatRifKeyFromRapFile(rap_fd));
|
||||
}
|
||||
else
|
||||
{
|
||||
sceNp.error(u8"npDrmIsAvailable(): Rap file not found: “%s”", rap_file);
|
||||
return {SCE_NP_DRM_ERROR_LICENSE_NOT_FOUND, enc_drm_path};
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sceNp.error(u8"npDrmIsAvailable(): Failed to verify npd file “%s”", enc_drm_path);
|
||||
return SCE_NP_DRM_ERROR_NO_ENTITLEMENT;
|
||||
return {SCE_NP_DRM_ERROR_NO_ENTITLEMENT, enc_drm_path};
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -534,6 +546,7 @@ error_code npDrmIsAvailable(vm::cptr<u8> k_licensee_addr, vm::cptr<char> drm_pat
|
|||
// for now assume its just unencrypted
|
||||
sceNp.notice(u8"npDrmIsAvailable(): Assuming npdrm file is unencrypted at “%s”", enc_drm_path);
|
||||
}
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -516,13 +516,32 @@ lv2_file::open_raw_result_t lv2_file::open_raw(const std::string& local_path, s3
|
|||
if (magic == "NPD\0"_u32)
|
||||
{
|
||||
auto& edatkeys = g_fxo->get<loaded_npdrm_keys>();
|
||||
auto sdata_file = std::make_unique<EDATADecrypter>(std::move(file), edatkeys.devKlic.load(), edatkeys.rifKey.load());
|
||||
if (!sdata_file->ReadHeader())
|
||||
{
|
||||
return {CELL_EFSSPECIFIC};
|
||||
}
|
||||
|
||||
file.reset(std::move(sdata_file));
|
||||
const u64 init_pos = edatkeys.dec_keys_pos;
|
||||
const auto& dec_keys = edatkeys.dec_keys;
|
||||
const u64 max_i = std::min<u64>(std::size(dec_keys), init_pos);
|
||||
|
||||
for (u64 i = 0;; i++)
|
||||
{
|
||||
if (i == max_i)
|
||||
{
|
||||
// Run out of keys to try
|
||||
return {CELL_EFSSPECIFIC};
|
||||
}
|
||||
|
||||
// Try all registered keys
|
||||
auto edata_file = std::make_unique<EDATADecrypter>(std::move(file), dec_keys[(init_pos - i - 1) % std::size(dec_keys)].load());
|
||||
if (!edata_file->ReadHeader())
|
||||
{
|
||||
// Prepare file for the next iteration
|
||||
file = std::move(edata_file->edata_file);
|
||||
continue;
|
||||
}
|
||||
|
||||
file.reset(std::move(edata_file));
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -677,7 +696,15 @@ error_code sys_fs_read(ppu_thread& ppu, u32 fd, vm::ptr<void> buf, u64 nbytes, v
|
|||
return CELL_EIO;
|
||||
}
|
||||
|
||||
*nread = file->op_read(buf, nbytes);
|
||||
const u64 read_bytes = file->op_read(buf, nbytes);
|
||||
|
||||
*nread = read_bytes;
|
||||
|
||||
if (!read_bytes && file->file.pos() < file->file.size())
|
||||
{
|
||||
// EDATA corruption perhaps
|
||||
return CELL_EFSSPECIFIC;
|
||||
}
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
@ -1466,6 +1493,8 @@ error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr<void> _arg, u32
|
|||
|
||||
ensure(old_pos == file->file.seek(old_pos));
|
||||
|
||||
// TODO: EDATA corruption detection
|
||||
|
||||
arg->out_code = CELL_OK;
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ static error_code overlay_load_module(vm::ptr<u32> ovlmid, const std::string& vp
|
|||
src = std::move(lv2_file);
|
||||
}
|
||||
|
||||
u128 klic = g_fxo->get<loaded_npdrm_keys>().devKlic.load();
|
||||
u128 klic = g_fxo->get<loaded_npdrm_keys>().last_key();
|
||||
|
||||
ppu_exec_object obj = decrypt_self(std::move(src), reinterpret_cast<u8*>(&klic));
|
||||
|
||||
|
|
|
@ -412,7 +412,7 @@ void _sys_process_exit2(ppu_thread& ppu, s32 status, vm::ptr<sys_exit2_param> ar
|
|||
disc = vfs::get(Emu.GetDir());
|
||||
|
||||
Emu.CallAfter([path = std::move(path), argv = std::move(argv), envp = std::move(envp), data = std::move(data), disc = std::move(disc)
|
||||
, hdd1 = std::move(hdd1), klic = g_fxo->get<loaded_npdrm_keys>().devKlic.load(), old_config = Emu.GetUsedConfig()]() mutable
|
||||
, hdd1 = std::move(hdd1), klic = g_fxo->get<loaded_npdrm_keys>().last_key(), old_config = Emu.GetUsedConfig()]() mutable
|
||||
{
|
||||
sys_process.success("Process finished -> %s", argv[0]);
|
||||
Emu.SetForceBoot(true);
|
||||
|
|
|
@ -266,7 +266,7 @@ static error_code prx_load_module(const std::string& vpath, u64 flags, vm::ptr<s
|
|||
src = std::move(lv2_file);
|
||||
}
|
||||
|
||||
u128 klic = g_fxo->get<loaded_npdrm_keys>().devKlic.load();
|
||||
u128 klic = g_fxo->get<loaded_npdrm_keys>().last_key();
|
||||
|
||||
ppu_prx_object obj = decrypt_self(std::move(src), reinterpret_cast<u8*>(&klic));
|
||||
|
||||
|
|
|
@ -332,7 +332,7 @@ error_code sys_spu_image_open(ppu_thread& ppu, vm::ptr<sys_spu_image> img, vm::c
|
|||
return {fs_error, path};
|
||||
}
|
||||
|
||||
u128 klic = g_fxo->get<loaded_npdrm_keys>().devKlic.load();
|
||||
u128 klic = g_fxo->get<loaded_npdrm_keys>().last_key();
|
||||
|
||||
const fs::file elf_file = decrypt_self(std::move(file), reinterpret_cast<u8*>(&klic));
|
||||
|
||||
|
|
|
@ -1287,7 +1287,7 @@ void main_window::DecryptSPRXLibraries()
|
|||
if (const auto keys = g_fxo->try_get<loaded_npdrm_keys>())
|
||||
{
|
||||
// Second klic: get it from a running game
|
||||
if (const u128 klic = keys->devKlic)
|
||||
if (const u128 klic = keys->last_key())
|
||||
{
|
||||
klics.emplace_back(klic);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue