/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2025 Xenia Canary. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #ifndef XENIA_KERNEL_XAM_XDBF_SPA_INFO_H_ #define XENIA_KERNEL_XAM_XDBF_SPA_INFO_H_ #include "xenia/kernel/xam/xdbf/xdbf_io.h" #include #include #include #include #include "xenia/base/memory.h" #include "xenia/xbox.h" namespace xe { namespace kernel { namespace xam { // 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 SpaSection : uint16_t { kMetadata = 0x0001, kImage = 0x0002, kStringTable = 0x0003, }; enum class TitleType : uint32_t { kSystem = 0, kFull = 1, kDemo = 2, kDownload = 3, kUnknown = 4, kApp = 5 }; enum class TitleFlags { kAlwaysIncludeInProfile = 1, kNeverIncludeInProfile = 2, }; #pragma pack(push, 1) struct TitleHeaderData { xe::be title_id; xe::be title_type; xe::be major; xe::be minor; xe::be build; xe::be revision; xe::be flags; xe::be padding_1; xe::be padding_2; xe::be padding_3; }; static_assert_size(TitleHeaderData, 32); struct StatsViewTableEntry { xe::be id; xe::be flags; xe::be shared_index; xe::be string_id; xe::be unused; }; static_assert_size(StatsViewTableEntry, 0x10); struct ViewFieldEntry { xe::be size; xe::be property_id; xe::be flags; xe::be attribute_id; xe::be string_id; xe::be aggregation_type; xe::be ordinal; xe::be field_type; xe::be format_type; xe::be unused_1; xe::be unused_2; }; static_assert_size(ViewFieldEntry, 0x20); struct SharedViewMetaTableEntry { xe::be column_count; xe::be row_count; xe::be unused_1; xe::be unused_2; }; static_assert_size(SharedViewMetaTableEntry, 0xC); struct PropertyBag { std::vector> contexts; std::vector> properties; }; struct SharedView { std::vector column_entries; std::vector row_entries; PropertyBag property_bag; }; struct ViewTable { StatsViewTableEntry view_entry; SharedView shared_view; }; struct AchievementTableEntry { 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(AchievementTableEntry, 0x24); #pragma pack(pop) class SpaInfo : public XdbfFile { public: SpaInfo(const std::span buffer); void Load(); const uint8_t* ReadXLast(uint32_t& compressed_size, uint32_t& decompressed_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. std::span title_icon() const; std::span GetIcon(uint64_t id) const; // The game's default language. XLanguage default_language() const; bool is_system_app() const; bool is_demo() const; bool include_in_profile() const; uint32_t title_id() const; // The game's title in its default language. std::string title_name() const; std::string title_name(XLanguage language) const; uint32_t achievement_count() const { return static_cast(achievements_.size()); } const AchievementTableEntry* GetAchievement(uint32_t id); std::vector GetAchievements() const { return achievements_; } std::vector GetContexts() const { return contexts_; } std::vector GetProperties() const { return properties_; } const XdbfContextTableEntry* GetContext(uint32_t id); const XdbfPropertyTableEntry* GetProperty(uint32_t id); uint32_t total_gamerscore() const { return std::accumulate(achievements_.cbegin(), achievements_.cend(), 0, [](uint32_t sum, const auto& entry) { return sum + entry->gamerscore; }); } friend bool operator<(const SpaInfo& first, const SpaInfo& second); friend bool operator<=(const SpaInfo& first, const SpaInfo& second); friend bool operator==(const SpaInfo& first, const SpaInfo& second); std::string GetStringTableEntry(XLanguage language, uint16_t string_id) const; private: // Base info. There should be comparator between different SpaInfos and entry // with newer data should replace old one. Such situation can happen when game // adds achievements and so on with DLC. TitleHeaderData title_header_; // SPA is Read-Only so it's reasonable to make it readonly. std::vector achievements_; std::vector contexts_; std::vector properties_; typedef std::map XdbfLanguageStrings; std::map language_strings_; void LoadTitleInformation(); void LoadAchievements(); void LoadLanguageData(); void LoadContexts(); void LoadProperties(); template static T GetSpaEntry(std::vector& container, uint32_t id); }; } // namespace xam } // namespace kernel } // namespace xe #endif // XENIA_KERNEL_XAM_XDBF_SPA_INFO_H_