[Kernel] Added GameInfoDatabase
This aggregates XDBF and XLAST information into one entity
This commit is contained in:
parent
c7e97d051b
commit
aabf039c05
|
@ -118,7 +118,7 @@ Emulator::Emulator(const std::filesystem::path& command_line,
|
||||||
kernel_state_(),
|
kernel_state_(),
|
||||||
main_thread_(),
|
main_thread_(),
|
||||||
title_id_(std::nullopt),
|
title_id_(std::nullopt),
|
||||||
title_xlast_(),
|
game_info_database_(),
|
||||||
paused_(false),
|
paused_(false),
|
||||||
restoring_(false),
|
restoring_(false),
|
||||||
restore_fence_() {
|
restore_fence_() {
|
||||||
|
@ -1121,10 +1121,12 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
|
||||||
game_config_load_callback_loop_next_index_ = SIZE_MAX;
|
game_config_load_callback_loop_next_index_ = SIZE_MAX;
|
||||||
|
|
||||||
const kernel::util::XdbfGameData db = kernel_state_->module_xdbf(module);
|
const kernel::util::XdbfGameData db = kernel_state_->module_xdbf(module);
|
||||||
if (db.is_valid()) {
|
|
||||||
XLanguage language =
|
game_info_database_ = std::make_unique<kernel::util::GameInfoDatabase>(&db);
|
||||||
db.GetExistingLanguage(static_cast<XLanguage>(cvars::user_language));
|
|
||||||
title_name_ = db.title(language);
|
if (game_info_database_->IsValid()) {
|
||||||
|
title_name_ = game_info_database_->GetTitleName(
|
||||||
|
static_cast<XLanguage>(cvars::user_language));
|
||||||
XELOGI("Title name: {}", title_name_);
|
XELOGI("Title name: {}", title_name_);
|
||||||
|
|
||||||
// Show achievments data
|
// Show achievments data
|
||||||
|
@ -1132,48 +1134,44 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
|
||||||
table.format().multi_byte_characters(true);
|
table.format().multi_byte_characters(true);
|
||||||
table.add_row({"ID", "Title", "Description", "Gamerscore"});
|
table.add_row({"ID", "Title", "Description", "Gamerscore"});
|
||||||
|
|
||||||
const std::vector<kernel::util::XdbfAchievementTableEntry>
|
const std::vector<kernel::util::GameInfoDatabase::Achievement>
|
||||||
achievement_list = db.GetAchievements();
|
achievement_list = game_info_database_->GetAchievements();
|
||||||
for (const kernel::util::XdbfAchievementTableEntry& entry :
|
for (const kernel::util::GameInfoDatabase::Achievement& entry :
|
||||||
achievement_list) {
|
achievement_list) {
|
||||||
std::string label = string_util::remove_eol(string_util::trim(
|
table.add_row({fmt::format("{}", entry.id), entry.label,
|
||||||
db.GetStringTableEntry(language, entry.label_id)));
|
entry.description, fmt::format("{}", entry.gamerscore)});
|
||||||
std::string desc = string_util::remove_eol(string_util::trim(
|
|
||||||
db.GetStringTableEntry(language, entry.description_id)));
|
|
||||||
|
|
||||||
table.add_row({fmt::format("{}", entry.id), label, desc,
|
|
||||||
fmt::format("{}", entry.gamerscore)});
|
|
||||||
}
|
}
|
||||||
XELOGI("-------------------- ACHIEVEMENTS --------------------\n{}",
|
XELOGI("-------------------- ACHIEVEMENTS --------------------\n{}",
|
||||||
table.str());
|
table.str());
|
||||||
|
|
||||||
const std::vector<kernel::util::XdbfPropertyTableEntry> properties_list =
|
const std::vector<kernel::util::GameInfoDatabase::Property>
|
||||||
db.GetProperties();
|
properties_list = game_info_database_->GetProperties();
|
||||||
|
|
||||||
table = tabulate::Table();
|
table = tabulate::Table();
|
||||||
table.format().multi_byte_characters(true);
|
table.format().multi_byte_characters(true);
|
||||||
table.add_row({"ID", "Name", "Data Size"});
|
table.add_row({"ID", "Name", "Data Size"});
|
||||||
|
|
||||||
for (const kernel::util::XdbfPropertyTableEntry& entry :
|
for (const kernel::util::GameInfoDatabase::Property& entry :
|
||||||
properties_list) {
|
properties_list) {
|
||||||
std::string label = string_util::remove_eol(string_util::trim(
|
std::string label =
|
||||||
db.GetStringTableEntry(language, entry.string_id)));
|
string_util::remove_eol(string_util::trim(entry.description));
|
||||||
table.add_row({fmt::format("{:08X}", entry.id), label,
|
table.add_row({fmt::format("{:08X}", entry.id), label,
|
||||||
fmt::format("{}", entry.data_size)});
|
fmt::format("{}", entry.data_size)});
|
||||||
}
|
}
|
||||||
XELOGI("-------------------- PROPERTIES --------------------\n{}",
|
XELOGI("-------------------- PROPERTIES --------------------\n{}",
|
||||||
table.str());
|
table.str());
|
||||||
|
|
||||||
const std::vector<kernel::util::XdbfContextTableEntry> contexts_list =
|
const std::vector<kernel::util::GameInfoDatabase::Context> contexts_list =
|
||||||
db.GetContexts();
|
game_info_database_->GetContexts();
|
||||||
|
|
||||||
table = tabulate::Table();
|
table = tabulate::Table();
|
||||||
table.format().multi_byte_characters(true);
|
table.format().multi_byte_characters(true);
|
||||||
table.add_row({"ID", "Name", "Default Value", "Max Value"});
|
table.add_row({"ID", "Name", "Default Value", "Max Value"});
|
||||||
|
|
||||||
for (const kernel::util::XdbfContextTableEntry& entry : contexts_list) {
|
for (const kernel::util::GameInfoDatabase::Context& entry :
|
||||||
std::string label = string_util::remove_eol(string_util::trim(
|
contexts_list) {
|
||||||
db.GetStringTableEntry(language, entry.string_id)));
|
std::string label =
|
||||||
|
string_util::remove_eol(string_util::trim(entry.description));
|
||||||
table.add_row({fmt::format("{:08X}", entry.id), label,
|
table.add_row({fmt::format("{:08X}", entry.id), label,
|
||||||
fmt::format("{}", entry.default_value),
|
fmt::format("{}", entry.default_value),
|
||||||
fmt::format("{}", entry.max_value)});
|
fmt::format("{}", entry.max_value)});
|
||||||
|
@ -1181,16 +1179,9 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
|
||||||
XELOGI("-------------------- CONTEXTS --------------------\n{}",
|
XELOGI("-------------------- CONTEXTS --------------------\n{}",
|
||||||
table.str());
|
table.str());
|
||||||
|
|
||||||
uint32_t compressed_size, decompressed_size = 0;
|
auto icon_block = game_info_database_->GetIcon();
|
||||||
const uint8_t* xlast_ptr =
|
if (!icon_block.empty()) {
|
||||||
db.ReadXLast(compressed_size, decompressed_size);
|
display_window_->SetIcon(icon_block.data(), icon_block.size());
|
||||||
|
|
||||||
title_xlast_ = std::make_unique<kernel::util::XLast>(
|
|
||||||
xlast_ptr, compressed_size, decompressed_size);
|
|
||||||
|
|
||||||
auto icon_block = db.icon();
|
|
||||||
if (icon_block) {
|
|
||||||
display_window_->SetIcon(icon_block.buffer, icon_block.size);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "xenia/base/delegate.h"
|
#include "xenia/base/delegate.h"
|
||||||
#include "xenia/base/exception_handler.h"
|
#include "xenia/base/exception_handler.h"
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
|
#include "xenia/kernel/util/game_info_database.h"
|
||||||
#include "xenia/kernel/util/xlast.h"
|
#include "xenia/kernel/util/xlast.h"
|
||||||
#include "xenia/memory.h"
|
#include "xenia/memory.h"
|
||||||
#include "xenia/patcher/patcher.h"
|
#include "xenia/patcher/patcher.h"
|
||||||
|
@ -299,7 +300,7 @@ class Emulator {
|
||||||
kernel::object_ref<kernel::XThread> main_thread_;
|
kernel::object_ref<kernel::XThread> main_thread_;
|
||||||
kernel::object_ref<kernel::XHostThread> plugin_loader_thread_;
|
kernel::object_ref<kernel::XHostThread> plugin_loader_thread_;
|
||||||
std::optional<uint32_t> title_id_; // Currently running title ID
|
std::optional<uint32_t> title_id_; // Currently running title ID
|
||||||
std::unique_ptr<kernel::util::XLast> title_xlast_;
|
std::unique_ptr<kernel::util::GameInfoDatabase> game_info_database_;
|
||||||
|
|
||||||
bool paused_;
|
bool paused_;
|
||||||
bool restoring_;
|
bool restoring_;
|
||||||
|
|
|
@ -0,0 +1,285 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* 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"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace kernel {
|
||||||
|
namespace util {
|
||||||
|
|
||||||
|
GameInfoDatabase::GameInfoDatabase(const XdbfGameData* data) {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data->is_valid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_valid_ = true;
|
||||||
|
xdbf_gamedata_ = std::make_unique<XdbfGameData>(*data);
|
||||||
|
|
||||||
|
uint32_t compressed_size, decompressed_size = 0;
|
||||||
|
const uint8_t* xlast_ptr =
|
||||||
|
xdbf_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GameInfoDatabase::~GameInfoDatabase() {}
|
||||||
|
|
||||||
|
std::string GameInfoDatabase::GetTitleName(const XLanguage language) const {
|
||||||
|
if (!is_valid_) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return xdbf_gamedata_->title(xdbf_gamedata_->GetExistingLanguage(language));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> GameInfoDatabase::GetIcon() const {
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
|
||||||
|
if (!is_valid_) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const XdbfBlock icon = xdbf_gamedata_->icon();
|
||||||
|
data.resize(icon.size);
|
||||||
|
std::memcpy(data.data(), icon.buffer, icon.size);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
XLanguage GameInfoDatabase::GetDefaultLanguage() const {
|
||||||
|
if (!is_valid_) {
|
||||||
|
return XLanguage::kEnglish;
|
||||||
|
}
|
||||||
|
|
||||||
|
return xdbf_gamedata_->default_language();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GameInfoDatabase::GetLocalizedString(const uint32_t id,
|
||||||
|
XLanguage language) const {
|
||||||
|
if (!is_valid_) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return xdbf_gamedata_->GetStringTableEntry(
|
||||||
|
xdbf_gamedata_->GetExistingLanguage(language), id);
|
||||||
|
}
|
||||||
|
|
||||||
|
GameInfoDatabase::Context GameInfoDatabase::GetContext(
|
||||||
|
const uint32_t id) const {
|
||||||
|
Context context = {};
|
||||||
|
|
||||||
|
if (!is_valid_) {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto xdbf_context = xdbf_gamedata_->GetContext(id);
|
||||||
|
|
||||||
|
context.id = xdbf_context.id;
|
||||||
|
context.default_value = xdbf_context.default_value;
|
||||||
|
context.max_value = xdbf_context.max_value;
|
||||||
|
context.description = GetLocalizedString(xdbf_context.string_id);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameInfoDatabase::Property GameInfoDatabase::GetProperty(
|
||||||
|
const uint32_t id) const {
|
||||||
|
Property property = {};
|
||||||
|
|
||||||
|
if (!is_valid_) {
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto xdbf_property = xdbf_gamedata_->GetProperty(id);
|
||||||
|
|
||||||
|
property.id = xdbf_property.id;
|
||||||
|
property.data_size = xdbf_property.data_size;
|
||||||
|
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 = xdbf_gamedata_->GetAchievement(id);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint32_t> GameInfoDatabase::GetMatchmakingAttributes(
|
||||||
|
const uint32_t id) const {
|
||||||
|
std::vector<uint32_t> result;
|
||||||
|
|
||||||
|
const auto xdbf_matchmaking_data = xdbf_gamedata_->GetMatchCollection();
|
||||||
|
|
||||||
|
result.insert(result.end(), xdbf_matchmaking_data.contexts.cbegin(),
|
||||||
|
xdbf_matchmaking_data.contexts.cend());
|
||||||
|
result.insert(result.end(), xdbf_matchmaking_data.properties.cbegin(),
|
||||||
|
xdbf_matchmaking_data.properties.cend());
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregators
|
||||||
|
std::vector<GameInfoDatabase::Context> GameInfoDatabase::GetContexts() const {
|
||||||
|
std::vector<Context> contexts;
|
||||||
|
|
||||||
|
if (!is_valid_) {
|
||||||
|
return contexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto xdbf_contexts = xdbf_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 = xdbf_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 = xdbf_gamedata_->GetAchievements();
|
||||||
|
for (const auto& entry : xdbf_achievements) {
|
||||||
|
achievements.push_back(GetAchievement(entry.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return achievements;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace util
|
||||||
|
} // namespace kernel
|
||||||
|
} // namespace xe
|
|
@ -0,0 +1,135 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* 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_UTIL_GAME_INFO_DATABASE_H_
|
||||||
|
#define XENIA_KERNEL_UTIL_GAME_INFO_DATABASE_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/kernel/util/xdbf_utils.h"
|
||||||
|
#include "xenia/kernel/util/xlast.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace kernel {
|
||||||
|
namespace util {
|
||||||
|
|
||||||
|
class GameInfoDatabase {
|
||||||
|
public:
|
||||||
|
struct Context {
|
||||||
|
uint32_t id;
|
||||||
|
uint32_t max_value;
|
||||||
|
uint32_t default_value;
|
||||||
|
std::string description;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Property {
|
||||||
|
uint32_t id;
|
||||||
|
uint32_t data_size;
|
||||||
|
std::string description;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Achievement {
|
||||||
|
uint32_t id;
|
||||||
|
std::string label;
|
||||||
|
std::string description;
|
||||||
|
std::string unachieved_description;
|
||||||
|
uint32_t image_id;
|
||||||
|
uint32_t gamerscore;
|
||||||
|
uint32_t flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Query {
|
||||||
|
uint32_t id;
|
||||||
|
std::string name;
|
||||||
|
std::vector<uint32_t> input_parameters;
|
||||||
|
std::vector<uint32_t> filters;
|
||||||
|
std::vector<uint32_t> expected_return;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Filter {
|
||||||
|
uint32_t left_id;
|
||||||
|
uint32_t right_id;
|
||||||
|
std::string comparation_operator;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Field {
|
||||||
|
uint32_t ordinal;
|
||||||
|
std::string name;
|
||||||
|
bool is_hidden;
|
||||||
|
uint16_t attribute_id;
|
||||||
|
// std::map<property_id, aggregation string>
|
||||||
|
std::map<uint32_t, std::string> property_aggregation;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StatsView {
|
||||||
|
uint32_t id;
|
||||||
|
std::string name;
|
||||||
|
std::vector<Field> fields;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ProductInformation {
|
||||||
|
uint32_t max_offline_players_count;
|
||||||
|
uint32_t max_systemlink_players_count;
|
||||||
|
uint32_t max_live_players_count;
|
||||||
|
std::string publisher_name;
|
||||||
|
std::string developer_name;
|
||||||
|
std::string marketing_info;
|
||||||
|
std::string genre_description;
|
||||||
|
std::vector<std::string> features;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Normally titles have at least XDBF file embedded into xex. There are
|
||||||
|
// certain exceptions and that's why we need to check if it is even valid.
|
||||||
|
GameInfoDatabase(const XdbfGameData* data);
|
||||||
|
~GameInfoDatabase();
|
||||||
|
|
||||||
|
bool IsValid() const { return is_valid_; }
|
||||||
|
|
||||||
|
// This is mostly extracted from XDBF.
|
||||||
|
std::string GetTitleName(
|
||||||
|
const XLanguage language = XLanguage::kInvalid) const;
|
||||||
|
|
||||||
|
XLanguage GetDefaultLanguage() const;
|
||||||
|
std::string GetLocalizedString(
|
||||||
|
const uint32_t id, XLanguage language = XLanguage::kInvalid) const;
|
||||||
|
|
||||||
|
std::vector<uint8_t> GetIcon() const;
|
||||||
|
|
||||||
|
Context GetContext(const uint32_t id) const;
|
||||||
|
Property GetProperty(const uint32_t id) const;
|
||||||
|
Achievement GetAchievement(const uint32_t id) const;
|
||||||
|
|
||||||
|
// TODO: Implement it in the future.
|
||||||
|
StatsView GetStatsView(const uint32_t id) const;
|
||||||
|
std::vector<uint32_t> GetMatchmakingAttributes(const uint32_t id) const;
|
||||||
|
|
||||||
|
// This is extracted from XLast.
|
||||||
|
Query GetQueryData(const uint32_t id) const;
|
||||||
|
std::vector<XLanguage> GetSupportedLanguages() const;
|
||||||
|
ProductInformation GetProductInformation() const;
|
||||||
|
|
||||||
|
// Aggregators for specific usecases
|
||||||
|
std::vector<Context> GetContexts() const;
|
||||||
|
std::vector<Property> GetProperties() const;
|
||||||
|
std::vector<Achievement> GetAchievements() const;
|
||||||
|
// TODO: Implement it in the future.
|
||||||
|
std::vector<StatsView> GetStatsViews() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool is_valid_ = false;
|
||||||
|
std::unique_ptr<XdbfGameData> xdbf_gamedata_;
|
||||||
|
std::unique_ptr<XLast> xlast_gamedata_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace util
|
||||||
|
} // namespace kernel
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_KERNEL_UTIL_GAME_INFO_DATABASE_H_
|
|
@ -86,7 +86,7 @@ XLast::XLast(const uint8_t* compressed_xml_data,
|
||||||
|
|
||||||
XLast::~XLast() {}
|
XLast::~XLast() {}
|
||||||
|
|
||||||
std::u16string XLast::GetTitleName() {
|
std::u16string XLast::GetTitleName() const {
|
||||||
std::string xpath = "/XboxLiveSubmissionProject/GameConfigProject";
|
std::string xpath = "/XboxLiveSubmissionProject/GameConfigProject";
|
||||||
|
|
||||||
const pugi::xpath_node node = parsed_xlast_->select_node(xpath.c_str());
|
const pugi::xpath_node node = parsed_xlast_->select_node(xpath.c_str());
|
||||||
|
@ -97,8 +97,70 @@ std::u16string XLast::GetTitleName() {
|
||||||
return xe::to_utf16(node.node().attribute("titleName").value());
|
return xe::to_utf16(node.node().attribute("titleName").value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::map<ProductInformationEntry, uint32_t>
|
||||||
|
XLast::GetProductInformationAttributes() const {
|
||||||
|
std::map<ProductInformationEntry, uint32_t> attributes;
|
||||||
|
|
||||||
|
std::string xpath =
|
||||||
|
"/XboxLiveSubmissionProject/GameConfigProject/ProductInformation";
|
||||||
|
|
||||||
|
const pugi::xpath_node node = parsed_xlast_->select_node(xpath.c_str());
|
||||||
|
if (!node) {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto node_attributes = node.node().attributes();
|
||||||
|
for (const auto& attribute : node_attributes) {
|
||||||
|
const auto entry =
|
||||||
|
product_information_entry_string_to_enum.find(attribute.name());
|
||||||
|
if (entry == product_information_entry_string_to_enum.cend()) {
|
||||||
|
XELOGW("GetProductInformationAttributes: Missing attribute: {}",
|
||||||
|
attribute.name());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string attribute_value = std::string(attribute.value());
|
||||||
|
if (attribute_value.empty()) {
|
||||||
|
XELOGW(
|
||||||
|
"GetProductInformationAttributes: Attribute: {} Contains no value!",
|
||||||
|
attribute.name());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes.emplace(entry->second,
|
||||||
|
xe::string_util::from_string<uint32_t>(attribute_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<XLanguage> XLast::GetSupportedLanguages() const {
|
||||||
|
std::vector<XLanguage> launguages;
|
||||||
|
|
||||||
|
std::string xpath = fmt::format(
|
||||||
|
"/XboxLiveSubmissionProject/GameConfigProject/LocalizedStrings");
|
||||||
|
|
||||||
|
const pugi::xpath_node node = parsed_xlast_->select_node(xpath.c_str());
|
||||||
|
if (!node) {
|
||||||
|
return launguages;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto locale = node.node().children("SupportedLocale");
|
||||||
|
for (auto itr = locale.begin(); itr != locale.end(); itr++) {
|
||||||
|
const std::string locale_name = itr->attribute("locale").value();
|
||||||
|
|
||||||
|
for (const auto& language : language_mapping) {
|
||||||
|
if (language.second == locale_name) {
|
||||||
|
launguages.push_back(language.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return launguages;
|
||||||
|
}
|
||||||
|
|
||||||
std::u16string XLast::GetLocalizedString(uint32_t string_id,
|
std::u16string XLast::GetLocalizedString(uint32_t string_id,
|
||||||
XLanguage language) {
|
XLanguage language) const {
|
||||||
std::string xpath = fmt::format(
|
std::string xpath = fmt::format(
|
||||||
"/XboxLiveSubmissionProject/GameConfigProject/LocalizedStrings/"
|
"/XboxLiveSubmissionProject/GameConfigProject/LocalizedStrings/"
|
||||||
"LocalizedString[@id = \"{}\"]",
|
"LocalizedString[@id = \"{}\"]",
|
||||||
|
@ -120,7 +182,8 @@ std::u16string XLast::GetLocalizedString(uint32_t string_id,
|
||||||
return xe::to_utf16(locale_node.child_value());
|
return xe::to_utf16(locale_node.child_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
XLastMatchmakingQuery* XLast::GetMatchmakingQuery(const uint32_t query_id) {
|
XLastMatchmakingQuery* XLast::GetMatchmakingQuery(
|
||||||
|
const uint32_t query_id) const {
|
||||||
std::string xpath = fmt::format(
|
std::string xpath = fmt::format(
|
||||||
"/XboxLiveSubmissionProject/GameConfigProject/Matchmaking/Queries/"
|
"/XboxLiveSubmissionProject/GameConfigProject/Matchmaking/Queries/"
|
||||||
"Query[@id = \"{}\"]",
|
"Query[@id = \"{}\"]",
|
||||||
|
@ -151,7 +214,7 @@ std::vector<uint32_t> XLast::GetAllValuesFromNode(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void XLast::Dump(std::string file_name) {
|
void XLast::Dump(std::string file_name) const {
|
||||||
if (xlast_decompressed_xml_.empty()) {
|
if (xlast_decompressed_xml_.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -171,7 +234,7 @@ void XLast::Dump(std::string file_name) {
|
||||||
fclose(outfile);
|
fclose(outfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string XLast::GetLocaleStringFromLanguage(XLanguage language) {
|
std::string XLast::GetLocaleStringFromLanguage(XLanguage language) const {
|
||||||
const auto value = language_mapping.find(language);
|
const auto value = language_mapping.find(language);
|
||||||
if (value != language_mapping.cend()) {
|
if (value != language_mapping.cend()) {
|
||||||
return value->second;
|
return value->second;
|
||||||
|
|
|
@ -21,6 +21,27 @@ namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
namespace util {
|
namespace util {
|
||||||
|
|
||||||
|
enum class ProductInformationEntry {
|
||||||
|
MaxOfflinePlayers,
|
||||||
|
MaxSystemLinkPlayers,
|
||||||
|
MaxLivePlayers,
|
||||||
|
PublisherString,
|
||||||
|
DeveloperString,
|
||||||
|
MarketingString,
|
||||||
|
GenreTypeString
|
||||||
|
};
|
||||||
|
|
||||||
|
static const std::map<std::string, ProductInformationEntry>
|
||||||
|
product_information_entry_string_to_enum = {
|
||||||
|
{"offlinePlayersMax", ProductInformationEntry::MaxOfflinePlayers},
|
||||||
|
{"systemLinkPlayersMax", ProductInformationEntry::MaxSystemLinkPlayers},
|
||||||
|
{"livePlayersMax", ProductInformationEntry::MaxLivePlayers},
|
||||||
|
{"publisherStringId", ProductInformationEntry::PublisherString},
|
||||||
|
{"developerStringId", ProductInformationEntry::DeveloperString},
|
||||||
|
{"sellTextStringId", ProductInformationEntry::MarketingString},
|
||||||
|
{"genreTextStringId", ProductInformationEntry::GenreTypeString},
|
||||||
|
};
|
||||||
|
|
||||||
static const std::map<XLanguage, std::string> language_mapping = {
|
static const std::map<XLanguage, std::string> language_mapping = {
|
||||||
{XLanguage::kEnglish, "en-US"}, {XLanguage::kJapanese, "ja-JP"},
|
{XLanguage::kEnglish, "en-US"}, {XLanguage::kJapanese, "ja-JP"},
|
||||||
{XLanguage::kGerman, "de-DE"}, {XLanguage::kFrench, "fr-FR"},
|
{XLanguage::kGerman, "de-DE"}, {XLanguage::kFrench, "fr-FR"},
|
||||||
|
@ -50,17 +71,22 @@ class XLast {
|
||||||
const uint32_t decompressed_data_size);
|
const uint32_t decompressed_data_size);
|
||||||
~XLast();
|
~XLast();
|
||||||
|
|
||||||
std::u16string GetTitleName();
|
std::u16string GetTitleName() const;
|
||||||
std::u16string GetLocalizedString(uint32_t string_id, XLanguage language);
|
std::map<ProductInformationEntry, uint32_t> GetProductInformationAttributes()
|
||||||
XLastMatchmakingQuery* GetMatchmakingQuery(uint32_t query_id);
|
const;
|
||||||
|
|
||||||
|
std::vector<XLanguage> GetSupportedLanguages() const;
|
||||||
|
std::u16string GetLocalizedString(uint32_t string_id,
|
||||||
|
XLanguage language) const;
|
||||||
|
XLastMatchmakingQuery* GetMatchmakingQuery(uint32_t query_id) const;
|
||||||
static std::vector<uint32_t> GetAllValuesFromNode(
|
static std::vector<uint32_t> GetAllValuesFromNode(
|
||||||
const pugi::xpath_node node, const std::string child_name,
|
const pugi::xpath_node node, const std::string child_name,
|
||||||
const std::string attirbute_name);
|
const std::string attirbute_name);
|
||||||
|
|
||||||
void Dump(std::string file_name);
|
void Dump(std::string file_name) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string GetLocaleStringFromLanguage(XLanguage language);
|
std::string GetLocaleStringFromLanguage(XLanguage language) const;
|
||||||
|
|
||||||
std::vector<uint8_t> xlast_decompressed_xml_;
|
std::vector<uint8_t> xlast_decompressed_xml_;
|
||||||
std::unique_ptr<pugi::xml_document> parsed_xlast_ = nullptr;
|
std::unique_ptr<pugi::xml_document> parsed_xlast_ = nullptr;
|
||||||
|
|
|
@ -16,6 +16,16 @@
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
|
|
||||||
|
union AttributeKey {
|
||||||
|
uint32_t value;
|
||||||
|
struct {
|
||||||
|
uint32_t id : 14;
|
||||||
|
uint32_t unk : 2;
|
||||||
|
uint32_t size : 12;
|
||||||
|
uint32_t type : 4;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
enum class X_USER_DATA_TYPE : uint8_t {
|
enum class X_USER_DATA_TYPE : uint8_t {
|
||||||
CONTENT = 0,
|
CONTENT = 0,
|
||||||
INT32 = 1,
|
INT32 = 1,
|
||||||
|
@ -64,16 +74,6 @@ class DataByteStream : public ByteStream {
|
||||||
|
|
||||||
class UserData {
|
class UserData {
|
||||||
public:
|
public:
|
||||||
union Key {
|
|
||||||
uint32_t value;
|
|
||||||
struct {
|
|
||||||
uint32_t id : 14;
|
|
||||||
uint32_t unk : 2;
|
|
||||||
uint32_t size : 12;
|
|
||||||
uint32_t type : 4;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
UserData(){};
|
UserData(){};
|
||||||
UserData(X_USER_DATA_TYPE type) { data_.type = type; }
|
UserData(X_USER_DATA_TYPE type) { data_.type = type; }
|
||||||
|
|
||||||
|
|
|
@ -146,7 +146,7 @@ class UserSetting {
|
||||||
X_USER_PROFILE_SETTING_SOURCE::DEFAULT;
|
X_USER_PROFILE_SETTING_SOURCE::DEFAULT;
|
||||||
|
|
||||||
X_USER_PROFILE_SETTING_HEADER header_ = {};
|
X_USER_PROFILE_SETTING_HEADER header_ = {};
|
||||||
UserData::Key setting_id_ = {};
|
AttributeKey setting_id_ = {};
|
||||||
std::unique_ptr<UserData> user_data_ = nullptr;
|
std::unique_ptr<UserData> user_data_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -199,7 +199,7 @@ uint32_t XamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index,
|
||||||
uint32_t needed_data_size = 0;
|
uint32_t needed_data_size = 0;
|
||||||
for (uint32_t i = 0; i < setting_count; ++i) {
|
for (uint32_t i = 0; i < setting_count; ++i) {
|
||||||
needed_header_size += sizeof(X_USER_PROFILE_SETTING);
|
needed_header_size += sizeof(X_USER_PROFILE_SETTING);
|
||||||
UserData::Key setting_key;
|
AttributeKey setting_key;
|
||||||
setting_key.value = static_cast<uint32_t>(setting_ids[i]);
|
setting_key.value = static_cast<uint32_t>(setting_ids[i]);
|
||||||
switch (static_cast<X_USER_DATA_TYPE>(setting_key.type)) {
|
switch (static_cast<X_USER_DATA_TYPE>(setting_key.type)) {
|
||||||
case X_USER_DATA_TYPE::WSTRING:
|
case X_USER_DATA_TYPE::WSTRING:
|
||||||
|
|
Loading…
Reference in New Issue