diff --git a/src/xenia/kernel/util/presence_string_builder.cc b/src/xenia/kernel/util/presence_string_builder.cc new file mode 100644 index 000000000..d10444f86 --- /dev/null +++ b/src/xenia/kernel/util/presence_string_builder.cc @@ -0,0 +1,153 @@ +/** + ****************************************************************************** + * 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/presence_string_builder.h" +#include "xenia/kernel/util/shim_utils.h" + +namespace xe { +namespace kernel { +namespace util { + +AttributeStringFormatter::~AttributeStringFormatter() {} + +AttributeStringFormatter::AttributeStringFormatter( + std::string_view attribute_string, XLast* title_xlast, + std::map contexts) + : attribute_string_(attribute_string), attribute_to_string_mapping_() { + contexts_ = contexts; + title_xlast_ = title_xlast; + + presence_string_ = ""; + + if (!ParseAttributeString()) { + return; + } + + BuildPresenceString(); +} + +bool AttributeStringFormatter::ParseAttributeString() { + auto specifiers = GetPresenceFormatSpecifiers(); + + if (specifiers.empty()) { + return true; + } + + std::string specifier; + while (!specifiers.empty()) { + std::string specifier = specifiers.front(); + attribute_to_string_mapping_[specifier] = GetStringFromSpecifier(specifier); + specifiers.pop(); + } + return true; +} + +void AttributeStringFormatter::BuildPresenceString() { + presence_string_ = attribute_string_; + + for (const auto& entry : attribute_to_string_mapping_) { + presence_string_.replace(presence_string_.find(entry.first), + entry.first.length(), entry.second); + } +} + +AttributeStringFormatter::AttributeType +AttributeStringFormatter::GetAttributeTypeFromSpecifier( + std::string_view specifier) const { + if (specifier.length() < 3) { + return AttributeStringFormatter::AttributeType::Unknown; + } + + const char presence_type = specifier.at(1); + if (presence_type == 'c') { + return AttributeStringFormatter::AttributeType::Context; + } + if (presence_type == 'p') { + return AttributeStringFormatter::AttributeType::Property; + } + return AttributeStringFormatter::AttributeType::Unknown; +} + +std::optional AttributeStringFormatter::GetAttributeIdFromSpecifier( + const std::string& specifier, + const AttributeStringFormatter::AttributeType specifier_type) const { + std::smatch string_match; + if (std::regex_search(specifier, string_match, + presence_id_extract_from_specifier)) { + return std::make_optional(stoi(string_match[1].str())); + } + + return std::nullopt; +} + +std::string AttributeStringFormatter::GetStringFromSpecifier( + std::string_view specifier) const { + const AttributeStringFormatter::AttributeType attribute_type = + GetAttributeTypeFromSpecifier(specifier); + + if (attribute_type == AttributeStringFormatter::AttributeType::Unknown) { + return ""; + } + + const auto attribute_id = + GetAttributeIdFromSpecifier(std::string(specifier), attribute_type); + if (!attribute_id) { + return ""; + } + + if (attribute_type == AttributeStringFormatter::AttributeType::Context) { + // TODO: Different handling for contexts and properties + const auto itr = contexts_.find(attribute_id.value()); + + if (itr == contexts_.cend()) { + auto attribute_placeholder = fmt::format("{{c{}}}", attribute_id.value()); + + return attribute_placeholder; + } + + const auto attribute_string_id = + title_xlast_->GetContextStringId(attribute_id.value(), itr->second); + + if (!attribute_string_id.has_value()) { + return ""; + } + + const auto attribute_string = title_xlast_->GetLocalizedString( + attribute_string_id.value(), XLanguage::kEnglish); + + return xe::to_utf8(attribute_string); + } + + if (attribute_type == AttributeStringFormatter::AttributeType::Property) { + return ""; + } + + return ""; +} + +std::queue AttributeStringFormatter::GetPresenceFormatSpecifiers() + const { + std::queue format_specifiers; + + std::smatch match; + + std::string attribute_string = attribute_string_; + + while (std::regex_search(attribute_string, match, + format_specifier_replace_fragment_regex_)) { + for (const auto& presence : match) { + format_specifiers.emplace(presence); + } + attribute_string = match.suffix().str(); + } + return format_specifiers; +} +} // namespace util +} // namespace kernel +} // namespace xe \ No newline at end of file diff --git a/src/xenia/kernel/util/presence_string_builder.h b/src/xenia/kernel/util/presence_string_builder.h new file mode 100644 index 000000000..69e8cc13f --- /dev/null +++ b/src/xenia/kernel/util/presence_string_builder.h @@ -0,0 +1,80 @@ +/** + ****************************************************************************** + * 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_PRESENCE_STRING_BUILDER_H_ +#define XENIA_KERNEL_UTIL_PRESENCE_STRING_BUILDER_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "xenia/kernel/util/xlast.h" + +namespace xe { +namespace kernel { +namespace util { + +class AttributeStringFormatter { + public: + ~AttributeStringFormatter(); + + AttributeStringFormatter(std::string_view attribute_string, + XLast* title_xlast, + std::map contexts); + + bool IsValid() const { return true; } + std::string GetPresenceString() const { return presence_string_; } + + private: + enum class AttributeType { Context = 0, Property = 1, Unknown = 255 }; + + const std::regex presence_id_extract_from_specifier = + std::regex("\\{c(\\d+)\\}"); + const std::regex format_specifier_replace_fragment_regex_ = + std::regex(R"(\{c\d+\}|\{p0x\d+\})"); + + bool ParseAttributeString(); + void BuildPresenceString(); + + std::string GetStringFromSpecifier(std::string_view specifier) const; + std::queue GetPresenceFormatSpecifiers() const; + + AttributeType GetAttributeTypeFromSpecifier(std::string_view specifier) const; + std::optional GetAttributeIdFromSpecifier( + const std::string& specifier, + const AttributeStringFormatter::AttributeType specifier_type) const; + + const std::string attribute_string_; + std::map attribute_to_string_mapping_; + + std::string presence_string_; + + std::map contexts_; + + XLast* title_xlast_; + + // Tests + // + // std::map contexts_ = { + // {0, "Context 0"}, {1, "Context 1"}, {2, "Context 2"}}; + + // std::map properties_ = { + // {0x10000001, "Prop 0"}, {0x20000002, "Prop 2"}, {0x30000001, "Prop + // 3"}}; +}; + +} // namespace util +} // namespace kernel +} // namespace xe + +#endif XENIA_KERNEL_UTIL_PRESENCE_STRING_BUILDER_H_ \ No newline at end of file diff --git a/src/xenia/kernel/util/xlast.cc b/src/xenia/kernel/util/xlast.cc index 9bef86bdd..29a75734b 100644 --- a/src/xenia/kernel/util/xlast.cc +++ b/src/xenia/kernel/util/xlast.cc @@ -258,6 +258,33 @@ const std::u16string XLast::GetPresenceRawString(const uint32_t presence_value, return raw_presence; } +const std::optional XLast::GetContextStringId( + const uint32_t context_id, const uint32_t context_value) { + std::string xpath = fmt::format( + "/XboxLiveSubmissionProject/GameConfigProject/Contexts/Context[@id = " + "\"0x{:08X}\"]/ContextValue[@value = \"{}\"]", + context_id, context_value); + + std::optional value = std::nullopt; + + if (!HasXLast()) { + return value; + } + + pugi::xpath_node node = parsed_xlast_->select_node(xpath.c_str()); + + if (node) { + // const auto default_value = + // node.node().parent().attribute("defaultValue").value(); + // value = xe::string_util::from_string(default_value); + + const auto string_id_value = node.node().attribute("stringId").value(); + value = xe::string_util::from_string(string_id_value); + } + + return value; +} + XLastMatchmakingQuery* XLast::GetMatchmakingQuery( const uint32_t query_id) const { std::string xpath = fmt::format( diff --git a/src/xenia/kernel/util/xlast.h b/src/xenia/kernel/util/xlast.h index 06111f21f..6624c7215 100644 --- a/src/xenia/kernel/util/xlast.h +++ b/src/xenia/kernel/util/xlast.h @@ -83,6 +83,8 @@ class XLast { const std::optional GetPropertyStringId(const uint32_t property_id); const std::u16string GetPresenceRawString(const uint32_t presence_value, const XLanguage language); + const std::optional GetContextStringId( + const uint32_t context_id, const uint32_t context_value); XLastMatchmakingQuery* GetMatchmakingQuery(uint32_t query_id) const; static std::vector GetAllValuesFromNode( const pugi::xpath_node node, const std::string child_name,