mirror of
https://github.com/RPCSX/rpcsx.git
synced 2026-04-04 14:08:37 +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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue