From 5e58083c01e0dd099949d5162781c4b3e1ee6d02 Mon Sep 17 00:00:00 2001
From: Philip Rebohle <philip.rebohle@tu-dortmund.de>
Date: Tue, 7 Aug 2018 14:13:57 +0200
Subject: [PATCH] [util] Add classes and functions to support configuration
 files

---
 src/util/config/config.cpp      |  86 ++++++++++++++++++++++++
 src/util/config/config.h        |  89 +++++++++++++++++++++++++
 src/util/config/config_user.cpp | 112 ++++++++++++++++++++++++++++++++
 src/util/config/config_user.h   |  30 +++++++++
 src/util/meson.build            |   3 +
 5 files changed, 320 insertions(+)
 create mode 100644 src/util/config/config.cpp
 create mode 100644 src/util/config/config.h
 create mode 100644 src/util/config/config_user.cpp
 create mode 100644 src/util/config/config_user.h

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 <string>
+#include <unordered_map>
+
+namespace dxvk {
+
+  /**
+   * \brief Config option set
+   * 
+   * Stores configuration options
+   * as a set of key-value pairs.
+   */
+  class Config {
+    using OptionMap = std::unordered_map<std::string, std::string>;
+  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<typename T>
+    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 <fstream>
+#include <sstream>
+#include <iostream>
+
+#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<std::string, Config> 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',