diff --git a/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp b/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp index 81b3356e77..8e07340622 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp @@ -154,7 +154,35 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } +// Helpers + +void show_trophy_notification(u32 context, u32 handle, u32 trophyId, const std::string& context_name) +{ + // Get icon for the notification. + const std::string padded_trophy_id = fmt::format("%03u", trophyId); + const std::string trophy_icon_path = "/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + context_name + "/TROP" + padded_trophy_id + ".PNG"; + fs::file trophy_icon_file = fs::file(vfs::get(trophy_icon_path)); + std::vector trophy_icon_data; + trophy_icon_file.read(trophy_icon_data, trophy_icon_file.size()); + + vm::var details({ 0 }); + vm::var _({ 0 }); + + const s32 ret = sceNpTrophyGetTrophyInfo(context, handle, trophyId, details, _); + if (ret != CELL_OK) + { + sceNpTrophy.error("Failed to get info for trophy dialog. Error code %x", ret); + *details = SceNpTrophyDetails(); + } + + if (auto trophy_notification_dialog = Emu.GetCallbacks().get_trophy_notification_dialog()) + { + trophy_notification_dialog->ShowTrophyNotification(*details, trophy_icon_data); + } +} + // Functions + error_code sceNpTrophyInit(vm::ptr pool, u32 poolSize, u32 containerId, u64 options) { sceNpTrophy.warning("sceNpTrophyInit(pool=*0x%x, poolSize=0x%x, containerId=0x%x, options=0x%llx)", pool, poolSize, containerId, options); @@ -795,37 +823,40 @@ error_code sceNpTrophyUnlockTrophy(u32 context, u32 handle, s32 trophyId, vm::pt if (ctxt->tropusr->GetTrophyUnlockState(trophyId)) return SCE_NP_TROPHY_ERROR_ALREADY_UNLOCKED; - ctxt->tropusr->UnlockTrophy(trophyId, 0, 0); // TODO - std::string trophyPath = "/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + ctxt->trp_name + "/TROPUSR.DAT"; - ctxt->tropusr->Save(trophyPath); + ctxt->tropusr->UnlockTrophy(trophyId, 0, 0); // TODO: add timestamps + + // TODO: Make sure that unlocking platinum trophies is properly implemented and improve upon it + const std::string& config_path = vfs::get("/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + ctxt->trp_name + "/TROPCONF.SFM"); + const u32 unlocked_platinum_id = ctxt->tropusr->GetUnlockedPlatinumID(trophyId, config_path); + + if (unlocked_platinum_id != SCE_NP_TROPHY_INVALID_TROPHY_ID) + { + sceNpTrophy.warning("sceNpTrophyUnlockTrophy: All requirements for unlocking the platinum trophy (ID = %d) were met.)", unlocked_platinum_id); + + if (ctxt->tropusr->UnlockTrophy(unlocked_platinum_id, 0, 0)) // TODO: add timestamps + { + sceNpTrophy.success("You unlocked a platinum trophy! Hooray!!!"); + } + } if (platinumId) { - *platinumId = SCE_NP_TROPHY_INVALID_TROPHY_ID; // TODO + *platinumId = unlocked_platinum_id; + sceNpTrophy.warning("sceNpTrophyUnlockTrophy: platinumId was set to %d)", unlocked_platinum_id); } + const std::string trophyPath = "/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + ctxt->trp_name + "/TROPUSR.DAT"; + ctxt->tropusr->Save(trophyPath); + if (g_cfg.misc.show_trophy_popups) { - // Get icon for the notification. - const std::string padded_trophy_id = fmt::format("%03u", trophyId); - const std::string trophy_icon_path = "/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + ctxt->trp_name + "/TROP" + padded_trophy_id + ".PNG"; - fs::file trophy_icon_file = fs::file(vfs::get(trophy_icon_path)); - std::vector trophy_icon_data; - trophy_icon_file.read(trophy_icon_data, trophy_icon_file.size()); + // Enqueue popup for the regular trophy + show_trophy_notification(context, handle, trophyId, ctxt->trp_name); - vm::var details({0}); - vm::var _({0}); - - const s32 ret = sceNpTrophyGetTrophyInfo(context, handle, trophyId, details, _); - if (ret != CELL_OK) + if (unlocked_platinum_id != SCE_NP_TROPHY_INVALID_TROPHY_ID) { - sceNpTrophy.error("Failed to get info for trophy dialog. Error code %x", ret); - *details = SceNpTrophyDetails(); - } - - if (auto trophy_notification_dialog = Emu.GetCallbacks().get_trophy_notification_dialog()) - { - trophy_notification_dialog->ShowTrophyNotification(*details, trophy_icon_data); + // Enqueue popup for the holy platinum trophy + show_trophy_notification(context, handle, unlocked_platinum_id, ctxt->trp_name); } } diff --git a/rpcs3/Loader/TROPUSR.cpp b/rpcs3/Loader/TROPUSR.cpp index 9b24f57504..7a57df8b54 100644 --- a/rpcs3/Loader/TROPUSR.cpp +++ b/rpcs3/Loader/TROPUSR.cpp @@ -1,4 +1,4 @@ -#include "stdafx.h" +#include "stdafx.h" #include "restore_new.h" #include "Utilities/rXml.h" #include "define_new_memleakdetect.h" @@ -159,18 +159,19 @@ bool TROPUSRLoader::Generate(const std::string& filepath, const std::string& con if (n->GetName() == "trophy") { const u32 trophy_id = std::atoi(n->GetAttribute("id").c_str()); + const u32 trophy_pid = std::atoi(n->GetAttribute("pid").c_str()); u32 trophy_grade; switch (n->GetAttribute("ttype")[0]) { - case 'B': trophy_grade = 4; break; - case 'S': trophy_grade = 3; break; - case 'G': trophy_grade = 2; break; - case 'P': trophy_grade = 1; break; - default: trophy_grade = 0; + case 'B': trophy_grade = trophy_grade::bronze; break; + case 'S': trophy_grade = trophy_grade::silver; break; + case 'G': trophy_grade = trophy_grade::gold; break; + case 'P': trophy_grade = trophy_grade::platinum; break; + default: trophy_grade = trophy_grade::unknown; break; } - TROPUSREntry4 entry4 = { 4, u32{sizeof(TROPUSREntry4)} - 0x10, ::size32(m_table4), 0, trophy_id, trophy_grade, 0xFFFFFFFF }; + TROPUSREntry4 entry4 = { 4, u32{sizeof(TROPUSREntry4)} - 0x10, ::size32(m_table4), 0, trophy_id, trophy_grade, trophy_pid }; TROPUSREntry6 entry6 = { 6, u32{sizeof(TROPUSREntry6)} - 0x10, ::size32(m_table6), 0, trophy_id }; m_table4.push_back(entry4); @@ -216,6 +217,80 @@ u32 TROPUSRLoader::GetUnlockedTrophiesCount() return count; } +u32 TROPUSRLoader::GetUnlockedPlatinumID(u32 trophy_id, const std::string& config_path) +{ + constexpr u32 invalid_trophy_id = -1; // SCE_NP_TROPHY_INVALID_TROPHY_ID; + + if (trophy_id >= m_table6.size() || trophy_id >= m_table4.size()) + { + LOG_WARNING(LOADER, "TROPUSRLoader::GetUnlockedPlatinumID: Invalid id=%d", trophy_id); + return invalid_trophy_id; + } + + if (m_table6.size() != m_table4.size()) + { + LOG_WARNING(LOADER, "TROPUSRLoader::GetUnlockedPlatinumID: Table size mismatch: %d vs. %d", m_table6.size(), m_table4.size()); + return invalid_trophy_id; + } + + // We need to read the trophy info from file here and update it for backwards compatibility. + // TROPUSRLoader::Generate will currently not be called on existing trophy data which might lack the pid. + fs::file config(config_path); + + if (!config) + { + return invalid_trophy_id; + } + + rXmlDocument doc; + doc.Read(config.to_string()); + + auto trophy_base = doc.GetRoot(); + if (trophy_base->GetChildren()->GetName() == "trophyconf") + { + trophy_base = trophy_base->GetChildren(); + } + + const size_t trophy_count = m_table4.size(); + + for (std::shared_ptr n = trophy_base->GetChildren(); n; n = n->GetNext()) + { + if (n->GetName() == "trophy") + { + const u32 trophy_id = std::atoi(n->GetAttribute("id").c_str()); + const u32 trophy_pid = std::atoi(n->GetAttribute("pid").c_str()); + + // We currently assume that trophies are ordered + if (trophy_id < trophy_count && m_table4[trophy_id].trophy_id == trophy_id) + { + // Update the pid for backwards compatibility + m_table4[trophy_id].trophy_pid = trophy_pid; + } + } + } + + // Get this trophy's platinum link id + const u32 pid = m_table4[trophy_id].trophy_pid; + + // The platinum trophy has to have a valid id and must still be locked + if (pid == invalid_trophy_id || GetTrophyUnlockState(pid)) // the first check is redundant but I'll keep it to prevent regressions + { + return invalid_trophy_id; + } + + // The platinum trophy stays locked if any relevant trophy is still locked + for (size_t i = 0; i < trophy_count; i++) + { + if (m_table4[i].trophy_pid == pid && !m_table6[i].trophy_state) + { + return invalid_trophy_id; + } + } + + // All relevant trophies for this platinum link id were unlocked + return pid; +} + u32 TROPUSRLoader::GetTrophyUnlockState(u32 id) { if (id >= m_table6.size()) @@ -243,6 +318,7 @@ bool TROPUSRLoader::UnlockTrophy(u32 id, u64 timestamp1, u64 timestamp2) { if (id >= m_table6.size()) { + LOG_WARNING(LOADER, "TROPUSRLoader::UnlockTrophy: Invalid id=%d", id); return false; } diff --git a/rpcs3/Loader/TROPUSR.h b/rpcs3/Loader/TROPUSR.h index d69da9af34..5e5b037b52 100644 --- a/rpcs3/Loader/TROPUSR.h +++ b/rpcs3/Loader/TROPUSR.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once struct TROPUSRHeader { @@ -30,7 +30,7 @@ struct TROPUSREntry4 // Entry Contents be_t trophy_id; // Trophy ID be_t trophy_grade; // This seems interesting - be_t unk5; // Seems to be FF FF FF FF + be_t trophy_pid; // (Assuming that this is the platinum link id) FF FF FF FF (-1) = SCE_NP_TROPHY_INVALID_TROPHY_ID char unk6[68]; // Just zeroes? }; @@ -51,11 +51,20 @@ struct TROPUSREntry6 be_t timestamp2; char unk6[64]; // Just zeroes? - //Note: One of the fields should hold a flag showing whether the trophy is hidden or not + // Note: One of the fields should hold a flag showing whether the trophy is hidden or not }; class TROPUSRLoader { + enum trophy_grade : u32 + { + unknown = 0, // SCE_NP_TROPHY_GRADE_UNKNOWN + platinum = 1, // SCE_NP_TROPHY_GRADE_PLATINUM + gold = 2, // SCE_NP_TROPHY_GRADE_GOLD + silver = 3, // SCE_NP_TROPHY_GRADE_SILVER + bronze = 4 // SCE_NP_TROPHY_GRADE_BRONZE + }; + fs::file m_file; TROPUSRHeader m_header{}; std::vector m_tableHeaders; @@ -75,6 +84,8 @@ public: virtual u32 GetTrophiesCount(); virtual u32 GetUnlockedTrophiesCount(); + virtual u32 GetUnlockedPlatinumID(u32 trophy_id, const std::string& config_path); + virtual u32 GetTrophyUnlockState(u32 id); virtual u64 GetTrophyTimestamp(u32 id); diff --git a/rpcs3/rpcs3qt/trophy_manager_dialog.cpp b/rpcs3/rpcs3qt/trophy_manager_dialog.cpp index a6a37f444a..696609dc91 100644 --- a/rpcs3/rpcs3qt/trophy_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/trophy_manager_dialog.cpp @@ -392,7 +392,7 @@ bool trophy_manager_dialog::LoadTrophyFolderToDB(const std::string& trop_name) const QString path = qstr(game_trophy_data->path) + "TROP" + padding + QString::number(trophy_id) + ".PNG"; if (!trophy_icon.load(path)) { - LOG_ERROR(GENERAL, "Failed to load trophy icon for trophy %n %s", trophy_id, game_trophy_data->path); + LOG_ERROR(GENERAL, "Failed to load trophy icon for trophy %d %s", trophy_id, game_trophy_data->path); } game_trophy_data->trophy_images.emplace_back(std::move(trophy_icon)); }