diff options
Diffstat (limited to 'src/libs/dxvk-native-1.9.2a/src/util/config/config.cpp')
-rw-r--r-- | src/libs/dxvk-native-1.9.2a/src/util/config/config.cpp | 723 |
1 files changed, 723 insertions, 0 deletions
diff --git a/src/libs/dxvk-native-1.9.2a/src/util/config/config.cpp b/src/libs/dxvk-native-1.9.2a/src/util/config/config.cpp new file mode 100644 index 00000000..f5c940d1 --- /dev/null +++ b/src/libs/dxvk-native-1.9.2a/src/util/config/config.cpp @@ -0,0 +1,723 @@ +#include <array> +#include <fstream> +#include <sstream> +#include <iostream> +#include <regex> +#include <utility> + +#include "config.h" + +#include "../log/log.h" + +#include "../util_env.h" + +namespace dxvk { + + const static std::vector<std::pair<const char*, Config>> g_appDefaults = {{ + /* Assassin's Creed Syndicate: amdags issues */ + { R"(\\ACS\.exe$)", {{ + { "dxgi.customVendorId", "10de" }, + }} }, + /* Dissidia Final Fantasy NT Free Edition */ + { R"(\\dffnt\.exe$)", {{ + { "dxgi.deferSurfaceCreation", "True" }, + }} }, + /* Elite Dangerous: Compiles weird shaders * + * when running on AMD hardware */ + { R"(\\EliteDangerous64\.exe$)", {{ + { "dxgi.customVendorId", "10de" }, + }} }, + /* The Vanishing of Ethan Carter Redux */ + { R"(\\EthanCarter-Win64-Shipping\.exe$)", {{ + { "dxgi.customVendorId", "10de" }, + }} }, + /* The Evil Within: Submits command lists * + * multiple times */ + { R"(\\EvilWithin(Demo)?\.exe$)", {{ + { "d3d11.dcSingleUseMode", "False" }, + }} }, + /* Far Cry 3: Assumes clear(0.5) on an UNORM * + * format to result in 128 on AMD and 127 on * + * Nvidia. We assume that the Vulkan drivers * + * match the clear behaviour of D3D11. */ + { R"(\\(farcry3|fc3_blooddragon)_d3d11\.exe$)", {{ + { "dxgi.nvapiHack", "False" }, + }} }, + /* Far Cry 4: Same as Far Cry 3 */ + { R"(\\FarCry4\.exe$)", {{ + { "dxgi.nvapiHack", "False" }, + }} }, + /* Frostpunk: Renders one frame with D3D9 * + * after creating the DXGI swap chain */ + { R"(\\Frostpunk\.exe$)", {{ + { "dxgi.deferSurfaceCreation", "True" }, + }} }, + /* Nioh: See Frostpunk, apparently? */ + { R"(\\nioh\.exe$)", {{ + { "dxgi.deferSurfaceCreation", "True" }, + }} }, + /* Quantum Break: Mever initializes shared * + * memory in one of its compute shaders */ + { R"(\\QuantumBreak\.exe$)", {{ + { "d3d11.zeroInitWorkgroupMemory", "True" }, + }} }, + /* Anno 2205: Random crashes with state cache */ + { R"(\\anno2205\.exe$)", {{ + { "dxvk.enableStateCache", "False" }, + }} }, + /* Fifa '19+: Binds typed buffer SRV to shader * + * that expects raw/structured buffer SRV */ + { R"(\\FIFA(19|[2-9][0-9])(_demo)?\.exe$)", {{ + { "dxvk.useRawSsbo", "True" }, + }} }, + /* Resident Evil 2/3: Ignore WaW hazards */ + { R"(\\re(2|3|3demo)\.exe$)", {{ + { "d3d11.relaxedBarriers", "True" }, + }} }, + /* Devil May Cry 5 */ + { R"(\\DevilMayCry5\.exe$)", {{ + { "d3d11.relaxedBarriers", "True" }, + }} }, + /* Call of Duty WW2 */ + { R"(\\s2_sp64_ship\.exe$)", {{ + { "dxgi.nvapiHack", "False" }, + }} }, + /* Need for Speed 2015 */ + { R"(\\NFS16\.exe$)", {{ + { "dxgi.nvapiHack", "False" }, + }} }, + /* Mass Effect Andromeda */ + { R"(\\MassEffectAndromeda\.exe$)", {{ + { "dxgi.nvapiHack", "False" }, + }} }, + /* Mirror`s Edge Catalyst: Crashes on AMD */ + { R"(\\MirrorsEdgeCatalyst(Trial)?\.exe$)", {{ + { "dxgi.customVendorId", "10de" }, + }} }, + /* Star Wars Battlefront (2015) */ + { R"(\\starwarsbattlefront(trial)?\.exe$)", {{ + { "dxgi.nvapiHack", "False" }, + }} }, + /* Dark Souls Remastered */ + { R"(\\DarkSoulsRemastered\.exe$)", {{ + { "d3d11.constantBufferRangeCheck", "True" }, + }} }, + /* Grim Dawn */ + { R"(\\Grim Dawn\.exe$)", {{ + { "d3d11.constantBufferRangeCheck", "True" }, + }} }, + /* NieR:Automata */ + { R"(\\NieRAutomata\.exe$)", {{ + { "d3d11.constantBufferRangeCheck", "True" }, + }} }, + /* NieR Replicant */ + { R"(\\NieR Replicant ver\.1\.22474487139\.exe)", {{ + { "dxgi.syncInterval", "1" }, + { "dxgi.maxFrameRate", "60" }, + }} }, + /* SteamVR performance test */ + { R"(\\vr\.exe$)", {{ + { "d3d11.dcSingleUseMode", "False" }, + }} }, + /* Hitman 2 and 3 - requires AGS library */ + { R"(\\HITMAN(2|3)\.exe$)", {{ + { "dxgi.customVendorId", "10de" }, + }} }, + /* Modern Warfare Remastered */ + { R"(\\h1_[ms]p64_ship\.exe$)", {{ + { "dxgi.customVendorId", "10de" }, + }} }, + /* Titan Quest */ + { R"(\\TQ\.exe$)", {{ + { "d3d11.constantBufferRangeCheck", "True" }, + }} }, + /* Saints Row IV */ + { R"(\\SaintsRowIV\.exe$)", {{ + { "d3d11.constantBufferRangeCheck", "True" }, + }} }, + /* Saints Row: The Third */ + { R"(\\SaintsRowTheThird_DX11\.exe$)", {{ + { "d3d11.constantBufferRangeCheck", "True" }, + }} }, + /* Metal Gear Solid 5 */ + { R"(\\mgsvtpp\.exe$)", {{ + { "dxvk.enableOpenVR", "False" }, + }} }, + /* Raft */ + { R"(\\Raft\.exe$)", {{ + { "dxvk.enableOpenVR", "False" }, + }} }, + /* Crysis 3 - slower if it notices AMD card * + * Apitrace mode helps massively in cpu bound * + * game parts */ + { R"(\\Crysis3\.exe$)", {{ + { "dxgi.customVendorId", "10de" }, + { "d3d11.apitraceMode", "True" }, + }} }, + /* Crysis 3 Remastered * + * Apitrace mode helps massively in cpu bound * + * game parts */ + { R"(\\Crysis3Remastered\.exe$)", {{ + { "d3d11.apitraceMode", "True" }, + }} }, + /* Atelier series - games try to render video * + * with a D3D9 swap chain over the DXGI swap * + * chain, which breaks D3D11 presentation */ + { R"(\\Atelier_(Ayesha|Escha_and_Logy|Shallie)(_EN)?\.exe$)", {{ + { "d3d9.deferSurfaceCreation", "True" }, + }} }, + /* Atelier Rorona/Totori/Meruru */ + { R"(\\A(11R|12V|13V)_x64_Release(_en)?\.exe$)", {{ + { "d3d9.deferSurfaceCreation", "True" }, + }} }, + /* Just how many of these games are there? */ + { R"(\\Atelier_(Lulua|Lydie_and_Suelle|Ryza(_2)?)\.exe$)", {{ + { "d3d9.deferSurfaceCreation", "True" }, + }} }, + /* ... */ + { R"(\\Atelier_(Lydie_and_Suelle|Firis|Sophie)_DX\.exe$)", {{ + { "d3d9.deferSurfaceCreation", "True" }, + }} }, + /* Fairy Tail */ + { R"(\\FAIRY_TAIL\.exe$)", {{ + { "d3d9.deferSurfaceCreation", "True" }, + }} }, + /* Nights of Azure */ + { R"(\\CNN\.exe$)", {{ + { "d3d9.deferSurfaceCreation", "True" }, + }} }, + /* Star Wars Battlefront II: amdags issues */ + { R"(\\starwarsbattlefrontii\.exe$)", {{ + { "dxgi.customVendorId", "10de" }, + }} }, + /* F1 games - do not synchronize TGSM access * + * in a compute shader, causing artifacts */ + { R"(\\F1_20(1[89]|[2-9][0-9])\.exe$)", {{ + { "d3d11.forceTgsmBarriers", "True" }, + }} }, + /* Subnautica */ + { R"(\\Subnautica\.exe$)", {{ + { "dxvk.enableOpenVR", "False" }, + }} }, + /* Blue Reflection */ + { R"(\\BLUE_REFLECTION\.exe$)", {{ + { "d3d11.constantBufferRangeCheck", "True" }, + }} }, + /* Secret World Legends */ + { R"(\\SecretWorldLegendsDX11\.exe$)", {{ + { "d3d11.constantBufferRangeCheck", "True" }, + }} }, + /* Darksiders Warmastered - apparently reads * + * from write-only mapped buffers */ + { R"(\\darksiders1\.exe$)", {{ + { "d3d11.apitraceMode", "True" }, + }} }, + /* Monster Hunter World */ + { R"(\\MonsterHunterWorld\.exe$)", {{ + { "d3d11.apitraceMode", "True" }, + }} }, + /* Kingdome Come: Deliverance */ + { R"(\\KingdomCome\.exe$)", {{ + { "d3d11.apitraceMode", "True" }, + }} }, + /* Homefront: The Revolution */ + { R"(\\Homefront2_Release\.exe$)", {{ + { "d3d11.apitraceMode", "True" }, + }} }, + /* Sniper Ghost Warrior Contracts */ + { R"(\\SGWContracts\.exe$)", {{ + { "d3d11.apitraceMode", "True" }, + }} }, + /* Shadow of the Tomb Raider - invariant * + * position breaks character rendering on NV */ + { R"(\\SOTTR\.exe$)", {{ + { "d3d11.invariantPosition", "False" }, + { "d3d11.floatControls", "False" }, + }} }, + /* Nioh 2 */ + { R"(\\nioh2\.exe$)", {{ + { "dxgi.deferSurfaceCreation", "True" }, + }} }, + /* DIRT 5 - uses amd_ags_x64.dll when it * + * detects an AMD GPU */ + { R"(\\DIRT5\.exe$)", {{ + { "dxgi.customVendorId", "10de" }, + }} }, + /* Crazy Machines 3 - crashes on long device * + * descriptions */ + { R"(\\cm3\.exe$)", {{ + { "dxgi.customDeviceDesc", "DXVK Adapter" }, + }} }, + /* World of Final Fantasy: Broken and useless * + * use of 4x MSAA throughout the renderer */ + { R"(\\WOFF\.exe$)", {{ + { "d3d11.disableMsaa", "True" }, + }} }, + + /**********************************************/ + /* D3D9 GAMES */ + /**********************************************/ + + /* A Hat in Time */ + { R"(\\HatinTimeGame\.exe$)", {{ + { "d3d9.strictPow", "False" }, + { "d3d9.lenientClear", "True" }, + }} }, + /* Anarchy Online */ + { R"(\\anarchyonline\.exe$)", {{ + { "d3d9.memoryTrackTest", "True" }, + }} }, + /* Borderlands 2 and The Pre Sequel! */ + { R"(\\Borderlands(2|PreSequel)\.exe$)", {{ + { "d3d9.lenientClear", "True" }, + { "d3d9.supportDFFormats", "False" }, + }} }, + /* Borderlands */ + { R"(\\Borderlands\.exe$)", {{ + { "d3d9.lenientClear", "True" }, + }} }, + /* Gothic 3 */ + { R"(\\Gothic(3|3Final| III Forsaken Gods)\.exe$)", {{ + { "d3d9.supportDFFormats", "False" }, + }} }, + /* Risen */ + { R"(\\Risen[23]?\.exe$)", {{ + { "d3d9.invariantPosition", "True" }, + }} }, + /* Sonic Adventure 2 */ + { R"(\\Sonic Adventure 2\\(launcher|sonic2app)\.exe$)", {{ + { "d3d9.floatEmulation", "False" }, + }} }, + /* The Sims 2, + Body Shop, + The Sims Life Stories, + The Sims Pet Stories, + and The Sims Castaway Stories */ + { R"(\\(Sims2.*|TS2BodyShop|SimsLS|SimsPS|SimsCS)\.exe$)", {{ + { "d3d9.customVendorId", "10de" }, + { "d3d9.customDeviceId", "0091" }, + { "d3d9.customDeviceDesc", "GeForce 7800 GTX" }, + { "d3d9.disableA8RT", "True" }, + { "d3d9.supportX4R4G4B4", "False" }, + { "d3d9.maxAvailableMemory", "2048" }, + { "d3d9.memoryTrackTest", "True" }, + // The Sims 2 will try to upload 1024 constants + // every frame otherwise, which it never uses + // causing a massive discard + upload. + { "d3d9.swvpFloatCount", "384" }, + { "d3d9.swvpIntCount", "16" }, + { "d3d9.swvpBoolCount", "16" }, + }} }, + /* Dead Space uses the a NULL render target instead + of a 1x1 one if DF24 is NOT supported */ + { R"(\\Dead Space\.exe$)", {{ + { "d3d9.supportDFFormats", "False" }, + }} }, + /* Halo 2 */ + { R"(\\halo2\.exe$)", {{ + { "d3d9.invariantPosition", "True" }, + }} }, + /* Halo CE/HaloPC */ + { R"(\\halo(ce)?\.exe$)", {{ + { "d3d9.invariantPosition", "True" }, + // Game enables minor decal layering fixes + // specifically when it detects AMD. + // Avoids chip being detected as unsupported + // when on intel. Avoids possible path towards + // invalid texture addressing methods. + { "d3d9.customVendorId", "1002" }, + // Avoids card not recognized error. + // Keeps game's rendering methods consistent + // for optimal compatibility. + { "d3d9.customDeviceId", "4172" }, + // The game uses incorrect sampler types in + // the shaders for glass rendering which + // breaks it on native + us if we don't + // spec-constantly chose the sampler type + // automagically. + { "d3d9.forceSamplerTypeSpecConstants", "True" }, + }} }, + /* Counter Strike: Global Offensive + Needs NVAPI to avoid a forced AO + Smoke + exploit so we must force AMD vendor ID. */ + { R"(\\csgo\.exe$)", {{ + { "d3d9.customVendorId", "1002" }, + }} }, + /* Vampire - The Masquerade Bloodlines */ + { R"(\\vampire\.exe$)", {{ + { "d3d9.deferSurfaceCreation", "True" }, + { "d3d9.memoryTrackTest", "True" }, + { "d3d9.maxAvailableMemory", "1024" }, + }} }, + /* Senran Kagura Shinovi Versus */ + { R"(\\SKShinoviVersus\.exe$)", {{ + { "d3d9.forceAspectRatio", "16:9" }, + }} }, + /* Metal Slug X */ + { R"(\\mslugx\.exe$)", {{ + { "d3d9.supportD32", "False" }, + }} }, + /* Skyrim (NVAPI) */ + { R"(\\TESV\.exe$)", {{ + { "d3d9.customVendorId", "1002" }, + }} }, + /* RTHDRIBL Demo + Uses DONOTWAIT after GetRenderTargetData + then goes into an infinite loop if it gets + D3DERR_WASSTILLDRAWING. + This is a better solution than penalizing + other apps that use this properly. */ + { R"(\\rthdribl\.exe$)", {{ + { "d3d9.allowDoNotWait", "False" }, + }} }, + /* Hyperdimension Neptunia U: Action Unleashed */ + { R"(\\Neptunia\.exe$)", {{ + { "d3d9.forceAspectRatio", "16:9" }, + }} }, + /* D&D - The Temple Of Elemental Evil */ + { R"(\\ToEE\.exe$)", {{ + { "d3d9.allowDiscard", "False" }, + }} }, + /* ZUSI 3 - Aerosoft Edition */ + { R"(\\ZusiSim\.exe$)", {{ + { "d3d9.noExplicitFrontBuffer", "True" }, + }} }, + /* GTA IV (NVAPI) */ + /* Also thinks we're always on Intel * + * and will report/use bad amounts of VRAM. */ + { R"(\\GTAIV\.exe$)", {{ + { "d3d9.customVendorId", "1002" }, + { "dxgi.emulateUMA", "True" }, + }} }, + /* Battlefield 2 (bad z-pass) */ + { R"(\\BF2\.exe$)", {{ + { "d3d9.longMad", "True" }, + { "d3d9.invariantPosition", "True" }, + }} }, + /* SpellForce 2 Series */ + { R"(\\SpellForce2.*\.exe$)", {{ + { "d3d9.forceSamplerTypeSpecConstants", "True" }, + }} }, + /* Everquest 2 */ + { R"(\\EverQuest2.*\.exe$)", {{ + { "d3d9.alphaTestWiggleRoom", "True" }, + }} }, + /* Tomb Raider: Legend */ + { R"(\\trl\.exe$)", {{ + { "d3d9.apitraceMode", "True" }, + }} }, + /* Everquest */ + { R"(\\eqgame\.exe$)", {{ + { "d3d9.apitraceMode", "True" }, + }} }, + /* Dark Messiah of Might & Magic */ + { R"(\\mm\.exe$)", {{ + { "d3d9.deferSurfaceCreation", "True" }, + { "d3d9.memoryTrackTest", "True" }, + }} }, + /* TrackMania Forever */ + { R"(\\TmForever\.exe$)", {{ + { "d3d9.swvpFloatCount", "256" }, + { "d3d9.swvpIntCount", "16" }, + { "d3d9.swvpBoolCount", "16" }, + }} }, + /* Mafia 2 */ + { R"(\\mafia2\.exe$)", {{ + { "d3d9.customVendorId", "10de" }, + { "d3d9.customDeviceId", "0402" }, + }} }, + /* Warhammer: Online */ + { R"(\\WAR(-64)?\.exe$)", {{ + { "d3d9.customVendorId", "1002" }, + }} }, + /* Dragon Nest */ + { R"(\\DragonNest_x64\.exe$)", {{ + { "d3d9.memoryTrackTest ", "True" }, + }} }, + /* Dal Segno */ + { R"(\\DST\.exe$)", {{ + { "d3d9.deferSurfaceCreation", "True" }, + }} }, + /* Kohan II */ + { R"(\\k2\.exe$)", {{ + { "d3d9.memoryTrackTest", "True" }, + }} }, + /* Ninja Gaiden Sigma 1/2 */ + { R"(\\NINJA GAIDEN SIGMA(2)?\.exe$)", {{ + { "d3d9.deferSurfaceCreation", "True" }, + }} }, + /* Demon Stone breaks at frame rates > 60fps */ + { R"(\\Demonstone\.exe$)", {{ + { "d3d9.maxFrameRate", "60" }, + }} }, + /* Far Cry 1 has worse water rendering when it detects AMD GPUs */ + { R"(\\FarCry\.exe$)", {{ + { "d3d9.customVendorId", "10de" }, + }} }, + /* Earth Defense Force 5 */ + { R"(\\EDF5\.exe$)", {{ + { "dxgi.tearFree", "False" }, + { "dxgi.syncInterval", "1" }, + }} }, + /* Sine Mora EX */ + { R"(\\SineMoraEX\.exe$)", {{ + { "d3d9.maxFrameRate", "60" }, + }} }, + /* Fantasy Grounds */ + { R"(\\FantasyGrounds\.exe$)", {{ + { "d3d9.noExplicitFrontBuffer", "True" }, + }} }, + }}; + + + static bool isWhitespace(char ch) { + return ch == ' ' || ch == '\x9' || ch == '\r'; + } + + + static bool isValidKeyChar(char ch) { + return (ch >= '0' && ch <= '9') + || (ch >= 'A' && ch <= 'Z') + || (ch >= 'a' && ch <= 'z') + || (ch == '.' || ch == '_'); + } + + + static size_t skipWhitespace(const std::string& line, size_t n) { + while (n < line.size() && isWhitespace(line[n])) + n += 1; + return n; + } + + + struct ConfigContext { + bool active; + }; + + + static void parseUserConfigLine(Config& config, ConfigContext& ctx, const std::string& line) { + std::stringstream key; + std::stringstream value; + + // Extract the key + size_t n = skipWhitespace(line, 0); + + if (n < line.size() && line[n] == '[') { + n += 1; + + size_t e = line.size() - 1; + while (e > n && line[e] != ']') + e -= 1; + + while (n < e) + key << line[n++]; + + ctx.active = key.str() == env::getExeName(); + } else { + 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 + bool insideString = false; + n = skipWhitespace(line, n + 1); + + while (n < line.size()) { + if (!insideString && isWhitespace(line[n])) + break; + + if (line[n] == '"') { + insideString = !insideString; + n++; + } else + value << line[n++]; + } + + if (ctx.active) + config.setOption(key.str(), value.str()); + } + } + + + 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) { + static const std::array<std::pair<const char*, bool>, 2> s_lookup = {{ + { "true", true }, + { "false", false }, + }}; + + return parseStringOption(value, + s_lookup.begin(), s_lookup.end(), result); + } + + + 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; + } + + + bool Config::parseOptionValue( + const std::string& value, + Tristate& result) { + static const std::array<std::pair<const char*, Tristate>, 3> s_lookup = {{ + { "true", Tristate::True }, + { "false", Tristate::False }, + { "auto", Tristate::Auto }, + }}; + + return parseStringOption(value, + s_lookup.begin(), s_lookup.end(), result); + } + + + template<typename I, typename V> + bool Config::parseStringOption( + std::string str, + I begin, + I end, + V& value) { + std::transform(str.begin(), str.end(), str.begin(), + [] (unsigned char c) { return (c >= 'A' && c <= 'Z') ? (c + 'a' - 'A') : c; }); + + for (auto i = begin; i != end; i++) { + if (str == i->first) { + value = i->second; + return true; + } + } + + return false; + } + + + Config Config::getAppConfig(const std::string& appName) { + auto appConfig = std::find_if(g_appDefaults.begin(), g_appDefaults.end(), + [&appName] (const std::pair<const char*, Config>& pair) { + std::regex expr(pair.first, std::regex::extended | std::regex::icase); + return std::regex_search(appName, expr); + }); + + if (appConfig != g_appDefaults.end()) { + // Inform the user that we loaded a default config + Logger::info(str::format("Found built-in config:")); + return appConfig->second; + } + + return Config(); + } + + + Config Config::getUserConfig() { + Config config; + + // Load either $DXVK_CONFIG_FILE or $PWD/dxvk.conf + std::string filePath = env::getEnvVar("DXVK_CONFIG_FILE"); + + if (filePath == "") + filePath = "dxvk.conf"; + + // Open the file if it exists +#ifdef _WIN32 + std::ifstream stream(str::tows(filePath.c_str()).c_str()); +#else + std::ifstream stream(filePath.c_str()); +#endif + + if (!stream) + return config; + + // Inform the user that we loaded a file, might + // help when debugging configuration issues + Logger::info(str::format("Found config file: ", filePath)); + + // Initialize parser context + ConfigContext ctx; + ctx.active = true; + + // Parse the file line by line + std::string line; + + while (std::getline(stream, line)) + parseUserConfigLine(config, ctx, line); + + return config; + } + + + void Config::logOptions() const { + if (!m_options.empty()) { + Logger::info("Effective configuration:"); + + for (auto& pair : m_options) + Logger::info(str::format(" ", pair.first, " = ", pair.second)); + } + } + +} |