Seregon/ShadPKG

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

C++/47.3 KB/No license
main.cpp
ShadPKG / main.cpp
1// SPDX-FileCopyrightText: Copyright 2025 shadPKG
2// SPDX-License-Identifier: GPL-2.0-or-later
3 
4#include "common/logging/backend.h"
5#include "common/logging/log.h"
6#include "core/decompiler/DecompilerContext.h"
7#include "core/decompiler/analysis/MemberAccessAnalysis.h"
8#include "core/file_format/iso_extractor.h"
9#include "core/file_format/pkg.h"
10#include "core/file_format/psf.h"
11#include "core/file_format/rif_generator.h"
12#include "core/patcher/elf_reconstruct.h"
13#include "core/patcher/psn_bypass.h"
14#include <filesystem>
15#include <fstream>
16#include <iostream>
17#include <string>
18 
19void showUsage(const char *program_name) {
20 std::cout << R"(
21 
22 __ ______ __ ________
23 _____/ /_ ____ _____/ / __ \/ //_/ ____/
24 / ___/ __ \/ __ `/ __ / /_/ / ,< / / __
25 (__ ) / / / /_/ / /_/ / ____/ /| / /_/ /
26/____/_/ /_/\__,_/\__,_/_/ /_/ |_\____/
27
28https://github.com/seregonwar/shadPKG
29---------------------------------------------
30 
31Usage:
32 )" << program_name
33 << R"( extract [options] <file.pkg> [output_dir] [file.rif]
34 )" << program_name
35 << R"( generate-rif <content_id> [output_dir]
36 )" << program_name
37 << R"( validate-rif <file.rif>
38 )" << program_name
39 << R"( pfs-info [options] <file.pkg>
40 )" << program_name
41 << R"( sfo-info [options] <file.pkg>
42 )" << program_name
43 << R"( patch [options] <eboot.bin>
44 )" << program_name
45 << R"( patch-pkg [options] <minecraft.pkg> [output.pkg]
46 
47Commands:
48 extract - Extracts and decrypts a PKG file and optionally decompiles it.
49 generate-rif - Generates a RIF file for the specified Content ID
50 validate-rif - Validates an existing RIF file
51 pfs-info - Scans the PKG and shows the PFS structure without extracting files
52 sfo-info - Displays param.sfo parameters from a PKG file
53 patch - Apply PSN bypass patches to eboot.bin (Minecraft, etc.)
54 patch-pkg - Complete PKG patcher: extract, scan, patch, and create installation-ready PKG
55 
56General Options:
57 -h, --help - Shows this help
58 
59Options for extract:
60 -i, --input <file> - Input PKG file
61 -o, --output <dir> - Output directory (fallback: creates subdirectory with TitleID)
62 -r, --rif <file> - Path to the RIF file to use during decryption
63
64 --export-project <dir> - Decompile and export as C++ project to <dir>
65 --list-functions - List all analyzed functions (Addr, Size, Name)
66 --decompile <hex_addr> - Decompile a single function to stdout
67 
68Options for pfs-info/sfo-info:
69 --json - Prints output in JSON format
70 -v, --verbose - Shows additional details (types, sizes)
71 -q, --query KEY - Query a single parameter value
72 
73Options for patch:
74 -i, --input <file> - Input eboot.bin file
75 -o, --output <file> - Output patched eboot.bin (default: eboot_patched.bin)
76 --game <id> - Game ID (e.g., CUSA00265 for Minecraft EU)
77 --version <ver> - Game version (e.g., 01.00)
78 --scan - Scan for PSN functions without patching
79 --gen-options - Generate Minecraft options.txt for PSN bypass
80 --elf - Convert output to valid ELF64 (for emulators like shadPS4)
81 
82Options for patch-pkg:
83 -i, --input <file> - Input PKG file
84 -o, --output <file> - Output patched PKG (default: <input>_patched.pkg)
85 --game <id> - Game ID (default: CUSA00265 for Minecraft)
86 --extract-only - Only extract and prepare, don't build final PKG
87 
88Examples:
89 )" << program_name
90 << R"( extract game.pkg ./output
91 )" << program_name
92 << R"( extract --input game.pkg --export-project ./MyDecompiledGame
93 )" << program_name
94 << R"( extract game.pkg -o ./out --list-functions
95)";
96}
97 
98// Helper to read binary file
99std::vector<uint8_t> readFile(const std::filesystem::path &path) {
100 std::ifstream file(path, std::ios::binary | std::ios::ate);
101 if (!file)
102 return {};
103 std::streamsize size = file.tellg();
104 file.seekg(0, std::ios::beg);
105 std::vector<uint8_t> buffer(size);
106 if (file.read((char *)buffer.data(), size))
107 return buffer;
108 return {};
109}
110 
111// Helper to detect file type (PKG vs ISO vs ELF)
112enum class FileType { Unknown, PKG, ISO_PS2, ISO_PS3, ELF };
113 
114FileType detectFileType(const std::filesystem::path &path) {
115 std::ifstream file(path, std::ios::binary);
116 if (!file.is_open())
117 return FileType::Unknown;
118 
119 // Check ELF magic (0x7F 0x45 0x4C 0x46 = "\x7FELF")
120 char magic[4];
121 file.read(magic, 4);
122 if (magic[0] == 0x7F && magic[1] == 0x45 && magic[2] == 0x4C &&
123 magic[3] == 0x46)
124 return FileType::ELF;
125 
126 // Check PKG magic (0x7F434E54 = ".CNT" in big-endian)
127 if (magic[0] == 0x7F && magic[1] == 0x43 && magic[2] == 0x4E &&
128 magic[3] == 0x54)
129 return FileType::PKG;
130 
131 // Check ISO 9660 signature at offset 0x8001
132 file.seekg(0x8001);
133 char sig[5] = {0};
134 file.read(sig, 5);
135 if (std::strncmp(sig, "CD001", 5) == 0) {
136 // Check if PS2 or PS3
137 file.seekg(0);
138 std::vector<char> buffer(2048);
139 file.read(buffer.data(), buffer.size());
140 std::string content(buffer.begin(), buffer.end());
141 if (content.find("BOOT2") != std::string::npos)
142 return FileType::ISO_PS3;
143 return FileType::ISO_PS2;
144 }
145 
146 // Check UDF signature
147 file.seekg(0x8000);
148 file.read(sig, 5);
149 if (std::strncmp(sig, "BEA01", 5) == 0)
150 return FileType::ISO_PS3;
151 
152 return FileType::Unknown;
153}
154 
155int main(int argc, char *argv[]) {
156 Common::Log::Initialize("pkg_extraction.log");
157 Common::Log::SetColorConsoleBackendEnabled(true);
158 LOG_INFO(Common, "[START] Starting PKG extractor with RIF generator");
159 
160 try {
161 if (argc < 2) {
162 showUsage(argv[0]);
163 return 1;
164 }
165 
166 std::string command = argv[1];
167 
168 // Global Help
169 if (command == "-h" || command == "--help" || command == "-help" ||
170 command == "-?") {
171 showUsage(argv[0]);
172 return 0;
173 }
174 
175 // Command to generate RIF file
176 if (command == "generate-rif") {
177 if (argc < 3) {
178 std::cerr << "Error: Content ID required for generate-rif\n";
179 showUsage(argv[0]);
180 return 1;
181 }
182 
183 std::string content_id = argv[2];
184 std::filesystem::path output_dir = (argc >= 4) ? argv[3] : ".";
185 
186 RIFGenerator generator;
187 if (generator.GenerateRIF(content_id, output_dir)) {
188 std::cout << "RIF file successfully generated for: " << content_id
189 << std::endl;
190 return 0;
191 } else {
192 std::cerr << "Error generating RIF file" << std::endl;
193 return 1;
194 }
195 }
196 
197 // Command to validate RIF file
198 if (command == "validate-rif") {
199 if (argc < 3) {
200 std::cerr << "Error: RIF file required for validate-rif\n";
201 showUsage(argv[0]);
202 return 1;
203 }
204 
205 std::filesystem::path rif_path = argv[2];
206 if (RIFGenerator::ValidateRIF(rif_path)) {
207 std::cout << "Valid RIF file: " << rif_path << std::endl;
208 return 0;
209 } else {
210 std::cerr << "Invalid RIF file: " << rif_path << std::endl;
211 return 1;
212 }
213 }
214 
215 // Lightweight command: pfs-info
216 if (command == "pfs-info") {
217 bool as_json = false;
218 std::filesystem::path pkg_path;
219 for (int i = 2; i < argc; ++i) {
220 std::string arg = argv[i];
221 if (arg == "-h" || arg == "--help" || arg == "-help" || arg == "-?") {
222 showUsage(argv[0]);
223 return 0;
224 } else if (arg == "--json") {
225 as_json = true;
226 } else if (!arg.empty() && arg[0] == '-') {
227 std::cerr << "Unrecognized option: " << arg << std::endl;
228 showUsage(argv[0]);
229 return 1;
230 } else if (pkg_path.empty()) {
231 pkg_path = arg;
232 } else {
233 std::cerr << "Excess arguments for pfs-info" << std::endl;
234 return 1;
235 }
236 }
237 
238 if (pkg_path.empty()) {
239 std::cerr << "Error: PKG file must be specified" << std::endl;
240 showUsage(argv[0]);
241 return 1;
242 }
243 
244 std::string failreason;
245 PKG pkg;
246 if (!pkg.Scan(pkg_path, failreason)) {
247 std::cerr << "Error in pfs-info: " << failreason << std::endl;
248 return 1;
249 }
250 
251 // Header/summary
252 auto header = pkg.GetPkgHeader();
253 auto title_id = pkg.GetTitleID();
254 auto flags = pkg.GetPkgFlags();
255 auto entries = pkg.GetEntriesInfo();
256 size_t n_files = 0, n_dirs = 0;
257 for (const auto &e : entries) {
258 if (e.type == PFS_FILE)
259 n_files++;
260 else if (e.type == PFS_DIR)
261 n_dirs++;
262 }
263 
264 if (as_json) {
265 // Simple JSON output
266 std::cout << "{\n";
267 std::cout << " \"title_id\": \"" << std::string(title_id) << "\",\n";
268 std::cout << " \"flags\": \"" << flags << "\",\n";
269 std::cout << " \"pkg_size\": " << pkg.GetPkgSize() << ",\n";
270 std::cout << " \"entries\": [\n";
271 for (size_t i = 0; i < entries.size(); ++i) {
272 const auto &e = entries[i];
273 std::cout << " {\n";
274 std::cout << " \"name\": \"" << e.name << "\",\n";
275 std::cout << " \"inode\": " << e.inode << ",\n";
276 std::cout << " \"type\": " << e.type << ",\n";
277 std::cout << " \"path\": \"" << e.path << "\",\n";
278 std::cout << " \"size\": " << e.size << ",\n";
279 std::cout << " \"blocks\": " << e.blocks << ",\n";
280 std::cout << " \"loc\": " << e.loc << "\n";
281 std::cout << " }" << (i + 1 < entries.size() ? "," : "") << "\n";
282 }
283 std::cout << " ],\n";
284 std::cout << " \"summary\": { \"files\": " << n_files
285 << ", \"dirs\": " << n_dirs << " }\n";
286 std::cout << "}\n";
287 } else {
288 // Human readable output
289 std::cout << "\n--- PFS Info ---\n";
290 std::cout << "TitleID: " << title_id << "\n";
291 std::cout << "Flags: " << flags << "\n";
292 std::cout << "PKG Size: " << pkg.GetPkgSize() << "\n";
293 std::cout << "Dirs: " << n_dirs << ", Files: " << n_files << "\n\n";
294 for (const auto &e : entries) {
295 std::cout << (e.type == PFS_DIR
296 ? "[D] "
297 : (e.type == PFS_FILE ? "[F] " : "[?] "))
298 << e.path << (e.path.empty() ? e.name : std::string())
299 << (e.type == PFS_FILE
300 ? (" (size=" + std::to_string(e.size) + ")")
301 : "")
302 << "\n";
303 }
304 }
305 return 0;
306 }
307 
308 // ╔═══════════════════════════════════════════════════════════════════════╗
309 // ║ sfo-info: Display param.sfo parameters from PKG ║
310 // ╚═══════════════════════════════════════════════════════════════════════╝
311 if (command == "sfo-info") {
312 bool as_json = false;
313 bool verbose = false;
314 std::string query_key;
315 std::filesystem::path pkg_path;
316 
317 for (int i = 2; i < argc; ++i) {
318 std::string arg = argv[i];
319 if (arg == "-h" || arg == "--help") {
320 showUsage(argv[0]);
321 return 0;
322 } else if (arg == "--json") {
323 as_json = true;
324 } else if (arg == "-v" || arg == "--verbose") {
325 verbose = true;
326 } else if (arg == "-q" || arg == "--query") {
327 if (i + 1 >= argc) {
328 std::cerr << "Error: --query requires a parameter name"
329 << std::endl;
330 return 1;
331 }
332 query_key = argv[++i];
333 } else if (!arg.empty() && arg[0] == '-') {
334 std::cerr << "Unrecognized option: " << arg << std::endl;
335 showUsage(argv[0]);
336 return 1;
337 } else if (pkg_path.empty()) {
338 pkg_path = arg;
339 } else {
340 std::cerr << "Excess arguments for sfo-info" << std::endl;
341 return 1;
342 }
343 }
344 
345 if (pkg_path.empty()) {
346 std::cerr << "Error: PKG file must be specified" << std::endl;
347 showUsage(argv[0]);
348 return 1;
349 }
350 
351 // Open PKG to get the SFO data
352 std::string failreason;
353 PKG pkg;
354 if (!pkg.Open(pkg_path, failreason)) {
355 std::cerr << "Error opening PKG: " << failreason << std::endl;
356 return 1;
357 }
358 
359 // Parse SFO from PKG's internal buffer
360 PSF psf;
361 if (pkg.sfo.empty()) {
362 std::cerr << "Error: param.sfo not found in PKG (entry 0x1000 missing "
363 "or lookup failed)"
364 << std::endl;
365 return 1;
366 }
367 if (!psf.Open(pkg.sfo)) {
368 std::cerr << "Error: Malformed param.sfo data (Open failed)"
369 << std::endl;
370 return 1;
371 }
372 
373 const auto &entries = psf.GetEntries();
374 
375 // Query mode: return just one value
376 if (!query_key.empty()) {
377 // Try string first
378 if (auto str = psf.GetString(query_key); str.has_value()) {
379 std::cout << *str << std::endl;
380 return 0;
381 }
382 // Try integer
383 if (auto val = psf.GetInteger(query_key); val.has_value()) {
384 std::cout << *val << std::endl;
385 return 0;
386 }
387 // Try binary (output as hex)
388 if (auto bin = psf.GetBinary(query_key); bin.has_value()) {
389 for (const auto &b : *bin) {
390 printf("%02x", b);
391 }
392 std::cout << std::endl;
393 return 0;
394 }
395 std::cerr << "Parameter not found: " << query_key << std::endl;
396 return 1;
397 }
398 
399 // JSON output
400 if (as_json) {
401 std::cout << "{\n";
402 std::cout << " \"title_id\": \"" << std::string(pkg.GetTitleID())
403 << "\",\n";
404 std::cout << " \"parameters\": {\n";
405 size_t idx = 0;
406 for (const auto &entry : entries) {
407 std::cout << " \"" << entry.key << "\": ";
408 switch (entry.param_fmt) {
409 case PSFEntryFmt::Text:
410 if (auto v = psf.GetString(entry.key); v.has_value()) {
411 std::cout << "\"" << *v << "\"";
412 }
413 break;
414 case PSFEntryFmt::Integer:
415 if (auto v = psf.GetInteger(entry.key); v.has_value()) {
416 std::cout << *v;
417 }
418 break;
419 case PSFEntryFmt::Binary:
420 if (auto v = psf.GetBinary(entry.key); v.has_value()) {
421 std::cout << "\"0x";
422 for (const auto &b : *v)
423 printf("%02x", b);
424 std::cout << "\"";
425 }
426 break;
427 }
428 std::cout << (++idx < entries.size() ? "," : "") << "\n";
429 }
430 std::cout << " }\n}\n";
431 return 0;
432 }
433 
434 // Human readable output
435 std::cout << "\n--- SFO Info ---\n";
436 std::cout << "TitleID: " << pkg.GetTitleID() << "\n";
437 if (verbose) {
438 std::cout << "Parameters: " << entries.size() << "\n";
439 }
440 std::cout << "\n";
441 
442 for (const auto &entry : entries) {
443 switch (entry.param_fmt) {
444 case PSFEntryFmt::Text:
445 if (auto v = psf.GetString(entry.key); v.has_value()) {
446 if (verbose) {
447 std::cout << entry.key << "=\"" << *v
448 << "\" (str, max=" << entry.max_len << ")\n";
449 } else {
450 std::cout << entry.key << "=" << *v << "\n";
451 }
452 }
453 break;
454 case PSFEntryFmt::Integer:
455 if (auto v = psf.GetInteger(entry.key); v.has_value()) {
456 if (verbose) {
457 std::cout << entry.key << "=" << *v << " (0x" << std::hex << *v
458 << std::dec << ", int)\n";
459 } else {
460 std::cout << entry.key << "=" << *v << "\n";
461 }
462 }
463 break;
464 case PSFEntryFmt::Binary:
465 if (auto v = psf.GetBinary(entry.key); v.has_value()) {
466 std::cout << entry.key << "=0x";
467 for (const auto &b : *v)
468 printf("%02x", b);
469 if (verbose) {
470 std::cout << " (binary, " << v->size() << " bytes)";
471 }
472 std::cout << "\n";
473 }
474 break;
475 }
476 }
477 return 0;
478 }
479 
480 // Command to extract PKG
481 if (command == "extract") {
482 std::cerr << "DEBUG: Entered extract block" << std::endl;
483 std::cerr.flush();
484 // Flexible parsing: supports both positional (legacy) and options
485 std::filesystem::path pkg_path;
486 std::filesystem::path out_dir = "."; // default
487 std::filesystem::path rif_path;
488 bool use_rif = false;
489 
490 // Decompiler Flags
491 bool export_project = false;
492 std::filesystem::path export_dir;
493 bool list_functions = false;
494 bool decompile_single = false;
495 uint64_t decompile_addr = 0;
496 std::filesystem::path load_db_path;
497 
498 for (int i = 2; i < argc; ++i) {
499 std::string arg = argv[i];
500 if (arg == "-h" || arg == "--help" || arg == "-help" || arg == "-?") {
501 showUsage(argv[0]);
502 return 0;
503 } else if (arg == "-o" || arg == "--output") {
504 if (i + 1 >= argc) {
505 std::cerr << "Error: missing value for --output" << std::endl;
506 return 1;
507 }
508 out_dir = argv[++i];
509 } else if (arg == "-i" || arg == "--input") {
510 if (i + 1 >= argc) {
511 std::cerr << "Error: missing value for --input" << std::endl;
512 return 1;
513 }
514 pkg_path = argv[++i];
515 } else if (arg == "-r" || arg == "--rif") {
516 if (i + 1 >= argc) {
517 std::cerr << "Error: missing value for --rif" << std::endl;
518 return 1;
519 }
520 rif_path = argv[++i];
521 use_rif = true;
522 } else if (arg == "--export-project") {
523 if (i + 1 >= argc) {
524 std::cerr << "Error: missing value for --export-project"
525 << std::endl;
526 return 1;
527 }
528 export_project = true;
529 export_dir = argv[++i];
530 } else if (arg == "--list-functions") {
531 list_functions = true;
532 } else if (arg == "--decompile") {
533 if (i + 1 >= argc) {
534 std::cerr << "Error: missing address for --decompile" << std::endl;
535 return 1;
536 }
537 decompile_single = true;
538 std::string addrStr = argv[++i];
539 try {
540 decompile_addr = std::stoull(addrStr, nullptr, 16);
541 } catch (...) {
542 std::cerr << "Error: invalid hex address: " << addrStr << std::endl;
543 return 1;
544 }
545 } else if (arg == "--load-db") {
546 if (i + 1 >= argc) {
547 std::cerr << "Error: missing value for --load-db" << std::endl;
548 return 1;
549 }
550 load_db_path = argv[++i];
551 } else if (!arg.empty() && arg[0] == '-') {
552 std::cerr << "Unrecognized option: " << arg << std::endl;
553 showUsage(argv[0]);
554 return 1;
555 } else {
556 // Positional args fallback
557 if (pkg_path.empty()) {
558 pkg_path = arg;
559 } else if (out_dir == ".") {
560 out_dir = arg;
561 } else if (!use_rif) {
562 rif_path = arg;
563 use_rif = true;
564 }
565 }
566 }
567 
568 std::cerr << "DEBUG: pkg_path=" << pkg_path << std::endl;
569 std::cerr.flush();
570 
571 if (pkg_path.empty()) {
572 std::cerr << "Error: PKG file must be specified" << std::endl;
573 showUsage(argv[0]);
574 return 1;
575 }
576 
577 std::cerr << "DEBUG: About to check RIF" << std::endl;
578 std::cerr.flush();
579 
580 if (use_rif) {
581 std::cout << "Using RIF file: " << rif_path << std::endl;
582 if (!RIFGenerator::ValidateRIF(rif_path)) {
583 std::cerr << "Warning: The specified RIF file does not appear valid"
584 << std::endl;
585 }
586 }
587 
588 std::cerr << "DEBUG: About to open PKG" << std::endl;
589 std::cerr << "DEBUG: export_project=" << export_project
590 << " export_dir=" << export_dir.string() << std::endl;
591 std::cerr.flush();
592 
593 // Continue with PKG extraction
594 std::cerr << "DEBUG: PKG File: " << pkg_path.string() << std::endl;
595 std::cerr << "DEBUG: Output Folder: " << out_dir.string() << std::endl;
596 std::cerr.flush();
597 
598 std::cerr << "DEBUG: Checking if file exists..." << std::endl;
599 std::cerr.flush();
600 
601 // Verify that the file exists
602 if (!std::filesystem::exists(pkg_path)) {
603 std::cerr << "Error: File does not exist: " << pkg_path.string()
604 << std::endl;
605 return 1;
606 }
607 
608 std::cerr << "DEBUG: File exists, detecting type..." << std::endl;
609 std::cerr.flush();
610 
611 // Detect file type
612 FileType fileType = detectFileType(pkg_path);
613 std::cout << "Detected file type: ";
614 
615 std::filesystem::path exePath;
616 
617 if (fileType == FileType::ELF) {
618 std::cout << "ELF Binary\n";
619 
620 // For ELF files, use the file directly as the executable
621 exePath = pkg_path;
622 
623 // Create output directory if needed for asset export
624 if (!std::filesystem::exists(out_dir)) {
625 std::filesystem::create_directories(out_dir);
626 LOG_INFO(Common, "Created output folder: {}", out_dir.string());
627 }
628 } else if (fileType == FileType::PKG) {
629 std::cout << "PS4 PKG\n";
630 
631 // Create the output folder if it doesn't exist
632 if (!std::filesystem::exists(out_dir)) {
633 std::filesystem::create_directories(out_dir);
634 LOG_INFO(Common, "Created output folder: {}", out_dir.string());
635 }
636 
637 std::string failreason;
638 PKG pkg;
639 if (!pkg.Open(pkg_path, failreason)) {
640 std::cerr << "Error opening PKG file: " << failreason << std::endl;
641 return 1;
642 }
643 
644 LOG_INFO(Common, "PKG file opened successfully!");
645 
646 // If a RIF file was provided, try to use it for decryption
647 if (use_rif) {
648 LOG_INFO(Common, "Attempting decryption with RIF file...");
649 // TODO: Implement RIF file integration with PKG decryption
650 }
651 
652 // Extraction and decryption
653 // Use Scan instead of Extract, as Scan is verified to work correctly
654 // for parsing
655 if (!pkg.Scan(pkg_path, failreason, out_dir)) {
656 LOG_ERROR(Lib_Kernel, "Error during scanning/decryption: {}",
657 failreason);
658 return 1;
659 }
660 
661 // Extract the actual files with progress bar
662 pkg.ExtractAllFilesWithProgress();
663 
664 std::cout << "Extraction and decryption completed successfully!\n";
665 
666 // Find executable for PKG
667 std::filesystem::path potentialEboot = out_dir / "eboot.bin";
668 std::filesystem::path potentialEbootUpper = out_dir / "EBOOT.BIN";
669 
670 if (std::filesystem::exists(potentialEboot))
671 exePath = potentialEboot;
672 else if (std::filesystem::exists(potentialEbootUpper))
673 exePath = potentialEbootUpper;
674 else {
675 // Search for SPRX
676 for (const auto &entry :
677 std::filesystem::recursive_directory_iterator(out_dir)) {
678 if (entry.path().extension() == ".sprx") {
679 exePath = entry.path();
680 break;
681 }
682 }
683 }
684 } else if (fileType == FileType::ISO_PS2 ||
685 fileType == FileType::ISO_PS3) {
686 std::cout << (fileType == FileType::ISO_PS3 ? "PS3 ISO" : "PS2 ISO")
687 << "\n";
688 
689 // Create the output folder if it doesn't exist
690 if (!std::filesystem::exists(out_dir)) {
691 std::filesystem::create_directories(out_dir);
692 LOG_INFO(Common, "Created output folder: {}", out_dir.string());
693 }
694 
695 // Extract ISO
696 ShadPKG::ISOExtractor isoExtractor;
697 auto result = isoExtractor.extract(pkg_path, out_dir);
698 
699 if (!result.success) {
700 std::cerr << "Error extracting ISO: " << result.errorMessage
701 << std::endl;
702 return 1;
703 }
704 
705 std::cout << "ISO extraction completed successfully!\n";
706 if (!result.gameTitle.empty()) {
707 std::cout << "Game: " << result.gameTitle << "\n";
708 }
709 if (!result.gameID.empty()) {
710 std::cout << "Game ID: " << result.gameID << "\n";
711 }
712 
713 exePath = result.extractedExecutable;
714 } else {
715 std::cerr << "Error: Unknown file type. Must be PKG or ISO.\n";
716 return 1;
717 }
718 
719 // --- Decompiler Integration ---
720 if (export_project || list_functions || decompile_single) {
721 LOG_INFO(Common, "Starting Decompiler Analysis...");
722 
723 // exePath already found above
724 if (exePath.empty()) {
725 std::cerr << "Error: Could not find executable in " << out_dir
726 << "\n";
727 return 1;
728 }
729 
730 LOG_INFO(Common, "Found executable: {}", exePath.string());
731 
732 // 2. Load into DecompilerContext
733 auto data = readFile(exePath);
734 if (data.empty()) {
735 std::cerr << "Error: Failed to read executable file.\n";
736 return 1;
737 }
738 
739 auto &ctx = ShadPKG::Decompiler::DecompilerContext::Get();
740 if (!ctx.LoadELF(data)) {
741 std::cerr << "Error: Failed to parse ELF file.\n";
742 return 1;
743 }
744 
745 if (!load_db_path.empty()) {
746 LOG_INFO(Common, "Loading project database from: {}",
747 load_db_path.string());
748 if (ctx.LoadProject(load_db_path.string())) {
749 LOG_INFO(Common, "Project database loaded successfully.");
750 } else {
751 LOG_ERROR(Common, "Failed to load project database.");
752 return 1;
753 }
754 }
755 
756 // 3. Analyze
757 LOG_INFO(Common, "Analyzing binary (this may take a while)...");
758 ctx.Analyze();
759 LOG_INFO(Common, "Analysis complete. Found {} functions.",
760 ctx.GetFunctions().size());
761 
762 // 4. Handle Commands
763 if (list_functions) {
764 std::cout << "\n--- Function List ---\n";
765 std::cout << "Address | Name\n";
766 std::cout << "-------------|-------------------\n";
767 for (const auto &func : ctx.GetFunctions()) {
768 printf("0x%010llX | %s\n", func->address, func->name.c_str());
769 }
770 }
771 
772 if (decompile_single) {
773 auto func = ctx.GetFunctionAt(decompile_addr);
774 if (!func) {
775 std::cerr << "Error: Function at 0x" << std::hex << decompile_addr
776 << " not found.\n";
777 } else {
778 
779 using namespace ShadPKG::Decompiler;
780 
781 // Init Global Analysis
782 auto symbols = std::make_shared<Analysis::SymbolAnalysis>(
783 ctx.GetRawData(), ctx.GetBaseAddress(),
784 ctx.GetSymbolDatabase());
785 symbols->analyze();
786 
787 auto dom = std::make_shared<Analysis::DominatorAnalysis>();
788 dom->analyze(func);
789 
790 Analysis::StructuralAnalysis structural(func, dom, symbols);
791 auto ast = structural.analyze();
792 
793 Lifter::VariableAnalysis lifter(func);
794 lifter.analyze();
795 lifter.applyToAST(ast);
796 
797 Analysis::DataFlowAnalysis dataflow(ast);
798 dataflow.analyze();
799 
800 Analysis::MemberAccessAnalysis memberAccess(ctx.GetTypeManager());
801 memberAccess.analyze(ast);
802 
803 Codegen::CppEmitter emitter;
804 std::cout << emitter.generate(ast) << "\n";
805 }
806 }
807 
808 if (export_project) {
809 LOG_INFO(Common, "Exporting project to: {}", export_dir.string());
810 ctx.ExportProject(export_dir.string());
811 
812 // Copy assets
813 try {
814 std::filesystem::path assetDest = export_dir / "assets";
815 std::filesystem::create_directories(assetDest);
816 
817 LOG_INFO(Common, "Copying assets from {} to {}...",
818 out_dir.string(), assetDest.string());
819 
820 // Get absolute paths for comparison to avoid recursion
821 std::filesystem::path absOut = std::filesystem::absolute(out_dir);
822 std::filesystem::path absExport =
823 std::filesystem::absolute(export_dir);
824 std::filesystem::path absAssetDest =
825 std::filesystem::absolute(assetDest);
826 
827 for (const auto &entry :
828 std::filesystem::directory_iterator(out_dir)) {
829 try {
830 std::filesystem::path entryAbs =
831 std::filesystem::absolute(entry.path());
832 
833 // Use equivalent() for robust path comparison
834 if (std::filesystem::equivalent(entryAbs, absExport)) {
835 LOG_INFO(Common, "Skipping export directory: {}",
836 entryAbs.string());
837 continue;
838 }
839 
840 if (std::filesystem::equivalent(entryAbs, absAssetDest)) {
841 LOG_INFO(Common, "Skipping asset destination directory: {}",
842 entryAbs.string());
843 continue;
844 }
845 
846 // Copy entry
847 std::filesystem::copy(
848 entry.path(), assetDest / entry.path().filename(),
849 std::filesystem::copy_options::recursive |
850 std::filesystem::copy_options::overwrite_existing);
851 } catch (...) {
852 // entries that don't exist or other errors
853 continue;
854 }
855 }
856 } catch (const std::exception &e) {
857 LOG_ERROR(Common, "Failed to copy assets: {}", e.what());
858 }
859 
860 LOG_INFO(Common, "Export complete.");
861 }
862 }
863 
864 return 0;
865 }
866 
867 // ═══════════════════════════════════════════════════════════════════════════
868 // PATCH COMMAND - PSN Bypass
869 // ═══════════════════════════════════════════════════════════════════════════
870 if (command == "patch") {
871 std::string input_file;
872 std::string output_file = "eboot_patched.bin";
873 std::string game_id = "CUSA00265"; // Default: Minecraft EU
874 std::string game_version = "01.00";
875 
876 bool scan_only = false;
877 bool gen_options = false;
878 bool convert_elf = false;
879 
880 // Parse arguments
881 for (int i = 2; i < argc; ++i) {
882 std::string arg = argv[i];
883 if ((arg == "-i" || arg == "--input") && i + 1 < argc) {
884 input_file = argv[++i];
885 } else if ((arg == "-o" || arg == "--output") && i + 1 < argc) {
886 output_file = argv[++i];
887 } else if (arg == "--game" && i + 1 < argc) {
888 game_id = argv[++i];
889 } else if (arg == "--version" && i + 1 < argc) {
890 game_version = argv[++i];
891 } else if (arg == "--scan") {
892 scan_only = true;
893 } else if (arg == "--gen-options") {
894 gen_options = true;
895 } else if (arg == "--elf") {
896 convert_elf = true;
897 } else if (arg[0] != '-' && input_file.empty()) {
898 input_file = arg;
899 }
900 }
901 
902 // Generate Minecraft options.txt
903 if (gen_options) {
904 std::cout << "=== Minecraft PSN Bypass options.txt ===" << std::endl;
905 std::cout << ShadPKG::Patcher::PSNBypass::generateMinecraftOptions()
906 << std::endl;
907 std::cout << "\nSave this content to your Minecraft save folder:"
908 << std::endl;
909 std::cout << "/mnt/pfs/"
910 "savedata_XXXXXXXX_CUSA00265_BedrockUserSettingsStorage/"
911 "options.txt"
912 << std::endl;
913 return 0;
914 }
915 
916 if (input_file.empty()) {
917 std::cerr << "Error: No input file specified for patch command."
918 << std::endl;
919 std::cerr << "Usage: " << argv[0]
920 << " patch -i <eboot.bin> [-o <output.bin>]" << std::endl;
921 return 1;
922 }
923 
924 ShadPKG::Patcher::PSNBypass patcher;
925 
926 if (!patcher.loadEboot(input_file)) {
927 std::cerr << "Error: Failed to load eboot file: " << input_file
928 << std::endl;
929 return 1;
930 }
931 
932 if (scan_only) {
933 std::cout << "=== Scanning for PSN functions ===" << std::endl;
934 patcher.findPSNFunctions();
935 return 0;
936 }
937 
938 // Apply patches
939 std::cout << "=== Applying PSN Bypass Patches ===" << std::endl;
940 std::cout << "Game ID: " << game_id << std::endl;
941 std::cout << "Version: " << game_version << std::endl;
942 
943 auto patchSet =
944 ShadPKG::Patcher::PSNBypass::getMinecraftPatches(game_version);
945 patchSet.gameId = game_id;
946 
947 bool applied = patcher.applyPatchSet(patchSet);
948 
949 if (applied) {
950 std::cout << "[PSNBypass] Patches applied successfully." << std::endl;
951 } else {
952 std::cout << "[PSNBypass] No patches applied (Conditions not met or "
953 "disabled)."
954 << std::endl;
955 }
956 
957 // Save output if patches applied OR if we are converting
958 if (applied || convert_elf) {
959 if (patcher.saveEboot(output_file)) {
960 std::cout << "Success! Eboot saved to: " << output_file << std::endl;
961 
962 if (convert_elf) {
963 std::cout << "Converting to ELF64..." << std::endl;
964 ShadPKG::Patcher::ElfReconstructor reconstructor;
965 if (reconstructor.loadFile(output_file)) {
966 if (reconstructor.reconstructElf(output_file)) { // Overwrite
967 std::cout << "Converted patched SELF to ELF: " << output_file
968 << std::endl;
969 } else {
970 std::cerr << "Failed to convert to ELF." << std::endl;
971 return 1;
972 }
973 } else {
974 std::cerr << "Failed to reload patched file for conversion."
975 << std::endl;
976 return 1;
977 }
978 }
979 
980 std::cout << "\nNext steps:" << std::endl;
981 std::cout << "1. Replace the original eboot.bin with the patched one"
982 << std::endl;
983 std::cout << "2. Generate options.txt: " << argv[0]
984 << " patch --gen-options" << std::endl;
985 std::cout << "3. Copy options.txt to your Minecraft save folder"
986 << std::endl;
987 } else {
988 std::cerr << "Error: Failed to save patched eboot" << std::endl;
989 return 1;
990 }
991 } else {
992 std::cerr
993 << "Warning: No patches were applied and no conversion requested."
994 << std::endl;
995 return 1;
996 }
997 
998 return 0;
999 }
1000 
1001 // ═══════════════════════════════════════════════════════════════════════════
1002 // PATCH-PKG COMMAND - Complete PKG Patcher
1003 // ═══════════════════════════════════════════════════════════════════════════
1004 if (command == "patch-pkg") {
1005 std::string input_pkg;
1006 std::string output_pkg;
1007 std::string game_id = "CUSA00265";
1008 bool extract_only = false;
1009 
1010 // Parse arguments
1011 for (int i = 2; i < argc; ++i) {
1012 std::string arg = argv[i];
1013 if ((arg == "-i" || arg == "--input") && i + 1 < argc) {
1014 input_pkg = argv[++i];
1015 } else if ((arg == "-o" || arg == "--output") && i + 1 < argc) {
1016 output_pkg = argv[++i];
1017 } else if (arg == "--game" && i + 1 < argc) {
1018 game_id = argv[++i];
1019 } else if (arg == "--extract-only") {
1020 extract_only = true;
1021 } else if (arg[0] != '-' && input_pkg.empty()) {
1022 input_pkg = arg;
1023 } else if (arg[0] != '-' && !input_pkg.empty() && output_pkg.empty()) {
1024 output_pkg = arg;
1025 }
1026 }
1027 
1028 if (input_pkg.empty()) {
1029 std::cerr << "Error: No input PKG specified." << std::endl;
1030 std::cerr << "Usage: " << argv[0]
1031 << " patch-pkg -i <input.pkg> [-o <output.pkg>]" << std::endl;
1032 return 1;
1033 }
1034 
1035 if (output_pkg.empty()) {
1036 std::filesystem::path pkg_path(input_pkg);
1037 output_pkg = (pkg_path.parent_path() /
1038 (pkg_path.stem().string() + "_patched.pkg"))
1039 .string();
1040 }
1041 
1042 std::cout << "╔══════════════════════════════════════════════════════════"
1043 "══════╗"
1044 << std::endl;
1045 std::cout << "║ Complete PKG Patcher - Minecraft PS4 PSN Bypass "
1046 " ║"
1047 << std::endl;
1048 std::cout << "╚══════════════════════════════════════════════════════════"
1049 "══════╝"
1050 << std::endl;
1051 std::cout << "\nInput PKG: " << input_pkg << std::endl;
1052 std::cout << "Output PKG: " << output_pkg << std::endl;
1053 std::cout << "Game ID: " << game_id << std::endl;
1054 
1055 // Step 1: Extract PKG using subprocess (more reliable for large files)
1056 std::cout << "\n[1/3] Extracting PKG..." << std::endl;
1057 std::filesystem::path extract_dir =
1058 std::filesystem::temp_directory_path() / "shadpkg_patch";
1059 std::filesystem::create_directories(extract_dir);
1060 
1061 std::string extract_cmd = std::string(argv[0]) + " extract --input \"" +
1062 input_pkg + "\" --export-project \"" +
1063 extract_dir.string() + "\"";
1064 int extract_result = system(extract_cmd.c_str());
1065 
1066 if (extract_result != 0) {
1067 std::cerr << "Error: Failed to extract PKG" << std::endl;
1068 return 1;
1069 }
1070 
1071 std::cout << "[+] PKG extracted to: " << extract_dir << std::endl;
1072 
1073 // Step 2: Find and scan eboot.bin
1074 std::cout << "\n[2/3] Scanning for PSN functions..." << std::endl;
1075 std::vector<std::filesystem::path> eboot_files;
1076 for (const auto &entry :
1077 std::filesystem::recursive_directory_iterator(extract_dir)) {
1078 if (entry.path().filename() == "eboot.bin") {
1079 eboot_files.push_back(entry.path());
1080 }
1081 }
1082 
1083 if (eboot_files.empty()) {
1084 std::cerr << "Error: eboot.bin not found in extracted PKG" << std::endl;
1085 return 1;
1086 }
1087 
1088 std::filesystem::path eboot_bin = eboot_files[0];
1089 std::cout << "[+] Found eboot.bin: " << eboot_bin << std::endl;
1090 
1091 ShadPKG::Patcher::PSNBypass patcher;
1092 if (!patcher.loadEboot(eboot_bin.string())) {
1093 std::cerr << "Error: Failed to load eboot.bin" << std::endl;
1094 return 1;
1095 }
1096 
1097 auto psnOffsets = patcher.findPSNFunctions();
1098 std::cout << "[+] Found " << psnOffsets.size() << " PSN functions/strings"
1099 << std::endl;
1100 
1101 // Apply patches to eboot.bin
1102 std::cout << "[*] Applying PSN bypass patches..." << std::endl;
1103 int patches_applied = 0;
1104 
1105 // Search for the specific PSN_Auth_Check pattern: 85 C0 74 (test eax,
1106 // eax; jz) This is the actual authentication check that needs to be
1107 // bypassed
1108 std::vector<uint8_t> psn_check_pattern = {0x85, 0xC0, 0x74};
1109 auto auth_check_offsets = patcher.searchPattern(psn_check_pattern);
1110 
1111 std::cout << "[*] Found " << auth_check_offsets.size()
1112 << " PSN_Auth_Check patterns (85 C0 74)" << std::endl;
1113 
1114 // Patch each PSN_Auth_Check pattern
1115 // Replace "test eax, eax; jz" with "xor eax, eax; ret" to always return
1116 // success
1117 std::vector<uint8_t> return_success = {0x31, 0xC0, 0xC3};
1118 
1119 for (const auto &offset : auth_check_offsets) {
1120 // Only patch if it looks like code (not in string section)
1121 // Verify by checking surrounding bytes
1122 if (offset > 0x1000 &&
1123 offset < 0x800000) { // Reasonable code section bounds
1124 if (patcher.patchOffset(offset, return_success)) {
1125 patches_applied++;
1126 std::cout << "[+] Patched PSN_Auth_Check at 0x" << std::hex
1127 << offset << std::dec << std::endl;
1128 }
1129 }
1130 }
1131 
1132 std::cout << "[+] Applied " << patches_applied
1133 << " PSN_Auth_Check patches" << std::endl;
1134 
1135 // Save patched eboot.bin
1136 if (!patcher.saveEboot(eboot_bin.string())) {
1137 std::cerr << "Warning: Could not save patched eboot.bin, continuing..."
1138 << std::endl;
1139 } else {
1140 std::cout << "[+] Patched eboot.bin saved" << std::endl;
1141 }
1142 
1143 // Step 3: Generate options.txt
1144 std::cout << "\n[3/3] Generating PSN bypass configuration..."
1145 << std::endl;
1146 std::filesystem::path options_file = extract_dir / "options.txt";
1147 std::string options_content =
1148 ShadPKG::Patcher::PSNBypass::generateMinecraftOptions();
1149 
1150 std::ofstream options_out(options_file);
1151 options_out << options_content;
1152 options_out.close();
1153 std::cout << "[+] Generated: " << options_file << std::endl;
1154 
1155 if (extract_only) {
1156 std::cout << "\n[+] Extract-only mode. Files ready at: " << extract_dir
1157 << std::endl;
1158 std::cout << "To build the final PKG, use Patch Builder or:"
1159 << std::endl;
1160 std::cout
1161 << " orbis-pub-cmd image_build --image_type pkg --image_path \""
1162 << extract_dir << "\" --output_path \"" << output_pkg << "\""
1163 << std::endl;
1164 return 0;
1165 }
1166 
1167 // Try to build final PKG
1168 std::cout << "\n[*] Attempting to build final PKG..." << std::endl;
1169 std::string build_cmd =
1170 "orbis-pub-cmd image_build --image_type pkg --image_path \"" +
1171 extract_dir.string() + "\" --output_path \"" + output_pkg + "\"";
1172 
1173 int result = system(build_cmd.c_str());
1174 
1175 if (result == 0 && std::filesystem::exists(output_pkg)) {
1176 auto size = std::filesystem::file_size(output_pkg);
1177 std::cout << "\n╔══════════════════════════════════════════════════════"
1178 "══════════╗"
1179 << std::endl;
1180 std::cout << "║ SUCCESS! "
1181 " ║"
1182 << std::endl;
1183 std::cout << "╚════════════════════════════════════════════════════════"
1184 "════════╝"
1185 << std::endl;
1186 std::cout << "\nPatched PKG: " << output_pkg << std::endl;
1187 std::cout << "Size: " << (size / (1024 * 1024)) << " MB" << std::endl;
1188 std::cout << "\nReady to install on PS4!" << std::endl;
1189 std::cout << "\nNEXT STEPS:" << std::endl;
1190 std::cout << "1. Copy PKG to PS4 via USB or FTP" << std::endl;
1191 std::cout << "2. Use Package Manager to install" << std::endl;
1192 std::cout << "3. Start Minecraft and configure multiplayer"
1193 << std::endl;
1194 std::cout << "4. Copy options.txt to save data for PSN bypass"
1195 << std::endl;
1196 return 0;
1197 } else {
1198 std::cout << "\n[!] orbis-pub-cmd not available or failed" << std::endl;
1199 std::cout << "Extracted files are ready at: " << extract_dir
1200 << std::endl;
1201 std::cout << "\nTo build the PKG, install LibOrbisPkg and run:"
1202 << std::endl;
1203 std::cout
1204 << " orbis-pub-cmd image_build --image_type pkg --image_path \""
1205 << extract_dir << "\" --output_path \"" << output_pkg << "\""
1206 << std::endl;
1207 std::cout << "\nOr use Patch Builder GUI:" << std::endl;
1208 std::cout << " "
1209 "https://www.mediafire.com/file/xw0zn2e0rjaf5k7/"
1210 "Patch_Builder_v1.3.3.zip/file"
1211 << std::endl;
1212 return 0;
1213 }
1214 }
1215 
1216 // Unrecognized command
1217 std::cerr << "Unrecognized command: " << command << std::endl;
1218 showUsage(argv[0]);
1219 return 1;
1220 } catch (const std::exception &e) {
1221 std::cerr << "Unhandled C++ exception: " << e.what() << std::endl;
1222 return 2;
1223 } catch (...) {
1224 std::cerr << "Fatal error: crash or unhandled exception." << std::endl;
1225 return 3;
1226 }
1227}