diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 0184524bcbb..026f7e836e3 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -39,6 +39,7 @@ #include "xenia/gpu/graphics_system.h" #include "xenia/hid/input_driver.h" #include "xenia/hid/input_system.h" +#include "xenia/kernel/achievement_dispatcher.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/user_module.h" #include "xenia/kernel/util/gameinfo_utils.h" @@ -1339,6 +1340,9 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path, main_thread_ = main_thread; on_launch(title_id_.value(), title_name_); + kernel_state_->achievement_manager()->LoadTitleAchievements( + kernel_state_->user_profile(static_cast(0))->xuid(), + module->title_id()); // Plugins must be loaded after calling LaunchModule() and // FinishLoadingUserModule() which will apply TUs and patching to the main // xex. diff --git a/src/xenia/kernel/achievement_dispatcher.cc b/src/xenia/kernel/achievement_dispatcher.cc new file mode 100644 index 00000000000..885d69efe7f --- /dev/null +++ b/src/xenia/kernel/achievement_dispatcher.cc @@ -0,0 +1,253 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2024 Xenia Canary. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/kernel/achievement_dispatcher.h" +#include "xenia/emulator.h" +#include "xenia/gpu/graphics_system.h" +#include "xenia/kernel/kernel_state.h" +#include "xenia/kernel/util/shim_utils.h" +#include "xenia/ui/imgui_guest_notification.h" + +DEFINE_bool(show_achievement_notification, false, + "Show achievement notification on screen.", "UI"); + +DEFINE_string(default_achievement_backend, "", + "Defines which achievement backend should be used as an defult. " + "Possible options: [].", + "Achievements"); + +DECLARE_int32(user_language); + +namespace xe { +namespace kernel { + +GpdAchievementBackend::GpdAchievementBackend() { achievements_.clear(); } + +GpdAchievementBackend::~GpdAchievementBackend() {} + +void GpdAchievementBackend::EarnAchievement(const uint64_t xuid, + const uint32_t title_id, + const uint32_t achievement_id) { + const auto user = kernel_state()->user_profile(xuid); + if (!user) { + return; + } + + auto achievement = GetAchievementInfoInternal(xuid, title_id, achievement_id); + if (!achievement) { + return; + } + + XELOGI("Player: {} Unlocked Achievement: {}", user->name(), + xe::to_utf8(xe::load_and_swap( + achievement->achievement_name.c_str()))); + + const uint64_t unlock_time = Clock::QueryHostSystemTime(); + achievement->flags = + achievement->flags | static_cast(AchievementFlags::kAchieved); + + achievement->unlock_time.high_part = static_cast(unlock_time << 31); + achievement->unlock_time.low_part = static_cast(unlock_time); + + SaveAchievementData(xuid, title_id, achievement_id); +} + +AchievementGpdStructure* GpdAchievementBackend::GetAchievementInfoInternal( + const uint64_t xuid, const uint32_t title_id, + const uint32_t achievement_id) const { + const auto user = kernel_state()->user_profile(xuid); + if (!user) { + return nullptr; + } + + if (!achievements_.count(xuid)) { + return nullptr; + } + + for (auto& entry : achievements_.at(xuid)) { + if (entry.achievement_id == achievement_id) { + return const_cast(&entry); + } + } + + return nullptr; +} + +const AchievementGpdStructure* GpdAchievementBackend::GetAchievementInfo( + const uint64_t xuid, const uint32_t title_id, + const uint32_t achievement_id) const { + return GetAchievementInfoInternal(xuid, title_id, achievement_id); +} + +bool GpdAchievementBackend::IsAchievementUnlocked( + const uint64_t xuid, const uint32_t title_id, + const uint32_t achievement_id) const { + const auto achievement = + GetAchievementInfoInternal(xuid, title_id, achievement_id); + + return (achievement->flags & + static_cast(AchievementFlags::kAchieved)) != 0; +} + +const std::vector +GpdAchievementBackend::GetTitleAchievements(const uint64_t xuid, + const uint32_t title_id) const { + return {}; +} + +bool GpdAchievementBackend::LoadAchievementsData(const uint64_t xuid, + const uint32_t title_id) { + const util::XdbfGameData title_xdbf = kernel_state()->title_xdbf(); + if (!title_xdbf.is_valid()) { + return false; + } + + const auto achievements = title_xdbf.GetAchievements(); + if (achievements.empty()) { + return true; + } + + const XLanguage title_language = title_xdbf.GetExistingLanguage( + static_cast(cvars::user_language)); + for (const auto& achievement : achievements) { + const std::string label = + title_xdbf.GetStringTableEntry(title_language, achievement.label_id); + const std::string desc = title_xdbf.GetStringTableEntry( + title_language, achievement.description_id); + const std::string locked_desc = title_xdbf.GetStringTableEntry( + title_language, achievement.unachieved_id); + + AchievementGpdStructure achievementData = {}; + achievementData.struct_size = 0x1C; + achievementData.achievement_id = static_cast(achievement.id); + achievementData.image_id = achievement.image_id; + achievementData.gamerscore = static_cast(achievement.gamerscore); + achievementData.flags = achievement.flags; + achievementData.unlock_time.high_part = 0; + achievementData.unlock_time.low_part = 0; + achievementData.achievement_name = + xe::load_and_swap(xe::to_utf16(label).c_str()); + achievementData.unlocked_description = + xe::load_and_swap(xe::to_utf16(desc).c_str()); + achievementData.locked_description = + xe::load_and_swap(xe::to_utf16(locked_desc).c_str()); + + achievements_[xuid].push_back(achievementData); + } + + // TODO(Gliniak): Here should be loader of GPD file for loaded title. That way + // we can load flags and unlock_time from specific user. + return true; +} + +bool GpdAchievementBackend::SaveAchievementData(const uint64_t xuid, + const uint32_t title_id, + const uint32_t achievement_id) { + return true; +} + +AchievementDispatcher::AchievementDispatcher() { + default_achievements_backend_ = std::make_unique(); + + // Add any optional backend here. +}; +void AchievementDispatcher::EarnAchievement( + const uint32_t user_index, const uint32_t title_id, + const uint32_t achievement_id) const { + const auto user = kernel_state()->user_profile(user_index); + if (!user) { + return; + } + + EarnAchievement(user->xuid(), title_id, achievement_id); +}; + +void AchievementDispatcher::EarnAchievement( + const uint64_t xuid, const uint32_t title_id, + const uint32_t achievement_id) const { + if (!DoesAchievementExist(achievement_id)) { + XELOGW( + "{}: Achievement with ID: {} for title: {:08X} doesn't exist in " + "database!", + __func__, achievement_id, title_id); + return; + } + // Always send request to unlock in 3PP backends. It's up to them to check if + // achievement was unlocked + for (auto& backend : achievement_backends_) { + backend->EarnAchievement(xuid, title_id, achievement_id); + } + + if (default_achievements_backend_->IsAchievementUnlocked(xuid, title_id, + achievement_id)) { + return; + } + + default_achievements_backend_->EarnAchievement(xuid, title_id, + achievement_id); + + if (!cvars::show_achievement_notification) { + return; + } + + const auto achievement = default_achievements_backend_->GetAchievementInfo( + xuid, title_id, achievement_id); + + if (!achievement) { + // Something went really wrong! + return; + } + ShowAchievementEarnedNotification(achievement); +} + +void AchievementDispatcher::LoadTitleAchievements( + const uint64_t xuid, const uint32_t title_id) const { + default_achievements_backend_->LoadAchievementsData(xuid, title_id); +} + +const AchievementGpdStructure* AchievementDispatcher::GetAchievementInfo( + const uint64_t xuid, const uint32_t title_id, + const uint32_t achievement_id) const { + return default_achievements_backend_->GetAchievementInfo(xuid, title_id, + achievement_id); +} + +bool AchievementDispatcher::DoesAchievementExist( + const uint32_t achievement_id) const { + const util::XdbfGameData title_xdbf = kernel_state()->title_xdbf(); + const util::XdbfAchievementTableEntry achievement = + title_xdbf.GetAchievement(achievement_id); + + if (!achievement.id) { + return false; + } + return true; +} + +void AchievementDispatcher::ShowAchievementEarnedNotification( + const AchievementGpdStructure* achievement) const { + const std::string description = + fmt::format("{}G - {}", achievement->gamerscore, + xe::to_utf8(xe::load_and_swap( + achievement->achievement_name.c_str()))); + + const Emulator* emulator = kernel_state()->emulator(); + ui::WindowedAppContext& app_context = + emulator->display_window()->app_context(); + ui::ImGuiDrawer* imgui_drawer = emulator->imgui_drawer(); + + app_context.CallInUIThread([imgui_drawer, description]() { + new xe::ui::AchievementNotificationWindow( + imgui_drawer, "Achievement unlocked", description, 0, + kernel_state()->notification_position_); + }); +} + +} // namespace kernel +} // namespace xe diff --git a/src/xenia/kernel/achievement_dispatcher.h b/src/xenia/kernel/achievement_dispatcher.h new file mode 100644 index 00000000000..019d6d400e9 --- /dev/null +++ b/src/xenia/kernel/achievement_dispatcher.h @@ -0,0 +1,181 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2024 Xenia Canary. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_KERNEL_ACHIEVEMENT_DISPATCHER_H_ +#define XENIA_KERNEL_ACHIEVEMENT_DISPATCHER_H_ + +#include +#include +#include + +#include "xenia/xbox.h" + +namespace xe { +namespace kernel { + +struct X_ACHIEVEMENT_UNLOCK_TIME { + xe::be high_part; + xe::be low_part; +}; + +struct X_ACHIEVEMENT_DETAILS { + xe::be id; + xe::be label_ptr; + xe::be description_ptr; + xe::be unachieved_ptr; + xe::be image_id; + xe::be gamerscore; + X_ACHIEVEMENT_UNLOCK_TIME unlock_time; + xe::be flags; + + static const size_t kStringBufferSize = 464; +}; +static_assert_size(X_ACHIEVEMENT_DETAILS, 36); + +// This is structure used inside GPD file. +// GPD is writeable XDBF. +// There are two info instances +// 1. In Dashboard ID which contains single GPD that contains info about any +// booted game (name, title_id, last boot time etc) +// 2. In specific Title ID directory GPD contains there structure below for +// every achievement. (unlocked or not) +struct AchievementGpdStructure { + xe::be struct_size; + xe::be achievement_id; + xe::be image_id; + xe::be gamerscore; + xe::be flags; + X_ACHIEVEMENT_UNLOCK_TIME unlock_time; + std::u16string achievement_name; + std::u16string unlocked_description; + std::u16string locked_description; +}; + +enum class AchievementType : uint32_t { + kCompletion = 1, + kLeveling = 2, + kUnlock = 3, + kEvent = 4, + kTournament = 5, + kCheckpoint = 6, + kOther = 7, +}; + +enum class AchievementPlatform : uint32_t { + kX360 = 0x100000, + kPC = 0x200000, + kMobile = 0x300000, + kWebGames = 0x400000, +}; + +enum class AchievementFlags : uint32_t { + kTypeMask = 0x7, + kShowUnachieved = 0x8, + kAchievedOnline = 0x10000, + kAchieved = 0x20000, + kNotAchievable = 0x40000, + kWasNotAchievable = 0x80000, + kPlatformMask = 0x700000, + kColorizable = 0x1000000, // avatar awards only? +}; + +class AchievementBackendInterface { + public: + virtual ~AchievementBackendInterface(){}; + + virtual void EarnAchievement(const uint64_t xuid, const uint32_t title_id, + const uint32_t achievement_id) = 0; + + virtual bool IsAchievementUnlocked(const uint64_t xuid, + const uint32_t title_id, + const uint32_t achievement_id) const = 0; + + virtual const AchievementGpdStructure* GetAchievementInfo( + const uint64_t xuid, const uint32_t title_id, + const uint32_t achievement_id) const = 0; + virtual const std::vector GetTitleAchievements( + const uint64_t xuid, const uint32_t title_id) const = 0; + virtual bool LoadAchievementsData(const uint64_t xuid, + const uint32_t title_id) = 0; + + private: + virtual bool SaveAchievementsData(const uint64_t xuid, + const uint32_t title_id) = 0; + virtual bool SaveAchievementData(const uint64_t xuid, const uint32_t title_id, + const uint32_t achievement_id) = 0; +}; + +class GpdAchievementBackend : public AchievementBackendInterface { + public: + GpdAchievementBackend(); + ~GpdAchievementBackend(); + + void EarnAchievement(const uint64_t xuid, const uint32_t title_id, + const uint32_t achievement_id) override; + bool IsAchievementUnlocked(const uint64_t xuid, const uint32_t title_id, + const uint32_t achievement_id) const override; + const AchievementGpdStructure* GetAchievementInfo( + const uint64_t xuid, const uint32_t title_id, + const uint32_t achievement_id) const override; + const std::vector GetTitleAchievements( + const uint64_t xuid, const uint32_t title_id) const override; + bool LoadAchievementsData(const uint64_t xuid, + const uint32_t title_id) override; + + private: + using UsersAchievements = + std::map>; + + AchievementGpdStructure* GetAchievementInfoInternal( + const uint64_t xuid, const uint32_t title_id, + const uint32_t achievement_id) const; + + bool SaveAchievementsData(const uint64_t xuid, + const uint32_t title_id) override { + return 0; + }; + bool SaveAchievementData(const uint64_t xuid, const uint32_t title_id, + const uint32_t achievement_id) override; + + UsersAchievements achievements_; +}; + +class AchievementDispatcher { + public: + AchievementDispatcher(); + + void LoadTitleAchievements(const uint64_t xuid, + const uint32_t title_id) const; + + void EarnAchievement(const uint32_t user_index, const uint32_t title_id, + const uint32_t achievement_id) const; + void EarnAchievement(const uint64_t xuid, const uint32_t title_id, + const uint32_t achievement_id) const; + const AchievementGpdStructure* GetAchievementInfo( + const uint64_t xuid, const uint32_t title_id, + const uint32_t achievement_id) const; + const std::vector GetTitleAchievements( + const uint64_t xuid, const uint32_t title_id) const; + + private: + bool DoesAchievementExist(const uint32_t achievement_id) const; + void ShowAchievementEarnedNotification( + const AchievementGpdStructure* achievement) const; + + // This contains all backends with exception of default storage. + std::vector> + achievement_backends_; + + std::unique_ptr default_achievements_backend_; +}; + +} // namespace kernel +} // namespace xe + +#endif // XENIA_KERNEL_ACHIEVEMENT_MANAGER_H_ diff --git a/src/xenia/kernel/achievement_manager.cc b/src/xenia/kernel/achievement_manager.cc deleted file mode 100644 index a555ada39f5..00000000000 --- a/src/xenia/kernel/achievement_manager.cc +++ /dev/null @@ -1,89 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2023 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "achievement_manager.h" -#include "xenia/emulator.h" -#include "xenia/gpu/graphics_system.h" -#include "xenia/kernel/kernel_state.h" -#include "xenia/kernel/util/shim_utils.h" -#include "xenia/ui/imgui_guest_notification.h" - -DEFINE_bool(show_achievement_notification, false, - "Show achievement notification on screen.", "UI"); - -DECLARE_int32(user_language); - -namespace xe { -namespace kernel { - -AchievementManager::AchievementManager() { unlocked_achievements.clear(); }; - -void AchievementManager::EarnAchievement(uint64_t xuid, uint32_t title_id, - uint32_t achievement_id) { - if (IsAchievementUnlocked(achievement_id)) { - return; - } - - const Emulator* emulator = kernel_state()->emulator(); - ui::WindowedAppContext& app_context = - kernel_state()->emulator()->display_window()->app_context(); - ui::ImGuiDrawer* imgui_drawer = emulator->imgui_drawer(); - - const util::XdbfGameData title_xdbf = kernel_state()->title_xdbf(); - const util::XdbfAchievementTableEntry achievement = - title_xdbf.GetAchievement(achievement_id); - - if (!achievement.id) { - return; - } - - const XLanguage title_language = title_xdbf.GetExistingLanguage( - static_cast(cvars::user_language)); - - const std::string label = - title_xdbf.GetStringTableEntry(title_language, achievement.label_id); - const std::string desc = title_xdbf.GetStringTableEntry( - title_language, achievement.description_id); - - XELOGI("Achievement unlocked: {}", label); - - unlocked_achievements[achievement_id] = Clock::QueryHostSystemTime(); - // Even if we disable popup we still should store info that this - // achievement was earned. - if (!cvars::show_achievement_notification) { - return; - } - - const std::string description = - fmt::format("{}G - {}", achievement.gamerscore, label); - - app_context.CallInUIThread([imgui_drawer, description]() { - new xe::ui::AchievementNotificationWindow( - imgui_drawer, "Achievement unlocked", description, 0, - kernel_state()->notification_position_); - }); -} - -bool AchievementManager::IsAchievementUnlocked(uint32_t achievement_id) { - auto itr = unlocked_achievements.find(achievement_id); - - return itr != unlocked_achievements.cend(); -} - -uint64_t AchievementManager::GetAchievementUnlockTime(uint32_t achievement_id) { - auto itr = unlocked_achievements.find(achievement_id); - if (itr == unlocked_achievements.cend()) { - return 0; - } - - return itr->second; -} - -} // namespace kernel -} // namespace xe diff --git a/src/xenia/kernel/achievement_manager.h b/src/xenia/kernel/achievement_manager.h deleted file mode 100644 index 6b935a1c1b7..00000000000 --- a/src/xenia/kernel/achievement_manager.h +++ /dev/null @@ -1,61 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2023 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_KERNEL_ACHIEVEMENT_MANAGER_H_ -#define XENIA_KERNEL_ACHIEVEMENT_MANAGER_H_ - -#include -#include -#include - -#include "xenia/xbox.h" - -namespace xe { -namespace kernel { - -// TODO(gibbed): probably a FILETIME/LARGE_INTEGER, unknown currently -struct X_ACHIEVEMENT_UNLOCK_TIME { - xe::be unk_0; - xe::be unk_4; -}; - -struct X_ACHIEVEMENT_DETAILS { - xe::be id; - xe::be label_ptr; - xe::be description_ptr; - xe::be unachieved_ptr; - xe::be image_id; - xe::be gamerscore; - X_ACHIEVEMENT_UNLOCK_TIME unlock_time; - xe::be flags; - - static const size_t kStringBufferSize = 464; -}; -static_assert_size(X_ACHIEVEMENT_DETAILS, 36); - -class AchievementManager { - public: - AchievementManager(); - - void EarnAchievement(uint64_t xuid, uint32_t title_id, - uint32_t achievement_id); - - bool IsAchievementUnlocked(uint32_t achievement_id); - uint64_t GetAchievementUnlockTime(uint32_t achievement_id); - - private: - std::map unlocked_achievements; - // void Load(); - // void Save(); -}; - -} // namespace kernel -} // namespace xe - -#endif // XENIA_KERNEL_ACHIEVEMENT_MANAGER_H_ diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index 55a14adf0fc..b0b55df3114 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -70,7 +70,6 @@ KernelState::KernelState(Emulator* emulator) file_system_ = emulator->file_system(); app_manager_ = std::make_unique(); - achievement_manager_ = std::make_unique(); user_profiles_.emplace(0, std::make_unique(0)); InitializeKernelGuestGlobals(); @@ -82,6 +81,8 @@ KernelState::KernelState(Emulator* emulator) } content_manager_ = std::make_unique(this, content_root); + achievement_manager_ = std::make_unique(); + // Hardcoded maximum of 2048 TLS slots. tls_bitmap_.Resize(2048); diff --git a/src/xenia/kernel/kernel_state.h b/src/xenia/kernel/kernel_state.h index b4097061b7e..ca7dafaf608 100644 --- a/src/xenia/kernel/kernel_state.h +++ b/src/xenia/kernel/kernel_state.h @@ -18,12 +18,12 @@ #include #include -#include "achievement_manager.h" #include "xenia/base/bit_map.h" #include "xenia/base/cvar.h" #include "xenia/base/mutex.h" #include "xenia/cpu/backend/backend.h" #include "xenia/cpu/export_resolver.h" +#include "xenia/kernel/achievement_dispatcher.h" #include "xenia/kernel/util/kernel_fwd.h" #include "xenia/kernel/util/native_list.h" #include "xenia/kernel/util/object_table.h" @@ -186,7 +186,7 @@ class KernelState { util::XdbfGameData title_xdbf() const; util::XdbfGameData module_xdbf(object_ref exec_module) const; - AchievementManager* achievement_manager() const { + AchievementDispatcher* achievement_manager() const { return achievement_manager_.get(); } xam::AppManager* app_manager() const { return app_manager_.get(); } @@ -372,7 +372,7 @@ class KernelState { std::unique_ptr app_manager_; std::unique_ptr content_manager_; std::map> user_profiles_; - std::unique_ptr achievement_manager_; + std::unique_ptr achievement_manager_; KernelVersion kernel_version_; diff --git a/src/xenia/kernel/xam/apps/xgi_app.cc b/src/xenia/kernel/xam/apps/xgi_app.cc index 0b9376b963d..af9368938d4 100644 --- a/src/xenia/kernel/xam/apps/xgi_app.cc +++ b/src/xenia/kernel/xam/apps/xgi_app.cc @@ -101,7 +101,8 @@ X_HRESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr, (X_XUSER_ACHIEVEMENT*)memory_->TranslateVirtual(achievements_ptr); for (uint32_t i = 0; i < achievement_count; i++, achievement++) { kernel_state_->achievement_manager()->EarnAchievement( - achievement->user_idx, 0, achievement->achievement_id); + achievement->user_idx, kernel_state_->title_id(), + achievement->achievement_id); } return X_E_SUCCESS; } diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 6aee7123d0d..77eb1c86061 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -604,7 +604,18 @@ dword_result_t XamUserCreateAchievementEnumerator_entry( return result; } + const auto user = kernel_state()->user_profile(user_index); + if (!user) { + return X_ERROR_INVALID_PARAMETER; + } + + uint64_t requester_xuid = user->xuid(); + if (xuid) { + requester_xuid = xuid; + } + const util::XdbfGameData db = kernel_state()->title_xdbf(); + uint32_t title_id_ = title_id ? title_id : kernel_state()->title_id(); if (db.is_valid()) { const XLanguage language = @@ -612,13 +623,11 @@ dword_result_t XamUserCreateAchievementEnumerator_entry( const std::vector achievement_list = db.GetAchievements(); + for (const util::XdbfAchievementTableEntry& entry : achievement_list) { - auto is_unlocked = - kernel_state()->achievement_manager()->IsAchievementUnlocked( - entry.id); - auto unlock_time = - kernel_state()->achievement_manager()->GetAchievementUnlockTime( - entry.id); + auto achievement_details = + kernel_state()->achievement_manager()->GetAchievementInfo( + requester_xuid, title_id_, entry.id); auto item = XAchievementEnumerator::AchievementDetails{ entry.id, @@ -627,9 +636,10 @@ dword_result_t XamUserCreateAchievementEnumerator_entry( xe::to_utf16(db.GetStringTableEntry(language, entry.unachieved_id)), entry.image_id, entry.gamerscore, - (uint32_t)(unlock_time << 31), - (uint32_t)unlock_time, - is_unlocked ? entry.flags | 0x20000 : entry.flags}; + achievement_details->unlock_time.high_part, + achievement_details->unlock_time.low_part, + achievement_details->flags, + }; e->AppendItem(item); } diff --git a/src/xenia/kernel/xenumerator.cc b/src/xenia/kernel/xenumerator.cc index 1780f72f31a..b947326c1e0 100644 --- a/src/xenia/kernel/xenumerator.cc +++ b/src/xenia/kernel/xenumerator.cc @@ -105,8 +105,8 @@ uint32_t XAchievementEnumerator::WriteItems(uint32_t buffer_ptr, !!(flags_ & 4) ? AppendString(string_buffer, item.unachieved) : 0; details[i].image_id = item.image_id; details[i].gamerscore = item.gamerscore; - details[i].unlock_time.unk_0 = item.unlock_time.unk_0; - details[i].unlock_time.unk_4 = item.unlock_time.unk_4; + details[i].unlock_time.high_part = item.unlock_time.unk_0; + details[i].unlock_time.low_part = item.unlock_time.unk_4; details[i].flags = item.flags; } diff --git a/src/xenia/kernel/xenumerator.h b/src/xenia/kernel/xenumerator.h index 08a3ff86bba..70d5c9d0016 100644 --- a/src/xenia/kernel/xenumerator.h +++ b/src/xenia/kernel/xenumerator.h @@ -14,7 +14,7 @@ #include #include -#include "xenia/kernel/achievement_manager.h" +#include "xenia/kernel/achievement_dispatcher.h" #include "xenia/kernel/xobject.h" #include "xenia/xbox.h"