Files
Xenia-Canary/src/xenia/kernel/util/game_info_database.cc
2025-11-23 19:17:39 +01:00

449 lines
12 KiB
C++

/**
******************************************************************************
* 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/util/game_info_database.h"
#include "xenia/base/logging.h"
#include "xenia/kernel/xam/user_data.h"
namespace xe {
namespace kernel {
namespace util {
GameInfoDatabase::GameInfoDatabase(const xam::SpaInfo* data) {
if (!data) {
return;
}
Init(data);
}
void GameInfoDatabase::Init(const xam::SpaInfo* data) {
spa_gamedata_ = std::make_unique<xam::SpaInfo>(*data);
spa_gamedata_->Load();
is_valid_ = true;
uint32_t compressed_size, decompressed_size = 0;
const uint8_t* xlast_ptr =
spa_gamedata_->ReadXLast(compressed_size, decompressed_size);
if (!xlast_ptr) {
XELOGW(
"GameDatabase: Title doesn't contain XLAST data! Multiplayer "
"functionality might be limited.");
return;
}
xlast_gamedata_ =
std::make_unique<XLast>(xlast_ptr, compressed_size, decompressed_size);
if (!xlast_gamedata_) {
XELOGW(
"GameDatabase: Title XLAST data is corrupted! Multiplayer "
"functionality might be limited.");
return;
}
}
void GameInfoDatabase::Update(const xam::SpaInfo* new_spa) {
if (*spa_gamedata_ <= *new_spa) {
return;
}
Init(new_spa);
}
std::string GameInfoDatabase::GetTitleName(const XLanguage language) const {
return spa_gamedata_->title_name(
spa_gamedata_->GetExistingLanguage(language));
}
std::vector<uint8_t> GameInfoDatabase::GetIcon() const {
std::vector<uint8_t> data;
const auto icon = spa_gamedata_->title_icon();
data.insert(data.begin(), icon.begin(), icon.end());
return data;
}
XLanguage GameInfoDatabase::GetDefaultLanguage() const {
if (!is_valid_) {
return XLanguage::kEnglish;
}
return spa_gamedata_->default_language();
}
std::string GameInfoDatabase::GetLocalizedString(const uint32_t id,
XLanguage language) const {
return spa_gamedata_->GetStringTableEntry(
spa_gamedata_->GetExistingLanguage(language), id);
}
GameInfoDatabase::Context GameInfoDatabase::GetContext(
const uint32_t id) const {
Context context = {.id = xam::kInvalidContextId};
if (!is_valid_) {
return context;
}
const auto xdbf_context = spa_gamedata_->GetContext(id);
if (!xdbf_context) {
return context;
}
context.id = xdbf_context->id;
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.is_matchmaking =
GetMatchmakingCollection().contexts.contains(xdbf_context->id);
context.description = GetLocalizedString(xdbf_context->string_id);
return context;
}
GameInfoDatabase::Property GameInfoDatabase::GetProperty(
const uint32_t id) const {
Property property = {.id = xam::kInvalidPropertyId};
if (!is_valid_) {
return property;
}
const auto xdbf_property = spa_gamedata_->GetProperty(id);
if (!xdbf_property) {
return property;
}
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.is_matchmaking =
GetMatchmakingCollection().properties.contains(xdbf_property->id);
property.description = GetLocalizedString(xdbf_property->string_id);
return property;
}
GameInfoDatabase::Achievement GameInfoDatabase::GetAchievement(
const uint32_t id) const {
Achievement achievement = {};
if (!is_valid_) {
return achievement;
}
const auto xdbf_achievement = spa_gamedata_->GetAchievement(id);
if (!xdbf_achievement) {
return achievement;
}
achievement.id = xdbf_achievement->id;
achievement.image_id = xdbf_achievement->id;
achievement.gamerscore = xdbf_achievement->gamerscore;
achievement.flags = xdbf_achievement->flags;
achievement.label = GetLocalizedString(xdbf_achievement->label_id);
achievement.description =
GetLocalizedString(xdbf_achievement->description_id);
achievement.unachieved_description =
GetLocalizedString(xdbf_achievement->unachieved_id);
return achievement;
}
GameInfoDatabase::PropertyBag GameInfoDatabase::GetPropertyBag(
const xam::PropertyBag& property_bag) const {
PropertyBag property_bag_native = {};
property_bag_native.contexts = {property_bag.contexts.cbegin(),
property_bag.contexts.cend()};
property_bag_native.properties = {property_bag.properties.cbegin(),
property_bag.properties.cend()};
return property_bag_native;
}
GameInfoDatabase::Field GameInfoDatabase::GetField(
const xam::ViewFieldEntry& field_entry) const {
Field field = {};
field.property_id = field_entry.property_id;
field.flags = field_entry.flags;
field.attribute_id = field_entry.attribute_id;
field.aggregation_type = field_entry.aggregation_type;
field.ordinal = field_entry.ordinal;
field.field_type = field_entry.field_type;
field.format_type = field_entry.format_type;
field.name = GetLocalizedString(field_entry.string_id);
if (field.name.empty()) {
field.name = xam::AttributeIdToName(field.attribute_id);
}
return field;
}
GameInfoDatabase::StatsView GameInfoDatabase::GetStatsView(
const uint32_t id) const {
StatsView stats_view = {};
if (!is_valid_) {
return stats_view;
}
const auto xdbf_stats_view = spa_gamedata_->GetStatsView(id);
if (!xdbf_stats_view.has_value()) {
return stats_view;
}
stats_view.view.id = xdbf_stats_view->view_entry.id;
stats_view.view.arbitrated =
xam::IsArbitrated(xdbf_stats_view->view_entry.flags);
stats_view.view.hidden = xam::IsHidden(xdbf_stats_view->view_entry.flags);
stats_view.view.team_view =
xam::IsTeamView(xdbf_stats_view->view_entry.flags);
stats_view.view.online_only =
xam::IsOnlineOnly(xdbf_stats_view->view_entry.flags);
stats_view.view.view_type =
xam::GetViewType(xdbf_stats_view->view_entry.flags);
stats_view.view.skilled = xam::IsLeaderboardIdSkill(stats_view.view.id);
stats_view.view.shared_index = xdbf_stats_view->view_entry.shared_index;
stats_view.view.name =
GetLocalizedString(xdbf_stats_view->view_entry.string_id);
for (const auto& column : xdbf_stats_view->shared_view.column_entries) {
stats_view.shared_view.column_entries.push_back(GetField(column));
}
for (const auto& row : xdbf_stats_view->shared_view.row_entries) {
stats_view.shared_view.row_entries.push_back(GetField(row));
}
stats_view.shared_view.properties =
GetPropertyBag(xdbf_stats_view->shared_view.property_bag);
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<uint32_t> GameInfoDatabase::GetMatchmakingAttributes(
const uint32_t id) const {
// TODO(Gliniak): Implement when we will fully understand how to read it from
// SPA.
std::vector<uint32_t> result;
return result;
}
// XLAST
GameInfoDatabase::Query GameInfoDatabase::GetQueryData(
const uint32_t id) const {
Query query = {};
if (!xlast_gamedata_) {
return query;
}
const auto xlast_query = xlast_gamedata_->GetMatchmakingQuery(id);
if (!xlast_query) {
return query;
}
query.id = id;
query.name = xlast_query->GetName();
query.input_parameters = xlast_query->GetParameters();
query.filters = xlast_query->GetFilters();
query.expected_return = xlast_query->GetReturns();
return query;
}
std::vector<XLanguage> GameInfoDatabase::GetSupportedLanguages() const {
if (!xlast_gamedata_) {
return {};
}
return xlast_gamedata_->GetSupportedLanguages();
}
GameInfoDatabase::ProductInformation GameInfoDatabase::GetProductInformation()
const {
if (!xlast_gamedata_) {
return {};
}
ProductInformation info = {};
const auto attributes = xlast_gamedata_->GetProductInformationAttributes();
for (const auto& attribute : attributes) {
switch (attribute.first) {
case ProductInformationEntry::MaxOfflinePlayers:
info.max_offline_players_count = attribute.second;
break;
case ProductInformationEntry::MaxSystemLinkPlayers:
info.max_systemlink_players_count = attribute.second;
break;
case ProductInformationEntry::MaxLivePlayers:
info.max_live_players_count = attribute.second;
break;
case ProductInformationEntry::PublisherString:
info.publisher_name = xe::to_utf8(xlast_gamedata_->GetLocalizedString(
attribute.second, XLanguage::kEnglish));
break;
case ProductInformationEntry::DeveloperString:
info.developer_name = xe::to_utf8(xlast_gamedata_->GetLocalizedString(
attribute.second, XLanguage::kEnglish));
break;
case ProductInformationEntry::MarketingString:
info.marketing_info = xe::to_utf8(xlast_gamedata_->GetLocalizedString(
attribute.second, XLanguage::kEnglish));
break;
case ProductInformationEntry::GenreTypeString:
info.genre_description =
xe::to_utf8(xlast_gamedata_->GetLocalizedString(
attribute.second, XLanguage::kEnglish));
break;
default:
break;
}
}
return info;
}
GameInfoDatabase::PropertyBag GameInfoDatabase::GetMatchmakingCollection()
const {
return GetPropertyBag(*spa_gamedata_->GetMatchCollection());
}
// Aggregators
std::vector<GameInfoDatabase::Context> GameInfoDatabase::GetContexts() const {
std::vector<Context> contexts;
if (!is_valid_) {
return contexts;
}
const auto xdbf_contexts = spa_gamedata_->GetContexts();
for (const auto& entry : xdbf_contexts) {
contexts.push_back(GetContext(entry->id));
}
return contexts;
}
std::vector<GameInfoDatabase::Property> GameInfoDatabase::GetProperties()
const {
std::vector<Property> properties;
if (!is_valid_) {
return properties;
}
const auto xdbf_properties = spa_gamedata_->GetProperties();
for (const auto& entry : xdbf_properties) {
properties.push_back(GetProperty(entry->id));
}
return properties;
}
std::vector<GameInfoDatabase::Achievement> GameInfoDatabase::GetAchievements()
const {
std::vector<Achievement> achievements;
if (!is_valid_) {
return achievements;
}
const auto xdbf_achievements = spa_gamedata_->GetAchievements();
for (const auto& entry : xdbf_achievements) {
achievements.push_back(GetAchievement(entry->id));
}
return achievements;
}
std::vector<GameInfoDatabase::StatsView> GameInfoDatabase::GetStatsViews()
const {
std::vector<StatsView> stats_views;
if (!is_valid_) {
return stats_views;
}
const auto xdbf_stats_views = spa_gamedata_->GetStatsViews();
for (const auto& entry : *xdbf_stats_views) {
stats_views.push_back(GetStatsView(entry.view_entry.id));
}
return stats_views;
}
std::vector<GameInfoDatabase::PresenceMode> GameInfoDatabase::GetPresenceModes()
const {
std::vector<PresenceMode> 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