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