[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 =
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue