Mself / Sdata: on the fly partial decoding support (#2468)

This commit is contained in:
Jake 2017-03-06 18:59:05 -06:00 committed by raven02
parent 039e295e53
commit 87fe93ee9a
8 changed files with 616 additions and 459 deletions

View file

@ -140,8 +140,7 @@ int decompress(unsigned char *out, unsigned char *in, unsigned int size)
unsigned int range = 0xFFFFFFFF; unsigned int range = 0xFFFFFFFF;
unsigned int code = (in[1] << 24) | (in[2] << 16) | (in[3] << 8) | in[4]; unsigned int code = (in[1] << 24) | (in[2] << 16) | (in[3] << 8) | in[4];
// TODO:: Syphurith: There was a check against the unsigned char head. if (head < 0) would always be false.. I don't know are you tried to if (head > 0x80)? if (head > 0x80) // Check if we have a valid starting byte.
if (head < 0) // Check if we have a valid starting byte.
{ {
// The dictionary header is invalid, the data is not compressed. // The dictionary header is invalid, the data is not compressed.
result = -1; result = -1;

View file

@ -2,6 +2,8 @@
#include "key_vault.h" #include "key_vault.h"
#include "unedat.h" #include "unedat.h"
#include <cmath>
void generate_key(int crypto_mode, int version, unsigned char *key_final, unsigned char *iv_final, unsigned char *key, unsigned char *iv) void generate_key(int crypto_mode, int version, unsigned char *key_final, unsigned char *iv_final, unsigned char *key, unsigned char *iv)
{ {
int mode = (int)(crypto_mode & 0xF0000000); int mode = (int)(crypto_mode & 0xF0000000);
@ -100,9 +102,9 @@ bool decrypt(int hash_mode, int crypto_mode, int version, unsigned char *in, uns
} }
// EDAT/SDAT functions. // EDAT/SDAT functions.
unsigned char* dec_section(unsigned char* metadata) std::tuple<u64, s32, s32> dec_section(unsigned char* metadata)
{ {
unsigned char *dec = new unsigned char[0x10]; std::array<u8, 0x10> dec;
dec[0x00] = (metadata[0xC] ^ metadata[0x8] ^ metadata[0x10]); dec[0x00] = (metadata[0xC] ^ metadata[0x8] ^ metadata[0x10]);
dec[0x01] = (metadata[0xD] ^ metadata[0x9] ^ metadata[0x11]); dec[0x01] = (metadata[0xD] ^ metadata[0x9] ^ metadata[0x11]);
dec[0x02] = (metadata[0xE] ^ metadata[0xA] ^ metadata[0x12]); dec[0x02] = (metadata[0xE] ^ metadata[0xA] ^ metadata[0x12]);
@ -119,58 +121,55 @@ unsigned char* dec_section(unsigned char* metadata)
dec[0x0D] = (metadata[0x5] ^ metadata[0x1] ^ metadata[0x1D]); dec[0x0D] = (metadata[0x5] ^ metadata[0x1] ^ metadata[0x1D]);
dec[0x0E] = (metadata[0x6] ^ metadata[0x2] ^ metadata[0x1E]); dec[0x0E] = (metadata[0x6] ^ metadata[0x2] ^ metadata[0x1E]);
dec[0x0F] = (metadata[0x7] ^ metadata[0x3] ^ metadata[0x1F]); dec[0x0F] = (metadata[0x7] ^ metadata[0x3] ^ metadata[0x1F]);
return dec;
u64 offset = swap64(*(u64*)&dec[0]);
s32 length = swap32(*(s32*)&dec[8]);
s32 compression_end = swap32(*(s32*)&dec[12]);
return std::make_tuple(offset, length, compression_end);
} }
unsigned char* get_block_key(int block, NPD_HEADER *npd) std::array<u8, 0x10> get_block_key(int block, NPD_HEADER *npd)
{ {
unsigned char empty_key[0x10] = {}; unsigned char empty_key[0x10] = {};
unsigned char *src_key = (npd->version <= 1) ? empty_key : npd->dev_hash; unsigned char *src_key = (npd->version <= 1) ? empty_key : npd->dev_hash;
unsigned char *dest_key = new unsigned char[0x10]; std::array<u8, 0x10> dest_key{};
memcpy(dest_key, src_key, 0xC); memcpy(dest_key.data(), src_key, 0xC);
dest_key[0xC] = (block >> 24 & 0xFF);
dest_key[0xD] = (block >> 16 & 0xFF); s32 swappedBlock = swap32(block);
dest_key[0xE] = (block >> 8 & 0xFF); memcpy(&dest_key[0xC], &swappedBlock, sizeof(swappedBlock));
dest_key[0xF] = (block & 0xFF);
return dest_key; return dest_key;
} }
// EDAT/SDAT decryption. // for out data, allocate a buffer the size of 'edat->block_size'
int decrypt_data(const fs::file* in, const fs::file* out, EDAT_HEADER *edat, NPD_HEADER *npd, unsigned char* crypt_key, bool verbose) // returns number of bytes written, -1 for error
s64 decrypt_block(const fs::file* in, u8* out, EDAT_HEADER *edat, NPD_HEADER *npd, u8* crypt_key, u32 block_num, u32 total_blocks, u64 size_left)
{ {
// Get metadata info and setup buffers. // Get metadata info and setup buffers.
int block_num = (int)((edat->file_size + edat->block_size - 1) / edat->block_size); const int metadata_section_size = ((edat->flags & EDAT_COMPRESSED_FLAG) != 0 || (edat->flags & EDAT_FLAG_0x20) != 0) ? 0x20 : 0x10;
int metadata_section_size = ((edat->flags & EDAT_COMPRESSED_FLAG) != 0 || (edat->flags & EDAT_FLAG_0x20) != 0) ? 0x20 : 0x10; const int metadata_offset = 0x100;
int metadata_offset = 0x100;
unsigned char *enc_data; std::unique_ptr<u8> enc_data;
unsigned char *dec_data; std::unique_ptr<u8> dec_data;
unsigned char *b_key; u8 hash[0x10] = { 0 };
unsigned char *iv; u8 key_result[0x10] = { 0 };
u8 hash_result[0x14] = { 0 };
unsigned char hash[0x10]; u64 offset = 0;
unsigned char key_result[0x10]; u64 metadata_sec_offset = 0;
unsigned char hash_result[0x14]; s32 length = 0;
memset(hash, 0, 0x10); s32 compression_end = 0;
memset(key_result, 0, 0x10);
memset(hash_result, 0, 0x14);
unsigned long long offset = 0;
unsigned long long metadata_sec_offset = 0;
int length = 0;
int compression_end = 0;
unsigned char empty_iv[0x10] = {}; unsigned char empty_iv[0x10] = {};
// Decrypt the metadata. const u64 file_offset = in->pos();
for (int i = 0; i < block_num; i++)
{
memset(hash_result, 0, 0x14); memset(hash_result, 0, 0x14);
// Decrypt the metadata.
if ((edat->flags & EDAT_COMPRESSED_FLAG) != 0) if ((edat->flags & EDAT_COMPRESSED_FLAG) != 0)
{ {
metadata_sec_offset = metadata_offset + (unsigned long long) i * metadata_section_size; metadata_sec_offset = metadata_offset + (unsigned long long) block_num * metadata_section_size;
in->seek(metadata_sec_offset); in->seek(file_offset + metadata_sec_offset);
unsigned char metadata[0x20]; unsigned char metadata[0x20];
memset(metadata, 0, 0x20); memset(metadata, 0, 0x20);
@ -186,11 +185,7 @@ int decrypt_data(const fs::file* in, const fs::file* out, EDAT_HEADER *edat, NPD
} }
else else
{ {
unsigned char *result = dec_section(metadata); std::tie(offset, length, compression_end) = dec_section(metadata);
offset = swap64(*(unsigned long long*)&result[0]);
length = swap32(*(int*)&result[8]);
compression_end = swap32(*(int*)&result[12]);
delete[] result;
} }
memcpy(hash_result, metadata, 0x10); memcpy(hash_result, metadata, 0x10);
@ -198,8 +193,8 @@ int decrypt_data(const fs::file* in, const fs::file* out, EDAT_HEADER *edat, NPD
else if ((edat->flags & EDAT_FLAG_0x20) != 0) else if ((edat->flags & EDAT_FLAG_0x20) != 0)
{ {
// If FLAG 0x20, the metadata precedes each data block. // If FLAG 0x20, the metadata precedes each data block.
metadata_sec_offset = metadata_offset + (unsigned long long) i * (metadata_section_size + length); metadata_sec_offset = metadata_offset + (u64) block_num * (metadata_section_size + length);
in->seek(metadata_sec_offset); in->seek(file_offset + metadata_sec_offset);
unsigned char metadata[0x20]; unsigned char metadata[0x20];
memset(metadata, 0, 0x20); memset(metadata, 0, 0x20);
@ -207,49 +202,46 @@ int decrypt_data(const fs::file* in, const fs::file* out, EDAT_HEADER *edat, NPD
memcpy(hash_result, metadata, 0x14); memcpy(hash_result, metadata, 0x14);
// If FLAG 0x20 is set, apply custom xor. // If FLAG 0x20 is set, apply custom xor.
int j; for (int j = 0; j < 0x10; j++)
for (j = 0; j < 0x10; j++)
hash_result[j] = (unsigned char)(metadata[j] ^ metadata[j + 0x10]); hash_result[j] = (unsigned char)(metadata[j] ^ metadata[j + 0x10]);
offset = metadata_sec_offset + 0x20; offset = metadata_sec_offset + 0x20;
length = edat->block_size; length = edat->block_size;
if ((i == (block_num - 1)) && (edat->file_size % edat->block_size)) if ((block_num == (total_blocks - 1)) && (edat->file_size % edat->block_size))
length = (int)(edat->file_size % edat->block_size); length = (int)(edat->file_size % edat->block_size);
} }
else else
{ {
metadata_sec_offset = metadata_offset + (unsigned long long) i * metadata_section_size; metadata_sec_offset = metadata_offset + (u64) block_num * metadata_section_size;
in->seek(metadata_sec_offset); in->seek(file_offset + metadata_sec_offset);
in->read(hash_result, 0x10); in->read(hash_result, 0x10);
offset = metadata_offset + (unsigned long long) i * edat->block_size + (unsigned long long) block_num * metadata_section_size; offset = metadata_offset + (u64) block_num * edat->block_size + (u64) block_num * metadata_section_size;
length = edat->block_size; length = edat->block_size;
if ((i == (block_num - 1)) && (edat->file_size % edat->block_size)) if ((block_num == (total_blocks - 1)) && (edat->file_size % edat->block_size))
length = (int)(edat->file_size % edat->block_size); length = (int)(edat->file_size % edat->block_size);
} }
// Locate the real data. // Locate the real data.
int pad_length = length; const int pad_length = length;
length = (int)((pad_length + 0xF) & 0xFFFFFFF0); length = (int)((pad_length + 0xF) & 0xFFFFFFF0);
// Setup buffers for decryption and read the data. // Setup buffers for decryption and read the data.
enc_data = new unsigned char[length]; enc_data.reset(new u8[length]{ 0 });
dec_data = new unsigned char[length]; dec_data.reset(new u8[length]{ 0 });
memset(enc_data, 0, length);
memset(dec_data, 0, length);
memset(hash, 0, 0x10); memset(hash, 0, 0x10);
memset(key_result, 0, 0x10); memset(key_result, 0, 0x10);
in->seek(offset); in->seek(file_offset + offset);
in->read(enc_data, length); in->read(enc_data.get(), length);
// Generate a key for the current block. // Generate a key for the current block.
b_key = get_block_key(i, npd); std::array<u8, 0x10> b_key = get_block_key(block_num, npd);
// Encrypt the block key with the crypto key. // Encrypt the block key with the crypto key.
aesecb128_encrypt(crypt_key, b_key, key_result); aesecb128_encrypt(crypt_key, b_key.data(), key_result);
if ((edat->flags & EDAT_FLAG_0x10) != 0) if ((edat->flags & EDAT_FLAG_0x10) != 0)
aesecb128_encrypt(crypt_key, key_result, hash); // If FLAG 0x10 is set, encrypt again to get the final hash. aesecb128_encrypt(crypt_key, key_result, hash); // If FLAG 0x10 is set, encrypt again to get the final hash.
else else
@ -278,83 +270,78 @@ int decrypt_data(const fs::file* in, const fs::file* out, EDAT_HEADER *edat, NPD
crypto_mode |= 0x01000000; crypto_mode |= 0x01000000;
hash_mode |= 0x01000000; hash_mode |= 0x01000000;
// Simply copy the data without the header or the footer. // Simply copy the data without the header or the footer.
memcpy(dec_data, enc_data, length); memcpy(dec_data.get(), enc_data.get(), length);
} }
else else
{ {
// IV is null if NPD version is 1 or 0. // IV is null if NPD version is 1 or 0.
iv = (npd->version <= 1) ? empty_iv : npd->digest; u8* iv = (npd->version <= 1) ? empty_iv : npd->digest;
// Call main crypto routine on this data block. // Call main crypto routine on this data block.
if (!decrypt(hash_mode, crypto_mode, (npd->version == 4), enc_data, dec_data, length, key_result, iv, hash, hash_result)) if (!decrypt(hash_mode, crypto_mode, (npd->version == 4), enc_data.get(), dec_data.get(), length, key_result, iv, hash, hash_result))
{ {
if (verbose) LOG_ERROR(LOADER, "EDAT: Block at offset 0x%llx has invalid hash!", (u64)offset);
LOG_WARNING(LOADER, "EDAT: Block at offset 0x%llx has invalid hash!", (u64)offset);
delete[] enc_data;
delete[] dec_data;
delete[] b_key;
return 1; return 1;
} }
} }
// Apply additional compression if needed and write the decrypted data. // Apply additional de-compression if needed and write the decrypted data.
if (((edat->flags & EDAT_COMPRESSED_FLAG) != 0) && compression_end) if (((edat->flags & EDAT_COMPRESSED_FLAG) != 0) && compression_end)
{ {
int decomp_size = (int)edat->file_size; const int res = decompress(out, dec_data.get(), edat->block_size);
unsigned char *decomp_data = new unsigned char[decomp_size];
memset(decomp_data, 0, decomp_size);
if (verbose) size_left -= res;
LOG_NOTICE(LOADER, "EDAT: Decompressing data...");
int res = decompress(decomp_data, dec_data, decomp_size); if (size_left == 0)
out->write(decomp_data, res);
if (verbose)
{
LOG_NOTICE(LOADER, "EDAT: Compressed block size: %d", pad_length);
LOG_NOTICE(LOADER, "EDAT: Decompressed block size: %d", res);
}
edat->file_size -= res;
if (edat->file_size == 0)
{ {
if (res < 0) if (res < 0)
{ {
LOG_ERROR(LOADER, "EDAT: Decompression failed!"); LOG_ERROR(LOADER, "EDAT: Decompression failed!");
return 1; return 1;
} }
else
LOG_NOTICE(LOADER, "EDAT: Successfully decompressed!");
} }
return res;
delete[] decomp_data;
} }
else else
{ {
out->write(dec_data, pad_length); memcpy(out, dec_data.get(), pad_length);
return pad_length;
} }
}
delete[] enc_data; // EDAT/SDAT decryption.
delete[] dec_data; // reset file to beginning of data before calling
delete[] b_key; int decrypt_data(const fs::file* in, const fs::file* out, EDAT_HEADER *edat, NPD_HEADER *npd, unsigned char* crypt_key, bool verbose)
{
const int total_blocks = (int)((edat->file_size + edat->block_size - 1) / edat->block_size);
u64 size_left = (int)edat->file_size;
std::unique_ptr<u8> data(new u8[edat->block_size]);
for (int i = 0; i < total_blocks; i++)
{
in->seek(0);
memset(data.get(), 0, edat->block_size);
u64 res = decrypt_block(in, data.get(), edat, npd, crypt_key, i, total_blocks, size_left);
if (res == -1)
{
LOG_ERROR(LOADER, "EDAT: Decrypt Block failed!");
return 1;
}
size_left -= res;
out->write(data.get(), res);
} }
return 0; return 0;
} }
// set file offset to beginning before calling
int check_data(unsigned char *key, EDAT_HEADER *edat, NPD_HEADER *npd, const fs::file* f, bool verbose) int check_data(unsigned char *key, EDAT_HEADER *edat, NPD_HEADER *npd, const fs::file* f, bool verbose)
{ {
f->seek(0); u8 header[0xA0] = { 0 };
unsigned char header[0xA0]; u8 empty_header[0xA0] = { 0 };
unsigned char empty_header[0xA0]; u8 header_hash[0x10] = { 0 };
unsigned char header_hash[0x10]; u8 metadata_hash[0x10] = { 0 };
unsigned char metadata_hash[0x10];
memset(header, 0, 0xA0); const u64 file_offset = f->pos();
memset(empty_header, 0, 0xA0);
memset(header_hash, 0, 0x10);
memset(metadata_hash, 0, 0x10);
// Check NPD version and flags. // Check NPD version and flags.
if ((npd->version == 0) || (npd->version == 1)) if ((npd->version == 0) || (npd->version == 1))
@ -391,12 +378,12 @@ int check_data(unsigned char *key, EDAT_HEADER *edat, NPD_HEADER *npd, const fs:
f->read(header, 0xA0); f->read(header, 0xA0);
// Read in the header and metadata section hashes. // Read in the header and metadata section hashes.
f->seek(0x90); f->seek(file_offset + 0x90);
f->read(metadata_hash, 0x10); f->read(metadata_hash, 0x10);
f->read(header_hash, 0x10); f->read(header_hash, 0x10);
// Setup the hashing mode and the crypto mode used in the file. // Setup the hashing mode and the crypto mode used in the file.
int crypto_mode = 0x1; const int crypto_mode = 0x1;
int hash_mode = ((edat->flags & EDAT_ENCRYPTED_KEY_FLAG) == 0) ? 0x00000002 : 0x10000002; int hash_mode = ((edat->flags & EDAT_ENCRYPTED_KEY_FLAG) == 0) ? 0x00000002 : 0x10000002;
if ((edat->flags & EDAT_DEBUG_DATA_FLAG) != 0) if ((edat->flags & EDAT_DEBUG_DATA_FLAG) != 0)
{ {
@ -407,10 +394,8 @@ int check_data(unsigned char *key, EDAT_HEADER *edat, NPD_HEADER *npd, const fs:
} }
// Setup header key and iv buffers. // Setup header key and iv buffers.
unsigned char header_key[0x10]; unsigned char header_key[0x10] = { 0 };
unsigned char header_iv[0x10]; unsigned char header_iv[0x10] = { 0 };
memset(header_key, 0, 0x10);
memset(header_iv, 0, 0x10);
// Test the header hash (located at offset 0xA0). // Test the header hash (located at offset 0xA0).
if (!decrypt(hash_mode, crypto_mode, (npd->version == 4), header, empty_header, 0xA0, header_key, header_iv, key, header_hash)) if (!decrypt(hash_mode, crypto_mode, (npd->version == 4), header, empty_header, 0xA0, header_key, header_iv, key, header_hash))
@ -427,30 +412,30 @@ int check_data(unsigned char *key, EDAT_HEADER *edat, NPD_HEADER *npd, const fs:
} }
// Parse the metadata info. // Parse the metadata info.
int metadata_section_size = ((edat->flags & EDAT_COMPRESSED_FLAG) != 0 || (edat->flags & EDAT_FLAG_0x20) != 0) ? 0x20 : 0x10; const int metadata_section_size = ((edat->flags & EDAT_COMPRESSED_FLAG) != 0 || (edat->flags & EDAT_FLAG_0x20) != 0) ? 0x20 : 0x10;
if (((edat->flags & EDAT_COMPRESSED_FLAG) != 0)) if (((edat->flags & EDAT_COMPRESSED_FLAG) != 0))
{ {
if (verbose) if (verbose)
LOG_WARNING(LOADER, "EDAT: COMPRESSED data detected!"); LOG_WARNING(LOADER, "EDAT: COMPRESSED data detected!");
} }
int block_num = (int)((edat->file_size + edat->block_size - 1) / edat->block_size); const int block_num = (int)((edat->file_size + edat->block_size - 1) / edat->block_size);
int metadata_offset = 0x100; const int metadata_offset = 0x100;
int metadata_size = metadata_section_size * block_num; const int metadata_size = metadata_section_size * block_num;
long long metadata_section_offset = metadata_offset; u64 metadata_section_offset = metadata_offset;
long bytes_read = 0; long bytes_read = 0;
long bytes_to_read = metadata_size; long bytes_to_read = metadata_size;
unsigned char *metadata = new unsigned char[metadata_size]; std::unique_ptr<u8> metadata(new u8[metadata_size]);
unsigned char *empty_metadata = new unsigned char[metadata_size]; std::unique_ptr<u8> empty_metadata(new u8[metadata_size]);
while (bytes_to_read > 0) while (bytes_to_read > 0)
{ {
// Locate the metadata blocks. // Locate the metadata blocks.
f->seek(metadata_section_offset); f->seek(file_offset + metadata_section_offset);
// Read in the metadata. // Read in the metadata.
f->read(metadata + bytes_read, metadata_section_size); f->read(metadata.get() + bytes_read, metadata_section_size);
// Adjust sizes. // Adjust sizes.
bytes_read += metadata_section_size; bytes_read += metadata_section_size;
@ -463,7 +448,7 @@ int check_data(unsigned char *key, EDAT_HEADER *edat, NPD_HEADER *npd, const fs:
} }
// Test the metadata section hash (located at offset 0x90). // Test the metadata section hash (located at offset 0x90).
if (!decrypt(hash_mode, crypto_mode, (npd->version == 4), metadata, empty_metadata, metadata_size, header_key, header_iv, key, metadata_hash)) if (!decrypt(hash_mode, crypto_mode, (npd->version == 4), metadata.get(), empty_metadata.get(), metadata_size, header_key, header_iv, key, metadata_hash))
{ {
if (verbose) if (verbose)
LOG_WARNING(LOADER, "EDAT: Metadata section hash is invalid!"); LOG_WARNING(LOADER, "EDAT: Metadata section hash is invalid!");
@ -472,31 +457,21 @@ int check_data(unsigned char *key, EDAT_HEADER *edat, NPD_HEADER *npd, const fs:
// Checking ECDSA signatures. // Checking ECDSA signatures.
if ((edat->flags & EDAT_DEBUG_DATA_FLAG) == 0) if ((edat->flags & EDAT_DEBUG_DATA_FLAG) == 0)
{ {
LOG_NOTICE(LOADER, "EDAT: Checking signatures...");
// Setup buffers. // Setup buffers.
unsigned char metadata_signature[0x28]; unsigned char metadata_signature[0x28] = { 0 };
unsigned char header_signature[0x28]; unsigned char header_signature[0x28] = { 0 };
unsigned char signature_hash[20]; unsigned char signature_hash[20] = { 0 };
unsigned char signature_r[0x15]; unsigned char signature_r[0x15] = { 0 };
unsigned char signature_s[0x15]; unsigned char signature_s[0x15] = { 0 };
unsigned char zero_buf[0x15]; unsigned char zero_buf[0x15] = { 0 };
memset(metadata_signature, 0, 0x28);
memset(header_signature, 0, 0x28);
memset(signature_hash, 0, 20);
memset(signature_r, 0, 0x15);
memset(signature_s, 0, 0x15);
memset(zero_buf, 0, 0x15);
// Setup ECDSA curve and public key. // Setup ECDSA curve and public key.
ecdsa_set_curve(VSH_CURVE_P, VSH_CURVE_A, VSH_CURVE_B, VSH_CURVE_N, VSH_CURVE_GX, VSH_CURVE_GY); ecdsa_set_curve(VSH_CURVE_P, VSH_CURVE_A, VSH_CURVE_B, VSH_CURVE_N, VSH_CURVE_GX, VSH_CURVE_GY);
ecdsa_set_pub(VSH_PUB); ecdsa_set_pub(VSH_PUB);
// Read in the metadata and header signatures. // Read in the metadata and header signatures.
f->seek(0xB0); f->seek(file_offset + 0xB0);
f->read(metadata_signature, 0x28); f->read(metadata_signature, 0x28);
f->seek(0xD8);
f->read(header_signature, 0x28); f->read(header_signature, 0x28);
// Checking metadata signature. // Checking metadata signature.
@ -511,14 +486,13 @@ int check_data(unsigned char *key, EDAT_HEADER *edat, NPD_HEADER *npd, const fs:
if ((edat->flags & EDAT_FLAG_0x20) != 0) //Sony failed again, they used buffer from 0x100 with half size of real metadata. if ((edat->flags & EDAT_FLAG_0x20) != 0) //Sony failed again, they used buffer from 0x100 with half size of real metadata.
{ {
int metadata_buf_size = block_num * 0x10; int metadata_buf_size = block_num * 0x10;
unsigned char *metadata_buf = new unsigned char[metadata_buf_size]; std::unique_ptr<u8> metadata_buf(new u8[metadata_buf_size]);
f->seek(metadata_offset); f->seek(file_offset + metadata_offset);
f->read(metadata_buf, metadata_buf_size); f->read(metadata_buf.get(), metadata_buf_size);
sha1(metadata_buf, metadata_buf_size, signature_hash); sha1(metadata_buf.get(), metadata_buf_size, signature_hash);
delete[] metadata_buf;
} }
else else
sha1(metadata, metadata_size, signature_hash); sha1(metadata.get(), metadata_size, signature_hash);
if (!ecdsa_verify(signature_hash, signature_r, signature_s)) if (!ecdsa_verify(signature_hash, signature_r, signature_s))
{ {
@ -526,11 +500,8 @@ int check_data(unsigned char *key, EDAT_HEADER *edat, NPD_HEADER *npd, const fs:
if (((unsigned long long)edat->block_size * block_num) > 0x100000000) if (((unsigned long long)edat->block_size * block_num) > 0x100000000)
LOG_WARNING(LOADER, "EDAT: *Due to large file size, metadata signature status may be incorrect!"); LOG_WARNING(LOADER, "EDAT: *Due to large file size, metadata signature status may be incorrect!");
} }
else
LOG_NOTICE(LOADER, "EDAT: Metadata signature is valid!");
} }
// Checking header signature. // Checking header signature.
// Setup header signature r and s. // Setup header signature r and s.
memset(signature_r, 0, 0x15); memset(signature_r, 0, 0x15);
@ -544,23 +515,16 @@ int check_data(unsigned char *key, EDAT_HEADER *edat, NPD_HEADER *npd, const fs:
{ {
// Setup header signature hash. // Setup header signature hash.
memset(signature_hash, 0, 20); memset(signature_hash, 0, 20);
unsigned char *header_buf = new unsigned char[0xD8]; std::unique_ptr<u8> header_buf(new u8[0xD8]);
f->seek(0x00); f->seek(file_offset);
f->read(header_buf, 0xD8); f->read(header_buf.get(), 0xD8);
sha1(header_buf, 0xD8, signature_hash ); sha1(header_buf.get(), 0xD8, signature_hash );
delete[] header_buf;
if (ecdsa_verify(signature_hash, signature_r, signature_s)) if (!ecdsa_verify(signature_hash, signature_r, signature_s))
LOG_NOTICE(LOADER, "EDAT: Header signature is valid!");
else
LOG_WARNING(LOADER, "EDAT: Header signature is invalid!"); LOG_WARNING(LOADER, "EDAT: Header signature is invalid!");
} }
} }
// Cleanup.
delete[] metadata;
delete[] empty_metadata;
return 0; return 0;
} }
@ -569,16 +533,14 @@ int validate_npd_hashes(const char* file_name, unsigned char *klicensee, NPD_HEA
int title_hash_result = 0; int title_hash_result = 0;
int dev_hash_result = 0; int dev_hash_result = 0;
int file_name_length = (int) strlen(file_name); const int file_name_length = (int) strlen(file_name);
unsigned char *buf = new unsigned char[0x30 + file_name_length]; std::unique_ptr<u8> buf(new u8[0x30 + file_name_length]);
unsigned char dev[0x60]; unsigned char dev[0x60] = { 0 };
unsigned char key[0x10]; unsigned char key[0x10] = { 0 };
memset(dev, 0, 0x60);
memset(key, 0, 0x10);
// Build the title buffer (content_id + file_name). // Build the title buffer (content_id + file_name).
memcpy(buf, npd->content_id, 0x30); memcpy(buf.get(), npd->content_id, 0x30);
memcpy(buf + 0x30, file_name, file_name_length); memcpy(buf.get() + 0x30, file_name, file_name_length);
// Build the dev buffer (first 0x60 bytes of NPD header in big-endian). // Build the dev buffer (first 0x60 bytes of NPD header in big-endian).
memcpy(dev, npd, 0x60); memcpy(dev, npd, 0x60);
@ -592,7 +554,7 @@ int validate_npd_hashes(const char* file_name, unsigned char *klicensee, NPD_HEA
memcpy(dev + 0xC, &type, 4); memcpy(dev + 0xC, &type, 4);
// Hash with NPDRM_OMAC_KEY_3 and compare with title_hash. // Hash with NPDRM_OMAC_KEY_3 and compare with title_hash.
title_hash_result = cmac_hash_compare(NP_OMAC_KEY_3, 0x10, buf, 0x30 + file_name_length, npd->title_hash, 0x10); title_hash_result = cmac_hash_compare(NP_OMAC_KEY_3, 0x10, buf.get(), 0x30 + file_name_length, npd->title_hash, 0x10);
if (verbose) if (verbose)
{ {
@ -624,7 +586,7 @@ int validate_npd_hashes(const char* file_name, unsigned char *klicensee, NPD_HEA
else else
{ {
// Generate klicensee xor key. // Generate klicensee xor key.
xor_key(key, klicensee, NP_OMAC_KEY_2, 0x10); xor_key(key, klicensee, NP_OMAC_KEY_2);
// Hash with generated key and compare with dev_hash. // Hash with generated key and compare with dev_hash.
dev_hash_result = cmac_hash_compare(key, 0x10, dev, 0x60, npd->dev_hash, 0x10); dev_hash_result = cmac_hash_compare(key, 0x10, dev, 0x60, npd->dev_hash, 0x10);
@ -638,102 +600,100 @@ int validate_npd_hashes(const char* file_name, unsigned char *klicensee, NPD_HEA
} }
} }
delete[] buf;
return (title_hash_result && dev_hash_result); return (title_hash_result && dev_hash_result);
} }
bool extract_data(const fs::file* input, const fs::file* output, const char* input_file_name, unsigned char* devklic, unsigned char* rifkey, bool verbose) void read_npd_edat_header(const fs::file* input, NPD_HEADER& NPD, EDAT_HEADER& EDAT)
{ {
// Setup NPD and EDAT/SDAT structs.
NPD_HEADER *NPD = new NPD_HEADER();
EDAT_HEADER *EDAT = new EDAT_HEADER();
// Read in the NPD and EDAT/SDAT headers.
char npd_header[0x80]; char npd_header[0x80];
char edat_header[0x10]; char edat_header[0x10];
input->read(npd_header, sizeof(npd_header)); input->read(npd_header, sizeof(npd_header));
input->read(edat_header, sizeof(edat_header)); input->read(edat_header, sizeof(edat_header));
memcpy(NPD->magic, npd_header, 4); memcpy(&NPD.magic, npd_header, 4);
NPD->version = swap32(*(int*)&npd_header[4]); NPD.version = swap32(*(int*)&npd_header[4]);
NPD->license = swap32(*(int*)&npd_header[8]); NPD.license = swap32(*(int*)&npd_header[8]);
NPD->type = swap32(*(int*)&npd_header[12]); NPD.type = swap32(*(int*)&npd_header[12]);
memcpy(NPD->content_id, (unsigned char*)&npd_header[16], 0x30); memcpy(NPD.content_id, (unsigned char*)&npd_header[16], 0x30);
memcpy(NPD->digest, (unsigned char*)&npd_header[64], 0x10); memcpy(NPD.digest, (unsigned char*)&npd_header[64], 0x10);
memcpy(NPD->title_hash, (unsigned char*)&npd_header[80], 0x10); memcpy(NPD.title_hash, (unsigned char*)&npd_header[80], 0x10);
memcpy(NPD->dev_hash, (unsigned char*)&npd_header[96], 0x10); memcpy(NPD.dev_hash, (unsigned char*)&npd_header[96], 0x10);
NPD->unk1 = swap64(*(u64*)&npd_header[112]); NPD.unk1 = swap64(*(u64*)&npd_header[112]);
NPD->unk2 = swap64(*(u64*)&npd_header[120]); NPD.unk2 = swap64(*(u64*)&npd_header[120]);
EDAT.flags = swap32(*(int*)&edat_header[0]);
EDAT.block_size = swap32(*(int*)&edat_header[4]);
EDAT.file_size = swap64(*(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)
{
// Setup NPD and EDAT/SDAT structs.
NPD_HEADER NPD;
EDAT_HEADER EDAT;
// 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 unsigned char npd_magic[4] = {0x4E, 0x50, 0x44, 0x00}; //NPD0
if (memcmp(NPD->magic, npd_magic, 4)) if (memcmp(&NPD.magic, npd_magic, 4))
{ {
LOG_ERROR(LOADER, "EDAT: %s has invalid NPD header or already decrypted.", input_file_name); LOG_ERROR(LOADER, "EDAT: %s has invalid NPD header or already decrypted.", input_file_name);
delete NPD;
delete EDAT;
return 1; return 1;
} }
EDAT->flags = swap32(*(int*)&edat_header[0]);
EDAT->block_size = swap32(*(int*)&edat_header[4]);
EDAT->file_size = swap64(*(u64*)&edat_header[8]);
if (verbose) if (verbose)
{ {
LOG_NOTICE(LOADER, "NPD HEADER"); LOG_NOTICE(LOADER, "NPD HEADER");
LOG_NOTICE(LOADER, "NPD version: %d", NPD->version); LOG_NOTICE(LOADER, "NPD version: %d", NPD.version);
LOG_NOTICE(LOADER, "NPD license: %d", NPD->license); LOG_NOTICE(LOADER, "NPD license: %d", NPD.license);
LOG_NOTICE(LOADER, "NPD type: %d", NPD->type); LOG_NOTICE(LOADER, "NPD type: %d", NPD.type);
} }
// Set decryption key. // Set decryption key.
unsigned char key[0x10]; u8 key[0x10] = { 0 };
memset(key, 0, 0x10);
// Check EDAT/SDAT flag. // Check EDAT/SDAT flag.
if ((EDAT->flags & SDAT_FLAG) == SDAT_FLAG) if ((EDAT.flags & SDAT_FLAG) == SDAT_FLAG)
{ {
if (verbose) if (verbose)
{ {
LOG_NOTICE(LOADER, "SDAT HEADER"); LOG_NOTICE(LOADER, "SDAT HEADER");
LOG_NOTICE(LOADER, "SDAT flags: 0x%08X", EDAT->flags); LOG_NOTICE(LOADER, "SDAT flags: 0x%08X", EDAT.flags);
LOG_NOTICE(LOADER, "SDAT block size: 0x%08X", EDAT->block_size); LOG_NOTICE(LOADER, "SDAT block size: 0x%08X", EDAT.block_size);
LOG_NOTICE(LOADER, "SDAT file size: 0x%08X", (u64)EDAT->file_size); LOG_NOTICE(LOADER, "SDAT file size: 0x%08X", (u64)EDAT.file_size);
} }
// Generate SDAT key. // Generate SDAT key.
xor_key(key, NPD->dev_hash, SDAT_KEY, 0x10); xor_key(key, NPD.dev_hash, SDAT_KEY);
} }
else else
{ {
if (verbose) if (verbose)
{ {
LOG_NOTICE(LOADER, "EDAT HEADER"); LOG_NOTICE(LOADER, "EDAT HEADER");
LOG_NOTICE(LOADER, "EDAT flags: 0x%08X", EDAT->flags); LOG_NOTICE(LOADER, "EDAT flags: 0x%08X", EDAT.flags);
LOG_NOTICE(LOADER, "EDAT block size: 0x%08X", EDAT->block_size); LOG_NOTICE(LOADER, "EDAT block size: 0x%08X", EDAT.block_size);
LOG_NOTICE(LOADER, "EDAT file size: 0x%08X", (u64)EDAT->file_size); LOG_NOTICE(LOADER, "EDAT file size: 0x%08X", (u64)EDAT.file_size);
} }
// Perform header validation (EDAT only). // Perform header validation (EDAT only).
char real_file_name[MAX_PATH]; char real_file_name[MAX_PATH];
extract_file_name(input_file_name, real_file_name); 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, verbose))
{ {
// Ignore header validation in DEBUG data. // Ignore header validation in DEBUG data.
if ((EDAT->flags & EDAT_DEBUG_DATA_FLAG) != EDAT_DEBUG_DATA_FLAG) if ((EDAT.flags & EDAT_DEBUG_DATA_FLAG) != EDAT_DEBUG_DATA_FLAG)
{ {
LOG_ERROR(LOADER, "EDAT: NPD hash validation failed!"); LOG_ERROR(LOADER, "EDAT: NPD hash validation failed!");
delete NPD;
delete EDAT;
return 1; return 1;
} }
} }
// 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); memcpy(key, devklic, 0x10);
else if ((NPD->license & 0x2) == 0x2) // Type 2: Use key from RAP file (RIF key). else if ((NPD.license & 0x2) == 0x2) // Type 2: Use key from RAP file (RIF key).
{ {
memcpy(key, rifkey, 0x10); memcpy(key, rifkey, 0x10);
@ -751,16 +711,12 @@ bool extract_data(const fs::file* input, const fs::file* output, const char* inp
if (!test) if (!test)
{ {
LOG_ERROR(LOADER, "EDAT: A valid RAP file is needed for this EDAT file!"); LOG_ERROR(LOADER, "EDAT: A valid RAP file is needed for this EDAT file!");
delete NPD;
delete EDAT;
return 1; return 1;
} }
} }
else if ((NPD->license & 0x1) == 0x1) // Type 1: Use network activation. else if ((NPD.license & 0x1) == 0x1) // Type 1: Use network activation.
{ {
LOG_ERROR(LOADER, "EDAT: Network license not supported!"); LOG_ERROR(LOADER, "EDAT: Network license not supported!");
delete NPD;
delete EDAT;
return 1; return 1;
} }
@ -785,45 +741,32 @@ bool extract_data(const fs::file* input, const fs::file* output, const char* inp
LOG_NOTICE(LOADER, "%02X", key[i]); LOG_NOTICE(LOADER, "%02X", key[i]);
} }
LOG_NOTICE(LOADER, "EDAT: Parsing data..."); input->seek(0);
if (check_data(key, EDAT, NPD, input, verbose)) if (check_data(key, &EDAT, &NPD, input, verbose))
{ {
LOG_ERROR(LOADER, "EDAT: Data parsing failed!"); LOG_ERROR(LOADER, "EDAT: Data parsing failed!");
delete NPD;
delete EDAT;
return 1; return 1;
} }
else
LOG_NOTICE(LOADER, "EDAT: Data successfully parsed!");
LOG_NOTICE(LOADER, "EDAT: Decrypting data..."); input->seek(0);
if (decrypt_data(input, output, EDAT, NPD, key, verbose)) if (decrypt_data(input, output, &EDAT, &NPD, key, verbose))
{ {
LOG_ERROR(LOADER, "EDAT: Data decryption failed!"); LOG_ERROR(LOADER, "EDAT: Data decryption failed!");
delete NPD;
delete EDAT;
return 1; return 1;
} }
else
LOG_NOTICE(LOADER, "EDAT: Data successfully decrypted!");
delete NPD;
delete EDAT;
return 0; return 0;
} }
int DecryptEDAT(const std::string& input_file_name, const std::string& output_file_name, int mode, const std::string& rap_file_name, unsigned char *custom_klic, bool verbose) // 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)
{ {
// Prepare the files. // Prepare the files.
fs::file input(input_file_name); input.seek(0);
fs::file output(output_file_name, fs::rewrite);
// Set keys (RIF and DEVKLIC). // Set keys (RIF and DEVKLIC).
unsigned char rifkey[0x10]; unsigned char rifkey[0x10] = { 0 };
unsigned char devklic[0x10]; unsigned char devklic[0x10] = { 0 };
memset(rifkey, 0, 0x10);
memset(devklic, 0, 0x10);
// Select the EDAT key mode. // Select the EDAT key mode.
switch (mode) switch (mode)
@ -858,21 +801,13 @@ int DecryptEDAT(const std::string& input_file_name, const std::string& output_fi
else else
{ {
LOG_ERROR(LOADER, "EDAT: Invalid custom klic!"); LOG_ERROR(LOADER, "EDAT: Invalid custom klic!");
return -1; return fs::file{};
} }
break; break;
} }
default: default:
LOG_ERROR(LOADER, "EDAT: Invalid mode!"); LOG_ERROR(LOADER, "EDAT: Invalid mode!");
return -1; return fs::file{};
}
// Check the input/output files.
if (!input || !output)
{
LOG_ERROR(LOADER, "EDAT: Failed to open files!");
return -1;
} }
// Read the RAP file, if provided. // Read the RAP file, if provided.
@ -880,8 +815,7 @@ int DecryptEDAT(const std::string& input_file_name, const std::string& output_fi
{ {
fs::file rap(rap_file_name); fs::file rap(rap_file_name);
unsigned char rapkey[0x10]; unsigned char rapkey[0x10] = { 0 };
memset(rapkey, 0, 0x10);
rap.read(rapkey, 0x10); rap.read(rapkey, 0x10);
@ -889,12 +823,92 @@ int DecryptEDAT(const std::string& input_file_name, const std::string& output_fi
} }
// Delete the bad output file if any errors arise. // Delete the bad output file if any errors arise.
if (extract_data(&input, &output, input_file_name.c_str(), devklic, rifkey, verbose)) fs::file output = fs::make_stream<std::vector<u8>>();
if (extract_all_data(&input, &output, input_file_name.c_str(), devklic, rifkey, verbose))
{ {
output.release(); output.release();
fs::remove_file(output_file_name); return fs::file{};
return -1;
} }
return 0; output.seek(0);
return output;
}
SDATADecrypter::SDATADecrypter(fs::file&& input, u64 offset)
: sdata_file(std::move(input)), file_offset(offset)
{
}
bool SDATADecrypter::ReadHeader()
{
sdata_file.seek(file_offset);
// Read in the NPD and EDAT/SDAT headers.
read_npd_edat_header(&sdata_file, npdHeader, edatHeader);
unsigned char npd_magic[4] = { 0x4E, 0x50, 0x44, 0x00 }; //NPD0
if (memcmp(&npdHeader.magic, npd_magic, 4))
{
return false;
}
// Check for SDAT flag.
if ((edatHeader.flags & SDAT_FLAG) != SDAT_FLAG)
{
fmt::raw_error("Only SDATA decyption supported in sdatadecrypter");
return false;
}
// Generate SDAT key.
xor_key(dec_key.data(), npdHeader.dev_hash, SDAT_KEY);
sdata_file.seek(file_offset);
// k the ecdsa_verify function in this check_data function takes a ridiculous amount of time
// like it slows down load time by a factor of x20, at least, so its ignored for now
/*if (check_data(dec_key.data(), &edatHeader, &npdHeader, &sdata_file, false))
{
return false;
}*/
file_size = edatHeader.file_size;
total_blocks = (u32)((edatHeader.file_size + edatHeader.block_size - 1) / edatHeader.block_size);
return true;
}
u64 SDATADecrypter::ReadData(u64 pos, u8* data, u64 size)
{
if (pos > edatHeader.file_size)
return 0;
// 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>(std::ceil((startOffset + size) / (double)edatHeader.block_size));
const u64 bufSize = num_blocks*edatHeader.block_size;
if (data_buf_size < (bufSize))
{
data_buf.reset(new u8[bufSize]);
data_buf_size = bufSize;
}
// find and decrypt block range covering pos + size
const u32 starting_block = static_cast<u32>(pos / edatHeader.block_size);
u64 writeOffset = 0;
for (u32 i = starting_block; i < (starting_block + num_blocks); ++i)
{
sdata_file.seek(file_offset);
u64 res = decrypt_block(&sdata_file, &data_buf[writeOffset], &edatHeader, &npdHeader, dec_key.data(), i, total_blocks, edatHeader.file_size);
if (res == -1)
{
LOG_ERROR(LOADER, "Error Decrypting data");
return 0;
}
writeOffset += res;
}
const u64 bytesWrote = std::min<u64>(writeOffset - startOffset, size);
memmove(data, &data_buf[startOffset], bytesWrote);
return bytesWrote;
} }

View file

@ -4,33 +4,95 @@
#include <string.h> #include <string.h>
#include "utils.h" #include "utils.h"
#define SDAT_FLAG 0x01000000 constexpr u32 SDAT_FLAG = 0x01000000;
#define EDAT_COMPRESSED_FLAG 0x00000001 constexpr u32 EDAT_COMPRESSED_FLAG = 0x00000001;
#define EDAT_FLAG_0x02 0x00000002 constexpr u32 EDAT_FLAG_0x02 = 0x00000002;
#define EDAT_ENCRYPTED_KEY_FLAG 0x00000008 constexpr u32 EDAT_ENCRYPTED_KEY_FLAG = 0x00000008;
#define EDAT_FLAG_0x10 0x00000010 constexpr u32 EDAT_FLAG_0x10 = 0x00000010;
#define EDAT_FLAG_0x20 0x00000020 constexpr u32 EDAT_FLAG_0x20 = 0x00000020;
#define EDAT_DEBUG_DATA_FLAG 0x80000000 constexpr u32 EDAT_DEBUG_DATA_FLAG = 0x80000000;
typedef struct struct NPD_HEADER
{ {
unsigned char magic[4]; u32 magic;
int version; s32 version;
int license; s32 license;
int type; s32 type;
unsigned char content_id[0x30]; u8 content_id[0x30];
unsigned char digest[0x10]; u8 digest[0x10];
unsigned char title_hash[0x10]; u8 title_hash[0x10];
unsigned char dev_hash[0x10]; u8 dev_hash[0x10];
unsigned long long unk1; u64 unk1;
unsigned long long unk2; u64 unk2;
} NPD_HEADER; };
typedef struct struct EDAT_HEADER
{ {
int flags; s32 flags;
int block_size; s32 block_size;
unsigned long long file_size; u64 file_size;
} EDAT_HEADER; };
int DecryptEDAT(const std::string& input_file_name, const std::string& output_file_name, int mode, const std::string& rap_file_name, unsigned char *custom_klic, bool verbose); // 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);
struct SDATADecrypter final : fs::file_base
{
// file stream
const fs::file sdata_file;
const u64 file_offset;
u64 file_size{0};
u32 total_blocks{0};
u64 pos{0};
NPD_HEADER npdHeader;
EDAT_HEADER edatHeader;
// Internal data buffers.
std::unique_ptr<u8[]> data_buf;
u64 data_buf_size{0};
std::array<u8, 0x10> dec_key{};
public:
SDATADecrypter(fs::file&& input, u64 offset=0);
~SDATADecrypter() override {}
// false if invalid
bool ReadHeader();
u64 ReadData(u64 pos, u8* data, u64 size);
fs::stat_t stat() override
{
fs::stat_t stats;
stats.is_directory = false;
stats.is_writable = false;
stats.size = file_size;
stats.atime = -1;
stats.ctime = -1;
stats.mtime = -1;
return stats;
}
bool trunc(u64 length) override
{
return true;
};
u64 read(void* buffer, u64 size) override
{
u64 bytesRead = ReadData(pos, (u8*)buffer, size);
pos += bytesRead;
return bytesRead;
}
u64 write(const void* buffer, u64 size) override
{
return 0;
}
u64 seek(s64 offset, fs::seek_mode whence) override
{
return
whence == fs::seek_set ? pos = offset :
whence == fs::seek_cur ? pos = offset + pos :
whence == fs::seek_end ? pos = offset + size() :
(fmt::raw_error("SDATADecrypter::seek(): invalid whence"), 0);
}
u64 size() override { return file_size; }
};

View file

@ -9,28 +9,10 @@
#include <memory> #include <memory>
// Auxiliary functions (endian swap, xor and prng). // Auxiliary functions (endian swap, xor and prng).
u16 swap16(u16 i)
{
return ((i & 0xFF00) >> 8) | ((i & 0xFF) << 8);
}
u32 swap32(u32 i) void xor_key(unsigned char *dest, unsigned char *src1, unsigned char *src2)
{ {
return ((i & 0xFF000000) >> 24) | ((i & 0xFF0000) >> 8) | ((i & 0xFF00) << 8) | ((i & 0xFF) << 24); for(int i = 0; i < 0x10; i++)
}
u64 swap64(u64 i)
{
return ((i & 0x00000000000000ff) << 56) | ((i & 0x000000000000ff00) << 40) |
((i & 0x0000000000ff0000) << 24) | ((i & 0x00000000ff000000) << 8) |
((i & 0x000000ff00000000) >> 8) | ((i & 0x0000ff0000000000) >> 24) |
((i & 0x00ff000000000000) >> 40) | ((i & 0xff00000000000000) >> 56);
}
void xor_key(unsigned char *dest, unsigned char *src1, unsigned char *src2, int size)
{
int i;
for(i = 0; i < size; i++)
{ {
dest[i] = src1[i] ^ src2[i]; dest[i] = src1[i] ^ src2[i];
} }
@ -38,16 +20,10 @@ void xor_key(unsigned char *dest, unsigned char *src1, unsigned char *src2, int
void prng(unsigned char *dest, int size) void prng(unsigned char *dest, int size)
{ {
unsigned char *buffer = new unsigned char[size];
srand((u32)time(0)); srand((u32)time(0));
int i; for(int i = 0; i < size; i++)
for(i = 0; i < size; i++) dest[i] = (unsigned char)(rand() & 0xFF);
buffer[i] = (unsigned char)(rand() & 0xFF);
memcpy(dest, buffer, size);
delete[] buffer;
} }
// Hex string conversion auxiliary functions. // Hex string conversion auxiliary functions.
@ -147,22 +123,11 @@ void aesecb128_encrypt(unsigned char *key, unsigned char *in, unsigned char *out
bool hmac_hash_compare(unsigned char *key, int key_len, unsigned char *in, int in_len, unsigned char *hash, int hash_len) bool hmac_hash_compare(unsigned char *key, int key_len, unsigned char *in, int in_len, unsigned char *hash, int hash_len)
{ {
unsigned char *out = new unsigned char[key_len]; std::unique_ptr<u8> out(new u8[key_len]);
sha1_hmac(key, key_len, in, in_len, out); sha1_hmac(key, key_len, in, in_len, out.get());
for (int i = 0; i < hash_len; i++) return std::memcmp(out.get(), hash, hash_len) == 0;
{
if (out[i] != hash[i])
{
delete[] out;
return false;
}
}
delete[] out;
return true;
} }
void hmac_hash_forge(unsigned char *key, int key_len, unsigned char *in, int in_len, unsigned char *hash) void hmac_hash_forge(unsigned char *key, int key_len, unsigned char *in, int in_len, unsigned char *hash)
@ -172,24 +137,13 @@ void hmac_hash_forge(unsigned char *key, int key_len, unsigned char *in, int in_
bool cmac_hash_compare(unsigned char *key, int key_len, unsigned char *in, int in_len, unsigned char *hash, int hash_len) bool cmac_hash_compare(unsigned char *key, int key_len, unsigned char *in, int in_len, unsigned char *hash, int hash_len)
{ {
unsigned char *out = new unsigned char[key_len]; std::unique_ptr<u8> out(new u8[key_len]);
aes_context ctx; aes_context ctx;
aes_setkey_enc(&ctx, key, 128); aes_setkey_enc(&ctx, key, 128);
aes_cmac(&ctx, in_len, in, out); aes_cmac(&ctx, in_len, in, out.get());
for (int i = 0; i < hash_len; i++) return std::memcmp(out.get(), hash, hash_len) == 0;
{
if (out[i] != hash[i])
{
delete[] out;
return false;
}
}
delete[] out;
return true;
} }
void cmac_hash_forge(unsigned char *key, int key_len, unsigned char *in, int in_len, unsigned char *hash) void cmac_hash_forge(unsigned char *key, int key_len, unsigned char *in, int in_len, unsigned char *hash)

View file

@ -15,10 +15,40 @@
#include "ec.h" #include "ec.h"
// Auxiliary functions (endian swap, xor, prng and file name). // Auxiliary functions (endian swap, xor, prng and file name).
u16 swap16(u16 i); inline u16 swap16(u16 i)
u32 swap32(u32 i); {
u64 swap64(u64 i); #if defined(__GNUG__)
void xor_key(unsigned char *dest, unsigned char *src1, unsigned char *src2, int size); return __builtin_bswap16(i);
#else
return _byteswap_ushort(i);
#endif
}
inline u32 swap32(u32 i)
{
#if defined(__GNUG__)
return __builtin_bswap32(i);
#else
return _byteswap_ulong(i);
#endif
}
inline u64 swap64(u64 i)
{
#if defined(__GNUG__)
return __builtin_bswap64(i);
#else
return _byteswap_uint64(i);
#endif
}
void xor_key(unsigned char *dest, unsigned char *src1, unsigned char *src2);
inline void xor_key_sse(u8* dest, const u8* src1, const u8* src2)
{
_mm_storeu_si128(&(((__m128i*)dest)[0]),
_mm_xor_si128(_mm_loadu_si128((__m128i*)src1), _mm_loadu_si128((__m128i*)src2)));
}
void prng(unsigned char *dest, int size); void prng(unsigned char *dest, int size);
char* extract_file_name(const char* file_path, char real_file_name[MAX_PATH]); char* extract_file_name(const char* file_path, char real_file_name[MAX_PATH]);

View file

@ -89,11 +89,16 @@ s32 npDrmIsAvailable(vm::cptr<u8> k_licensee_addr, vm::cptr<char> drm_path)
} }
const std::string& enc_drm_path_local = vfs::get(enc_drm_path); const std::string& enc_drm_path_local = vfs::get(enc_drm_path);
const std::string& dec_drm_path_local = vfs::get(dec_drm_path); const fs::file enc_file(enc_drm_path_local);
if (DecryptEDAT(enc_drm_path_local, dec_drm_path_local, 8, rap_lpath, k_licensee, false) >= 0) if (const fs::file dec_file = DecryptEDAT(enc_file, enc_drm_path_local, 8, rap_lpath, k_licensee, false))
{ {
// If decryption succeeds, replace the encrypted file with it. // If decryption succeeds, replace the encrypted file with it.
const std::string& dec_drm_path_local = vfs::get(dec_drm_path);
fs::file dec_out(dec_drm_path_local, fs::rewrite);
dec_out.write(dec_file.to_vector<u8>());
fs::remove_file(enc_drm_path_local); fs::remove_file(enc_drm_path_local);
fs::rename(dec_drm_path_local, enc_drm_path_local); fs::rename(dec_drm_path_local, enc_drm_path_local);
} }

View file

@ -3,6 +3,8 @@
#include <mutex> #include <mutex>
#include "Emu/Cell/PPUThread.h"
#include "Crypto/unedat.h"
#include "Emu/VFS.h" #include "Emu/VFS.h"
#include "Emu/IdManager.h" #include "Emu/IdManager.h"
#include "Utilities/StrUtil.h" #include "Utilities/StrUtil.h"
@ -23,6 +25,37 @@ lv2_fs_mount_point g_mp_sys_dev_bdvd;
lv2_fs_mount_point g_mp_sys_app_home; lv2_fs_mount_point g_mp_sys_app_home;
lv2_fs_mount_point g_mp_sys_host_root; lv2_fs_mount_point g_mp_sys_host_root;
bool verify_mself(u32 fd, fs::file const& mself_file)
{
FsMselfHeader mself_header;
if (!mself_file.read<FsMselfHeader>(mself_header))
{
sys_fs.error("verify_mself: Didn't read expected bytes for header.");
return false;
}
if (mself_header.m_magic != 0x4D534600)
{
sys_fs.error("verify_mself: Header magic is incorrect.");
return false;
}
if (mself_header.m_format_version != 1)
{
sys_fs.error("verify_mself: Unexpected header format version.");
return false;
}
// sanity check
if (mself_header.m_entry_size != sizeof(FsMselfEntry))
{
sys_fs.error("verify_mself: Unexpected header entry size.");
return false;
}
return true;
}
lv2_fs_mount_point* lv2_fs_object::get_mp(const char* filename) lv2_fs_mount_point* lv2_fs_object::get_mp(const char* filename)
{ {
// TODO // TODO
@ -75,8 +108,6 @@ struct lv2_file::file_view : fs::file_base
u64 read(void* buffer, u64 size) override u64 read(void* buffer, u64 size) override
{ {
std::lock_guard<std::mutex> lock(m_file->mp->mutex);
const u64 old_pos = m_file->file.pos(); const u64 old_pos = m_file->file.pos();
const u64 new_pos = m_file->file.seek(m_off + m_pos); const u64 new_pos = m_file->file.seek(m_off + m_pos);
const u64 result = m_file->file.read(buffer, size); const u64 result = m_file->file.read(buffer, size);
@ -182,7 +213,17 @@ error_code sys_fs_open(vm::cptr<char> path, s32 flags, vm::ptr<u32> fd, s32 mode
} }
} }
if (flags & ~(CELL_FS_O_ACCMODE | CELL_FS_O_CREAT | CELL_FS_O_TRUNC | CELL_FS_O_APPEND | CELL_FS_O_EXCL)) if (flags & CELL_FS_O_MSELF)
{
open_mode = fs::read;
// mself can be mself or mself | rdonly
if (flags & ~(CELL_FS_O_MSELF | CELL_FS_O_RDONLY))
{
open_mode = {};
}
}
if (flags & ~(CELL_FS_O_ACCMODE | CELL_FS_O_CREAT | CELL_FS_O_TRUNC | CELL_FS_O_APPEND | CELL_FS_O_EXCL | CELL_FS_O_MSELF))
{ {
open_mode = {}; // error open_mode = {}; // error
} }
@ -211,6 +252,30 @@ error_code sys_fs_open(vm::cptr<char> path, s32 flags, vm::ptr<u32> fd, s32 mode
return CELL_ENOENT; return CELL_ENOENT;
} }
if ((flags & CELL_FS_O_MSELF) && (!verify_mself(*fd, file)))
return CELL_ENOTMSELF;
// sdata encryption arg flag
const be_t<u32>* casted_args = static_cast<const be_t<u32> *>(arg.get_ptr());
if (size == 8 && casted_args[0] == 0x180 && casted_args[1] == 0x10)
{
// check if the file has the NPD header, or else assume its not encrypted
u32 magic;
file.read<u32>(magic);
file.seek(0);
if (magic == "NPD\0"_u32)
{
auto sdata_file = std::make_unique<SDATADecrypter>(std::move(file));
if (!sdata_file->ReadHeader())
{
sys_fs.error("sys_fs_open(%s): Error reading sdata header!", path);
return CELL_EFSSPECIFIC;
}
file.reset(std::move(sdata_file));
}
}
if (const u32 id = idm::make<lv2_fs_object, lv2_file>(path.get_ptr(), std::move(file), mode, flags)) if (const u32 id = idm::make<lv2_fs_object, lv2_file>(path.get_ptr(), std::move(file), mode, flags))
{ {
*fd = id; *fd = id;
@ -559,8 +624,16 @@ error_code sys_fs_fcntl(u32 fd, u32 op, vm::ptr<void> _arg, u32 _size)
return CELL_EBADF; return CELL_EBADF;
} }
// TODO auto sdata_file = std::make_unique<SDATADecrypter>(lv2_file::make_view(file, arg->offset));
if (const u32 id = idm::make<lv2_fs_object, lv2_file>(file->mp, lv2_file::make_view(file, arg->offset), file->mode, file->flags))
if (!sdata_file->ReadHeader())
{
return CELL_EFSSPECIFIC;
}
fs::file stream;
stream.reset(std::move(sdata_file));
if (const u32 id = idm::make<lv2_fs_object, lv2_file>(file->mp, std::move(stream), file->mode, file->flags))
{ {
arg->out_code = CELL_OK; arg->out_code = CELL_OK;
arg->out_fd = id; arg->out_fd = id;

View file

@ -91,6 +91,26 @@ struct CellFsUtimbuf
CHECK_SIZE_ALIGN(CellFsUtimbuf, 16, 4); CHECK_SIZE_ALIGN(CellFsUtimbuf, 16, 4);
// MSelf file structs
struct FsMselfHeader
{
be_t<u32> m_magic;
be_t<u32> m_format_version;
be_t<u64> m_file_size;
be_t<u32> m_entry_num;
be_t<u32> m_entry_size;
u8 m_reserve[40];
};
struct FsMselfEntry
{
char m_name[32];
be_t<u64> m_offset;
be_t<u64> m_size;
u8 m_reserve[16];
};
struct lv2_fs_mount_point; struct lv2_fs_mount_point;
struct lv2_fs_object struct lv2_fs_object