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:
Eladash 2021-10-15 12:11:16 +03:00 committed by Ivan
parent 933d96af5f
commit c49ebba648
9 changed files with 141 additions and 105 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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