From 04fcdeb24dd1a0a7e0b0e584ffbbdfcb2c44ca84 Mon Sep 17 00:00:00 2001 From: emoose Date: Fri, 16 Nov 2018 08:59:50 +0000 Subject: [PATCH] [Kernel] GPD: Set unlock/played times, add missing SPA achievements to existing GPDs... For example if a user applies a TU that adds achievements after already running the non-patched game, this should add any new achievements it finds. Will also update the last_played time of a title when the SPA gets loaded in (on game launch), and also set the unlock_time when unlocking achievements. Unlocking also only updates the title GPD (+ dash GPD) now, instead of needing to rewrite every GPD. --- src/xenia/kernel/util/xdbf_utils.cc | 4 +- src/xenia/kernel/util/xdbf_utils.h | 12 ++-- src/xenia/kernel/xam/apps/xgi_app.cc | 2 +- src/xenia/kernel/xam/user_profile.cc | 102 ++++++++++++++++++++++----- src/xenia/kernel/xam/user_profile.h | 4 +- 5 files changed, 99 insertions(+), 25 deletions(-) diff --git a/src/xenia/kernel/util/xdbf_utils.cc b/src/xenia/kernel/util/xdbf_utils.cc index 488eb1641..e8aeb2101 100644 --- a/src/xenia/kernel/util/xdbf_utils.cc +++ b/src/xenia/kernel/util/xdbf_utils.cc @@ -283,7 +283,9 @@ bool GpdFile::GetAchievement(uint16_t id, XdbfAchievement* dest) { auto* ach_data = reinterpret_cast(entry->data.data()); - dest->ReadGPD(ach_data); + if (dest) { + dest->ReadGPD(ach_data); + } return true; } diff --git a/src/xenia/kernel/util/xdbf_utils.h b/src/xenia/kernel/util/xdbf_utils.h index 7640f1186..562d8bfa4 100644 --- a/src/xenia/kernel/util/xdbf_utils.h +++ b/src/xenia/kernel/util/xdbf_utils.h @@ -13,6 +13,7 @@ #include #include +#include "xenia/base/clock.h" #include "xenia/base/memory.h" namespace xe { @@ -165,7 +166,7 @@ struct X_XDBF_GPD_TITLEPLAYED { xe::be male_avatar_awards; xe::be female_avatar_awards; xe::be reserved_flags; - xe::be last_loaded; + xe::be last_played; // wchar_t* title_name; }; #pragma pack(pop) @@ -193,7 +194,7 @@ struct XdbfTitlePlayed { uint16_t male_avatar_awards = 0; uint16_t female_avatar_awards = 0; uint32_t reserved_flags = 0; - uint64_t last_loaded = 0; + uint64_t last_played = 0; void ReadGPD(const X_XDBF_GPD_TITLEPLAYED* src) { title_id = src->title_id; @@ -206,7 +207,7 @@ struct XdbfTitlePlayed { male_avatar_awards = src->male_avatar_awards; female_avatar_awards = src->female_avatar_awards; reserved_flags = src->reserved_flags; - last_loaded = src->last_loaded; + last_played = src->last_played; auto* txt_ptr = reinterpret_cast(src + 1); title_name = ReadNullTermString((const wchar_t*)txt_ptr); @@ -223,7 +224,7 @@ struct XdbfTitlePlayed { dest->male_avatar_awards = male_avatar_awards; dest->female_avatar_awards = female_avatar_awards; dest->reserved_flags = reserved_flags; - dest->last_loaded = last_loaded; + dest->last_played = last_played; auto* txt_ptr = reinterpret_cast(dest + 1); xe::copy_and_swap((wchar_t*)txt_ptr, title_name.c_str(), @@ -276,7 +277,8 @@ struct XdbfAchievement { if (online) { flags |= static_cast(XdbfAchievementFlags::kAchievedOnline); } - // TODO: set unlock time? + + unlock_time = Clock::QueryHostSystemTime(); } void Lock() { diff --git a/src/xenia/kernel/xam/apps/xgi_app.cc b/src/xenia/kernel/xam/apps/xgi_app.cc index 8873715f1..f0058d1c0 100644 --- a/src/xenia/kernel/xam/apps/xgi_app.cc +++ b/src/xenia/kernel/xam/apps/xgi_app.cc @@ -83,7 +83,7 @@ X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr, } } if (modified) { - kernel_state_->user_profile()->UpdateGpdFiles(); + kernel_state_->user_profile()->UpdateTitleGpd(); } return X_ERROR_SUCCESS; diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index 0907c2008..55d4c434f 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -12,6 +12,7 @@ #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xam/user_profile.h" +#include "xenia/base/clock.h" #include "xenia/base/filesystem.h" #include "xenia/base/logging.h" #include "xenia/base/mapped_memory.h" @@ -99,7 +100,8 @@ void UserProfile::LoadGpdFiles() { auto mmap_ = MappedMemory::Open(L"profile\\FFFE07D1.gpd", MappedMemory::Mode::kRead); if (!mmap_) { - XELOGW("Dash GPD not found, using blank one"); + XELOGW( + "Failed to open dash GPD (FFFE07D1.gpd) for reading, using blank one"); return; } @@ -114,7 +116,7 @@ void UserProfile::LoadGpdFiles() { _swprintf(fname, L"profile\\%X.gpd", title.title_id); mmap_ = MappedMemory::Open(fname, MappedMemory::Mode::kRead); if (!mmap_) { - XELOGE("GPD for title %X (%ws) not found!", title.title_id, + XELOGE("Failed to open GPD for title %X (%ws)!", title.title_id, title.title_name.c_str()); continue; } @@ -130,19 +132,73 @@ void UserProfile::LoadGpdFiles() { util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { uint32_t spa_title = spa_data.GetTitleId(); - auto gpd = title_gpds_.find(spa_title); + std::vector spa_achievements; + // TODO: let user choose locale? + spa_data.GetAchievements(spa_data.GetDefaultLocale(), &spa_achievements); - if (gpd == title_gpds_.end()) { + util::XdbfTitlePlayed title_info; + + auto gpd = title_gpds_.find(spa_title); + if (gpd != title_gpds_.end()) { + auto& title_gpd = (*gpd).second; + + bool always_update_title = false; + if (!dash_gpd_.GetTitle(spa_title, &title_info)) { + assert_always(); + XELOGE( + "GPD exists but is missing XbdfTitlePlayed entry? (this shouldn't be " + "happening!)"); + // Try to work around it... + title_info.title_name = xe::to_wstring(spa_data.GetTitleName()); + title_info.title_id = spa_title; + title_info.achievements_possible = 0; + title_info.achievements_earned = 0; + title_info.gamerscore_total = 0; + title_info.gamerscore_earned = 0; + always_update_title = true; + } + title_info.last_played = Clock::QueryHostSystemTime(); + + // Check SPA for any achievements current GPD might be missing + // (maybe added in TUs etc?) + bool ach_updated = false; + for (auto ach : spa_achievements) { + bool ach_exists = title_gpd.GetAchievement(ach.id, nullptr); + if (ach_exists && !always_update_title) { + continue; + } + + // Achievement doesn't exist in current title info, lets add it + title_info.achievements_possible++; + title_info.gamerscore_total += ach.gamerscore; + + // If it doesn't exist in GPD, add it to that too + if (!ach_exists) { + XELOGD( + "Adding new achievement %d (%ws) from SPA (wasn't inside existing " + "GPD)", + ach.id, ach.label.c_str()); + + ach_updated = true; + title_gpd.UpdateAchievement(ach); + } + } + + // Update dash with new title_info + dash_gpd_.UpdateTitle(title_info); + + // Only write game GPD if achievements were updated + if (ach_updated) { + UpdateGpd(spa_title, title_gpd); + } + UpdateGpd(kDashboardID, dash_gpd_); + } else { // GPD not found... have to create it! XELOGD("Creating new GPD for title %X", spa_title); - util::XdbfTitlePlayed title_info; title_info.title_name = xe::to_wstring(spa_data.GetTitleName()); title_info.title_id = spa_title; - - std::vector spa_achievements; - // TODO: let user choose locale? - spa_data.GetAchievements(spa_data.GetDefaultLocale(), &spa_achievements); + title_info.last_played = Clock::QueryHostSystemTime(); // Copy cheevos from SPA -> GPD util::GpdFile title_gpd; @@ -162,7 +218,7 @@ util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { } } - // try adding title image & name + // Try adding title image & name auto* title_image = spa_data.GetEntry(static_cast(util::XdbfSpaSection::kImage), static_cast(util::XdbfSpaID::Title)); @@ -191,21 +247,33 @@ util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { UpdateGpd(kDashboardID, dash_gpd_); } - // TODO: check SPA for any achievements current GPD might be missing - // (maybe added in TUs etc?) - curr_gpd_ = &title_gpds_[spa_title]; + curr_title_id_ = spa_title; return curr_gpd_; } -bool UserProfile::UpdateGpdFiles() { +bool UserProfile::UpdateTitleGpd() { + if (!curr_gpd_ || curr_title_id_ == -1) { + return false; + } + + bool result = UpdateGpd(curr_title_id_, *curr_gpd_); + if (!result) { + XELOGE("UpdateTitleGpd failed on title %X!", curr_title_id_); + } else { + XELOGD("Updated title %X GPD successfully!", curr_title_id_); + } + return result; +} + +bool UserProfile::UpdateAllGpds() { // TODO: optimize so we only have to update the current title? for (const auto& pair : title_gpds_) { auto gpd = pair.second; bool result = UpdateGpd(pair.first, gpd); if (!result) { - XELOGE("UpdateGpdFiles failed on title %X...", pair.first); - return false; + XELOGE("UpdateGpdFiles failed on title %X!", pair.first); + continue; } } @@ -217,7 +285,7 @@ bool UserProfile::UpdateGpdFiles() { bool UserProfile::UpdateGpd(uint32_t title_id, util::GpdFile& gpd_data) { size_t gpd_length = 0; if (!gpd_data.Write(nullptr, &gpd_length)) { - XELOGE("Failed to get GPD size for %X!", title_id); + XELOGE("Failed to get GPD size for title %X!", title_id); return false; } diff --git a/src/xenia/kernel/xam/user_profile.h b/src/xenia/kernel/xam/user_profile.h index e6223fe34..02d9dcb50 100644 --- a/src/xenia/kernel/xam/user_profile.h +++ b/src/xenia/kernel/xam/user_profile.h @@ -210,7 +210,8 @@ class UserProfile { util::GpdFile* SetTitleSpaData(const util::SpaFile& spa_data); util::GpdFile* GetTitleGpd() { return curr_gpd_; } - bool UpdateGpdFiles(); + bool UpdateTitleGpd(); + bool UpdateAllGpds(); private: void LoadGpdFiles(); @@ -227,6 +228,7 @@ class UserProfile { std::unordered_map title_gpds_; util::GpdFile dash_gpd_; util::GpdFile* curr_gpd_ = nullptr; + uint32_t curr_title_id_ = -1; }; } // namespace xam