From d915dd808d2194734c7e36f647f0f88d5791191f Mon Sep 17 00:00:00 2001 From: Adrian <78108584+AdrianCassar@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:41:32 +0000 Subject: [PATCH] [XAM] Implemented SPA XRPT --- src/xenia/emulator.cc | 18 ++++- src/xenia/kernel/util/game_info_database.cc | 56 +++++++++++++ src/xenia/kernel/util/game_info_database.h | 17 ++++ src/xenia/kernel/xam/xdbf/spa_info.cc | 88 +++++++++++++++++++++ src/xenia/kernel/xam/xdbf/spa_info.h | 12 +++ src/xenia/kernel/xam/xdbf/xdbf_io.h | 2 + 6 files changed, 192 insertions(+), 1 deletion(-) diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 8f3eb0149..8a6a8123d 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -1611,10 +1611,26 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path, totals = fmt::format("\nViews: {}/{}", stats_views_limit.size(), stats_views.size()); } - XELOGI("\n-------------------- Stats Views --------------------{}\n{}", totals.c_str(), table.str()); + const std::vector + presence_modes = game_info_database_->GetPresenceModes(); + + table = tabulate::Table(); + table.format().multi_byte_characters(true); + table.add_row({"Context Value", "Contexts Count", "Properties Count"}); + + for (const kernel::util::GameInfoDatabase::PresenceMode& entry : + presence_modes) { + table.add_row( + {fmt::format("{}", entry.context_value), + fmt::format("{}", entry.property_bag.contexts.size()), + fmt::format("{}", entry.property_bag.properties.size())}); + } + XELOGI("\n-------------------- PRESENCE MODES --------------------\n{}", + table.str()); + auto icon_block = game_info_database_->GetIcon(); if (!icon_block.empty()) { display_window_->SetIcon(icon_block.data(), icon_block.size()); diff --git a/src/xenia/kernel/util/game_info_database.cc b/src/xenia/kernel/util/game_info_database.cc index 3aedba03e..b04bbc754 100644 --- a/src/xenia/kernel/util/game_info_database.cc +++ b/src/xenia/kernel/util/game_info_database.cc @@ -102,6 +102,7 @@ GameInfoDatabase::Context GameInfoDatabase::GetContext( context.default_value = xdbf_context->default_value; context.max_value = xdbf_context->max_value; context.is_system = xam::UserData::is_system_property(xdbf_context->id); + context.is_presence = GetPresence().property_bag.contexts.contains(id); context.description = GetLocalizedString(xdbf_context->string_id); return context; } @@ -122,6 +123,7 @@ GameInfoDatabase::Property GameInfoDatabase::GetProperty( property.id = xdbf_property->id; property.data_size = xdbf_property->data_size; property.is_system = xam::UserData::is_system_property(xdbf_property->id); + property.is_presence = GetPresence().property_bag.properties.contains(id); property.description = GetLocalizedString(xdbf_property->string_id); return property; } @@ -231,6 +233,41 @@ GameInfoDatabase::StatsView GameInfoDatabase::GetStatsView( return stats_view; } +GameInfoDatabase::Presence GameInfoDatabase::GetPresence() const { + Presence presence; + + if (!is_valid_) { + return presence; + } + + const auto xdbf_presence = spa_gamedata_->GetPresence(); + + presence.property_bag = GetPropertyBag(xdbf_presence->property_bag); + presence.presence_modes = GetPresenceModes(); + + return presence; +} + +GameInfoDatabase::PresenceMode GameInfoDatabase::GetPresenceMode( + const uint32_t context_value) const { + PresenceMode presence_mode = {}; + + if (!is_valid_) { + return presence_mode; + } + + const auto xdbf_presence_mode = spa_gamedata_->GetPresenceMode(context_value); + + if (!xdbf_presence_mode.has_value()) { + return presence_mode; + } + + presence_mode.context_value = context_value; + presence_mode.property_bag = GetPropertyBag(xdbf_presence_mode.value()); + + return presence_mode; +} + std::vector GameInfoDatabase::GetMatchmakingAttributes( const uint32_t id) const { // TODO(Gliniak): Implement when we will fully understand how to read it from @@ -378,6 +415,25 @@ std::vector GameInfoDatabase::GetStatsViews() return stats_views; } +std::vector GameInfoDatabase::GetPresenceModes() + const { + std::vector presence_modes; + + if (!is_valid_) { + return presence_modes; + } + + const auto& xdbf_presence_modes = + spa_gamedata_->GetPresence()->presence_modes; + + for (uint32_t context_value = 0; context_value < xdbf_presence_modes.size(); + context_value++) { + presence_modes.push_back(GetPresenceMode(context_value)); + } + + return presence_modes; +} + } // namespace util } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/util/game_info_database.h b/src/xenia/kernel/util/game_info_database.h index 20e7159af..e08b1488e 100644 --- a/src/xenia/kernel/util/game_info_database.h +++ b/src/xenia/kernel/util/game_info_database.h @@ -33,6 +33,7 @@ class GameInfoDatabase { uint32_t max_value; uint32_t default_value; bool is_system; + bool is_presence; std::string description; }; @@ -40,6 +41,7 @@ class GameInfoDatabase { uint32_t id; uint32_t data_size; bool is_system; + bool is_presence; std::string description; }; @@ -53,6 +55,16 @@ class GameInfoDatabase { uint32_t flags; }; + struct PresenceMode { + uint32_t context_value; + PropertyBag property_bag; + }; + + struct Presence { + PropertyBag property_bag; + std::vector presence_modes; + }; + struct Query { uint32_t id; std::string name; @@ -136,6 +148,10 @@ class GameInfoDatabase { PropertyBag GetPropertyBag(const xam::PropertyBag& property_bag) const; Field GetField(const xam::ViewFieldEntry& field_entry) const; StatsView GetStatsView(const uint32_t id) const; + Presence GetPresence() const; + PresenceMode GetPresenceMode(const uint32_t context_value) const; + std::vector GetPresenceModes( + const std::vector property_bags) const; // TODO: Implement it in the future. std::vector GetMatchmakingAttributes(const uint32_t id) const; @@ -149,6 +165,7 @@ class GameInfoDatabase { std::vector GetContexts() const; std::vector GetProperties() const; std::vector GetAchievements() const; + std::vector GetPresenceModes() const; std::vector GetStatsViews() const; private: diff --git a/src/xenia/kernel/xam/xdbf/spa_info.cc b/src/xenia/kernel/xam/xdbf/spa_info.cc index bb158a87d..52a4a67ae 100644 --- a/src/xenia/kernel/xam/xdbf/spa_info.cc +++ b/src/xenia/kernel/xam/xdbf/spa_info.cc @@ -24,6 +24,7 @@ void SpaInfo::Load() { LoadAchievements(); LoadProperties(); LoadContexts(); + LoadPresenceModes(); LoadStatsViews(); } @@ -252,6 +253,82 @@ void SpaInfo::LoadStatsViews() { } } +void SpaInfo::LoadPresenceModes() { + auto presence_modes_table = + GetEntry(static_cast(SpaSection::kMetadata), kXdbfIdXrpt); + if (!presence_modes_table) { + return; + } + + auto xrpt_head = reinterpret_cast( + presence_modes_table->data.data()); + assert_true(xrpt_head->magic == kXdbfSignatureXrpt); + assert_true(xrpt_head->version == 1); + + auto xpbm_head_start_ptr = reinterpret_cast(xrpt_head + 1); + + auto xpbm_head = + reinterpret_cast(xpbm_head_start_ptr); + + assert_true(xpbm_head->magic == kXdbfSignatureXpbm); + assert_true(xpbm_head->version == 1); + + const PropertyBagEntry* property_bag_header_ptr = + reinterpret_cast(xpbm_head + 1); + + auto contexts_ptr = + reinterpret_cast*>(property_bag_header_ptr + 1); + + auto properties_ptr = reinterpret_cast*>( + contexts_ptr + property_bag_header_ptr->contexts_count); + + for (uint32_t i = 0; i < property_bag_header_ptr->contexts_count; i++) { + presence_.property_bag.contexts.insert(contexts_ptr[i]); + } + + for (uint32_t i = 0; i < property_bag_header_ptr->properties_count; i++) { + presence_.property_bag.properties.insert(properties_ptr[i]); + } + + xpbm_head_start_ptr += xpbm_head->size + sizeof(uint32_t); + + const uint16_t presence_modes_count = + xe::load_and_swap(xpbm_head_start_ptr); + + xpbm_head_start_ptr += sizeof(uint16_t); + + for (size_t i = 0; i < presence_modes_count; i++) { + PropertyBag property_bag = {}; + + auto xpbm_head = + reinterpret_cast(xpbm_head_start_ptr); + + assert_true(xpbm_head->magic == kXdbfSignatureXpbm); + assert_true(xpbm_head->version == 1); + + const PropertyBagEntry* property_bag_header_ptr = + reinterpret_cast(xpbm_head + 1); + + auto contexts_ptr = + reinterpret_cast*>(property_bag_header_ptr + 1); + + auto properties_ptr = reinterpret_cast*>( + contexts_ptr + property_bag_header_ptr->contexts_count); + + for (uint32_t i = 0; i < property_bag_header_ptr->contexts_count; i++) { + property_bag.contexts.insert(contexts_ptr[i]); + } + + for (uint32_t i = 0; i < property_bag_header_ptr->properties_count; i++) { + property_bag.properties.insert(properties_ptr[i]); + } + + xpbm_head_start_ptr += xpbm_head->size + sizeof(uint32_t); + + presence_.presence_modes.push_back(property_bag); + } +} + const uint8_t* SpaInfo::ReadXLast(uint32_t& compressed_size, uint32_t& decompressed_size) { auto xlast_table = @@ -380,6 +457,17 @@ const XdbfPropertyTableEntry* SpaInfo::GetProperty(uint32_t id) { return GetSpaEntry(properties_, id); } +const std::optional SpaInfo::GetPresenceMode( + uint32_t context_value) const { + std::optional entry = std::nullopt; + + if (context_value < presence_.presence_modes.size()) { + entry = presence_.presence_modes.at(context_value); + } + + return entry; +} + const std::optional SpaInfo::GetStatsView(uint32_t id) { const auto itr = std::ranges::find_if( stats_views_, diff --git a/src/xenia/kernel/xam/xdbf/spa_info.h b/src/xenia/kernel/xam/xdbf/spa_info.h index 8338dd17e..cfffe6126 100644 --- a/src/xenia/kernel/xam/xdbf/spa_info.h +++ b/src/xenia/kernel/xam/xdbf/spa_info.h @@ -222,6 +222,11 @@ struct ViewTable { SharedView shared_view; }; +struct PresenceTableEntry { + PropertyBag property_bag; + std::vector presence_modes; +}; + struct AchievementTableEntry { xe::be id; xe::be label_id; @@ -290,9 +295,13 @@ class SpaInfo : public XdbfFile { const std::vector* GetStatsViews() const { return &stats_views_; } + const PresenceTableEntry* GetPresence() const { return &presence_; } + const XdbfContextTableEntry* GetContext(uint32_t id); const XdbfPropertyTableEntry* GetProperty(uint32_t id); const std::optional GetStatsView(uint32_t id); + const std::optional GetPresenceMode( + uint32_t context_value) const; uint32_t total_gamerscore() const { return std::accumulate(achievements_.cbegin(), achievements_.cend(), 0, @@ -318,6 +327,7 @@ class SpaInfo : public XdbfFile { std::vector contexts_; std::vector properties_; std::vector stats_views_; + PresenceTableEntry presence_; using XdbfLanguageStrings = std::map; @@ -333,6 +343,8 @@ class SpaInfo : public XdbfFile { void LoadStatsViews(); + void LoadPresenceModes(); + template static T GetSpaEntry(std::vector& container, uint32_t id); }; diff --git a/src/xenia/kernel/xam/xdbf/xdbf_io.h b/src/xenia/kernel/xam/xdbf/xdbf_io.h index 88f879f44..7880da9ab 100644 --- a/src/xenia/kernel/xam/xdbf/xdbf_io.h +++ b/src/xenia/kernel/xam/xdbf/xdbf_io.h @@ -34,6 +34,7 @@ constexpr fourcc_t kXdbfSignatureXvc2 = make_fourcc("XVC2"); constexpr fourcc_t kXdbfSignatureXmat = make_fourcc("XMAT"); constexpr fourcc_t kXdbfSignatureXsrc = make_fourcc("XSRC"); constexpr fourcc_t kXdbfSignatureXthd = make_fourcc("XTHD"); +constexpr fourcc_t kXdbfSignatureXrpt = make_fourcc("XRPT"); constexpr fourcc_t kXdbfSignatureXpbm = make_fourcc("XPBM"); constexpr uint64_t kXdbfIdTitle = 0x8000; @@ -46,6 +47,7 @@ constexpr uint64_t kXdbfIdXmat = 0x584D4154; constexpr uint64_t kXdbfIdXsrc = 0x58535243; constexpr uint64_t kXdbfIdXthd = 0x58544844; constexpr uint64_t kXdbfIdXpbm = 0x5850424D; +constexpr uint64_t kXdbfIdXrpt = 0x58525054; #pragma pack(push, 1) struct XdbfHeader {