Introducing key_manager for storing encryption keys . (#3935)

* Introducing key_manager for storing encryption keys . Currently only trophy key is neccesary

* keep gcc happy?

* addded logging to keymanager

* revert file

* added npbind file format and rewrote part of trp file format
This commit is contained in:
georgemoralis
2026-01-19 18:49:57 +02:00
committed by GitHub
parent 950d390daf
commit c898071b72
10 changed files with 675 additions and 78 deletions

View File

@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
# SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
# Version 3.24 needed for FetchContent OVERRIDE_FIND_PACKAGE
@@ -744,6 +744,8 @@ set(COMMON src/common/logging/backend.cpp
src/common/memory_patcher.cpp
${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp
src/common/scm_rev.h
src/common/key_manager.cpp
src/common/key_manager.h
)
if (ENABLE_DISCORD_RPC)
@@ -787,6 +789,8 @@ set(CORE src/core/aerolib/stubs.cpp
src/core/file_format/playgo_chunk.h
src/core/file_format/trp.cpp
src/core/file_format/trp.h
src/core/file_format/npbind.cpp
src/core/file_format/npbind.h
src/core/file_sys/fs.cpp
src/core/file_sys/fs.h
src/core/ipc/ipc.cpp

161
src/common/key_manager.cpp Normal file
View File

@@ -0,0 +1,161 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fstream>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include "common/logging/log.h"
#include "key_manager.h"
#include "path_util.h"
std::shared_ptr<KeyManager> KeyManager::s_instance = nullptr;
std::mutex KeyManager::s_mutex;
// ------------------- Constructor & Singleton -------------------
KeyManager::KeyManager() {
SetDefaultKeys();
}
KeyManager::~KeyManager() {
SaveToFile();
}
std::shared_ptr<KeyManager> KeyManager::GetInstance() {
std::lock_guard<std::mutex> lock(s_mutex);
if (!s_instance)
s_instance = std::make_shared<KeyManager>();
return s_instance;
}
void KeyManager::SetInstance(std::shared_ptr<KeyManager> instance) {
std::lock_guard<std::mutex> lock(s_mutex);
s_instance = instance;
}
// ------------------- Load / Save -------------------
bool KeyManager::LoadFromFile() {
try {
const auto userDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
const auto keysPath = userDir / "keys.json";
if (!std::filesystem::exists(keysPath)) {
SetDefaultKeys();
SaveToFile();
LOG_DEBUG(KeyManager, "Created default key file: {}", keysPath.string());
return true;
}
std::ifstream file(keysPath);
if (!file.is_open()) {
LOG_ERROR(KeyManager, "Could not open key file: {}", keysPath.string());
return false;
}
json j;
file >> j;
SetDefaultKeys(); // start from defaults
if (j.contains("TrophyKeySet"))
j.at("TrophyKeySet").get_to(m_keys.TrophyKeySet);
LOG_DEBUG(KeyManager, "Successfully loaded keys from: {}", keysPath.string());
return true;
} catch (const std::exception& e) {
LOG_ERROR(KeyManager, "Error loading keys, using defaults: {}", e.what());
SetDefaultKeys();
return false;
}
}
bool KeyManager::SaveToFile() {
try {
const auto userDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
const auto keysPath = userDir / "keys.json";
json j;
KeysToJson(j);
std::ofstream file(keysPath);
if (!file.is_open()) {
LOG_ERROR(KeyManager, "Could not open key file for writing: {}", keysPath.string());
return false;
}
file << std::setw(4) << j;
file.flush();
if (file.fail()) {
LOG_ERROR(KeyManager, "Failed to write keys to: {}", keysPath.string());
return false;
}
LOG_DEBUG(KeyManager, "Successfully saved keys to: {}", keysPath.string());
return true;
} catch (const std::exception& e) {
LOG_ERROR(KeyManager, "Error saving keys: {}", e.what());
return false;
}
}
// ------------------- JSON conversion -------------------
void KeyManager::KeysToJson(json& j) const {
j = m_keys;
}
void KeyManager::JsonToKeys(const json& j) {
json current = m_keys; // serialize current defaults
current.update(j); // merge only fields present in file
m_keys = current.get<AllKeys>(); // deserialize back
}
// ------------------- Defaults / Checks -------------------
void KeyManager::SetDefaultKeys() {
m_keys = AllKeys{};
}
bool KeyManager::HasKeys() const {
return !m_keys.TrophyKeySet.ReleaseTrophyKey.empty();
}
// ------------------- Hex conversion -------------------
std::vector<u8> KeyManager::HexStringToBytes(const std::string& hexStr) {
std::vector<u8> bytes;
if (hexStr.empty())
return bytes;
if (hexStr.size() % 2 != 0)
throw std::runtime_error("Invalid hex string length");
bytes.reserve(hexStr.size() / 2);
auto hexCharToInt = [](char c) -> u8 {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
throw std::runtime_error("Invalid hex character");
};
for (size_t i = 0; i < hexStr.size(); i += 2) {
u8 high = hexCharToInt(hexStr[i]);
u8 low = hexCharToInt(hexStr[i + 1]);
bytes.push_back((high << 4) | low);
}
return bytes;
}
std::string KeyManager::BytesToHexString(const std::vector<u8>& bytes) {
static const char hexDigits[] = "0123456789ABCDEF";
std::string hexStr;
hexStr.reserve(bytes.size() * 2);
for (u8 b : bytes) {
hexStr.push_back(hexDigits[(b >> 4) & 0xF]);
hexStr.push_back(hexDigits[b & 0xF]);
}
return hexStr;
}

79
src/common/key_manager.h Normal file
View File

@@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstdint>
#include <filesystem>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "common/types.h"
#include "nlohmann/json.hpp"
using json = nlohmann::json;
class KeyManager {
public:
// ------------------- Nested keysets -------------------
struct TrophyKeySet {
std::vector<u8> ReleaseTrophyKey;
};
struct AllKeys {
KeyManager::TrophyKeySet TrophyKeySet;
};
// ------------------- Construction -------------------
KeyManager();
~KeyManager();
// ------------------- Singleton -------------------
static std::shared_ptr<KeyManager> GetInstance();
static void SetInstance(std::shared_ptr<KeyManager> instance);
// ------------------- File operations -------------------
bool LoadFromFile();
bool SaveToFile();
// ------------------- Key operations -------------------
void SetDefaultKeys();
bool HasKeys() const;
// ------------------- Getters / Setters -------------------
const AllKeys& GetAllKeys() const {
return m_keys;
}
void SetAllKeys(const AllKeys& keys) {
m_keys = keys;
}
static std::vector<u8> HexStringToBytes(const std::string& hexStr);
static std::string BytesToHexString(const std::vector<u8>& bytes);
private:
void KeysToJson(json& j) const;
void JsonToKeys(const json& j);
AllKeys m_keys{};
static std::shared_ptr<KeyManager> s_instance;
static std::mutex s_mutex;
};
// ------------------- NLOHMANN macros -------------------
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(KeyManager::TrophyKeySet, ReleaseTrophyKey)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(KeyManager::AllKeys, TrophyKeySet)
namespace nlohmann {
template <>
struct adl_serializer<std::vector<u8>> {
static void to_json(json& j, const std::vector<u8>& vec) {
j = KeyManager::BytesToHexString(vec);
}
static void from_json(const json& j, std::vector<u8>& vec) {
vec = KeyManager::HexStringToBytes(j.get<std::string>());
}
};
} // namespace nlohmann

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
@@ -159,6 +160,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
CLS(ImGui) \
CLS(Input) \
CLS(Tty) \
CLS(KeyManager) \
CLS(Loader)
// GetClassName is a macro defined by Windows.h, grrr...

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2023 Citra Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -127,6 +128,7 @@ enum class Class : u8 {
Loader, ///< ROM loader
Input, ///< Input emulation
Tty, ///< Debug output from emu
KeyManager, ///< Key management system
Count ///< Total number of logging classes
};

View File

@@ -0,0 +1,114 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <vector>
#include "npbind.h"
bool NPBindFile::Load(const std::string& path) {
Clear(); // Clear any existing data
std::ifstream f(path, std::ios::binary | std::ios::ate);
if (!f)
return false;
std::streamsize sz = f.tellg();
if (sz <= 0)
return false;
f.seekg(0, std::ios::beg);
std::vector<u8> buf(static_cast<size_t>(sz));
if (!f.read(reinterpret_cast<char*>(buf.data()), sz))
return false;
const u64 size = buf.size();
if (size < sizeof(NpBindHeader))
return false;
// Read header
memcpy(&m_header, buf.data(), sizeof(NpBindHeader));
if (m_header.magic != NPBIND_MAGIC)
return false;
// offset start of bodies
size_t offset = sizeof(NpBindHeader);
m_bodies.reserve(static_cast<size_t>(m_header.num_entries));
// For each body: read 4 TLV entries then skip padding (0x98 = 152 bytes)
const u64 body_padding = 0x98; // 152
for (u64 bi = 0; bi < m_header.num_entries; ++bi) {
// Ensure we have room for 4 entries' headers at least
if (offset + 4 * 4 > size)
return false; // 4 entries x (type+size)
NPBindBody body;
// helper lambda to read one entry
auto read_entry = [&](NPBindEntryRaw& e) -> bool {
if (offset + 4 > size)
return false;
memcpy(&e.type, &buf[offset], 2);
memcpy(&e.size, &buf[offset + 2], 2);
offset += 4;
if (offset + e.size > size)
return false;
e.data.assign(buf.begin() + offset, buf.begin() + offset + e.size);
offset += e.size;
return true;
};
// read 4 entries in order
if (!read_entry(body.npcommid))
return false;
if (!read_entry(body.trophy))
return false;
if (!read_entry(body.unk1))
return false;
if (!read_entry(body.unk2))
return false;
// skip fixed padding after body if present (but don't overrun)
if (offset + body_padding <= size) {
offset += body_padding;
} else {
// If padding not fully present, allow file to end (some variants may omit)
offset = size;
}
m_bodies.push_back(std::move(body));
}
// Read digest if available
if (size >= 20) {
// Digest is typically the last 20 bytes, independent of offset
memcpy(m_digest, &buf[size - 20], 20);
} else {
memset(m_digest, 0, 20);
}
return true;
}
std::vector<std::string> NPBindFile::GetNpCommIds() const {
std::vector<std::string> npcommids;
npcommids.reserve(m_bodies.size());
for (const auto& body : m_bodies) {
// Convert binary data to string directly
if (!body.npcommid.data.empty()) {
std::string raw_string(reinterpret_cast<const char*>(body.npcommid.data.data()),
body.npcommid.data.size());
npcommids.push_back(raw_string);
}
}
return npcommids;
}

View File

@@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include "common/endian.h"
#include "common/types.h"
#define NPBIND_MAGIC 0xD294A018u
#pragma pack(push, 1)
struct NpBindHeader {
u32_be magic;
u32_be version;
u64_be file_size;
u64_be entry_size;
u64_be num_entries;
char padding[0x60]; // 96 bytes
};
#pragma pack(pop)
struct NPBindEntryRaw {
u16_be type;
u16_be size; // includes internal padding
std::vector<u8> data;
};
struct NPBindBody {
NPBindEntryRaw npcommid; // expected type 0x0010, size 12
NPBindEntryRaw trophy; // expected type 0x0011, size 12
NPBindEntryRaw unk1; // expected type 0x0012, size 176
NPBindEntryRaw unk2; // expected type 0x0013, size 16
// The 0x98 padding after these entries is skipped while parsing
};
class NPBindFile {
private:
NpBindHeader m_header;
std::vector<NPBindBody> m_bodies;
u8 m_digest[20]; // zeroed if absent
public:
NPBindFile() {
memset(m_digest, 0, sizeof(m_digest));
}
// Load from file
bool Load(const std::string& path);
// Accessors
const NpBindHeader& Header() const {
return m_header;
}
const std::vector<NPBindBody>& Bodies() const {
return m_bodies;
}
const u8* Digest() const {
return m_digest;
}
// Get npcommid data
std::vector<std::string> GetNpCommIds() const;
// Get specific body
const NPBindBody& GetBody(size_t index) const {
return m_bodies.at(index);
}
// Get number of bodies
u64 BodyCount() const {
return m_bodies.size();
}
// Check if file was loaded successfully
bool IsValid() const {
return m_header.magic == NPBIND_MAGIC;
}
// Clear all data
void Clear() {
m_header = NpBindHeader{};
m_bodies.clear();
memset(m_digest, 0, sizeof(m_digest));
}
};

View File

@@ -1,44 +1,31 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/aes.h"
#include "common/config.h"
#include "common/key_manager.h"
#include "common/logging/log.h"
#include "common/path_util.h"
#include "core/file_format/npbind.h"
#include "core/file_format/trp.h"
static void DecryptEFSM(std::span<u8, 16> trophyKey, std::span<u8, 16> NPcommID,
std::span<u8, 16> efsmIv, std::span<u8> ciphertext,
static void DecryptEFSM(std::span<const u8, 16> trophyKey, std::span<const u8, 16> NPcommID,
std::span<const u8, 16> efsmIv, std::span<const u8> ciphertext,
std::span<u8> decrypted) {
// Step 1: Encrypt NPcommID
std::array<u8, 16> trophyIv{};
std::array<u8, 16> trpKey;
// Convert spans to pointers for the aes functions
aes::encrypt_cbc(NPcommID.data(), NPcommID.size(), trophyKey.data(), trophyKey.size(),
trophyIv.data(), trpKey.data(), trpKey.size(), false);
// Step 2: Decrypt EFSM
aes::decrypt_cbc(ciphertext.data(), ciphertext.size(), trpKey.data(), trpKey.size(),
efsmIv.data(), decrypted.data(), decrypted.size(), nullptr);
const_cast<u8*>(efsmIv.data()), decrypted.data(), decrypted.size(), nullptr);
}
TRP::TRP() = default;
TRP::~TRP() = default;
void TRP::GetNPcommID(const 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()) {
LOG_CRITICAL(Common_Filesystem, "Failed to open npbind.dat file");
return;
}
if (!npbindFile.Seek(0x84 + (index * 0x180))) {
LOG_CRITICAL(Common_Filesystem, "Failed to seek to NPbind offset");
return;
}
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 == '>') {
@@ -63,91 +50,232 @@ bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string tit
return false;
}
const auto user_key_str = Config::getTrophyKey();
if (user_key_str.size() != 32) {
const auto& user_key_vec =
KeyManager::GetInstance()->GetAllKeys().TrophyKeySet.ReleaseTrophyKey;
if (user_key_vec.size() != 16) {
LOG_INFO(Common_Filesystem, "Trophy decryption key is not specified");
return false;
}
std::array<u8, 16> user_key{};
hexToBytes(user_key_str.c_str(), user_key.data());
std::copy(user_key_vec.begin(), user_key_vec.end(), user_key.begin());
for (int index = 0; const auto& it : std::filesystem::directory_iterator(gameSysDir)) {
if (it.is_regular_file()) {
GetNPcommID(trophyPath, index);
// Load npbind.dat using the new class
std::filesystem::path npbindPath = trophyPath / "sce_sys/npbind.dat";
NPBindFile npbind;
if (!npbind.Load(npbindPath.string())) {
LOG_WARNING(Common_Filesystem, "Failed to load npbind.dat file");
}
auto npCommIds = npbind.GetNpCommIds();
if (npCommIds.empty()) {
LOG_WARNING(Common_Filesystem, "No NPComm IDs found in npbind.dat");
}
bool success = true;
int trpFileIndex = 0;
try {
// Process each TRP file in the trophy directory
for (const auto& it : std::filesystem::directory_iterator(gameSysDir)) {
if (!it.is_regular_file() || it.path().extension() != ".trp") {
continue; // Skip non-TRP files
}
// Get NPCommID for this TRP file (if available)
std::string npCommId;
if (trpFileIndex < static_cast<int>(npCommIds.size())) {
npCommId = npCommIds[trpFileIndex];
LOG_DEBUG(Common_Filesystem, "Using NPCommID: {} for {}", npCommId,
it.path().filename().string());
} else {
LOG_WARNING(Common_Filesystem, "No NPCommID found for TRP file index {}",
trpFileIndex);
}
Common::FS::IOFile file(it.path(), Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
LOG_CRITICAL(Common_Filesystem, "Unable to open trophy file for read");
return false;
LOG_ERROR(Common_Filesystem, "Unable to open trophy file: {}", it.path().string());
success = false;
continue;
}
TrpHeader header;
file.Read(header);
if (header.magic != 0xDCA24D00) {
LOG_CRITICAL(Common_Filesystem, "Wrong trophy magic number");
return false;
if (!file.Read(header)) {
LOG_ERROR(Common_Filesystem, "Failed to read TRP header from {}",
it.path().string());
success = false;
continue;
}
if (header.magic != TRP_MAGIC) {
LOG_ERROR(Common_Filesystem, "Wrong trophy magic number in {}", it.path().string());
success = false;
continue;
}
s64 seekPos = sizeof(TrpHeader);
std::filesystem::path trpFilesPath(
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / titleId /
"TrophyFiles" / it.path().stem());
std::filesystem::create_directories(trpFilesPath / "Icons");
std::filesystem::create_directory(trpFilesPath / "Xml");
// Create output directories
if (!std::filesystem::create_directories(trpFilesPath / "Icons") ||
!std::filesystem::create_directories(trpFilesPath / "Xml")) {
LOG_ERROR(Common_Filesystem, "Failed to create output directories for {}", titleId);
success = false;
continue;
}
// Process each entry in the TRP file
for (int i = 0; i < header.entry_num; i++) {
if (!file.Seek(seekPos)) {
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
return false;
LOG_ERROR(Common_Filesystem, "Failed to seek to TRP entry offset");
success = false;
break;
}
seekPos += (s64)header.entry_size;
seekPos += static_cast<s64>(header.entry_size);
TrpEntry entry;
file.Read(entry);
std::string_view name(entry.entry_name);
if (entry.flag == 0) { // PNG
if (!file.Seek(entry.entry_pos)) {
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
return false;
}
std::vector<u8> icon(entry.entry_len);
file.Read(icon);
Common::FS::IOFile::WriteBytes(trpFilesPath / "Icons" / name, icon);
if (!file.Read(entry)) {
LOG_ERROR(Common_Filesystem, "Failed to read TRP entry");
success = false;
break;
}
if (entry.flag == 3 && np_comm_id[0] == 'N' &&
np_comm_id[1] == 'P') { // ESFM, encrypted.
if (!file.Seek(entry.entry_pos)) {
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
return false;
std::string_view name(entry.entry_name);
if (entry.flag == ENTRY_FLAG_PNG) {
if (!ProcessPngEntry(file, entry, trpFilesPath, name)) {
success = false;
// Continue with next entry
}
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);
if (!file.Seek(entry.entry_pos + iv_len)) {
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry + iv offset");
return false;
}
file.Read(ESFM);
DecryptEFSM(user_key, 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");
std::filesystem::path path = trpFilesPath / "Xml" / xml_name;
size_t written = Common::FS::IOFile::WriteBytes(path, XML);
if (written != XML.size()) {
LOG_CRITICAL(
Common_Filesystem,
"Trophy XML {} write failed, wanted to write {} bytes, wrote {}",
fmt::UTF(path.u8string()), XML.size(), written);
} else if (entry.flag == ENTRY_FLAG_ENCRYPTED_XML) {
// Check if we have a valid NPCommID for decryption
if (npCommId.size() >= 12 && npCommId[0] == 'N' && npCommId[1] == 'P') {
if (!ProcessEncryptedXmlEntry(file, entry, trpFilesPath, name, user_key,
npCommId)) {
success = false;
// Continue with next entry
}
} else {
LOG_WARNING(Common_Filesystem,
"Skipping encrypted XML entry - invalid NPCommID");
// Skip this entry but continue
}
} else {
LOG_DEBUG(Common_Filesystem, "Unknown entry flag: {} for {}",
static_cast<unsigned int>(entry.flag), name);
}
}
trpFileIndex++;
}
index++;
} catch (const std::filesystem::filesystem_error& e) {
LOG_CRITICAL(Common_Filesystem, "Filesystem error during trophy extraction: {}", e.what());
return false;
} catch (const std::exception& e) {
LOG_CRITICAL(Common_Filesystem, "Error during trophy extraction: {}", e.what());
return false;
}
if (success) {
LOG_INFO(Common_Filesystem, "Successfully extracted {} trophy files for {}", trpFileIndex,
titleId);
}
return success;
}
bool TRP::ProcessPngEntry(Common::FS::IOFile& file, const TrpEntry& entry,
const std::filesystem::path& outputPath, std::string_view name) {
if (!file.Seek(entry.entry_pos)) {
LOG_ERROR(Common_Filesystem, "Failed to seek to PNG entry offset");
return false;
}
std::vector<u8> icon(entry.entry_len);
if (!file.Read(icon)) {
LOG_ERROR(Common_Filesystem, "Failed to read PNG data");
return false;
}
auto outputFile = outputPath / "Icons" / name;
size_t written = Common::FS::IOFile::WriteBytes(outputFile, icon);
if (written != icon.size()) {
LOG_ERROR(Common_Filesystem, "PNG write failed: wanted {} bytes, wrote {}", icon.size(),
written);
return false;
}
return true;
}
bool TRP::ProcessEncryptedXmlEntry(Common::FS::IOFile& file, const TrpEntry& entry,
const std::filesystem::path& outputPath, std::string_view name,
const std::array<u8, 16>& user_key,
const std::string& npCommId) {
constexpr size_t IV_LEN = 16;
if (!file.Seek(entry.entry_pos)) {
LOG_ERROR(Common_Filesystem, "Failed to seek to encrypted XML entry offset");
return false;
}
std::array<u8, IV_LEN> esfmIv;
if (!file.Read(esfmIv)) {
LOG_ERROR(Common_Filesystem, "Failed to read IV for encrypted XML");
return false;
}
if (entry.entry_len <= IV_LEN) {
LOG_ERROR(Common_Filesystem, "Encrypted XML entry too small");
return false;
}
// Skip to the encrypted data (after IV)
if (!file.Seek(entry.entry_pos + IV_LEN)) {
LOG_ERROR(Common_Filesystem, "Failed to seek to encrypted data");
return false;
}
std::vector<u8> ESFM(entry.entry_len - IV_LEN);
std::vector<u8> XML(entry.entry_len - IV_LEN);
if (!file.Read(ESFM)) {
LOG_ERROR(Common_Filesystem, "Failed to read encrypted XML data");
return false;
}
// Decrypt the data - FIX: Don't check return value since DecryptEFSM returns void
std::span<const u8, 16> key_span(user_key);
// Convert npCommId string to span (pad or truncate to 16 bytes)
std::array<u8, 16> npcommid_array{};
size_t copy_len = std::min(npCommId.size(), npcommid_array.size());
std::memcpy(npcommid_array.data(), npCommId.data(), copy_len);
std::span<const u8, 16> npcommid_span(npcommid_array);
DecryptEFSM(key_span, npcommid_span, esfmIv, ESFM, XML);
// Remove padding
removePadding(XML);
// Create output filename (replace ESFM with XML)
std::string xml_name(entry.entry_name);
size_t pos = xml_name.find("ESFM");
if (pos != std::string::npos) {
xml_name.replace(pos, 4, "XML");
}
auto outputFile = outputPath / "Xml" / xml_name;
size_t written = Common::FS::IOFile::WriteBytes(outputFile, XML);
if (written != XML.size()) {
LOG_ERROR(Common_Filesystem, "XML write failed: wanted {} bytes, wrote {}", XML.size(),
written);
return false;
}
return true;
}

View File

@@ -8,6 +8,10 @@
#include "common/io_file.h"
#include "common/types.h"
static constexpr u32 TRP_MAGIC = 0xDCA24D00;
static constexpr u8 ENTRY_FLAG_PNG = 0;
static constexpr u8 ENTRY_FLAG_ENCRYPTED_XML = 3;
struct TrpHeader {
u32_be magic; // (0xDCA24D00)
u32_be version;
@@ -33,9 +37,14 @@ public:
TRP();
~TRP();
bool Extract(const std::filesystem::path& trophyPath, const std::string titleId);
void GetNPcommID(const std::filesystem::path& trophyPath, int index);
private:
bool ProcessPngEntry(Common::FS::IOFile& file, const TrpEntry& entry,
const std::filesystem::path& outputPath, std::string_view name);
bool ProcessEncryptedXmlEntry(Common::FS::IOFile& file, const TrpEntry& entry,
const std::filesystem::path& outputPath, std::string_view name,
const std::array<u8, 16>& user_key, const std::string& npCommId);
std::vector<u8> NPcommID = std::vector<u8>(12);
std::array<u8, 16> np_comm_id{};
std::array<u8, 16> esfmIv{};

View File

@@ -22,6 +22,7 @@
#ifdef _WIN32
#include <windows.h>
#endif
#include <common/key_manager.h>
int main(int argc, char* argv[]) {
#ifdef _WIN32
@@ -34,7 +35,17 @@ int main(int argc, char* argv[]) {
// Load configurations
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::load(user_dir / "config.toml");
// temp copy the trophy key from old config to key manager if exists
auto key_manager = KeyManager::GetInstance();
if (key_manager->GetAllKeys().TrophyKeySet.ReleaseTrophyKey.empty()) {
if (!Config::getTrophyKey().empty()) {
key_manager->SetAllKeys(
{.TrophyKeySet = {.ReleaseTrophyKey =
KeyManager::HexStringToBytes(Config::getTrophyKey())}});
key_manager->SaveToFile();
}
}
bool has_game_argument = false;
std::string game_path;
std::vector<std::string> game_args{};