Seregon/ShadPKG

A tool for deriving PKG packet encryption keys for ps4 written in c++

C++/47.3 KB/No license
core/file_format/trp.cpp
ShadPKG / core / file_format / trp.cpp
1// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3 
4#include "common/config.h"
5#include "common/logging/log.h"
6#include "common/path_util.h"
7#include "trp.h"
8 
9TRP::TRP() = default;
10TRP::~TRP() = default;
11 
12void TRP::GetNPcommID(const std::filesystem::path& trophyPath, int index) {
13 std::filesystem::path trpPath = trophyPath / "sce_sys/npbind.dat";
14 Common::FS::IOFile npbindFile(trpPath, Common::FS::FileAccessMode::Read);
15 if (!npbindFile.IsOpen()) {
16 LOG_CRITICAL(Common_Filesystem, "Failed to open npbind.dat file");
17 return;
18 }
19 if (!npbindFile.Seek(0x84 + (index * 0x180))) {
20 LOG_CRITICAL(Common_Filesystem, "Failed to seek to NPbind offset");
21 return;
22 }
23 npbindFile.ReadRaw<u8>(np_comm_id.data(), 12);
24 std::fill(np_comm_id.begin() + 12, np_comm_id.end(), 0); // fill with 0, we need 16 bytes.
25}
26 
27static void removePadding(std::vector<u8>& vec) {
28 for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
29 if (*it == '>') {
30 size_t pos = std::distance(vec.begin(), it.base());
31 vec.resize(pos);
32 break;
33 }
34 }
35}
36 
37static void hexToBytes(const char* hex, unsigned char* dst) {
38 for (size_t i = 0; hex[i] != 0; i++) {
39 const unsigned char value = (hex[i] < 0x3A) ? (hex[i] - 0x30) : (hex[i] - 0x37);
40 dst[i / 2] |= ((i % 2) == 0) ? (value << 4) : (value);
41 }
42}
43 
44bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string titleId) {
45 std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/";
46 if (!std::filesystem::exists(gameSysDir)) {
47 LOG_CRITICAL(Common_Filesystem, "Game sce_sys directory doesn't exist");
48 return false;
49 }
50 
51 const auto user_key_str = Config::getTrophyKey();
52 if (user_key_str.size() != 32) {
53 LOG_CRITICAL(Common_Filesystem, "Trophy decryption key is not specified");
54 return false;
55 }
56 
57 std::array<CryptoPP::byte, 16> user_key{};
58 hexToBytes(user_key_str.c_str(), user_key.data());
59 
60 for (int index = 0; const auto& it : std::filesystem::directory_iterator(gameSysDir)) {
61 if (it.is_regular_file()) {
62 GetNPcommID(trophyPath, index);
63 
64 Common::FS::IOFile file(it.path(), Common::FS::FileAccessMode::Read);
65 if (!file.IsOpen()) {
66 LOG_CRITICAL(Common_Filesystem, "Unable to open trophy file for read");
67 return false;
68 }
69 
70 TrpHeader header;
71 file.Read(header);
72 if (header.magic != 0xDCA24D00) {
73 LOG_CRITICAL(Common_Filesystem, "Wrong trophy magic number");
74 return false;
75 }
76 
77 s64 seekPos = sizeof(TrpHeader);
78 std::filesystem::path trpFilesPath(
79 Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / titleId /
80 "TrophyFiles" / it.path().stem());
81 std::filesystem::create_directories(trpFilesPath / "Icons");
82 std::filesystem::create_directories(trpFilesPath / "Xml");
83 
84 for (int i = 0; i < header.entry_num; i++) {
85 if (!file.Seek(seekPos)) {
86 LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
87 return false;
88 }
89 seekPos += (s64)header.entry_size;
90 TrpEntry entry;
91 file.Read(entry);
92 std::string_view name(entry.entry_name);
93 if (entry.flag == 0 && name.find("TROP") != std::string::npos) { // PNG
94 if (!file.Seek(entry.entry_pos)) {
95 LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
96 return false;
97 }
98 std::vector<u8> icon(entry.entry_len);
99 file.Read(icon);
100 Common::FS::IOFile::WriteBytes(trpFilesPath / "Icons" / name, icon);
101 }
102 if (entry.flag == 3 && np_comm_id[0] == 'N' &&
103 np_comm_id[1] == 'P') { // ESFM, encrypted.
104 if (!file.Seek(entry.entry_pos)) {
105 LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
106 return false;
107 }
108 file.Read(esfmIv); // get iv key.
109 // Skip the first 16 bytes which are the iv key on every entry as we want a
110 // clean xml file.
111 std::vector<u8> ESFM(entry.entry_len - iv_len);
112 std::vector<u8> XML(entry.entry_len - iv_len);
113 if (!file.Seek(entry.entry_pos + iv_len)) {
114 LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry + iv offset");
115 return false;
116 }
117 file.Read(ESFM);
118 crypto.decryptEFSM(user_key, np_comm_id, esfmIv, ESFM, XML); // decrypt
119 removePadding(XML);
120 std::string xml_name = entry.entry_name;
121 size_t pos = xml_name.find("ESFM");
122 if (pos != std::string::npos)
123 xml_name.replace(pos, xml_name.length(), "XML");
124 std::filesystem::path path = trpFilesPath / "Xml" / xml_name;
125 size_t written = Common::FS::IOFile::WriteBytes(path, XML);
126 if (written != XML.size()) {
127 LOG_CRITICAL(
128 Common_Filesystem,
129 "Trophy XML {} write failed, wanted to write {} bytes, wrote {}",
130 fmt::UTF(path.u8string()), XML.size(), written);
131 }
132 }
133 }
134 }
135 index++;
136 }
137 return true;
138}
139