// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" #include "common/path_util.h" #include "trp.h" TRP::TRP() = default; TRP::~TRP() = default; void TRP::GetNPcommID(const std::filesystem::path& trophyPath, int index) { std::filesystem::path trpPath = trophyPath / "sce_sys/npbind.dat"; Common::FS::IOFile npbindFile(trpPath, Common::FS::FileAccessMode::Read); if (!npbindFile.IsOpen()) { LOG_CRITICAL(Common_Filesystem, "Failed to open npbind.dat file"); return; } if (!npbindFile.Seek(0x84 + (index * 0x180))) { LOG_CRITICAL(Common_Filesystem, "Failed to seek to NPbind offset"); return; } npbindFile.ReadRaw(np_comm_id.data(), 12); std::fill(np_comm_id.begin() + 12, np_comm_id.end(), 0); // fill with 0, we need 16 bytes. } static void removePadding(std::vector& vec) { for (auto it = vec.rbegin(); it != vec.rend(); ++it) { if (*it == '>') { size_t pos = std::distance(vec.begin(), it.base()); vec.resize(pos); break; } } } bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string titleId) { std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/"; if (!std::filesystem::exists(gameSysDir)) { LOG_CRITICAL(Common_Filesystem, "Game sce_sys directory doesn't exist"); return false; } for (int index = 0; const auto& it : std::filesystem::directory_iterator(gameSysDir)) { if (it.is_regular_file()) { GetNPcommID(trophyPath, index); Common::FS::IOFile file(it.path(), Common::FS::FileAccessMode::Read); if (!file.IsOpen()) { LOG_CRITICAL(Common_Filesystem, "Unable to open trophy file for read"); return false; } TrpHeader header; file.Read(header); if (header.magic != 0xDCA24D00) { LOG_CRITICAL(Common_Filesystem, "Wrong trophy magic number"); return false; } s64 seekPos = sizeof(TrpHeader); std::filesystem::path trpFilesPath( Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / titleId / "TrophyFiles" / it.path().stem()); std::filesystem::create_directories(trpFilesPath / "Icons"); std::filesystem::create_directory(trpFilesPath / "Xml"); for (int i = 0; i < header.entry_num; i++) { if (!file.Seek(seekPos)) { LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset"); return false; } seekPos += (s64)header.entry_size; TrpEntry entry; file.Read(entry); std::string_view name(entry.entry_name); if (entry.flag == 0 && name.find("TROP") != std::string::npos) { // PNG if (!file.Seek(entry.entry_pos)) { LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset"); return false; } std::vector icon(entry.entry_len); file.Read(icon); Common::FS::IOFile::WriteBytes(trpFilesPath / "Icons" / name, icon); } if (entry.flag == 3 && np_comm_id[0] == 'N' && np_comm_id[1] == 'P') { // ESFM, encrypted. if (!file.Seek(entry.entry_pos)) { LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset"); return false; } file.Read(esfmIv); // get iv key. // Skip the first 16 bytes which are the iv key on every entry as we want a // clean xml file. std::vector ESFM(entry.entry_len - iv_len); std::vector XML(entry.entry_len - iv_len); if (!file.Seek(entry.entry_pos + iv_len)) { LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry + iv offset"); return false; } file.Read(ESFM); crypto.decryptEFSM(np_comm_id, esfmIv, ESFM, XML); // decrypt removePadding(XML); std::string xml_name = entry.entry_name; size_t pos = xml_name.find("ESFM"); if (pos != std::string::npos) xml_name.replace(pos, xml_name.length(), "XML"); std::filesystem::path path = trpFilesPath / "Xml" / xml_name; size_t written = Common::FS::IOFile::WriteBytes(path, XML); if (written != XML.size()) { LOG_CRITICAL( Common_Filesystem, "Trophy XML {} write failed, wanted to write {} bytes, wrote {}", fmt::UTF(path.u8string()), XML.size(), written); } } } } index++; } return true; }