From bfb60e4c84d69fea838724a5e839b6a9e14d2871 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 31 Jan 2023 10:05:01 +0200 Subject: [PATCH] pkgextract tool : added already working code from gui version --- tools/pkg extractor/FsFile.cpp | 90 ++++++++ tools/pkg extractor/FsFile.h | 72 +++++++ tools/pkg extractor/PKG.cpp | 112 ++++++++++ tools/pkg extractor/PKG.h | 201 ++++++++++++++++++ tools/pkg extractor/Types.h | 46 ++++ tools/pkg extractor/pkgextract.vcxproj | 7 + .../pkg extractor/pkgextract.vcxproj.filters | 17 ++ 7 files changed, 545 insertions(+) create mode 100644 tools/pkg extractor/FsFile.cpp create mode 100644 tools/pkg extractor/FsFile.h create mode 100644 tools/pkg extractor/PKG.cpp create mode 100644 tools/pkg extractor/PKG.h create mode 100644 tools/pkg extractor/Types.h diff --git a/tools/pkg extractor/FsFile.cpp b/tools/pkg extractor/FsFile.cpp new file mode 100644 index 00000000..311fc8e5 --- /dev/null +++ b/tools/pkg extractor/FsFile.cpp @@ -0,0 +1,90 @@ +#include "FsFile.h" + +FsFile::FsFile() +{ + m_file = nullptr; +} +FsFile::FsFile(const std::string& path, fsOpenMode mode) +{ + Open(path, mode); +} +bool FsFile::Open(const std::string& path, fsOpenMode mode) +{ + Close(); + fopen_s(&m_file, path.c_str(), getOpenMode(mode)); + return IsOpen(); +} +bool FsFile::Close() +{ + if (!IsOpen() || std::fclose(m_file) != 0) { + m_file = nullptr; + return false; + } + + m_file = nullptr; + return true; +} +FsFile::~FsFile() +{ + Close(); +} + +bool FsFile::Write(const void* src, U64 size) +{ + if (!IsOpen() || std::fwrite(src, 1, size, m_file) != size) { + return false; + } + return true; +} + +bool FsFile::Read(void* dst, U64 size) +{ + if (!IsOpen() || std::fread(dst, 1, size, m_file) != size) { + return false; + } + return true; +} + +U32 FsFile::ReadBytes(void* dst, U64 size) +{ + return std::fread(dst, 1, size, m_file); +} + +bool FsFile::Seek(S64 offset, fsSeekMode mode) +{ + if (!IsOpen() || _fseeki64(m_file, offset, getSeekMode(mode)) != 0) { + return false; + } + return true; +} + +U64 FsFile::Tell() const +{ + if (IsOpen()) { + return _ftelli64(m_file); + } + else { + return -1; + } +} +U64 FsFile::getFileSize() +{ + U64 pos = _ftelli64(m_file); + if (_fseeki64(m_file, 0, SEEK_END) != 0) { + + return 0; + } + + U64 size = _ftelli64(m_file); + if (_fseeki64(m_file, pos, SEEK_SET) != 0) { + + return 0; + } + return size; +} + +bool FsFile::IsOpen() const +{ + return m_file != nullptr; +} + diff --git a/tools/pkg extractor/FsFile.h b/tools/pkg extractor/FsFile.h new file mode 100644 index 00000000..7de85272 --- /dev/null +++ b/tools/pkg extractor/FsFile.h @@ -0,0 +1,72 @@ +#pragma once +#include +#include +#include "Types.h" + +enum fsOpenMode +{ + fsRead = 0x1, + fsWrite = 0x2, + fsReadWrite = fsRead | fsWrite +}; + +enum fsSeekMode +{ + fsSeekSet, + fsSeekCur, + fsSeekEnd, +}; + +class FsFile +{ + std::FILE* m_file; +public: + FsFile(); + FsFile(const std::string& path, fsOpenMode mode = fsRead); + bool Open(const std::string& path, fsOpenMode mode = fsRead); + bool IsOpen() const; + bool Close(); + bool Read(void* dst, U64 size); + U32 ReadBytes(void* dst, U64 size); + bool Write(const void* src, U64 size); + bool Seek(S64 offset, fsSeekMode mode); + U64 getFileSize(); + U64 Tell() const; + ~FsFile(); + + template< typename T > bool ReadBE(T& dst) + { + if (!Read(&dst, sizeof(T))) { + return false; + } + ::ReadBE(dst); + return true; + } + + const char* getOpenMode(fsOpenMode mode) + { + switch (mode) { + case fsRead: return "rb"; + case fsWrite: return "wb"; + case fsReadWrite: return "r+b"; + } + + return "r"; + } + + const int getSeekMode(fsSeekMode mode) + { + switch (mode) { + case fsSeekSet: return SEEK_SET; + case fsSeekCur: return SEEK_CUR; + case fsSeekEnd: return SEEK_END; + } + + return SEEK_SET; + } + std::FILE* fileDescr() + { + return m_file; + } +}; + diff --git a/tools/pkg extractor/PKG.cpp b/tools/pkg extractor/PKG.cpp new file mode 100644 index 00000000..3b439b93 --- /dev/null +++ b/tools/pkg extractor/PKG.cpp @@ -0,0 +1,112 @@ +#include "PKG.h" +#include "FsFile.h" +#include + +PKG::PKG() +{ +} + + +PKG::~PKG() +{ +} + +bool PKG::open(const std::string& filepath) { + FsFile file; + if (!file.Open(filepath, fsRead)) + { + return false; + } + pkgSize = file.getFileSize(); + PKGHeader pkgheader; + file.ReadBE(pkgheader); + //we have already checked magic should be ok + + //find title id it is part of pkg_content_id starting at offset 0x40 + file.Seek(0x47, fsSeekSet);//skip first 7 characters of content_id + file.Read(&pkgTitleID, sizeof(pkgTitleID)); + + file.Close(); + + return true; +} +bool PKG::extract(const std::string& filepath, const std::string& extractPath, std::string& failreason) +{ + this->extractPath = extractPath; + FsFile file; + if (!file.Open(filepath, fsRead)) + { + return false; + } + pkgSize = file.getFileSize(); + PKGHeader pkgheader; + file.ReadBE(pkgheader); + + if (pkgheader.pkg_size > pkgSize) + { + failreason = "PKG file size is different"; + return false; + } + if ((pkgheader.pkg_content_size + pkgheader.pkg_content_offset) > pkgheader.pkg_size) + { + failreason = "Content size is bigger than pkg size"; + return false; + } + file.Seek(0, fsSeekSet); + pkg = (U08*)mmap(pkgSize, file.fileDescr()); + if (pkg == nullptr) + { + failreason = "Can't allocate size for image"; + return false; + } + + file.Read(pkg, pkgSize); + + U32 offset = pkgheader.pkg_table_entry_offset; + U32 n_files = pkgheader.pkg_table_entry_count; + + + for (U32 i = 0; i < n_files; i++) { + PKGEntry entry = (PKGEntry&)pkg[offset + i * 0x20]; + ReadBE(entry); + //try to figure out the name + std::string name = getEntryNameByType(entry.id); + if (!name.empty()) + { + std::size_t pos = name.find("/");//check if we have a directory (assuming we only have 1 level of subdirectories) + if (pos != std::string::npos) + { + //directory found + std::string dir = name.substr(0, pos+1);//include '/' as well + std::string path = extractPath + dir; + _mkdir(path.c_str());//make dir + std::string file = name.substr(pos + 1);//read filename + FsFile out; + out.Open(path + file, fsWrite); + out.Write(pkg + entry.offset, entry.size); + out.Close(); + + } + //found an name use it + FsFile out; + out.Open(extractPath + name, fsWrite); + out.Write(pkg + entry.offset, entry.size); + out.Close(); + } + else + { + //just print with id + FsFile out; + out.Open(extractPath + std::to_string(entry.id), fsWrite); + out.Write(pkg + entry.offset, entry.size); + out.Close(); + } + } + //extract pfs_image.dat + FsFile out; + out.Open(extractPath + "pfs_image.dat", fsWrite); + out.Write(pkg + pkgheader.pfs_image_offset, pkgheader.pfs_image_size); + out.Close(); + munmap(pkg); + return true; +} \ No newline at end of file diff --git a/tools/pkg extractor/PKG.h b/tools/pkg extractor/PKG.h new file mode 100644 index 00000000..ea8f810f --- /dev/null +++ b/tools/pkg extractor/PKG.h @@ -0,0 +1,201 @@ +#pragma once +#include +#include "Types.h" +#include +#include +#include + +struct PKGHeader { + /*BE*/U32 magic;// Magic + /*BE*/U32 pkg_type; + /*BE*/U32 pkg_0x8; //unknown field + /*BE*/U32 pkg_file_count; + /*BE*/U32 pkg_table_entry_count; + /*BE*/U16 pkg_sc_entry_count; + /*BE*/U16 pkg_table_entry_count_2;// same as pkg_entry_count + /*BE*/U32 pkg_table_entry_offset;//file table offset + /*BE*/U32 pkg_sc_entry_data_size; + /*BE*/U64 pkg_body_offset;//offset of PKG entries + /*BE*/U64 pkg_body_size;//length of all PKG entries + /*BE*/U64 pkg_content_offset; + /*BE*/U64 pkg_content_size; + U08 pkg_content_id[0x24];//packages' content ID as a 36-byte string + U08 pkg_padding[0xC];//padding + /*BE*/U32 pkg_drm_type;//DRM type + /*BE*/U32 pkg_content_type;//Content type + /*BE*/U32 pkg_content_flags;//Content flags + /*BE*/U32 pkg_promote_size; + /*BE*/U32 pkg_version_date; + /*BE*/U32 pkg_version_hash; + /*BE*/U32 pkg_0x088; + /*BE*/U32 pkg_0x08C; + /*BE*/U32 pkg_0x090; + /*BE*/U32 pkg_0x094; + /*BE*/U32 pkg_iro_tag; + /*BE*/U32 pkg_drm_type_version; + + U08 pkg_zeroes_1[0x60]; + + /* Digest table */ + U08 digest_entries1[0x20]; // sha256 digest for main entry 1 + U08 digest_entries2[0x20]; // sha256 digest for main entry 2 + U08 digest_table_digest[0x20]; // sha256 digest for digest table + U08 digest_body_digest[0x20]; // sha256 digest for main table + + U08 pkg_zeroes_2[0x280]; + + U32 pkg_0x400; + + U32 pfs_image_count; // count of PFS images + U64 pfs_image_flags; // PFS flags + U64 pfs_image_offset; // offset to start of external PFS image + U64 pfs_image_size; // size of external PFS image + U64 mount_image_offset; + U64 mount_image_size; + U64 pkg_size; + U32 pfs_signed_size; + U32 pfs_cache_size; + U08 pfs_image_digest[0x20]; + U08 pfs_signed_digest[0x20]; + U64 pfs_split_size_nth_0; + U64 pfs_split_size_nth_1; + + U08 pkg_zeroes_3[0xB50]; + + U08 pkg_digest[0x20]; + +}; + +inline void ReadBE(PKGHeader& s) +{ + ReadBE(s.magic); + ReadBE(s.pkg_table_entry_offset); + ReadBE(s.pkg_table_entry_count); + ReadBE(s.pkg_content_offset); + ReadBE(s.pkg_content_size); + ReadBE(s.pfs_image_offset); + ReadBE(s.pfs_image_size); + ReadBE(s.pkg_size); +} + +struct PKGEntry { + U32 id; // File ID, useful for files without a filename entry + U32 filename_offset; // Offset into the filenames table (ID 0x200) where this file's name is located + U32 flags1; // Flags including encrypted flag, etc + U32 flags2; // Flags including encryption key index, etc + U32 offset; // Offset into PKG to find the file + U32 size; // Size of the file + U64 padding; // blank padding +}; + +inline void ReadBE(PKGEntry& s) +{ + ReadBE(s.id); + ReadBE(s.filename_offset); + ReadBE(s.flags1); + ReadBE(s.flags2); + ReadBE(s.offset); + ReadBE(s.size); + ReadBE(s.padding); +} + +class PKG +{ +private: + U08* pkg; + U64 pkgSize = 0; + S08 pkgTitleID[9]; + std::string extractPath; + +public: + PKG(); + ~PKG(); + bool open(const std::string& filepath); + U64 getPkgSize() + { + return pkgSize; + } + std::string getTitleID() + { + return std::string(pkgTitleID,9); + } + bool extract(const std::string& filepath, const std::string& extractPath, std::string& failreason); + + void* mmap(size_t sLength, std::FILE* nFd) { + HANDLE hHandle; + void* pStart=nullptr; + hHandle = CreateFileMapping( + (HANDLE)_get_osfhandle(_fileno((nFd))), + NULL, // default security + PAGE_WRITECOPY, // read/write access + 0, // maximum object size (high-order DWORD) + 0, // maximum object size (low-order DWORD) + NULL); // name of mapping object + + if (hHandle != NULL) { + pStart = MapViewOfFile(hHandle, FILE_MAP_COPY, 0, 0, sLength); + } + if(pStart == NULL) + { + return nullptr; + } + return pStart; + } + int munmap(void* pStart) { + if (UnmapViewOfFile(pStart) != 0) + return FALSE; + + return TRUE; + } + typedef struct { + U32 type; + std::string name; + } pkg_entry_value; + + std::string getEntryNameByType(U32 type) + { + pkg_entry_value entries[] = { + { 0x0001, "digests" }, + { 0x0010, "entry_keys" }, + { 0x0020, "image_key" }, + { 0x0080, "general_digests" }, + { 0x0100, "metas" }, + { 0x0200, "entry_names" }, + { 0x0400, "license.dat" }, + { 0x0401, "license.info" }, + { 0x0402, "nptitle.dat" }, + { 0x0403, "npbind.dat" }, + { 0x0409, "psreserved.dat" }, + { 0x1000, "param.sfo" }, + { 0x1001, "playgo-chunk.dat" }, + { 0x1002, "playgo-chunk.sha" }, + { 0x1003, "playgo-manifest.xml" }, + { 0x1004, "pronunciation.xml" }, + { 0x1005, "pronunciation.sig" }, + { 0x1006, "pic1.png" }, + { 0x1007, "pubtoolinfo.dat" }, + { 0x100B, "shareparam.json" }, + { 0x100C, "shareoverlayimage.png" }, + { 0x100E, "shareprivacyguardimage.png"}, + { 0x1200, "icon0.png" }, + { 0x1220, "pic0.png" }, + { 0x1240, "snd0.at9" }, + { 0x1280, "icon0.dds" }, + { 0x12A0, "pic0.dds" }, + { 0x12C0, "pic1.dds" }, + { 0x1400, "trophy/trophy00.trp" } + }; + std::string entry_name=""; + + for (size_t i = 0; i < sizeof entries / sizeof entries[0]; i++) { + if (type == entries[i].type) { + entry_name = entries[i].name; + break; + } + } + + return entry_name; + } + +}; + diff --git a/tools/pkg extractor/Types.h b/tools/pkg extractor/Types.h new file mode 100644 index 00000000..aeb19eb7 --- /dev/null +++ b/tools/pkg extractor/Types.h @@ -0,0 +1,46 @@ +#pragma once +#include + +using S08 = char; +using S16 = short; +using S32 = int; +using S64 = long long; + +using U08 = unsigned char; +using U16 = unsigned short; +using U32 = unsigned int; +using U64 = unsigned long long; + +using F32 = float; +using F64 = double; + + +template< typename T > T inline LoadBE(T* src) { return *src; }; +template< typename T > inline void StoreBE(T* dst, T src) { *dst = src; }; + +inline S16 LoadBE(S16* src) { return _loadbe_i16(src); }; +inline S32 LoadBE(S32* src) { return _loadbe_i32(src); }; +inline S64 LoadBE(S64* src) { return _loadbe_i64(src); }; + +inline U16 LoadBE(U16* src) { return _load_be_u16(src); }; +inline U32 LoadBE(U32* src) { return _load_be_u32(src); }; +inline U64 LoadBE(U64* src) { return _load_be_u64(src); }; + +inline void StoreBE(S16* dst, S16 const src) { _storebe_i16(dst, src); }; +inline void StoreBE(S32* dst, S32 const src) { _storebe_i32(dst, src); }; +inline void StoreBE(S64* dst, S64 const src) { _storebe_i64(dst, src); }; + +inline void StoreBE(U16* dst, U16 const src) { _store_be_u16(dst, src); }; +inline void StoreBE(U32* dst, U32 const src) { _store_be_u32(dst, src); }; +inline void StoreBE(U64* dst, U64 const src) { _store_be_u64(dst, src); }; + + +template< typename T > inline void ReadBE(T& val) +{ + val = LoadBE(&val); // swap inplace +} + +template< typename T > inline void WriteBE(T& val) +{ + StoreBE(&val, val); // swap inplace +} \ No newline at end of file diff --git a/tools/pkg extractor/pkgextract.vcxproj b/tools/pkg extractor/pkgextract.vcxproj index c2e4518a..735d7e16 100644 --- a/tools/pkg extractor/pkgextract.vcxproj +++ b/tools/pkg extractor/pkgextract.vcxproj @@ -127,8 +127,15 @@ + + + + + + + diff --git a/tools/pkg extractor/pkgextract.vcxproj.filters b/tools/pkg extractor/pkgextract.vcxproj.filters index bec5b80e..0a91041f 100644 --- a/tools/pkg extractor/pkgextract.vcxproj.filters +++ b/tools/pkg extractor/pkgextract.vcxproj.filters @@ -18,5 +18,22 @@ Source Files + + Source Files + + + Source Files + + + + + Source Files + + + Source Files + + + Source Files + \ No newline at end of file