From 87fe93ee9a4d8e36cd400c58efb9312c12154291 Mon Sep 17 00:00:00 2001 From: Jake Date: Mon, 6 Mar 2017 18:59:05 -0600 Subject: [PATCH] Mself / Sdata: on the fly partial decoding support (#2468) --- rpcs3/Crypto/lz.cpp | 3 +- rpcs3/Crypto/unedat.cpp | 732 ++++++++++++++++--------------- rpcs3/Crypto/unedat.h | 112 +++-- rpcs3/Crypto/utils.cpp | 78 +--- rpcs3/Crypto/utils.h | 38 +- rpcs3/Emu/Cell/Modules/sceNp.cpp | 9 +- rpcs3/Emu/Cell/lv2/sys_fs.cpp | 83 +++- rpcs3/Emu/Cell/lv2/sys_fs.h | 20 + 8 files changed, 616 insertions(+), 459 deletions(-) diff --git a/rpcs3/Crypto/lz.cpp b/rpcs3/Crypto/lz.cpp index b56ac78e6e..56a11c4853 100644 --- a/rpcs3/Crypto/lz.cpp +++ b/rpcs3/Crypto/lz.cpp @@ -140,8 +140,7 @@ int decompress(unsigned char *out, unsigned char *in, unsigned int size) unsigned int range = 0xFFFFFFFF; 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 < 0) // Check if we have a valid starting byte. + if (head > 0x80) // Check if we have a valid starting byte. { // The dictionary header is invalid, the data is not compressed. result = -1; diff --git a/rpcs3/Crypto/unedat.cpp b/rpcs3/Crypto/unedat.cpp index 09f743079f..7946880976 100644 --- a/rpcs3/Crypto/unedat.cpp +++ b/rpcs3/Crypto/unedat.cpp @@ -2,6 +2,8 @@ #include "key_vault.h" #include "unedat.h" +#include + 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); @@ -100,9 +102,9 @@ bool decrypt(int hash_mode, int crypto_mode, int version, unsigned char *in, uns } // EDAT/SDAT functions. -unsigned char* dec_section(unsigned char* metadata) +std::tuple dec_section(unsigned char* metadata) { - unsigned char *dec = new unsigned char[0x10]; + std::array dec; dec[0x00] = (metadata[0xC] ^ metadata[0x8] ^ metadata[0x10]); dec[0x01] = (metadata[0xD] ^ metadata[0x9] ^ metadata[0x11]); dec[0x02] = (metadata[0xE] ^ metadata[0xA] ^ metadata[0x12]); @@ -119,242 +121,227 @@ unsigned char* dec_section(unsigned char* metadata) dec[0x0D] = (metadata[0x5] ^ metadata[0x1] ^ metadata[0x1D]); dec[0x0E] = (metadata[0x6] ^ metadata[0x2] ^ metadata[0x1E]); 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 get_block_key(int block, NPD_HEADER *npd) { unsigned char empty_key[0x10] = {}; unsigned char *src_key = (npd->version <= 1) ? empty_key : npd->dev_hash; - unsigned char *dest_key = new unsigned char[0x10]; - memcpy(dest_key, src_key, 0xC); - dest_key[0xC] = (block >> 24 & 0xFF); - dest_key[0xD] = (block >> 16 & 0xFF); - dest_key[0xE] = (block >> 8 & 0xFF); - dest_key[0xF] = (block & 0xFF); + std::array dest_key{}; + memcpy(dest_key.data(), src_key, 0xC); + + s32 swappedBlock = swap32(block); + memcpy(&dest_key[0xC], &swappedBlock, sizeof(swappedBlock)); return dest_key; } -// EDAT/SDAT decryption. -int decrypt_data(const fs::file* in, const fs::file* out, EDAT_HEADER *edat, NPD_HEADER *npd, unsigned char* crypt_key, bool verbose) +// for out data, allocate a buffer the size of 'edat->block_size' +// 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. - int block_num = (int)((edat->file_size + edat->block_size - 1) / edat->block_size); - int metadata_section_size = ((edat->flags & EDAT_COMPRESSED_FLAG) != 0 || (edat->flags & EDAT_FLAG_0x20) != 0) ? 0x20 : 0x10; - int metadata_offset = 0x100; + const int metadata_section_size = ((edat->flags & EDAT_COMPRESSED_FLAG) != 0 || (edat->flags & EDAT_FLAG_0x20) != 0) ? 0x20 : 0x10; + const int metadata_offset = 0x100; - unsigned char *enc_data; - unsigned char *dec_data; - unsigned char *b_key; - unsigned char *iv; + std::unique_ptr enc_data; + std::unique_ptr dec_data; + u8 hash[0x10] = { 0 }; + u8 key_result[0x10] = { 0 }; + u8 hash_result[0x14] = { 0 }; - unsigned char hash[0x10]; - unsigned char key_result[0x10]; - unsigned char hash_result[0x14]; - memset(hash, 0, 0x10); - 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; + u64 offset = 0; + u64 metadata_sec_offset = 0; + s32 length = 0; + s32 compression_end = 0; unsigned char empty_iv[0x10] = {}; + const u64 file_offset = in->pos(); + memset(hash_result, 0, 0x14); + // Decrypt the metadata. - for (int i = 0; i < block_num; i++) + if ((edat->flags & EDAT_COMPRESSED_FLAG) != 0) { - memset(hash_result, 0, 0x14); + metadata_sec_offset = metadata_offset + (unsigned long long) block_num * metadata_section_size; - if ((edat->flags & EDAT_COMPRESSED_FLAG) != 0) + in->seek(file_offset + metadata_sec_offset); + + unsigned char metadata[0x20]; + memset(metadata, 0, 0x20); + in->read(metadata, 0x20); + + // If the data is compressed, decrypt the metadata. + // NOTE: For NPD version 1 the metadata is not encrypted. + if (npd->version <= 1) { - metadata_sec_offset = metadata_offset + (unsigned long long) i * metadata_section_size; + offset = swap64(*(unsigned long long*)&metadata[0x10]); + length = swap32(*(int*)&metadata[0x18]); + compression_end = swap32(*(int*)&metadata[0x1C]); + } + else + { + std::tie(offset, length, compression_end) = dec_section(metadata); + } - in->seek(metadata_sec_offset); - - unsigned char metadata[0x20]; - memset(metadata, 0, 0x20); - in->read(metadata, 0x20); + memcpy(hash_result, metadata, 0x10); + } + else if ((edat->flags & EDAT_FLAG_0x20) != 0) + { + // If FLAG 0x20, the metadata precedes each data block. + metadata_sec_offset = metadata_offset + (u64) block_num * (metadata_section_size + length); + in->seek(file_offset + metadata_sec_offset); - // If the data is compressed, decrypt the metadata. - // NOTE: For NPD version 1 the metadata is not encrypted. - if (npd->version <= 1) + unsigned char metadata[0x20]; + memset(metadata, 0, 0x20); + in->read(metadata, 0x20); + memcpy(hash_result, metadata, 0x14); + + // If FLAG 0x20 is set, apply custom xor. + for (int j = 0; j < 0x10; j++) + hash_result[j] = (unsigned char)(metadata[j] ^ metadata[j + 0x10]); + + offset = metadata_sec_offset + 0x20; + length = edat->block_size; + + if ((block_num == (total_blocks - 1)) && (edat->file_size % edat->block_size)) + length = (int)(edat->file_size % edat->block_size); + } + else + { + metadata_sec_offset = metadata_offset + (u64) block_num * metadata_section_size; + in->seek(file_offset + metadata_sec_offset); + + in->read(hash_result, 0x10); + offset = metadata_offset + (u64) block_num * edat->block_size + (u64) block_num * metadata_section_size; + length = edat->block_size; + + if ((block_num == (total_blocks - 1)) && (edat->file_size % edat->block_size)) + length = (int)(edat->file_size % edat->block_size); + } + + // Locate the real data. + const int pad_length = length; + length = (int)((pad_length + 0xF) & 0xFFFFFFF0); + + // Setup buffers for decryption and read the data. + enc_data.reset(new u8[length]{ 0 }); + dec_data.reset(new u8[length]{ 0 }); + memset(hash, 0, 0x10); + memset(key_result, 0, 0x10); + + in->seek(file_offset + offset); + in->read(enc_data.get(), length); + + // Generate a key for the current block. + std::array b_key = get_block_key(block_num, npd); + + // Encrypt the block key with the crypto key. + aesecb128_encrypt(crypt_key, b_key.data(), key_result); + 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. + else + memcpy(hash, key_result, 0x10); + + // Setup the crypto and hashing mode based on the extra flags. + int crypto_mode = ((edat->flags & EDAT_FLAG_0x02) == 0) ? 0x2 : 0x1; + int hash_mode; + + if ((edat->flags & EDAT_FLAG_0x10) == 0) + hash_mode = 0x02; + else if ((edat->flags & EDAT_FLAG_0x20) == 0) + hash_mode = 0x04; + else + hash_mode = 0x01; + + if ((edat->flags & EDAT_ENCRYPTED_KEY_FLAG) != 0) + { + crypto_mode |= 0x10000000; + hash_mode |= 0x10000000; + } + + if ((edat->flags & EDAT_DEBUG_DATA_FLAG) != 0) + { + // Reset the flags. + crypto_mode |= 0x01000000; + hash_mode |= 0x01000000; + // Simply copy the data without the header or the footer. + memcpy(dec_data.get(), enc_data.get(), length); + } + else + { + // IV is null if NPD version is 1 or 0. + u8* iv = (npd->version <= 1) ? empty_iv : npd->digest; + // Call main crypto routine on this data block. + if (!decrypt(hash_mode, crypto_mode, (npd->version == 4), enc_data.get(), dec_data.get(), length, key_result, iv, hash, hash_result)) + { + LOG_ERROR(LOADER, "EDAT: Block at offset 0x%llx has invalid hash!", (u64)offset); + return 1; + } + } + + // Apply additional de-compression if needed and write the decrypted data. + if (((edat->flags & EDAT_COMPRESSED_FLAG) != 0) && compression_end) + { + const int res = decompress(out, dec_data.get(), edat->block_size); + + size_left -= res; + + if (size_left == 0) + { + if (res < 0) { - offset = swap64(*(unsigned long long*)&metadata[0x10]); - length = swap32(*(int*)&metadata[0x18]); - compression_end = swap32(*(int*)&metadata[0x1C]); - } - else - { - unsigned char *result = 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); - } - else if ((edat->flags & EDAT_FLAG_0x20) != 0) - { - // If FLAG 0x20, the metadata precedes each data block. - metadata_sec_offset = metadata_offset + (unsigned long long) i * (metadata_section_size + length); - in->seek(metadata_sec_offset); - - unsigned char metadata[0x20]; - memset(metadata, 0, 0x20); - in->read(metadata, 0x20); - memcpy(hash_result, metadata, 0x14); - - // If FLAG 0x20 is set, apply custom xor. - int j; - for (j = 0; j < 0x10; j++) - hash_result[j] = (unsigned char)(metadata[j] ^ metadata[j + 0x10]); - - offset = metadata_sec_offset + 0x20; - length = edat->block_size; - - if ((i == (block_num - 1)) && (edat->file_size % edat->block_size)) - length = (int)(edat->file_size % edat->block_size); - } - else - { - metadata_sec_offset = metadata_offset + (unsigned long long) i * metadata_section_size; - in->seek(metadata_sec_offset); - - in->read(hash_result, 0x10); - offset = metadata_offset + (unsigned long long) i * edat->block_size + (unsigned long long) block_num * metadata_section_size; - length = edat->block_size; - - if ((i == (block_num - 1)) && (edat->file_size % edat->block_size)) - length = (int)(edat->file_size % edat->block_size); - } - - // Locate the real data. - int pad_length = length; - length = (int)((pad_length + 0xF) & 0xFFFFFFF0); - - // Setup buffers for decryption and read the data. - enc_data = new unsigned char[length]; - dec_data = new unsigned char[length]; - memset(enc_data, 0, length); - memset(dec_data, 0, length); - memset(hash, 0, 0x10); - memset(key_result, 0, 0x10); - - in->seek(offset); - in->read(enc_data, length); - - // Generate a key for the current block. - b_key = get_block_key(i, npd); - - // Encrypt the block key with the crypto key. - aesecb128_encrypt(crypt_key, b_key, key_result); - 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. - else - memcpy(hash, key_result, 0x10); - - // Setup the crypto and hashing mode based on the extra flags. - int crypto_mode = ((edat->flags & EDAT_FLAG_0x02) == 0) ? 0x2 : 0x1; - int hash_mode; - - if ((edat->flags & EDAT_FLAG_0x10) == 0) - hash_mode = 0x02; - else if ((edat->flags & EDAT_FLAG_0x20) == 0) - hash_mode = 0x04; - else - hash_mode = 0x01; - - if ((edat->flags & EDAT_ENCRYPTED_KEY_FLAG) != 0) - { - crypto_mode |= 0x10000000; - hash_mode |= 0x10000000; - } - - if ((edat->flags & EDAT_DEBUG_DATA_FLAG) != 0) - { - // Reset the flags. - crypto_mode |= 0x01000000; - hash_mode |= 0x01000000; - // Simply copy the data without the header or the footer. - memcpy(dec_data, enc_data, length); - } - else - { - // IV is null if NPD version is 1 or 0. - iv = (npd->version <= 1) ? empty_iv : npd->digest; - // 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 (verbose) - LOG_WARNING(LOADER, "EDAT: Block at offset 0x%llx has invalid hash!", (u64)offset); - - delete[] enc_data; - delete[] dec_data; - delete[] b_key; + LOG_ERROR(LOADER, "EDAT: Decompression failed!"); return 1; } } + return res; + } + else + { + memcpy(out, dec_data.get(), pad_length); + return pad_length; + } +} - // Apply additional compression if needed and write the decrypted data. - if (((edat->flags & EDAT_COMPRESSED_FLAG) != 0) && compression_end) +// EDAT/SDAT decryption. +// reset file to beginning of data before calling +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 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) { - int decomp_size = (int)edat->file_size; - unsigned char *decomp_data = new unsigned char[decomp_size]; - memset(decomp_data, 0, decomp_size); - - if (verbose) - LOG_NOTICE(LOADER, "EDAT: Decompressing data..."); - - int res = decompress(decomp_data, dec_data, decomp_size); - 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) - { - LOG_ERROR(LOADER, "EDAT: Decompression failed!"); - return 1; - } - else - LOG_NOTICE(LOADER, "EDAT: Successfully decompressed!"); - } - - delete[] decomp_data; + LOG_ERROR(LOADER, "EDAT: Decrypt Block failed!"); + return 1; } - else - { - out->write(dec_data, pad_length); - } - - delete[] enc_data; - delete[] dec_data; - delete[] b_key; + size_left -= res; + out->write(data.get(), res); } 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) { - f->seek(0); - unsigned char header[0xA0]; - unsigned char empty_header[0xA0]; - unsigned char header_hash[0x10]; - unsigned char metadata_hash[0x10]; - memset(header, 0, 0xA0); - memset(empty_header, 0, 0xA0); - memset(header_hash, 0, 0x10); - memset(metadata_hash, 0, 0x10); + u8 header[0xA0] = { 0 }; + u8 empty_header[0xA0] = { 0 }; + u8 header_hash[0x10] = { 0 }; + u8 metadata_hash[0x10] = { 0 }; + + const u64 file_offset = f->pos(); // Check NPD version and flags. 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); // Read in the header and metadata section hashes. - f->seek(0x90); + f->seek(file_offset + 0x90); f->read(metadata_hash, 0x10); f->read(header_hash, 0x10); // 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; if ((edat->flags & EDAT_DEBUG_DATA_FLAG) != 0) { @@ -407,11 +394,9 @@ int check_data(unsigned char *key, EDAT_HEADER *edat, NPD_HEADER *npd, const fs: } // Setup header key and iv buffers. - unsigned char header_key[0x10]; - unsigned char header_iv[0x10]; - memset(header_key, 0, 0x10); - memset(header_iv, 0, 0x10); - + unsigned char header_key[0x10] = { 0 }; + unsigned char header_iv[0x10] = { 0 }; + // 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)) { @@ -427,30 +412,30 @@ int check_data(unsigned char *key, EDAT_HEADER *edat, NPD_HEADER *npd, const fs: } // 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 (verbose) LOG_WARNING(LOADER, "EDAT: COMPRESSED data detected!"); } - int block_num = (int)((edat->file_size + edat->block_size - 1) / edat->block_size); - int metadata_offset = 0x100; - int metadata_size = metadata_section_size * block_num; - long long metadata_section_offset = metadata_offset; + const int block_num = (int)((edat->file_size + edat->block_size - 1) / edat->block_size); + const int metadata_offset = 0x100; + const int metadata_size = metadata_section_size * block_num; + u64 metadata_section_offset = metadata_offset; long bytes_read = 0; long bytes_to_read = metadata_size; - unsigned char *metadata = new unsigned char[metadata_size]; - unsigned char *empty_metadata = new unsigned char[metadata_size]; + std::unique_ptr metadata(new u8[metadata_size]); + std::unique_ptr empty_metadata(new u8[metadata_size]); while (bytes_to_read > 0) { // Locate the metadata blocks. - f->seek(metadata_section_offset); + f->seek(file_offset + metadata_section_offset); // Read in the metadata. - f->read(metadata + bytes_read, metadata_section_size); + f->read(metadata.get() + bytes_read, metadata_section_size); // Adjust sizes. 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). - 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) 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. if ((edat->flags & EDAT_DEBUG_DATA_FLAG) == 0) { - LOG_NOTICE(LOADER, "EDAT: Checking signatures..."); - // Setup buffers. - unsigned char metadata_signature[0x28]; - unsigned char header_signature[0x28]; - unsigned char signature_hash[20]; - unsigned char signature_r[0x15]; - unsigned char signature_s[0x15]; - unsigned char zero_buf[0x15]; - 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); - + unsigned char metadata_signature[0x28] = { 0 }; + unsigned char header_signature[0x28] = { 0 }; + unsigned char signature_hash[20] = { 0 }; + unsigned char signature_r[0x15] = { 0 }; + unsigned char signature_s[0x15] = { 0 }; + unsigned char zero_buf[0x15] = { 0 }; + // 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_pub(VSH_PUB); - // Read in the metadata and header signatures. - f->seek(0xB0); + f->seek(file_offset + 0xB0); f->read(metadata_signature, 0x28); - f->seek(0xD8); f->read(header_signature, 0x28); // 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. { int metadata_buf_size = block_num * 0x10; - unsigned char *metadata_buf = new unsigned char[metadata_buf_size]; - f->seek(metadata_offset); - f->read(metadata_buf, metadata_buf_size); - sha1(metadata_buf, metadata_buf_size, signature_hash); - delete[] metadata_buf; + std::unique_ptr metadata_buf(new u8[metadata_buf_size]); + f->seek(file_offset + metadata_offset); + f->read(metadata_buf.get(), metadata_buf_size); + sha1(metadata_buf.get(), metadata_buf_size, signature_hash); } else - sha1(metadata, metadata_size, signature_hash); + sha1(metadata.get(), metadata_size, signature_hash); 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) 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. // Setup header signature r and s. 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. memset(signature_hash, 0, 20); - unsigned char *header_buf = new unsigned char[0xD8]; - f->seek(0x00); - f->read(header_buf, 0xD8); - sha1(header_buf, 0xD8, signature_hash ); - delete[] header_buf; + std::unique_ptr header_buf(new u8[0xD8]); + f->seek(file_offset); + f->read(header_buf.get(), 0xD8); + sha1(header_buf.get(), 0xD8, signature_hash ); - if (ecdsa_verify(signature_hash, signature_r, signature_s)) - LOG_NOTICE(LOADER, "EDAT: Header signature is valid!"); - else + if (!ecdsa_verify(signature_hash, signature_r, signature_s)) LOG_WARNING(LOADER, "EDAT: Header signature is invalid!"); } } - // Cleanup. - delete[] metadata; - delete[] empty_metadata; - 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 dev_hash_result = 0; - int file_name_length = (int) strlen(file_name); - unsigned char *buf = new unsigned char[0x30 + file_name_length]; - unsigned char dev[0x60]; - unsigned char key[0x10]; - memset(dev, 0, 0x60); - memset(key, 0, 0x10); - + const int file_name_length = (int) strlen(file_name); + std::unique_ptr buf(new u8[0x30 + file_name_length]); + unsigned char dev[0x60] = { 0 }; + unsigned char key[0x10] = { 0 }; + // Build the title buffer (content_id + file_name). - memcpy(buf, npd->content_id, 0x30); - memcpy(buf + 0x30, file_name, file_name_length); + memcpy(buf.get(), npd->content_id, 0x30); + memcpy(buf.get() + 0x30, file_name, file_name_length); // Build the dev buffer (first 0x60 bytes of NPD header in big-endian). 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); // 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) { @@ -624,7 +586,7 @@ int validate_npd_hashes(const char* file_name, unsigned char *klicensee, NPD_HEA else { // 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. 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); } -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 edat_header[0x10]; input->read(npd_header, sizeof(npd_header)); input->read(edat_header, sizeof(edat_header)); - memcpy(NPD->magic, npd_header, 4); - NPD->version = swap32(*(int*)&npd_header[4]); - NPD->license = swap32(*(int*)&npd_header[8]); - NPD->type = swap32(*(int*)&npd_header[12]); - memcpy(NPD->content_id, (unsigned char*)&npd_header[16], 0x30); - memcpy(NPD->digest, (unsigned char*)&npd_header[64], 0x10); - memcpy(NPD->title_hash, (unsigned char*)&npd_header[80], 0x10); - memcpy(NPD->dev_hash, (unsigned char*)&npd_header[96], 0x10); - NPD->unk1 = swap64(*(u64*)&npd_header[112]); - NPD->unk2 = swap64(*(u64*)&npd_header[120]); + memcpy(&NPD.magic, npd_header, 4); + NPD.version = swap32(*(int*)&npd_header[4]); + NPD.license = swap32(*(int*)&npd_header[8]); + NPD.type = swap32(*(int*)&npd_header[12]); + memcpy(NPD.content_id, (unsigned char*)&npd_header[16], 0x30); + memcpy(NPD.digest, (unsigned char*)&npd_header[64], 0x10); + memcpy(NPD.title_hash, (unsigned char*)&npd_header[80], 0x10); + memcpy(NPD.dev_hash, (unsigned char*)&npd_header[96], 0x10); + NPD.unk1 = swap64(*(u64*)&npd_header[112]); + 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 - 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); - delete NPD; - delete EDAT; 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) { LOG_NOTICE(LOADER, "NPD HEADER"); - LOG_NOTICE(LOADER, "NPD version: %d", NPD->version); - LOG_NOTICE(LOADER, "NPD license: %d", NPD->license); - LOG_NOTICE(LOADER, "NPD type: %d", NPD->type); + LOG_NOTICE(LOADER, "NPD version: %d", NPD.version); + LOG_NOTICE(LOADER, "NPD license: %d", NPD.license); + LOG_NOTICE(LOADER, "NPD type: %d", NPD.type); } // Set decryption key. - unsigned char key[0x10]; - memset(key, 0, 0x10); + u8 key[0x10] = { 0 }; // Check EDAT/SDAT flag. - if ((EDAT->flags & SDAT_FLAG) == SDAT_FLAG) + if ((EDAT.flags & SDAT_FLAG) == SDAT_FLAG) { if (verbose) { LOG_NOTICE(LOADER, "SDAT HEADER"); - LOG_NOTICE(LOADER, "SDAT flags: 0x%08X", EDAT->flags); - 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 flags: 0x%08X", EDAT.flags); + LOG_NOTICE(LOADER, "SDAT block size: 0x%08X", EDAT.block_size); + LOG_NOTICE(LOADER, "SDAT file size: 0x%08X", (u64)EDAT.file_size); } // Generate SDAT key. - xor_key(key, NPD->dev_hash, SDAT_KEY, 0x10); + xor_key(key, NPD.dev_hash, SDAT_KEY); } else { if (verbose) { LOG_NOTICE(LOADER, "EDAT HEADER"); - LOG_NOTICE(LOADER, "EDAT flags: 0x%08X", EDAT->flags); - 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 flags: 0x%08X", EDAT.flags); + LOG_NOTICE(LOADER, "EDAT block size: 0x%08X", EDAT.block_size); + LOG_NOTICE(LOADER, "EDAT file size: 0x%08X", (u64)EDAT.file_size); } // Perform header validation (EDAT only). char real_file_name[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, verbose)) { // 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!"); - delete NPD; - delete EDAT; return 1; } } // 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). + else if ((NPD.license & 0x2) == 0x2) // Type 2: Use key from RAP file (RIF key). { memcpy(key, rifkey, 0x10); @@ -751,16 +711,12 @@ bool extract_data(const fs::file* input, const fs::file* output, const char* inp if (!test) { LOG_ERROR(LOADER, "EDAT: A valid RAP file is needed for this EDAT file!"); - delete NPD; - delete EDAT; 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!"); - delete NPD; - delete EDAT; return 1; } @@ -785,46 +741,33 @@ bool extract_data(const fs::file* input, const fs::file* output, const char* inp LOG_NOTICE(LOADER, "%02X", key[i]); } - LOG_NOTICE(LOADER, "EDAT: Parsing data..."); - if (check_data(key, EDAT, NPD, input, verbose)) + input->seek(0); + if (check_data(key, &EDAT, &NPD, input, verbose)) { LOG_ERROR(LOADER, "EDAT: Data parsing failed!"); - delete NPD; - delete EDAT; return 1; } - else - LOG_NOTICE(LOADER, "EDAT: Data successfully parsed!"); - LOG_NOTICE(LOADER, "EDAT: Decrypting data..."); - if (decrypt_data(input, output, EDAT, NPD, key, verbose)) + input->seek(0); + if (decrypt_data(input, output, &EDAT, &NPD, key, verbose)) { LOG_ERROR(LOADER, "EDAT: Data decryption failed!"); - delete NPD; - delete EDAT; return 1; } - else - LOG_NOTICE(LOADER, "EDAT: Data successfully decrypted!"); - - delete NPD; - delete EDAT; 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. - fs::file input(input_file_name); - fs::file output(output_file_name, fs::rewrite); + input.seek(0); // Set keys (RIF and DEVKLIC). - unsigned char rifkey[0x10]; - unsigned char devklic[0x10]; - memset(rifkey, 0, 0x10); - memset(devklic, 0, 0x10); - + unsigned char rifkey[0x10] = { 0 }; + unsigned char devklic[0x10] = { 0 }; + // Select the EDAT key mode. switch (mode) { @@ -858,21 +801,13 @@ int DecryptEDAT(const std::string& input_file_name, const std::string& output_fi else { LOG_ERROR(LOADER, "EDAT: Invalid custom klic!"); - return -1; + return fs::file{}; } - break; } default: LOG_ERROR(LOADER, "EDAT: Invalid mode!"); - return -1; - } - - // Check the input/output files. - if (!input || !output) - { - LOG_ERROR(LOADER, "EDAT: Failed to open files!"); - return -1; + return fs::file{}; } // 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); - unsigned char rapkey[0x10]; - memset(rapkey, 0, 0x10); + unsigned char rapkey[0x10] = { 0 }; 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. - if (extract_data(&input, &output, input_file_name.c_str(), devklic, rifkey, verbose)) + fs::file output = fs::make_stream>(); + if (extract_all_data(&input, &output, input_file_name.c_str(), devklic, rifkey, verbose)) { output.release(); - fs::remove_file(output_file_name); - return -1; + return fs::file{}; } - 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(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(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(writeOffset - startOffset, size); + + memmove(data, &data_buf[startOffset], bytesWrote); + return bytesWrote; } diff --git a/rpcs3/Crypto/unedat.h b/rpcs3/Crypto/unedat.h index 4513902e83..14ba8876bb 100644 --- a/rpcs3/Crypto/unedat.h +++ b/rpcs3/Crypto/unedat.h @@ -4,33 +4,95 @@ #include #include "utils.h" -#define SDAT_FLAG 0x01000000 -#define EDAT_COMPRESSED_FLAG 0x00000001 -#define EDAT_FLAG_0x02 0x00000002 -#define EDAT_ENCRYPTED_KEY_FLAG 0x00000008 -#define EDAT_FLAG_0x10 0x00000010 -#define EDAT_FLAG_0x20 0x00000020 -#define EDAT_DEBUG_DATA_FLAG 0x80000000 +constexpr u32 SDAT_FLAG = 0x01000000; +constexpr u32 EDAT_COMPRESSED_FLAG = 0x00000001; +constexpr u32 EDAT_FLAG_0x02 = 0x00000002; +constexpr u32 EDAT_ENCRYPTED_KEY_FLAG = 0x00000008; +constexpr u32 EDAT_FLAG_0x10 = 0x00000010; +constexpr u32 EDAT_FLAG_0x20 = 0x00000020; +constexpr u32 EDAT_DEBUG_DATA_FLAG = 0x80000000; -typedef struct +struct NPD_HEADER { - unsigned char magic[4]; - int version; - int license; - int type; - unsigned char content_id[0x30]; - unsigned char digest[0x10]; - unsigned char title_hash[0x10]; - unsigned char dev_hash[0x10]; - unsigned long long unk1; - unsigned long long unk2; -} NPD_HEADER; + u32 magic; + s32 version; + s32 license; + s32 type; + u8 content_id[0x30]; + u8 digest[0x10]; + u8 title_hash[0x10]; + u8 dev_hash[0x10]; + u64 unk1; + u64 unk2; +}; -typedef struct +struct EDAT_HEADER { - int flags; - int block_size; - unsigned long long file_size; -} EDAT_HEADER; + s32 flags; + s32 block_size; + u64 file_size; +}; -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 data_buf; + u64 data_buf_size{0}; + + std::array 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; } +}; diff --git a/rpcs3/Crypto/utils.cpp b/rpcs3/Crypto/utils.cpp index 9271e85d0b..9e66e993b2 100644 --- a/rpcs3/Crypto/utils.cpp +++ b/rpcs3/Crypto/utils.cpp @@ -9,28 +9,10 @@ #include // 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); -} - -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++) + for(int i = 0; i < 0x10; 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) { - unsigned char *buffer = new unsigned char[size]; srand((u32)time(0)); - int i; - for(i = 0; i < size; i++) - buffer[i] = (unsigned char)(rand() & 0xFF); - - memcpy(dest, buffer, size); - - delete[] buffer; + for(int i = 0; i < size; i++) + dest[i] = (unsigned char)(rand() & 0xFF); } // Hex string conversion auxiliary functions. @@ -102,19 +78,19 @@ void hex_to_bytes(unsigned char *data, const char *hex_str, unsigned int str_len bool is_hex(const char* hex_str, unsigned int str_length) { - static const char hex_chars[] = "0123456789abcdefABCDEF"; + static const char hex_chars[] = "0123456789abcdefABCDEF"; - if (hex_str == NULL) - return false; + if (hex_str == NULL) + return false; - unsigned int i; - for (i = 0; i < str_length; i++) + unsigned int i; + for (i = 0; i < str_length; i++) { if (strchr(hex_chars, hex_str[i]) == 0) return false; } - return true; + return true; } // Crypto functions (AES128-CBC, AES128-ECB, SHA1-HMAC and AES-CMAC). @@ -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) { - unsigned char *out = new unsigned char[key_len]; + std::unique_ptr 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++) - { - if (out[i] != hash[i]) - { - delete[] out; - return false; - } - } - - delete[] out; - - return true; + return std::memcmp(out.get(), hash, hash_len) == 0; } 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) { - unsigned char *out = new unsigned char[key_len]; + std::unique_ptr out(new u8[key_len]); aes_context ctx; 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++) - { - if (out[i] != hash[i]) - { - delete[] out; - return false; - } - } - - delete[] out; - - return true; + return std::memcmp(out.get(), hash, hash_len) == 0; } void cmac_hash_forge(unsigned char *key, int key_len, unsigned char *in, int in_len, unsigned char *hash) diff --git a/rpcs3/Crypto/utils.h b/rpcs3/Crypto/utils.h index a4471733c2..5565cc0060 100644 --- a/rpcs3/Crypto/utils.h +++ b/rpcs3/Crypto/utils.h @@ -15,10 +15,40 @@ #include "ec.h" // Auxiliary functions (endian swap, xor, prng and file name). -u16 swap16(u16 i); -u32 swap32(u32 i); -u64 swap64(u64 i); -void xor_key(unsigned char *dest, unsigned char *src1, unsigned char *src2, int size); +inline u16 swap16(u16 i) +{ +#if defined(__GNUG__) + 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); char* extract_file_name(const char* file_path, char real_file_name[MAX_PATH]); diff --git a/rpcs3/Emu/Cell/Modules/sceNp.cpp b/rpcs3/Emu/Cell/Modules/sceNp.cpp index e8f200fce8..b1515ad3e7 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNp.cpp @@ -89,11 +89,16 @@ s32 npDrmIsAvailable(vm::cptr k_licensee_addr, vm::cptr 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. + 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()); + fs::remove_file(enc_drm_path_local); fs::rename(dec_drm_path_local, enc_drm_path_local); } diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index 10fd45e767..b629f5b8ca 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -3,6 +3,8 @@ #include +#include "Emu/Cell/PPUThread.h" +#include "Crypto/unedat.h" #include "Emu/VFS.h" #include "Emu/IdManager.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_host_root; +bool verify_mself(u32 fd, fs::file const& mself_file) +{ + FsMselfHeader mself_header; + if (!mself_file.read(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) { // TODO @@ -75,8 +108,6 @@ struct lv2_file::file_view : fs::file_base u64 read(void* buffer, u64 size) override { - std::lock_guard lock(m_file->mp->mutex); - const u64 old_pos = m_file->file.pos(); const u64 new_pos = m_file->file.seek(m_off + m_pos); const u64 result = m_file->file.read(buffer, size); @@ -182,7 +213,17 @@ error_code sys_fs_open(vm::cptr path, s32 flags, vm::ptr 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 } @@ -211,6 +252,30 @@ error_code sys_fs_open(vm::cptr path, s32 flags, vm::ptr fd, s32 mode return CELL_ENOENT; } + if ((flags & CELL_FS_O_MSELF) && (!verify_mself(*fd, file))) + return CELL_ENOTMSELF; + + // sdata encryption arg flag + const be_t* casted_args = static_cast *>(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(magic); + file.seek(0); + if (magic == "NPD\0"_u32) + { + auto sdata_file = std::make_unique(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(path.get_ptr(), std::move(file), mode, flags)) { *fd = id; @@ -559,8 +624,16 @@ error_code sys_fs_fcntl(u32 fd, u32 op, vm::ptr _arg, u32 _size) return CELL_EBADF; } - // TODO - if (const u32 id = idm::make(file->mp, lv2_file::make_view(file, arg->offset), file->mode, file->flags)) + auto sdata_file = std::make_unique(lv2_file::make_view(file, arg->offset)); + + if (!sdata_file->ReadHeader()) + { + return CELL_EFSSPECIFIC; + } + + fs::file stream; + stream.reset(std::move(sdata_file)); + if (const u32 id = idm::make(file->mp, std::move(stream), file->mode, file->flags)) { arg->out_code = CELL_OK; arg->out_fd = id; diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.h b/rpcs3/Emu/Cell/lv2/sys_fs.h index 6c1c92a887..8abb663b06 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.h +++ b/rpcs3/Emu/Cell/lv2/sys_fs.h @@ -91,6 +91,26 @@ struct CellFsUtimbuf CHECK_SIZE_ALIGN(CellFsUtimbuf, 16, 4); +// MSelf file structs +struct FsMselfHeader +{ + be_t m_magic; + be_t m_format_version; + be_t m_file_size; + be_t m_entry_num; + be_t m_entry_size; + u8 m_reserve[40]; + +}; + +struct FsMselfEntry +{ + char m_name[32]; + be_t m_offset; + be_t m_size; + u8 m_reserve[16]; +}; + struct lv2_fs_mount_point; struct lv2_fs_object