[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:
parent
faeddbd34d
commit
04fcdeb24d
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue