diff --git a/src/util/config/config.cpp b/src/util/config/config.cpp new file mode 100644 index 00000000..e9f05527 --- /dev/null +++ b/src/util/config/config.cpp @@ -0,0 +1,86 @@ +#include "config.h" + +namespace dxvk { + + Config::Config() { } + Config::~Config() { } + + + Config::Config(OptionMap&& options) + : m_options(std::move(options)) { } + + + void Config::merge(const Config& other) { + for (auto& pair : other.m_options) + m_options.insert(pair); + } + + + void Config::setOption(const std::string& key, const std::string& value) { + m_options.insert_or_assign(key, value); + } + + + std::string Config::getOptionValue(const char* option) const { + auto iter = m_options.find(option); + + return iter != m_options.end() + ? iter->second : std::string(); + } + + + bool Config::parseOptionValue( + const std::string& value, + std::string& result) { + result = value; + return true; + } + + + bool Config::parseOptionValue( + const std::string& value, + bool& result) { + if (value == "True") { + result = true; + return true; + } else if (value == "False") { + result = false; + return true; + } else { + return false; + } + } + + + bool Config::parseOptionValue( + const std::string& value, + int32_t& result) { + if (value.size() == 0) + return false; + + // Parse sign, don't allow '+' + int32_t sign = 1; + size_t start = 0; + + if (value[0] == '-') { + sign = -1; + start = 1; + } + + // Parse absolute number + int32_t intval = 0; + + for (size_t i = start; i < value.size(); i++) { + if (value[i] < '0' || value[i] > '9') + return false; + + intval *= 10; + intval += value[i] - '0'; + } + + // Apply sign and return + result = sign * intval; + return true; + } + +} \ No newline at end of file diff --git a/src/util/config/config.h b/src/util/config/config.h new file mode 100644 index 00000000..dd0ae951 --- /dev/null +++ b/src/util/config/config.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include + +namespace dxvk { + + /** + * \brief Config option set + * + * Stores configuration options + * as a set of key-value pairs. + */ + class Config { + using OptionMap = std::unordered_map; + public: + + Config(); + Config(OptionMap&& options); + ~Config(); + + /** + * \brief Merges two configuration sets + * + * Options specified in this config object will + * not be overridden if they are specified in + * the second config object. + * \param [in] other Config set to merge. + */ + void merge(const Config& other); + + /** + * \brief Sets an option + * + * \param [in] key Option name + * \param [in] value Option value + */ + void setOption( + const std::string& key, + const std::string& value); + + /** + * \brief Parses an option value + * + * Retrieves the option value as a string, and then + * tries to convert that string to the given type. + * If parsing the string fails because it is either + * invalid or if the option is not defined, this + * method will return a fallback value. + * + * Currently, this supports the types \c bool, + * \c int32_t, and \c std::string. + * \tparam T Return value type + * \param [in] option Option name + * \param [in] fallback Fallback value + * \returns Parsed option value + * \returns The parsed option value + */ + template + T getOption(const char* option, T fallback = T()) const { + const std::string& value = getOptionValue(option); + + T result = fallback; + parseOptionValue(value, result); + return result; + } + + private: + + OptionMap m_options; + + std::string getOptionValue( + const char* option) const; + + static bool parseOptionValue( + const std::string& value, + std::string& result); + + static bool parseOptionValue( + const std::string& value, + bool& result); + + static bool parseOptionValue( + const std::string& value, + int32_t& result); + + }; + +} \ No newline at end of file diff --git a/src/util/config/config_user.cpp b/src/util/config/config_user.cpp new file mode 100644 index 00000000..49d9f355 --- /dev/null +++ b/src/util/config/config_user.cpp @@ -0,0 +1,112 @@ +#include +#include +#include + +#include "config.h" +#include "config_user.h" + +#include "../log/log.h" + +namespace dxvk { + + void parseUserConfigLine(Config& config, std::string line); + + const static std::unordered_map g_appDefaults = {{ + { "Dishonored2.exe", {{ + { "d3d11.allowMapFlagNoWait", "True" } + }} }, + { "F1_2015.exe", {{ + { "d3d11.fakeStreamOutSupport", "True" }, + }} }, + { "FarCry5.exe", {{ + { "d3d11.allowMapFlagNoWait", "True" } + }} }, + { "Frostpunk.exe", {{ + { "dxgi.deferSurfaceCreation", "True" }, + }} }, + { "Overwatch.exe", {{ + { "d3d11.fakeStreamOutSupport", "True" }, + }} }, + { "Wow.exe", {{ + { "dxgi.fakeDx10Support", "True" }, + }} }, + { "ffxv_s.exe", {{ + { "d3d11.fakeStreamOutSupport", "True" }, + }} }, + { "mafia3.exe", {{ + { "d3d11.fakeStreamOutSupport", "True" }, + }} }, + }}; + + + Config getAppConfig(const std::string& appName) { + auto appConfig = g_appDefaults.find(appName); + if (appConfig != g_appDefaults.end()) + return appConfig->second; + return Config(); + } + + + Config getUserConfig() { + Config config; + + // Load either $DXVK_CONFIG_FILE or $PWD/dxvk.conf + std::string filePath = env::getEnvVar(L"DXVK_CONFIG_FILE"); + + if (filePath == "") + filePath = "dxvk.conf"; + + // Parse the file line by line + std::ifstream stream(filePath); + std::string line; + + while (std::getline(stream, line)) + parseUserConfigLine(config, line); + + return config; + } + + + bool isWhitespace(char ch) { + return ch == ' ' || ch == '\x9' || ch == '\r'; + } + + + bool isValidKeyChar(char ch) { + return (ch >= '0' && ch <= '9') + || (ch >= 'A' && ch <= 'Z') + || (ch >= 'a' && ch <= 'z') + || (ch == '.' || ch == '_'); + } + + + size_t skipWhitespace(const std::string& line, size_t n) { + while (n < line.size() && isWhitespace(line[n])) + n += 1; + return n; + } + + + void parseUserConfigLine(Config& config, std::string line) { + std::stringstream key; + std::stringstream value; + + // Extract the key + size_t n = skipWhitespace(line, 0); + while (n < line.size() && isValidKeyChar(line[n])) + key << line[n++]; + + // Check whether the next char is a '=' + n = skipWhitespace(line, n); + if (n >= line.size() || line[n] != '=') + return; + + // Extract the value + n = skipWhitespace(line, n + 1); + while (n < line.size() && !isWhitespace(line[n])) + value << line[n++]; + + config.setOption(key.str(), value.str()); + } + +} \ No newline at end of file diff --git a/src/util/config/config_user.h b/src/util/config/config_user.h new file mode 100644 index 00000000..d98117cf --- /dev/null +++ b/src/util/config/config_user.h @@ -0,0 +1,30 @@ +#pragma once + +#include "../util_env.h" + +#include "config.h" + +namespace dxvk { + + /** + * \brief Retrieves application defaults + * + * Some apps have options enabled by default + * in order to improve compatibility and/or + * performance. + * \param [in] appName Application name + * \returns Default configuration for the app + */ + Config getAppConfig(const std::string& appName); + + /** + * \brief Retrieves user configuration + * + * Opens and parses the file \c dxvk.conf if it + * exists, or whatever file name is specified in + * the environment variable \c DXVK_CONFIG_FILE. + * \returns User configuration + */ + Config getUserConfig(); + +} \ No newline at end of file diff --git a/src/util/meson.build b/src/util/meson.build index 265aee80..889eccee 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -4,6 +4,9 @@ util_src = files([ 'com/com_guid.cpp', 'com/com_private_data.cpp', + + 'config/config.cpp', + 'config/config_user.cpp', 'log/log.cpp', 'log/log_debug.cpp',