From 74c2888aaa9be011f004959751f6357e9d409c65 Mon Sep 17 00:00:00 2001 From: CrazyBloo Date: Tue, 10 Sep 2024 23:50:55 -0400 Subject: [PATCH] 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 --- .gitmodules | 3 + CMakeLists.txt | 5 +- externals/CMakeLists.txt | 5 + externals/pugixml | 1 + src/core/libraries/error_codes.h | 40 ++- src/core/libraries/np_trophy/np_trophy.cpp | 292 +++++++++++++++++++-- src/core/libraries/np_trophy/np_trophy.h | 157 +++++++++-- src/core/libraries/np_trophy/trophy_ui.cpp | 74 ++++++ src/core/libraries/np_trophy/trophy_ui.h | 40 +++ src/emulator.cpp | 11 + src/qt_gui/trophy_viewer.cpp | 32 ++- 11 files changed, 607 insertions(+), 53 deletions(-) create mode 160000 externals/pugixml create mode 100644 src/core/libraries/np_trophy/trophy_ui.cpp create mode 100644 src/core/libraries/np_trophy/trophy_ui.h diff --git a/.gitmodules b/.gitmodules index be4c1851..b2543534 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 90496953..9101af9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index fe4ac5e9..5410f37e 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -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() \ No newline at end of file diff --git a/externals/pugixml b/externals/pugixml new file mode 160000 index 00000000..30cc354f --- /dev/null +++ b/externals/pugixml @@ -0,0 +1 @@ +Subproject commit 30cc354fe37114ec7a0a4ed2192951690357c2ed diff --git a/src/core/libraries/error_codes.h b/src/core/libraries/error_codes.h index 094ea660..b9896b6c 100644 --- a/src/core/libraries/error_codes.h +++ b/src/core/libraries/error_codes.h @@ -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; diff --git a/src/core/libraries/np_trophy/np_trophy.cpp b/src/core/libraries/np_trophy/np_trophy.cpp index ed25322b..c3f83341 100644 --- a/src/core/libraries/np_trophy/np_trophy.cpp +++ b/src/core/libraries/np_trophy/np_trophy.cpp @@ -4,13 +4,20 @@ #include #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 trophy_handles{}; +static Common::SlotVector trophy_handles{}; static Common::SlotVector trophy_contexts{}; static std::unordered_map 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(handle)})) { return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; } - trophy_handles.erase({handle}); + trophy_handles.erase({static_cast(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; } diff --git a/src/core/libraries/np_trophy/np_trophy.h b/src/core/libraries/np_trophy/np_trophy.h index d05d353f..ae08b296 100644 --- a/src/core/libraries/np_trophy/np_trophy.h +++ b/src/core/libraries/np_trophy/np_trophy.h @@ -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(); diff --git a/src/core/libraries/np_trophy/trophy_ui.cpp b/src/core/libraries/np_trophy/trophy_ui.cpp new file mode 100644 index 00000000..d2350010 --- /dev/null +++ b/src/core/libraries/np_trophy/trophy_ui.cpp @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#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(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(); + } + } +} \ No newline at end of file diff --git a/src/core/libraries/np_trophy/trophy_ui.h b/src/core/libraries/np_trophy/trophy_ui.h new file mode 100644 index 00000000..d730aca5 --- /dev/null +++ b/src/core/libraries/np_trophy/trophy_ui.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#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 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 \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index e631698f..9c41a3db 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -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::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 diff --git a/src/qt_gui/trophy_viewer.cpp b/src/qt_gui/trophy_viewer.cpp index 6fd5322c..8b96948c 100644 --- a/src/qt_gui/trophy_viewer.cpp +++ b/src/qt_gui/trophy_viewer.cpp @@ -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);