Seregon/ShadPKG

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

C++/47.3 KB/No license
core/file_format/pkg.cpp
ShadPKG / core / file_format / pkg.cpp
1// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3 
4#include "core/file_format/pkg.h"
5#include "common/alignment.h"
6#include "common/io_file.h"
7#include "common/logging/formatter.h"
8#include "common/logging/log.h"
9#include "core/file_format/pkg_type.h"
10#include <atomic>
11#include <chrono>
12#include <iomanip>
13#include <iostream>
14#include <mutex>
15#include <span>
16#include <sstream>
17#include <thread>
18#include <zlib.h>
19 
20static void DecompressPFSC(char *compressed_data, size_t compressed_size,
21 char *decompressed_data, size_t decompressed_size) {
22 z_stream decompressStream;
23 decompressStream.zalloc = Z_NULL;
24 decompressStream.zfree = Z_NULL;
25 decompressStream.opaque = Z_NULL;
26 
27 if (inflateInit(&decompressStream) != Z_OK) {
28 // std::cerr << "Error initializing zlib for deflation." << std::endl;
29 }
30 
31 decompressStream.avail_in = static_cast<uInt>(compressed_size);
32 decompressStream.next_in = reinterpret_cast<unsigned char *>(compressed_data);
33 decompressStream.avail_out = static_cast<uInt>(decompressed_size);
34 decompressStream.next_out =
35 reinterpret_cast<unsigned char *>(decompressed_data);
36 
37 if (inflate(&decompressStream, Z_FINISH)) {
38 }
39 if (inflateEnd(&decompressStream) != Z_OK) {
40 // std::cerr << "Error ending zlib inflate" << std::endl;
41 }
42}
43 
44u32 GetPFSCOffset(const u8 *pfs_image, size_t size) {
45 static constexpr u32 PfscMagic = 0x43534650;
46 u32 value;
47 for (u32 i = 0x20000; i < size; i += 0x10000) {
48 std::memcpy(&value, pfs_image + i, sizeof(u32));
49 if (value == PfscMagic)
50 return i;
51 }
52 return -1;
53}
54 
55PKG::PKG() = default;
56 
57PKG::~PKG() = default;
58 
59bool PKG::Open(const std::filesystem::path &filepath, std::string &failreason) {
60 LOG_DEBUG(Common, "Inizio PKG::Open su {}", filepath.string());
61 Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
62 if (!file.IsOpen()) {
63 LOG_ERROR(Common, "File non aperto: {}", filepath.string());
64 return false;
65 }
66 pkgSize = file.GetSize();
67 
68 file.Read(pkgheader);
69 if (pkgheader.magic != 0x7F434E54) {
70 LOG_ERROR(Common, "Magic non valido nel PKG header");
71 return false;
72 }
73 
74 for (const auto &flag : flagNames) {
75 if (isFlagSet(pkgheader.pkg_content_flags, flag.first)) {
76 if (!pkgFlags.empty())
77 pkgFlags += (", ");
78 pkgFlags += (flag.second);
79 }
80 }
81 
82 // Find title id it is part of pkg_content_id starting at offset 0x40
83 file.Seek(0x47); // skip first 7 characters of content_id
84 file.Read(pkgTitleID);
85 
86 u32 offset = pkgheader.pkg_table_entry_offset;
87 u32 n_files = pkgheader.pkg_table_entry_count;
88 
89 LOG_DEBUG(Common, "Table entry offset: {}, count: {}", offset, n_files);
90 
91 if (!file.Seek(offset)) {
92 failreason = "Failed to seek to PKG table entry offset";
93 LOG_ERROR(Common, "{}", failreason);
94 return false;
95 }
96 
97 pkgEntries.clear();
98 for (int i = 0; i < n_files; i++) {
99 PKGEntry entry{};
100 file.Read(entry.id);
101 file.Read(entry.filename_offset);
102 file.Read(entry.flags1);
103 file.Read(entry.flags2);
104 file.Read(entry.offset);
105 file.Read(entry.size);
106 file.Seek(8, Common::FS::SeekOrigin::CurrentPosition);
107 pkgEntries.push_back(entry);
108 // Try to figure out the name
109 const auto name = GetEntryNameByType(entry.id);
110 
111 // Direct debug output to stderr
112 LOG_DEBUG(Common, "Entry {}: id={} (0x{:x}), name={}", i, entry.id,
113 (u32)entry.id, std::string(name));
114 
115 if (name == "param.sfo") {
116 sfo.clear();
117 if (!file.Seek(entry.offset)) {
118 failreason = "Failed to seek to param.sfo offset";
119 LOG_ERROR(Common, "{}", failreason);
120 return false;
121 }
122 sfo.resize(entry.size);
123 file.ReadRaw<u8>(sfo.data(), entry.size);
124 }
125 }
126 file.Close();
127 
128 LOG_DEBUG(Common, "Fine PKG::Open");
129 return true;
130}
131 
132bool PKG::Extract(const std::filesystem::path &filepath,
133 const std::filesystem::path &extract,
134 std::string &failreason) {
135 LOG_DEBUG(Common, "Inizio PKG::Extract su {} -> {}", filepath.string(),
136 extract.string());
137 extract_path = extract;
138 pkgpath = filepath;
139 Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
140 if (!file.IsOpen()) {
141 LOG_ERROR(Common, "File non aperto in Extract: {}", filepath.string());
142 return false;
143 }
144 pkgSize = file.GetSize();
145 file.ReadRaw<u8>(&pkgheader, sizeof(PKGHeader));
146 
147 LOG_DEBUG(Common, "pkgheader.magic: {}", pkgheader.magic);
148 LOG_DEBUG(Common, "pkgheader.pkg_size: {}", pkgheader.pkg_size);
149 LOG_DEBUG(Common, "pkgheader.pkg_content_size: {}",
150 pkgheader.pkg_content_size);
151 LOG_DEBUG(Common, "pkgheader.pkg_content_offset: {}",
152 pkgheader.pkg_content_offset);
153 LOG_DEBUG(Common, "pkgheader.pkg_table_entry_offset: {}",
154 pkgheader.pkg_table_entry_offset);
155 LOG_DEBUG(Common, "pkgheader.pkg_table_entry_count: {}",
156 pkgheader.pkg_table_entry_count);
157 LOG_DEBUG(Common, "pkgheader.pfs_image_offset: {}",
158 pkgheader.pfs_image_offset);
159 LOG_DEBUG(Common, "pkgheader.pfs_cache_size: {}", pkgheader.pfs_cache_size);
160 
161 if (pkgheader.magic != 0x7F434E54) {
162 LOG_ERROR(Common, "Magic non valido in Extract");
163 return false;
164 }
165 
166 if (pkgheader.pkg_size > pkgSize) {
167 failreason = "PKG file size is different";
168 LOG_ERROR(Common, "{}", failreason);
169 return false;
170 }
171 if ((pkgheader.pkg_content_size + pkgheader.pkg_content_offset) >
172 pkgheader.pkg_size) {
173 failreason = "Content size is bigger than pkg size";
174 LOG_ERROR(Common, "{}", failreason);
175 return false;
176 }
177 
178 u32 offset = pkgheader.pkg_table_entry_offset;
179 u32 n_files = pkgheader.pkg_table_entry_count;
180 LOG_DEBUG(Common, "Table entry offset: {}, count: {}", offset, n_files);
181 
182 std::array<u8, 64> concatenated_ivkey_dk3;
183 std::array<u8, 32> seed_digest;
184 std::array<std::array<u8, 32>, 7> digest1;
185 std::array<std::array<u8, 256>, 7> key1;
186 std::array<u8, 256> imgkeydata;
187 
188 if (!file.Seek(offset)) {
189 failreason = "Failed to seek to PKG table entry offset";
190 return false;
191 }
192 
193 for (int i = 0; i < n_files; i++) {
194 PKGEntry entry{};
195 file.Read(entry.id);
196 file.Read(entry.filename_offset);
197 file.Read(entry.flags1);
198 file.Read(entry.flags2);
199 file.Read(entry.offset);
200 file.Read(entry.size);
201 file.Seek(8, Common::FS::SeekOrigin::CurrentPosition);
202 
203 auto currentPos = file.Tell();
204 
205 // Try to figure out the name
206 const auto name = GetEntryNameByType(entry.id);
207 const auto filepath = extract_path / "sce_sys" / name;
208 std::filesystem::create_directories(filepath.parent_path());
209 
210 if (name.empty()) {
211 // Just print with id
212 Common::FS::IOFile out(extract_path / "sce_sys" /
213 std::to_string(entry.id),
214 Common::FS::FileAccessMode::Write);
215 if (!file.Seek(entry.offset)) {
216 failreason = "Failed to seek to PKG entry offset";
217 return false;
218 }
219 
220 std::vector<u8> data;
221 data.resize(entry.size);
222 file.ReadRaw<u8>(data.data(), entry.size);
223 out.WriteRaw<u8>(data.data(), data.size());
224 out.Close();
225 
226 file.Seek(currentPos);
227 continue;
228 }
229 
230 if (entry.id == 0x1) { // DIGESTS, seek;
231 // file.Seek(entry.offset, fsSeekSet);
232 } else if (entry.id == 0x10) { // ENTRY_KEYS, seek;
233 file.Seek(entry.offset);
234 file.Read(seed_digest);
235 
236 for (int i = 0; i < 7; i++) {
237 file.Read(digest1[i]);
238 }
239 
240 for (int i = 0; i < 7; i++) {
241 file.Read(key1[i]);
242 }
243 
244 PKG::crypto.RSA2048Decrypt(dk3_, key1[3], true); // decrypt DK3
245 } else if (entry.id == 0x20) { // IMAGE_KEY, seek; IV_KEY
246 file.Seek(entry.offset);
247 file.Read(imgkeydata);
248 
249 // The Concatenated iv + dk3 imagekey for HASH256
250 std::memcpy(concatenated_ivkey_dk3.data(), &entry, sizeof(entry));
251 std::memcpy(concatenated_ivkey_dk3.data() + sizeof(entry), dk3_.data(),
252 sizeof(dk3_));
253 
254 PKG::crypto.ivKeyHASH256(concatenated_ivkey_dk3, ivKey); // ivkey_
255 // imgkey_ to use for last step to get ekpfs
256 PKG::crypto.aesCbcCfb128Decrypt(ivKey, imgkeydata, imgKey);
257 // ekpfs key to get data and tweak keys.
258 PKG::crypto.RSA2048Decrypt(ekpfsKey, imgKey, false);
259 } else if (entry.id == 0x80) {
260 // GENERAL_DIGESTS, seek;
261 // file.Seek(entry.offset, fsSeekSet);
262 }
263 
264 Common::FS::IOFile out(extract_path / "sce_sys" / name,
265 Common::FS::FileAccessMode::Write);
266 if (!file.Seek(entry.offset)) {
267 failreason = "Failed to seek to PKG entry offset";
268 return false;
269 }
270 
271 std::vector<u8> data;
272 data.resize(Common::AlignUp(static_cast<u32>(entry.size), 16));
273 file.ReadRaw<u8>(data.data(), entry.size);
274 out.WriteRaw<u8>(data.data(), entry.size);
275 out.Close();
276 
277 // Decrypt Np stuff and overwrite.
278 if (entry.id == 0x400 || entry.id == 0x401 || entry.id == 0x402 ||
279 entry.id == 0x403) { // somehow 0x401 is not decrypting
280 decNp.resize(Common::AlignUp(static_cast<u32>(entry.size), 16));
281 if (!file.Seek(entry.offset)) {
282 failreason = "Failed to seek to PKG entry offset";
283 return false;
284 }
285 
286 std::array<u8, 64> concatenated_ivkey_dk3_;
287 std::memcpy(concatenated_ivkey_dk3_.data(), &entry, sizeof(entry));
288 std::memcpy(concatenated_ivkey_dk3_.data() + sizeof(entry), dk3_.data(),
289 sizeof(dk3_));
290 PKG::crypto.ivKeyHASH256(concatenated_ivkey_dk3_, ivKey);
291 PKG::crypto.aesCbcCfb128DecryptEntry(
292 std::span<const CryptoPP::byte, 32>(
293 reinterpret_cast<const CryptoPP::byte *>(ivKey.data()), 32),
294 std::span<CryptoPP::byte>(
295 reinterpret_cast<CryptoPP::byte *>(data.data()), data.size()),
296 std::span<CryptoPP::byte>(
297 reinterpret_cast<CryptoPP::byte *>(decNp.data()), decNp.size()));
298 Common::FS::IOFile out(extract_path / "sce_sys" / name,
299 Common::FS::FileAccessMode::Write);
300 out.WriteRaw<u8>(decNp.data(), entry.size);
301 out.Close();
302 }
303 
304 file.Seek(currentPos);
305 }
306 
307 // Read the seed
308 std::array<u8, 16> seed;
309 if (!file.Seek(pkgheader.pfs_image_offset + 0x370)) {
310 failreason = "Failed to seek to PFS image offset";
311 return false;
312 }
313 file.Read(seed);
314 
315 // Get data and tweak keys.
316 PKG::crypto.PfsGenCryptoKey(ekpfsKey, seed, dataKey, tweakKey);
317 const u32 length = pkgheader.pfs_cache_size * 0x2; // Seems to be ok.
318 
319 int num_blocks = 0;
320 std::vector<u8> pfsc(length);
321 if (length != 0) {
322 // For large files, use streaming to avoid massive memory allocation
323 const size_t CHUNK_SIZE = 0x1000000; // 16MB chunks
324 std::vector<u8> pfs_encrypted(std::min(static_cast<size_t>(length), CHUNK_SIZE));
325 std::vector<u8> pfs_decrypted(std::min(static_cast<size_t>(length), CHUNK_SIZE));
326
327 file.Seek(pkgheader.pfs_image_offset);
328
329 // Read and decrypt in chunks
330 size_t total_read = 0;
331 size_t pfsc_write_pos = 0;
332
333 while (total_read < length) {
334 size_t to_read = std::min(CHUNK_SIZE, static_cast<size_t>(length - total_read));
335 pfs_encrypted.resize(to_read);
336 pfs_decrypted.resize(to_read);
337
338 file.ReadRaw<u8>(pfs_encrypted.data(), to_read);
339
340 // Decrypt chunk
341 PKG::crypto.decryptPFS(dataKey, tweakKey, pfs_encrypted, pfs_decrypted, total_read / 0x1000);
342
343 // Copy to pfsc buffer
344 size_t to_copy = std::min(to_read, static_cast<size_t>(length - pfsc_write_pos));
345 std::memcpy(pfsc.data() + pfsc_write_pos, pfs_decrypted.data(), to_copy);
346
347 total_read += to_read;
348 pfsc_write_pos += to_copy;
349 }
350
351 file.Close();
352 
353 // Retrieve PFSC from decrypted pfs_image.
354 pfsc_offset = GetPFSCOffset(pfsc.data(), pfsc.size());
355 
356 PFSCHdr pfsChdr;
357 std::memcpy(&pfsChdr, pfsc.data(), sizeof(pfsChdr));
358 
359 num_blocks = (int)(pfsChdr.data_length / pfsChdr.block_sz2);
360 sectorMap.resize(num_blocks +
361 1); // 8 bytes, need extra 1 to get the last offset.
362 
363 for (int i = 0; i < num_blocks + 1; i++) {
364 std::memcpy(&sectorMap[i], pfsc.data() + pfsChdr.block_offsets + i * 8,
365 8);
366 }
367 }
368 
369 u32 ent_size = 0;
370 u32 ndinode = 0;
371 int ndinode_counter = 0;
372 bool dinode_reached = false;
373 bool uroot_reached = false;
374 std::vector<char> compressedData;
375 std::vector<char> decompressedData(0x10000);
376 
377 // Get iNdoes and Dirents.
378 LOG_DEBUG(Common, "Inizio parsing blocchi PFS, num_blocks: {}", num_blocks);
379 for (int i = 0; i < num_blocks; i++) {
380 const u64 sectorOffset = sectorMap[i];
381 const u64 sectorSize = sectorMap[i + 1] - sectorOffset;
382 
383 compressedData.resize(sectorSize);
384 std::memcpy(compressedData.data(), pfsc.data() + sectorOffset, sectorSize);
385 
386 if (sectorSize == 0x10000) // Uncompressed data
387 std::memcpy(decompressedData.data(), compressedData.data(), 0x10000);
388 else if (sectorSize < 0x10000) // Compressed data
389 DecompressPFSC(compressedData.data(), compressedData.size(),
390 decompressedData.data(), decompressedData.size());
391 
392 if (i == 0) {
393 std::memcpy(&ndinode, decompressedData.data() + 0x30,
394 4); // number of folders and files
395 LOG_DEBUG(Common, "ndinode (num folder/file): {}", ndinode);
396 }
397 
398 int occupied_blocks =
399 (ndinode * 0xA8) /
400 0x10000; // how many blocks(0x10000) are taken by iNodes.
401 if (((ndinode * 0xA8) % 0x10000) != 0)
402 occupied_blocks += 1;
403 
404 if (i >= 1 && i <= occupied_blocks) { // Get all iNodes, gives type, file
405 // size and location.
406 for (int p = 0; p < 0x10000; p += 0xA8) {
407 Inode node;
408 std::memcpy(&node, &decompressedData[p], sizeof(node));
409 if (node.Mode == 0) {
410 break;
411 }
412 iNodeBuf.push_back(node);
413 LOG_DEBUG(Common, "iNode aggiunto: Mode={}", node.Mode);
414 }
415 }
416 
417 // let's deal with the root/uroot entries here.
418 // Sometimes it's more than 2 entries (Tomb Raider Remastered)
419 const std::string_view flat_path_table(&decompressedData[0x10], 15);
420 if (flat_path_table == "flat_path_table") {
421 uroot_reached = true;
422 LOG_DEBUG(Common, "flat_path_table trovato, uroot_reached=true");
423 }
424 
425 if (uroot_reached) {
426 for (int i = 0; i < 0x10000; i += ent_size) {
427 Dirent dirent;
428 std::memcpy(&dirent, &decompressedData[i], sizeof(dirent));
429 ent_size = dirent.entsize;
430 LOG_DEBUG(Common, "Dirent uroot: ino={}, entsize={}", dirent.ino,
431 dirent.entsize);
432 if (dirent.ino != 0) {
433 ndinode_counter++;
434 } else {
435 // Imposta la cartella base per l'estrazione.
436 // NON forzare parent_path/TitleID: rispetta la directory di output
437 // scelta dall'utente. Tutto verrà estratto sotto extract_path.
438 extractPaths[ndinode_counter] = extract_path;
439 uroot_reached = false;
440 break;
441 }
442 }
443 }
444 
445 const char dot = decompressedData[0x10];
446 const std::string_view dotdot(&decompressedData[0x28], 2);
447 if (dot == '.' && dotdot == "..") {
448 dinode_reached = true;
449 LOG_DEBUG(Common, "dinode_reached=true");
450 }
451 
452 // Get folder and file names.
453 bool end_reached = false;
454 if (dinode_reached) {
455 for (int j = 0; j < 0x10000;
456 j += ent_size) { // Skip the first parent and child.
457 Dirent dirent;
458 std::memcpy(&dirent, &decompressedData[j], sizeof(dirent));
459 
460 // Stop here and continue the main loop
461 if (dirent.ino == 0) {
462 LOG_DEBUG(Common, "Dirent.ino==0, break ciclo");
463 break;
464 }
465 
466 ent_size = dirent.entsize;
467 auto &table = fsTable.emplace_back();
468 table.name = std::string(dirent.name, dirent.namelen);
469 table.inode = dirent.ino;
470 table.type = dirent.type;
471 LOG_DEBUG(Common, "fsTable aggiunta: nome={}, inode={}, type={}",
472 table.name, table.inode, table.type);
473 
474 if (table.type == PFS_CURRENT_DIR) {
475 current_dir = extractPaths[table.inode];
476 }
477 extractPaths[table.inode] =
478 current_dir / std::filesystem::path(table.name);
479 if (table.type == PFS_FILE || table.type == PFS_DIR) {
480 if (table.type == PFS_DIR) {
481 // no disk writes in Scan
482 }
483 ndinode_counter++;
484 if ((ndinode_counter + 1) == ndinode)
485 end_reached = true;
486 }
487 }
488 if (end_reached) {
489 LOG_DEBUG(Common, "end_reached=true, break ciclo blocchi");
490 break;
491 }
492 }
493 }
494 LOG_DEBUG(Common, "Finished parsing PFS blocks");
495 return true;
496}
497 
498void PKG::ExtractAllFilesWithProgress() {
499 const size_t num_files = fsTable.size();
500 const size_t max_threads =
501 std::min<size_t>(8, std::thread::hardware_concurrency());
502 std::atomic<size_t> files_done{0};
503 std::mutex print_mutex;
504 
505 auto print_progress = [&](size_t done) {
506 float percent = (float)done / (float)num_files * 100.0f;
507 int barWidth = 40;
508 int pos = (int)(barWidth * percent / 100.0f);
509 std::ostringstream oss;
510 oss << "[";
511 for (int i = 0; i < barWidth; ++i)
512 oss << (i < pos ? "=" : (i == pos ? ">" : " "));
513 oss << "] ";
514 oss << std::setw(3) << int(percent) << "% ";
515 oss << done << "/" << num_files << " extracted";
516 std::lock_guard<std::mutex> lock(print_mutex);
517 std::cout << "\r" << std::string(80, ' ') << "\r" << oss.str()
518 << std::flush;
519 };
520 
521 auto extract_worker = [&](size_t start, size_t end) {
522 for (size_t i = start; i < end; ++i) {
523 ExtractFiles(i);
524 size_t done = ++files_done;
525 if (done % 1 == 0 || done == num_files)
526 print_progress(done);
527 }
528 };
529 
530 std::vector<std::thread> threads;
531 size_t batch = (num_files + max_threads - 1) / max_threads;
532 for (size_t t = 0; t < max_threads; ++t) {
533 size_t start = t * batch;
534 size_t end = std::min(num_files, start + batch);
535 if (start < end)
536 threads.emplace_back(extract_worker, start, end);
537 }
538 for (auto &th : threads)
539 th.join();
540 print_progress(num_files);
541 std::cout << std::endl;
542}
543 
544void PKG::ExtractFiles(const int index) {
545 int inode_number = fsTable[index].inode;
546 int inode_type = fsTable[index].type;
547 std::string inode_name = fsTable[index].name;
548 
549 // Removed verbose logging for cleaner progress bar
550 // LOG_DEBUG(Common, "ExtractFiles: index={}, inode={}, type={}, name={}",
551 // index, inode_number, inode_type, inode_name);
552 
553 if (inode_type == PFS_FILE) {
554 // Create destination directory only for the file about to be written
555 try {
556 std::filesystem::create_directories(
557 extractPaths[inode_number].parent_path());
558 } catch (const std::exception &e) {
559 LOG_ERROR(Common, "Directory creation failed: {}", e.what());
560 }
561 int sector_loc = iNodeBuf[inode_number].loc;
562 int nblocks = iNodeBuf[inode_number].Blocks;
563 int bsize = iNodeBuf[inode_number].Size;
564 
565 Common::FS::IOFile inflated;
566 inflated.Open(extractPaths[inode_number],
567 Common::FS::FileAccessMode::Write);
568 
569 Common::FS::IOFile
570 pkgFile; // Open the file for each iteration to avoid conflict.
571 pkgFile.Open(pkgpath, Common::FS::FileAccessMode::Read);
572 
573 int size_decompressed = 0;
574 std::vector<char> compressedData;
575 std::vector<char> decompressedData(0x10000);
576 
577 u64 pfsc_buf_size = 0x11000; // extra 0x1000
578 std::vector<u8> pfsc(pfsc_buf_size);
579 std::vector<u8> pfs_decrypted(pfsc_buf_size);
580 
581 for (int j = 0; j < nblocks; j++) {
582 u64 sectorOffset =
583 sectorMap[sector_loc +
584 j]; // offset into PFSC_image and not pfs_image.
585 u64 sectorSize = sectorMap[sector_loc + j + 1] -
586 sectorOffset; // indicates if data is compressed or not.
587 u64 fileOffset =
588 (pkgheader.pfs_image_offset + pfsc_offset + sectorOffset);
589 u64 currentSector1 = (pfsc_offset + sectorOffset) /
590 0x1000; // block size is 0x1000 for xts decryption.
591 
592 int sectorOffsetMask = (sectorOffset + pfsc_offset) & 0xFFFFF000;
593 int previousData = (sectorOffset + pfsc_offset) - sectorOffsetMask;
594 
595 pkgFile.Seek(fileOffset - previousData);
596 pkgFile.Read(pfsc);
597 
598 // Thread-safe crypto access
599 {
600 std::lock_guard<std::mutex> lock(crypto_mutex_);
601 crypto.decryptPFS(dataKey, tweakKey, pfsc, pfs_decrypted,
602 currentSector1);
603 }
604 
605 compressedData.resize(sectorSize);
606 std::memcpy(compressedData.data(), pfs_decrypted.data() + previousData,
607 sectorSize);
608 
609 if (sectorSize == 0x10000) // Uncompressed data
610 std::memcpy(decompressedData.data(), compressedData.data(), 0x10000);
611 else if (sectorSize < 0x10000) // Compressed data
612 DecompressPFSC(compressedData.data(), compressedData.size(),
613 decompressedData.data(), decompressedData.size());
614 
615 size_decompressed += 0x10000;
616 
617 if (j < nblocks - 1) {
618 inflated.WriteRaw<u8>(
619 reinterpret_cast<const u8 *>(decompressedData.data()),
620 decompressedData.size());
621 } else {
622 // This is to remove the zeros at the end of the file.
623 const u32 write_size =
624 decompressedData.size() - (size_decompressed - bsize);
625 inflated.WriteRaw<u8>(
626 reinterpret_cast<const u8 *>(decompressedData.data()), write_size);
627 }
628 }
629 pkgFile.Close();
630 inflated.Close();
631 } else if (inode_name.empty()) {
632 // Extract also nameless entries (unknown)
633 std::ostringstream oss;
634 oss << "entry_0x" << std::hex << inode_number << ".bin";
635 std::filesystem::path outpath = extract_path / oss.str();
636 // Create destination directory only for the file about to be written
637 try {
638 std::filesystem::create_directories(outpath.parent_path());
639 } catch (const std::exception &e) {
640 LOG_ERROR(Common, "Directory creation failed: {}", e.what());
641 }
642 // Search for the corresponding PKGEntry
643 for (const auto &entry : pkgEntries) {
644 if (entry.id == static_cast<u32>(inode_number)) {
645 Common::FS::IOFile pkgFile;
646 pkgFile.Open(pkgpath, Common::FS::FileAccessMode::Read);
647 pkgFile.Seek(entry.offset);
648 std::vector<u8> data(entry.size);
649 pkgFile.ReadRaw<u8>(data.data(), entry.size);
650 Common::FS::IOFile out(outpath, Common::FS::FileAccessMode::Write);
651 out.WriteRaw<u8>(data.data(), data.size());
652 out.Close();
653 pkgFile.Close();
654 break;
655 }
656 }
657 }
658}
659 
660std::vector<u8> PKG::GetFileBuffer(const std::string &filename) {
661 int index = -1;
662 for(size_t i=0; i<fsTable.size(); ++i) {
663 if(fsTable[i].name == filename) {
664 index = i;
665 break;
666 }
667 }
668 if (index == -1) return {};
669 
670 int inode_number = fsTable[index].inode;
671 int inode_type = fsTable[index].type;
672
673 if (inode_type != PFS_FILE) return {};
674 
675 int sector_loc = iNodeBuf[inode_number].loc;
676 int nblocks = iNodeBuf[inode_number].Blocks;
677 int bsize = iNodeBuf[inode_number].Size;
678 
679 std::vector<u8> resultBuffer;
680 resultBuffer.reserve(bsize);
681 
682 Common::FS::IOFile pkgFile;
683 pkgFile.Open(pkgpath, Common::FS::FileAccessMode::Read);
684
685 int size_decompressed = 0;
686 std::vector<char> compressedData;
687 std::vector<char> decompressedData(0x10000);
688 
689 u64 pfsc_buf_size = 0x11000;
690 std::vector<u8> pfsc(pfsc_buf_size);
691 std::vector<u8> pfs_decrypted(pfsc_buf_size);
692 
693 for (int j = 0; j < nblocks; j++) {
694 u64 sectorOffset = sectorMap[sector_loc + j];
695 u64 sectorSize = sectorMap[sector_loc + j + 1] - sectorOffset;
696 u64 fileOffset = (pkgheader.pfs_image_offset + pfsc_offset + sectorOffset);
697 u64 currentSector1 = (pfsc_offset + sectorOffset) / 0x1000;
698 
699 int sectorOffsetMask = (sectorOffset + pfsc_offset) & 0xFFFFF000;
700 int previousData = (sectorOffset + pfsc_offset) - sectorOffsetMask;
701 
702 pkgFile.Seek(fileOffset - previousData);
703 pkgFile.Read(pfsc);
704 
705 {
706 std::lock_guard<std::mutex> lock(crypto_mutex_);
707 crypto.decryptPFS(dataKey, tweakKey, pfsc, pfs_decrypted, currentSector1);
708 }
709
710 compressedData.resize(sectorSize);
711 std::memcpy(compressedData.data(), pfs_decrypted.data() + previousData, sectorSize);
712 
713 if (sectorSize == 0x10000)
714 std::memcpy(decompressedData.data(), compressedData.data(), 0x10000);
715 else if (sectorSize < 0x10000)
716 DecompressPFSC(compressedData.data(), compressedData.size(), decompressedData.data(), decompressedData.size());
717
718 size_decompressed += 0x10000;
719 
720 u32 write_size = 0x10000;
721 if (j == nblocks - 1) {
722 write_size = decompressedData.size() - (size_decompressed - bsize);
723 }
724
725 resultBuffer.insert(resultBuffer.end(),
726 reinterpret_cast<u8*>(decompressedData.data()),
727 reinterpret_cast<u8*>(decompressedData.data()) + write_size);
728 }
729
730 return resultBuffer;
731}
732 
733std::vector<std::string> PKG::GetFileList() const {
734 std::vector<std::string> files;
735 for (const auto &entry : fsTable) {
736 if (entry.type == PFS_FILE) {
737 files.push_back(entry.name);
738 }
739 }
740 return files;
741}
742 
743std::vector<std::tuple<std::string, u32, u32>> PKG::GetAllEntries() const {
744 LOG_DEBUG(Common, "Calling GetAllEntries, fsTable size: {}", fsTable.size());
745 std::vector<std::tuple<std::string, u32, u32>> entries;
746 for (const auto &entry : fsTable) {
747 LOG_DEBUG(Common, "fsTable entry: name={}, inode={}, type={}", entry.name,
748 entry.inode, entry.type);
749 entries.emplace_back(entry.name, entry.inode, entry.type);
750 }
751 return entries;
752}
753 
754bool PKG::Scan(const std::filesystem::path &filepath, std::string &failreason,
755 std::filesystem::path extract_root) {
756 LOG_DEBUG(Common, "Inizio PKG::Scan su {}", filepath.string());
757 extract_path = extract_root; // base relativa per ricostruire i path
758 pkgpath = filepath;
759 
760 Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
761 if (!file.IsOpen()) {
762 failreason = "Unable to open file";
763 LOG_ERROR(Common, "{}: {}", failreason, filepath.string());
764 return false;
765 }
766 pkgSize = file.GetSize();
767 file.ReadRaw<u8>(&pkgheader, sizeof(PKGHeader));
768 
769 if (pkgheader.magic != 0x7F434E54) {
770 failreason = "Invalid PKG Magic";
771 LOG_ERROR(Common, "{}", failreason);
772 return false;
773 }
774 
775 // Readable flags
776 pkgFlags.clear();
777 for (const auto &flag : flagNames) {
778 if (isFlagSet(pkgheader.pkg_content_flags, flag.first)) {
779 if (!pkgFlags.empty())
780 pkgFlags += ", ";
781 pkgFlags += flag.second;
782 }
783 }
784 
785 // TitleID
786 file.Seek(0x47);
787 file.Read(pkgTitleID);
788 
789 // Read table entries to find param.sfo etc. (no disk writes)
790 u32 offset = pkgheader.pkg_table_entry_offset;
791 u32 n_files = pkgheader.pkg_table_entry_count;
792 if (!file.Seek(offset)) {
793 failreason = "Seek failed to table entry";
794 return false;
795 }
796 pkgEntries.clear();
797 for (u32 i = 0; i < n_files; i++) {
798 PKGEntry entry{};
799 file.Read(entry.id);
800 file.Read(entry.filename_offset);
801 file.Read(entry.flags1);
802 file.Read(entry.flags2);
803 file.Read(entry.offset);
804 file.Read(entry.size);
805 file.Seek(8, Common::FS::SeekOrigin::CurrentPosition);
806 pkgEntries.push_back(entry);
807 }
808 
809 // Seed e decrypt PFS come in Extract(), ma senza scritture file.
810 if (pkgheader.pkg_size > pkgSize) {
811 failreason = "Inconsistent PKG size";
812 return false;
813 }
814 if ((pkgheader.pkg_content_size + pkgheader.pkg_content_offset) >
815 pkgheader.pkg_size) {
816 failreason = "Content size exceeds pkg size";
817 return false;
818 }
819 
820 // Avanza fino a popolare sectorMap come in Extract()
821 // Leggi strutture preliminari necessarie a derivare le chiavi
822 // e popolare ivKey, imgKey, ekpfsKey.
823 // Riusa lo stesso codice di Extract per le voci necessarie.
824 
825 // Riposizionarsi sulla tabella entries
826 file.Seek(pkgheader.pkg_table_entry_offset);
827 std::array<u8, 64> concatenated_ivkey_dk3;
828 std::array<u8, 32> seed_digest;
829 std::array<std::array<u8, 32>, 7> digest1;
830 std::array<std::array<u8, 256>, 7> key1;
831 std::array<u8, 256> imgkeydata;
832 
833 for (u32 i = 0; i < n_files; i++) {
834 PKGEntry entry{};
835 file.Read(entry.id);
836 file.Read(entry.filename_offset);
837 file.Read(entry.flags1);
838 file.Read(entry.flags2);
839 file.Read(entry.offset);
840 file.Read(entry.size);
841 file.Seek(8, Common::FS::SeekOrigin::CurrentPosition);
842 
843 auto currentPos = file.Tell();
844 if (entry.id == 0x10) { // ENTRY_KEYS
845 file.Seek(entry.offset);
846 file.Read(seed_digest);
847 for (int j = 0; j < 7; j++)
848 file.Read(digest1[j]);
849 for (int j = 0; j < 7; j++)
850 file.Read(key1[j]);
851 PKG::crypto.RSA2048Decrypt(dk3_, key1[3], true); // decrypt DK3
852 } else if (entry.id == 0x20) { // IMAGE_KEY (IV_KEY)
853 file.Seek(entry.offset);
854 file.Read(imgkeydata);
855 std::memcpy(concatenated_ivkey_dk3.data(), &entry, sizeof(entry));
856 std::memcpy(concatenated_ivkey_dk3.data() + sizeof(entry), dk3_.data(),
857 sizeof(dk3_));
858 PKG::crypto.ivKeyHASH256(concatenated_ivkey_dk3, ivKey);
859 PKG::crypto.aesCbcCfb128Decrypt(ivKey, imgkeydata, imgKey);
860 PKG::crypto.RSA2048Decrypt(ekpfsKey, imgKey, false);
861 }
862 file.Seek(currentPos);
863 }
864 
865 // Seed
866 std::array<u8, 16> seed;
867 if (!file.Seek(pkgheader.pfs_image_offset + 0x370)) {
868 failreason = "Seek failed to PFS seed";
869 return false;
870 }
871 file.Read(seed);
872 PKG::crypto.PfsGenCryptoKey(ekpfsKey, seed, dataKey, tweakKey);
873 
874 const u32 length = pkgheader.pfs_cache_size * 0x2;
875 int num_blocks = 0;
876 std::vector<u8> pfsc(length);
877 if (length != 0) {
878 std::vector<u8> pfs_encrypted(length);
879 file.Seek(pkgheader.pfs_image_offset);
880 file.Read(pfs_encrypted);
881 file.Close();
882 
883 std::vector<u8> pfs_decrypted(length);
884 PKG::crypto.decryptPFS(dataKey, tweakKey, pfs_encrypted, pfs_decrypted, 0);
885 
886 pfsc_offset = GetPFSCOffset(pfs_decrypted.data(), pfs_decrypted.size());
887 std::memcpy(pfsc.data(), pfs_decrypted.data() + pfsc_offset,
888 length - pfsc_offset);
889 
890 PFSCHdr pfsChdr;
891 std::memcpy(&pfsChdr, pfsc.data(), sizeof(pfsChdr));
892 num_blocks = (int)(pfsChdr.data_length / pfsChdr.block_sz2);
893 sectorMap.resize(num_blocks + 1);
894 for (int i = 0; i < num_blocks + 1; i++) {
895 std::memcpy(&sectorMap[i], pfsc.data() + pfsChdr.block_offsets + i * 8,
896 8);
897 }
898 }
899 
900 // Parse iNodes e Dirents per popolare fsTable/extractPaths
901 iNodeBuf.clear();
902 fsTable.clear();
903 extractPaths.clear();
904 u32 ent_size = 0;
905 u32 ndinode = 0;
906 int ndinode_counter = 0;
907 bool dinode_reached = false;
908 bool uroot_reached = false;
909 std::vector<char> compressedData;
910 std::vector<char> decompressedData(0x10000);
911 
912 for (int i = 0; i < (int)sectorMap.size() - 1; i++) {
913 const u64 sectorOffset = sectorMap[i];
914 const u64 sectorSize = sectorMap[i + 1] - sectorOffset;
915 compressedData.resize(sectorSize);
916 std::memcpy(compressedData.data(), pfsc.data() + sectorOffset, sectorSize);
917 if (sectorSize == 0x10000)
918 std::memcpy(decompressedData.data(), compressedData.data(), 0x10000);
919 else if (sectorSize < 0x10000)
920 DecompressPFSC(compressedData.data(), compressedData.size(),
921 decompressedData.data(), decompressedData.size());
922 
923 if (i == 0) {
924 std::memcpy(&ndinode, decompressedData.data() + 0x30, 4);
925 }
926 
927 int occupied_blocks = (ndinode * 0xA8) / 0x10000;
928 if (((ndinode * 0xA8) % 0x10000) != 0)
929 occupied_blocks += 1;
930 
931 if (i >= 1 && i <= occupied_blocks) {
932 for (int p = 0; p < 0x10000; p += 0xA8) {
933 Inode node;
934 std::memcpy(&node, &decompressedData[p], sizeof(node));
935 if (node.Mode == 0)
936 break;
937 iNodeBuf.push_back(node);
938 }
939 }
940 
941 const std::string_view flat_path_table(&decompressedData[0x10], 15);
942 if (flat_path_table == "flat_path_table") {
943 uroot_reached = true;
944 }
945 
946 if (uroot_reached) {
947 for (int k = 0; k < 0x10000; k += ent_size) {
948 Dirent dirent;
949 std::memcpy(&dirent, &decompressedData[k], sizeof(dirent));
950 ent_size = dirent.entsize;
951 if (dirent.ino != 0) {
952 ndinode_counter++;
953 } else {
954 extractPaths[ndinode_counter] = extract_path;
955 uroot_reached = false;
956 break;
957 }
958 }
959 }
960 
961 const char dot = decompressedData[0x10];
962 const std::string_view dotdot(&decompressedData[0x28], 2);
963 if (dot == '.' && dotdot == "..") {
964 dinode_reached = true;
965 }
966 
967 bool end_reached = false;
968 if (dinode_reached) {
969 for (int j = 0; j < 0x10000; j += ent_size) {
970 Dirent dirent;
971 std::memcpy(&dirent, &decompressedData[j], sizeof(dirent));
972 if (dirent.ino == 0)
973 break;
974 ent_size = dirent.entsize;
975 auto &table = fsTable.emplace_back();
976 table.name = std::string(dirent.name, dirent.namelen);
977 table.inode = dirent.ino;
978 table.type = dirent.type;
979 if (table.type == PFS_CURRENT_DIR) {
980 current_dir = extractPaths[table.inode];
981 }
982 extractPaths[table.inode] =
983 current_dir / std::filesystem::path(table.name);
984 if (table.type == PFS_FILE || table.type == PFS_DIR) {
985 if (table.type == PFS_DIR) {
986 // niente scritture su disco in Scan
987 }
988 ndinode_counter++;
989 if ((ndinode_counter + 1) == ndinode)
990 end_reached = true;
991 }
992 }
993 if (end_reached) {
994 LOG_DEBUG(Common, "end_reached=true, break ciclo blocchi");
995 break;
996 }
997 }
998 }
999 LOG_DEBUG(Common, "Fine PKG::Scan, entries: {}", fsTable.size());
1000 return true;
1001}
1002 
1003std::vector<PKG::EntryInfo> PKG::GetEntriesInfo() const {
1004 std::vector<EntryInfo> out;
1005 out.reserve(fsTable.size());
1006 for (const auto &e : fsTable) {
1007 EntryInfo info{};
1008 info.name = e.name;
1009 info.inode = e.inode;
1010 info.type = e.type;
1011 auto it = extractPaths.find(e.inode);
1012 info.path =
1013 (it != extractPaths.end()) ? it->second.string() : std::string();
1014 if (e.type == PFS_FILE && e.inode < iNodeBuf.size()) {
1015 info.size = static_cast<u64>(iNodeBuf[e.inode].Size);
1016 info.blocks = static_cast<u32>(iNodeBuf[e.inode].Blocks);
1017 info.loc = static_cast<u32>(iNodeBuf[e.inode].loc);
1018 } else {
1019 info.size = 0;
1020 info.blocks = 0;
1021 info.loc = 0;
1022 }
1023 out.push_back(std::move(info));
1024 }
1025 return out;
1026}
1027