support for unlocking trophies (#854)

* add pugixml

* trophy_viewer: support for trophy unlocking

* nptrophy: UnlockTrophy(), DestroyContext()

* initial imgui popup

* queue to handle multiple trophies at once

* extract trophy info on game start + various fixes

* platinum trophy support + extract trophy data on startup

* format

* nptrophy: GetTrophyUnlockState

* implement vinicius' reviews
This commit is contained in:
CrazyBloo 2024-09-10 23:50:55 -04:00 committed by GitHub
parent fa8e81df96
commit 5b26dc8e36
11 changed files with 607 additions and 53 deletions

3
.gitmodules vendored
View file

@ -90,3 +90,6 @@
url = https://github.com/shadps4-emu/ext-imgui.git
shallow = true
branch = docking
[submodule "externals/pugixml"]
path = externals/pugixml
url = https://github.com/zeux/pugixml.git

View file

@ -108,6 +108,7 @@ find_package(xbyak 7.07 CONFIG)
find_package(xxHash 0.8.2 MODULE)
find_package(zlib-ng 2.1.7 MODULE)
find_package(Zydis 5.0.0 CONFIG)
find_package(pugixml 1.14 CONFIG)
if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR NOT MSVC)
find_package(cryptopp 8.9.0 MODULE)
@ -313,6 +314,8 @@ set(NP_LIBS src/core/libraries/np_manager/np_manager.cpp
src/core/libraries/np_score/np_score.h
src/core/libraries/np_trophy/np_trophy.cpp
src/core/libraries/np_trophy/np_trophy.h
src/core/libraries/np_trophy/trophy_ui.cpp
src/core/libraries/np_trophy/trophy_ui.h
)
set(MISC_LIBS src/core/libraries/screenshot/screenshot.cpp
@ -689,7 +692,7 @@ endif()
create_target_directory_groups(shadps4)
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui)
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3)
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3 pugixml::pugixml)
target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h")
target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h")

View file

@ -178,3 +178,8 @@ option(TRACY_NO_SAMPLING "" ON)
option(TRACY_ONLY_LOCALHOST "" ON)
option(TRACY_NO_CONTEXT_SWITCH "" ON)
add_subdirectory(tracy)
# pugixml
if (NOT TARGET pugixml::pugixml)
add_subdirectory(pugixml)
endif()

1
externals/pugixml vendored Submodule

@ -0,0 +1 @@
Subproject commit 30cc354fe37114ec7a0a4ed2192951690357c2ed

View file

@ -440,11 +440,47 @@ constexpr int ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT = 0x8096000A;
constexpr int ORBIS_SYSTEM_SERVICE_ERROR_PARAMETER = 0x80A10003;
// NpTrophy library
constexpr int ORBIS_NP_TROPHY_ERROR_UNKNOWN = 0x80551600;
constexpr int ORBIS_NP_TROPHY_ERROR_NOT_INITIALIZED = 0x80551601;
constexpr int ORBIS_NP_TROPHY_ERROR_ALREADY_INITIALIZED = 0x80551602;
constexpr int ORBIS_NP_TROPHY_ERROR_OUT_OF_MEMORY = 0x80551603;
constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT = 0x80551604;
constexpr int ORBIS_NP_TROPHY_ERROR_INSUFFICIENT_BUFFER = 0x80551605;
constexpr int ORBIS_NP_TROPHY_ERROR_EXCEEDS_MAX = 0x80551606;
constexpr int ORBIS_NP_TROPHY_ERROR_ABORT = 0x80551607;
constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE = 0x80551608;
constexpr int ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX = 0x80551624;
constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT = 0x80551609;
constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID = 0x8055160A;
constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_GROUP_ID = 0x8055160B;
constexpr int ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED = 0x8055160C;
constexpr int ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK = 0x8055160D;
constexpr int ORBIS_NP_TROPHY_ERROR_ACCOUNTID_NOT_MATCH = 0x8055160E;
constexpr int ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED = 0x8055160F;
constexpr int ORBIS_NP_TROPHY_ERROR_ALREADY_REGISTERED = 0x80551610;
constexpr int ORBIS_NP_TROPHY_ERROR_BROKEN_DATA = 0x80551611;
constexpr int ORBIS_NP_TROPHY_ERROR_INSUFFICIENT_SPACE = 0x80551612;
constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_ALREADY_EXISTS = 0x80551613;
constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_EXCEEDS_MAX = 0x80551622;
constexpr int ORBIS_NP_TROPHY_ERROR_ICON_FILE_NOT_FOUND = 0x80551614;
constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TRP_FILE_FORMAT = 0x80551616;
constexpr int ORBIS_NP_TROPHY_ERROR_UNSUPPORTED_TRP_FILE = 0x80551617;
constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_CONF_FORMAT = 0x80551618;
constexpr int ORBIS_NP_TROPHY_ERROR_UNSUPPORTED_TROPHY_CONF = 0x80551619;
constexpr int ORBIS_NP_TROPHY_ERROR_TROPHY_NOT_UNLOCKED = 0x8055161A;
constexpr int ORBIS_NP_TROPHY_ERROR_USER_NOT_FOUND = 0x8055161C;
constexpr int ORBIS_NP_TROPHY_ERROR_USER_NOT_LOGGED_IN = 0x8055161D;
constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_USER_LOGOUT = 0x8055161E;
constexpr int ORBIS_NP_TROPHY_ERROR_USE_TRP_FOR_DEVELOPMENT = 0x8055161F;
constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_NP_SERVICE_LABEL = 0x80551621;
constexpr int ORBIS_NP_TROPHY_ERROR_NOT_SUPPORTED = 0x80551622;
constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_EXCEEDS_MAX = 0x80551623;
constexpr int ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX = 0x80551624;
constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_USER_ID = 0x80551625;
constexpr int ORBIS_NP_TROPHY_ERROR_TITLE_CONF_NOT_INSTALLED = 0x80551626;
constexpr int ORBIS_NP_TROPHY_ERROR_BROKEN_TITLE_CONF = 0x80551627;
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;
// AvPlayer library
constexpr int ORBIS_AVPLAYER_ERROR_INVALID_PARAMS = 0x806A0001;

View file

@ -4,13 +4,20 @@
#include <unordered_map>
#include "common/logging/log.h"
#include "common/path_util.h"
#include "common/slot_vector.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "externals/pugixml/src/pugixml.hpp"
#include "np_trophy.h"
#include "trophy_ui.h"
namespace Libraries::NpTrophy {
static TrophyUI g_trophy_ui;
std::string game_serial;
static constexpr auto MaxTrophyHandles = 4u;
static constexpr auto MaxTrophyContexts = 8u;
@ -24,11 +31,50 @@ struct ContextKeyHash {
struct TrophyContext {
u32 context_id;
};
static Common::SlotVector<u32> trophy_handles{};
static Common::SlotVector<OrbisNpTrophyHandle> trophy_handles{};
static Common::SlotVector<ContextKey> trophy_contexts{};
static std::unordered_map<ContextKey, TrophyContext, ContextKeyHash> contexts_internal{};
int PS4_SYSV_ABI sceNpTrophyAbortHandle() {
void ORBIS_NP_TROPHY_FLAG_ZERO(OrbisNpTrophyFlagArray* p) {
for (int i = 0; i < ORBIS_NP_TROPHY_NUM_MAX; i++) {
uint32_t array_index = i / 32;
uint32_t bit_position = i % 32;
p->flag_bits[array_index] &= ~(1U << bit_position);
}
}
void ORBIS_NP_TROPHY_FLAG_SET(int32_t trophyId, OrbisNpTrophyFlagArray* p) {
uint32_t array_index = trophyId / 32;
uint32_t bit_position = trophyId % 32;
p->flag_bits[array_index] |= (1U << bit_position);
}
void ORBIS_NP_TROPHY_FLAG_SET_ALL(OrbisNpTrophyFlagArray* p) {
for (int i = 0; i < ORBIS_NP_TROPHY_NUM_MAX; i++) {
uint32_t array_index = i / 32;
uint32_t bit_position = i % 32;
p->flag_bits[array_index] |= (1U << bit_position);
}
}
void ORBIS_NP_TROPHY_FLAG_CLR(int32_t trophyId, OrbisNpTrophyFlagArray* p) {
uint32_t array_index = trophyId / 32;
uint32_t bit_position = trophyId % 32;
p->flag_bits[array_index] &= ~(1U << bit_position);
}
bool ORBIS_NP_TROPHY_FLAG_ISSET(int32_t trophyId, OrbisNpTrophyFlagArray* p) {
uint32_t array_index = trophyId / 32;
uint32_t bit_position = trophyId % 32;
return (p->flag_bits[array_index] & (1U << bit_position)) ? 1 : 0;
}
int PS4_SYSV_ABI sceNpTrophyAbortHandle(OrbisNpTrophyHandle handle) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
@ -83,8 +129,8 @@ int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature() {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpTrophyCreateContext(u32* context, u32 user_id, u32 service_label,
u64 options) {
s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, int32_t user_id,
uint32_t service_label, uint64_t options) {
ASSERT(options == 0ull);
if (!context) {
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
@ -107,7 +153,7 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateContext(u32* context, u32 user_id, u32 service
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(u32* handle) {
s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(OrbisNpTrophyHandle* handle) {
if (!handle) {
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
@ -122,55 +168,120 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(u32* handle) {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyDestroyContext() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context) {
LOG_INFO(Lib_NpTrophy, "Destroyed Context {}", context);
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
Common::SlotId contextId;
contextId.index = context;
ContextKey contextkey = trophy_contexts[contextId];
trophy_contexts.erase(contextId);
contexts_internal.erase(contextkey);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(u32 handle) {
if (!trophy_handles.is_allocated({handle})) {
s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(OrbisNpTrophyHandle handle) {
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
if (!trophy_handles.is_allocated({static_cast<u32>(handle)})) {
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
}
trophy_handles.erase({handle});
trophy_handles.erase({static_cast<u32>(handle)});
LOG_INFO(Lib_NpTrophy, "Handle {} destroyed", handle);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyGetGameIcon() {
int PS4_SYSV_ABI sceNpTrophyGetGameIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
void* buffer, size_t* size) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyGetGameInfo() {
int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyGameDetails* details,
OrbisNpTrophyGameData* data) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyGetGroupIcon() {
int PS4_SYSV_ABI sceNpTrophyGetGroupIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyGroupId groupId, void* buffer, size_t* size) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyGetGroupInfo() {
int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyGroupId groupId,
OrbisNpTrophyGroupDetails* details,
OrbisNpTrophyGroupData* data) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon() {
int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyId trophyId, void* buffer, size_t* size) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo() {
int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyId trophyId, OrbisNpTrophyDetails* details,
OrbisNpTrophyData* data) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(u32 context, u32 handle, u32* flags, u32* count) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
*flags = 0u;
*count = 0;
s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
OrbisNpTrophyHandle handle,
OrbisNpTrophyFlagArray* flags, u32* count) {
LOG_INFO(Lib_NpTrophy, "GetTrophyUnlockState 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;
if (flags == nullptr || count == nullptr)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
ORBIS_NP_TROPHY_FLAG_ZERO(flags);
const auto trophyDir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
pugi::xml_document doc;
pugi::xml_parse_result result =
doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
int numTrophies = 0;
if (result) {
auto trophyconf = doc.child("trophyconf");
for (pugi::xml_node_iterator it = trophyconf.children().begin();
it != trophyconf.children().end(); ++it) {
std::string currentTrophyId = it->attribute("id").value();
std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
if (std::string(it->name()) == "trophy") {
numTrophies++;
}
if (currentTrophyUnlockState == "unlocked") {
ORBIS_NP_TROPHY_FLAG_SET(std::stoi(currentTrophyId), flags);
}
}
} else
LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
*count = numTrophies;
return ORBIS_OK;
}
@ -239,8 +350,16 @@ int PS4_SYSV_ABI sceNpTrophyNumInfoGetTotal() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyRegisterContext() {
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;
}
@ -254,7 +373,8 @@ int PS4_SYSV_ABI sceNpTrophySetInfoGetTrophyNum() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyShowTrophyList() {
int PS4_SYSV_ABI sceNpTrophyShowTrophyList(OrbisNpTrophyContext context,
OrbisNpTrophyHandle handle) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
@ -474,8 +594,132 @@ int PS4_SYSV_ABI sceNpTrophySystemSetDbgParamInt() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyUnlockTrophy() {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
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 >= 127)
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
if (platinumId == nullptr)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
const auto trophyDir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
pugi::xml_document doc;
pugi::xml_parse_result result =
doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
*platinumId = ORBIS_NP_TROPHY_INVALID_TROPHY_ID;
int numTrophies = 0;
int numTrophiesUnlocked = 0;
pugi::xml_node_iterator platinumIt;
int platinumTrophyGroup = -1;
if (result) {
auto trophyconf = doc.child("trophyconf");
for (pugi::xml_node_iterator it = trophyconf.children().begin();
it != trophyconf.children().end(); ++it) {
std::string currentTrophyId = it->attribute("id").value();
std::string currentTrophyName = it->child("name").text().as_string();
std::string currentTrophyDescription = it->child("detail").text().as_string();
std::string currentTrophyType = it->attribute("ttype").value();
std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
if (currentTrophyType == "P") {
platinumIt = it;
if (std::string(platinumIt->attribute("gid").value()).empty()) {
platinumTrophyGroup = -1;
} else {
platinumTrophyGroup =
std::stoi(std::string(platinumIt->attribute("gid").value()));
}
if (trophyId == std::stoi(currentTrophyId)) {
return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK;
}
}
if (std::string(it->name()) == "trophy") {
if (platinumTrophyGroup == -1) {
if (std::string(it->attribute("gid").value()).empty()) {
numTrophies++;
if (currentTrophyUnlockState == "unlocked") {
numTrophiesUnlocked++;
}
}
} else {
if (!std::string(it->attribute("gid").value()).empty()) {
if (std::stoi(std::string(it->attribute("gid").value())) ==
platinumTrophyGroup) {
numTrophies++;
if (currentTrophyUnlockState == "unlocked") {
numTrophiesUnlocked++;
}
}
}
}
if (std::stoi(currentTrophyId) == trophyId) {
LOG_INFO(Lib_NpTrophy, "Found trophy to unlock {} : {}",
it->child("name").text().as_string(),
it->child("detail").text().as_string());
if (currentTrophyUnlockState == "unlocked") {
LOG_INFO(Lib_NpTrophy, "Trophy already unlocked");
return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED;
} else {
if (std::string(it->attribute("unlockstate").value()).empty()) {
it->append_attribute("unlockstate") = "unlocked";
} else {
it->attribute("unlockstate").set_value("unlocked");
}
g_trophy_ui.AddTrophyToQueue(trophyId, currentTrophyName);
}
}
}
}
if (std::string(platinumIt->attribute("unlockstate").value()).empty()) {
if ((numTrophies - 2) == numTrophiesUnlocked) {
platinumIt->append_attribute("unlockstate") = "unlocked";
std::string platinumTrophyId = platinumIt->attribute("id").value();
std::string platinumTrophyName = platinumIt->child("name").text().as_string();
*platinumId = std::stoi(platinumTrophyId);
g_trophy_ui.AddTrophyToQueue(*platinumId, platinumTrophyName);
}
} else if (std::string(platinumIt->attribute("unlockstate").value()) == "locked") {
if ((numTrophies - 2) == numTrophiesUnlocked) {
platinumIt->attribute("unlockstate").set_value("unlocked");
std::string platinumTrophyId = platinumIt->attribute("id").value();
std::string platinumTrophyName = platinumIt->child("name").text().as_string();
*platinumId = std::stoi(platinumTrophyId);
g_trophy_ui.AddTrophyToQueue(*platinumId, platinumTrophyName);
}
}
doc.save_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
} else
LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
return ORBIS_OK;
}

View file

@ -4,6 +4,7 @@
#pragma once
#include "common/types.h"
#include "core/libraries/rtc/rtc.h"
namespace Core::Loader {
class SymbolsResolver;
@ -11,7 +12,116 @@ class SymbolsResolver;
namespace Libraries::NpTrophy {
int PS4_SYSV_ABI sceNpTrophyAbortHandle();
extern std::string game_serial;
constexpr int ORBIS_NP_TROPHY_FLAG_SETSIZE = 128;
constexpr int ORBIS_NP_TROPHY_FLAG_BITS_SHIFT = 5;
constexpr int ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE = 128;
constexpr int ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE = 1024;
constexpr int ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE = 128;
constexpr int ORBIS_NP_TROPHY_GROUP_DESCR_MAX_SIZE = 1024;
constexpr int ORBIS_NP_TROPHY_NAME_MAX_SIZE = 128;
constexpr int ORBIS_NP_TROPHY_DESCR_MAX_SIZE = 1024;
constexpr int ORBIS_NP_TROPHY_NUM_MAX = 128;
constexpr int ORBIS_NP_TROPHY_INVALID_HANDLE = -1;
constexpr int ORBIS_NP_TROPHY_INVALID_CONTEXT = -1;
constexpr int ORBIS_NP_TROPHY_INVALID_TROPHY_ID = -1;
typedef int32_t OrbisNpTrophyHandle;
typedef int32_t OrbisNpTrophyContext;
typedef int32_t OrbisNpTrophyId;
typedef uint32_t OrbisNpTrophyFlagMask;
struct OrbisNpTrophyFlagArray {
OrbisNpTrophyFlagMask
flag_bits[ORBIS_NP_TROPHY_FLAG_SETSIZE >> ORBIS_NP_TROPHY_FLAG_BITS_SHIFT];
};
void ORBIS_NP_TROPHY_FLAG_ZERO(OrbisNpTrophyFlagArray* p);
void ORBIS_NP_TROPHY_FLAG_SET(int32_t trophyId, OrbisNpTrophyFlagArray* p);
void ORBIS_NP_TROPHY_FLAG_SET_ALL(OrbisNpTrophyFlagArray* p);
void ORBIS_NP_TROPHY_FLAG_CLR(int32_t trophyId, OrbisNpTrophyFlagArray* p);
bool ORBIS_NP_TROPHY_FLAG_ISSET(int32_t trophyId, OrbisNpTrophyFlagArray* p);
struct OrbisNpTrophyData {
size_t size;
OrbisNpTrophyId trophyId;
bool unlocked;
uint8_t reserved[3];
Rtc::OrbisRtcTick timestamp;
};
typedef int32_t OrbisNpTrophyGrade;
constexpr int ORBIS_NP_TROPHY_GRADE_UNKNOWN = 0;
constexpr int ORBIS_NP_TROPHY_GRADE_PLATINUM = 1;
constexpr int ORBIS_NP_TROPHY_GRADE_GOLD = 2;
constexpr int ORBIS_NP_TROPHY_GRADE_SILVER = 3;
constexpr int ORBIS_NP_TROPHY_GRADE_BRONZE = 4;
typedef int32_t OrbisNpTrophyGroupId;
constexpr int ORBIS_NP_TROPHY_BASE_GAME_GROUP_ID = -1;
constexpr int ORBIS_NP_TROPHY_INVALID_GROUP_ID = -2;
struct OrbisNpTrophyDetails {
size_t size;
OrbisNpTrophyId trophyId;
OrbisNpTrophyGrade trophyGrade;
OrbisNpTrophyGroupId groupId;
bool hidden;
uint8_t reserved[3];
char name[ORBIS_NP_TROPHY_NAME_MAX_SIZE];
char description[ORBIS_NP_TROPHY_DESCR_MAX_SIZE];
};
struct OrbisNpTrophyGameData {
size_t size;
uint32_t unlockedTrophies;
uint32_t unlockedPlatinum;
uint32_t unlockedGold;
uint32_t unlockedSilver;
uint32_t unlockedBronze;
uint32_t progressPercentage;
};
struct OrbisNpTrophyGameDetails {
size_t size;
uint32_t numGroups;
uint32_t numTrophies;
uint32_t numPlatinum;
uint32_t numGold;
uint32_t numSilver;
uint32_t numBronze;
char title[ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE];
char description[ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE];
};
struct OrbisNpTrophyGroupData {
size_t size;
OrbisNpTrophyGroupId groupId;
uint32_t unlockedTrophies;
uint32_t unlockedPlatinum;
uint32_t unlockedGold;
uint32_t unlockedSilver;
uint32_t unlockedBronze;
uint32_t progressPercentage;
uint8_t reserved[4];
};
struct OrbisNpTrophyGroupDetails {
size_t size;
OrbisNpTrophyGroupId groupId;
uint32_t numTrophies;
uint32_t numPlatinum;
uint32_t numGold;
uint32_t numSilver;
uint32_t numBronze;
char title[ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE];
char description[ORBIS_NP_TROPHY_GROUP_DESCR_MAX_SIZE];
};
int PS4_SYSV_ABI sceNpTrophyAbortHandle(OrbisNpTrophyHandle handle);
int PS4_SYSV_ABI sceNpTrophyCaptureScreenshot();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyDetails();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyFlagArray();
@ -22,18 +132,30 @@ int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfoInGroup();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetVersion();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyTitleDetails();
int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature();
s32 PS4_SYSV_ABI sceNpTrophyCreateContext(u32* context, u32 user_id, u32 service_label,
u64 options);
s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(u32* handle);
int PS4_SYSV_ABI sceNpTrophyDestroyContext();
s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(u32 handle);
int PS4_SYSV_ABI sceNpTrophyGetGameIcon();
int PS4_SYSV_ABI sceNpTrophyGetGameInfo();
int PS4_SYSV_ABI sceNpTrophyGetGroupIcon();
int PS4_SYSV_ABI sceNpTrophyGetGroupInfo();
int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon();
int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo();
s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(u32 context, u32 handle, u32* flags, u32* count);
s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, int32_t user_id,
uint32_t service_label, uint64_t options);
s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(OrbisNpTrophyHandle* handle);
int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context);
s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(OrbisNpTrophyHandle handle);
int PS4_SYSV_ABI sceNpTrophyGetGameIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
void* buffer, size_t* size);
int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyGameDetails* details,
OrbisNpTrophyGameData* data);
int PS4_SYSV_ABI sceNpTrophyGetGroupIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyGroupId groupId, void* buffer, size_t* size);
int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyGroupId groupId,
OrbisNpTrophyGroupDetails* details,
OrbisNpTrophyGroupData* data);
int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyId trophyId, void* buffer, size_t* size);
int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyId trophyId, OrbisNpTrophyDetails* details,
OrbisNpTrophyData* data);
s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
OrbisNpTrophyHandle handle,
OrbisNpTrophyFlagArray* flags, u32* count);
int PS4_SYSV_ABI sceNpTrophyGroupArrayGetNum();
int PS4_SYSV_ABI sceNpTrophyIntAbortHandle();
int PS4_SYSV_ABI sceNpTrophyIntCheckNetSyncTitles();
@ -47,10 +169,12 @@ int PS4_SYSV_ABI sceNpTrophyIntGetTrpIconByUri();
int PS4_SYSV_ABI sceNpTrophyIntNetSyncTitle();
int PS4_SYSV_ABI sceNpTrophyIntNetSyncTitles();
int PS4_SYSV_ABI sceNpTrophyNumInfoGetTotal();
int PS4_SYSV_ABI sceNpTrophyRegisterContext();
int PS4_SYSV_ABI sceNpTrophyRegisterContext(OrbisNpTrophyContext context,
OrbisNpTrophyHandle handle, uint64_t options);
int PS4_SYSV_ABI sceNpTrophySetInfoGetTrophyFlagArray();
int PS4_SYSV_ABI sceNpTrophySetInfoGetTrophyNum();
int PS4_SYSV_ABI sceNpTrophyShowTrophyList();
int PS4_SYSV_ABI sceNpTrophyShowTrophyList(OrbisNpTrophyContext context,
OrbisNpTrophyHandle handle);
int PS4_SYSV_ABI sceNpTrophySystemAbortHandle();
int PS4_SYSV_ABI sceNpTrophySystemBuildGroupIconUri();
int PS4_SYSV_ABI sceNpTrophySystemBuildNetTrophyIconUri();
@ -94,7 +218,8 @@ int PS4_SYSV_ABI sceNpTrophySystemRemoveTitleData();
int PS4_SYSV_ABI sceNpTrophySystemRemoveUserData();
int PS4_SYSV_ABI sceNpTrophySystemSetDbgParam();
int PS4_SYSV_ABI sceNpTrophySystemSetDbgParamInt();
int PS4_SYSV_ABI sceNpTrophyUnlockTrophy();
int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyId trophyId, OrbisNpTrophyId* platinumId);
int PS4_SYSV_ABI Func_149656DA81D41C59();
int PS4_SYSV_ABI Func_9F80071876FFA5F6();
int PS4_SYSV_ABI Func_F8EF6F5350A91990();

View file

@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <imgui.h>
#include "common/assert.h"
#include "imgui/imgui_std.h"
#include "trophy_ui.h"
using namespace ImGui;
using namespace Libraries::NpTrophy;
TrophyUI::TrophyUI() {
AddLayer(this);
}
TrophyUI::~TrophyUI() {
Finish();
}
void Libraries::NpTrophy::TrophyUI::AddTrophyToQueue(int trophyId, std::string trophyName) {
TrophyInfo newInfo;
newInfo.trophyId = trophyId;
newInfo.trophyName = trophyName;
trophyQueue.push_back(newInfo);
}
void TrophyUI::Finish() {
RemoveLayer(this);
}
bool displayingTrophy;
std::chrono::steady_clock::time_point trophyStartedTime;
void TrophyUI::Draw() {
const auto& io = GetIO();
const ImVec2 window_size{
std::min(io.DisplaySize.x, 200.f),
std::min(io.DisplaySize.y, 75.f),
};
if (trophyQueue.size() != 0) {
if (!displayingTrophy) {
displayingTrophy = true;
trophyStartedTime = std::chrono::steady_clock::now();
}
std::chrono::steady_clock::time_point timeNow = std::chrono::steady_clock::now();
std::chrono::seconds duration =
std::chrono::duration_cast<std::chrono::seconds>(timeNow - trophyStartedTime);
if (duration.count() >= 5) {
trophyQueue.erase(trophyQueue.begin());
displayingTrophy = false;
}
if (trophyQueue.size() != 0) {
SetNextWindowSize(window_size);
SetNextWindowCollapsed(false);
SetNextWindowPos(ImVec2(io.DisplaySize.x - 200, 50));
KeepNavHighlight();
TrophyInfo currentTrophyInfo = trophyQueue[0];
if (Begin("Trophy Window", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoInputs)) {
Text("Trophy earned!");
TextWrapped(currentTrophyInfo.trophyName.c_str());
}
End();
}
}
}

View file

@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include <variant>
#include <vector>
#include "common/fixed_value.h"
#include "common/types.h"
#include "core/libraries/np_trophy/np_trophy.h"
#include "imgui/imgui_layer.h"
namespace Libraries::NpTrophy {
struct TrophyInfo {
int trophyId = -1;
std::string trophyName;
};
class TrophyUI final : public ImGui::Layer {
std::vector<TrophyInfo> trophyQueue;
public:
TrophyUI();
~TrophyUI() override;
void AddTrophyToQueue(int trophyId, std::string trophyName);
void Finish();
void Draw() override;
bool ShouldGrabGamepad() override {
return false;
}
};
}; // namespace Libraries::NpTrophy

View file

@ -19,12 +19,14 @@
#include "core/file_format/playgo_chunk.h"
#include "core/file_format/psf.h"
#include "core/file_format/splash.h"
#include "core/file_format/trp.h"
#include "core/file_sys/fs.h"
#include "core/libraries/disc_map/disc_map.h"
#include "core/libraries/kernel/thread_management.h"
#include "core/libraries/libc_internal/libc_internal.h"
#include "core/libraries/libs.h"
#include "core/libraries/ngs2/ngs2.h"
#include "core/libraries/np_trophy/np_trophy.h"
#include "core/libraries/rtc/rtc.h"
#include "core/linker.h"
#include "core/memory.h"
@ -98,6 +100,15 @@ void Emulator::Run(const std::filesystem::path& file) {
auto* param_sfo = Common::Singleton<PSF>::Instance();
param_sfo->open(sce_sys_folder.string() + "/param.sfo", {});
id = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9);
Libraries::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(file.parent_path())) {
LOG_ERROR(Loader, "Couldn't extract trophies");
}
}
#ifdef ENABLE_QT_GUI
MemoryPatcher::g_game_serial = id;
#endif

View file

@ -9,7 +9,8 @@ TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath) : QMainWindo
this->setAttribute(Qt::WA_DeleteOnClose);
tabWidget = new QTabWidget(this);
gameTrpPath_ = gameTrpPath;
headers << "Trophy"
headers << "Unlocked"
<< "Trophy"
<< "Name"
<< "Description"
<< "ID"
@ -61,6 +62,7 @@ void TrophyViewer::PopulateTrophyWidget(QString title) {
QStringList trpId;
QStringList trpHidden;
QStringList trpUnlocked;
QStringList trpType;
QStringList trpPid;
QStringList trophyNames;
@ -81,6 +83,15 @@ void TrophyViewer::PopulateTrophyWidget(QString title) {
trpHidden.append(reader.attributes().value("hidden").toString());
trpType.append(reader.attributes().value("ttype").toString());
trpPid.append(reader.attributes().value("pid").toString());
if (reader.attributes().hasAttribute("unlockstate")) {
if (reader.attributes().value("unlockstate").toString() == "unlocked") {
trpUnlocked.append("unlocked");
} else {
trpUnlocked.append("locked");
}
} else {
trpUnlocked.append("locked");
}
}
if (reader.name().toString() == "name" && !trpId.isEmpty()) {
@ -93,7 +104,7 @@ void TrophyViewer::PopulateTrophyWidget(QString title) {
}
QTableWidget* tableWidget = new QTableWidget(this);
tableWidget->setShowGrid(false);
tableWidget->setColumnCount(7);
tableWidget->setColumnCount(8);
tableWidget->setHorizontalHeaderLabels(headers);
tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
@ -105,21 +116,22 @@ void TrophyViewer::PopulateTrophyWidget(QString title) {
QTableWidgetItem* item = new QTableWidgetItem();
item->setData(Qt::DecorationRole, icon);
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
tableWidget->setItem(row, 0, item);
tableWidget->setItem(row, 1, item);
if (!trophyNames.isEmpty() && !trophyDetails.isEmpty()) {
SetTableItem(tableWidget, row, 1, trophyNames[row]);
SetTableItem(tableWidget, row, 2, trophyDetails[row]);
SetTableItem(tableWidget, row, 3, trpId[row]);
SetTableItem(tableWidget, row, 4, trpHidden[row]);
SetTableItem(tableWidget, row, 5, GetTrpType(trpType[row].at(0)));
SetTableItem(tableWidget, row, 6, trpPid[row]);
SetTableItem(tableWidget, row, 0, trpUnlocked[row]);
SetTableItem(tableWidget, row, 2, trophyNames[row]);
SetTableItem(tableWidget, row, 3, trophyDetails[row]);
SetTableItem(tableWidget, row, 4, trpId[row]);
SetTableItem(tableWidget, row, 5, trpHidden[row]);
SetTableItem(tableWidget, row, 6, GetTrpType(trpType[row].at(0)));
SetTableItem(tableWidget, row, 7, trpPid[row]);
}
tableWidget->verticalHeader()->resizeSection(row, icon.height());
row++;
}
tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
int width = 16;
for (int i = 0; i < 7; i++) {
for (int i = 0; i < 8; i++) {
width += tableWidget->horizontalHeader()->sectionSize(i);
}
tableWidget->resize(width, 720);