mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-05-22 11:25:02 +00:00
- 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:
parent
71dda8c776
commit
0f27e0edf2
49 changed files with 1616 additions and 891 deletions
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
91
src/core/file_format/trp.cpp
Normal file
91
src/core/file_format/trp.cpp
Normal 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;
|
||||
}
|
46
src/core/file_format/trp.h
Normal file
46
src/core/file_format/trp.h
Normal 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;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue