From 52984280c372498588d5c162ae4370b88f6efd00 Mon Sep 17 00:00:00 2001 From: emoose Date: Fri, 16 Nov 2018 01:51:54 +0000 Subject: [PATCH 01/21] [Kernel] Add achievement stuff to XdbfWrapper, convert it to a full XDBF parser... XdbfWrapper would only wrap existing in-memory data, making modifying data (or creating new data) non-trivial. The new XdbfFile class parses all the XDBF stuff into different vectors, making modifications a lot easier. GpdFile and SpaFile are both children of XdbfWrapper, SpaFile has the same functions as the older XdbfGameData class, but now includes support for parsing achievements too. The GpdFile class also supports achievements, and both classes parse into a common XdbfAchievement struct, which makes it simple to eg. copy achievements from a SPA file into a new GPD file. TODO: - images? - GPD settings (user_profile has some code for this, not sure how much we can use here though...) - modify user_profile to make use of this --- src/xenia/kernel/util/xdbf_utils.cc | 367 ++++++++++++++++++++++++---- src/xenia/kernel/util/xdbf_utils.h | 278 ++++++++++++++------- 2 files changed, 505 insertions(+), 140 deletions(-) diff --git a/src/xenia/kernel/util/xdbf_utils.cc b/src/xenia/kernel/util/xdbf_utils.cc index e79b15449..691f7d53e 100644 --- a/src/xenia/kernel/util/xdbf_utils.cc +++ b/src/xenia/kernel/util/xdbf_utils.cc @@ -8,68 +8,139 @@ */ #include "xenia/kernel/util/xdbf_utils.h" +#include "xenia/base/string.h" namespace xe { namespace kernel { namespace util { constexpr uint32_t kXdbfMagicXdbf = 'XDBF'; -constexpr uint32_t kXdbfMagicXstc = 'XSTC'; -constexpr uint32_t kXdbfMagicXstr = 'XSTR'; -XdbfWrapper::XdbfWrapper(const uint8_t* data, size_t data_size) - : data_(data), data_size_(data_size) { - if (!data || data_size <= sizeof(XbdfHeader)) { - data_ = nullptr; - return; +bool XdbfFile::Read(const uint8_t* data, size_t data_size) { + if (!data || data_size <= sizeof(X_XDBF_HEADER)) { + return false; } - const uint8_t* ptr = data_; - - header_ = reinterpret_cast(ptr); - ptr += sizeof(XbdfHeader); - if (header_->magic != kXdbfMagicXdbf) { - data_ = nullptr; - return; + auto* ptr = data; + memcpy(&header, ptr, sizeof(X_XDBF_HEADER)); + if (header.magic != kXdbfMagicXdbf) { + return false; } - entries_ = reinterpret_cast(ptr); - ptr += sizeof(XbdfEntry) * header_->entry_count; + ptr += sizeof(X_XDBF_HEADER); - files_ = reinterpret_cast(ptr); - ptr += sizeof(XbdfFileLoc) * header_->free_count; + auto* free_ptr = (const X_XDBF_FILELOC*)(ptr + (sizeof(X_XDBF_ENTRY) * + header.entry_count)); + auto* data_ptr = + (uint8_t*)free_ptr + (sizeof(X_XDBF_FILELOC) * header.free_count); - content_offset_ = ptr; + for (uint32_t i = 0; i < header.entry_used; i++) { + XdbfEntry entry; + memcpy(&entry.info, ptr, sizeof(X_XDBF_ENTRY)); + entry.data.resize(entry.info.size); + memcpy(entry.data.data(), data_ptr + entry.info.offset, entry.info.size); + entries.push_back(entry); + + ptr += sizeof(X_XDBF_ENTRY); + } + + for (uint32_t i = 0; i < header.free_used; i++) { + free_entries.push_back(*free_ptr); + free_ptr++; + } + + return true; } -XdbfBlock XdbfWrapper::GetEntry(XdbfSection section, uint64_t id) const { - for (uint32_t i = 0; i < header_->entry_used; ++i) { - auto& entry = entries_[i]; - if (entry.section == static_cast(section) && entry.id == id) { - XdbfBlock block; - block.buffer = content_offset_ + entry.offset; - block.size = entry.size; - return block; +bool XdbfFile::Write(uint8_t* data, size_t* data_size) { + *data_size = 0; + + *data_size += sizeof(X_XDBF_HEADER); + *data_size += entries.size() * sizeof(X_XDBF_ENTRY); + *data_size += free_entries.size() * sizeof(X_XDBF_FILELOC); + + size_t entries_size = 0; + for (auto ent : entries) { + entries_size += ent.data.size(); + } + + *data_size += entries_size; + + if (!data) { + return true; + } + + header.entry_count = header.entry_used = (uint32_t)entries.size(); + header.free_count = header.free_used = (uint32_t)free_entries.size(); + + auto* ptr = data; + memcpy(ptr, &header, sizeof(X_XDBF_HEADER)); + ptr += sizeof(X_XDBF_HEADER); + + auto* free_ptr = + (X_XDBF_FILELOC*)(ptr + (sizeof(X_XDBF_ENTRY) * header.entry_count)); + auto* data_start = + (uint8_t*)free_ptr + (sizeof(X_XDBF_FILELOC) * header.free_count); + + auto* data_ptr = data_start; + for (auto ent : entries) { + ent.info.offset = (uint32_t)(data_ptr - data_start); + ent.info.size = (uint32_t)ent.data.size(); + memcpy(ptr, &ent.info, sizeof(X_XDBF_ENTRY)); + + memcpy(data_ptr, ent.data.data(), ent.data.size()); + data_ptr += ent.data.size(); + ptr += sizeof(X_XDBF_ENTRY); + } + + for (auto ent : free_entries) { + memcpy(free_ptr, &ent, sizeof(X_XDBF_FILELOC)); + free_ptr++; + } + + return true; +} + +XdbfEntry* XdbfFile::GetEntry(uint16_t section, uint64_t id) const { + for (size_t i = 0; i < entries.size(); i++) { + auto* entry = (XdbfEntry*)&entries[i]; + if (entry->info.section != section || entry->info.id != id) { + continue; } + + return entry; } - return {0}; + + return nullptr; } -std::string XdbfWrapper::GetStringTableEntry(XdbfLocale locale, - uint16_t string_id) const { - auto language_block = - GetEntry(XdbfSection::kStringTable, static_cast(locale)); - if (!language_block) { - return ""; +bool XdbfFile::UpdateEntry(XdbfEntry entry) { + for (size_t i = 0; i < entries.size(); i++) { + auto* ent = (XdbfEntry*)&entries[i]; + if (ent->info.section != entry.info.section || + ent->info.id != entry.info.id) { + continue; + } + + ent->data = entry.data; + ent->info.size = (uint32_t)entry.data.size(); + return true; } - auto xstr_head = - reinterpret_cast(language_block.buffer); - assert_true(xstr_head->magic == kXdbfMagicXstr); - assert_true(xstr_head->version == 1); + XdbfEntry new_entry; + new_entry.info.section = entry.info.section; + new_entry.info.id = entry.info.id; + new_entry.info.size = (uint32_t)entry.data.size(); + new_entry.data = entry.data; - const uint8_t* ptr = language_block.buffer + sizeof(XdbfXstrHeader); - for (uint16_t i = 0; i < xstr_head->string_count; ++i) { + entries.push_back(new_entry); + return true; +} + +std::string GetStringTableEntry_(const uint8_t* table_start, uint16_t string_id, + uint16_t count) { + auto* ptr = table_start; + for (uint16_t i = 0; i < count; ++i) { auto entry = reinterpret_cast(ptr); ptr += sizeof(XdbfStringTableEntry); if (entry->id == string_id) { @@ -81,25 +152,219 @@ std::string XdbfWrapper::GetStringTableEntry(XdbfLocale locale, return ""; } -constexpr uint64_t kXdbfIdTitle = 0x8000; -constexpr uint64_t kXdbfIdXstc = 0x58535443; +std::string SpaFile::GetStringTableEntry(XdbfLocale locale, + uint16_t string_id) const { + auto xstr_table = + GetEntry(static_cast(XdbfSpaSection::kStringTable), + static_cast(locale)); + if (!xstr_table) { + return ""; + } -XdbfBlock XdbfGameData::icon() const { - return GetEntry(XdbfSection::kImage, kXdbfIdTitle); + auto xstr_head = + reinterpret_cast(xstr_table->data.data()); + assert_true(xstr_head->magic == static_cast(XdbfSpaID::Xstr)); + assert_true(xstr_head->version == 1); + + const uint8_t* ptr = xstr_table->data.data() + sizeof(X_XDBF_TABLE_HEADER); + + return GetStringTableEntry_(ptr, string_id, xstr_head->count); } -XdbfLocale XdbfGameData::default_language() const { - auto block = GetEntry(XdbfSection::kMetadata, kXdbfIdXstc); - if (!block.buffer) { +uint32_t SpaFile::GetAchievements( + XdbfLocale locale, std::vector* achievements) const { + auto xach_table = GetEntry(static_cast(XdbfSpaSection::kMetadata), + static_cast(XdbfSpaID::Xach)); + if (!xach_table) { + return 0; + } + + auto xach_head = + reinterpret_cast(xach_table->data.data()); + assert_true(xach_head->magic == static_cast(XdbfSpaID::Xach)); + assert_true(xach_head->version == 1); + + auto xstr_table = + GetEntry(static_cast(XdbfSpaSection::kStringTable), + static_cast(locale)); + if (!xstr_table) { + return 0; + } + + auto xstr_head = + reinterpret_cast(xstr_table->data.data()); + assert_true(xstr_head->magic == static_cast(XdbfSpaID::Xstr)); + assert_true(xstr_head->version == 1); + + const uint8_t* xstr_ptr = + xstr_table->data.data() + sizeof(X_XDBF_TABLE_HEADER); + + if (achievements) { + auto* ach_data = + reinterpret_cast(xach_head + 1); + for (uint32_t i = 0; i < xach_head->count; i++) { + XdbfAchievement ach; + ach.id = ach_data->id; + ach.image_id = ach_data->image_id; + ach.gamerscore = ach_data->gamerscore; + ach.flags = ach_data->flags; + + ach.label = xe::to_wstring( + GetStringTableEntry_(xstr_ptr, ach_data->label_id, xstr_head->count)); + + ach.description = xe::to_wstring(GetStringTableEntry_( + xstr_ptr, ach_data->description_id, xstr_head->count)); + + ach.unachieved_desc = xe::to_wstring(GetStringTableEntry_( + xstr_ptr, ach_data->unachieved_id, xstr_head->count)); + + achievements->push_back(ach); + ach_data++; + } + } + + return xach_head->count; +} + +XdbfEntry* SpaFile::GetIcon() const { + return GetEntry(static_cast(XdbfSpaSection::kImage), + static_cast(XdbfSpaID::Title)); +} + +XdbfLocale SpaFile::GetDefaultLocale() const { + auto block = GetEntry(static_cast(XdbfSpaSection::kMetadata), + static_cast(XdbfSpaID::Xstc)); + if (!block) { return XdbfLocale::kEnglish; } - auto xstc = reinterpret_cast(block.buffer); - assert_true(xstc->magic == kXdbfMagicXstc); + + auto xstc = reinterpret_cast(block->data.data()); + assert_true(xstc->magic == static_cast(XdbfSpaID::Xstc)); + return static_cast(static_cast(xstc->default_language)); } -std::string XdbfGameData::title() const { - return GetStringTableEntry(default_language(), kXdbfIdTitle); +std::string SpaFile::GetTitle() const { + return GetStringTableEntry(GetDefaultLocale(), + 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]; + if (entry->info.section != + static_cast(XdbfGpdSection::kAchievement) || + entry->info.id != id) { + continue; + } + + auto* ach_data = + reinterpret_cast(entry->data.data()); + + ConvertGPDToXdbfAchievement(ach_data, dest); + return true; + } + + return false; +} + +uint32_t GpdFile::GetAchievements( + std::vector* achievements) const { + uint32_t ach_count = 0; + + for (size_t i = 0; i < entries.size(); i++) { + auto* entry = (XdbfEntry*)&entries[i]; + if (entry->info.section != + static_cast(XdbfGpdSection::kAchievement)) { + continue; + } + + ach_count++; + + if (achievements) { + auto* ach_data = + reinterpret_cast(entry->data.data()); + + XdbfAchievement ach; + ConvertGPDToXdbfAchievement(ach_data, &ach); + + achievements->push_back(ach); + } + } + + return ach_count; +} + +bool GpdFile::UpdateAchievement(XdbfAchievement ach) { + XdbfEntry ent; + ent.info.section = static_cast(XdbfGpdSection::kAchievement); + ent.info.id = ach.id; + + // calculate entry size... + size_t label_len = (ach.label.length() * 2) + 2; + size_t desc_len = (ach.description.length() * 2) + 2; + size_t unach_len = (ach.unachieved_desc.length() * 2) + 2; + + size_t est_size = sizeof(X_XDBF_GPD_ACHIEVEMENT); + est_size += label_len; + est_size += desc_len; + est_size += unach_len; + + ent.data.resize(est_size); + memset(ent.data.data(), 0, est_size); + + // convert XdbfAchievement to GPD achievement + auto* ach_data = reinterpret_cast(ent.data.data()); + ach_data->id = ach.id; + ach_data->image_id = ach.image_id; + ach_data->gamerscore = ach.gamerscore; + ach_data->flags = ach.flags; + ach_data->unlock_time = ach.unlock_time; + + auto* label_ptr = reinterpret_cast(ent.data.data() + + sizeof(X_XDBF_GPD_ACHIEVEMENT)); + auto* desc_ptr = label_ptr + label_len; + auto* unach_ptr = desc_ptr + desc_len; + + xe::copy_and_swap((wchar_t*)label_ptr, ach.label.c_str(), + ach.label.size()); + xe::copy_and_swap((wchar_t*)desc_ptr, ach.description.c_str(), + ach.description.size()); + xe::copy_and_swap((wchar_t*)unach_ptr, ach.unachieved_desc.c_str(), + ach.unachieved_desc.size()); + + UpdateEntry(ent); + return true; } } // namespace util diff --git a/src/xenia/kernel/util/xdbf_utils.h b/src/xenia/kernel/util/xdbf_utils.h index 83ba12e27..2cb0d1d38 100644 --- a/src/xenia/kernel/util/xdbf_utils.h +++ b/src/xenia/kernel/util/xdbf_utils.h @@ -22,10 +22,26 @@ namespace util { // https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/XEX/SPA.h // https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/XEX/SPA.cpp -enum class XdbfSection : uint16_t { - kMetadata = 0x0001, - kImage = 0x0002, - kStringTable = 0x0003, +enum class XdbfSpaID : uint64_t { + Xach = 'XACH', + Xstr = 'XSTR', + Xstc = 'XSTC', + Title = 0x8000, +}; + +enum class XdbfSpaSection : uint16_t { + kMetadata = 0x1, + kImage = 0x2, + kStringTable = 0x3, +}; + +enum class XdbfGpdSection : uint16_t { + kAchievement = 0x1, + kImage = 0x2, + kSetting = 0x3, + kTitle = 0x4, + kString = 0x5, + kSecurity = 0x6 }; // Found by dumping the kSectionStringTable sections of various games: @@ -41,102 +57,186 @@ enum class XdbfLocale : uint32_t { kChinese = 8, }; -struct XdbfBlock { - const uint8_t* buffer; - size_t size; +struct XdbfStringTableEntry { + xe::be id; + xe::be string_length; +}; +static_assert_size(XdbfStringTableEntry, 4); - operator bool() const { return buffer != nullptr; } +#pragma pack(push, 1) +struct X_XDBF_HEADER { + xe::be magic; + xe::be version; + xe::be entry_count; + xe::be entry_used; + xe::be free_count; + xe::be free_used; +}; +static_assert_size(X_XDBF_HEADER, 24); + +struct X_XDBF_ENTRY { + xe::be section; + xe::be id; + xe::be offset; + xe::be size; +}; +static_assert_size(X_XDBF_ENTRY, 18); + +struct X_XDBF_FILELOC { + xe::be offset; + xe::be size; +}; +static_assert_size(X_XDBF_FILELOC, 8); + +struct X_XDBF_XSTC_DATA { + xe::be magic; + xe::be version; + xe::be size; + xe::be default_language; +}; +static_assert_size(X_XDBF_XSTC_DATA, 16); + +struct X_XDBF_TABLE_HEADER { + xe::be magic; + xe::be version; + xe::be size; + xe::be count; +}; +static_assert_size(X_XDBF_TABLE_HEADER, 14); + +struct X_XDBF_SPA_ACHIEVEMENT { + xe::be id; + xe::be label_id; + xe::be description_id; + xe::be unachieved_id; + xe::be image_id; + xe::be gamerscore; + xe::be unkE; + xe::be flags; + xe::be unk14; + xe::be unk18; + xe::be unk1C; + xe::be unk20; +}; +static_assert_size(X_XDBF_SPA_ACHIEVEMENT, 0x24); + +struct X_XDBF_GPD_ACHIEVEMENT { + xe::be magic; + xe::be id; + xe::be image_id; + xe::be gamerscore; + xe::be flags; + xe::be unlock_time; + // wchar_t* title; + // wchar_t* description; + // wchar_t* unlocked_description; }; -// Wraps an XBDF (XboxDataBaseFormat) in-memory database. -// https://free60project.github.io/wiki/XDBF.html -class XdbfWrapper { - public: - XdbfWrapper(const uint8_t* data, size_t data_size); - - // True if the target memory contains a valid XDBF instance. - bool is_valid() const { return data_ != nullptr; } - - // Gets an entry in the given section. - // If the entry is not found the returned block will be nullptr. - XdbfBlock GetEntry(XdbfSection section, uint64_t id) const; - - // Gets a string from the string table in the given language. - // Returns the empty string if the entry is not found. - std::string GetStringTableEntry(XdbfLocale locale, uint16_t string_id) const; - - protected: -#pragma pack(push, 1) - struct XbdfHeader { - xe::be magic; - xe::be version; - xe::be entry_count; - xe::be entry_used; - xe::be free_count; - xe::be free_used; - }; - static_assert_size(XbdfHeader, 24); - - struct XbdfEntry { - xe::be section; - xe::be id; - xe::be offset; - xe::be size; - }; - static_assert_size(XbdfEntry, 18); - - struct XbdfFileLoc { - xe::be offset; - xe::be size; - }; - static_assert_size(XbdfFileLoc, 8); - - struct XdbfXstc { - xe::be magic; - xe::be version; - xe::be size; - xe::be default_language; - }; - static_assert_size(XdbfXstc, 16); - - struct XdbfXstrHeader { - xe::be magic; - xe::be version; - xe::be size; - xe::be string_count; - }; - static_assert_size(XdbfXstrHeader, 14); - - struct XdbfStringTableEntry { - xe::be id; - xe::be string_length; - }; - static_assert_size(XdbfStringTableEntry, 4); #pragma pack(pop) - private: - const uint8_t* data_ = nullptr; - size_t data_size_ = 0; - const uint8_t* content_offset_ = nullptr; - - const XbdfHeader* header_ = nullptr; - const XbdfEntry* entries_ = nullptr; - const XbdfFileLoc* files_ = nullptr; +enum class XdbfAchievementType : uint32_t { + kCompletion = 1, + kLeveling = 2, + kUnlock = 3, + kEvent = 4, + kTournament = 5, + kCheckpoint = 6, + kOther = 7, }; -class XdbfGameData : public XdbfWrapper { +enum class XdbfAchievementFlags : uint32_t { + kTypeMask = 0x7, + kShowUnachieved = 0x8, + kAchievedOnline = 0x10000, + kAchieved = 0x20000 +}; + +struct XdbfAchievement { + uint16_t id = 0; + std::wstring label; + std::wstring description; + std::wstring unachieved_desc; + uint32_t image_id = 0; + uint32_t gamerscore = 0; + uint32_t flags = 0; + uint64_t unlock_time = 0; + + XdbfAchievementType GetType() { + return static_cast( + flags & static_cast(XdbfAchievementFlags::kTypeMask)); + } + + bool IsUnlocked() { + return flags & static_cast(XdbfAchievementFlags::kAchieved); + } + + bool IsUnlockedOnline() { + return flags & static_cast(XdbfAchievementFlags::kAchievedOnline); + } + + void Unlock(bool online = false) { + flags |= static_cast(XdbfAchievementFlags::kAchieved); + if (online) { + flags |= static_cast(XdbfAchievementFlags::kAchievedOnline); + } + // TODO: set unlock time? + } + + void Lock() { + flags = flags & ~(static_cast(XdbfAchievementFlags::kAchieved)); + flags = + flags & ~(static_cast(XdbfAchievementFlags::kAchievedOnline)); + unlock_time = 0; + } +}; + +struct XdbfEntry { + X_XDBF_ENTRY info; + std::vector data; +}; + +// Parses/creates an XDBF (XboxDataBaseFormat) file +// http://www.free60.org/wiki/XDBF +class XdbfFile { public: - XdbfGameData(const uint8_t* data, size_t data_size) - : XdbfWrapper(data, data_size) {} + XdbfFile() { + header.magic = 'XDBF'; + header.version = 1; + } - // The game icon image, if found. - XdbfBlock icon() const; + bool Read(const uint8_t* data, size_t data_size); + bool Write(uint8_t* data, size_t* data_size); - // The game's default language. - XdbfLocale default_language() const; + XdbfEntry* GetEntry(uint16_t section, uint64_t id) const; - // The game's title in its default language. - std::string title() const; + // Updates (or adds) an entry + bool UpdateEntry(XdbfEntry entry); + + protected: + X_XDBF_HEADER header; + std::vector entries; + std::vector free_entries; +}; + +class SpaFile : public XdbfFile { + public: + std::string GetStringTableEntry(XdbfLocale locale, uint16_t string_id) const; + + uint32_t GetAchievements(XdbfLocale locale, + std::vector* achievements) const; + + XdbfEntry* GetIcon() const; + XdbfLocale GetDefaultLocale() const; + std::string GetTitle() const; +}; + +class GpdFile : public XdbfFile { + public: + bool GetAchievement(uint16_t id, XdbfAchievement* dest); + uint32_t GetAchievements(std::vector* achievements) const; + + // Updates (or adds) an achievement + bool UpdateAchievement(XdbfAchievement ach); }; } // namespace util From 00f3627715b6466ca61688df25f0363e3f83e5e4 Mon Sep 17 00:00:00 2001 From: emoose Date: Fri, 16 Nov 2018 01:53:07 +0000 Subject: [PATCH 02/21] [Base] Make Emulator::CompleteLaunch use SpaFile instead --- src/xenia/emulator.cc | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index b1d976c9e..104de7318 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -662,13 +662,14 @@ X_STATUS Emulator::CompleteLaunch(const std::wstring& path, uint32_t resource_size = 0; if (XSUCCEEDED( module->GetSection(title_id, &resource_data, &resource_size))) { - kernel::util::XdbfGameData db( - module->memory()->TranslateVirtual(resource_data), resource_size); - if (db.is_valid()) { - game_title_ = xe::to_wstring(db.title()); - auto icon_block = db.icon(); + kernel::util::SpaFile spa; + if (spa.Read(module->memory()->TranslateVirtual(resource_data), + resource_size)) { + game_title_ = xe::to_wstring(spa.GetTitle()); + auto icon_block = spa.GetIcon(); if (icon_block) { - display_window_->SetIcon(icon_block.buffer, icon_block.size); + display_window_->SetIcon(icon_block->data.data(), + icon_block->data.size()); } } } From 734bbc7515b4dfaec53a38efd6ad775d7ac43b08 Mon Sep 17 00:00:00 2001 From: emoose Date: Fri, 16 Nov 2018 02:43:18 +0000 Subject: [PATCH 03/21] [Kernel] Add XdbfTitlePlayed support to GpdFile, used by dash GPD Eventually user_profile would read dash GPD, loop through XdbfTitlePlayed entries, load GPD for each one... Also eventually settings will be stored in dash GPD (eg. the ones currently defined in user_profile.cc) --- src/xenia/kernel/util/xdbf_utils.cc | 107 +++++++++++++++++++--------- src/xenia/kernel/util/xdbf_utils.h | 103 ++++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 34 deletions(-) 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 From 2a5ab07024168d8470b6dd875d0168f4fd5cd461 Mon Sep 17 00:00:00 2001 From: emoose Date: Fri, 16 Nov 2018 05:00:07 +0000 Subject: [PATCH 04/21] [Kernel] Let UserProfile load/save GPDs, convert SPA -> GPD on XEX load UserProfile will now try loading dash GPD + any game GPDs from the "profile" folder when initialized. After loading an XEX the title's SPA data gets passed to UserProfile, which will then either set current GPD based on title ID in the SPA, or create new GPD and copy achievements/images over to it. --- src/xenia/emulator.cc | 4 +- src/xenia/kernel/util/xdbf_utils.cc | 15 ++- src/xenia/kernel/util/xdbf_utils.h | 21 +++- src/xenia/kernel/xam/user_profile.cc | 175 +++++++++++++++++++++++++++ src/xenia/kernel/xam/user_profile.h | 13 ++ 5 files changed, 225 insertions(+), 3 deletions(-) diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 104de7318..09fe4dd63 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -665,7 +665,9 @@ X_STATUS Emulator::CompleteLaunch(const std::wstring& path, kernel::util::SpaFile spa; if (spa.Read(module->memory()->TranslateVirtual(resource_data), resource_size)) { - game_title_ = xe::to_wstring(spa.GetTitle()); + // Set title SPA and get title name/icon + kernel_state_->user_profile()->SetTitleSpaData(spa); + game_title_ = xe::to_wstring(spa.GetTitleName()); auto icon_block = spa.GetIcon(); if (icon_block) { display_window_->SetIcon(icon_block->data.data(), diff --git a/src/xenia/kernel/util/xdbf_utils.cc b/src/xenia/kernel/util/xdbf_utils.cc index 5f7ff9672..4af86cb8f 100644 --- a/src/xenia/kernel/util/xdbf_utils.cc +++ b/src/xenia/kernel/util/xdbf_utils.cc @@ -244,11 +244,24 @@ XdbfLocale SpaFile::GetDefaultLocale() const { return static_cast(static_cast(xstc->default_language)); } -std::string SpaFile::GetTitle() const { +std::string SpaFile::GetTitleName() const { return GetStringTableEntry(GetDefaultLocale(), static_cast(XdbfSpaID::Title)); } +uint32_t SpaFile::GetTitleId() const { + auto block = GetEntry(static_cast(XdbfSpaSection::kMetadata), + static_cast(XdbfSpaID::Xthd)); + if (!block) { + return -1; + } + + auto xthd = reinterpret_cast(block->data.data()); + assert_true(xthd->magic == static_cast(XdbfSpaID::Xthd)); + + return xthd->title_id; +} + bool GpdFile::GetAchievement(uint16_t id, XdbfAchievement* dest) { for (size_t i = 0; i < entries.size(); i++) { auto* entry = (XdbfEntry*)&entries[i]; diff --git a/src/xenia/kernel/util/xdbf_utils.h b/src/xenia/kernel/util/xdbf_utils.h index 6f9c3b321..7640f1186 100644 --- a/src/xenia/kernel/util/xdbf_utils.h +++ b/src/xenia/kernel/util/xdbf_utils.h @@ -26,6 +26,7 @@ enum class XdbfSpaID : uint64_t { Xach = 'XACH', Xstr = 'XSTR', Xstc = 'XSTC', + Xthd = 'XTHD', Title = 0x8000, }; @@ -96,6 +97,23 @@ struct X_XDBF_XSTC_DATA { }; static_assert_size(X_XDBF_XSTC_DATA, 16); +struct X_XDBF_XTHD_DATA { + xe::be magic; + xe::be version; + xe::be unk8; + xe::be title_id; + xe::be unk10; // always 1? + xe::be title_version_major; + xe::be title_version_minor; + xe::be title_version_build; + xe::be title_version_revision; + xe::be unk1C; + xe::be unk20; + xe::be unk24; + xe::be unk28; +}; +static_assert_size(X_XDBF_XTHD_DATA, 0x2C); + struct X_XDBF_TABLE_HEADER { xe::be magic; xe::be version; @@ -324,7 +342,8 @@ class SpaFile : public XdbfFile { XdbfEntry* GetIcon() const; XdbfLocale GetDefaultLocale() const; - std::string GetTitle() const; + std::string GetTitleName() const; + uint32_t GetTitleId() const; }; class GpdFile : public XdbfFile { diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index 6471b3d1e..0230f2b2d 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -12,11 +12,16 @@ #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xam/user_profile.h" +#include "xenia/base/filesystem.h" +#include "xenia/base/logging.h" +#include "xenia/base/mapped_memory.h" namespace xe { namespace kernel { namespace xam { +constexpr uint32_t kDashboardID = 0xFFFE07D1; + UserProfile::UserProfile() { xuid_ = 0xBABEBABEBABEBABE; name_ = "User"; @@ -85,6 +90,176 @@ UserProfile::UserProfile() { AddSetting(std::make_unique(0x63E83FFE)); // XPROFILE_TITLE_SPECIFIC3 AddSetting(std::make_unique(0x63E83FFD)); + + // Try loading profile GPD files... + LoadGpdFiles(); +} + +void UserProfile::LoadGpdFiles() { + auto mmap_ = + MappedMemory::Open(L"profile\\FFFE07D1.gpd", MappedMemory::Mode::kRead); + if (!mmap_) { + XELOGW("Dash GPD not found, using blank one"); + return; + } + + dash_gpd_.Read(mmap_->data(), mmap_->size()); + mmap_->Close(); + + std::vector titles; + dash_gpd_.GetTitles(&titles); + + for (auto title : titles) { + wchar_t fname[256]; + _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, + title.title_name.c_str()); + continue; + } + + util::GpdFile title_gpd; + title_gpd.Read(mmap_->data(), mmap_->size()); + mmap_->Close(); + + title_gpds_[title.title_id] = title_gpd; + } +} + +util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { + uint32_t spa_title = spa_data.GetTitleId(); + + auto gpd = title_gpds_.find(spa_title); + + if (gpd == title_gpds_.end()) { + // 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 spa_achievements; + // TODO: let user choose locale? + spa_data.GetAchievements(spa_data.GetDefaultLocale(), &spa_achievements); + + // Copy cheevos from SPA -> GPD + util::GpdFile title_gpd; + for (auto ach : spa_achievements) { + title_gpd.UpdateAchievement(ach); + + title_info.achievements_possible++; + title_info.gamerscore_total += ach.gamerscore; + } + + // Try copying achievement images if we can... + for (auto ach : spa_achievements) { + auto* image_entry = spa_data.GetEntry( + static_cast(util::XdbfSpaSection::kImage), ach.image_id); + if (image_entry) { + title_gpd.UpdateEntry(*image_entry); + } + } + + title_gpds_[spa_title] = title_gpd; + + // Update dash GPD with title and write updated GPDs + dash_gpd_.UpdateTitle(title_info); + + UpdateGpd(spa_title, title_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]; + return curr_gpd_; +} + +bool UserProfile::UpdateGpdFiles() { + // 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; + } + } + + // No need to update dash GPD here, the UpdateGpd func should take care of it + // when needed + return true; +} + +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); + return false; + } + + if (!filesystem::PathExists(L"profile\\")) { + filesystem::CreateFolder(L"profile\\"); + } + + wchar_t fname[256]; + _swprintf(fname, L"profile\\%X.gpd", title_id); + + filesystem::CreateFile(fname); + auto mmap_ = + MappedMemory::Open(fname, MappedMemory::Mode::kReadWrite, 0, gpd_length); + if (!mmap_) { + XELOGE("Failed to open %X.gpd for writing!", title_id); + return false; + } + + bool ret_val = true; + + if (!gpd_data.Write(mmap_->data(), &gpd_length)) { + XELOGE("Failed to write GPD data for %X!", title_id); + ret_val = false; + } else { + // Check if we need to update dashboard data... + if (title_id != kDashboardID) { + util::XdbfTitlePlayed title_info; + if (dash_gpd_.GetTitle(title_id, &title_info)) { + std::vector gpd_achievements; + // TODO: let user choose locale? + gpd_data.GetAchievements(&gpd_achievements); + uint32_t num_ach_total = 0; + uint32_t num_ach_earned = 0; + uint32_t gamerscore_total = 0; + uint32_t gamerscore_earned = 0; + for (auto ach : gpd_achievements) { + num_ach_total++; + gamerscore_total += ach.gamerscore; + if (ach.IsUnlocked()) { + num_ach_earned++; + gamerscore_earned += ach.gamerscore; + } + } + + if (num_ach_total != title_info.achievements_possible || + num_ach_earned != title_info.achievements_earned || + gamerscore_total != title_info.gamerscore_total || + gamerscore_earned != title_info.gamerscore_earned) { + title_info.achievements_possible = num_ach_total; + title_info.achievements_earned = num_ach_earned; + title_info.gamerscore_total = gamerscore_total; + title_info.gamerscore_earned = gamerscore_earned; + + dash_gpd_.UpdateTitle(title_info); + UpdateGpd(kDashboardID, dash_gpd_); + } + } + } + } + + mmap_->Close(gpd_length); + return ret_val; } void UserProfile::AddSetting(std::unique_ptr setting) { diff --git a/src/xenia/kernel/xam/user_profile.h b/src/xenia/kernel/xam/user_profile.h index f8f2dacd8..e6223fe34 100644 --- a/src/xenia/kernel/xam/user_profile.h +++ b/src/xenia/kernel/xam/user_profile.h @@ -15,6 +15,7 @@ #include #include +#include "xenia/kernel/util/xdbf_utils.h" #include "xenia/xbox.h" namespace xe { @@ -206,7 +207,15 @@ class UserProfile { void AddSetting(std::unique_ptr setting); Setting* GetSetting(uint32_t setting_id); + util::GpdFile* SetTitleSpaData(const util::SpaFile& spa_data); + util::GpdFile* GetTitleGpd() { return curr_gpd_; } + + bool UpdateGpdFiles(); + private: + void LoadGpdFiles(); + bool UpdateGpd(uint32_t title_id, util::GpdFile& gpd_data); + uint64_t xuid_; std::string name_; std::vector> setting_list_; @@ -214,6 +223,10 @@ class UserProfile { void LoadSetting(UserProfile::Setting*); void SaveSetting(UserProfile::Setting*); + + std::unordered_map title_gpds_; + util::GpdFile dash_gpd_; + util::GpdFile* curr_gpd_ = nullptr; }; } // namespace xam From 8368e8ce06070bd8b75d149fc9345d3b699189ac Mon Sep 17 00:00:00 2001 From: emoose Date: Fri, 16 Nov 2018 05:03:23 +0000 Subject: [PATCH 05/21] [Kernel] Let XGIUserWriteAchievements unlock achievements XGIUserWriteAchievements seems to be the main (only?) way achievements are unlocked, when used that'll now try getting the GPD from UserProfile and unlock the achievements passed to it. Dash GPD info is recalced and GPDs get written to disk whenever achievements are unlocked, letting achievements persist across instances. TODO: - "Achievement Unlocked!" UI, writing to log isn't much of an indicator :P - XamUserCreateAchievementEnumerator, needs to return XACHIEVEMENT_DETAILS structs, which also contain pointers to strings in guest memory... - Copy over more data from SPA? (need to check what data 360 copies to it) - Optimize GPD writing, atm every GPD gets rewritten even if it hasn't been changed... - Change profile folder path? Maybe allow using 360 profiles eventually? - GPD settings --- src/xenia/kernel/xam/apps/xgi_app.cc | 31 ++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/xenia/kernel/xam/apps/xgi_app.cc b/src/xenia/kernel/xam/apps/xgi_app.cc index c232dacd0..8873715f1 100644 --- a/src/xenia/kernel/xam/apps/xgi_app.cc +++ b/src/xenia/kernel/xam/apps/xgi_app.cc @@ -17,6 +17,11 @@ namespace kernel { namespace xam { namespace apps { +struct X_XUSER_ACHIEVEMENT { + xe::be user_idx; + xe::be achievement_id; +}; + XgiApp::XgiApp(KernelState* kernel_state) : App(kernel_state, 0xFB) {} // http://mb.mirage.org/bugzilla/xliveless/main.c @@ -55,6 +60,32 @@ X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr, uint32_t achievements_ptr = xe::load_and_swap(buffer + 4); XELOGD("XGIUserWriteAchievements(%.8X, %.8X)", achievement_count, achievements_ptr); + + auto* game_gpd = kernel_state_->user_profile()->GetTitleGpd(); + if (!game_gpd) { + XELOGE("XGIUserWriteAchievements failed, no game GPD set?"); + return X_ERROR_SUCCESS; + } + + bool modified = false; + auto* achievement = + (X_XUSER_ACHIEVEMENT*)memory_->TranslateVirtual(achievements_ptr); + for (uint32_t i = 0; i < achievement_count; i++) { + util::XdbfAchievement ach; + if (game_gpd->GetAchievement(achievement->achievement_id, &ach)) { + if (!ach.IsUnlocked()) { + XELOGI("Achievement Unlocked! %ws (%d gamerscore) - %ws", + ach.label.c_str(), ach.gamerscore, ach.description.c_str()); + ach.Unlock(false); + game_gpd->UpdateAchievement(ach); + modified = true; + } + } + } + if (modified) { + kernel_state_->user_profile()->UpdateGpdFiles(); + } + return X_ERROR_SUCCESS; } case 0x000B0010: { From e510972691e7313b5b8f4c88b5fd0554d038068b Mon Sep 17 00:00:00 2001 From: emoose Date: Fri, 16 Nov 2018 05:50:48 +0000 Subject: [PATCH 06/21] [Kernel] Update XamUserCreateAchievementEnumerator to work with GPDs Seems to work fine, tested with Tetris TGM ACE which uses achievements to track progression & unlock things in the game, and unlocking achievements in the GPDs does unlock things in game as intended. Haven't actually tested unlocking the achievements in-game and then checking though (my tetris skills are awful :P), but I think it should work fine. Strings aren't copied into it yet since I didn't want to alloc guest memory for every CreateEnumerator call... Not many games actually have in-game achievement lists though, but I've added a placeholder string for any that might. Like said in the comment, maybe we should alloc those strings in guest mem when the game GPD/SPA is first loaded in? --- src/xenia/kernel/xam/xam_user.cc | 63 +++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 03b72ebcd..2e6bb6c1c 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -448,6 +448,20 @@ dword_result_t XamShowSigninUI(dword_t unk, dword_t unk_mask) { } DECLARE_XAM_EXPORT1(XamShowSigninUI, kUserProfiles, kStub); +#pragma pack(push, 1) +struct X_XACHIEVEMENT_DETAILS { + xe::be id; + xe::be label_ptr; + xe::be description_ptr; + xe::be unachieved_ptr; + xe::be image_id; + xe::be gamerscore; + xe::be unlock_time; + xe::be flags; +}; +static_assert_size(X_XACHIEVEMENT_DETAILS, 36); +#pragma pack(pop) + dword_result_t XamUserCreateAchievementEnumerator(dword_t title_id, dword_t user_index, dword_t xuid, dword_t flags, @@ -455,14 +469,59 @@ dword_result_t XamUserCreateAchievementEnumerator(dword_t title_id, lpdword_t buffer_size_ptr, lpdword_t handle_ptr) { if (buffer_size_ptr) { - *buffer_size_ptr = 500 * count; + *buffer_size_ptr = sizeof(X_XACHIEVEMENT_DETAILS) * count; } - auto e = new XStaticEnumerator(kernel_state(), count, 500); + auto e = new XStaticEnumerator(kernel_state(), count, + sizeof(X_XACHIEVEMENT_DETAILS)); e->Initialize(); *handle_ptr = e->handle(); + // Copy achievements into the enumerator if game GPD is loaded + auto* game_gpd = kernel_state()->user_profile()->GetTitleGpd(); + if (!game_gpd) { + XELOGE( + "XamUserCreateAchievementEnumerator called without GPD being loaded!"); + return X_ERROR_SUCCESS; + } + + static uint32_t placeholder = 0; + + if (!placeholder) { + wchar_t* placeholder_val = L""; + + placeholder = kernel_memory()->SystemHeapAlloc( + ((uint32_t)wcslen(placeholder_val) + 1) * 2); + auto* place_addr = kernel_memory()->TranslateVirtual(placeholder); + + memset(place_addr, 0, (wcslen(placeholder_val) + 1) * 2); + xe::copy_and_swap(place_addr, placeholder_val, wcslen(placeholder_val)); + } + + std::vector achievements; + game_gpd->GetAchievements(&achievements); + + for (auto ach : achievements) { + auto* details = (X_XACHIEVEMENT_DETAILS*)e->AppendItem(); + details->id = ach.id; + details->image_id = ach.image_id; + details->gamerscore = ach.gamerscore; + details->unlock_time = ach.unlock_time; + details->flags = ach.flags; + + // TODO: these, allocating guest mem for them every CreateEnum call would be + // very bad... + + // maybe we could alloc these in guest when the title GPD is first loaded? + details->label_ptr = placeholder; + details->description_ptr = placeholder; + details->unachieved_ptr = placeholder; + } + + XELOGD("XamUserCreateAchievementEnumerator: added %d items to enumerator", + e->item_count()); + return X_ERROR_SUCCESS; } DECLARE_XAM_EXPORT1(XamUserCreateAchievementEnumerator, kUserProfiles, From faeddbd34de451a268b0ed3c0fbab195fab9d795 Mon Sep 17 00:00:00 2001 From: emoose Date: Fri, 16 Nov 2018 07:05:54 +0000 Subject: [PATCH 07/21] [Kernel] Copy title image/name from SPA->GPD, fix XDBF free data... Velocity should now load Xenia-created GPDs fine :D To try it just make a new profile package in Velocity, inject GPDs from Xenia, and inject random Account file from a 360 profile. Then close created package, use Tools -> Profile Tools -> Profile Editor to open the package you just created, and if all goes well the profile should load up fine. --- src/xenia/kernel/util/xdbf_utils.cc | 13 +++++++++++-- src/xenia/kernel/xam/user_profile.cc | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/xenia/kernel/util/xdbf_utils.cc b/src/xenia/kernel/util/xdbf_utils.cc index 4af86cb8f..488eb1641 100644 --- a/src/xenia/kernel/util/xdbf_utils.cc +++ b/src/xenia/kernel/util/xdbf_utils.cc @@ -57,7 +57,7 @@ bool XdbfFile::Write(uint8_t* data, size_t* data_size) { *data_size += sizeof(X_XDBF_HEADER); *data_size += entries.size() * sizeof(X_XDBF_ENTRY); - *data_size += free_entries.size() * sizeof(X_XDBF_FILELOC); + *data_size += 1 * sizeof(X_XDBF_FILELOC); size_t entries_size = 0; for (auto ent : entries) { @@ -71,7 +71,7 @@ bool XdbfFile::Write(uint8_t* data, size_t* data_size) { } header.entry_count = header.entry_used = (uint32_t)entries.size(); - header.free_count = header.free_used = (uint32_t)free_entries.size(); + header.free_count = header.free_used = 1; auto* ptr = data; memcpy(ptr, &header, sizeof(X_XDBF_HEADER)); @@ -93,6 +93,15 @@ bool XdbfFile::Write(uint8_t* data, size_t* data_size) { ptr += sizeof(X_XDBF_ENTRY); } + free_entries.clear(); + X_XDBF_FILELOC free_ent; + free_ent.offset = (uint32_t)*data_size - sizeof(X_XDBF_HEADER) - + (sizeof(X_XDBF_ENTRY) * header.entry_count) - + (sizeof(X_XDBF_FILELOC) * header.free_count); + + free_ent.size = 0 - free_ent.offset; + free_entries.push_back(free_ent); + for (auto ent : free_entries) { memcpy(free_ptr, &ent, sizeof(X_XDBF_FILELOC)); free_ptr++; diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index 0230f2b2d..0907c2008 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -162,6 +162,26 @@ util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { } } + // try adding title image & name + auto* title_image = + spa_data.GetEntry(static_cast(util::XdbfSpaSection::kImage), + static_cast(util::XdbfSpaID::Title)); + if (title_image) { + title_gpd.UpdateEntry(*title_image); + } + + auto title_name = xe::to_wstring(spa_data.GetTitleName()); + if (title_name.length()) { + util::XdbfEntry title_name_ent; + title_name_ent.info.section = + static_cast(util::XdbfGpdSection::kString); + title_name_ent.info.id = static_cast(util::XdbfSpaID::Title); + title_name_ent.data.resize((title_name.length() + 1) * 2); + xe::copy_and_swap((wchar_t*)title_name_ent.data.data(), + title_name.c_str(), title_name.length()); + title_gpd.UpdateEntry(title_name_ent); + } + title_gpds_[spa_title] = title_gpd; // Update dash GPD with title and write updated GPDs From 04fcdeb24dd1a0a7e0b0e584ffbbdfcb2c44ca84 Mon Sep 17 00:00:00 2001 From: emoose Date: Fri, 16 Nov 2018 08:59:50 +0000 Subject: [PATCH 08/21] [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. --- src/xenia/kernel/util/xdbf_utils.cc | 4 +- src/xenia/kernel/util/xdbf_utils.h | 12 ++-- src/xenia/kernel/xam/apps/xgi_app.cc | 2 +- src/xenia/kernel/xam/user_profile.cc | 102 ++++++++++++++++++++++----- src/xenia/kernel/xam/user_profile.h | 4 +- 5 files changed, 99 insertions(+), 25 deletions(-) diff --git a/src/xenia/kernel/util/xdbf_utils.cc b/src/xenia/kernel/util/xdbf_utils.cc index 488eb1641..e8aeb2101 100644 --- a/src/xenia/kernel/util/xdbf_utils.cc +++ b/src/xenia/kernel/util/xdbf_utils.cc @@ -283,7 +283,9 @@ bool GpdFile::GetAchievement(uint16_t id, XdbfAchievement* dest) { auto* ach_data = reinterpret_cast(entry->data.data()); - dest->ReadGPD(ach_data); + if (dest) { + dest->ReadGPD(ach_data); + } return true; } diff --git a/src/xenia/kernel/util/xdbf_utils.h b/src/xenia/kernel/util/xdbf_utils.h index 7640f1186..562d8bfa4 100644 --- a/src/xenia/kernel/util/xdbf_utils.h +++ b/src/xenia/kernel/util/xdbf_utils.h @@ -13,6 +13,7 @@ #include #include +#include "xenia/base/clock.h" #include "xenia/base/memory.h" namespace xe { @@ -165,7 +166,7 @@ struct X_XDBF_GPD_TITLEPLAYED { xe::be male_avatar_awards; xe::be female_avatar_awards; xe::be reserved_flags; - xe::be last_loaded; + xe::be 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(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(dest + 1); xe::copy_and_swap((wchar_t*)txt_ptr, title_name.c_str(), @@ -276,7 +277,8 @@ struct XdbfAchievement { if (online) { flags |= static_cast(XdbfAchievementFlags::kAchievedOnline); } - // TODO: set unlock time? + + unlock_time = Clock::QueryHostSystemTime(); } void Lock() { diff --git a/src/xenia/kernel/xam/apps/xgi_app.cc b/src/xenia/kernel/xam/apps/xgi_app.cc index 8873715f1..f0058d1c0 100644 --- a/src/xenia/kernel/xam/apps/xgi_app.cc +++ b/src/xenia/kernel/xam/apps/xgi_app.cc @@ -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; diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index 0907c2008..55d4c434f 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -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 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 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(util::XdbfSpaSection::kImage), static_cast(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; } diff --git a/src/xenia/kernel/xam/user_profile.h b/src/xenia/kernel/xam/user_profile.h index e6223fe34..02d9dcb50 100644 --- a/src/xenia/kernel/xam/user_profile.h +++ b/src/xenia/kernel/xam/user_profile.h @@ -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 title_gpds_; util::GpdFile dash_gpd_; util::GpdFile* curr_gpd_ = nullptr; + uint32_t curr_title_id_ = -1; }; } // namespace xam From 66fca40b047d8ef986ee3f585158582f7b35d2a6 Mon Sep 17 00:00:00 2001 From: emoose Date: Fri, 16 Nov 2018 09:37:59 +0000 Subject: [PATCH 09/21] [Kernel] Update CreateAchEnum to actually use the title_id parameter --- src/xenia/kernel/xam/user_profile.cc | 12 ++++++++++++ src/xenia/kernel/xam/user_profile.h | 2 +- src/xenia/kernel/xam/xam_user.cc | 5 +++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index 55d4c434f..a5f4dff3a 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -251,6 +251,18 @@ util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { curr_title_id_ = spa_title; return curr_gpd_; } +util::GpdFile* UserProfile::GetTitleGpd(uint32_t title_id) { + if (!title_id) { + return curr_gpd_; + } + + auto gpd = title_gpds_.find(title_id); + if (gpd == title_gpds_.end()) { + return nullptr; + } + + return &(*gpd).second; +} bool UserProfile::UpdateTitleGpd() { if (!curr_gpd_ || curr_title_id_ == -1) { diff --git a/src/xenia/kernel/xam/user_profile.h b/src/xenia/kernel/xam/user_profile.h index 02d9dcb50..bcb4ae36e 100644 --- a/src/xenia/kernel/xam/user_profile.h +++ b/src/xenia/kernel/xam/user_profile.h @@ -208,7 +208,7 @@ class UserProfile { Setting* GetSetting(uint32_t setting_id); util::GpdFile* SetTitleSpaData(const util::SpaFile& spa_data); - util::GpdFile* GetTitleGpd() { return curr_gpd_; } + util::GpdFile* GetTitleGpd(uint32_t title_id = 0); bool UpdateTitleGpd(); bool UpdateAllGpds(); diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 2e6bb6c1c..3cdc35425 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -479,10 +479,11 @@ dword_result_t XamUserCreateAchievementEnumerator(dword_t title_id, *handle_ptr = e->handle(); // Copy achievements into the enumerator if game GPD is loaded - auto* game_gpd = kernel_state()->user_profile()->GetTitleGpd(); + auto* game_gpd = kernel_state()->user_profile()->GetTitleGpd(title_id); if (!game_gpd) { XELOGE( - "XamUserCreateAchievementEnumerator called without GPD being loaded!"); + "XamUserCreateAchievementEnumerator failed to find GPD for title %X!", + title_id); return X_ERROR_SUCCESS; } From be8d85b16804399c8e29334aaa21f1538c610734 Mon Sep 17 00:00:00 2001 From: emoose Date: Sat, 17 Nov 2018 17:36:19 +0000 Subject: [PATCH 10/21] [Kernel] Add --profile_directory flag, print achievements to log Also added more error checking, and changed XELOG statements that use %ws to use %s & xe::to_string instead, seems to_string handles some non-latin strings better than %ws does. --- src/xenia/kernel/xam/user_profile.cc | 80 ++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 17 deletions(-) diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index a5f4dff3a..4c90e3b73 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -11,6 +11,9 @@ #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/util/shim_utils.h" + +#include + #include "xenia/kernel/xam/user_profile.h" #include "xenia/base/clock.h" #include "xenia/base/filesystem.h" @@ -21,6 +24,9 @@ namespace xe { namespace kernel { namespace xam { +DEFINE_string(profile_directory, "Content\\Profile\\", + "The directory to store profile data inside"); + constexpr uint32_t kDashboardID = 0xFFFE07D1; UserProfile::UserProfile() { @@ -97,8 +103,11 @@ UserProfile::UserProfile() { } void UserProfile::LoadGpdFiles() { - auto mmap_ = - MappedMemory::Open(L"profile\\FFFE07D1.gpd", MappedMemory::Mode::kRead); + XELOGI("Loading profile GPDs from path %s", FLAGS_profile_directory.c_str()); + + auto mmap_ = MappedMemory::Open( + xe::to_wstring(FLAGS_profile_directory) + L"FFFE07D1.gpd", + MappedMemory::Mode::kRead); if (!mmap_) { XELOGW( "Failed to open dash GPD (FFFE07D1.gpd) for reading, using blank one"); @@ -113,20 +122,29 @@ void UserProfile::LoadGpdFiles() { for (auto title : titles) { wchar_t fname[256]; - _swprintf(fname, L"profile\\%X.gpd", title.title_id); - mmap_ = MappedMemory::Open(fname, MappedMemory::Mode::kRead); + _swprintf(fname, L"%X.gpd", title.title_id); + mmap_ = MappedMemory::Open(xe::to_wstring(FLAGS_profile_directory) + fname, + MappedMemory::Mode::kRead); if (!mmap_) { - XELOGE("Failed to open GPD for title %X (%ws)!", title.title_id, - title.title_name.c_str()); + XELOGE("Failed to open GPD for title %X (%s)!", title.title_id, + xe::to_string(title.title_name).c_str()); continue; } util::GpdFile title_gpd; - title_gpd.Read(mmap_->data(), mmap_->size()); + bool result = title_gpd.Read(mmap_->data(), mmap_->size()); mmap_->Close(); + if (!result) { + XELOGE("Failed to read GPD for title %X (%s)!", title.title_id, + xe::to_string(title.title_name).c_str()); + continue; + } + title_gpds_[title.title_id] = title_gpd; } + + XELOGI("Loaded %d profile GPDs", title_gpds_.size() + 1); } util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { @@ -142,6 +160,8 @@ util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { if (gpd != title_gpds_.end()) { auto& title_gpd = (*gpd).second; + XELOGI("Loaded existing GPD for title %X", spa_title); + bool always_update_title = false; if (!dash_gpd_.GetTitle(spa_title, &title_info)) { assert_always(); @@ -175,9 +195,9 @@ util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { // 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 " + "Adding new achievement %d (%s) from SPA (wasn't inside existing " "GPD)", - ach.id, ach.label.c_str()); + ach.id, xe::to_string(ach.label).c_str()); ach_updated = true; title_gpd.UpdateAchievement(ach); @@ -194,7 +214,7 @@ util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { UpdateGpd(kDashboardID, dash_gpd_); } else { // GPD not found... have to create it! - XELOGD("Creating new GPD for title %X", spa_title); + XELOGI("Creating new GPD for title %X", spa_title); title_info.title_name = xe::to_wstring(spa_data.GetTitleName()); title_info.title_id = spa_title; @@ -249,8 +269,30 @@ util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { curr_gpd_ = &title_gpds_[spa_title]; curr_title_id_ = spa_title; + + // Print achievement list to log, ATM there's no other way for users to see + // achievement status... + std::vector achievements; + if (curr_gpd_->GetAchievements(&achievements)) { + XELOGI("Achievement list:"); + + for (auto ach : achievements) { + // TODO: use ach.unachieved_desc for locked achievements? + // depends on XdbfAchievementFlags::kShowUnachieved afaik + XELOGI("%d - %s - %s - %d GS - %s", ach.id, + xe::to_string(ach.label).c_str(), + xe::to_string(ach.description).c_str(), ach.gamerscore, + ach.IsUnlocked() ? "unlocked" : "locked"); + } + + XELOGI("Unlocked achievements: %d/%d, gamerscore: %d/%d\r\n", + title_info.achievements_earned, title_info.achievements_possible, + title_info.gamerscore_earned, title_info.gamerscore_total); + } + return curr_gpd_; } + util::GpdFile* UserProfile::GetTitleGpd(uint32_t title_id) { if (!title_id) { return curr_gpd_; @@ -279,7 +321,6 @@ bool UserProfile::UpdateTitleGpd() { } 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); @@ -301,16 +342,17 @@ bool UserProfile::UpdateGpd(uint32_t title_id, util::GpdFile& gpd_data) { return false; } - if (!filesystem::PathExists(L"profile\\")) { - filesystem::CreateFolder(L"profile\\"); + if (!filesystem::PathExists(xe::to_wstring(FLAGS_profile_directory))) { + filesystem::CreateFolder(xe::to_wstring(FLAGS_profile_directory)); } wchar_t fname[256]; - _swprintf(fname, L"profile\\%X.gpd", title_id); + _swprintf(fname, L"%X.gpd", title_id); - filesystem::CreateFile(fname); + filesystem::CreateFile(xe::to_wstring(FLAGS_profile_directory) + fname); auto mmap_ = - MappedMemory::Open(fname, MappedMemory::Mode::kReadWrite, 0, gpd_length); + MappedMemory::Open(xe::to_wstring(FLAGS_profile_directory) + fname, + MappedMemory::Mode::kReadWrite, 0, gpd_length); if (!mmap_) { XELOGE("Failed to open %X.gpd for writing!", title_id); return false; @@ -327,8 +369,8 @@ bool UserProfile::UpdateGpd(uint32_t title_id, util::GpdFile& gpd_data) { util::XdbfTitlePlayed title_info; if (dash_gpd_.GetTitle(title_id, &title_info)) { std::vector gpd_achievements; - // TODO: let user choose locale? gpd_data.GetAchievements(&gpd_achievements); + uint32_t num_ach_total = 0; uint32_t num_ach_earned = 0; uint32_t gamerscore_total = 0; @@ -342,6 +384,7 @@ bool UserProfile::UpdateGpd(uint32_t title_id, util::GpdFile& gpd_data) { } } + // Only update dash GPD if something has changed if (num_ach_total != title_info.achievements_possible || num_ach_earned != title_info.achievements_earned || gamerscore_total != title_info.gamerscore_total || @@ -353,6 +396,9 @@ bool UserProfile::UpdateGpd(uint32_t title_id, util::GpdFile& gpd_data) { dash_gpd_.UpdateTitle(title_info); UpdateGpd(kDashboardID, dash_gpd_); + + // TODO: update gamerscore/achievements earned/titles played settings + // in dashboard GPD } } } From 40145b8dbb6ed41abb3bcf51b50cf5cc75543e5c Mon Sep 17 00:00:00 2001 From: emoose Date: Sat, 17 Nov 2018 23:47:53 +0000 Subject: [PATCH 11/21] [Kernel] Implement XamProfileEnumerate functions --- src/xenia/kernel/xam/xam_user.cc | 99 ++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 3cdc35425..96aac1f61 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -21,6 +21,105 @@ namespace xe { namespace kernel { namespace xam { +// from https://github.com/xemio/testdev/blob/master/xkelib/xam/_xamext.h +#pragma pack(push, 4) +struct X_XAMACCOUNTINFO { + xe::be reserved; + xe::be live_flags; + wchar_t gamertag[0x10]; + xe::be xuid_online; // 09.... + xe::be user_flags; + xe::be network_id; + char passcode[4]; + char online_domain[0x14]; + char online_kerberos_realm[0x18]; + char online_key[0x10]; + char passport_membername[0x72]; + char passport_password[0x20]; + char owner_passport_membername[0x72]; +}; +static_assert_size(X_XAMACCOUNTINFO, 0x17C); +#pragma pack(pop) + +struct X_PROFILEENUMRESULT { + xe::be xuid_offline; // E0..... + X_XAMACCOUNTINFO account; + xe::be device_id; +}; +static_assert_size(X_PROFILEENUMRESULT, 0x188); + +dword_result_t XamProfileCreateEnumerator(dword_t device_id, + lpdword_t handle_out) { + assert_not_null(handle_out); + + auto e = + new XStaticEnumerator(kernel_state(), 1, sizeof(X_PROFILEENUMRESULT)); + + e->Initialize(); + + const auto& user_profile = kernel_state()->user_profile(); + + X_PROFILEENUMRESULT* profile = (X_PROFILEENUMRESULT*)e->AppendItem(); + memset(profile, 0, sizeof(X_PROFILEENUMRESULT)); + profile->xuid_offline = user_profile->xuid(); + profile->device_id = 0xF00D0000; + + auto tag = xe::to_wstring(user_profile->name()); + xe::copy_and_swap(profile->account.gamertag, tag.c_str(), + tag.length()); + profile->account.xuid_online = user_profile->xuid(); + + *handle_out = e->handle(); + return X_ERROR_SUCCESS; +} +DECLARE_XAM_EXPORT(XamProfileCreateEnumerator, + ExportTag::kUserProfiles | ExportTag::kImplemented); + +dword_result_t XamProfileEnumerate(dword_t handle, dword_t flags, + lpvoid_t buffer, + pointer_t overlapped) { + assert_true(flags == 0); + + auto e = kernel_state()->object_table()->LookupObject(handle); + if (!e) { + if (overlapped) { + kernel_state()->CompleteOverlappedImmediateEx( + overlapped, X_ERROR_INVALID_HANDLE, X_ERROR_INVALID_HANDLE, 0); + return X_ERROR_IO_PENDING; + } else { + return X_ERROR_INVALID_HANDLE; + } + } + + buffer.Zero(sizeof(X_PROFILEENUMRESULT)); + + X_RESULT result; + + if (e->current_item() >= e->item_count()) { + result = X_ERROR_NO_MORE_FILES; + } else { + auto item_buffer = buffer.as(); + if (!e->WriteItem(item_buffer)) { + result = X_ERROR_NO_MORE_FILES; + } else { + result = X_ERROR_SUCCESS; + } + } + + // Return X_ERROR_NO_MORE_FILES in HRESULT form. + X_HRESULT extended_result = result != 0 ? X_HRESULT_FROM_WIN32(result) : 0; + if (overlapped) { + kernel_state()->CompleteOverlappedImmediateEx( + overlapped, result, extended_result, result == X_ERROR_SUCCESS ? 1 : 0); + return X_ERROR_IO_PENDING; + } else { + assert_always(); + return X_ERROR_INVALID_PARAMETER; + } +} +DECLARE_XAM_EXPORT(XamProfileEnumerate, + ExportTag::kUserProfiles | ExportTag::kImplemented); + X_HRESULT_result_t XamUserGetXUID(dword_t user_index, dword_t unk, lpqword_t xuid_ptr) { if (user_index) { From 9b8ebcfe7b1c95962ca9c2ae044cc228c5418464 Mon Sep 17 00:00:00 2001 From: emoose Date: Sat, 17 Nov 2018 23:49:46 +0000 Subject: [PATCH 12/21] [Kernel] Fix achievement iterator inside XGIUserWriteAchievements Thanks to jgoyvaerts for noticing it! --- src/xenia/kernel/xam/apps/xgi_app.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/xenia/kernel/xam/apps/xgi_app.cc b/src/xenia/kernel/xam/apps/xgi_app.cc index f0058d1c0..e285bab53 100644 --- a/src/xenia/kernel/xam/apps/xgi_app.cc +++ b/src/xenia/kernel/xam/apps/xgi_app.cc @@ -70,8 +70,8 @@ X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr, bool modified = false; auto* achievement = (X_XUSER_ACHIEVEMENT*)memory_->TranslateVirtual(achievements_ptr); - for (uint32_t i = 0; i < achievement_count; i++) { - util::XdbfAchievement ach; + util::XdbfAchievement ach; + for (uint32_t i = 0; i < achievement_count; i++, achievement++) { if (game_gpd->GetAchievement(achievement->achievement_id, &ach)) { if (!ach.IsUnlocked()) { XELOGI("Achievement Unlocked! %ws (%d gamerscore) - %ws", From 69eca7940281643cb1dca286689ac32736ed3c5c Mon Sep 17 00:00:00 2001 From: emoose Date: Sun, 18 Nov 2018 00:04:15 +0000 Subject: [PATCH 13/21] [Kernel] Format XdbfFile class members like rest of project Oops, holdover from when this was XbdfWrapper :P --- src/xenia/kernel/util/xdbf_utils.cc | 68 ++++++++++++++--------------- src/xenia/kernel/util/xdbf_utils.h | 10 ++--- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/xenia/kernel/util/xdbf_utils.cc b/src/xenia/kernel/util/xdbf_utils.cc index e8aeb2101..bce9b8eaf 100644 --- a/src/xenia/kernel/util/xdbf_utils.cc +++ b/src/xenia/kernel/util/xdbf_utils.cc @@ -22,30 +22,30 @@ bool XdbfFile::Read(const uint8_t* data, size_t data_size) { } auto* ptr = data; - memcpy(&header, ptr, sizeof(X_XDBF_HEADER)); - if (header.magic != kXdbfMagicXdbf) { + memcpy(&header_, ptr, sizeof(X_XDBF_HEADER)); + if (header_.magic != kXdbfMagicXdbf) { return false; } ptr += sizeof(X_XDBF_HEADER); auto* free_ptr = (const X_XDBF_FILELOC*)(ptr + (sizeof(X_XDBF_ENTRY) * - header.entry_count)); + header_.entry_count)); auto* data_ptr = - (uint8_t*)free_ptr + (sizeof(X_XDBF_FILELOC) * header.free_count); + (uint8_t*)free_ptr + (sizeof(X_XDBF_FILELOC) * header_.free_count); - for (uint32_t i = 0; i < header.entry_used; i++) { + for (uint32_t i = 0; i < header_.entry_used; i++) { XdbfEntry entry; memcpy(&entry.info, ptr, sizeof(X_XDBF_ENTRY)); entry.data.resize(entry.info.size); memcpy(entry.data.data(), data_ptr + entry.info.offset, entry.info.size); - entries.push_back(entry); + entries_.push_back(entry); ptr += sizeof(X_XDBF_ENTRY); } - for (uint32_t i = 0; i < header.free_used; i++) { - free_entries.push_back(*free_ptr); + for (uint32_t i = 0; i < header_.free_used; i++) { + free_entries_.push_back(*free_ptr); free_ptr++; } @@ -56,11 +56,11 @@ bool XdbfFile::Write(uint8_t* data, size_t* data_size) { *data_size = 0; *data_size += sizeof(X_XDBF_HEADER); - *data_size += entries.size() * sizeof(X_XDBF_ENTRY); + *data_size += entries_.size() * sizeof(X_XDBF_ENTRY); *data_size += 1 * sizeof(X_XDBF_FILELOC); size_t entries_size = 0; - for (auto ent : entries) { + for (auto ent : entries_) { entries_size += ent.data.size(); } @@ -70,20 +70,20 @@ bool XdbfFile::Write(uint8_t* data, size_t* data_size) { return true; } - header.entry_count = header.entry_used = (uint32_t)entries.size(); - header.free_count = header.free_used = 1; + header_.entry_count = header_.entry_used = (uint32_t)entries_.size(); + header_.free_count = header_.free_used = 1; auto* ptr = data; - memcpy(ptr, &header, sizeof(X_XDBF_HEADER)); + memcpy(ptr, &header_, sizeof(X_XDBF_HEADER)); ptr += sizeof(X_XDBF_HEADER); auto* free_ptr = - (X_XDBF_FILELOC*)(ptr + (sizeof(X_XDBF_ENTRY) * header.entry_count)); + (X_XDBF_FILELOC*)(ptr + (sizeof(X_XDBF_ENTRY) * header_.entry_count)); auto* data_start = - (uint8_t*)free_ptr + (sizeof(X_XDBF_FILELOC) * header.free_count); + (uint8_t*)free_ptr + (sizeof(X_XDBF_FILELOC) * header_.free_count); auto* data_ptr = data_start; - for (auto ent : entries) { + for (auto ent : entries_) { ent.info.offset = (uint32_t)(data_ptr - data_start); ent.info.size = (uint32_t)ent.data.size(); memcpy(ptr, &ent.info, sizeof(X_XDBF_ENTRY)); @@ -93,16 +93,16 @@ bool XdbfFile::Write(uint8_t* data, size_t* data_size) { ptr += sizeof(X_XDBF_ENTRY); } - free_entries.clear(); + free_entries_.clear(); X_XDBF_FILELOC free_ent; free_ent.offset = (uint32_t)*data_size - sizeof(X_XDBF_HEADER) - - (sizeof(X_XDBF_ENTRY) * header.entry_count) - - (sizeof(X_XDBF_FILELOC) * header.free_count); + (sizeof(X_XDBF_ENTRY) * header_.entry_count) - + (sizeof(X_XDBF_FILELOC) * header_.free_count); free_ent.size = 0 - free_ent.offset; - free_entries.push_back(free_ent); + free_entries_.push_back(free_ent); - for (auto ent : free_entries) { + for (auto ent : free_entries_) { memcpy(free_ptr, &ent, sizeof(X_XDBF_FILELOC)); free_ptr++; } @@ -111,8 +111,8 @@ bool XdbfFile::Write(uint8_t* data, size_t* data_size) { } XdbfEntry* XdbfFile::GetEntry(uint16_t section, uint64_t id) const { - for (size_t i = 0; i < entries.size(); i++) { - auto* entry = (XdbfEntry*)&entries[i]; + for (size_t i = 0; i < entries_.size(); i++) { + auto* entry = (XdbfEntry*)&entries_[i]; if (entry->info.section != section || entry->info.id != id) { continue; } @@ -124,8 +124,8 @@ XdbfEntry* XdbfFile::GetEntry(uint16_t section, uint64_t id) const { } bool XdbfFile::UpdateEntry(XdbfEntry entry) { - for (size_t i = 0; i < entries.size(); i++) { - auto* ent = (XdbfEntry*)&entries[i]; + for (size_t i = 0; i < entries_.size(); i++) { + auto* ent = (XdbfEntry*)&entries_[i]; if (ent->info.section != entry.info.section || ent->info.id != entry.info.id) { continue; @@ -142,7 +142,7 @@ bool XdbfFile::UpdateEntry(XdbfEntry entry) { new_entry.info.size = (uint32_t)entry.data.size(); new_entry.data = entry.data; - entries.push_back(new_entry); + entries_.push_back(new_entry); return true; } @@ -272,8 +272,8 @@ uint32_t SpaFile::GetTitleId() const { } bool GpdFile::GetAchievement(uint16_t id, XdbfAchievement* dest) { - for (size_t i = 0; i < entries.size(); i++) { - auto* entry = (XdbfEntry*)&entries[i]; + for (size_t i = 0; i < entries_.size(); i++) { + auto* entry = (XdbfEntry*)&entries_[i]; if (entry->info.section != static_cast(XdbfGpdSection::kAchievement) || entry->info.id != id) { @@ -296,8 +296,8 @@ uint32_t GpdFile::GetAchievements( std::vector* achievements) const { uint32_t ach_count = 0; - for (size_t i = 0; i < entries.size(); i++) { - auto* entry = (XdbfEntry*)&entries[i]; + for (size_t i = 0; i < entries_.size(); i++) { + auto* entry = (XdbfEntry*)&entries_[i]; if (entry->info.section != static_cast(XdbfGpdSection::kAchievement)) { continue; @@ -323,8 +323,8 @@ uint32_t GpdFile::GetAchievements( } bool GpdFile::GetTitle(uint32_t title_id, XdbfTitlePlayed* dest) { - for (size_t i = 0; i < entries.size(); i++) { - auto* entry = (XdbfEntry*)&entries[i]; + 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; @@ -344,8 +344,8 @@ bool GpdFile::GetTitle(uint32_t title_id, XdbfTitlePlayed* dest) { 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]; + for (size_t i = 0; i < entries_.size(); i++) { + auto* entry = (XdbfEntry*)&entries_[i]; if (entry->info.section != static_cast(XdbfGpdSection::kTitle)) { continue; } diff --git a/src/xenia/kernel/util/xdbf_utils.h b/src/xenia/kernel/util/xdbf_utils.h index 562d8bfa4..e607cc146 100644 --- a/src/xenia/kernel/util/xdbf_utils.h +++ b/src/xenia/kernel/util/xdbf_utils.h @@ -317,8 +317,8 @@ struct XdbfEntry { class XdbfFile { public: XdbfFile() { - header.magic = 'XDBF'; - header.version = 1; + header_.magic = 'XDBF'; + header_.version = 1; } bool Read(const uint8_t* data, size_t data_size); @@ -330,9 +330,9 @@ class XdbfFile { bool UpdateEntry(XdbfEntry entry); protected: - X_XDBF_HEADER header; - std::vector entries; - std::vector free_entries; + X_XDBF_HEADER header_; + std::vector entries_; + std::vector free_entries_; }; class SpaFile : public XdbfFile { From 00cffcacbb92f86af46ff7b48e53e057ccb0a537 Mon Sep 17 00:00:00 2001 From: emoose Date: Sun, 18 Nov 2018 00:07:19 +0000 Subject: [PATCH 14/21] [Kernel] Add GpdFile::GetTitleId() member --- src/xenia/kernel/util/xdbf_utils.h | 7 +++++++ src/xenia/kernel/xam/user_profile.cc | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/xenia/kernel/util/xdbf_utils.h b/src/xenia/kernel/util/xdbf_utils.h index e607cc146..42a396938 100644 --- a/src/xenia/kernel/util/xdbf_utils.h +++ b/src/xenia/kernel/util/xdbf_utils.h @@ -350,6 +350,8 @@ class SpaFile : public XdbfFile { class GpdFile : public XdbfFile { public: + GpdFile(uint32_t title_id) : title_id_(title_id) {} + bool GetAchievement(uint16_t id, XdbfAchievement* dest); uint32_t GetAchievements(std::vector* achievements) const; @@ -361,6 +363,11 @@ class GpdFile : public XdbfFile { // Updates (or adds) a title bool UpdateTitle(XdbfTitlePlayed title); + + uint32_t GetTitleId() { return title_id_; } + + private: + uint32_t title_id_ = -1; }; } // namespace util diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index 4c90e3b73..6a51792ff 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -131,7 +131,7 @@ void UserProfile::LoadGpdFiles() { continue; } - util::GpdFile title_gpd; + util::GpdFile title_gpd(title.title_id); bool result = title_gpd.Read(mmap_->data(), mmap_->size()); mmap_->Close(); @@ -221,7 +221,7 @@ util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { title_info.last_played = Clock::QueryHostSystemTime(); // Copy cheevos from SPA -> GPD - util::GpdFile title_gpd; + util::GpdFile title_gpd(spa_title); for (auto ach : spa_achievements) { title_gpd.UpdateAchievement(ach); From fa2cd63b1f7939216af46097b0ae33bb017bf45c Mon Sep 17 00:00:00 2001 From: emoose Date: Sun, 18 Nov 2018 00:10:44 +0000 Subject: [PATCH 15/21] [Kernel] Add UserProfile::GetTitles, add title_id param to UpdateTitleGpd Also changed GetTItleGpd to check for -1 to use current GPD (some games might be using 00... as title id, eg. protos) --- src/xenia/kernel/xam/user_profile.cc | 23 ++++++++++++++++------- src/xenia/kernel/xam/user_profile.h | 6 ++++-- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index 6a51792ff..b4ec797dd 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -294,7 +294,7 @@ util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { } util::GpdFile* UserProfile::GetTitleGpd(uint32_t title_id) { - if (!title_id) { + if (title_id == -1) { return curr_gpd_; } @@ -306,16 +306,25 @@ util::GpdFile* UserProfile::GetTitleGpd(uint32_t title_id) { return &(*gpd).second; } -bool UserProfile::UpdateTitleGpd() { - if (!curr_gpd_ || curr_title_id_ == -1) { - return false; +void UserProfile::GetTitles(std::vector& titles) { + for (auto title : title_gpds_) { + titles.push_back(&title.second); + } +} + +bool UserProfile::UpdateTitleGpd(uint32_t title_id) { + if (title_id == -1) { + if (!curr_gpd_ || curr_title_id_ == -1) { + return false; + } + title_id = curr_title_id_; } - bool result = UpdateGpd(curr_title_id_, *curr_gpd_); + bool result = UpdateGpd(title_id, *curr_gpd_); if (!result) { - XELOGE("UpdateTitleGpd failed on title %X!", curr_title_id_); + XELOGE("UpdateTitleGpd failed on title %X!", title_id); } else { - XELOGD("Updated title %X GPD successfully!", curr_title_id_); + XELOGD("Updated title %X GPD successfully!", title_id); } return result; } diff --git a/src/xenia/kernel/xam/user_profile.h b/src/xenia/kernel/xam/user_profile.h index bcb4ae36e..08a9cd3bb 100644 --- a/src/xenia/kernel/xam/user_profile.h +++ b/src/xenia/kernel/xam/user_profile.h @@ -208,9 +208,11 @@ class UserProfile { Setting* GetSetting(uint32_t setting_id); util::GpdFile* SetTitleSpaData(const util::SpaFile& spa_data); - util::GpdFile* GetTitleGpd(uint32_t title_id = 0); + util::GpdFile* GetTitleGpd(uint32_t title_id = -1); - bool UpdateTitleGpd(); + void GetTitles(std::vector& titles); + + bool UpdateTitleGpd(uint32_t title_id = -1); bool UpdateAllGpds(); private: From 8f5a82b444b09d3a4a2f00fd0cc0ce719d4e164d Mon Sep 17 00:00:00 2001 From: emoose Date: Sun, 18 Nov 2018 04:58:30 +0000 Subject: [PATCH 16/21] [Kernel] Fix GpdFile missing default constructor & init dash with proper ID --- src/xenia/kernel/util/xdbf_utils.h | 1 + src/xenia/kernel/xam/user_profile.cc | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/xenia/kernel/util/xdbf_utils.h b/src/xenia/kernel/util/xdbf_utils.h index 42a396938..4d7eecb15 100644 --- a/src/xenia/kernel/util/xdbf_utils.h +++ b/src/xenia/kernel/util/xdbf_utils.h @@ -350,6 +350,7 @@ class SpaFile : public XdbfFile { class GpdFile : public XdbfFile { public: + GpdFile() : title_id_(-1) {} GpdFile(uint32_t title_id) : title_id_(title_id) {} bool GetAchievement(uint16_t id, XdbfAchievement* dest); diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index b4ec797dd..a1227d61a 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -29,7 +29,7 @@ DEFINE_string(profile_directory, "Content\\Profile\\", constexpr uint32_t kDashboardID = 0xFFFE07D1; -UserProfile::UserProfile() { +UserProfile::UserProfile() : dash_gpd_(kDashboardID) { xuid_ = 0xBABEBABEBABEBABE; name_ = "User"; From c1eaf6879a22cd5960f9fd9a8a5a428e18f61caa Mon Sep 17 00:00:00 2001 From: emoose Date: Sun, 18 Nov 2018 06:06:38 +0000 Subject: [PATCH 17/21] [Kernel] Move XDBF code to its own namespace --- src/xenia/emulator.cc | 4 +- src/xenia/kernel/xam/apps/xgi_app.cc | 2 +- src/xenia/kernel/xam/user_profile.cc | 36 ++--- src/xenia/kernel/xam/user_profile.h | 18 +-- src/xenia/kernel/xam/xam_user.cc | 2 +- .../{util/xdbf_utils.cc => xam/xdbf/xdbf.cc} | 8 +- .../{util/xdbf_utils.h => xam/xdbf/xdbf.h} | 126 ++-------------- src/xenia/kernel/xam/xdbf/xdbf_xbox.h | 137 ++++++++++++++++++ 8 files changed, 182 insertions(+), 151 deletions(-) rename src/xenia/kernel/{util/xdbf_utils.cc => xam/xdbf/xdbf.cc} (99%) rename src/xenia/kernel/{util/xdbf_utils.h => xam/xdbf/xdbf.h} (69%) create mode 100644 src/xenia/kernel/xam/xdbf/xdbf_xbox.h diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 09fe4dd63..7177045eb 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -33,8 +33,8 @@ #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/user_module.h" #include "xenia/kernel/util/gameinfo_utils.h" -#include "xenia/kernel/util/xdbf_utils.h" #include "xenia/kernel/xam/xam_module.h" +#include "xenia/kernel/xam/xdbf/xdbf.h" #include "xenia/kernel/xbdm/xbdm_module.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_module.h" #include "xenia/memory.h" @@ -662,7 +662,7 @@ X_STATUS Emulator::CompleteLaunch(const std::wstring& path, uint32_t resource_size = 0; if (XSUCCEEDED( module->GetSection(title_id, &resource_data, &resource_size))) { - kernel::util::SpaFile spa; + kernel::xam::xdbf::SpaFile spa; if (spa.Read(module->memory()->TranslateVirtual(resource_data), resource_size)) { // Set title SPA and get title name/icon diff --git a/src/xenia/kernel/xam/apps/xgi_app.cc b/src/xenia/kernel/xam/apps/xgi_app.cc index e285bab53..420e72786 100644 --- a/src/xenia/kernel/xam/apps/xgi_app.cc +++ b/src/xenia/kernel/xam/apps/xgi_app.cc @@ -70,7 +70,7 @@ X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr, bool modified = false; auto* achievement = (X_XUSER_ACHIEVEMENT*)memory_->TranslateVirtual(achievements_ptr); - util::XdbfAchievement ach; + xdbf::XdbfAchievement ach; for (uint32_t i = 0; i < achievement_count; i++, achievement++) { if (game_gpd->GetAchievement(achievement->achievement_id, &ach)) { if (!ach.IsUnlocked()) { diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index a1227d61a..25e5375c9 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -117,7 +117,7 @@ void UserProfile::LoadGpdFiles() { dash_gpd_.Read(mmap_->data(), mmap_->size()); mmap_->Close(); - std::vector titles; + std::vector titles; dash_gpd_.GetTitles(&titles); for (auto title : titles) { @@ -131,7 +131,7 @@ void UserProfile::LoadGpdFiles() { continue; } - util::GpdFile title_gpd(title.title_id); + xdbf::GpdFile title_gpd(title.title_id); bool result = title_gpd.Read(mmap_->data(), mmap_->size()); mmap_->Close(); @@ -147,14 +147,14 @@ void UserProfile::LoadGpdFiles() { XELOGI("Loaded %d profile GPDs", title_gpds_.size() + 1); } -util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { +xdbf::GpdFile* UserProfile::SetTitleSpaData(const xdbf::SpaFile& spa_data) { uint32_t spa_title = spa_data.GetTitleId(); - std::vector spa_achievements; + std::vector spa_achievements; // TODO: let user choose locale? spa_data.GetAchievements(spa_data.GetDefaultLocale(), &spa_achievements); - util::XdbfTitlePlayed title_info; + xdbf::XdbfTitlePlayed title_info; auto gpd = title_gpds_.find(spa_title); if (gpd != title_gpds_.end()) { @@ -221,7 +221,7 @@ util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { title_info.last_played = Clock::QueryHostSystemTime(); // Copy cheevos from SPA -> GPD - util::GpdFile title_gpd(spa_title); + xdbf::GpdFile title_gpd(spa_title); for (auto ach : spa_achievements) { title_gpd.UpdateAchievement(ach); @@ -232,7 +232,7 @@ util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { // Try copying achievement images if we can... for (auto ach : spa_achievements) { auto* image_entry = spa_data.GetEntry( - static_cast(util::XdbfSpaSection::kImage), ach.image_id); + static_cast(xdbf::XdbfSpaSection::kImage), ach.image_id); if (image_entry) { title_gpd.UpdateEntry(*image_entry); } @@ -240,18 +240,18 @@ util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { // Try adding title image & name auto* title_image = - spa_data.GetEntry(static_cast(util::XdbfSpaSection::kImage), - static_cast(util::XdbfSpaID::Title)); + spa_data.GetEntry(static_cast(xdbf::XdbfSpaSection::kImage), + static_cast(xdbf::XdbfSpaID::Title)); if (title_image) { title_gpd.UpdateEntry(*title_image); } auto title_name = xe::to_wstring(spa_data.GetTitleName()); if (title_name.length()) { - util::XdbfEntry title_name_ent; + xdbf::XdbfEntry title_name_ent; title_name_ent.info.section = - static_cast(util::XdbfGpdSection::kString); - title_name_ent.info.id = static_cast(util::XdbfSpaID::Title); + static_cast(xdbf::XdbfGpdSection::kString); + title_name_ent.info.id = static_cast(xdbf::XdbfSpaID::Title); title_name_ent.data.resize((title_name.length() + 1) * 2); xe::copy_and_swap((wchar_t*)title_name_ent.data.data(), title_name.c_str(), title_name.length()); @@ -272,7 +272,7 @@ util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { // Print achievement list to log, ATM there's no other way for users to see // achievement status... - std::vector achievements; + std::vector achievements; if (curr_gpd_->GetAchievements(&achievements)) { XELOGI("Achievement list:"); @@ -293,7 +293,7 @@ util::GpdFile* UserProfile::SetTitleSpaData(const util::SpaFile& spa_data) { return curr_gpd_; } -util::GpdFile* UserProfile::GetTitleGpd(uint32_t title_id) { +xdbf::GpdFile* UserProfile::GetTitleGpd(uint32_t title_id) { if (title_id == -1) { return curr_gpd_; } @@ -306,7 +306,7 @@ util::GpdFile* UserProfile::GetTitleGpd(uint32_t title_id) { return &(*gpd).second; } -void UserProfile::GetTitles(std::vector& titles) { +void UserProfile::GetTitles(std::vector& titles) { for (auto title : title_gpds_) { titles.push_back(&title.second); } @@ -344,7 +344,7 @@ bool UserProfile::UpdateAllGpds() { return true; } -bool UserProfile::UpdateGpd(uint32_t title_id, util::GpdFile& gpd_data) { +bool UserProfile::UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data) { size_t gpd_length = 0; if (!gpd_data.Write(nullptr, &gpd_length)) { XELOGE("Failed to get GPD size for title %X!", title_id); @@ -375,9 +375,9 @@ bool UserProfile::UpdateGpd(uint32_t title_id, util::GpdFile& gpd_data) { } else { // Check if we need to update dashboard data... if (title_id != kDashboardID) { - util::XdbfTitlePlayed title_info; + xdbf::XdbfTitlePlayed title_info; if (dash_gpd_.GetTitle(title_id, &title_info)) { - std::vector gpd_achievements; + std::vector gpd_achievements; gpd_data.GetAchievements(&gpd_achievements); uint32_t num_ach_total = 0; diff --git a/src/xenia/kernel/xam/user_profile.h b/src/xenia/kernel/xam/user_profile.h index 08a9cd3bb..2b0ce4c18 100644 --- a/src/xenia/kernel/xam/user_profile.h +++ b/src/xenia/kernel/xam/user_profile.h @@ -15,7 +15,7 @@ #include #include -#include "xenia/kernel/util/xdbf_utils.h" +#include "xenia/kernel/xam/xdbf/xdbf.h" #include "xenia/xbox.h" namespace xe { @@ -207,17 +207,17 @@ class UserProfile { void AddSetting(std::unique_ptr setting); Setting* GetSetting(uint32_t setting_id); - util::GpdFile* SetTitleSpaData(const util::SpaFile& spa_data); - util::GpdFile* GetTitleGpd(uint32_t title_id = -1); + xdbf::GpdFile* SetTitleSpaData(const xdbf::SpaFile& spa_data); + xdbf::GpdFile* GetTitleGpd(uint32_t title_id = -1); - void GetTitles(std::vector& titles); + void GetTitles(std::vector& titles); bool UpdateTitleGpd(uint32_t title_id = -1); bool UpdateAllGpds(); private: void LoadGpdFiles(); - bool UpdateGpd(uint32_t title_id, util::GpdFile& gpd_data); + bool UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data); uint64_t xuid_; std::string name_; @@ -226,10 +226,10 @@ class UserProfile { void LoadSetting(UserProfile::Setting*); void SaveSetting(UserProfile::Setting*); - - std::unordered_map title_gpds_; - util::GpdFile dash_gpd_; - util::GpdFile* curr_gpd_ = nullptr; + + std::unordered_map title_gpds_; + xdbf::GpdFile dash_gpd_; + xdbf::GpdFile* curr_gpd_ = nullptr; uint32_t curr_title_id_ = -1; }; diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 96aac1f61..d994f4089 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -599,7 +599,7 @@ dword_result_t XamUserCreateAchievementEnumerator(dword_t title_id, xe::copy_and_swap(place_addr, placeholder_val, wcslen(placeholder_val)); } - std::vector achievements; + std::vector achievements; game_gpd->GetAchievements(&achievements); for (auto ach : achievements) { diff --git a/src/xenia/kernel/util/xdbf_utils.cc b/src/xenia/kernel/xam/xdbf/xdbf.cc similarity index 99% rename from src/xenia/kernel/util/xdbf_utils.cc rename to src/xenia/kernel/xam/xdbf/xdbf.cc index bce9b8eaf..ecef34136 100644 --- a/src/xenia/kernel/util/xdbf_utils.cc +++ b/src/xenia/kernel/xam/xdbf/xdbf.cc @@ -7,12 +7,13 @@ ****************************************************************************** */ -#include "xenia/kernel/util/xdbf_utils.h" +#include "xenia/kernel/xam/xdbf/xdbf.h" #include "xenia/base/string.h" namespace xe { namespace kernel { -namespace util { +namespace xam { +namespace xdbf { constexpr uint32_t kXdbfMagicXdbf = 'XDBF'; @@ -430,6 +431,7 @@ bool GpdFile::UpdateTitle(XdbfTitlePlayed title) { return UpdateEntry(ent); } -} // namespace util +} // namespace xdbf +} // namespace xam } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/util/xdbf_utils.h b/src/xenia/kernel/xam/xdbf/xdbf.h similarity index 69% rename from src/xenia/kernel/util/xdbf_utils.h rename to src/xenia/kernel/xam/xdbf/xdbf.h index 4d7eecb15..17e1988be 100644 --- a/src/xenia/kernel/util/xdbf_utils.h +++ b/src/xenia/kernel/xam/xdbf/xdbf.h @@ -7,8 +7,8 @@ ****************************************************************************** */ -#ifndef XENIA_KERNEL_UTIL_XDBF_UTILS_H_ -#define XENIA_KERNEL_UTIL_XDBF_UTILS_H_ +#ifndef XENIA_KERNEL_XAM_XDBF_XDBF_H_ +#define XENIA_KERNEL_XAM_XDBF_XDBF_H_ #include #include @@ -16,9 +16,12 @@ #include "xenia/base/clock.h" #include "xenia/base/memory.h" +#include "xenia/kernel/xam/xdbf/xdbf_xbox.h" + namespace xe { namespace kernel { -namespace util { +namespace xam { +namespace xdbf { // https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/XEX/SPA.h // https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/XEX/SPA.cpp @@ -59,118 +62,6 @@ enum class XdbfLocale : uint32_t { kChinese = 8, }; -struct XdbfStringTableEntry { - xe::be id; - xe::be string_length; -}; -static_assert_size(XdbfStringTableEntry, 4); - -#pragma pack(push, 1) -struct X_XDBF_HEADER { - xe::be magic; - xe::be version; - xe::be entry_count; - xe::be entry_used; - xe::be free_count; - xe::be free_used; -}; -static_assert_size(X_XDBF_HEADER, 24); - -struct X_XDBF_ENTRY { - xe::be section; - xe::be id; - xe::be offset; - xe::be size; -}; -static_assert_size(X_XDBF_ENTRY, 18); - -struct X_XDBF_FILELOC { - xe::be offset; - xe::be size; -}; -static_assert_size(X_XDBF_FILELOC, 8); - -struct X_XDBF_XSTC_DATA { - xe::be magic; - xe::be version; - xe::be size; - xe::be default_language; -}; -static_assert_size(X_XDBF_XSTC_DATA, 16); - -struct X_XDBF_XTHD_DATA { - xe::be magic; - xe::be version; - xe::be unk8; - xe::be title_id; - xe::be unk10; // always 1? - xe::be title_version_major; - xe::be title_version_minor; - xe::be title_version_build; - xe::be title_version_revision; - xe::be unk1C; - xe::be unk20; - xe::be unk24; - xe::be unk28; -}; -static_assert_size(X_XDBF_XTHD_DATA, 0x2C); - -struct X_XDBF_TABLE_HEADER { - xe::be magic; - xe::be version; - xe::be size; - xe::be count; -}; -static_assert_size(X_XDBF_TABLE_HEADER, 14); - -struct X_XDBF_SPA_ACHIEVEMENT { - xe::be id; - xe::be label_id; - xe::be description_id; - xe::be unachieved_id; - xe::be image_id; - xe::be gamerscore; - xe::be unkE; - xe::be flags; - xe::be unk14; - xe::be unk18; - xe::be unk1C; - xe::be unk20; -}; -static_assert_size(X_XDBF_SPA_ACHIEVEMENT, 0x24); - -struct X_XDBF_GPD_ACHIEVEMENT { - xe::be magic; - xe::be id; - xe::be image_id; - xe::be gamerscore; - xe::be flags; - xe::be unlock_time; - // wchar_t* title; - // wchar_t* description; - // 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_played; - // 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); @@ -371,8 +262,9 @@ class GpdFile : public XdbfFile { uint32_t title_id_ = -1; }; -} // namespace util +} // namespace xdbf +} // namespace xam } // namespace kernel } // namespace xe -#endif // XENIA_KERNEL_UTIL_XDBF_UTILS_H_ +#endif // XENIA_KERNEL_XAM_XDBF_XDBF_H_ diff --git a/src/xenia/kernel/xam/xdbf/xdbf_xbox.h b/src/xenia/kernel/xam/xdbf/xdbf_xbox.h new file mode 100644 index 000000000..cff02d68d --- /dev/null +++ b/src/xenia/kernel/xam/xdbf/xdbf_xbox.h @@ -0,0 +1,137 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2016 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_KERNEL_XAM_XDBF_XDBF_XBOX_H_ +#define XENIA_KERNEL_XAM_XDBF_XDBF_XBOX_H_ + +namespace xe { +namespace kernel { +namespace xam { +namespace xdbf { + +/* Native XDBF structs used by 360 are in this file */ + +struct XdbfStringTableEntry { + xe::be id; + xe::be string_length; +}; +static_assert_size(XdbfStringTableEntry, 4); + +#pragma pack(push, 1) +struct X_XDBF_HEADER { + xe::be magic; + xe::be version; + xe::be entry_count; + xe::be entry_used; + xe::be free_count; + xe::be free_used; +}; +static_assert_size(X_XDBF_HEADER, 24); + +struct X_XDBF_ENTRY { + xe::be section; + xe::be id; + xe::be offset; + xe::be size; +}; +static_assert_size(X_XDBF_ENTRY, 18); + +struct X_XDBF_FILELOC { + xe::be offset; + xe::be size; +}; +static_assert_size(X_XDBF_FILELOC, 8); + +struct X_XDBF_XSTC_DATA { + xe::be magic; + xe::be version; + xe::be size; + xe::be default_language; +}; +static_assert_size(X_XDBF_XSTC_DATA, 16); + +struct X_XDBF_XTHD_DATA { + xe::be magic; + xe::be version; + xe::be unk8; + xe::be title_id; + xe::be unk10; // always 1? + xe::be title_version_major; + xe::be title_version_minor; + xe::be title_version_build; + xe::be title_version_revision; + xe::be unk1C; + xe::be unk20; + xe::be unk24; + xe::be unk28; +}; +static_assert_size(X_XDBF_XTHD_DATA, 0x2C); + +struct X_XDBF_TABLE_HEADER { + xe::be magic; + xe::be version; + xe::be size; + xe::be count; +}; +static_assert_size(X_XDBF_TABLE_HEADER, 14); + +struct X_XDBF_SPA_ACHIEVEMENT { + xe::be id; + xe::be label_id; + xe::be description_id; + xe::be unachieved_id; + xe::be image_id; + xe::be gamerscore; + xe::be unkE; + xe::be flags; + xe::be unk14; + xe::be unk18; + xe::be unk1C; + xe::be unk20; +}; +static_assert_size(X_XDBF_SPA_ACHIEVEMENT, 0x24); + +struct X_XDBF_GPD_ACHIEVEMENT { + xe::be magic; + xe::be id; + xe::be image_id; + xe::be gamerscore; + xe::be flags; + xe::be unlock_time; + // wchar_t* title; + // wchar_t* description; + // 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_played; + // wchar_t* title_name; +}; +#pragma pack(pop) + +} // namespace xdbf +} // namespace xam +} // namespace kernel +} // namespace xe + +#endif // XENIA_KERNEL_XAM_XDBF_XDBF_XBOX_H_ \ No newline at end of file From fa611069841b63830d84a1a8361c7e1b86adc754 Mon Sep 17 00:00:00 2001 From: emoose Date: Sun, 18 Nov 2018 06:30:22 +0000 Subject: [PATCH 18/21] [Kernel] Remove redundant 'Xdbf' from names --- src/xenia/kernel/xam/apps/xgi_app.cc | 2 +- src/xenia/kernel/xam/user_profile.cc | 24 +++--- src/xenia/kernel/xam/xam_user.cc | 2 +- src/xenia/kernel/xam/xdbf/xdbf.cc | 106 +++++++++++++-------------- src/xenia/kernel/xam/xdbf/xdbf.h | 65 ++++++++-------- 5 files changed, 98 insertions(+), 101 deletions(-) diff --git a/src/xenia/kernel/xam/apps/xgi_app.cc b/src/xenia/kernel/xam/apps/xgi_app.cc index 420e72786..346e15c26 100644 --- a/src/xenia/kernel/xam/apps/xgi_app.cc +++ b/src/xenia/kernel/xam/apps/xgi_app.cc @@ -70,7 +70,7 @@ X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr, bool modified = false; auto* achievement = (X_XUSER_ACHIEVEMENT*)memory_->TranslateVirtual(achievements_ptr); - xdbf::XdbfAchievement ach; + xdbf::Achievement ach; for (uint32_t i = 0; i < achievement_count; i++, achievement++) { if (game_gpd->GetAchievement(achievement->achievement_id, &ach)) { if (!ach.IsUnlocked()) { diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index 25e5375c9..591762861 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -117,7 +117,7 @@ void UserProfile::LoadGpdFiles() { dash_gpd_.Read(mmap_->data(), mmap_->size()); mmap_->Close(); - std::vector titles; + std::vector titles; dash_gpd_.GetTitles(&titles); for (auto title : titles) { @@ -150,11 +150,11 @@ void UserProfile::LoadGpdFiles() { xdbf::GpdFile* UserProfile::SetTitleSpaData(const xdbf::SpaFile& spa_data) { uint32_t spa_title = spa_data.GetTitleId(); - std::vector spa_achievements; + std::vector spa_achievements; // TODO: let user choose locale? spa_data.GetAchievements(spa_data.GetDefaultLocale(), &spa_achievements); - xdbf::XdbfTitlePlayed title_info; + xdbf::TitlePlayed title_info; auto gpd = title_gpds_.find(spa_title); if (gpd != title_gpds_.end()) { @@ -232,7 +232,7 @@ xdbf::GpdFile* UserProfile::SetTitleSpaData(const xdbf::SpaFile& spa_data) { // Try copying achievement images if we can... for (auto ach : spa_achievements) { auto* image_entry = spa_data.GetEntry( - static_cast(xdbf::XdbfSpaSection::kImage), ach.image_id); + static_cast(xdbf::SpaSection::kImage), ach.image_id); if (image_entry) { title_gpd.UpdateEntry(*image_entry); } @@ -240,18 +240,18 @@ xdbf::GpdFile* UserProfile::SetTitleSpaData(const xdbf::SpaFile& spa_data) { // Try adding title image & name auto* title_image = - spa_data.GetEntry(static_cast(xdbf::XdbfSpaSection::kImage), - static_cast(xdbf::XdbfSpaID::Title)); + spa_data.GetEntry(static_cast(xdbf::SpaSection::kImage), + static_cast(xdbf::SpaID::Title)); if (title_image) { title_gpd.UpdateEntry(*title_image); } auto title_name = xe::to_wstring(spa_data.GetTitleName()); if (title_name.length()) { - xdbf::XdbfEntry title_name_ent; + xdbf::Entry title_name_ent; title_name_ent.info.section = - static_cast(xdbf::XdbfGpdSection::kString); - title_name_ent.info.id = static_cast(xdbf::XdbfSpaID::Title); + static_cast(xdbf::GpdSection::kString); + title_name_ent.info.id = static_cast(xdbf::SpaID::Title); title_name_ent.data.resize((title_name.length() + 1) * 2); xe::copy_and_swap((wchar_t*)title_name_ent.data.data(), title_name.c_str(), title_name.length()); @@ -272,7 +272,7 @@ xdbf::GpdFile* UserProfile::SetTitleSpaData(const xdbf::SpaFile& spa_data) { // Print achievement list to log, ATM there's no other way for users to see // achievement status... - std::vector achievements; + std::vector achievements; if (curr_gpd_->GetAchievements(&achievements)) { XELOGI("Achievement list:"); @@ -375,9 +375,9 @@ bool UserProfile::UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data) { } else { // Check if we need to update dashboard data... if (title_id != kDashboardID) { - xdbf::XdbfTitlePlayed title_info; + xdbf::TitlePlayed title_info; if (dash_gpd_.GetTitle(title_id, &title_info)) { - std::vector gpd_achievements; + std::vector gpd_achievements; gpd_data.GetAchievements(&gpd_achievements); uint32_t num_ach_total = 0; diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index d994f4089..83d0171a3 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -599,7 +599,7 @@ dword_result_t XamUserCreateAchievementEnumerator(dword_t title_id, xe::copy_and_swap(place_addr, placeholder_val, wcslen(placeholder_val)); } - std::vector achievements; + std::vector achievements; game_gpd->GetAchievements(&achievements); for (auto ach : achievements) { diff --git a/src/xenia/kernel/xam/xdbf/xdbf.cc b/src/xenia/kernel/xam/xdbf/xdbf.cc index ecef34136..f04a18656 100644 --- a/src/xenia/kernel/xam/xdbf/xdbf.cc +++ b/src/xenia/kernel/xam/xdbf/xdbf.cc @@ -36,7 +36,7 @@ bool XdbfFile::Read(const uint8_t* data, size_t data_size) { (uint8_t*)free_ptr + (sizeof(X_XDBF_FILELOC) * header_.free_count); for (uint32_t i = 0; i < header_.entry_used; i++) { - XdbfEntry entry; + Entry entry; memcpy(&entry.info, ptr, sizeof(X_XDBF_ENTRY)); entry.data.resize(entry.info.size); memcpy(entry.data.data(), data_ptr + entry.info.offset, entry.info.size); @@ -111,9 +111,9 @@ bool XdbfFile::Write(uint8_t* data, size_t* data_size) { return true; } -XdbfEntry* XdbfFile::GetEntry(uint16_t section, uint64_t id) const { +Entry* XdbfFile::GetEntry(uint16_t section, uint64_t id) const { for (size_t i = 0; i < entries_.size(); i++) { - auto* entry = (XdbfEntry*)&entries_[i]; + auto* entry = (Entry*)&entries_[i]; if (entry->info.section != section || entry->info.id != id) { continue; } @@ -124,9 +124,9 @@ XdbfEntry* XdbfFile::GetEntry(uint16_t section, uint64_t id) const { return nullptr; } -bool XdbfFile::UpdateEntry(XdbfEntry entry) { +bool XdbfFile::UpdateEntry(Entry entry) { for (size_t i = 0; i < entries_.size(); i++) { - auto* ent = (XdbfEntry*)&entries_[i]; + auto* ent = (Entry*)&entries_[i]; if (ent->info.section != entry.info.section || ent->info.id != entry.info.id) { continue; @@ -137,7 +137,7 @@ bool XdbfFile::UpdateEntry(XdbfEntry entry) { return true; } - XdbfEntry new_entry; + Entry new_entry; new_entry.info.section = entry.info.section; new_entry.info.id = entry.info.id; new_entry.info.size = (uint32_t)entry.data.size(); @@ -162,18 +162,17 @@ std::string GetStringTableEntry_(const uint8_t* table_start, uint16_t string_id, return ""; } -std::string SpaFile::GetStringTableEntry(XdbfLocale locale, +std::string SpaFile::GetStringTableEntry(Locale locale, uint16_t string_id) const { - auto xstr_table = - GetEntry(static_cast(XdbfSpaSection::kStringTable), - static_cast(locale)); + auto xstr_table = GetEntry(static_cast(SpaSection::kStringTable), + static_cast(locale)); if (!xstr_table) { return ""; } auto xstr_head = reinterpret_cast(xstr_table->data.data()); - assert_true(xstr_head->magic == static_cast(XdbfSpaID::Xstr)); + assert_true(xstr_head->magic == static_cast(SpaID::Xstr)); assert_true(xstr_head->version == 1); const uint8_t* ptr = xstr_table->data.data() + sizeof(X_XDBF_TABLE_HEADER); @@ -182,28 +181,27 @@ std::string SpaFile::GetStringTableEntry(XdbfLocale locale, } uint32_t SpaFile::GetAchievements( - XdbfLocale locale, std::vector* achievements) const { - auto xach_table = GetEntry(static_cast(XdbfSpaSection::kMetadata), - static_cast(XdbfSpaID::Xach)); + Locale locale, std::vector* achievements) const { + auto xach_table = GetEntry(static_cast(SpaSection::kMetadata), + static_cast(SpaID::Xach)); if (!xach_table) { return 0; } auto xach_head = reinterpret_cast(xach_table->data.data()); - assert_true(xach_head->magic == static_cast(XdbfSpaID::Xach)); + assert_true(xach_head->magic == static_cast(SpaID::Xach)); assert_true(xach_head->version == 1); - auto xstr_table = - GetEntry(static_cast(XdbfSpaSection::kStringTable), - static_cast(locale)); + auto xstr_table = GetEntry(static_cast(SpaSection::kStringTable), + static_cast(locale)); if (!xstr_table) { return 0; } auto xstr_head = reinterpret_cast(xstr_table->data.data()); - assert_true(xstr_head->magic == static_cast(XdbfSpaID::Xstr)); + assert_true(xstr_head->magic == static_cast(SpaID::Xstr)); assert_true(xstr_head->version == 1); const uint8_t* xstr_ptr = @@ -213,7 +211,7 @@ uint32_t SpaFile::GetAchievements( auto* ach_data = reinterpret_cast(xach_head + 1); for (uint32_t i = 0; i < xach_head->count; i++) { - XdbfAchievement ach; + Achievement ach; ach.id = ach_data->id; ach.image_id = ach_data->image_id; ach.gamerscore = ach_data->gamerscore; @@ -236,47 +234,47 @@ uint32_t SpaFile::GetAchievements( return xach_head->count; } -XdbfEntry* SpaFile::GetIcon() const { - return GetEntry(static_cast(XdbfSpaSection::kImage), - static_cast(XdbfSpaID::Title)); +Entry* SpaFile::GetIcon() const { + return GetEntry(static_cast(SpaSection::kImage), + static_cast(SpaID::Title)); } -XdbfLocale SpaFile::GetDefaultLocale() const { - auto block = GetEntry(static_cast(XdbfSpaSection::kMetadata), - static_cast(XdbfSpaID::Xstc)); +Locale SpaFile::GetDefaultLocale() const { + auto block = GetEntry(static_cast(SpaSection::kMetadata), + static_cast(SpaID::Xstc)); if (!block) { - return XdbfLocale::kEnglish; + return Locale::kEnglish; } auto xstc = reinterpret_cast(block->data.data()); - assert_true(xstc->magic == static_cast(XdbfSpaID::Xstc)); + assert_true(xstc->magic == static_cast(SpaID::Xstc)); - return static_cast(static_cast(xstc->default_language)); + return static_cast(static_cast(xstc->default_language)); } std::string SpaFile::GetTitleName() const { return GetStringTableEntry(GetDefaultLocale(), - static_cast(XdbfSpaID::Title)); + static_cast(SpaID::Title)); } uint32_t SpaFile::GetTitleId() const { - auto block = GetEntry(static_cast(XdbfSpaSection::kMetadata), - static_cast(XdbfSpaID::Xthd)); + auto block = GetEntry(static_cast(SpaSection::kMetadata), + static_cast(SpaID::Xthd)); if (!block) { return -1; } auto xthd = reinterpret_cast(block->data.data()); - assert_true(xthd->magic == static_cast(XdbfSpaID::Xthd)); + assert_true(xthd->magic == static_cast(SpaID::Xthd)); return xthd->title_id; } -bool GpdFile::GetAchievement(uint16_t id, XdbfAchievement* dest) { +bool GpdFile::GetAchievement(uint16_t id, Achievement* dest) { for (size_t i = 0; i < entries_.size(); i++) { - auto* entry = (XdbfEntry*)&entries_[i]; + auto* entry = (Entry*)&entries_[i]; if (entry->info.section != - static_cast(XdbfGpdSection::kAchievement) || + static_cast(GpdSection::kAchievement) || entry->info.id != id) { continue; } @@ -294,13 +292,13 @@ bool GpdFile::GetAchievement(uint16_t id, XdbfAchievement* dest) { } uint32_t GpdFile::GetAchievements( - std::vector* achievements) const { + std::vector* achievements) const { uint32_t ach_count = 0; for (size_t i = 0; i < entries_.size(); i++) { - auto* entry = (XdbfEntry*)&entries_[i]; + auto* entry = (Entry*)&entries_[i]; if (entry->info.section != - static_cast(XdbfGpdSection::kAchievement)) { + static_cast(GpdSection::kAchievement)) { continue; } if (entry->info.id == 0x100000000 || entry->info.id == 0x200000000) { @@ -313,7 +311,7 @@ uint32_t GpdFile::GetAchievements( auto* ach_data = reinterpret_cast(entry->data.data()); - XdbfAchievement ach; + Achievement ach; ach.ReadGPD(ach_data); achievements->push_back(ach); @@ -323,10 +321,10 @@ uint32_t GpdFile::GetAchievements( return ach_count; } -bool GpdFile::GetTitle(uint32_t title_id, XdbfTitlePlayed* dest) { +bool GpdFile::GetTitle(uint32_t title_id, TitlePlayed* dest) { for (size_t i = 0; i < entries_.size(); i++) { - auto* entry = (XdbfEntry*)&entries_[i]; - if (entry->info.section != static_cast(XdbfGpdSection::kTitle) || + auto* entry = (Entry*)&entries_[i]; + if (entry->info.section != static_cast(GpdSection::kTitle) || entry->info.id != title_id) { continue; } @@ -342,12 +340,12 @@ bool GpdFile::GetTitle(uint32_t title_id, XdbfTitlePlayed* dest) { return false; } -uint32_t GpdFile::GetTitles(std::vector* titles) const { +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)) { + auto* entry = (Entry*)&entries_[i]; + if (entry->info.section != static_cast(GpdSection::kTitle)) { continue; } if (entry->info.id == 0x100000000 || entry->info.id == 0x200000000) { @@ -360,7 +358,7 @@ uint32_t GpdFile::GetTitles(std::vector* titles) const { auto* title_data = reinterpret_cast(entry->data.data()); - XdbfTitlePlayed title; + TitlePlayed title; title.ReadGPD(title_data); titles->push_back(title); } @@ -369,9 +367,9 @@ uint32_t GpdFile::GetTitles(std::vector* titles) const { return title_count; } -bool GpdFile::UpdateAchievement(XdbfAchievement ach) { - XdbfEntry ent; - ent.info.section = static_cast(XdbfGpdSection::kAchievement); +bool GpdFile::UpdateAchievement(Achievement ach) { + Entry ent; + ent.info.section = static_cast(GpdSection::kAchievement); ent.info.id = ach.id; // calculate entry size... @@ -387,7 +385,7 @@ bool GpdFile::UpdateAchievement(XdbfAchievement ach) { ent.data.resize(est_size); memset(ent.data.data(), 0, est_size); - // convert XdbfAchievement to GPD achievement + // convert Achievement to GPD achievement auto* ach_data = reinterpret_cast(ent.data.data()); ach_data->id = ach.id; ach_data->image_id = ach.image_id; @@ -410,9 +408,9 @@ bool GpdFile::UpdateAchievement(XdbfAchievement ach) { return UpdateEntry(ent); } -bool GpdFile::UpdateTitle(XdbfTitlePlayed title) { - XdbfEntry ent; - ent.info.section = static_cast(XdbfGpdSection::kTitle); +bool GpdFile::UpdateTitle(TitlePlayed title) { + Entry ent; + ent.info.section = static_cast(GpdSection::kTitle); ent.info.id = title.title_id; // calculate entry size... diff --git a/src/xenia/kernel/xam/xdbf/xdbf.h b/src/xenia/kernel/xam/xdbf/xdbf.h index 17e1988be..de12617af 100644 --- a/src/xenia/kernel/xam/xdbf/xdbf.h +++ b/src/xenia/kernel/xam/xdbf/xdbf.h @@ -26,7 +26,7 @@ namespace xdbf { // https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/XEX/SPA.h // https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/XEX/SPA.cpp -enum class XdbfSpaID : uint64_t { +enum class SpaID : uint64_t { Xach = 'XACH', Xstr = 'XSTR', Xstc = 'XSTC', @@ -34,13 +34,13 @@ enum class XdbfSpaID : uint64_t { Title = 0x8000, }; -enum class XdbfSpaSection : uint16_t { +enum class SpaSection : uint16_t { kMetadata = 0x1, kImage = 0x2, kStringTable = 0x3, }; -enum class XdbfGpdSection : uint16_t { +enum class GpdSection : uint16_t { kAchievement = 0x1, kImage = 0x2, kSetting = 0x3, @@ -50,7 +50,7 @@ enum class XdbfGpdSection : uint16_t { }; // Found by dumping the kSectionStringTable sections of various games: -enum class XdbfLocale : uint32_t { +enum class Locale : uint32_t { kUnknown = 0, kEnglish = 1, kJapanese = 2, @@ -73,7 +73,7 @@ inline std::wstring ReadNullTermString(const wchar_t* ptr) { return retval; } -struct XdbfTitlePlayed { +struct TitlePlayed { uint32_t title_id = 0; std::wstring title_name; uint32_t achievements_possible = 0; @@ -123,7 +123,7 @@ struct XdbfTitlePlayed { } }; -enum class XdbfAchievementType : uint32_t { +enum class AchievementType : uint32_t { kCompletion = 1, kLeveling = 2, kUnlock = 3, @@ -133,14 +133,14 @@ enum class XdbfAchievementType : uint32_t { kOther = 7, }; -enum class XdbfAchievementFlags : uint32_t { +enum class AchievementFlags : uint32_t { kTypeMask = 0x7, kShowUnachieved = 0x8, kAchievedOnline = 0x10000, kAchieved = 0x20000 }; -struct XdbfAchievement { +struct Achievement { uint16_t id = 0; std::wstring label; std::wstring description; @@ -150,32 +150,31 @@ struct XdbfAchievement { uint32_t flags = 0; uint64_t unlock_time = 0; - XdbfAchievementType GetType() { - return static_cast( - flags & static_cast(XdbfAchievementFlags::kTypeMask)); + AchievementType GetType() { + return static_cast( + flags & static_cast(AchievementFlags::kTypeMask)); } bool IsUnlocked() { - return flags & static_cast(XdbfAchievementFlags::kAchieved); + return flags & static_cast(AchievementFlags::kAchieved); } bool IsUnlockedOnline() { - return flags & static_cast(XdbfAchievementFlags::kAchievedOnline); + return flags & static_cast(AchievementFlags::kAchievedOnline); } void Unlock(bool online = false) { - flags |= static_cast(XdbfAchievementFlags::kAchieved); + flags |= static_cast(AchievementFlags::kAchieved); if (online) { - flags |= static_cast(XdbfAchievementFlags::kAchievedOnline); + flags |= static_cast(AchievementFlags::kAchievedOnline); } unlock_time = Clock::QueryHostSystemTime(); } void Lock() { - flags = flags & ~(static_cast(XdbfAchievementFlags::kAchieved)); - flags = - flags & ~(static_cast(XdbfAchievementFlags::kAchievedOnline)); + flags = flags & ~(static_cast(AchievementFlags::kAchieved)); + flags = flags & ~(static_cast(AchievementFlags::kAchievedOnline)); unlock_time = 0; } @@ -198,7 +197,7 @@ struct XdbfAchievement { } }; -struct XdbfEntry { +struct Entry { X_XDBF_ENTRY info; std::vector data; }; @@ -215,26 +214,26 @@ class XdbfFile { bool Read(const uint8_t* data, size_t data_size); bool Write(uint8_t* data, size_t* data_size); - XdbfEntry* GetEntry(uint16_t section, uint64_t id) const; + Entry* GetEntry(uint16_t section, uint64_t id) const; // Updates (or adds) an entry - bool UpdateEntry(XdbfEntry entry); + bool UpdateEntry(Entry entry); protected: X_XDBF_HEADER header_; - std::vector entries_; + std::vector entries_; std::vector free_entries_; }; class SpaFile : public XdbfFile { public: - std::string GetStringTableEntry(XdbfLocale locale, uint16_t string_id) const; + std::string GetStringTableEntry(Locale locale, uint16_t string_id) const; - uint32_t GetAchievements(XdbfLocale locale, - std::vector* achievements) const; + uint32_t GetAchievements(Locale locale, + std::vector* achievements) const; - XdbfEntry* GetIcon() const; - XdbfLocale GetDefaultLocale() const; + Entry* GetIcon() const; + Locale GetDefaultLocale() const; std::string GetTitleName() const; uint32_t GetTitleId() const; }; @@ -244,17 +243,17 @@ class GpdFile : public XdbfFile { GpdFile() : title_id_(-1) {} GpdFile(uint32_t title_id) : title_id_(title_id) {} - bool GetAchievement(uint16_t id, XdbfAchievement* dest); - uint32_t GetAchievements(std::vector* achievements) const; + bool GetAchievement(uint16_t id, Achievement* dest); + uint32_t GetAchievements(std::vector* achievements) const; - bool GetTitle(uint32_t title_id, XdbfTitlePlayed* title); - uint32_t GetTitles(std::vector* titles) const; + bool GetTitle(uint32_t title_id, TitlePlayed* title); + uint32_t GetTitles(std::vector* titles) const; // Updates (or adds) an achievement - bool UpdateAchievement(XdbfAchievement ach); + bool UpdateAchievement(Achievement ach); // Updates (or adds) a title - bool UpdateTitle(XdbfTitlePlayed title); + bool UpdateTitle(TitlePlayed title); uint32_t GetTitleId() { return title_id_; } From 981eff5902bc6367019efb2f3d36ea955accd610 Mon Sep 17 00:00:00 2001 From: emoose Date: Tue, 4 Dec 2018 04:01:30 +0000 Subject: [PATCH 19/21] [Kernel] Move HmacSha function to seperate file, add RC4 --- src/xenia/kernel/util/crypto_utils.cc | 130 ++++++++++++++++++++ src/xenia/kernel/util/crypto_utils.h | 28 +++++ src/xenia/kernel/xboxkrnl/xboxkrnl_crypt.cc | 65 +--------- 3 files changed, 162 insertions(+), 61 deletions(-) create mode 100644 src/xenia/kernel/util/crypto_utils.cc create mode 100644 src/xenia/kernel/util/crypto_utils.h diff --git a/src/xenia/kernel/util/crypto_utils.cc b/src/xenia/kernel/util/crypto_utils.cc new file mode 100644 index 000000000..9c08441cf --- /dev/null +++ b/src/xenia/kernel/util/crypto_utils.cc @@ -0,0 +1,130 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ +#include + +#include "xenia/kernel/util/crypto_utils.h" +#include "xenia/xbox.h" + +#include "third_party/crypto/TinySHA1.hpp" + +namespace xe { +namespace kernel { +namespace util { + +uint8_t xekey_0x19[] = {0xE1, 0xBC, 0x15, 0x9C, 0x73, 0xB1, 0xEA, 0xE9, + 0xAB, 0x31, 0x70, 0xF3, 0xAD, 0x47, 0xEB, 0xF3}; + +uint8_t xekey_0x19_devkit[] = {0xDA, 0xB6, 0x9A, 0xD9, 0x8E, 0x28, 0x76, 0x4F, + 0x97, 0x7E, 0xE2, 0x48, 0x7E, 0x4F, 0x3F, 0x68}; + +const uint8_t* GetXeKey(uint32_t idx, bool devkit) { + if (idx != 0x19) { + return nullptr; + } + + return devkit ? xekey_0x19_devkit : xekey_0x19; +} + +void HmacSha(const uint8_t* key, uint32_t key_size_in, const uint8_t* inp_1, + uint32_t inp_1_size, const uint8_t* inp_2, uint32_t inp_2_size, + const uint8_t* inp_3, uint32_t inp_3_size, uint8_t* out, + uint32_t out_size) { + uint32_t key_size = key_size_in; + sha1::SHA1 sha; + uint8_t kpad_i[0x40]; + uint8_t kpad_o[0x40]; + uint8_t tmp_key[0x40]; + std::memset(kpad_i, 0x36, 0x40); + std::memset(kpad_o, 0x5C, 0x40); + + // Setup HMAC key + // If > block size, use its hash + if (key_size > 0x40) { + sha1::SHA1 sha_key; + sha_key.processBytes(key, key_size); + sha_key.finalize((uint8_t*)tmp_key); + + key_size = 0x14u; + } else { + std::memcpy(tmp_key, key, key_size); + } + + for (uint32_t i = 0; i < key_size; i++) { + kpad_i[i] = tmp_key[i] ^ 0x36; + kpad_o[i] = tmp_key[i] ^ 0x5C; + } + + // Inner + sha.processBytes(kpad_i, 0x40); + + if (inp_1_size) { + sha.processBytes(inp_1, inp_1_size); + } + + if (inp_2_size) { + sha.processBytes(inp_2, inp_2_size); + } + + if (inp_3_size) { + sha.processBytes(inp_3, inp_3_size); + } + + uint8_t digest[0x14]; + sha.finalize(digest); + sha.reset(); + + // Outer + sha.processBytes(kpad_o, 0x40); + sha.processBytes(digest, 0x14); + sha.finalize(digest); + + std::memcpy(out, digest, std::min((uint32_t)out_size, 0x14u)); +} + +void RC4(const uint8_t* key, uint32_t key_size_in, const uint8_t* data, + uint32_t data_size, uint8_t* out, uint32_t out_size) { + uint8_t tmp_key[0x10]; + uint32_t sbox_size; + uint8_t sbox[0x100]; + uint32_t i; + uint32_t j; + + // Setup RC4 session... + std::memcpy(tmp_key, key, 0x10); + i = j = 0; + sbox_size = 0x100; + for (uint32_t x = 0; x < sbox_size; x++) { + sbox[x] = (uint8_t)x; + } + + uint32_t idx = 0; + for (uint32_t x = 0; x < sbox_size; x++) { + idx = (idx + sbox[x] + key[x % 0x10]) % sbox_size; + uint8_t temp = sbox[idx]; + sbox[idx] = sbox[x]; + sbox[x] = temp; + } + + // Crypt data + for (uint32_t idx = 0; idx < data_size; idx++) { + i = (i + 1) % sbox_size; + j = (j + sbox[i]) % sbox_size; + uint8_t temp = sbox[i]; + sbox[i] = sbox[j]; + sbox[j] = temp; + + uint8_t a = data[idx]; + uint8_t b = sbox[(sbox[i] + sbox[j]) % sbox_size]; + out[idx] = (uint8_t)(a ^ b); + } +} + +} // namespace util +} // namespace kernel +} // namespace xe diff --git a/src/xenia/kernel/util/crypto_utils.h b/src/xenia/kernel/util/crypto_utils.h new file mode 100644 index 000000000..456b7cff7 --- /dev/null +++ b/src/xenia/kernel/util/crypto_utils.h @@ -0,0 +1,28 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/xbox.h" + +namespace xe { +namespace kernel { +namespace util { + +const uint8_t* GetXeKey(uint32_t idx, bool devkit = false); + +void HmacSha(const uint8_t* key, uint32_t key_size_in, const uint8_t* inp_1, + uint32_t inp_1_size, const uint8_t* inp_2, uint32_t inp_2_size, + const uint8_t* inp_3, uint32_t inp_3_size, uint8_t* out, + uint32_t out_size); + +void RC4(const uint8_t* key, uint32_t key_size_in, const uint8_t* data, + uint32_t data_size, uint8_t* out, uint32_t out_size); + +} // namespace util +} // namespace kernel +} // namespace xe diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_crypt.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_crypt.cc index 3d78e5b48..507dc6860 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_crypt.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_crypt.cc @@ -9,6 +9,7 @@ #include "xenia/base/logging.h" #include "xenia/kernel/kernel_state.h" +#include "xenia/kernel/util/crypto_utils.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_private.h" #include "xenia/xbox.h" @@ -428,75 +429,17 @@ void XeCryptHmacSha(lpvoid_t key, dword_t key_size_in, lpvoid_t inp_1, dword_t inp_1_size, lpvoid_t inp_2, dword_t inp_2_size, lpvoid_t inp_3, dword_t inp_3_size, lpvoid_t out, dword_t out_size) { - uint32_t key_size = key_size_in; - sha1::SHA1 sha; - uint8_t kpad_i[0x40]; - uint8_t kpad_o[0x40]; - uint8_t tmp_key[0x40]; - std::memset(kpad_i, 0x36, 0x40); - std::memset(kpad_o, 0x5C, 0x40); - - // Setup HMAC key - // If > block size, use its hash - if (key_size > 0x40) { - sha1::SHA1 sha_key; - sha_key.processBytes(key, key_size); - sha_key.finalize((uint8_t*)tmp_key); - - key_size = 0x14u; - } else { - std::memcpy(tmp_key, key, key_size); - } - - for (uint32_t i = 0; i < key_size; i++) { - kpad_i[i] = tmp_key[i] ^ 0x36; - kpad_o[i] = tmp_key[i] ^ 0x5C; - } - - // Inner - sha.processBytes(kpad_i, 0x40); - - if (inp_1_size) { - sha.processBytes(inp_1, inp_1_size); - } - - if (inp_2_size) { - sha.processBytes(inp_2, inp_2_size); - } - - if (inp_3_size) { - sha.processBytes(inp_3, inp_3_size); - } - - uint8_t digest[0x14]; - sha.finalize(digest); - sha.reset(); - - // Outer - sha.processBytes(kpad_o, 0x40); - sha.processBytes(digest, 0x14); - sha.finalize(digest); - - std::memcpy(out, digest, std::min((uint32_t)out_size, 0x14u)); + util::HmacSha(key, key_size_in, inp_1, inp_1_size, inp_2, inp_2_size, inp_3, + inp_3_size, out, out_size); } DECLARE_XBOXKRNL_EXPORT1(XeCryptHmacSha, kNone, kImplemented); -// Keys -// TODO: Array of keys we need - -// Retail key 0x19 -static const uint8_t key19[] = {0xE1, 0xBC, 0x15, 0x9C, 0x73, 0xB1, 0xEA, 0xE9, - 0xAB, 0x31, 0x70, 0xF3, 0xAD, 0x47, 0xEB, 0xF3}; - dword_result_t XeKeysHmacSha(dword_t key_num, lpvoid_t inp_1, dword_t inp_1_size, lpvoid_t inp_2, dword_t inp_2_size, lpvoid_t inp_3, dword_t inp_3_size, lpvoid_t out, dword_t out_size) { - const uint8_t* key = nullptr; - if (key_num == 0x19) { - key = key19; - } + const uint8_t* key = util::GetXeKey(key_num); if (key) { XeCryptHmacSha((void*)key, 0x10, inp_1, inp_1_size, inp_2, inp_2_size, From ad47cd70228629aa74f54c7b5fb48b7ed6feaa88 Mon Sep 17 00:00:00 2001 From: emoose Date: Tue, 4 Dec 2018 04:10:33 +0000 Subject: [PATCH 20/21] [Kernel] Allow loading info from "Account" file (gamertag etc) This should let Xenia load in Account files from an actual 360, one step closer to fully supporting 360 profiles! Only decrypts/reads atm, but the code is there for re-encrypting, maybe once new UI is ready we can make use of that for creating new 360-compatible profiles? --- src/xenia/kernel/xam/user_profile.cc | 110 ++++++++++++++++++++++-- src/xenia/kernel/xam/user_profile.h | 120 +++++++++++++++++++++++++-- src/xenia/kernel/xam/xam_user.cc | 20 ----- 3 files changed, 219 insertions(+), 31 deletions(-) diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index 591762861..bd8156e59 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -14,11 +14,12 @@ #include -#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" +#include "xenia/kernel/util/crypto_utils.h" +#include "xenia/kernel/xam/user_profile.h" namespace xe { namespace kernel { @@ -29,9 +30,83 @@ DEFINE_string(profile_directory, "Content\\Profile\\", constexpr uint32_t kDashboardID = 0xFFFE07D1; +std::string X_XAMACCOUNTINFO::GetGamertagString() const { + return xe::to_string(std::wstring(gamertag)); +} + +bool UserProfile::DecryptAccountFile(const uint8_t* data, + X_XAMACCOUNTINFO* output, bool devkit) { + const uint8_t* key = util::GetXeKey(0x19, devkit); + if (!key) { + return false; // this shouldn't happen... + } + + // Generate RC4 key from data hash + uint8_t rc4_key[0x14]; + util::HmacSha(key, 0x10, data, 0x10, 0, 0, 0, 0, rc4_key, 0x14); + + uint8_t dec_data[sizeof(X_XAMACCOUNTINFO) + 8]; + + // Decrypt data + util::RC4(rc4_key, 0x10, data + 0x10, sizeof(dec_data), dec_data, + sizeof(dec_data)); + + // Verify decrypted data against hash + uint8_t data_hash[0x14]; + util::HmacSha(key, 0x10, dec_data, sizeof(dec_data), 0, 0, 0, 0, data_hash, + 0x14); + + if (std::memcmp(data, data_hash, 0x10) == 0) { + // Copy account data to output + std::memcpy(output, dec_data + 8, sizeof(X_XAMACCOUNTINFO)); + + // Swap gamertag endian + xe::copy_and_swap(output->gamertag, output->gamertag, 0x10); + return true; + } + + return false; +} + +void UserProfile::EncryptAccountFile(const X_XAMACCOUNTINFO* input, + uint8_t* output, bool devkit) { + const uint8_t* key = util::GetXeKey(0x19, devkit); + if (!key) { + return; // this shouldn't happen... + } + + X_XAMACCOUNTINFO* output_acct = (X_XAMACCOUNTINFO*)(output + 0x18); + std::memcpy(output_acct, input, sizeof(X_XAMACCOUNTINFO)); + + // Swap gamertag endian + xe::copy_and_swap(output_acct->gamertag, output_acct->gamertag, + 0x10); + + // Set confounder, should be random but meh + std::memset(output + 0x10, 0xFD, 8); + + // Encrypted data = xam account info + 8 byte confounder + uint32_t enc_data_size = sizeof(X_XAMACCOUNTINFO) + 8; + + // Set data hash + uint8_t data_hash[0x14]; + util::HmacSha(key, 0x10, output + 0x10, enc_data_size, 0, 0, 0, 0, data_hash, + 0x14); + + std::memcpy(output, data_hash, 0x10); + + // Generate RC4 key from data hash + uint8_t rc4_key[0x14]; + util::HmacSha(key, 0x10, data_hash, 0x10, 0, 0, 0, 0, rc4_key, 0x14); + + // Encrypt data + util::RC4(rc4_key, 0x10, output + 0x10, enc_data_size, output + 0x10, + enc_data_size); +} + UserProfile::UserProfile() : dash_gpd_(kDashboardID) { - xuid_ = 0xBABEBABEBABEBABE; - name_ = "User"; + account_.xuid_online = 0xBABEBABEBABEBABE; + wcscpy_s(account_.gamertag, L"XeniaUser"); // https://cs.rin.ru/forum/viewtopic.php?f=38&t=60668&hilit=gfwl+live&start=195 // https://github.com/arkem/py360/blob/master/py360/constants.py @@ -99,13 +174,36 @@ UserProfile::UserProfile() : dash_gpd_(kDashboardID) { AddSetting(std::make_unique(0x63E83FFD)); // Try loading profile GPD files... - LoadGpdFiles(); + LoadProfile(); } -void UserProfile::LoadGpdFiles() { +void UserProfile::LoadProfile() { + auto mmap_ = + MappedMemory::Open(xe::to_wstring(FLAGS_profile_directory) + L"Account", + MappedMemory::Mode::kRead); + if (mmap_) { + XELOGI("Loading Account file from path %sAccount", + FLAGS_profile_directory.c_str()); + + X_XAMACCOUNTINFO tmp_acct; + bool success = DecryptAccountFile(mmap_->data(), &tmp_acct); + if (!success) { + success = DecryptAccountFile(mmap_->data(), &tmp_acct, true); + } + + if (!success) { + XELOGW("Failed to decrypt Account file data"); + } else { + std::memcpy(&account_, &tmp_acct, sizeof(X_XAMACCOUNTINFO)); + XELOGI("Loaded Account \"%s\" successfully!", name().c_str()); + } + + mmap_->Close(); + } + XELOGI("Loading profile GPDs from path %s", FLAGS_profile_directory.c_str()); - auto mmap_ = MappedMemory::Open( + mmap_ = MappedMemory::Open( xe::to_wstring(FLAGS_profile_directory) + L"FFFE07D1.gpd", MappedMemory::Mode::kRead); if (!mmap_) { diff --git a/src/xenia/kernel/xam/user_profile.h b/src/xenia/kernel/xam/user_profile.h index 2b0ce4c18..76ad5f696 100644 --- a/src/xenia/kernel/xam/user_profile.h +++ b/src/xenia/kernel/xam/user_profile.h @@ -22,6 +22,111 @@ namespace xe { namespace kernel { namespace xam { +// from https://github.com/xemio/testdev/blob/master/xkelib/xam/_xamext.h +#pragma pack(push, 4) +struct X_XAMACCOUNTINFO { + enum AccountReservedFlags { + kPasswordProtected = 0x10000000, + kLiveEnabled = 0x20000000, + kRecovering = 0x40000000, + kVersionMask = 0x000000FF + }; + + enum AccountUserFlags { + kPaymentInstrumentCreditCard = 1, + + kCountryMask = 0xFF00, + kSubscriptionTierMask = 0xF00000, + kLanguageMask = 0x3E000000, + + kParentalControlEnabled = 0x1000000, + }; + + enum AccountSubscriptionTier { + kSubscriptionTierSilver = 3, + kSubscriptionTierGold = 6, + kSubscriptionTierFamilyGold = 9 + }; + + // already exists inside xdbf.h?? + enum AccountLanguage { + kNoLanguage, + kEnglish, + kJapanese, + kGerman, + kFrench, + kSpanish, + kItalian, + kKorean, + kTChinese, + kPortuguese, + kSChinese, + kPolish, + kRussian, + kNorwegian = 15 + }; + + enum AccountLiveFlags { kAcctRequiresManagement = 1 }; + + xe::be reserved_flags; + xe::be live_flags; + wchar_t gamertag[0x10]; + xe::be xuid_online; // 09.... + xe::be cached_user_flags; + xe::be network_id; + char passcode[4]; + char online_domain[0x14]; + char online_kerberos_realm[0x18]; + char online_key[0x10]; + char passport_membername[0x72]; + char passport_password[0x20]; + char owner_passport_membername[0x72]; + + bool IsPasscodeEnabled() { + return (bool)(reserved_flags & AccountReservedFlags::kPasswordProtected); + } + + bool IsLiveEnabled() { + return (bool)(reserved_flags & AccountReservedFlags::kLiveEnabled); + } + + bool IsRecovering() { + return (bool)(reserved_flags & AccountReservedFlags::kRecovering); + } + + bool IsPaymentInstrumentCreditCard() { + return (bool)(cached_user_flags & + AccountUserFlags::kPaymentInstrumentCreditCard); + } + + bool IsParentalControlled() { + return (bool)(cached_user_flags & + AccountUserFlags::kParentalControlEnabled); + } + + bool IsXUIDOffline() { return ((xuid_online >> 60) & 0xF) == 0xE; } + bool IsXUIDOnline() { return ((xuid_online >> 48) & 0xFFFF) == 0x9; } + bool IsXUIDValid() { return IsXUIDOffline() != IsXUIDOnline(); } + bool IsTeamXUID() { + return (xuid_online & 0xFF00000000000140) == 0xFE00000000000100; + } + + uint32_t GetCountry() { return (cached_user_flags & kCountryMask) >> 8; } + + AccountSubscriptionTier GetSubscriptionTier() { + return (AccountSubscriptionTier)( + (cached_user_flags & kSubscriptionTierMask) >> 20); + } + + AccountLanguage GetLanguage() { + return (AccountLanguage)((cached_user_flags & kLanguageMask) >> 25); + } + + std::string GetGamertagString() const; +}; +static_assert_size(X_XAMACCOUNTINFO, 0x17C); +#pragma pack(pop) + class UserProfile { public: struct Setting { @@ -198,10 +303,16 @@ class UserProfile { } }; + static bool DecryptAccountFile(const uint8_t* data, X_XAMACCOUNTINFO* output, + bool devkit = false); + + static void EncryptAccountFile(const X_XAMACCOUNTINFO* input, uint8_t* output, + bool devkit = false); + UserProfile(); - uint64_t xuid() const { return xuid_; } - std::string name() const { return name_; } + uint64_t xuid() const { return account_.xuid_online; } + std::string name() const { return account_.GetGamertagString(); } uint32_t signin_state() const { return 1; } void AddSetting(std::unique_ptr setting); @@ -216,11 +327,10 @@ class UserProfile { bool UpdateAllGpds(); private: - void LoadGpdFiles(); + void LoadProfile(); bool UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data); - uint64_t xuid_; - std::string name_; + X_XAMACCOUNTINFO account_; std::vector> setting_list_; std::unordered_map settings_; diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 83d0171a3..34e64db8d 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -21,26 +21,6 @@ namespace xe { namespace kernel { namespace xam { -// from https://github.com/xemio/testdev/blob/master/xkelib/xam/_xamext.h -#pragma pack(push, 4) -struct X_XAMACCOUNTINFO { - xe::be reserved; - xe::be live_flags; - wchar_t gamertag[0x10]; - xe::be xuid_online; // 09.... - xe::be user_flags; - xe::be network_id; - char passcode[4]; - char online_domain[0x14]; - char online_kerberos_realm[0x18]; - char online_key[0x10]; - char passport_membername[0x72]; - char passport_password[0x20]; - char owner_passport_membername[0x72]; -}; -static_assert_size(X_XAMACCOUNTINFO, 0x17C); -#pragma pack(pop) - struct X_PROFILEENUMRESULT { xe::be xuid_offline; // E0..... X_XAMACCOUNTINFO account; From e68057affb63d5786905415f4fe6995decce90ed Mon Sep 17 00:00:00 2001 From: emoose Date: Tue, 23 Jul 2019 19:22:45 +0100 Subject: [PATCH 21/21] [Kernel] Update profile-gpds to work with latest master --- src/xenia/kernel/xam/xam_user.cc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 34e64db8d..306bada4d 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -52,8 +52,7 @@ dword_result_t XamProfileCreateEnumerator(dword_t device_id, *handle_out = e->handle(); return X_ERROR_SUCCESS; } -DECLARE_XAM_EXPORT(XamProfileCreateEnumerator, - ExportTag::kUserProfiles | ExportTag::kImplemented); +DECLARE_XAM_EXPORT1(XamProfileCreateEnumerator, kUserProfiles, kImplemented); dword_result_t XamProfileEnumerate(dword_t handle, dword_t flags, lpvoid_t buffer, @@ -97,8 +96,7 @@ dword_result_t XamProfileEnumerate(dword_t handle, dword_t flags, return X_ERROR_INVALID_PARAMETER; } } -DECLARE_XAM_EXPORT(XamProfileEnumerate, - ExportTag::kUserProfiles | ExportTag::kImplemented); +DECLARE_XAM_EXPORT1(XamProfileEnumerate, kUserProfiles, kImplemented); X_HRESULT_result_t XamUserGetXUID(dword_t user_index, dword_t unk, lpqword_t xuid_ptr) {