- Added trophy decryption when extracting a fpkg. trp icons and xmls are dumped to game_data/<title> (can be restored if deleted by accident by opening the trophy viewer)

- Added a trophy viewer (right click on game ==> trophy viewer)
- Enabled Run button.
- Switched gui settings to toml.
- Added recent files (6 max)
- Applied @raphaelthegreat suggestions and corrections (Thanks a lot).
- Fixed several bugs and crashes.
- Full screen should disabled by default.
- Added region in list mode.
- Added a simple temp elf list widget.
- Added messages when extracting pkg (ex: installing a patch before the game...etc)
This commit is contained in:
raziel1000 2024-06-10 20:42:21 -06:00
parent 71dda8c776
commit 0f27e0edf2
49 changed files with 1616 additions and 891 deletions

View file

@ -45,26 +45,54 @@ PKG::PKG() = default;
PKG::~PKG() = default;
bool PKG::Open(const std::string& filepath) {
bool PKG::Open(const std::filesystem::path& filepath) {
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
return false;
}
pkgSize = file.GetSize();
PKGHeader pkgheader;
file.Read(pkgheader);
if (pkgheader.magic != 0x7F434E54)
return false;
for (const auto& flag : flagNames) {
if (isFlagSet(pkgheader.pkg_content_flags, flag.first)) {
if (!pkgFlags.empty())
pkgFlags += (", ");
pkgFlags += (flag.second);
}
}
// Find title id it is part of pkg_content_id starting at offset 0x40
file.Seek(0x47); // skip first 7 characters of content_id
file.Read(pkgTitleID);
file.Seek(0);
pkg.resize(pkgheader.pkg_promote_size);
file.Read(pkg);
u32 offset = pkgheader.pkg_table_entry_offset;
u32 n_files = pkgheader.pkg_table_entry_count;
for (int i = 0; i < n_files; i++) {
PKGEntry entry;
std::memcpy(&entry, &pkg[offset + i * 0x20], sizeof(entry));
// Try to figure out the name
const auto name = GetEntryNameByType(entry.id);
if (name == "param.sfo") {
sfo.clear();
file.Seek(entry.offset);
sfo.resize(entry.size);
file.ReadRaw<u8>(sfo.data(), entry.size);
}
}
file.Close();
return true;
}
bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extract,
bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::path& extract,
std::string& failreason) {
extract_path = extract;
pkgpath = filepath;
@ -75,6 +103,9 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr
pkgSize = file.GetSize();
file.ReadRaw<u8>(&pkgheader, sizeof(PKGHeader));
if (pkgheader.magic != 0x7F434E54)
return false;
if (pkgheader.pkg_size > pkgSize) {
failreason = "PKG file size is different";
return false;
@ -90,6 +121,7 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr
u32 offset = pkgheader.pkg_table_entry_offset;
u32 n_files = pkgheader.pkg_table_entry_count;
std::array<u8, 64> concatenated_ivkey_dk3;
std::array<u8, 32> seed_digest;
std::array<std::array<u8, 32>, 7> digest1;
std::array<std::array<u8, 256>, 7> key1;
@ -101,6 +133,9 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr
// Try to figure out the name
const auto name = GetEntryNameByType(entry.id);
const auto filepath = extract_path / "sce_sys" / name;
std::filesystem::create_directories(filepath.parent_path());
if (name.empty()) {
// Just print with id
Common::FS::IOFile out(extract_path / "sce_sys" / std::to_string(entry.id),
@ -110,9 +145,6 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr
continue;
}
const auto filepath = extract_path / "sce_sys" / name;
std::filesystem::create_directories(filepath.parent_path());
if (entry.id == 0x1) { // DIGESTS, seek;
// file.Seek(entry.offset, fsSeekSet);
} else if (entry.id == 0x10) { // ENTRY_KEYS, seek;
@ -133,7 +165,6 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr
file.Read(imgkeydata);
// The Concatenated iv + dk3 imagekey for HASH256
std::array<CryptoPP::byte, 64> concatenated_ivkey_dk3;
std::memcpy(concatenated_ivkey_dk3.data(), &entry, sizeof(entry));
std::memcpy(concatenated_ivkey_dk3.data() + sizeof(entry), dk3_.data(), sizeof(dk3_));
@ -150,10 +181,33 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr
Common::FS::IOFile out(extract_path / "sce_sys" / name, Common::FS::FileAccessMode::Write);
out.WriteRaw<u8>(pkg.data() + entry.offset, entry.size);
out.Close();
// Decrypt Np stuff and overwrite.
if (entry.id == 0x400 || entry.id == 0x401 || entry.id == 0x402 ||
entry.id == 0x403) { // somehow 0x401 is not decrypting
decNp.resize(entry.size);
std::span<u8> cipherNp(pkg.data() + entry.offset, entry.size);
std::array<u8, 64> concatenated_ivkey_dk3_;
std::memcpy(concatenated_ivkey_dk3_.data(), &entry, sizeof(entry));
std::memcpy(concatenated_ivkey_dk3_.data() + sizeof(entry), dk3_.data(), sizeof(dk3_));
PKG::crypto.ivKeyHASH256(concatenated_ivkey_dk3_, ivKey);
PKG::crypto.aesCbcCfb128DecryptEntry(ivKey, cipherNp, decNp);
Common::FS::IOFile out(extract_path / "sce_sys" / name,
Common::FS::FileAccessMode::Write);
out.Write(decNp);
out.Close();
}
}
// Extract trophy files
if (!trp.Extract(extract_path)) {
// Do nothing some pkg come with no trp file.
// return false;
}
// Read the seed
std::array<CryptoPP::byte, 16> seed;
std::array<u8, 16> seed;
file.Seek(pkgheader.pfs_image_offset + 0x370);
file.Read(seed);
@ -165,7 +219,7 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr
std::vector<u8> pfs_encrypted(length);
file.Seek(pkgheader.pfs_image_offset);
file.Read(pfs_encrypted);
file.Close();
// Decrypt the pfs_image.
std::vector<u8> pfs_decrypted(length);
PKG::crypto.decryptPFS(dataKey, tweakKey, pfs_encrypted, pfs_decrypted, 0);

View file

@ -12,6 +12,7 @@
#include "common/endian.h"
#include "core/crypto/crypto.h"
#include "pfs.h"
#include "trp.h"
struct PKGHeader {
u32_be magic; // Magic
@ -103,11 +104,13 @@ public:
PKG();
~PKG();
bool Open(const std::string& filepath);
bool Open(const std::filesystem::path& filepath);
void ExtractFiles(const int& index);
bool Extract(const std::string& filepath, const std::filesystem::path& extract,
bool Extract(const std::filesystem::path& filepath, const std::filesystem::path& extract,
std::string& failreason);
std::vector<u8> sfo;
u32 GetNumberOfFiles() {
return fsTable.size();
}
@ -116,16 +119,42 @@ public:
return pkgSize;
}
std::string GetPkgFlags() {
return pkgFlags;
}
std::string_view GetTitleID() {
return std::string_view(pkgTitleID, 9);
}
PKGHeader GetPkgHeader() {
return pkgheader;
}
static bool isFlagSet(u32_be variable, PKGContentFlag flag) {
return (variable) & static_cast<u32>(flag);
}
static constexpr std::array<std::pair<PKGContentFlag, std::string_view>, 10> flagNames = {
{{PKGContentFlag::FIRST_PATCH, "FIRST_PATCH"},
{PKGContentFlag::PATCHGO, "PATCHGO"},
{PKGContentFlag::REMASTER, "REMASTER"},
{PKGContentFlag::PS_CLOUD, "PS_CLOUD"},
{PKGContentFlag::GD_AC, "GD_AC"},
{PKGContentFlag::NON_GAME, "NON_GAME"},
{PKGContentFlag::UNKNOWN_0x8000000, "UNKNOWN_0x8000000"},
{PKGContentFlag::SUBSEQUENT_PATCH, "SUBSEQUENT_PATCH"},
{PKGContentFlag::DELTA_PATCH, "DELTA_PATCH"},
{PKGContentFlag::CUMULATIVE_PATCH, "CUMULATIVE_PATCH"}}};
private:
Crypto crypto;
TRP trp;
std::vector<u8> pkg;
u64 pkgSize = 0;
char pkgTitleID[9];
PKGHeader pkgheader;
std::string pkgFlags;
std::unordered_map<int, std::filesystem::path> extractPaths;
std::vector<pfs_fs_table> fsTable;
@ -133,12 +162,13 @@ private:
std::vector<u64> sectorMap;
u64 pfsc_offset;
std::array<CryptoPP::byte, 32> dk3_;
std::array<CryptoPP::byte, 32> ivKey;
std::array<CryptoPP::byte, 256> imgKey;
std::array<CryptoPP::byte, 32> ekpfsKey;
std::array<CryptoPP::byte, 16> dataKey;
std::array<CryptoPP::byte, 16> tweakKey;
std::array<u8, 32> dk3_;
std::array<u8, 32> ivKey;
std::array<u8, 256> imgKey;
std::array<u8, 32> ekpfsKey;
std::array<u8, 16> dataKey;
std::array<u8, 16> tweakKey;
std::vector<u8> decNp;
std::filesystem::path pkgpath;
std::filesystem::path current_dir;

View file

@ -12,16 +12,22 @@ PSF::PSF() = default;
PSF::~PSF() = default;
bool PSF::open(const std::string& filepath) {
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
return false;
}
bool PSF::open(const std::string& filepath, std::vector<u8> psfBuffer) {
if (!psfBuffer.empty()) {
psf.resize(psfBuffer.size());
psf = psfBuffer;
} else {
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
return false;
}
const u64 psfSize = file.GetSize();
psf.resize(psfSize);
file.Seek(0);
file.Read(psf);
const u64 psfSize = file.GetSize();
psf.resize(psfSize);
file.Seek(0);
file.Read(psf);
file.Close();
}
// Parse file contents
PSFHeader header;

View file

@ -35,7 +35,7 @@ public:
PSF();
~PSF();
bool open(const std::string& filepath);
bool open(const std::string& filepath, std::vector<u8> psfBuffer);
std::string GetString(const std::string& key);
u32 GetInteger(const std::string& key);

View file

@ -0,0 +1,91 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "trp.h"
TRP::TRP() = default;
TRP::~TRP() = default;
void TRP::GetNPcommID(std::filesystem::path trophyPath, int index) {
std::filesystem::path trpPath = trophyPath / "sce_sys/npbind.dat";
Common::FS::IOFile npbindFile(trpPath, Common::FS::FileAccessMode::Read);
if (!npbindFile.IsOpen()) {
return;
}
npbindFile.Seek(0x84 + (index * 0x180));
npbindFile.ReadRaw<u8>(np_comm_id.data(), 12);
std::fill(np_comm_id.begin() + 12, np_comm_id.end(), 0); // fill with 0, we need 16 bytes.
}
static void removePadding(std::vector<u8>& vec) {
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
if (*it == '>') {
size_t pos = std::distance(vec.begin(), it.base());
vec.resize(pos);
break;
}
}
}
bool TRP::Extract(std::filesystem::path trophyPath) {
std::string title = trophyPath.filename().string();
std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/";
if (!std::filesystem::exists(gameSysDir)) {
return false;
}
for (int index = 0; const auto& it : std::filesystem::directory_iterator(gameSysDir)) {
if (it.is_regular_file()) {
GetNPcommID(trophyPath, index);
Common::FS::IOFile file(it.path(), Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
return false;
}
TrpHeader header;
file.Read(header);
if (header.magic != 0xDCA24D00)
return false;
s64 seekPos = sizeof(TrpHeader);
std::filesystem::path trpFilesPath(std::filesystem::current_path() / "game_data" /
title / "TrophyFiles" / it.path().stem());
std::filesystem::create_directories(trpFilesPath / "Icons");
std::filesystem::create_directory(trpFilesPath / "Xml");
for (int i = 0; i < header.entry_num; i++) {
file.Seek(seekPos);
seekPos += (s64)header.entry_size;
TrpEntry entry;
file.Read(entry);
std::string_view name(entry.entry_name);
if (entry.flag == 0 && name.find("TROP") != std::string::npos) { // PNG
file.Seek(entry.entry_pos);
std::vector<u8> icon(entry.entry_len);
file.Read(icon);
Common::FS::IOFile::WriteBytes(trpFilesPath / "Icons" / name, icon);
}
if (entry.flag == 3 && np_comm_id[0] == 'N' &&
np_comm_id[1] == 'P') { // ESFM, encrypted.
file.Seek(entry.entry_pos);
file.Read(esfmIv); // get iv key.
// Skip the first 16 bytes which are the iv key on every entry as we want a
// clean xml file.
std::vector<u8> ESFM(entry.entry_len - iv_len);
std::vector<u8> XML(entry.entry_len - iv_len);
file.Seek(entry.entry_pos + iv_len);
file.Read(ESFM);
crypto.decryptEFSM(np_comm_id, esfmIv, ESFM, XML); // decrypt
removePadding(XML);
std::string xml_name = entry.entry_name;
size_t pos = xml_name.find("ESFM");
if (pos != std::string::npos)
xml_name.replace(pos, xml_name.length(), "XML");
Common::FS::IOFile::WriteBytes(trpFilesPath / "Xml" / xml_name, XML);
}
}
}
index++;
}
return true;
}

View file

@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <vector>
#include "common/endian.h"
#include "common/io_file.h"
#include "common/types.h"
#include "core/crypto/crypto.h"
struct TrpHeader {
u32_be magic; // (0xDCA24D00)
u32_be version;
u64_be file_size; // size of full trp file
u32_be entry_num; // num entries
u32_be entry_size; // size of entry
u32_be dev_flag; // 1: dev
unsigned char digest[20]; // sha1 hash
u32_be key_index; // 3031300?
unsigned char padding[44];
};
struct TrpEntry {
char entry_name[32];
u64_be entry_pos;
u64_be entry_len;
u32_be flag; // 3 = CONFIG/ESFM , 0 = PNG
unsigned char padding[12];
};
class TRP {
public:
TRP();
~TRP();
bool Extract(std::filesystem::path trophyPath);
void GetNPcommID(std::filesystem::path trophyPath, int index);
private:
Crypto crypto;
std::vector<u8> NPcommID = std::vector<u8>(12);
std::array<u8, 16> np_comm_id{};
std::array<u8, 16> esfmIv{};
std::filesystem::path trpFilesPath;
static constexpr int iv_len = 16;
};