Trophies fixes (#4114)

* added extraction to users based on npcommid

* npcommids are stored in elf_info now

Made nptrophy to work correcty with npcommids

* fixed loading of keymanager

* make linux happy

* partial trophy rewrite with lanugages support

* fixed troph file naming
This commit is contained in:
georgemoralis 2026-03-11 23:23:35 +02:00 committed by GitHub
parent d06a3d4777
commit ff96c54451
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 568 additions and 355 deletions

View File

@ -6,6 +6,7 @@
#include <filesystem>
#include <string>
#include <string_view>
#include <vector>
#include "assert.h"
#include "bit_field.h"
@ -73,6 +74,7 @@ class ElfInfo {
std::filesystem::path splash_path{};
std::filesystem::path game_folder{};
std::vector<std::string> npCommIds{};
public:
static constexpr u32 FW_10 = 0x1000000;
@ -139,6 +141,10 @@ public:
[[nodiscard]] const std::filesystem::path& GetGameFolder() const {
return game_folder;
}
[[nodiscard]] const std::vector<std::string> GetNpCommIds() const {
return npCommIds;
}
};
} // namespace Common

View File

@ -5,7 +5,6 @@
#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<const u8, 16> trophyKey, std::span<const u8, 16> NPcommID,
@ -43,8 +42,10 @@ static void hexToBytes(const char* hex, unsigned char* dst) {
}
}
bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string titleId) {
std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/";
bool TRP::Extract(const std::filesystem::path& trophyPath, int index, std::string npCommId,
const std::filesystem::path& outputPath) {
std::filesystem::path gameSysDir =
trophyPath / "sce_sys/trophy/" / std::format("trophy{:02d}.trp", index);
if (!std::filesystem::exists(gameSysDir)) {
LOG_WARNING(Common_Filesystem, "Game trophy directory doesn't exist");
return false;
@ -61,117 +62,82 @@ bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string tit
std::array<u8, 16> user_key{};
std::copy(user_key_vec.begin(), user_key_vec.end(), user_key.begin());
// 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
}
const auto& it = gameSysDir;
if (it.extension() != ".trp") {
return false;
}
Common::FS::IOFile file(it, Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
LOG_ERROR(Common_Filesystem, "Unable to open trophy file: {}", it.string());
return false;
}
// 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);
}
TrpHeader header;
if (!file.Read(header)) {
LOG_ERROR(Common_Filesystem, "Failed to read TRP header from {}", it.string());
return false;
}
Common::FS::IOFile file(it.path(), Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
LOG_ERROR(Common_Filesystem, "Unable to open trophy file: {}", it.path().string());
if (header.magic != TRP_MAGIC) {
LOG_ERROR(Common_Filesystem, "Wrong trophy magic number in {}", it.string());
return false;
}
s64 seekPos = sizeof(TrpHeader);
// Create output directories
if (!std::filesystem::create_directories(outputPath / "Icons") ||
!std::filesystem::create_directories(outputPath / "Xml")) {
LOG_ERROR(Common_Filesystem, "Failed to create output directories for {}", npCommId);
return false;
}
// Process each entry in the TRP file
for (int i = 0; i < header.entry_num; i++) {
if (!file.Seek(seekPos)) {
LOG_ERROR(Common_Filesystem, "Failed to seek to TRP entry offset");
success = false;
continue;
break;
}
seekPos += static_cast<s64>(header.entry_size);
TrpHeader header;
if (!file.Read(header)) {
LOG_ERROR(Common_Filesystem, "Failed to read TRP header from {}",
it.path().string());
TrpEntry entry;
if (!file.Read(entry)) {
LOG_ERROR(Common_Filesystem, "Failed to read TRP entry");
success = false;
continue;
break;
}
if (header.magic != TRP_MAGIC) {
LOG_ERROR(Common_Filesystem, "Wrong trophy magic number in {}", it.path().string());
success = false;
continue;
}
std::string_view name(entry.entry_name);
s64 seekPos = sizeof(TrpHeader);
std::filesystem::path trpFilesPath(
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / titleId /
"TrophyFiles" / it.path().stem());
// 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_ERROR(Common_Filesystem, "Failed to seek to TRP entry offset");
if (entry.flag == ENTRY_FLAG_PNG) {
if (!ProcessPngEntry(file, entry, outputPath, name)) {
success = false;
break;
// Continue with next entry
}
seekPos += static_cast<s64>(header.entry_size);
TrpEntry entry;
if (!file.Read(entry)) {
LOG_ERROR(Common_Filesystem, "Failed to read TRP entry");
success = false;
break;
}
std::string_view name(entry.entry_name);
if (entry.flag == ENTRY_FLAG_PNG) {
if (!ProcessPngEntry(file, entry, trpFilesPath, name)) {
} 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, outputPath, name, user_key,
npCommId)) {
success = false;
// Continue with next entry
}
} 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);
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++;
}
} catch (const std::filesystem::filesystem_error& e) {
LOG_CRITICAL(Common_Filesystem, "Filesystem error during trophy extraction: {}", e.what());
return false;
@ -182,7 +148,7 @@ bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string tit
if (success) {
LOG_INFO(Common_Filesystem, "Successfully extracted {} trophy files for {}", trpFileIndex,
titleId);
npCommId);
}
return success;

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
#pragma once
@ -36,7 +36,8 @@ class TRP {
public:
TRP();
~TRP();
bool Extract(const std::filesystem::path& trophyPath, const std::string titleId);
bool Extract(const std::filesystem::path& trophyPath, int index, std::string npCommId,
const std::filesystem::path& outputPath);
private:
bool ProcessPngEntry(Common::FS::IOFile& file, const TrpEntry& entry,
@ -45,9 +46,6 @@ private:
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{};
std::filesystem::path trpFilesPath;
static constexpr int iv_len = 16;
};

View File

@ -1,22 +1,139 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <unordered_map>
#include <pugixml.hpp>
#include "common/elf_info.h"
#include "common/logging/log.h"
#include "common/path_util.h"
#include "common/slot_vector.h"
#include "core/emulator_settings.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_trophy.h"
#include "core/libraries/np/np_trophy_error.h"
#include "core/libraries/np/trophy_ui.h"
#include "core/libraries/system/userservice.h"
#include "core/memory.h"
namespace Libraries::Np::NpTrophy {
std::string game_serial;
// PS4 system language IDs map directly to TROP00.XML .. TROP30.XML.
// Index = OrbisSystemServiceParamId language value reported by the system.
// clang-format off
static constexpr std::array<std::string_view, 31> s_language_xml_names = {
"TROP_00.XML", // 00 Japanese
"TROP_01.XML", // 01 English (US)
"TROP_02.XML", // 02 French
"TROP_03.XML", // 03 Spanish (ES)
"TROP_04.XML", // 04 German
"TROP_05.XML", // 05 Italian
"TROP_06.XML", // 06 Dutch
"TROP_07.XML", // 07 Portuguese (PT)
"TROP_08.XML", // 08 Russian
"TROP_09.XML", // 09 Korean
"TROP_10.XML", // 10 Traditional Chinese
"TROP_11.XML", // 11 Simplified Chinese
"TROP_12.XML", // 12 Finnish
"TROP_13.XML", // 13 Swedish
"TROP_14.XML", // 14 Danish
"TROP_15.XML", // 15 Norwegian
"TROP_16.XML", // 16 Polish
"TROP_17.XML", // 17 Portuguese (BR)
"TROP_18.XML", // 18 English (GB)
"TROP_19.XML", // 19 Turkish
"TROP_20.XML", // 20 Spanish (LA)
"TROP_21.XML", // 21 Arabic
"TROP_22.XML", // 22 French (CA)
"TROP_23.XML", // 23 Czech
"TROP_24.XML", // 24 Hungarian
"TROP_25.XML", // 25 Greek
"TROP_26.XML", // 26 Romanian
"TROP_27.XML", // 27 Thai
"TROP_28.XML", // 28 Vietnamese
"TROP_29.XML", // 29 Indonesian
"TROP_30.XML", // 30 Unkrainian
};
// clang-format on
// Returns the best available trophy XML path for the current system language.
// Resolution order:
// 1. TROP_XX.XML for the active system language (e.g. TROP01.XML for English)
// 2. TROP.XML (master / language-neutral fallback)
static std::filesystem::path GetTrophyXmlPath(const std::filesystem::path& xml_dir,
int system_language) {
// Try the exact language file first.
if (system_language >= 0 && system_language < static_cast<int>(s_language_xml_names.size())) {
auto lang_path = xml_dir / s_language_xml_names[system_language];
if (std::filesystem::exists(lang_path)) {
return lang_path;
}
}
// Final fallback: master TROP.XML (always present).
return xml_dir / "TROP.XML";
}
static std::vector<std::filesystem::path> GetAllTrophyXmlPaths(
const std::filesystem::path& xml_dir) {
std::vector<std::filesystem::path> paths;
// Always include the master file.
auto master = xml_dir / "TROP.XML";
if (std::filesystem::exists(master)) {
paths.push_back(master);
}
// Include every language file that is actually present.
for (const auto& name : s_language_xml_names) {
auto lang_path = xml_dir / name;
if (std::filesystem::exists(lang_path)) {
paths.push_back(lang_path);
}
}
return paths;
}
static void ApplyUnlockToXmlFile(const std::filesystem::path& xml_path, OrbisNpTrophyId trophyId,
u64 trophyTimestamp, bool unlock_platinum,
OrbisNpTrophyId platinumId, u64 platinumTimestamp) {
pugi::xml_document doc;
if (!doc.load_file(xml_path.native().c_str())) {
LOG_WARNING(Lib_NpTrophy, "ApplyUnlock: failed to load {}", xml_path.string());
return;
}
auto trophyconf = doc.child("trophyconf");
for (pugi::xml_node& node : trophyconf.children()) {
if (std::string_view(node.name()) != "trophy") {
continue;
}
int id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
auto set_unlock = [&](u64 ts) {
if (node.attribute("unlockstate").empty()) {
node.append_attribute("unlockstate") = "true";
} else {
node.attribute("unlockstate").set_value("true");
}
const auto ts_str = std::to_string(ts);
if (node.attribute("timestamp").empty()) {
node.append_attribute("timestamp") = ts_str.c_str();
} else {
node.attribute("timestamp").set_value(ts_str.c_str());
}
};
if (id == trophyId) {
set_unlock(trophyTimestamp);
} else if (unlock_platinum && id == platinumId) {
set_unlock(platinumTimestamp);
}
}
doc.save_file(xml_path.native().c_str());
}
static constexpr auto MaxTrophyHandles = 4u;
static constexpr auto MaxTrophyContexts = 8u;
@ -30,6 +147,10 @@ struct ContextKeyHash {
struct TrophyContext {
u32 context_id;
bool registered = false;
std::filesystem::path trophy_xml_path; // resolved once at CreateContext
std::filesystem::path xml_dir; // .../Xml/
std::filesystem::path icons_dir; // .../Icons/
};
static Common::SlotVector<OrbisNpTrophyHandle> trophy_handles{};
static Common::SlotVector<ContextKey> trophy_contexts{};
@ -94,66 +215,10 @@ OrbisNpTrophyGrade GetTrophyGradeFromChar(char trophyType) {
}
}
int PS4_SYSV_ABI sceNpTrophyAbortHandle(OrbisNpTrophyHandle handle) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyCaptureScreenshot() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyDetails() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyFlagArray() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyGroupArray() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyGroupDetails() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfo() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfoInGroup() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetVersion() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyTitleDetails() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context,
Libraries::UserService::OrbisUserServiceUserId user_id,
uint32_t service_label, u64 options) {
ASSERT(options == 0ull);
if (!context) {
if (!context || options != 0ull) {
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
@ -169,7 +234,18 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context,
const auto ctx_id = trophy_contexts.insert(user_id, service_label);
*context = ctx_id.index + 1;
contexts_internal[key].context_id = *context;
auto& ctx = contexts_internal[key];
ctx.context_id = *context;
// Resolve and cache all paths once so callers never recompute them.
const std::string trophy_folder = Common::ElfInfo::Instance().GetNpCommIds()[service_label];
const auto trophy_base = Common::FS::GetUserPath(Common::FS::PathType::HomeDir) /
std::to_string(user_id) / "trophy" / trophy_folder;
ctx.xml_dir = trophy_base / "Xml";
ctx.icons_dir = trophy_base / "Icons";
ctx.trophy_xml_path = GetTrophyXmlPath(ctx.xml_dir, EmulatorSettings.GetConsoleLanguage());
LOG_INFO(Lib_NpTrophy, "New context = {}, user_id = {} service label = {}", *context, user_id,
service_label);
@ -206,6 +282,10 @@ int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context) {
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
}
if (!trophy_contexts.is_allocated(contextId)) {
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
}
ContextKey contextkey = trophy_contexts[contextId];
trophy_contexts.erase(contextId);
contexts_internal.erase(contextkey);
@ -251,12 +331,10 @@ int PS4_SYSV_ABI sceNpTrophyGetGameIcon(OrbisNpTrophyContext context, OrbisNpTro
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
}
ContextKey contextkey = trophy_contexts[contextId];
char trophy_folder[9];
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
const auto trophy_dir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
auto icon_file = trophy_dir / trophy_folder / "Icons" / "ICON0.PNG";
const auto& ctx = contexts_internal[contextkey];
auto icon_file = ctx.icons_dir / "ICON0.PNG";
Common::FS::IOFile icon(icon_file, Common::FS::FileAccessMode::Read);
if (!icon.IsOpen()) {
@ -304,12 +382,10 @@ int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTro
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
}
ContextKey contextkey = trophy_contexts[contextId];
char trophy_folder[9];
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
const auto trophy_dir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
auto trophy_file = trophy_dir / trophy_folder / "Xml" / "TROP.XML";
const auto& ctx = contexts_internal[contextkey];
if (!ctx.registered)
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
const auto& trophy_file = ctx.trophy_xml_path;
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
@ -368,8 +444,9 @@ int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTro
data->unlocked_silver = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
data->unlocked_bronze = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
// maybe this should be 1 instead of 100?
data->progress_percentage = 100;
data->progress_percentage = (game_info.num_trophies > 0)
? (game_info.unlocked_trophies * 100u) / game_info.num_trophies
: 0;
return ORBIS_OK;
}
@ -411,12 +488,10 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTr
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
}
ContextKey contextkey = trophy_contexts[contextId];
char trophy_folder[9];
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
const auto trophy_dir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
auto trophy_file = trophy_dir / trophy_folder / "Xml" / "TROP.XML";
const auto& ctx = contexts_internal[contextkey];
if (!ctx.registered)
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
const auto& trophy_file = ctx.trophy_xml_path;
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
@ -484,15 +559,84 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTr
data->unlocked_silver = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
data->unlocked_bronze = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
// maybe this should be 1 instead of 100?
data->progress_percentage = 100;
data->progress_percentage =
(group_info.num_trophies > 0)
? (group_info.unlocked_trophies * 100u) / group_info.num_trophies
: 0;
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyId trophyId, void* buffer, u64* size) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
if (size == nullptr)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
if (trophyId < 0 || trophyId >= ORBIS_NP_TROPHY_NUM_MAX)
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
Common::SlotId contextId;
contextId.index = context - 1;
if (contextId.index >= trophy_contexts.size() || !trophy_contexts.is_allocated(contextId)) {
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
}
s32 handle_index = handle - 1;
if (handle_index >= trophy_handles.size() ||
!trophy_handles.is_allocated({static_cast<u32>(handle_index)})) {
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
}
ContextKey contextkey = trophy_contexts[contextId];
const auto& ctx = contexts_internal[contextkey];
if (!ctx.registered)
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
// Check that the trophy is unlocked — icons are only available for earned trophies.
pugi::xml_document doc;
if (!doc.load_file(ctx.trophy_xml_path.native().c_str())) {
LOG_ERROR(Lib_NpTrophy, "Failed to open trophy XML: {}", ctx.trophy_xml_path.string());
return ORBIS_NP_TROPHY_ERROR_ICON_FILE_NOT_FOUND;
}
bool unlocked = false;
bool found = false;
for (const pugi::xml_node& node : doc.child("trophyconf").children()) {
if (std::string_view(node.name()) != "trophy")
continue;
if (node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID) == trophyId) {
found = true;
unlocked = node.attribute("unlockstate").as_bool();
break;
}
}
if (!found)
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
if (!unlocked)
return ORBIS_NP_TROPHY_ERROR_TROPHY_NOT_UNLOCKED;
const std::string icon_name = fmt::format("TROP{:03d}.PNG", trophyId);
const auto icon_path = ctx.icons_dir / icon_name;
Common::FS::IOFile icon(icon_path, Common::FS::FileAccessMode::Read);
if (!icon.IsOpen()) {
LOG_ERROR(Lib_NpTrophy, "Failed to open trophy icon: {}", icon_path.string());
return ORBIS_NP_TROPHY_ERROR_ICON_FILE_NOT_FOUND;
}
if (buffer != nullptr) {
ReadFile(icon, buffer, *size);
} else {
*size = icon.GetSize();
}
return ORBIS_OK;
}
@ -507,7 +651,7 @@ int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpT
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
if (trophyId >= 127)
if (trophyId >= ORBIS_NP_TROPHY_NUM_MAX)
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
if (details == nullptr || data == nullptr)
@ -522,12 +666,10 @@ int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpT
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
}
ContextKey contextkey = trophy_contexts[contextId];
char trophy_folder[9];
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
const auto trophy_dir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
auto trophy_file = trophy_dir / trophy_folder / "Xml" / "TROP.XML";
const auto& ctx = contexts_internal[contextkey];
if (!ctx.registered)
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
const auto& trophy_file = ctx.trophy_xml_path;
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
@ -579,29 +721,34 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
OrbisNpTrophyFlagArray* flags, u32* count) {
LOG_INFO(Lib_NpTrophy, "called");
if (flags == nullptr || count == nullptr)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
if (flags == nullptr || count == nullptr)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
ORBIS_NP_TROPHY_FLAG_ZERO(flags);
Common::SlotId contextId;
contextId.index = context - 1;
if (contextId.index >= trophy_contexts.size()) {
if (contextId.index >= trophy_contexts.size() || !trophy_contexts.is_allocated(contextId)) {
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
}
ContextKey contextkey = trophy_contexts[contextId];
char trophy_folder[9];
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
const auto trophy_dir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
auto trophy_file = trophy_dir / trophy_folder / "Xml" / "TROP.XML";
s32 handle_index = handle - 1;
if (handle_index >= trophy_handles.size() ||
!trophy_handles.is_allocated({static_cast<u32>(handle_index)})) {
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
}
ContextKey contextkey = trophy_contexts[contextId];
const auto& ctx = contexts_internal[contextkey];
if (!ctx.registered)
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
const auto& trophy_file = ctx.trophy_xml_path;
ORBIS_NP_TROPHY_FLAG_ZERO(flags);
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
@ -622,10 +769,9 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
if (node_name == "trophy") {
num_trophies++;
}
if (current_trophy_unlockstate) {
ORBIS_NP_TROPHY_FLAG_SET(current_trophy_id, flags);
if (current_trophy_unlockstate) {
ORBIS_NP_TROPHY_FLAG_SET(current_trophy_id, flags);
}
}
}
@ -633,6 +779,185 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyRegisterContext(OrbisNpTrophyContext context,
OrbisNpTrophyHandle handle, uint64_t options) {
if (options != 0ull)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
Common::SlotId contextId;
contextId.index = context - 1;
if (contextId.index >= trophy_contexts.size() || !trophy_contexts.is_allocated(contextId)) {
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
}
s32 handle_index = handle - 1;
if (handle_index >= trophy_handles.size() ||
!trophy_handles.is_allocated({static_cast<u32>(handle_index)})) {
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
}
ContextKey contextkey = trophy_contexts[contextId];
auto& ctx = contexts_internal[contextkey];
if (ctx.registered)
return ORBIS_NP_TROPHY_ERROR_ALREADY_REGISTERED;
if (!std::filesystem::exists(ctx.trophy_xml_path))
return ORBIS_NP_TROPHY_ERROR_TITLE_CONF_NOT_INSTALLED;
ctx.registered = true;
LOG_INFO(Lib_NpTrophy, "Context {} registered", context);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyId trophyId, OrbisNpTrophyId* platinumId) {
LOG_INFO(Lib_NpTrophy, "Unlocking trophy id {}", trophyId);
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
if (trophyId >= ORBIS_NP_TROPHY_NUM_MAX)
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
if (platinumId == nullptr)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
Common::SlotId contextId;
contextId.index = context - 1;
if (contextId.index >= trophy_contexts.size() || !trophy_contexts.is_allocated(contextId)) {
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
}
s32 handle_index = handle - 1;
if (handle_index >= trophy_handles.size() ||
!trophy_handles.is_allocated({static_cast<u32>(handle_index)})) {
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
}
ContextKey contextkey = trophy_contexts[contextId];
const auto& ctx = contexts_internal[contextkey];
if (!ctx.registered)
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
const auto& xml_dir = ctx.xml_dir;
const auto& trophy_file = ctx.trophy_xml_path;
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
if (!result) {
LOG_ERROR(Lib_NpTrophy, "Failed to parse trophy xml : {}", result.description());
return ORBIS_NP_TROPHY_ERROR_TITLE_NOT_FOUND;
}
*platinumId = ORBIS_NP_TROPHY_INVALID_TROPHY_ID;
int num_trophies = 0;
int num_trophies_unlocked = 0;
pugi::xml_node platinum_node;
// Outputs filled during the scan.
bool trophy_found = false;
const char* trophy_name = "";
std::string_view trophy_type;
std::filesystem::path trophy_icon_path;
auto trophyconf = doc.child("trophyconf");
for (pugi::xml_node& node : trophyconf.children()) {
if (std::string_view(node.name()) != "trophy")
continue;
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
std::string_view current_trophy_type = node.attribute("ttype").value();
if (current_trophy_type == "P") {
platinum_node = node;
if (trophyId == current_trophy_id) {
return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK;
}
}
if (node.attribute("pid").as_int(-1) != ORBIS_NP_TROPHY_INVALID_TROPHY_ID) {
num_trophies++;
if (current_trophy_unlockstate) {
num_trophies_unlocked++;
}
}
if (current_trophy_id == trophyId) {
if (current_trophy_unlockstate) {
LOG_INFO(Lib_NpTrophy, "Trophy already unlocked");
return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED;
}
trophy_found = true;
trophy_name = node.child("name").text().as_string();
trophy_type = current_trophy_type;
const std::string icon_file = fmt::format("TROP{:03d}.PNG", current_trophy_id);
trophy_icon_path = ctx.icons_dir / icon_file;
}
}
if (!trophy_found)
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
// Capture timestamps once so every file gets the exact same value.
const auto now_secs = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
const u64 trophy_timestamp = static_cast<u64>(now_secs);
// Decide platinum.
bool unlock_platinum = false;
OrbisNpTrophyId platinum_id = ORBIS_NP_TROPHY_INVALID_TROPHY_ID;
u64 platinum_timestamp = 0;
const char* platinum_name = "";
std::filesystem::path platinum_icon_path;
if (!platinum_node.attribute("unlockstate").as_bool()) {
if ((num_trophies - 1) == num_trophies_unlocked) {
unlock_platinum = true;
platinum_id = platinum_node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
platinum_timestamp = trophy_timestamp; // same second is fine
platinum_name = platinum_node.child("name").text().as_string();
const std::string plat_icon_file = fmt::format("TROP{:03d}.PNG", platinum_id);
platinum_icon_path = ctx.icons_dir / plat_icon_file;
*platinumId = platinum_id;
}
}
// Queue UI notifications (only once, using the primary XML's strings).
AddTrophyToQueue(trophy_icon_path, trophy_name, trophy_type);
if (unlock_platinum) {
AddTrophyToQueue(platinum_icon_path, platinum_name, "P");
}
// TROP.XML + TROP00.XML .. TROP30.XML are all written so that switching
// the system language never loses trophy progress.
const auto all_xml_paths = GetAllTrophyXmlPaths(xml_dir);
for (const auto& xml_path : all_xml_paths) {
ApplyUnlockToXmlFile(xml_path, trophyId, trophy_timestamp, unlock_platinum, platinum_id,
platinum_timestamp);
}
LOG_INFO(Lib_NpTrophy, "Trophy {} written to {} XML file(s)", trophyId, all_xml_paths.size());
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyGroupArrayGetNum() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
@ -698,19 +1023,6 @@ int PS4_SYSV_ABI sceNpTrophyNumInfoGetTotal() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyRegisterContext(OrbisNpTrophyContext context,
OrbisNpTrophyHandle handle, uint64_t options) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophySetInfoGetTrophyFlagArray() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
@ -942,147 +1254,58 @@ int PS4_SYSV_ABI sceNpTrophySystemSetDbgParamInt() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyId trophyId, OrbisNpTrophyId* platinumId) {
LOG_INFO(Lib_NpTrophy, "Unlocking trophy id {}", trophyId);
int PS4_SYSV_ABI sceNpTrophyAbortHandle(OrbisNpTrophyHandle handle) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
int PS4_SYSV_ABI sceNpTrophyCaptureScreenshot() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyDetails() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
if (trophyId >= 127)
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyFlagArray() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
if (platinumId == nullptr)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyGroupArray() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
Common::SlotId contextId;
contextId.index = context - 1;
if (contextId.index >= trophy_contexts.size()) {
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
}
ContextKey contextkey = trophy_contexts[contextId];
char trophy_folder[9];
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyGroupDetails() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
const auto trophy_dir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
auto trophy_file = trophy_dir / trophy_folder / "Xml" / "TROP.XML";
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfo() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfoInGroup() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
if (!result) {
LOG_ERROR(Lib_NpTrophy, "Failed to parse trophy xml : {}", result.description());
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetVersion() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
*platinumId = ORBIS_NP_TROPHY_INVALID_TROPHY_ID;
int num_trophies = 0;
int num_trophies_unlocked = 0;
pugi::xml_node platinum_node;
auto trophyconf = doc.child("trophyconf");
for (pugi::xml_node& node : trophyconf.children()) {
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
const char* current_trophy_name = node.child("name").text().as_string();
std::string_view current_trophy_description = node.child("detail").text().as_string();
std::string_view current_trophy_type = node.attribute("ttype").value();
if (current_trophy_type == "P") {
platinum_node = node;
if (trophyId == current_trophy_id) {
return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK;
}
}
if (std::string_view(node.name()) == "trophy") {
if (node.attribute("pid").as_int(-1) != ORBIS_NP_TROPHY_INVALID_TROPHY_ID) {
num_trophies++;
if (current_trophy_unlockstate) {
num_trophies_unlocked++;
}
}
if (current_trophy_id == trophyId) {
if (current_trophy_unlockstate) {
LOG_INFO(Lib_NpTrophy, "Trophy already unlocked");
return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED;
} else {
if (node.attribute("unlockstate").empty()) {
node.append_attribute("unlockstate") = "true";
} else {
node.attribute("unlockstate").set_value("true");
}
auto trophyTimestamp = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
if (node.attribute("timestamp").empty()) {
node.append_attribute("timestamp") =
std::to_string(trophyTimestamp).c_str();
} else {
node.attribute("timestamp")
.set_value(std::to_string(trophyTimestamp).c_str());
}
std::string trophy_icon_file = "TROP";
trophy_icon_file.append(node.attribute("id").value());
trophy_icon_file.append(".PNG");
std::filesystem::path current_icon_path =
trophy_dir / trophy_folder / "Icons" / trophy_icon_file;
AddTrophyToQueue(current_icon_path, current_trophy_name, current_trophy_type);
}
}
}
}
if (!platinum_node.attribute("unlockstate").as_bool()) {
if ((num_trophies - 1) == num_trophies_unlocked) {
if (platinum_node.attribute("unlockstate").empty()) {
platinum_node.append_attribute("unlockstate") = "true";
} else {
platinum_node.attribute("unlockstate").set_value("true");
}
auto trophyTimestamp = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
if (platinum_node.attribute("timestamp").empty()) {
platinum_node.append_attribute("timestamp") =
std::to_string(trophyTimestamp).c_str();
} else {
platinum_node.attribute("timestamp")
.set_value(std::to_string(trophyTimestamp).c_str());
}
int platinum_trophy_id =
platinum_node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
const char* platinum_trophy_name = platinum_node.child("name").text().as_string();
std::string platinum_icon_file = "TROP";
platinum_icon_file.append(platinum_node.attribute("id").value());
platinum_icon_file.append(".PNG");
std::filesystem::path platinum_icon_path =
trophy_dir / trophy_folder / "Icons" / platinum_icon_file;
*platinumId = platinum_trophy_id;
AddTrophyToQueue(platinum_icon_path, platinum_trophy_name, "P");
}
}
doc.save_file((trophy_dir / trophy_folder / "Xml" / "TROP.XML").native().c_str());
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyTitleDetails() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}

View File

@ -13,8 +13,6 @@ class SymbolsResolver;
namespace Libraries::Np::NpTrophy {
extern std::string game_serial;
constexpr int ORBIS_NP_TROPHY_FLAG_SETSIZE = 128;
constexpr int ORBIS_NP_TROPHY_FLAG_BITS_SHIFT = 5;

View File

@ -47,3 +47,4 @@ constexpr int ORBIS_NP_TROPHY_ERROR_INCONSISTENT_TITLE_CONF = 0x80551628;
constexpr int ORBIS_NP_TROPHY_ERROR_TITLE_BACKGROUND = 0x80551629;
constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_DISABLED = 0x8055162B;
constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_DISPLAY_BUFFER_NOT_IN_USE = 0x8055162D;
constexpr int ORBIS_NP_TROPHY_ERROR_TITLE_NOT_FOUND = 0x805516C2;

View File

@ -38,6 +38,7 @@
#include "core/libraries/save_data/save_backup.h"
#include "core/linker.h"
#include "core/memory.h"
#include "core/user_settings.h"
#include "emulator.h"
#include "video_core/cache_storage.h"
#include "video_core/renderdoc.h"
@ -50,6 +51,7 @@
#include <sys/wait.h>
#include <unistd.h>
#endif
#include <core/file_format/npbind.h>
Frontend::WindowSDL* g_window = nullptr;
@ -196,6 +198,18 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
}
game_info.game_folder = game_folder;
std::filesystem::path npbindPath = game_folder / "sce_sys/npbind.dat";
NPBindFile npbind;
if (!npbind.Load(npbindPath.string())) {
LOG_WARNING(Common_Filesystem, "Failed to load npbind.dat file");
} else {
auto npCommIds = npbind.GetNpCommIds();
if (npCommIds.empty()) {
LOG_WARNING(Common_Filesystem, "No NPComm IDs found in npbind.dat");
} else {
game_info.npCommIds = std::move(npCommIds);
}
}
EmulatorSettings.Load(id);
@ -291,15 +305,20 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
// Initialize patcher and trophies
if (!id.empty()) {
MemoryPatcher::g_game_serial = id;
Libraries::Np::NpTrophy::game_serial = id;
const auto trophyDir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / id / "TrophyFiles";
if (!std::filesystem::exists(trophyDir)) {
TRP trp;
if (!trp.Extract(game_folder, id)) {
LOG_ERROR(Loader, "Couldn't extract trophies");
int index = 0;
for (std::string npCommId : game_info.npCommIds) {
for (User user : UserSettings.GetUserManager().GetValidUsers()) {
const auto trophyDir = Common::FS::GetUserPath(Common::FS::PathType::HomeDir) /
std::to_string(user.user_id) / "trophy" / npCommId;
if (!std::filesystem::exists(trophyDir)) {
TRP trp;
if (!trp.Extract(game_folder, index, npCommId, trophyDir)) {
LOG_ERROR(Loader, "Couldn't extract trophies");
}
}
}
index++;
}
}

View File

@ -40,6 +40,8 @@ int main(int argc, char* argv[]) {
UserSettings.Load();
// TODO add back trophy key migration without the need of entire previous config framework
auto key_manager = KeyManager::GetInstance();
key_manager->LoadFromFile();
CLI::App app{"shadPS4 Emulator CLI"};