#pragma once #include "util/tinyxml2/tinyxml2.h" #include "Common/FileStream.h" #include "config/ConfigValue.h" #include #include class XMLConfigParser { public: XMLConfigParser(tinyxml2::XMLDocument* document) : m_document(document), m_current_element(nullptr), m_is_root(true) {} private: XMLConfigParser(tinyxml2::XMLDocument* document, tinyxml2::XMLElement* element) : m_document(document), m_current_element(element), m_is_root(false) {} public: template auto get(const char* name, T default_value = {}) { if constexpr(std::is_enum_v) return static_cast(get(name, static_cast>(default_value))); const auto* element = m_current_element ? m_current_element->FirstChildElement(name) : m_document->FirstChildElement(name); if (element == nullptr) return default_value; if constexpr (std::is_same_v) return element->BoolText(default_value); else if constexpr (std::is_same_v) return element->FloatText(default_value); else if constexpr (std::is_same_v) return element->IntText(default_value); else if constexpr (std::is_same_v) return element->UnsignedText(default_value); else if constexpr (std::is_same_v) return element->Int64Text(default_value); else if constexpr (std::is_same_v) // doesnt support real uint64... return (uint64)element->Int64Text((sint64)default_value); else if constexpr (std::is_same_v || std::is_same_v) { const char* text = element->GetText(); return text ? text : default_value; } return default_value; } template T get(const char* name, const ConfigValue& default_value) { return get(name, default_value.GetInitValue()); } template T get(const char* name, const ConfigValueBounds& default_value) { return get(name, default_value.GetInitValue()); } template auto get_attribute(const char* name, T default_value = {}) { if constexpr (std::is_enum_v) return static_cast(get_attribute(name, static_cast>(default_value))); if (m_current_element == nullptr) return default_value; if constexpr (std::is_same_v) return m_current_element->BoolAttribute(name, default_value); else if constexpr (std::is_same_v) return m_current_element->FloatAttribute(name, default_value); else if constexpr (std::is_same_v) return m_current_element->IntAttribute(name, default_value); else if constexpr (std::is_same_v) return m_current_element->UnsignedAttribute(name, default_value); else if constexpr (std::is_same_v) return m_current_element->Int64Attribute(name, default_value); else if constexpr (std::is_same_v) // doesnt support real uint64... return (uint64)m_current_element->Int64Attribute(name, (sint64)default_value); else if constexpr (std::is_same_v || std::is_same_v) { const char* text = m_current_element->Attribute(name); return text ? text : default_value; } return default_value; } template T get_attribute(const char* name, const ConfigValue& default_value) { return get_attribute(name, default_value.GetInitValue()); } template T get_attribute(const char* name, const ConfigValueBounds& default_value) { return get_attribute(name, default_value.GetInitValue()); } int char2int(char input) { if(input >= '0' && input <= '9') return input - '0'; if(input >= 'A' && input <= 'F') return input - 'A' + 10; if(input >= 'a' && input <= 'f') return input - 'a' + 10; throw std::invalid_argument("Invalid input string"); } template std::array& get(const char* name, std::array& arr) { arr = {}; const auto element = m_current_element ? m_current_element->FirstChildElement(name) : m_document->FirstChildElement(name); if (element == nullptr) return arr; const char* text = element->GetText(); if(text) { assert(strlen(text) == (arr.size() * 2)); std::istringstream iss(text); for(int i = 0; i < arr.size(); ++i) { arr[i] = (char2int(text[i*2]) << 4) + char2int(text[i * 2 + 1]); } } return arr; } template T value(T default_value = T()) { //peterBreak(); return default_value; } bool value(bool default_value) { return m_current_element ? m_current_element->BoolText(default_value) : default_value; } float value(float default_value) { return m_current_element ? m_current_element->FloatText(default_value) : default_value; } double value(double default_value) { return m_current_element ? m_current_element->DoubleText(default_value) : default_value; } uint32 value(uint32 default_value) { return m_current_element ? m_current_element->UnsignedText(default_value) : default_value; } sint32 value(sint32 default_value) { return m_current_element ? m_current_element->IntText(default_value) : default_value; } uint64 value(uint64 default_value) { return m_current_element ? (uint64)m_current_element->Int64Text(default_value) : default_value; } sint64 value(sint64 default_value) { return m_current_element ? m_current_element->Int64Text(default_value) : default_value; } const char* value(const char* default_value) { if (m_current_element) { if (m_current_element->GetText()) return m_current_element->GetText(); } return default_value; } template void set(const char* name, const std::array& value) { auto element = m_document->NewElement(name); std::stringstream str; for(const auto& v : value) { str << fmt::format("{:02x}", v); } element->SetText(str.str().c_str()); if (m_current_element) m_current_element->InsertEndChild(element); else m_document->InsertEndChild(element); } template void set(const char* name, T value) { auto* element = m_document->NewElement(name); if constexpr (std::is_enum::value) element->SetText(fmt::format("{}", static_cast::type>(value)).c_str()); else element->SetText(fmt::format("{}", value).c_str()); if (m_current_element) m_current_element->InsertEndChild(element); else m_document->InsertEndChild(element); } template void set(const char* name, const std::atomic& value) { set(name, value.load()); } void set(const char* name, uint64 value) { set(name, (sint64)value); } tinyxml2::XMLElement* GetCurrentElement() const { return m_current_element; } XMLConfigParser get(const char* name) const { const auto element = m_current_element ? m_current_element->FirstChildElement(name) : m_document->FirstChildElement(name); return {m_document, element}; } XMLConfigParser get(const char* name, const XMLConfigParser& parser) { const auto element = parser.m_current_element ? parser.m_current_element->NextSiblingElement(name) : parser.m_document->NextSiblingElement(name); return { m_document, element }; } XMLConfigParser set(const char* name) const { const auto element = m_document->NewElement(name); if (m_current_element) m_current_element->InsertEndChild(element); else m_document->InsertEndChild(element); return {m_document, element}; } template XMLConfigParser& set_attribute(const char* name, const T& value) { cemu_assert_debug(m_current_element != nullptr); if (m_current_element) m_current_element->SetAttribute(name, value); return *this; } template XMLConfigParser& set_attribute(const char* name, const ConfigValue& value) { return set_attribute(name, value.GetValue()); } template XMLConfigParser& set_attribute(const char* name, const ConfigValueBounds& value) { return set_attribute(name, value.GetValue()); } XMLConfigParser& set_attribute(const char* name, const std::string& value) { return set_attribute(name, value.c_str()); } bool valid() const { if (m_is_root) return m_document != nullptr; return m_document != nullptr && m_current_element != nullptr; } private: tinyxml2::XMLDocument* m_document; tinyxml2::XMLElement* m_current_element; bool m_is_root; }; template class XMLConfig { public: XMLConfig() = delete; XMLConfig(T& instance) : m_instance(instance) {} XMLConfig(T& instance, std::wstring_view filename) : m_instance(instance), m_filename(filename) {} XMLConfig(const XMLConfig&) = delete; virtual ~XMLConfig() = default; bool Load() { if (L == nullptr) return false; if (m_filename.empty()) return false; return Load(m_filename); } bool Load(const std::wstring& filename) { if (L == nullptr) return false; FileStream* fs = FileStream::openFile(filename.c_str()); if (!fs) { cemuLog_logDebug(LogType::Force, "XMLConfig::Load > failed \"{}\" with error {}", boost::nowide::narrow(filename), errno); return false; } std::vector xmlData; xmlData.resize(fs->GetSize()); fs->readData(xmlData.data(), xmlData.size()); delete fs; tinyxml2::XMLDocument doc; const tinyxml2::XMLError error = doc.Parse((const char*)xmlData.data(), xmlData.size()); const bool success = error == tinyxml2::XML_SUCCESS; if (error != 0) { cemuLog_logDebug(LogType::Force, "XMLConfig::Load > LoadFile {}", error); } if (success) { auto parser = XMLConfigParser(&doc); (m_instance.*L)(parser); } return true; } bool Save() { if (S == nullptr) return false; if (m_filename.empty()) return false; return Save(m_filename); } bool Save(const std::wstring& filename) { if (S == nullptr) return false; std::wstring tmp_name = fmt::format(L"{}_{}.tmp", filename,rand() % 1000); std::error_code err; fs::create_directories(fs::path(filename).parent_path(), err); if (err) { cemuLog_log(LogType::Force, "can't create parent path for save file: {}", err.message()); return false; } FILE* file = nullptr; #if BOOST_OS_WINDOWS file = _wfopen(tmp_name.c_str(), L"wb"); #else file = fopen(boost::nowide::narrow(tmp_name).c_str(), "wb"); #endif if (!file) { cemuLog_logDebug(LogType::Force, "XMLConfig::Save > failed \"{}\" with error {}", boost::nowide::narrow(filename), errno); return false; } tinyxml2::XMLDocument doc; const auto declaration = doc.NewDeclaration(); doc.InsertFirstChild(declaration); auto parser = XMLConfigParser(&doc); (m_instance.*S)(parser); const tinyxml2::XMLError error = doc.SaveFile(file); const bool success = error == tinyxml2::XML_SUCCESS; if(error != 0) cemuLog_logDebug(LogType::Force, "XMLConfig::Save > SaveFile {}", error); fflush(file); fclose(file); fs::rename(tmp_name, filename, err); if(err) { cemuLog_log(LogType::Force, "Unable to save settings to file: {}", err.message().c_str()); fs::remove(tmp_name, err); } return success; } [[nodiscard]] const std::wstring& GetFilename() const { return m_filename; } void SetFilename(const std::wstring& filename) { m_filename = filename; } std::unique_lock Lock() { return std::unique_lock(m_mutex); } private: T& m_instance; std::wstring m_filename; std::mutex m_mutex; }; template class XMLDataConfig : public XMLConfig { public: XMLDataConfig() : XMLConfig::XMLConfig(m_data), m_data() {} XMLDataConfig(std::wstring_view filename) : XMLConfig::XMLConfig(m_data, filename), m_data() {} XMLDataConfig(std::wstring_view filename, T init_data) : XMLConfig::XMLConfig(m_data, filename), m_data(std::move(init_data)) {} XMLDataConfig(const XMLDataConfig& o) = delete; T& data() { return m_data; } private: T m_data; };