diff --git a/src/xenia/kernel/util/xdbf_utils.cc b/src/xenia/kernel/util/xdbf_utils.cc index 691f7d53e..5f7ff9672 100644 --- a/src/xenia/kernel/util/xdbf_utils.cc +++ b/src/xenia/kernel/util/xdbf_utils.cc @@ -249,36 +249,6 @@ std::string SpaFile::GetTitle() const { static_cast(XdbfSpaID::Title)); } -std::wstring ReadNullTermString(const wchar_t* ptr) { - std::wstring retval; - wchar_t data = xe::byte_swap(*ptr); - while (data != 0) { - retval += data; - ptr++; - data = xe::byte_swap(*ptr); - } - return retval; -} - -void ConvertGPDToXdbfAchievement(const X_XDBF_GPD_ACHIEVEMENT* src, - XdbfAchievement* dest) { - dest->id = src->id; - dest->image_id = src->image_id; - dest->gamerscore = src->gamerscore; - dest->flags = src->flags; - dest->unlock_time = src->unlock_time; - - auto* txt_ptr = reinterpret_cast(src + 1); - - dest->label = ReadNullTermString((const wchar_t*)txt_ptr); - - txt_ptr += (dest->label.length() * 2) + 2; - dest->description = ReadNullTermString((const wchar_t*)txt_ptr); - - txt_ptr += (dest->description.length() * 2) + 2; - dest->unachieved_desc = ReadNullTermString((const wchar_t*)txt_ptr); -} - bool GpdFile::GetAchievement(uint16_t id, XdbfAchievement* dest) { for (size_t i = 0; i < entries.size(); i++) { auto* entry = (XdbfEntry*)&entries[i]; @@ -291,7 +261,7 @@ bool GpdFile::GetAchievement(uint16_t id, XdbfAchievement* dest) { auto* ach_data = reinterpret_cast(entry->data.data()); - ConvertGPDToXdbfAchievement(ach_data, dest); + dest->ReadGPD(ach_data); return true; } @@ -308,6 +278,9 @@ uint32_t GpdFile::GetAchievements( static_cast(XdbfGpdSection::kAchievement)) { continue; } + if (entry->info.id == 0x100000000 || entry->info.id == 0x200000000) { + continue; // achievement sync data, ignore it + } ach_count++; @@ -316,7 +289,7 @@ uint32_t GpdFile::GetAchievements( reinterpret_cast(entry->data.data()); XdbfAchievement ach; - ConvertGPDToXdbfAchievement(ach_data, &ach); + ach.ReadGPD(ach_data); achievements->push_back(ach); } @@ -325,6 +298,52 @@ uint32_t GpdFile::GetAchievements( return ach_count; } +bool GpdFile::GetTitle(uint32_t title_id, XdbfTitlePlayed* dest) { + for (size_t i = 0; i < entries.size(); i++) { + auto* entry = (XdbfEntry*)&entries[i]; + if (entry->info.section != static_cast(XdbfGpdSection::kTitle) || + entry->info.id != title_id) { + continue; + } + + auto* title_data = + reinterpret_cast(entry->data.data()); + + dest->ReadGPD(title_data); + + return true; + } + + return false; +} + +uint32_t GpdFile::GetTitles(std::vector* titles) const { + uint32_t title_count = 0; + + for (size_t i = 0; i < entries.size(); i++) { + auto* entry = (XdbfEntry*)&entries[i]; + if (entry->info.section != static_cast(XdbfGpdSection::kTitle)) { + continue; + } + if (entry->info.id == 0x100000000 || entry->info.id == 0x200000000) { + continue; // achievement sync data, ignore it + } + + title_count++; + + if (titles) { + auto* title_data = + reinterpret_cast(entry->data.data()); + + XdbfTitlePlayed title; + title.ReadGPD(title_data); + titles->push_back(title); + } + } + + return title_count; +} + bool GpdFile::UpdateAchievement(XdbfAchievement ach) { XdbfEntry ent; ent.info.section = static_cast(XdbfGpdSection::kAchievement); @@ -363,8 +382,28 @@ bool GpdFile::UpdateAchievement(XdbfAchievement ach) { xe::copy_and_swap((wchar_t*)unach_ptr, ach.unachieved_desc.c_str(), ach.unachieved_desc.size()); - UpdateEntry(ent); - return true; + return UpdateEntry(ent); +} + +bool GpdFile::UpdateTitle(XdbfTitlePlayed title) { + XdbfEntry ent; + ent.info.section = static_cast(XdbfGpdSection::kTitle); + ent.info.id = title.title_id; + + // calculate entry size... + size_t name_len = (title.title_name.length() * 2) + 2; + + size_t est_size = sizeof(X_XDBF_GPD_TITLEPLAYED); + est_size += name_len; + + ent.data.resize(est_size); + memset(ent.data.data(), 0, est_size); + + // convert XdbfTitlePlayed to GPD title + auto* title_data = reinterpret_cast(ent.data.data()); + title.WriteGPD(title_data); + + return UpdateEntry(ent); } } // namespace util diff --git a/src/xenia/kernel/util/xdbf_utils.h b/src/xenia/kernel/util/xdbf_utils.h index 2cb0d1d38..6f9c3b321 100644 --- a/src/xenia/kernel/util/xdbf_utils.h +++ b/src/xenia/kernel/util/xdbf_utils.h @@ -132,8 +132,87 @@ struct X_XDBF_GPD_ACHIEVEMENT { // wchar_t* unlocked_description; }; +// from https://github.com/xemio/testdev/blob/master/xkelib/xam/_xamext.h +struct X_XDBF_GPD_TITLEPLAYED { + xe::be title_id; + xe::be achievements_possible; + xe::be achievements_earned; + xe::be gamerscore_total; + xe::be gamerscore_earned; + xe::be reserved_achievement_count; + + // the following are meant to be split into possible/earned, 1 byte each + // but who cares + xe::be all_avatar_awards; + xe::be male_avatar_awards; + xe::be female_avatar_awards; + xe::be reserved_flags; + xe::be last_loaded; + // wchar_t* title_name; +}; #pragma pack(pop) +inline std::wstring ReadNullTermString(const wchar_t* ptr) { + std::wstring retval; + wchar_t data = xe::byte_swap(*ptr); + while (data != 0) { + retval += data; + ptr++; + data = xe::byte_swap(*ptr); + } + return retval; +} + +struct XdbfTitlePlayed { + uint32_t title_id = 0; + std::wstring title_name; + uint32_t achievements_possible = 0; + uint32_t achievements_earned = 0; + uint32_t gamerscore_total = 0; + uint32_t gamerscore_earned = 0; + uint16_t reserved_achievement_count = 0; + uint16_t all_avatar_awards = 0; + uint16_t male_avatar_awards = 0; + uint16_t female_avatar_awards = 0; + uint32_t reserved_flags = 0; + uint64_t last_loaded = 0; + + void ReadGPD(const X_XDBF_GPD_TITLEPLAYED* src) { + title_id = src->title_id; + achievements_possible = src->achievements_possible; + achievements_earned = src->achievements_earned; + gamerscore_total = src->gamerscore_total; + gamerscore_earned = src->gamerscore_earned; + reserved_achievement_count = src->reserved_achievement_count; + all_avatar_awards = src->all_avatar_awards; + male_avatar_awards = src->male_avatar_awards; + female_avatar_awards = src->female_avatar_awards; + reserved_flags = src->reserved_flags; + last_loaded = src->last_loaded; + + auto* txt_ptr = reinterpret_cast(src + 1); + title_name = ReadNullTermString((const wchar_t*)txt_ptr); + } + + void WriteGPD(X_XDBF_GPD_TITLEPLAYED* dest) { + dest->title_id = title_id; + dest->achievements_possible = achievements_possible; + dest->achievements_earned = achievements_earned; + dest->gamerscore_total = gamerscore_total; + dest->gamerscore_earned = gamerscore_earned; + dest->reserved_achievement_count = reserved_achievement_count; + dest->all_avatar_awards = all_avatar_awards; + dest->male_avatar_awards = male_avatar_awards; + dest->female_avatar_awards = female_avatar_awards; + dest->reserved_flags = reserved_flags; + dest->last_loaded = last_loaded; + + auto* txt_ptr = reinterpret_cast(dest + 1); + xe::copy_and_swap((wchar_t*)txt_ptr, title_name.c_str(), + title_name.size()); + } +}; + enum class XdbfAchievementType : uint32_t { kCompletion = 1, kLeveling = 2, @@ -188,6 +267,24 @@ struct XdbfAchievement { flags & ~(static_cast(XdbfAchievementFlags::kAchievedOnline)); unlock_time = 0; } + + void ReadGPD(const X_XDBF_GPD_ACHIEVEMENT* src) { + id = src->id; + image_id = src->image_id; + gamerscore = src->gamerscore; + flags = src->flags; + unlock_time = src->unlock_time; + + auto* txt_ptr = reinterpret_cast(src + 1); + + label = ReadNullTermString((const wchar_t*)txt_ptr); + + txt_ptr += (label.length() * 2) + 2; + description = ReadNullTermString((const wchar_t*)txt_ptr); + + txt_ptr += (description.length() * 2) + 2; + unachieved_desc = ReadNullTermString((const wchar_t*)txt_ptr); + } }; struct XdbfEntry { @@ -235,8 +332,14 @@ class GpdFile : public XdbfFile { bool GetAchievement(uint16_t id, XdbfAchievement* dest); uint32_t GetAchievements(std::vector* achievements) const; + bool GetTitle(uint32_t title_id, XdbfTitlePlayed* title); + uint32_t GetTitles(std::vector* titles) const; + // Updates (or adds) an achievement bool UpdateAchievement(XdbfAchievement ach); + + // Updates (or adds) a title + bool UpdateTitle(XdbfTitlePlayed title); }; } // namespace util