[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.
This commit is contained in:
emoose 2018-11-16 08:59:50 +00:00
parent faeddbd34d
commit 04fcdeb24d
No known key found for this signature in database
GPG Key ID: 3735C67912F5FF97
5 changed files with 99 additions and 25 deletions

View File

@ -283,7 +283,9 @@ bool GpdFile::GetAchievement(uint16_t id, XdbfAchievement* dest) {
auto* ach_data = auto* ach_data =
reinterpret_cast<const X_XDBF_GPD_ACHIEVEMENT*>(entry->data.data()); reinterpret_cast<const X_XDBF_GPD_ACHIEVEMENT*>(entry->data.data());
dest->ReadGPD(ach_data); if (dest) {
dest->ReadGPD(ach_data);
}
return true; return true;
} }

View File

@ -13,6 +13,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "xenia/base/clock.h"
#include "xenia/base/memory.h" #include "xenia/base/memory.h"
namespace xe { namespace xe {
@ -165,7 +166,7 @@ struct X_XDBF_GPD_TITLEPLAYED {
xe::be<uint16_t> male_avatar_awards; xe::be<uint16_t> male_avatar_awards;
xe::be<uint16_t> female_avatar_awards; xe::be<uint16_t> female_avatar_awards;
xe::be<uint32_t> reserved_flags; xe::be<uint32_t> reserved_flags;
xe::be<uint64_t> last_loaded; xe::be<uint64_t> last_played;
// wchar_t* title_name; // wchar_t* title_name;
}; };
#pragma pack(pop) #pragma pack(pop)
@ -193,7 +194,7 @@ struct XdbfTitlePlayed {
uint16_t male_avatar_awards = 0; uint16_t male_avatar_awards = 0;
uint16_t female_avatar_awards = 0; uint16_t female_avatar_awards = 0;
uint32_t reserved_flags = 0; uint32_t reserved_flags = 0;
uint64_t last_loaded = 0; uint64_t last_played = 0;
void ReadGPD(const X_XDBF_GPD_TITLEPLAYED* src) { void ReadGPD(const X_XDBF_GPD_TITLEPLAYED* src) {
title_id = src->title_id; title_id = src->title_id;
@ -206,7 +207,7 @@ struct XdbfTitlePlayed {
male_avatar_awards = src->male_avatar_awards; male_avatar_awards = src->male_avatar_awards;
female_avatar_awards = src->female_avatar_awards; female_avatar_awards = src->female_avatar_awards;
reserved_flags = src->reserved_flags; reserved_flags = src->reserved_flags;
last_loaded = src->last_loaded; last_played = src->last_played;
auto* txt_ptr = reinterpret_cast<const uint8_t*>(src + 1); auto* txt_ptr = reinterpret_cast<const uint8_t*>(src + 1);
title_name = ReadNullTermString((const wchar_t*)txt_ptr); title_name = ReadNullTermString((const wchar_t*)txt_ptr);
@ -223,7 +224,7 @@ struct XdbfTitlePlayed {
dest->male_avatar_awards = male_avatar_awards; dest->male_avatar_awards = male_avatar_awards;
dest->female_avatar_awards = female_avatar_awards; dest->female_avatar_awards = female_avatar_awards;
dest->reserved_flags = reserved_flags; dest->reserved_flags = reserved_flags;
dest->last_loaded = last_loaded; dest->last_played = last_played;
auto* txt_ptr = reinterpret_cast<const uint8_t*>(dest + 1); auto* txt_ptr = reinterpret_cast<const uint8_t*>(dest + 1);
xe::copy_and_swap<wchar_t>((wchar_t*)txt_ptr, title_name.c_str(), xe::copy_and_swap<wchar_t>((wchar_t*)txt_ptr, title_name.c_str(),
@ -276,7 +277,8 @@ struct XdbfAchievement {
if (online) { if (online) {
flags |= static_cast<uint32_t>(XdbfAchievementFlags::kAchievedOnline); flags |= static_cast<uint32_t>(XdbfAchievementFlags::kAchievedOnline);
} }
// TODO: set unlock time?
unlock_time = Clock::QueryHostSystemTime();
} }
void Lock() { void Lock() {

View File

@ -83,7 +83,7 @@ X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
} }
} }
if (modified) { if (modified) {
kernel_state_->user_profile()->UpdateGpdFiles(); kernel_state_->user_profile()->UpdateTitleGpd();
} }
return X_ERROR_SUCCESS; return X_ERROR_SUCCESS;

View File

@ -12,6 +12,7 @@
#include "xenia/kernel/kernel_state.h" #include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/util/shim_utils.h"
#include "xenia/kernel/xam/user_profile.h" #include "xenia/kernel/xam/user_profile.h"
#include "xenia/base/clock.h"
#include "xenia/base/filesystem.h" #include "xenia/base/filesystem.h"
#include "xenia/base/logging.h" #include "xenia/base/logging.h"
#include "xenia/base/mapped_memory.h" #include "xenia/base/mapped_memory.h"
@ -99,7 +100,8 @@ void UserProfile::LoadGpdFiles() {
auto mmap_ = auto mmap_ =
MappedMemory::Open(L"profile\\FFFE07D1.gpd", MappedMemory::Mode::kRead); MappedMemory::Open(L"profile\\FFFE07D1.gpd", MappedMemory::Mode::kRead);
if (!mmap_) { if (!mmap_) {
XELOGW("Dash GPD not found, using blank one"); XELOGW(
"Failed to open dash GPD (FFFE07D1.gpd) for reading, using blank one");
return; return;
} }
@ -114,7 +116,7 @@ void UserProfile::LoadGpdFiles() {
_swprintf(fname, L"profile\\%X.gpd", title.title_id); _swprintf(fname, L"profile\\%X.gpd", title.title_id);
mmap_ = MappedMemory::Open(fname, MappedMemory::Mode::kRead); mmap_ = MappedMemory::Open(fname, MappedMemory::Mode::kRead);
if (!mmap_) { 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()); title.title_name.c_str());
continue; continue;
} }
@ -130,19 +132,73 @@ void UserProfile::LoadGpdFiles() {
util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) {
uint32_t spa_title = spa_data.GetTitleId(); uint32_t spa_title = spa_data.GetTitleId();
auto gpd = title_gpds_.find(spa_title); std::vector<util::XdbfAchievement> 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! // GPD not found... have to create it!
XELOGD("Creating new GPD for title %X", spa_title); 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_name = xe::to_wstring(spa_data.GetTitleName());
title_info.title_id = spa_title; title_info.title_id = spa_title;
title_info.last_played = Clock::QueryHostSystemTime();
std::vector<util::XdbfAchievement> spa_achievements;
// TODO: let user choose locale?
spa_data.GetAchievements(spa_data.GetDefaultLocale(), &spa_achievements);
// Copy cheevos from SPA -> GPD // Copy cheevos from SPA -> GPD
util::GpdFile title_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 = auto* title_image =
spa_data.GetEntry(static_cast<uint16_t>(util::XdbfSpaSection::kImage), spa_data.GetEntry(static_cast<uint16_t>(util::XdbfSpaSection::kImage),
static_cast<uint64_t>(util::XdbfSpaID::Title)); static_cast<uint64_t>(util::XdbfSpaID::Title));
@ -191,21 +247,33 @@ util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) {
UpdateGpd(kDashboardID, dash_gpd_); 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_gpd_ = &title_gpds_[spa_title];
curr_title_id_ = spa_title;
return curr_gpd_; 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? // TODO: optimize so we only have to update the current title?
for (const auto& pair : title_gpds_) { for (const auto& pair : title_gpds_) {
auto gpd = pair.second; auto gpd = pair.second;
bool result = UpdateGpd(pair.first, gpd); bool result = UpdateGpd(pair.first, gpd);
if (!result) { if (!result) {
XELOGE("UpdateGpdFiles failed on title %X...", pair.first); XELOGE("UpdateGpdFiles failed on title %X!", pair.first);
return false; continue;
} }
} }
@ -217,7 +285,7 @@ bool UserProfile::UpdateGpdFiles() {
bool UserProfile::UpdateGpd(uint32_t title_id, util::GpdFile& gpd_data) { bool UserProfile::UpdateGpd(uint32_t title_id, util::GpdFile& gpd_data) {
size_t gpd_length = 0; size_t gpd_length = 0;
if (!gpd_data.Write(nullptr, &gpd_length)) { 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; return false;
} }

View File

@ -210,7 +210,8 @@ class UserProfile {
util::GpdFile* SetTitleSpaData(const util::SpaFile& spa_data); util::GpdFile* SetTitleSpaData(const util::SpaFile& spa_data);
util::GpdFile* GetTitleGpd() { return curr_gpd_; } util::GpdFile* GetTitleGpd() { return curr_gpd_; }
bool UpdateGpdFiles(); bool UpdateTitleGpd();
bool UpdateAllGpds();
private: private:
void LoadGpdFiles(); void LoadGpdFiles();
@ -227,6 +228,7 @@ class UserProfile {
std::unordered_map<uint32_t, util::GpdFile> title_gpds_; std::unordered_map<uint32_t, util::GpdFile> title_gpds_;
util::GpdFile dash_gpd_; util::GpdFile dash_gpd_;
util::GpdFile* curr_gpd_ = nullptr; util::GpdFile* curr_gpd_ = nullptr;
uint32_t curr_title_id_ = -1;
}; };
} // namespace xam } // namespace xam