Seregon/ShadPKG

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

C++/47.3 KB/No license
core/file_format/iso_extractor.cpp
ShadPKG / core / file_format / iso_extractor.cpp
1#include "iso_extractor.h"
2#include "common/logging/log.h"
3#include <cstdlib>
4#include <cstring>
5#include <fstream>
6 
7namespace ShadPKG {
8 
9ISOExtractor::ISOExtractor() = default;
10 
11ISOExtractor::~ISOExtractor() = default;
12 
13bool ISOExtractor::isValidISO(const std::filesystem::path &isoPath) {
14 if (!std::filesystem::exists(isoPath)) {
15 return false;
16 }
17 
18 std::ifstream file(isoPath, std::ios::binary);
19 if (!file.is_open()) {
20 return false;
21 }
22 
23 // Check for ISO 9660 signature at offset 0x8001
24 file.seekg(0x8001);
25 char sig[5] = {0};
26 file.read(sig, 5);
27
28 // Check for "CD001" (ISO 9660) or UDF signatures
29 if (std::strncmp(sig, "CD001", 5) == 0) {
30 return true;
31 }
32 
33 // Check for UDF signature
34 file.seekg(0x8000);
35 file.read(sig, 5);
36 if (std::strncmp(sig, "BEA01", 5) == 0) {
37 return true;
38 }
39 
40 return false;
41}
42 
43std::string ISOExtractor::detectISOType(const std::filesystem::path &isoPath) {
44 std::ifstream file(isoPath, std::ios::binary);
45 if (!file.is_open()) {
46 return "unknown";
47 }
48 
49 // Check for PS3 SYSTEM.CNF marker
50 file.seekg(0);
51 std::vector<char> buffer(1024);
52 file.read(buffer.data(), buffer.size());
53
54 std::string content(buffer.begin(), buffer.end());
55 if (content.find("BOOT2") != std::string::npos) {
56 return "PS3";
57 }
58 if (content.find("BOOT") != std::string::npos) {
59 return "PS2";
60 }
61 
62 return "unknown";
63}
64 
65ISOExtractor::ExtractionResult
66ISOExtractor::extract(const std::filesystem::path &isoPath,
67 const std::filesystem::path &outputDir) {
68 ExtractionResult result;
69 
70 if (!isValidISO(isoPath)) {
71 result.errorMessage = "Invalid ISO file";
72 LOG_ERROR(Common, "Invalid ISO: {}", isoPath.string());
73 return result;
74 }
75 
76 std::filesystem::path mountPoint = outputDir / "iso_mount";
77 std::filesystem::create_directories(mountPoint);
78 
79 // Mount ISO
80 if (!mountISO(isoPath, mountPoint)) {
81 result.errorMessage = "Failed to mount ISO";
82 LOG_ERROR(Common, "Failed to mount ISO: {}", isoPath.string());
83 return result;
84 }
85 
86 // Extract metadata
87 extractMetadata(mountPoint, result.gameTitle, result.gameID);
88 
89 // Find main executable
90 std::filesystem::path exePath = findMainExecutable(mountPoint);
91 if (exePath.empty()) {
92 result.errorMessage = "Could not find main executable";
93 LOG_ERROR(Common, "No executable found in ISO");
94 unmountISO(mountPoint);
95 return result;
96 }
97 
98 // Copy executable to output directory BEFORE unmounting
99 std::filesystem::path outputExe = outputDir / exePath.filename();
100 try {
101 LOG_INFO(Common, "Copying executable from {} to {}", exePath.string(), outputExe.string());
102 std::filesystem::copy_file(exePath, outputExe,
103 std::filesystem::copy_options::overwrite_existing);
104 result.extractedExecutable = outputExe;
105 result.extractedFiles.push_back(outputExe);
106 LOG_INFO(Common, "Successfully copied executable");
107 } catch (const std::exception &e) {
108 result.errorMessage = std::string("Failed to copy executable: ") + e.what();
109 LOG_ERROR(Common, "{}", result.errorMessage);
110 unmountISO(mountPoint);
111 return result;
112 }
113 
114 // Unmount ISO
115 unmountISO(mountPoint);
116 
117 result.success = true;
118 LOG_INFO(Common, "ISO extraction successful: {}", result.extractedExecutable.string());
119 return result;
120}
121 
122bool ISOExtractor::extractFile(const std::filesystem::path &isoPath,
123 const std::string &internalPath,
124 const std::filesystem::path &outputPath) {
125 std::filesystem::path mountPoint = std::filesystem::temp_directory_path() / "iso_temp";
126 std::filesystem::create_directories(mountPoint);
127 
128 if (!mountISO(isoPath, mountPoint)) {
129 return false;
130 }
131 
132 std::filesystem::path sourcePath = mountPoint / internalPath;
133 bool success = false;
134 
135 if (std::filesystem::exists(sourcePath)) {
136 try {
137 std::filesystem::copy_file(sourcePath, outputPath,
138 std::filesystem::copy_options::overwrite_existing);
139 success = true;
140 } catch (const std::exception &e) {
141 LOG_ERROR(Common, "Failed to copy file: {}", e.what());
142 }
143 }
144 
145 unmountISO(mountPoint);
146 return success;
147}
148 
149bool ISOExtractor::mountISO(const std::filesystem::path &isoPath,
150 std::filesystem::path &mountPoint) {
151#ifdef __APPLE__
152 // macOS: use hdiutil
153 std::string cmd = "hdiutil attach -readonly \"" + isoPath.string() +
154 "\" -mountpoint \"" + mountPoint.string() + "\" 2>/dev/null";
155 int ret = std::system(cmd.c_str());
156 return ret == 0;
157#elif defined(__linux__)
158 // Linux: use mount
159 std::string cmd = "sudo mount -o loop,ro \"" + isoPath.string() +
160 "\" \"" + mountPoint.string() + "\" 2>/dev/null";
161 int ret = std::system(cmd.c_str());
162 return ret == 0;
163#else
164 LOG_ERROR(Common, "ISO mounting not supported on this platform");
165 return false;
166#endif
167}
168 
169bool ISOExtractor::unmountISO(const std::filesystem::path &mountPoint) {
170#ifdef __APPLE__
171 std::string cmd = "hdiutil detach \"" + mountPoint.string() + "\" 2>/dev/null";
172 std::system(cmd.c_str());
173 return true;
174#elif defined(__linux__)
175 std::string cmd = "sudo umount \"" + mountPoint.string() + "\" 2>/dev/null";
176 std::system(cmd.c_str());
177 return true;
178#else
179 return false;
180#endif
181}
182 
183std::filesystem::path ISOExtractor::findMainExecutable(
184 const std::filesystem::path &mountPoint) {
185 // PS3: Look for .elf files in BIN/ directory
186 std::filesystem::path binDir = mountPoint / "BIN";
187 if (std::filesystem::exists(binDir)) {
188 for (const auto &entry : std::filesystem::directory_iterator(binDir)) {
189 if (entry.is_regular_file()) {
190 std::string filename = entry.path().filename().string();
191 // PS3 executables are usually *.BIN or *.elf
192 if (filename.find(".BIN") != std::string::npos ||
193 filename.find(".elf") != std::string::npos ||
194 filename.find(".ELF") != std::string::npos) {
195 return entry.path();
196 }
197 }
198 }
199 }
200 
201 // PS2: Look for *.ELF in root or BIN/
202 for (const auto &entry : std::filesystem::recursive_directory_iterator(mountPoint)) {
203 if (entry.is_regular_file()) {
204 std::string filename = entry.path().filename().string();
205 if (filename.find(".ELF") != std::string::npos ||
206 filename.find(".elf") != std::string::npos) {
207 return entry.path();
208 }
209 }
210 }
211 
212 return std::filesystem::path();
213}
214 
215bool ISOExtractor::extractMetadata(const std::filesystem::path &mountPoint,
216 std::string &gameTitle,
217 std::string &gameID) {
218 // Try to read SYSTEM.CNF for PS2/PS3
219 std::filesystem::path systemCnf = mountPoint / "SYSTEM.CNF";
220 if (std::filesystem::exists(systemCnf)) {
221 std::ifstream file(systemCnf);
222 std::string line;
223 while (std::getline(file, line)) {
224 if (line.find("TITLE") != std::string::npos) {
225 size_t pos = line.find('=');
226 if (pos != std::string::npos) {
227 gameTitle = line.substr(pos + 1);
228 // Trim whitespace
229 gameTitle.erase(0, gameTitle.find_first_not_of(" \t"));
230 gameTitle.erase(gameTitle.find_last_not_of(" \t") + 1);
231 }
232 }
233 }
234 }
235 
236 // Try to extract game ID from filename or directory
237 for (const auto &entry : std::filesystem::directory_iterator(mountPoint)) {
238 std::string name = entry.path().filename().string();
239 if (name.find("SLUS") != std::string::npos ||
240 name.find("SLPM") != std::string::npos ||
241 name.find("SCUS") != std::string::npos) {
242 gameID = name;
243 break;
244 }
245 }
246 
247 return !gameTitle.empty() || !gameID.empty();
248}
249 
250} // namespace ShadPKG
251