From 859bb895553d82a34396afffd0b6d775b850243b Mon Sep 17 00:00:00 2001 From: Gliniak Date: Sun, 25 Dec 2022 13:16:30 +0100 Subject: [PATCH] [Kernel] Support for loading achievement data --- src/xenia/emulator.cc | 41 ++++++---- src/xenia/kernel/kernel_state.cc | 20 +++++ src/xenia/kernel/kernel_state.h | 3 + src/xenia/kernel/util/xdbf_utils.cc | 42 ++++++++-- src/xenia/kernel/util/xdbf_utils.h | 117 ++++++++++++++++------------ src/xenia/kernel/xam/xam_user.cc | 36 +++++---- 6 files changed, 175 insertions(+), 84 deletions(-) diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 38a2bb09c..cca28982f 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -55,6 +55,8 @@ #include "xenia/cpu/backend/x64/x64_backend.h" #endif // XE_ARCH +DECLARE_int32(user_language); + DEFINE_double(time_scalar, 1.0, "Scalar used to speed or slow time (1x, 2x, 1/2x, etc).", "General"); @@ -795,23 +797,28 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path, } game_config_load_callback_loop_next_index_ = SIZE_MAX; - uint32_t resource_data = 0; - uint32_t resource_size = 0; - if (XSUCCEEDED(module->GetSection(title_id.c_str(), &resource_data, - &resource_size))) { - kernel::util::XdbfGameData db( - module->memory()->TranslateVirtual(resource_data), resource_size); - if (db.is_valid()) { - // TODO(gibbed): get title respective to user locale. - title_name_ = db.title(XLanguage::kEnglish); - if (title_name_.empty()) { - // If English title is unavailable, get the title in default locale. - title_name_ = db.title(); - } - auto icon_block = db.icon(); - if (icon_block) { - display_window_->SetIcon(icon_block.buffer, icon_block.size); - } + const kernel::util::XdbfGameData db = kernel_state_->module_xdbf(module); + if (db.is_valid()) { + XLanguage language = + db.GetExistingLanguage(static_cast(cvars::user_language)); + title_name_ = db.title(language); + + XELOGI("-------------------- ACHIEVEMENTS --------------------"); + const std::vector + achievement_list = db.GetAchievements(); + for (const kernel::util::XdbfAchievementTableEntry& entry : + achievement_list) { + std::string label = db.GetStringTableEntry(language, entry.label_id); + std::string desc = + db.GetStringTableEntry(language, entry.description_id); + + XELOGI("{} - {} - {} - {}", entry.id, label, desc, entry.gamerscore); + } + XELOGI("----------------- END OF ACHIEVEMENTS ----------------"); + + auto icon_block = db.icon(); + if (icon_block) { + display_window_->SetIcon(icon_block.buffer, icon_block.size); } } } diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index 7d60aac64..95144ee09 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -104,6 +104,26 @@ uint32_t KernelState::title_id() const { return 0; } +util::XdbfGameData KernelState::title_xdbf() const { + return module_xdbf(executable_module_); +} + +util::XdbfGameData KernelState::module_xdbf( + object_ref exec_module) const { + assert_not_null(exec_module); + + uint32_t resource_data = 0; + uint32_t resource_size = 0; + if (XSUCCEEDED(exec_module->GetSection( + fmt::format("{:08X}", exec_module->title_id()).c_str(), + &resource_data, &resource_size))) { + util::XdbfGameData db(memory()->TranslateVirtual(resource_data), + resource_size); + return db; + } + return util::XdbfGameData(nullptr, resource_size); +} + uint32_t KernelState::process_type() const { auto pib = memory_->TranslateVirtual(process_info_block_address_); diff --git a/src/xenia/kernel/kernel_state.h b/src/xenia/kernel/kernel_state.h index 796d2dada..c38c7910b 100644 --- a/src/xenia/kernel/kernel_state.h +++ b/src/xenia/kernel/kernel_state.h @@ -23,6 +23,7 @@ #include "xenia/cpu/export_resolver.h" #include "xenia/kernel/util/native_list.h" #include "xenia/kernel/util/object_table.h" +#include "xenia/kernel/util/xdbf_utils.h" #include "xenia/kernel/xam/app_manager.h" #include "xenia/kernel/xam/content_manager.h" #include "xenia/kernel/xam/user_profile.h" @@ -99,6 +100,8 @@ class KernelState { vfs::VirtualFileSystem* file_system() const { return file_system_; } uint32_t title_id() const; + util::XdbfGameData title_xdbf() const; + util::XdbfGameData module_xdbf(object_ref exec_module) const; xam::AppManager* app_manager() const { return app_manager_.get(); } xam::ContentManager* content_manager() const { diff --git a/src/xenia/kernel/util/xdbf_utils.cc b/src/xenia/kernel/util/xdbf_utils.cc index c5e6fd217..f60efe306 100644 --- a/src/xenia/kernel/util/xdbf_utils.cc +++ b/src/xenia/kernel/util/xdbf_utils.cc @@ -16,6 +16,11 @@ namespace util { constexpr fourcc_t kXdbfSignatureXdbf = make_fourcc("XDBF"); constexpr fourcc_t kXdbfSignatureXstc = make_fourcc("XSTC"); constexpr fourcc_t kXdbfSignatureXstr = make_fourcc("XSTR"); +constexpr fourcc_t kXdbfSignatureXach = make_fourcc("XACH"); + +constexpr uint64_t kXdbfIdTitle = 0x8000; +constexpr uint64_t kXdbfIdXstc = 0x58535443; +constexpr uint64_t kXdbfIdXach = 0x58414348; XdbfWrapper::XdbfWrapper(const uint8_t* data, size_t data_size) : data_(data), data_size_(data_size) { @@ -64,12 +69,12 @@ std::string XdbfWrapper::GetStringTableEntry(XLanguage language, } auto xstr_head = - reinterpret_cast(language_block.buffer); + reinterpret_cast(language_block.buffer); assert_true(xstr_head->magic == kXdbfSignatureXstr); assert_true(xstr_head->version == 1); - const uint8_t* ptr = language_block.buffer + sizeof(XdbfXstrHeader); - for (uint16_t i = 0; i < xstr_head->string_count; ++i) { + const uint8_t* ptr = language_block.buffer + sizeof(XdbfSectionHeader); + for (uint16_t i = 0; i < xstr_head->count; ++i) { auto entry = reinterpret_cast(ptr); ptr += sizeof(XdbfStringTableEntry); if (entry->id == string_id) { @@ -81,8 +86,35 @@ std::string XdbfWrapper::GetStringTableEntry(XLanguage language, return ""; } -constexpr uint64_t kXdbfIdTitle = 0x8000; -constexpr uint64_t kXdbfIdXstc = 0x58535443; +std::vector XdbfWrapper::GetAchievements() const { + std::vector achievements; + + auto achievement_table = GetEntry(XdbfSection::kMetadata, kXdbfIdXach); + if (!achievement_table) { + return achievements; + } + + auto xach_head = + reinterpret_cast(achievement_table.buffer); + assert_true(xach_head->magic == kXdbfSignatureXach); + assert_true(xach_head->version == 1); + + const uint8_t* ptr = achievement_table.buffer + sizeof(XdbfSectionHeader); + for (uint16_t i = 0; i < xach_head->count; ++i) { + auto entry = reinterpret_cast(ptr); + ptr += sizeof(XdbfAchievementTableEntry); + achievements.push_back(*entry); + } + return achievements; + +} + +XLanguage XdbfGameData::GetExistingLanguage(XLanguage language_to_check) const { + // A bit of a hack. Check if title in specific language exist. + // If it doesn't then for sure language is not supported. + return title(language_to_check).empty() ? default_language() + : language_to_check; +} XdbfBlock XdbfGameData::icon() const { return GetEntry(XdbfSection::kImage, kXdbfIdTitle); diff --git a/src/xenia/kernel/util/xdbf_utils.h b/src/xenia/kernel/util/xdbf_utils.h index ff05a5b1b..f08fd0d6f 100644 --- a/src/xenia/kernel/util/xdbf_utils.h +++ b/src/xenia/kernel/util/xdbf_utils.h @@ -29,6 +29,70 @@ enum class XdbfSection : uint16_t { kStringTable = 0x0003, }; +#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 XdbfSectionHeader { + xe::be magic; + xe::be version; + xe::be size; + xe::be count; +}; +static_assert_size(XdbfSectionHeader, 14); + +struct XdbfStringTableEntry { + xe::be id; + xe::be string_length; +}; +static_assert_size(XdbfStringTableEntry, 4); + +struct XdbfAchievementTableEntry { + 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(XdbfAchievementTableEntry, 0x24); +#pragma pack(pop) + struct XdbfBlock { const uint8_t* buffer; size_t size; @@ -52,55 +116,7 @@ class XdbfWrapper { // Gets a string from the string table in the given language. // Returns the empty string if the entry is not found. std::string GetStringTableEntry(XLanguage language, 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) + std::vector GetAchievements() const; private: const uint8_t* data_ = nullptr; @@ -117,6 +133,9 @@ class XdbfGameData : public XdbfWrapper { XdbfGameData(const uint8_t* data, size_t data_size) : XdbfWrapper(data, data_size) {} + // Checks if provided language exist, if not returns default title language. + XLanguage GetExistingLanguage(XLanguage language_to_check) const; + // The game icon image, if found. XdbfBlock icon() const; diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 2155504e8..f9ff58439 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -20,6 +20,8 @@ #include "xenia/kernel/xthread.h" #include "xenia/xbox.h" +DECLARE_int32(user_language); + namespace xe { namespace kernel { namespace xam { @@ -672,19 +674,27 @@ dword_result_t XamUserCreateAchievementEnumerator_entry( return result; } - uint32_t dummy_count = std::min(100u, uint32_t(count)); - for (uint32_t i = 1; i <= dummy_count; ++i) { - auto item = XStaticAchievementEnumerator::AchievementDetails{ - i, // dummy achievement id - fmt::format(u"Dummy {}", i), - u"Dummy description", - u"Dummy unachieved", - i, // dummy image id - 0, - {0, 0}, - 8}; // flags=8 makes dummy achievements show up in 4D5307DC - // achievements list. - e->AppendItem(item); + const util::XdbfGameData db = kernel_state()->title_xdbf(); + + if (db.is_valid()) { + const XLanguage language = + db.GetExistingLanguage(static_cast(cvars::user_language)); + const std::vector achievement_list = + db.GetAchievements(); + + for (const util::XdbfAchievementTableEntry& entry : achievement_list) { + auto item = XStaticAchievementEnumerator::AchievementDetails{ + entry.id, + xe::to_utf16(db.GetStringTableEntry(language, entry.label_id)), + xe::to_utf16(db.GetStringTableEntry(language, entry.description_id)), + xe::to_utf16(db.GetStringTableEntry(language, entry.unachieved_id)), + entry.image_id, + entry.gamerscore, + {0, 0}, + entry.flags}; + + e->AppendItem(item); + } } *handle_ptr = e->handle();