[Kernel] Implemented Game Info Database

This aggregates XDBF and XLAST into one entity.

Replaced UserData::Key into more versatile AttributeKey for future usage in properties
This commit is contained in:
Gliniak 2024-03-22 11:14:31 +01:00
parent 2709a5d3e8
commit 86bd62125d
16 changed files with 840 additions and 29 deletions

6
.gitmodules vendored
View File

@ -97,3 +97,9 @@
[submodule "third_party/rapidcsv"] [submodule "third_party/rapidcsv"]
path = third_party/rapidcsv path = third_party/rapidcsv
url = https://github.com/d99kris/rapidcsv url = https://github.com/d99kris/rapidcsv
[submodule "third_party/zlib"]
path = third_party/zlib
url = https://github.com/madler/zlib.git
[submodule "third_party/pugixml"]
path = third_party/pugixml
url = https://github.com/zeux/pugixml.git

View File

@ -262,6 +262,8 @@ workspace("xenia")
include("third_party/xxhash.lua") include("third_party/xxhash.lua")
include("third_party/zarchive.lua") include("third_party/zarchive.lua")
include("third_party/zstd.lua") include("third_party/zstd.lua")
include("third_party/zlib.lua")
include("third_party/pugixml.lua")
if not os.istarget("android") then if not os.istarget("android") then
-- SDL2 requires sdl2-config, and as of November 2020 isn't high-quality on -- SDL2 requires sdl2-config, and as of November 2020 isn't high-quality on

View File

@ -120,6 +120,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),
game_info_database_(),
paused_(false), paused_(false),
restoring_(false), restoring_(false),
restore_fence_() { restore_fence_() {
@ -1257,10 +1258,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
@ -1268,48 +1271,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)});
@ -1317,9 +1316,9 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
XELOGI("-------------------- CONTEXTS --------------------\n{}", XELOGI("-------------------- CONTEXTS --------------------\n{}",
table.str()); table.str());
auto icon_block = db.icon(); auto icon_block = game_info_database_->GetIcon();
if (icon_block) { if (!icon_block.empty()) {
display_window_->SetIcon(icon_block.buffer, icon_block.size); display_window_->SetIcon(icon_block.data(), icon_block.size());
} }
} }
} }

View File

@ -20,6 +20,8 @@
#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/memory.h" #include "xenia/memory.h"
#include "xenia/patcher/patcher.h" #include "xenia/patcher/patcher.h"
#include "xenia/patcher/plugin_loader.h" #include "xenia/patcher/plugin_loader.h"
@ -318,6 +320,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::GameInfoDatabase> game_info_database_;
bool paused_; bool paused_;
bool restoring_; bool restoring_;

View File

@ -9,6 +9,8 @@ project("xenia-kernel")
links({ links({
"aes_128", "aes_128",
"fmt", "fmt",
"zlib",
"pugixml",
"xenia-apu", "xenia-apu",
"xenia-base", "xenia-base",
"xenia-cpu", "xenia-cpu",

View File

@ -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

View File

@ -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_

View File

@ -0,0 +1,248 @@
/**
******************************************************************************
* 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/xlast.h"
#include "third_party/zlib/zlib.h"
#include "xenia/base/filesystem.h"
#include "xenia/base/logging.h"
#include "xenia/base/string_util.h"
namespace xe {
namespace kernel {
namespace util {
XLastMatchmakingQuery::XLastMatchmakingQuery() {}
XLastMatchmakingQuery::XLastMatchmakingQuery(
const pugi::xpath_node query_node) {
node_ = query_node;
}
std::string XLastMatchmakingQuery::GetName() const {
return node_.node().attribute("friendlyName").value();
}
std::vector<uint32_t> XLastMatchmakingQuery::GetReturns() const {
return XLast::GetAllValuesFromNode(node_, "Returns", "id");
}
std::vector<uint32_t> XLastMatchmakingQuery::GetParameters() const {
return XLast::GetAllValuesFromNode(node_, "Parameters", "id");
}
std::vector<uint32_t> XLastMatchmakingQuery::GetFilters() const {
return XLast::GetAllValuesFromNode(node_, "Filters", "left");
}
XLast::XLast() : parsed_xlast_(nullptr) {}
XLast::XLast(const uint8_t* compressed_xml_data,
const uint32_t compressed_data_size,
const uint32_t decompressed_data_size) {
if (!compressed_data_size || !decompressed_data_size) {
XELOGW("XLast: Current title don't have any XLast XML data!");
return;
}
parsed_xlast_ = std::make_unique<pugi::xml_document>();
xlast_decompressed_xml_.resize(decompressed_data_size);
z_stream stream;
stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
stream.opaque = Z_NULL;
stream.avail_in = 0;
stream.next_in = Z_NULL;
int ret = inflateInit2(
&stream, 16 + MAX_WBITS); // 16 + MAX_WBITS enables gzip decoding
if (ret != Z_OK) {
XELOGE("XLast: Error during Zlib stream init");
return;
}
stream.avail_in = compressed_data_size;
stream.next_in =
reinterpret_cast<Bytef*>(const_cast<uint8_t*>(compressed_xml_data));
stream.avail_out = decompressed_data_size;
stream.next_out = reinterpret_cast<Bytef*>(xlast_decompressed_xml_.data());
ret = inflate(&stream, Z_NO_FLUSH);
if (ret == Z_STREAM_ERROR) {
XELOGE("XLast: Error during XLast decompression");
inflateEnd(&stream);
return;
}
inflateEnd(&stream);
parse_result_ = parsed_xlast_->load_buffer(xlast_decompressed_xml_.data(),
xlast_decompressed_xml_.size());
}
XLast::~XLast() {}
std::u16string XLast::GetTitleName() const {
std::string xpath = "/XboxLiveSubmissionProject/GameConfigProject";
const pugi::xpath_node node = parsed_xlast_->select_node(xpath.c_str());
if (!node) {
return std::u16string();
}
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,
XLanguage language) const {
std::string xpath = fmt::format(
"/XboxLiveSubmissionProject/GameConfigProject/LocalizedStrings/"
"LocalizedString[@id = \"{}\"]",
string_id);
const pugi::xpath_node node = parsed_xlast_->select_node(xpath.c_str());
if (!node) {
return std::u16string();
}
const std::string locale_name = GetLocaleStringFromLanguage(language);
const pugi::xml_node locale_node =
node.node().find_child_by_attribute("locale", locale_name.c_str());
if (!locale_node) {
return std::u16string();
}
return xe::to_utf16(locale_node.child_value());
}
XLastMatchmakingQuery* XLast::GetMatchmakingQuery(
const uint32_t query_id) const {
std::string xpath = fmt::format(
"/XboxLiveSubmissionProject/GameConfigProject/Matchmaking/Queries/"
"Query[@id = \"{}\"]",
query_id);
XLastMatchmakingQuery* query = nullptr;
pugi::xpath_node node = parsed_xlast_->select_node(xpath.c_str());
if (!node) {
return query;
}
return new XLastMatchmakingQuery(node);
}
std::vector<uint32_t> XLast::GetAllValuesFromNode(
const pugi::xpath_node node, const std::string child_name,
const std::string attirbute_name) {
std::vector<uint32_t> result{};
const auto searched_child = node.node().child(child_name.c_str());
for (pugi::xml_node_iterator itr = searched_child.begin();
itr != searched_child.end(); itr++) {
result.push_back(xe::string_util::from_string<uint32_t>(
itr->attribute(attirbute_name.c_str()).value(), true));
}
return result;
}
void XLast::Dump(std::string file_name) const {
if (xlast_decompressed_xml_.empty()) {
return;
}
if (file_name.empty()) {
file_name = xe::to_utf8(GetTitleName());
}
FILE* outfile =
xe::filesystem::OpenFile(fmt::format("{}.xml", file_name).c_str(), "ab");
if (!outfile) {
return;
}
fwrite(xlast_decompressed_xml_.data(), 1, xlast_decompressed_xml_.size(),
outfile);
fclose(outfile);
}
std::string XLast::GetLocaleStringFromLanguage(XLanguage language) const {
const auto value = language_mapping.find(language);
if (value != language_mapping.cend()) {
return value->second;
}
return language_mapping.at(XLanguage::kEnglish);
}
} // namespace util
} // namespace kernel
} // namespace xe

View File

@ -0,0 +1,100 @@
/**
******************************************************************************
* 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_XLAST_H_
#define XENIA_KERNEL_UTIL_XLAST_H_
#include <map>
#include <string>
#include <vector>
#include "third_party/pugixml/src/pugixml.hpp"
#include "xenia/xbox.h"
namespace xe {
namespace kernel {
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 = {
{XLanguage::kEnglish, "en-US"}, {XLanguage::kJapanese, "ja-JP"},
{XLanguage::kGerman, "de-DE"}, {XLanguage::kFrench, "fr-FR"},
{XLanguage::kSpanish, "es-ES"}, {XLanguage::kItalian, "it-IT"},
{XLanguage::kKorean, "ko-KR"}, {XLanguage::kTChinese, "zh-CHT"},
{XLanguage::kPortuguese, "pt-PT"}, {XLanguage::kPolish, "pl-PL"},
{XLanguage::kRussian, "ru-RU"}};
class XLastMatchmakingQuery {
public:
XLastMatchmakingQuery();
XLastMatchmakingQuery(const pugi::xpath_node query_node);
std::string GetName() const;
std::vector<uint32_t> GetReturns() const;
std::vector<uint32_t> GetParameters() const;
std::vector<uint32_t> GetFilters() const;
private:
pugi::xpath_node node_;
};
class XLast {
public:
XLast();
XLast(const uint8_t* compressed_xml_data, const uint32_t compressed_data_size,
const uint32_t decompressed_data_size);
~XLast();
std::u16string GetTitleName() const;
std::map<ProductInformationEntry, uint32_t> GetProductInformationAttributes()
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(
const pugi::xpath_node node, const std::string child_name,
const std::string attirbute_name);
void Dump(std::string file_name) const;
private:
std::string GetLocaleStringFromLanguage(XLanguage language) const;
std::vector<uint8_t> xlast_decompressed_xml_;
std::unique_ptr<pugi::xml_document> parsed_xlast_ = nullptr;
pugi::xml_parse_result parse_result_ = {};
};
} // namespace util
} // namespace kernel
} // namespace xe
#endif

View File

@ -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,

View File

@ -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;
}; };

View File

@ -200,7 +200,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:

1
third_party/pugixml vendored Submodule

@ -0,0 +1 @@
Subproject commit b2b466403084667c90a0f0cc4e960405cfc8117a

10
third_party/pugixml.lua vendored Normal file
View File

@ -0,0 +1,10 @@
group("third_party")
project("pugixml")
uuid("d1089e5e-46ae-48c9-bee6-d38c674c8f61")
kind("StaticLib")
language("C++")
files({
"pugixml/src/pugiconfig.hpp",
"pugixml/src/pugixml.cpp",
"pugixml/src/pugixml.hpp",
})

1
third_party/zlib vendored Submodule

@ -0,0 +1 @@
Subproject commit 51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf

9
third_party/zlib.lua vendored Normal file
View File

@ -0,0 +1,9 @@
group("third_party")
project("zlib")
uuid("13d4073d-f1c9-47e3-a057-79b133596fc2")
kind("StaticLib")
language("C")
files({
"zlib/*.c",
"zlib/*.h",
})