diff --git a/rpcs3/Crypto/unedat.h b/rpcs3/Crypto/unedat.h index b438806233..0ea7cc7176 100644 --- a/rpcs3/Crypto/unedat.h +++ b/rpcs3/Crypto/unedat.h @@ -57,6 +57,7 @@ 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 void read_npd_edat_header(const fs::file* input, NPD_HEADER& NPD, EDAT_HEADER& EDAT); extern bool VerifyEDATHeaderWithKLicense(const fs::file& input, const std::string& input_file_name, const u8* custom_klic, NPD_HEADER *npd_out = nullptr); u128 GetEdatRifKeyFromRapFile(const fs::file& rap_file); diff --git a/rpcs3/Crypto/unself.cpp b/rpcs3/Crypto/unself.cpp index 60a8596bd5..95831223f0 100644 --- a/rpcs3/Crypto/unself.cpp +++ b/rpcs3/Crypto/unself.cpp @@ -1497,9 +1497,42 @@ bool verify_npdrm_self_headers(const fs::file& self, u8* klic_key, NPD_HEADER* n } } } + return true; } +bool get_npdrm_self_header(const fs::file& self, NPD_HEADER &npd_out) +{ + if (!self) + return false; + + self.seek(0); + + if (self.size() >= 4 && self.read() == "SCE\0"_u32 && !IsDebugSelf(self)) + { + // Check the ELF file class (32 or 64 bit). + const bool isElf32 = IsSelfElf32(self); + + // Start the decrypter on this SELF file. + SELFDecrypter self_dec(self); + + // Load the SELF file headers. + if (!self_dec.LoadHeaders(isElf32)) + { + self_log.error("Failed to load SELF file headers!"); + return false; + } + + if (const NPD_HEADER* npd = self_dec.GetNPDHeader()) + { + memcpy(&npd_out, npd, sizeof(NPD_HEADER)); + return true; + } + } + + return false; +} + u128 get_default_self_klic() { return std::bit_cast(NP_KLIC_FREE); diff --git a/rpcs3/Crypto/unself.h b/rpcs3/Crypto/unself.h index b6ed6445a2..3ee13ef03a 100644 --- a/rpcs3/Crypto/unself.h +++ b/rpcs3/Crypto/unself.h @@ -498,5 +498,6 @@ private: fs::file decrypt_self(fs::file elf_or_self, u8* klic_key = nullptr, SelfAdditionalInfo* additional_info = nullptr); bool verify_npdrm_self_headers(const fs::file& self, u8* klic_key = nullptr, NPD_HEADER* npd_out = nullptr); +bool get_npdrm_self_header(const fs::file& self, NPD_HEADER& npd); u128 get_default_self_klic(); diff --git a/rpcs3/Emu/Cell/Modules/sceNp.cpp b/rpcs3/Emu/Cell/Modules/sceNp.cpp index a555135a5e..4a0648f4c5 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNp.cpp @@ -608,15 +608,79 @@ error_code sceNpDrmExecuteGamePurchase() error_code sceNpDrmGetTimelimit(vm::cptr path, vm::ptr time_remain) { - sceNp.todo("sceNpDrmGetTimelimit(path=%s, time_remain=*0x%x)", path, time_remain); + sceNp.warning("sceNpDrmGetTimelimit(path=%s, time_remain=*0x%x)", path, time_remain); if (!path || !time_remain) { return SCE_NP_DRM_ERROR_INVALID_PARAM; } - *time_remain = SCE_NP_DRM_TIME_INFO_ENDLESS; + vm::var sec; + vm::var nsec; + // Get system time (real or fake) to compare to + error_code ret = sys_time_get_current_time(sec, nsec); + if (ret != CELL_OK) + { + return ret; + } + + const std::string enc_drm_path(path.get_ptr(), std::find(path.get_ptr(), path.get_ptr() + 0x100, '\0')); + 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; + NPD_HEADER npd; + + enc_file.read(magic); + enc_file.seek(0); + + // Read expiration time from NPD header which is Unix timestamp in milliseconds + if (magic == "SCE\0"_u32) + { + if (!get_npdrm_self_header(enc_file, npd)) + { + sceNp.error("sceNpDrmGetTimelimit(): Failed to read NPD header from sce file '%s'", enc_drm_path); + return {SCE_NP_DRM_ERROR_BAD_FORMAT, enc_drm_path}; + } + } + else if (magic == "NPD\0"_u32) + { + // edata / sdata files + EDAT_HEADER edat; + read_npd_edat_header(&enc_file, npd, edat); + } + else + { + // Unknown file type + return {SCE_NP_DRM_ERROR_BAD_FORMAT, enc_drm_path}; + } + + // Convert time to milliseconds + s64 msec = *sec * 1000ll + *nsec / 1000ll; + + // Return the remaining time in microseconds + if (npd.activate_time != 0 && msec < npd.activate_time) + { + return SCE_NP_DRM_ERROR_SERVICE_NOT_STARTED; + } + + if (npd.expire_time == 0) + { + *time_remain = SCE_NP_DRM_TIME_INFO_ENDLESS; + return CELL_OK; + } + + if (msec >= npd.expire_time) + { + return SCE_NP_DRM_ERROR_TIME_LIMIT; + } + + *time_remain = (npd.expire_time - msec) * 1000ll; return CELL_OK; }