/** ****************************************************************************** * 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_XAM_ACHIEVEMENT_MANAGER_H_ #define XENIA_KERNEL_XAM_ACHIEVEMENT_MANAGER_H_ #include #include #include #include #include "xenia/kernel/util/xfiletime.h" #include "xenia/kernel/xam/xdbf/gpd_info.h" #include "xenia/kernel/xam/xdbf/spa_info.h" namespace xe { namespace kernel { namespace xam { 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, }; 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_FILETIME unlock_time; xe::be flags; static constexpr size_t kStringBufferSize = 464; }; static_assert_size(X_ACHIEVEMENT_DETAILS, 36); // Host structures struct AchievementDetails { uint32_t id; std::u16string label; std::u16string description; std::u16string unachieved; uint32_t image_id; uint32_t gamerscore; X_FILETIME unlock_time; uint32_t flags; AchievementDetails(uint32_t id, std::u16string label, std::u16string description, std::u16string unachieved, uint32_t image_id, uint32_t gamerscore, X_FILETIME unlock_time, uint32_t flags) : id(id), label(label), description(description), unachieved(unachieved), image_id(image_id), gamerscore(gamerscore), unlock_time(unlock_time), flags(flags) {}; AchievementDetails(const AchievementTableEntry* entry, const SpaInfo* spa_data, const XLanguage language) { id = entry->id; image_id = entry->image_id; gamerscore = entry->gamerscore; flags = entry->flags; unlock_time = {}; label = xe::to_utf16(spa_data->GetStringTableEntry(language, entry->label_id)); description = xe::to_utf16( spa_data->GetStringTableEntry(language, entry->description_id)); unachieved = xe::to_utf16( spa_data->GetStringTableEntry(language, entry->unachieved_id)); } }; struct TitleAchievementsProfileInfo { uint32_t achievements_count; uint32_t unlocked_achievements_count; uint32_t gamerscore; }; // 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 Achievement { Achievement() {}; Achievement(const X_XDBF_GPD_ACHIEVEMENT* xdbf_ach) { if (!xdbf_ach) { return; } achievement_id = xdbf_ach->id; image_id = xdbf_ach->image_id; flags = xdbf_ach->flags; gamerscore = xdbf_ach->gamerscore; unlock_time = static_cast(xdbf_ach->unlock_time); } Achievement(const XLanguage language, const SpaInfo xdbf, const AchievementTableEntry& xdbf_entry) { const std::string label = ""; xdbf.GetStringTableEntry(language, xdbf_entry.label_id); const std::string desc = ""; xdbf.GetStringTableEntry(language, xdbf_entry.description_id); const std::string locked_desc = ""; xdbf.GetStringTableEntry(language, xdbf_entry.unachieved_id); achievement_id = static_cast>(xdbf_entry.id); image_id = xdbf_entry.image_id; gamerscore = static_cast>(xdbf_entry.gamerscore); flags = xdbf_entry.flags; unlock_time = {}; achievement_name = xe::load_and_swap(xe::to_utf16(label).c_str()); unlocked_description = xe::load_and_swap(xe::to_utf16(desc).c_str()); locked_description = xe::load_and_swap(xe::to_utf16(locked_desc).c_str()); } uint32_t achievement_id = 0; uint32_t image_id = 0; uint32_t gamerscore = 0; uint32_t flags = 0; X_FILETIME unlock_time; std::u16string achievement_name; std::u16string unlocked_description; std::u16string locked_description; bool IsUnlocked() const { return (flags & static_cast(AchievementFlags::kAchieved)) || IsUnlockedOnline(); } bool IsUnlockedOnline() const { return (flags & static_cast(AchievementFlags::kAchievedOnline)); } }; 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 std::optional 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 const std::span GetAchievementIcon( const uint64_t xuid, const uint32_t title_id, const uint32_t achievement_id) const = 0; virtual bool LoadAchievementsData(const uint64_t xuid) = 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 Achievement* achievement) = 0; }; class AchievementManager { public: AchievementManager(); void LoadTitleAchievements(const uint64_t xuid) 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 std::optional 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; const std::optional GetTitleAchievementsInfo( const uint64_t xuid, const uint32_t title_id) const; const std::span GetAchievementIcon( const uint64_t xuid, const uint32_t title_id, const uint32_t achievement_id) const; private: bool DoesAchievementExist(const uint32_t achievement_id) const; void ShowAchievementEarnedNotification(const Achievement* achievement) const; // This contains all backends with exception of default storage. std::vector> achievement_backends_; std::unique_ptr default_achievements_backend_; }; } // namespace xam } // namespace kernel } // namespace xe #endif // XENIA_KERNEL_XAM_ACHIEVEMENT_MANAGER_H_