[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 =
reinterpret_cast<const X_XDBF_GPD_ACHIEVEMENT*>(entry->data.data());
dest->ReadGPD(ach_data);
if (dest) {
dest->ReadGPD(ach_data);
}
return true;
}

View File

@ -13,6 +13,7 @@
#include <string>
#include <vector>
#include "xenia/base/clock.h"
#include "xenia/base/memory.h"
namespace xe {
@ -165,7 +166,7 @@ struct X_XDBF_GPD_TITLEPLAYED {
xe::be<uint16_t> male_avatar_awards;
xe::be<uint16_t> female_avatar_awards;
xe::be<uint32_t> reserved_flags;
xe::be<uint64_t> last_loaded;
xe::be<uint64_t> 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<const uint8_t*>(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<const uint8_t*>(dest + 1);
xe::copy_and_swap<wchar_t>((wchar_t*)txt_ptr, title_name.c_str(),
@ -276,7 +277,8 @@ struct XdbfAchievement {
if (online) {
flags |= static_cast<uint32_t>(XdbfAchievementFlags::kAchievedOnline);
}
// TODO: set unlock time?
unlock_time = Clock::QueryHostSystemTime();
}
void Lock() {

View File

@ -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;

View File

@ -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<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!
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<util::XdbfAchievement> 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<uint16_t>(util::XdbfSpaSection::kImage),
static_cast<uint64_t>(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;
}

View File

@ -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<uint32_t, util::GpdFile> title_gpds_;
util::GpdFile dash_gpd_;
util::GpdFile* curr_gpd_ = nullptr;
uint32_t curr_title_id_ = -1;
};
} // namespace xam