From dd5ba5c57b88a1000a1cd8f781e84d2a3b33f8f5 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 13 Jan 2022 00:20:16 +0100 Subject: [PATCH 1/8] Redirect log only after parsing configuration --- apps/opencs/editor.cpp | 2 + apps/opencs/main.cpp | 2 +- apps/openmw/main.cpp | 4 +- components/debug/debugging.cpp | 80 ++++++++++++++++++---------------- components/debug/debugging.hpp | 7 ++- 5 files changed, 54 insertions(+), 41 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 862d1c545f..7d5230f894 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -105,6 +106,7 @@ std::pair > CS::Editor::readConfi boost::program_options::notify(variables); mCfgMgr.readConfiguration(variables, desc, false); + setupLogging(mCfgMgr.getLogPath().string(), "OpenMW-CS"); Fallback::Map::init(variables["fallback"].as().mMap); diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index c7d57a256e..1e6e718983 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -79,5 +79,5 @@ int runApplication(int argc, char *argv[]) int main(int argc, char *argv[]) { - return wrapApplication(&runApplication, argc, argv, "OpenMW-CS"); + return wrapApplication(&runApplication, argc, argv, "OpenMW-CS", false); } diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index d5bed4ba25..6b92fa78f5 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -65,6 +65,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat cfgMgr.readConfiguration(variables, desc); Files::mergeComposingVariables(variables, composingVariables, desc); + setupLogging(cfgMgr.getLogPath().string(), "OpenMW"); + Version::Version v = Version::getOpenmwVersion(variables["resources"].as().string()); Log(Debug::Info) << v.describe(); @@ -230,7 +232,7 @@ extern "C" int SDL_main(int argc, char**argv) int main(int argc, char**argv) #endif { - return wrapApplication(&runApplication, argc, argv, "OpenMW"); + return wrapApplication(&runApplication, argc, argv, "OpenMW", false); } // Platform specific for Windows when there is no console built into the executable. diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index 0e372942f2..c84bb39d3d 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -137,61 +137,65 @@ namespace Debug } static std::unique_ptr rawStdout = nullptr; +static std::unique_ptr rawStderr = nullptr; +static boost::filesystem::ofstream logfile; + +#if defined(_WIN32) && defined(_DEBUG) +static boost::iostreams::stream_buffer sb; +#else +static boost::iostreams::stream_buffer coutsb; +static boost::iostreams::stream_buffer cerrsb; +#endif std::ostream& getRawStdout() { return rawStdout ? *rawStdout : std::cout; } -int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName) +// Redirect cout and cerr to the log file +void setupLogging(const std::string& logDir, const std::string& appName, std::ios_base::openmode mode) +{ +#if defined(_WIN32) && defined(_DEBUG) + // Redirect cout and cerr to VS debug output when running in debug mode + sb.open(Debug::DebugOutput()); + std::cout.rdbuf(&sb); + std::cerr.rdbuf(&sb); +#else + const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log"; + logfile.open(boost::filesystem::path(logDir) / logName, mode); + + coutsb.open(Debug::Tee(logfile, *rawStdout)); + cerrsb.open(Debug::Tee(logfile, *rawStderr)); + + std::cout.rdbuf(&coutsb); + std::cerr.rdbuf(&cerrsb); +#endif +} + +int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], + const std::string& appName, bool autoSetupLogging) { #if defined _WIN32 (void)Debug::attachParentConsole(); #endif rawStdout = std::make_unique(std::cout.rdbuf()); - - // Some objects used to redirect cout and cerr - // Scope must be here, so this still works inside the catch block for logging exceptions - std::streambuf* cout_rdbuf = std::cout.rdbuf (); - std::streambuf* cerr_rdbuf = std::cerr.rdbuf (); - -#if defined(_WIN32) && defined(_DEBUG) - boost::iostreams::stream_buffer sb; -#else - boost::iostreams::stream_buffer coutsb; - boost::iostreams::stream_buffer cerrsb; - std::ostream oldcout(cout_rdbuf); - std::ostream oldcerr(cerr_rdbuf); -#endif - - const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log"; - boost::filesystem::ofstream logfile; + rawStderr = std::make_unique(std::cerr.rdbuf()); int ret = 0; try { Files::ConfigurationManager cfgMgr; -#if defined(_WIN32) && defined(_DEBUG) - // Redirect cout and cerr to VS debug output when running in debug mode - sb.open(Debug::DebugOutput()); - std::cout.rdbuf (&sb); - std::cerr.rdbuf (&sb); -#else - // Redirect cout and cerr to the log file - // If we are collecting a stack trace, append to existing log file - std::ios_base::openmode mode = std::ios::out; - if(argc == 2 && strcmp(argv[1], crash_switch) == 0) - mode |= std::ios::app; + if (autoSetupLogging) + { + std::ios_base::openmode mode = std::ios::out; - logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / logName), mode); + // If we are collecting a stack trace, append to existing log file + if (argc == 2 && strcmp(argv[1], crash_switch) == 0) + mode |= std::ios::app; - coutsb.open (Debug::Tee(logfile, oldcout)); - cerrsb.open (Debug::Tee(logfile, oldcerr)); - - std::cout.rdbuf (&coutsb); - std::cerr.rdbuf (&cerrsb); -#endif + setupLogging(cfgMgr.getLogPath().string(), appName, mode); + } #if defined(_WIN32) const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.dmp"; @@ -217,8 +221,8 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c } // Restore cout and cerr - std::cout.rdbuf(cout_rdbuf); - std::cerr.rdbuf(cerr_rdbuf); + std::cout.rdbuf(rawStdout->rdbuf()); + std::cerr.rdbuf(rawStderr->rdbuf()); Debug::CurrentDebugLevel = Debug::NoLevel; return ret; diff --git a/components/debug/debugging.hpp b/components/debug/debugging.hpp index d8849cd896..a2c5ae9e6c 100644 --- a/components/debug/debugging.hpp +++ b/components/debug/debugging.hpp @@ -133,11 +133,16 @@ namespace Debug std::map mColors; }; #endif + + } // Can be used to print messages without timestamps std::ostream& getRawStdout(); -int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName); +void setupLogging(const std::string& logDir, const std::string& appName, std::ios_base::openmode = std::ios::out); + +int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], + const std::string& appName, bool autoSetupLogging = true); #endif From 5ca56a4f8ad12fa0f67fa8cb468540b0034717b9 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 13 Jan 2022 23:10:09 +0100 Subject: [PATCH 2/8] New option "config" for specifying additional config directories. --- apps/essimporter/main.cpp | 1 + apps/navmeshtool/main.cpp | 1 + apps/opencs/editor.cpp | 1 + apps/openmw/main.cpp | 3 +- apps/openmw_test_suite/mwworld/test_store.cpp | 1 + components/files/configurationmanager.cpp | 129 ++++++++++++------ components/files/configurationmanager.hpp | 18 ++- files/openmw.cfg | 1 + files/openmw.cfg.local | 1 + 9 files changed, 113 insertions(+), 43 deletions(-) diff --git a/apps/essimporter/main.cpp b/apps/essimporter/main.cpp index e81368c607..bc5d34bc03 100644 --- a/apps/essimporter/main.cpp +++ b/apps/essimporter/main.cpp @@ -25,6 +25,7 @@ int main(int argc, char** argv) ("encoding", boost::program_options::value()->default_value("win1252"), "encoding of the save file") ; p_desc.add("mwsave", 1).add("output", 1); + Files::ConfigurationManager::addCommonOptions(desc); bpo::variables_map variables; diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 8e46170e63..ce3cb10b20 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -84,6 +84,7 @@ namespace NavMeshTool ("process-interior-cells", bpo::value()->implicit_value(true) ->default_value(false), "build navmesh for interior cells") ; + Files::ConfigurationManager::addCommonOptions(result); return result; } diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 7d5230f894..3db201874a 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -102,6 +102,7 @@ std::pair > CS::Editor::readConfi ->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)") ("script-blacklist-use", boost::program_options::value()->implicit_value(true) ->default_value(true), "enable script blacklisting"); + Files::ConfigurationManager::addCommonOptions(desc); boost::program_options::notify(variables); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 6b92fa78f5..456fe23b60 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -40,6 +40,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat typedef std::vector StringsVector; bpo::options_description desc = OpenMW::makeOptionsDescription(); + Files::ConfigurationManager::addCommonOptions(desc); bpo::variables_map variables; @@ -61,9 +62,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat return false; } - bpo::variables_map composingVariables = Files::separateComposingVariables(variables, desc); cfgMgr.readConfiguration(variables, desc); - Files::mergeComposingVariables(variables, composingVariables, desc); setupLogging(cfgMgr.getLogPath().string(), "OpenMW"); diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index 10003cfdfd..bf1a40f7d9 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -58,6 +58,7 @@ struct ContentFileTest : public ::testing::Test ("content", boost::program_options::value>()->default_value(std::vector(), "") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") ("data-local", boost::program_options::value()->default_value(Files::MaybeQuotedPathContainer::value_type(), "")); + Files::ConfigurationManager::addCommonOptions(desc); boost::program_options::notify(variables); diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index c2cd44960f..739c7f7b43 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -20,6 +20,7 @@ static const char* const applicationName = "openmw"; #endif const char* const localToken = "?local?"; +const char* const userConfigToken = "?userconfig?"; const char* const userDataToken = "?userdata?"; const char* const globalToken = "?global?"; @@ -50,6 +51,7 @@ ConfigurationManager::~ConfigurationManager() void ConfigurationManager::setupTokensMapping() { mTokensMapping.insert(std::make_pair(localToken, &FixedPath<>::getLocalPath)); + mTokensMapping.insert(std::make_pair(userConfigToken, &FixedPath<>::getUserConfigPath)); mTokensMapping.insert(std::make_pair(userDataToken, &FixedPath<>::getUserDataPath)); mTokensMapping.insert(std::make_pair(globalToken, &FixedPath<>::getGlobalDataPath)); } @@ -57,31 +59,92 @@ void ConfigurationManager::setupTokensMapping() void ConfigurationManager::readConfiguration(boost::program_options::variables_map& variables, boost::program_options::options_description& description, bool quiet) { + using ParsedConfigFile = bpo::basic_parsed_options; bool silent = mSilent; mSilent = quiet; - - // User config has the highest priority. - auto composingVariables = separateComposingVariables(variables, description); - loadConfig(mFixedPath.getUserConfigPath(), variables, description); - mergeComposingVariables(variables, composingVariables, description); - boost::program_options::notify(variables); - // read either local or global config depending on type of installation - composingVariables = separateComposingVariables(variables, description); - bool loaded = loadConfig(mFixedPath.getLocalPath(), variables, description); - mergeComposingVariables(variables, composingVariables, description); - boost::program_options::notify(variables); - if (!loaded) + std::optional config = loadConfig(mFixedPath.getLocalPath(), description); + if (config) + mActiveConfigPaths.push_back(mFixedPath.getLocalPath()); + else { - composingVariables = separateComposingVariables(variables, description); - loadConfig(mFixedPath.getGlobalConfigPath(), variables, description); - mergeComposingVariables(variables, composingVariables, description); - boost::program_options::notify(variables); + mActiveConfigPaths.push_back(mFixedPath.getGlobalConfigPath()); + config = loadConfig(mFixedPath.getGlobalConfigPath(), description); + } + if (!config) + { + if (!quiet) + Log(Debug::Error) << "Neither local config nor global config are available."; + mSilent = silent; + return; } + std::stack extraConfigDirs; + addExtraConfigDirs(extraConfigDirs, variables); + addExtraConfigDirs(extraConfigDirs, *config); + + std::vector parsedOptions{*std::move(config)}; + std::set alreadyParsedPaths; // needed to prevent infinite loop in case of a circular link + alreadyParsedPaths.insert(boost::filesystem::path(mActiveConfigPaths.front())); + + while (!extraConfigDirs.empty()) + { + boost::filesystem::path path = extraConfigDirs.top(); + extraConfigDirs.pop(); + if (alreadyParsedPaths.count(path) > 0) + { + if (!quiet) + Log(Debug::Warning) << "Repeated config dir: " << path; + continue; + } + alreadyParsedPaths.insert(path); + mActiveConfigPaths.push_back(path); + config = loadConfig(path, description); + if (!config) + continue; + addExtraConfigDirs(extraConfigDirs, *config); + parsedOptions.push_back(*std::move(config)); + } + + for (auto it = parsedOptions.rbegin(); it != parsedOptions.rend(); ++it) + { + auto composingVariables = separateComposingVariables(variables, description); + boost::program_options::store(std::move(*it), variables); + mergeComposingVariables(variables, composingVariables, description); + } + + mLogPath = mActiveConfigPaths.back(); mSilent = silent; } +void ConfigurationManager::addExtraConfigDirs(std::stack& dirs, + const bpo::basic_parsed_options& options) const +{ + boost::program_options::variables_map variables; + boost::program_options::store(options, variables); + boost::program_options::notify(variables); + addExtraConfigDirs(dirs, variables); +} + +void ConfigurationManager::addExtraConfigDirs(std::stack& dirs, + const boost::program_options::variables_map& variables) const +{ + auto configIt = variables.find("config"); + if (configIt == variables.end()) + return; + Files::PathContainer newDirs = asPathContainer(configIt->second.as()); + processPaths(newDirs); + for (auto it = newDirs.rbegin(); it != newDirs.rend(); ++it) + dirs.push(*it); +} + +void ConfigurationManager::addCommonOptions(boost::program_options::options_description& description) +{ + description.add_options() + ("config", bpo::value()->default_value(Files::MaybeQuotedPathContainer(), "config") + ->multitoken()->composing(), "additional config directories"); +} + boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map & variables, boost::program_options::options_description& description) { @@ -107,7 +170,7 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost if (description.find_nothrow("replace", false)) { auto replace = second["replace"]; - if (!replace.defaulted() && !replace.empty()) + if (!replace.empty()) { std::vector replaceVector = replace.as>(); replacedVariables.insert(replaceVector.begin(), replaceVector.end()); @@ -132,7 +195,7 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost continue; } - if (second[name].defaulted() || second[name].empty()) + if (second[name].empty()) continue; boost::any& firstValue = firstPosition->second.value(); @@ -142,7 +205,6 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost { auto& firstPathContainer = boost::any_cast(firstValue); const auto& secondPathContainer = boost::any_cast(secondValue); - firstPathContainer.insert(firstPathContainer.end(), secondPathContainer.begin(), secondPathContainer.end()); } else if (firstValue.type() == typeid(std::vector)) @@ -165,10 +227,10 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost Log(Debug::Error) << "Unexpected composing variable type. Curse boost and their blasted arcane templates."; } } - + boost::program_options::notify(first); } -void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool create) +void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool create) const { std::string path; for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) @@ -181,7 +243,7 @@ void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool cre std::string::size_type pos = path.find('?', 1); if (pos != std::string::npos && pos != 0) { - TokensMappingContainer::iterator tokenIt = mTokensMapping.find(path.substr(0, pos + 1)); + auto tokenIt = mTokensMapping.find(path.substr(0, pos + 1)); if (tokenIt != mTokensMapping.end()) { boost::filesystem::path tempPath(((mFixedPath).*(tokenIt->second))()); @@ -224,8 +286,8 @@ void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool cre std::bind(&boost::filesystem::path::empty, std::placeholders::_1)), dataDirs.end()); } -bool ConfigurationManager::loadConfig(const boost::filesystem::path& path, - boost::program_options::variables_map& variables, +std::optional> ConfigurationManager::loadConfig( + const boost::filesystem::path& path, boost::program_options::options_description& description) { boost::filesystem::path cfgFile(path); @@ -238,20 +300,11 @@ bool ConfigurationManager::loadConfig(const boost::filesystem::path& path, boost::filesystem::ifstream configFileStream(cfgFile); if (configFileStream.is_open()) - { - parseConfig(configFileStream, variables, description); - - return true; - } - else - { - if (!mSilent) - Log(Debug::Error) << "Loading failed."; - - return false; - } + return Files::parse_config_file(configFileStream, description, true); + else if (!mSilent) + Log(Debug::Error) << "Loading failed."; } - return false; + return std::nullopt; } const boost::filesystem::path& ConfigurationManager::getGlobalPath() const @@ -344,4 +397,4 @@ PathContainer asPathContainer(const MaybeQuotedPathContainer& MaybeQuotedPathCon return PathContainer(MaybeQuotedPathContainer.begin(), MaybeQuotedPathContainer.end()); } -} /* namespace Cfg */ +} /* namespace Files */ diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 4b641c12fd..07a9300305 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -2,6 +2,8 @@ #define COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP #include +#include +#include #include @@ -25,7 +27,7 @@ struct ConfigurationManager void readConfiguration(boost::program_options::variables_map& variables, boost::program_options::options_description& description, bool quiet=false); - void processPaths(Files::PathContainer& dataDirs, bool create = false); + void processPaths(Files::PathContainer& dataDirs, bool create = false) const; ///< \param create Try creating the directory, if it does not exist. /**< Fixed paths */ @@ -37,24 +39,34 @@ struct ConfigurationManager const boost::filesystem::path& getUserDataPath() const; const boost::filesystem::path& getLocalDataPath() const; const boost::filesystem::path& getInstallPath() const; + const std::vector& getActiveConfigPaths() const { return mActiveConfigPaths; } const boost::filesystem::path& getCachePath() const; const boost::filesystem::path& getLogPath() const; const boost::filesystem::path& getScreenshotPath() const; + static void addCommonOptions(boost::program_options::options_description& description); + private: typedef Files::FixedPath<> FixedPathType; typedef const boost::filesystem::path& (FixedPathType::*path_type_f)() const; typedef std::map TokensMappingContainer; - bool loadConfig(const boost::filesystem::path& path, - boost::program_options::variables_map& variables, + std::optional> loadConfig( + const boost::filesystem::path& path, boost::program_options::options_description& description); + void addExtraConfigDirs(std::stack& dirs, + const boost::program_options::variables_map& variables) const; + void addExtraConfigDirs(std::stack& dirs, + const boost::program_options::basic_parsed_options& options) const; + void setupTokensMapping(); + std::vector mActiveConfigPaths; + FixedPathType mFixedPath; boost::filesystem::path mLogPath; diff --git a/files/openmw.cfg b/files/openmw.cfg index f524911489..7bd70c3e5e 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -4,6 +4,7 @@ content=builtin.omwscripts data-local="?userdata?data" +config="?userconfig?" resources=${OPENMW_RESOURCE_FILES} script-blacklist=Museum script-blacklist=MockChangeScript diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index bed9b9b10a..79ab9e6156 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -4,6 +4,7 @@ content=builtin.omwscripts data-local="?userdata?data" +config="?userconfig?" resources=./resources script-blacklist=Museum script-blacklist=MockChangeScript From 1bcc4a8bcc6cfbf3570a276c5647bcd294773a7c Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 14 Jan 2022 00:54:54 +0100 Subject: [PATCH 3/8] Read settings.cfg from all active config dirs --- CHANGELOG.md | 1 + apps/openmw/engine.cpp | 28 +++++++++++++++++----------- components/settings/parser.cpp | 7 +++++-- components/settings/parser.hpp | 3 ++- components/settings/settings.cpp | 4 ++-- components/settings/settings.hpp | 2 +- 6 files changed, 28 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56d0910d22..a181a35098 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ Bug #6579: OpenMW compilation error when using OSG doubles for BoundingSphere Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions + Feature #2491: Ability to make OpenMW "portable" Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 9e2e1aedad..50053b8b76 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -520,20 +520,26 @@ void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) std::string OMW::Engine::loadSettings (Settings::Manager & settings) { - // Create the settings manager and load default settings file - const std::string localdefault = (mCfgMgr.getLocalPath() / "defaults.bin").string(); - const std::string globaldefault = (mCfgMgr.getGlobalPath() / "defaults.bin").string(); + const std::vector& paths = mCfgMgr.getActiveConfigPaths(); + if (paths.empty()) + throw std::runtime_error("No config dirs! ConfigurationManager::readConfiguration must be called first."); - // prefer local - if (boost::filesystem::exists(localdefault)) - settings.loadDefault(localdefault); - else if (boost::filesystem::exists(globaldefault)) - settings.loadDefault(globaldefault); - else + // Create the settings manager and load default settings file. + const std::string defaultsBin = (paths.front() / "defaults.bin").string(); + if (!boost::filesystem::exists(defaultsBin)) throw std::runtime_error ("No default settings file found! Make sure the file \"defaults.bin\" was properly installed."); + settings.loadDefault(defaultsBin); - // load user settings if they exist - std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); + // Load "settings.cfg" from every config dir except the last one as additional default settings. + for (int i = 0; i < static_cast(paths.size()) - 1; ++i) + { + const std::string additionalDefaults = (paths[i] / "settings.cfg").string(); + if (boost::filesystem::exists(additionalDefaults)) + settings.loadDefault(additionalDefaults, true); + } + + // Load "settings.cfg" from the last config as user settings if they exist. This path will be used to save modified settings. + std::string settingspath = (paths.back() / "settings.cfg").string(); if (boost::filesystem::exists(settingspath)) settings.loadUser(settingspath); diff --git a/components/settings/parser.cpp b/components/settings/parser.cpp index f2419dfdd6..dd1b8c1a20 100644 --- a/components/settings/parser.cpp +++ b/components/settings/parser.cpp @@ -9,7 +9,8 @@ #include -void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, CategorySettingValueMap& settings, bool base64Encoded) +void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, CategorySettingValueMap& settings, + bool base64Encoded, bool overrideExisting) { mFile = file; boost::filesystem::ifstream fstream; @@ -73,7 +74,9 @@ void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, Cat std::string value = line.substr(valueBegin); Misc::StringUtils::trim(value); - if (settings.insert(std::make_pair(std::make_pair(currentCategory, setting), value)).second == false) + if (overrideExisting) + settings[std::make_pair(currentCategory, setting)] = value; + else if (settings.insert(std::make_pair(std::make_pair(currentCategory, setting), value)).second == false) fail(std::string("duplicate setting: [" + currentCategory + "] " + setting)); } } diff --git a/components/settings/parser.hpp b/components/settings/parser.hpp index 45b1a18f72..c934fbea07 100644 --- a/components/settings/parser.hpp +++ b/components/settings/parser.hpp @@ -10,7 +10,8 @@ namespace Settings class SettingsFileParser { public: - void loadSettingsFile(const std::string& file, CategorySettingValueMap& settings, bool base64encoded = false); + void loadSettingsFile(const std::string& file, CategorySettingValueMap& settings, + bool base64encoded = false, bool overrideExisting = false); void saveSettingsFile(const std::string& file, const CategorySettingValueMap& settings); diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 7fa625e4ab..09e74b348b 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -19,10 +19,10 @@ void Manager::clear() mChangedSettings.clear(); } -void Manager::loadDefault(const std::string &file) +void Manager::loadDefault(const std::string &file, bool overrideExisting) { SettingsFileParser parser; - parser.loadSettingsFile(file, mDefaultSettings, true); + parser.loadSettingsFile(file, mDefaultSettings, true, overrideExisting); } void Manager::loadUser(const std::string &file) diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index a4b1cf3a54..84e7066fbd 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -26,7 +26,7 @@ namespace Settings void clear(); ///< clears all settings and default settings - void loadDefault (const std::string& file); + void loadDefault (const std::string& file, bool overrideExisting = false); ///< load file as the default settings (can be overridden by user settings) void loadUser (const std::string& file); From a453e5c19884e716a66abb10175fd351dc243d36 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 16 Jan 2022 01:59:20 +0100 Subject: [PATCH 4/8] Reuse the same code to load settings in apps/openmw, apps/launcher, apps/opencs --- apps/launcher/maindialog.cpp | 64 ++++++++----------------------- apps/navmeshtool/main.cpp | 19 +-------- apps/opencs/editor.cpp | 11 +++++- apps/opencs/editor.hpp | 3 ++ apps/opencs/model/prefs/state.cpp | 17 +------- apps/openmw/engine.cpp | 31 +-------------- components/settings/settings.cpp | 32 ++++++++++++---- components/settings/settings.hpp | 12 +++--- 8 files changed, 62 insertions(+), 127 deletions(-) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index d335d7cded..167f6b9c26 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -414,57 +414,23 @@ bool Launcher::MainDialog::setupGameData() bool Launcher::MainDialog::setupGraphicsSettings() { - // This method is almost a copy of OMW::Engine::loadSettings(). They should definitely - // remain consistent, and possibly be merged into a shared component. At the very least - // the filenames should be in the CfgMgr component. - - // Ensure to clear previous settings in case we had already loaded settings. - mEngineSettings.clear(); - - // Create the settings manager and load default settings file - const std::string localDefault = (mCfgMgr.getLocalPath() / "defaults.bin").string(); - const std::string globalDefault = (mCfgMgr.getGlobalPath() / "defaults.bin").string(); - std::string defaultPath; - - // Prefer the defaults.bin in the current directory. - if (boost::filesystem::exists(localDefault)) - defaultPath = localDefault; - else if (boost::filesystem::exists(globalDefault)) - defaultPath = globalDefault; - // Something's very wrong if we can't find the file at all. - else { - cfgError(tr("Error reading OpenMW configuration file"), - tr("
Could not find defaults.bin

\ - The problem may be due to an incomplete installation of OpenMW.
\ - Reinstalling OpenMW may resolve the problem.")); + mEngineSettings.clear(); // Ensure to clear previous settings in case we had already loaded settings. + try + { + boost::program_options::variables_map variables; + boost::program_options::options_description desc; + mCfgMgr.addCommonOptions(desc); + mCfgMgr.readConfiguration(variables, desc, true); + mEngineSettings.load(mCfgMgr); + return true; + } + catch (std::exception& e) + { + cfgError(tr("Error reading OpenMW configuration files"), + tr("
The problem may be due to an incomplete installation of OpenMW.
\ + Reinstalling OpenMW may resolve the problem.
") + e.what()); return false; } - - // Load the default settings, report any parsing errors. - try { - mEngineSettings.loadDefault(defaultPath); - } - catch (std::exception& e) { - std::string msg = std::string("
Error reading defaults.bin

") + e.what(); - cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str())); - return false; - } - - // Load user settings if they exist - const std::string userPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); - // User settings are not required to exist, so if they don't we're done. - if (!boost::filesystem::exists(userPath)) return true; - - try { - mEngineSettings.loadUser(userPath); - } - catch (std::exception& e) { - std::string msg = std::string("
Error reading settings.cfg

") + e.what(); - cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str())); - return false; - } - - return true; } void Launcher::MainDialog::loadSettings() diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index ce3cb10b20..f89e80e542 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -89,23 +89,6 @@ namespace NavMeshTool return result; } - void loadSettings(const Files::ConfigurationManager& config, Settings::Manager& settings) - { - const std::string localDefault = (config.getLocalPath() / "defaults.bin").string(); - const std::string globalDefault = (config.getGlobalPath() / "defaults.bin").string(); - - if (boost::filesystem::exists(localDefault)) - settings.loadDefault(localDefault); - else if (boost::filesystem::exists(globalDefault)) - settings.loadDefault(globalDefault); - else - throw std::runtime_error("No default settings file found! Make sure the file \"defaults.bin\" was properly installed."); - - const std::string settingsPath = (config.getUserConfigPath() / "settings.cfg").string(); - if (boost::filesystem::exists(settingsPath)) - settings.loadUser(settingsPath); - } - int runNavMeshTool(int argc, char *argv[]) { bpo::options_description desc = makeOptionsDescription(); @@ -166,7 +149,7 @@ namespace NavMeshTool VFS::registerArchives(&vfs, fileCollections, archives, true); Settings::Manager settings; - loadSettings(config, settings); + settings.load(config); const osg::Vec3f agentHalfExtents = Settings::Manager::getVector3("default actor pathfind half extents", "Game"); diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 3db201874a..1d5934fe5d 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -21,7 +21,7 @@ using namespace Fallback; CS::Editor::Editor (int argc, char **argv) -: mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr), +: mConfigVariables(readConfiguration()), mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr), mPid(""), mLock(), mMerge (mDocumentManager), mIpcServerName ("org.openmw.OpenCS"), mServer(nullptr), mClientSocket(nullptr) { @@ -83,7 +83,7 @@ CS::Editor::~Editor () remove(mPid.string().c_str())); // ignore any error } -std::pair > CS::Editor::readConfig(bool quiet) +boost::program_options::variables_map CS::Editor::readConfiguration() { boost::program_options::variables_map variables; boost::program_options::options_description desc("Syntax: openmw-cs \nAllowed options"); @@ -109,6 +109,13 @@ std::pair > CS::Editor::readConfi mCfgMgr.readConfiguration(variables, desc, false); setupLogging(mCfgMgr.getLogPath().string(), "OpenMW-CS"); + return variables; +} + +std::pair > CS::Editor::readConfig(bool quiet) +{ + boost::program_options::variables_map& variables = mConfigVariables; + Fallback::Map::init(variables["fallback"].as().mMap); mEncodingName = variables["encoding"].as(); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 1c93427613..7b258c8049 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -40,6 +40,7 @@ namespace CS Q_OBJECT Files::ConfigurationManager mCfgMgr; + boost::program_options::variables_map mConfigVariables; CSMPrefs::State mSettingsState; CSMDoc::DocumentManager mDocumentManager; CSVDoc::StartupDialogue mStartup; @@ -58,6 +59,8 @@ namespace CS Files::PathContainer mDataDirs; std::string mEncodingName; + boost::program_options::variables_map readConfiguration(); + ///< Calls mCfgMgr.readConfiguration; should be used before initialization of mSettingsState as it depends on the configuration. std::pair > readConfig(bool quiet=false); ///< \return data paths diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 06dc1f4a2e..ee0cbbed5a 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -16,22 +16,7 @@ CSMPrefs::State *CSMPrefs::State::sThis = nullptr; void CSMPrefs::State::load() { - // default settings file - boost::filesystem::path local = mConfigurationManager.getLocalPath() / mDefaultConfigFile; - boost::filesystem::path global = mConfigurationManager.getGlobalPath() / mDefaultConfigFile; - - if (boost::filesystem::exists (local)) - mSettings.loadDefault (local.string()); - else if (boost::filesystem::exists (global)) - mSettings.loadDefault (global.string()); - else - throw std::runtime_error ("No default settings file found! Make sure the file \"" + mDefaultConfigFile + "\" was properly installed."); - - // user settings file - boost::filesystem::path user = mConfigurationManager.getUserConfigPath() / mConfigFile; - - if (boost::filesystem::exists (user)) - mSettings.loadUser (user.string()); + mSettings.load(mConfigurationManager); } void CSMPrefs::State::declare() diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 50053b8b76..cd32cb3b0d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -518,34 +518,6 @@ void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) mNewGame = newGame; } -std::string OMW::Engine::loadSettings (Settings::Manager & settings) -{ - const std::vector& paths = mCfgMgr.getActiveConfigPaths(); - if (paths.empty()) - throw std::runtime_error("No config dirs! ConfigurationManager::readConfiguration must be called first."); - - // Create the settings manager and load default settings file. - const std::string defaultsBin = (paths.front() / "defaults.bin").string(); - if (!boost::filesystem::exists(defaultsBin)) - throw std::runtime_error ("No default settings file found! Make sure the file \"defaults.bin\" was properly installed."); - settings.loadDefault(defaultsBin); - - // Load "settings.cfg" from every config dir except the last one as additional default settings. - for (int i = 0; i < static_cast(paths.size()) - 1; ++i) - { - const std::string additionalDefaults = (paths[i] / "settings.cfg").string(); - if (boost::filesystem::exists(additionalDefaults)) - settings.loadDefault(additionalDefaults, true); - } - - // Load "settings.cfg" from the last config as user settings if they exist. This path will be used to save modified settings. - std::string settingspath = (paths.back() / "settings.cfg").string(); - if (boost::filesystem::exists(settingspath)) - settings.loadUser(settingspath); - - return settingspath; -} - void OMW::Engine::createWindow(Settings::Manager& settings) { int screen = settings.getInt("screen", "Video"); @@ -981,8 +953,7 @@ void OMW::Engine::go() // Load settings Settings::Manager settings; - std::string settingspath; - settingspath = loadSettings (settings); + std::string settingspath = settings.load(mCfgMgr); MWClass::registerClasses(); diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 09e74b348b..b8ff700492 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -3,6 +3,7 @@ #include +#include #include namespace Settings @@ -19,16 +20,33 @@ void Manager::clear() mChangedSettings.clear(); } -void Manager::loadDefault(const std::string &file, bool overrideExisting) +std::string Manager::load(const Files::ConfigurationManager& cfgMgr) { SettingsFileParser parser; - parser.loadSettingsFile(file, mDefaultSettings, true, overrideExisting); -} + const std::vector& paths = cfgMgr.getActiveConfigPaths(); + if (paths.empty()) + throw std::runtime_error("No config dirs! ConfigurationManager::readConfiguration must be called first."); -void Manager::loadUser(const std::string &file) -{ - SettingsFileParser parser; - parser.loadSettingsFile(file, mUserSettings); + // Create the settings manager and load default settings file. + const std::string defaultsBin = (paths.front() / "defaults.bin").string(); + if (!boost::filesystem::exists(defaultsBin)) + throw std::runtime_error ("No default settings file found! Make sure the file \"defaults.bin\" was properly installed."); + parser.loadSettingsFile(defaultsBin, mDefaultSettings, true, false); + + // Load "settings.cfg" from every config dir except the last one as additional default settings. + for (int i = 0; i < static_cast(paths.size()) - 1; ++i) + { + const std::string additionalDefaults = (paths[i] / "settings.cfg").string(); + if (boost::filesystem::exists(additionalDefaults)) + parser.loadSettingsFile(additionalDefaults, mDefaultSettings, false, true); + } + + // Load "settings.cfg" from the last config as user settings if they exist. This path will be used to save modified settings. + std::string settingspath = (paths.back() / "settings.cfg").string(); + if (boost::filesystem::exists(settingspath)) + parser.loadSettingsFile(settingspath, mUserSettings, false, false); + + return settingspath; } void Manager::saveUser(const std::string &file) diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index 84e7066fbd..04831ef171 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -9,6 +9,11 @@ #include #include +namespace Files +{ + struct ConfigurationManager; +} + namespace Settings { /// @@ -26,11 +31,8 @@ namespace Settings void clear(); ///< clears all settings and default settings - void loadDefault (const std::string& file, bool overrideExisting = false); - ///< load file as the default settings (can be overridden by user settings) - - void loadUser (const std::string& file); - ///< load file as user settings + std::string load(const Files::ConfigurationManager& cfgMgr); + ///< load settings from all active config dirs. Returns the path of the last loaded file. void saveUser (const std::string& file); ///< save user settings to file From 5ff2fc55ac6976750c9f096ea84085a03782ac4e Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 16 Jan 2022 02:10:13 +0100 Subject: [PATCH 5/8] Make ConfigurationManager::getUserConfigPath to return the actual config path where the settings, logs, and Lua storage are stored. --- components/files/configurationmanager.cpp | 5 ++++- components/files/configurationmanager.hpp | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 739c7f7b43..b814da18ff 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -314,7 +314,10 @@ const boost::filesystem::path& ConfigurationManager::getGlobalPath() const const boost::filesystem::path& ConfigurationManager::getUserConfigPath() const { - return mFixedPath.getUserConfigPath(); + if (mActiveConfigPaths.empty()) + return mFixedPath.getUserConfigPath(); + else + return mActiveConfigPaths.back(); } const boost::filesystem::path& ConfigurationManager::getUserDataPath() const diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 07a9300305..fcbb0d5ff5 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -32,10 +32,10 @@ struct ConfigurationManager /**< Fixed paths */ const boost::filesystem::path& getGlobalPath() const; - const boost::filesystem::path& getUserConfigPath() const; const boost::filesystem::path& getLocalPath() const; const boost::filesystem::path& getGlobalDataPath() const; + const boost::filesystem::path& getUserConfigPath() const; const boost::filesystem::path& getUserDataPath() const; const boost::filesystem::path& getLocalDataPath() const; const boost::filesystem::path& getInstallPath() const; From 9c1ff16b6202b00d3df706e0fb4965ee0730827f Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 20 Jan 2022 18:20:28 +0100 Subject: [PATCH 6/8] Add config variable `user-data="?userdata?"`. Allows to override paths to saves and screenshots. --- components/files/configurationmanager.cpp | 130 +++++++++++++--------- components/files/configurationmanager.hpp | 2 + files/openmw.cfg | 1 + files/openmw.cfg.local | 1 + 4 files changed, 79 insertions(+), 55 deletions(-) diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index b814da18ff..4101c4ef54 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -30,18 +30,10 @@ ConfigurationManager::ConfigurationManager(bool silent) { setupTokensMapping(); - boost::filesystem::create_directories(mFixedPath.getUserConfigPath()); - boost::filesystem::create_directories(mFixedPath.getUserDataPath()); - + // Initialize with fixed paths, will be overridden in `readConfiguration`. mLogPath = mFixedPath.getUserConfigPath(); - + mUserDataPath = mFixedPath.getUserDataPath(); mScreenshotPath = mFixedPath.getUserDataPath() / "screenshots"; - - // probably not necessary but validate the creation of the screenshots directory and fallback to the original behavior if it fails - boost::system::error_code dirErr; - if (!boost::filesystem::create_directories(mScreenshotPath, dirErr) && !boost::filesystem::is_directory(mScreenshotPath)) { - mScreenshotPath = mFixedPath.getUserDataPath(); - } } ConfigurationManager::~ConfigurationManager() @@ -114,6 +106,30 @@ void ConfigurationManager::readConfiguration(boost::program_options::variables_m } mLogPath = mActiveConfigPaths.back(); + mUserDataPath = variables["user-data"].as(); + if (mUserDataPath.empty()) + { + if (!quiet) + Log(Debug::Warning) << "Error: `user-data` is not specified"; + mUserDataPath = mFixedPath.getUserDataPath(); + } + processPath(mUserDataPath, true); + mScreenshotPath = mUserDataPath / "screenshots"; + + boost::filesystem::create_directories(getUserConfigPath()); + boost::filesystem::create_directories(mScreenshotPath); + + // probably not necessary but validate the creation of the screenshots directory and fallback to the original behavior if it fails + if (!boost::filesystem::is_directory(mScreenshotPath)) + mScreenshotPath = mUserDataPath; + + if (!quiet) + { + Log(Debug::Info) << "Logs dir: " << getUserConfigPath().string(); + Log(Debug::Info) << "User data dir: " << mUserDataPath.string(); + Log(Debug::Info) << "Screenshots dir: " << mScreenshotPath.string(); + } + mSilent = silent; } @@ -141,8 +157,9 @@ void ConfigurationManager::addExtraConfigDirs(std::stack()->default_value(Files::MaybeQuotedPathContainer(), "config") - ->multitoken()->composing(), "additional config directories"); + ("config", bpo::value()->multitoken()->composing(), "additional config directories") + ("user-data", bpo::value(), + "set user data directory (used for saves, screenshots, etc)"); } boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map & variables, @@ -230,54 +247,57 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost boost::program_options::notify(first); } +void ConfigurationManager::processPath(boost::filesystem::path& path, bool create) const +{ + std::string str = path.string(); + + // Do nothing if the path doesn't start with a token + if (str.empty() || str[0] != '?') + return; + + std::string::size_type pos = str.find('?', 1); + if (pos != std::string::npos && pos != 0) + { + auto tokenIt = mTokensMapping.find(str.substr(0, pos + 1)); + if (tokenIt != mTokensMapping.end()) + { + boost::filesystem::path tempPath(((mFixedPath).*(tokenIt->second))()); + if (pos < str.length() - 1) + { + // There is something after the token, so we should + // append it to the path + tempPath /= str.substr(pos + 1, str.length() - pos); + } + + path = tempPath; + } + else + { + if (!mSilent) + Log(Debug::Warning) << "Path starts with unknown token: " << path; + path.clear(); + } + } + + if (!boost::filesystem::is_directory(path) && create) + { + try + { + boost::filesystem::create_directories(path); + } + catch (...) {} + } +} + void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool create) const { - std::string path; for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) { - path = it->string(); - - // Check if path contains a token - if (!path.empty() && *path.begin() == '?') - { - std::string::size_type pos = path.find('?', 1); - if (pos != std::string::npos && pos != 0) - { - auto tokenIt = mTokensMapping.find(path.substr(0, pos + 1)); - if (tokenIt != mTokensMapping.end()) - { - boost::filesystem::path tempPath(((mFixedPath).*(tokenIt->second))()); - if (pos < path.length() - 1) - { - // There is something after the token, so we should - // append it to the path - tempPath /= path.substr(pos + 1, path.length() - pos); - } - - *it = tempPath; - } - else - { - // Clean invalid / unknown token, it will be removed outside the loop - (*it).clear(); - } - } - } - + processPath(*it, create); if (!boost::filesystem::is_directory(*it)) { - if (create) - { - try - { - boost::filesystem::create_directories (*it); - } - catch (...) {} - - if (boost::filesystem::is_directory(*it)) - continue; - } - + if (!mSilent) + Log(Debug::Warning) << "No such dir: " << *it; (*it).clear(); } } @@ -322,7 +342,7 @@ const boost::filesystem::path& ConfigurationManager::getUserConfigPath() const const boost::filesystem::path& ConfigurationManager::getUserDataPath() const { - return mFixedPath.getUserDataPath(); + return mUserDataPath; } const boost::filesystem::path& ConfigurationManager::getLocalPath() const diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index fcbb0d5ff5..49844cca41 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -27,6 +27,7 @@ struct ConfigurationManager void readConfiguration(boost::program_options::variables_map& variables, boost::program_options::options_description& description, bool quiet=false); + void processPath(boost::filesystem::path& path, bool create = false) const; void processPaths(Files::PathContainer& dataDirs, bool create = false) const; ///< \param create Try creating the directory, if it does not exist. @@ -70,6 +71,7 @@ struct ConfigurationManager FixedPathType mFixedPath; boost::filesystem::path mLogPath; + boost::filesystem::path mUserDataPath; boost::filesystem::path mScreenshotPath; TokensMappingContainer mTokensMapping; diff --git a/files/openmw.cfg b/files/openmw.cfg index 7bd70c3e5e..d1ecd6f8a3 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -4,6 +4,7 @@ content=builtin.omwscripts data-local="?userdata?data" +user-data="?userdata?" config="?userconfig?" resources=${OPENMW_RESOURCE_FILES} script-blacklist=Museum diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index 79ab9e6156..f928113002 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -4,6 +4,7 @@ content=builtin.omwscripts data-local="?userdata?data" +user-data="?userdata?" config="?userconfig?" resources=./resources script-blacklist=Museum From 5b23ba3faf36c0aebe5de58a3a5decb2b600d779 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 2 Feb 2022 00:20:32 +0100 Subject: [PATCH 7/8] Fix `defaulted` in ConfigurationManager::mergeComposingVariables --- components/files/configurationmanager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 4101c4ef54..4ffd9013b7 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -187,7 +187,7 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost if (description.find_nothrow("replace", false)) { auto replace = second["replace"]; - if (!replace.empty()) + if (!replace.defaulted() && !replace.empty()) { std::vector replaceVector = replace.as>(); replacedVariables.insert(replaceVector.begin(), replaceVector.end()); @@ -206,13 +206,13 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost continue; } - if (replacedVariables.count(name)) + if (replacedVariables.count(name) || firstPosition->second.defaulted() || firstPosition->second.empty()) { firstPosition->second = second[name]; continue; } - if (second[name].empty()) + if (second[name].defaulted() || second[name].empty()) continue; boost::any& firstValue = firstPosition->second.value(); From 6084dbfc3a64feaee7305b2f710c5855f8f34678 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 3 Feb 2022 00:07:30 +0100 Subject: [PATCH 8/8] Add a warning if replace==config is used. --- components/files/configurationmanager.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 4ffd9013b7..87f4cd4943 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -123,6 +123,18 @@ void ConfigurationManager::readConfiguration(boost::program_options::variables_m if (!boost::filesystem::is_directory(mScreenshotPath)) mScreenshotPath = mUserDataPath; + if (!quiet && !variables["replace"].empty()) + { + for (const std::string& var : variables["replace"].as>()) + { + if (var == "config") + { + Log(Debug::Warning) << "replace=config is not allowed and was ignored"; + break; + } + } + } + if (!quiet) { Log(Debug::Info) << "Logs dir: " << getUserConfigPath().string();