1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-21 11:09:43 +00:00

Merge branch 'portable' into 'master'

Make OpenMW "portable"

Closes #2491

See merge request OpenMW/openmw!1555
This commit is contained in:
AnyOldName3 2022-02-03 22:49:49 +00:00
commit 9d8853442b
21 changed files with 328 additions and 260 deletions

View file

@ -103,6 +103,7 @@
Bug #6579: OpenMW compilation error when using OSG doubles for BoundingSphere Bug #6579: OpenMW compilation error when using OSG doubles for BoundingSphere
Feature #890: OpenMW-CS: Column filtering Feature #890: OpenMW-CS: Column filtering
Feature #1465: "Reset" argument for AI functions 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 #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 #2780: A way to see current OpenMW version in the console
Feature #3616: Allow Zoom levels on the World Map Feature #3616: Allow Zoom levels on the World Map

View file

@ -25,6 +25,7 @@ int main(int argc, char** argv)
("encoding", boost::program_options::value<std::string>()->default_value("win1252"), "encoding of the save file") ("encoding", boost::program_options::value<std::string>()->default_value("win1252"), "encoding of the save file")
; ;
p_desc.add("mwsave", 1).add("output", 1); p_desc.add("mwsave", 1).add("output", 1);
Files::ConfigurationManager::addCommonOptions(desc);
bpo::variables_map variables; bpo::variables_map variables;

View file

@ -414,57 +414,23 @@ bool Launcher::MainDialog::setupGameData()
bool Launcher::MainDialog::setupGraphicsSettings() bool Launcher::MainDialog::setupGraphicsSettings()
{ {
// This method is almost a copy of OMW::Engine::loadSettings(). They should definitely mEngineSettings.clear(); // Ensure to clear previous settings in case we had already loaded settings.
// remain consistent, and possibly be merged into a shared component. At the very least try
// the filenames should be in the CfgMgr component. {
boost::program_options::variables_map variables;
// Ensure to clear previous settings in case we had already loaded settings. boost::program_options::options_description desc;
mEngineSettings.clear(); mCfgMgr.addCommonOptions(desc);
mCfgMgr.readConfiguration(variables, desc, true);
// Create the settings manager and load default settings file mEngineSettings.load(mCfgMgr);
const std::string localDefault = (mCfgMgr.getLocalPath() / "defaults.bin").string(); return true;
const std::string globalDefault = (mCfgMgr.getGlobalPath() / "defaults.bin").string(); }
std::string defaultPath; catch (std::exception& e)
{
// Prefer the defaults.bin in the current directory. cfgError(tr("Error reading OpenMW configuration files"),
if (boost::filesystem::exists(localDefault)) tr("<br>The problem may be due to an incomplete installation of OpenMW.<br> \
defaultPath = localDefault; Reinstalling OpenMW may resolve the problem.<br>") + e.what());
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("<br><b>Could not find defaults.bin</b><br><br> \
The problem may be due to an incomplete installation of OpenMW.<br> \
Reinstalling OpenMW may resolve the problem."));
return false; return false;
} }
// Load the default settings, report any parsing errors.
try {
mEngineSettings.loadDefault(defaultPath);
}
catch (std::exception& e) {
std::string msg = std::string("<br><b>Error reading defaults.bin</b><br><br>") + 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("<br><b>Error reading settings.cfg</b><br><br>") + e.what();
cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str()));
return false;
}
return true;
} }
void Launcher::MainDialog::loadSettings() void Launcher::MainDialog::loadSettings()

View file

@ -84,27 +84,11 @@ namespace NavMeshTool
("process-interior-cells", bpo::value<bool>()->implicit_value(true) ("process-interior-cells", bpo::value<bool>()->implicit_value(true)
->default_value(false), "build navmesh for interior cells") ->default_value(false), "build navmesh for interior cells")
; ;
Files::ConfigurationManager::addCommonOptions(result);
return result; 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[]) int runNavMeshTool(int argc, char *argv[])
{ {
bpo::options_description desc = makeOptionsDescription(); bpo::options_description desc = makeOptionsDescription();
@ -165,7 +149,7 @@ namespace NavMeshTool
VFS::registerArchives(&vfs, fileCollections, archives, true); VFS::registerArchives(&vfs, fileCollections, archives, true);
Settings::Manager settings; Settings::Manager settings;
loadSettings(config, settings); settings.load(config);
const osg::Vec3f agentHalfExtents = Settings::Manager::getVector3("default actor pathfind half extents", "Game"); const osg::Vec3f agentHalfExtents = Settings::Manager::getVector3("default actor pathfind half extents", "Game");

View file

@ -5,6 +5,7 @@
#include <QLocalSocket> #include <QLocalSocket>
#include <QMessageBox> #include <QMessageBox>
#include <components/debug/debugging.hpp>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/fallback/validate.hpp> #include <components/fallback/validate.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
@ -20,7 +21,7 @@
using namespace Fallback; using namespace Fallback;
CS::Editor::Editor (int argc, char **argv) CS::Editor::Editor (int argc, char **argv)
: mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr), : mConfigVariables(readConfiguration()), mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr),
mPid(""), mLock(), mMerge (mDocumentManager), mPid(""), mLock(), mMerge (mDocumentManager),
mIpcServerName ("org.openmw.OpenCS"), mServer(nullptr), mClientSocket(nullptr) mIpcServerName ("org.openmw.OpenCS"), mServer(nullptr), mClientSocket(nullptr)
{ {
@ -82,7 +83,7 @@ CS::Editor::~Editor ()
remove(mPid.string().c_str())); // ignore any error remove(mPid.string().c_str())); // ignore any error
} }
std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfig(bool quiet) boost::program_options::variables_map CS::Editor::readConfiguration()
{ {
boost::program_options::variables_map variables; boost::program_options::variables_map variables;
boost::program_options::options_description desc("Syntax: openmw-cs <options>\nAllowed options"); boost::program_options::options_description desc("Syntax: openmw-cs <options>\nAllowed options");
@ -101,10 +102,19 @@ std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfi
->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)") ->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)")
("script-blacklist-use", boost::program_options::value<bool>()->implicit_value(true) ("script-blacklist-use", boost::program_options::value<bool>()->implicit_value(true)
->default_value(true), "enable script blacklisting"); ->default_value(true), "enable script blacklisting");
Files::ConfigurationManager::addCommonOptions(desc);
boost::program_options::notify(variables); boost::program_options::notify(variables);
mCfgMgr.readConfiguration(variables, desc, false); mCfgMgr.readConfiguration(variables, desc, false);
setupLogging(mCfgMgr.getLogPath().string(), "OpenMW-CS");
return variables;
}
std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfig(bool quiet)
{
boost::program_options::variables_map& variables = mConfigVariables;
Fallback::Map::init(variables["fallback"].as<FallbackMap>().mMap); Fallback::Map::init(variables["fallback"].as<FallbackMap>().mMap);

View file

@ -40,6 +40,7 @@ namespace CS
Q_OBJECT Q_OBJECT
Files::ConfigurationManager mCfgMgr; Files::ConfigurationManager mCfgMgr;
boost::program_options::variables_map mConfigVariables;
CSMPrefs::State mSettingsState; CSMPrefs::State mSettingsState;
CSMDoc::DocumentManager mDocumentManager; CSMDoc::DocumentManager mDocumentManager;
CSVDoc::StartupDialogue mStartup; CSVDoc::StartupDialogue mStartup;
@ -58,6 +59,8 @@ namespace CS
Files::PathContainer mDataDirs; Files::PathContainer mDataDirs;
std::string mEncodingName; 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<Files::PathContainer, std::vector<std::string> > readConfig(bool quiet=false); std::pair<Files::PathContainer, std::vector<std::string> > readConfig(bool quiet=false);
///< \return data paths ///< \return data paths

View file

@ -79,5 +79,5 @@ int runApplication(int argc, char *argv[])
int main(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);
} }

View file

@ -16,22 +16,7 @@ CSMPrefs::State *CSMPrefs::State::sThis = nullptr;
void CSMPrefs::State::load() void CSMPrefs::State::load()
{ {
// default settings file mSettings.load(mConfigurationManager);
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());
} }
void CSMPrefs::State::declare() void CSMPrefs::State::declare()

View file

@ -518,28 +518,6 @@ void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame)
mNewGame = newGame; mNewGame = 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();
// prefer local
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.");
// load user settings if they exist
std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string();
if (boost::filesystem::exists(settingspath))
settings.loadUser(settingspath);
return settingspath;
}
void OMW::Engine::createWindow(Settings::Manager& settings) void OMW::Engine::createWindow(Settings::Manager& settings)
{ {
int screen = settings.getInt("screen", "Video"); int screen = settings.getInt("screen", "Video");
@ -975,8 +953,7 @@ void OMW::Engine::go()
// Load settings // Load settings
Settings::Manager settings; Settings::Manager settings;
std::string settingspath; std::string settingspath = settings.load(mCfgMgr);
settingspath = loadSettings (settings);
MWClass::registerClasses(); MWClass::registerClasses();

View file

@ -40,6 +40,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
typedef std::vector<std::string> StringsVector; typedef std::vector<std::string> StringsVector;
bpo::options_description desc = OpenMW::makeOptionsDescription(); bpo::options_description desc = OpenMW::makeOptionsDescription();
Files::ConfigurationManager::addCommonOptions(desc);
bpo::variables_map variables; bpo::variables_map variables;
@ -61,9 +62,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
return false; return false;
} }
bpo::variables_map composingVariables = Files::separateComposingVariables(variables, desc);
cfgMgr.readConfiguration(variables, desc); cfgMgr.readConfiguration(variables, desc);
Files::mergeComposingVariables(variables, composingVariables, desc);
setupLogging(cfgMgr.getLogPath().string(), "OpenMW");
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::MaybeQuotedPath>().string()); Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::MaybeQuotedPath>().string());
Log(Debug::Info) << v.describe(); Log(Debug::Info) << v.describe();
@ -230,7 +231,7 @@ extern "C" int SDL_main(int argc, char**argv)
int main(int argc, char**argv) int main(int argc, char**argv)
#endif #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. // Platform specific for Windows when there is no console built into the executable.

View file

@ -58,6 +58,7 @@ struct ContentFileTest : public ::testing::Test
("content", boost::program_options::value<std::vector<std::string>>()->default_value(std::vector<std::string>(), "") ("content", boost::program_options::value<std::vector<std::string>>()->default_value(std::vector<std::string>(), "")
->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon")
("data-local", boost::program_options::value<Files::MaybeQuotedPathContainer::value_type>()->default_value(Files::MaybeQuotedPathContainer::value_type(), "")); ("data-local", boost::program_options::value<Files::MaybeQuotedPathContainer::value_type>()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""));
Files::ConfigurationManager::addCommonOptions(desc);
boost::program_options::notify(variables); boost::program_options::notify(variables);

View file

@ -137,61 +137,65 @@ namespace Debug
} }
static std::unique_ptr<std::ostream> rawStdout = nullptr; static std::unique_ptr<std::ostream> rawStdout = nullptr;
static std::unique_ptr<std::ostream> rawStderr = nullptr;
static boost::filesystem::ofstream logfile;
#if defined(_WIN32) && defined(_DEBUG)
static boost::iostreams::stream_buffer<Debug::DebugOutput> sb;
#else
static boost::iostreams::stream_buffer<Debug::Tee> coutsb;
static boost::iostreams::stream_buffer<Debug::Tee> cerrsb;
#endif
std::ostream& getRawStdout() std::ostream& getRawStdout()
{ {
return rawStdout ? *rawStdout : std::cout; 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 #if defined _WIN32
(void)Debug::attachParentConsole(); (void)Debug::attachParentConsole();
#endif #endif
rawStdout = std::make_unique<std::ostream>(std::cout.rdbuf()); rawStdout = std::make_unique<std::ostream>(std::cout.rdbuf());
rawStderr = std::make_unique<std::ostream>(std::cerr.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<Debug::DebugOutput> sb;
#else
boost::iostreams::stream_buffer<Debug::Tee> coutsb;
boost::iostreams::stream_buffer<Debug::Tee> 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;
int ret = 0; int ret = 0;
try try
{ {
Files::ConfigurationManager cfgMgr; Files::ConfigurationManager cfgMgr;
#if defined(_WIN32) && defined(_DEBUG) if (autoSetupLogging)
// Redirect cout and cerr to VS debug output when running in debug mode {
sb.open(Debug::DebugOutput()); std::ios_base::openmode mode = std::ios::out;
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;
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)); setupLogging(cfgMgr.getLogPath().string(), appName, mode);
cerrsb.open (Debug::Tee(logfile, oldcerr)); }
std::cout.rdbuf (&coutsb);
std::cerr.rdbuf (&cerrsb);
#endif
#if defined(_WIN32) #if defined(_WIN32)
const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.dmp"; 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 // Restore cout and cerr
std::cout.rdbuf(cout_rdbuf); std::cout.rdbuf(rawStdout->rdbuf());
std::cerr.rdbuf(cerr_rdbuf); std::cerr.rdbuf(rawStderr->rdbuf());
Debug::CurrentDebugLevel = Debug::NoLevel; Debug::CurrentDebugLevel = Debug::NoLevel;
return ret; return ret;

View file

@ -133,11 +133,16 @@ namespace Debug
std::map<Level, int> mColors; std::map<Level, int> mColors;
}; };
#endif #endif
} }
// Can be used to print messages without timestamps // Can be used to print messages without timestamps
std::ostream& getRawStdout(); 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 #endif

View file

@ -20,6 +20,7 @@ static const char* const applicationName = "openmw";
#endif #endif
const char* const localToken = "?local?"; const char* const localToken = "?local?";
const char* const userConfigToken = "?userconfig?";
const char* const userDataToken = "?userdata?"; const char* const userDataToken = "?userdata?";
const char* const globalToken = "?global?"; const char* const globalToken = "?global?";
@ -29,18 +30,10 @@ ConfigurationManager::ConfigurationManager(bool silent)
{ {
setupTokensMapping(); setupTokensMapping();
boost::filesystem::create_directories(mFixedPath.getUserConfigPath()); // Initialize with fixed paths, will be overridden in `readConfiguration`.
boost::filesystem::create_directories(mFixedPath.getUserDataPath());
mLogPath = mFixedPath.getUserConfigPath(); mLogPath = mFixedPath.getUserConfigPath();
mUserDataPath = mFixedPath.getUserDataPath();
mScreenshotPath = mFixedPath.getUserDataPath() / "screenshots"; 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() ConfigurationManager::~ConfigurationManager()
@ -50,6 +43,7 @@ ConfigurationManager::~ConfigurationManager()
void ConfigurationManager::setupTokensMapping() void ConfigurationManager::setupTokensMapping()
{ {
mTokensMapping.insert(std::make_pair(localToken, &FixedPath<>::getLocalPath)); 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(userDataToken, &FixedPath<>::getUserDataPath));
mTokensMapping.insert(std::make_pair(globalToken, &FixedPath<>::getGlobalDataPath)); mTokensMapping.insert(std::make_pair(globalToken, &FixedPath<>::getGlobalDataPath));
} }
@ -57,31 +51,129 @@ void ConfigurationManager::setupTokensMapping()
void ConfigurationManager::readConfiguration(boost::program_options::variables_map& variables, void ConfigurationManager::readConfiguration(boost::program_options::variables_map& variables,
boost::program_options::options_description& description, bool quiet) boost::program_options::options_description& description, bool quiet)
{ {
using ParsedConfigFile = bpo::basic_parsed_options<char>;
bool silent = mSilent; bool silent = mSilent;
mSilent = quiet; 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 std::optional<ParsedConfigFile> config = loadConfig(mFixedPath.getLocalPath(), description);
composingVariables = separateComposingVariables(variables, description); if (config)
bool loaded = loadConfig(mFixedPath.getLocalPath(), variables, description); mActiveConfigPaths.push_back(mFixedPath.getLocalPath());
mergeComposingVariables(variables, composingVariables, description); else
boost::program_options::notify(variables);
if (!loaded)
{ {
composingVariables = separateComposingVariables(variables, description); mActiveConfigPaths.push_back(mFixedPath.getGlobalConfigPath());
loadConfig(mFixedPath.getGlobalConfigPath(), variables, description); 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<boost::filesystem::path> extraConfigDirs;
addExtraConfigDirs(extraConfigDirs, variables);
addExtraConfigDirs(extraConfigDirs, *config);
std::vector<ParsedConfigFile> parsedOptions{*std::move(config)};
std::set<boost::filesystem::path> 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); mergeComposingVariables(variables, composingVariables, description);
boost::program_options::notify(variables); }
mLogPath = mActiveConfigPaths.back();
mUserDataPath = variables["user-data"].as<Files::MaybeQuotedPath>();
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 && !variables["replace"].empty())
{
for (const std::string& var : variables["replace"].as<std::vector<std::string>>())
{
if (var == "config")
{
Log(Debug::Warning) << "replace=config is not allowed and was ignored";
break;
}
}
}
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; mSilent = silent;
} }
void ConfigurationManager::addExtraConfigDirs(std::stack<boost::filesystem::path>& dirs,
const bpo::basic_parsed_options<char>& 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<boost::filesystem::path>& 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<Files::MaybeQuotedPathContainer>());
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<Files::MaybeQuotedPathContainer>()->multitoken()->composing(), "additional config directories")
("user-data", bpo::value<Files::MaybeQuotedPath>(),
"set user data directory (used for saves, screenshots, etc)");
}
boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map & variables, boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map & variables,
boost::program_options::options_description& description) boost::program_options::options_description& description)
{ {
@ -126,7 +218,7 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost
continue; continue;
} }
if (replacedVariables.count(name)) if (replacedVariables.count(name) || firstPosition->second.defaulted() || firstPosition->second.empty())
{ {
firstPosition->second = second[name]; firstPosition->second = second[name];
continue; continue;
@ -142,7 +234,6 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost
{ {
auto& firstPathContainer = boost::any_cast<Files::MaybeQuotedPathContainer&>(firstValue); auto& firstPathContainer = boost::any_cast<Files::MaybeQuotedPathContainer&>(firstValue);
const auto& secondPathContainer = boost::any_cast<const Files::MaybeQuotedPathContainer&>(secondValue); const auto& secondPathContainer = boost::any_cast<const Files::MaybeQuotedPathContainer&>(secondValue);
firstPathContainer.insert(firstPathContainer.end(), secondPathContainer.begin(), secondPathContainer.end()); firstPathContainer.insert(firstPathContainer.end(), secondPathContainer.begin(), secondPathContainer.end());
} }
else if (firstValue.type() == typeid(std::vector<std::string>)) else if (firstValue.type() == typeid(std::vector<std::string>))
@ -165,57 +256,60 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost
Log(Debug::Error) << "Unexpected composing variable type. Curse boost and their blasted arcane templates."; 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::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) for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it)
{ {
path = it->string(); processPath(*it, create);
// 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)
{
TokensMappingContainer::iterator 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();
}
}
}
if (!boost::filesystem::is_directory(*it)) if (!boost::filesystem::is_directory(*it))
{ {
if (create) if (!mSilent)
{ Log(Debug::Warning) << "No such dir: " << *it;
try
{
boost::filesystem::create_directories (*it);
}
catch (...) {}
if (boost::filesystem::is_directory(*it))
continue;
}
(*it).clear(); (*it).clear();
} }
} }
@ -224,8 +318,8 @@ void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool cre
std::bind(&boost::filesystem::path::empty, std::placeholders::_1)), dataDirs.end()); std::bind(&boost::filesystem::path::empty, std::placeholders::_1)), dataDirs.end());
} }
bool ConfigurationManager::loadConfig(const boost::filesystem::path& path, std::optional<bpo::basic_parsed_options<char>> ConfigurationManager::loadConfig(
boost::program_options::variables_map& variables, const boost::filesystem::path& path,
boost::program_options::options_description& description) boost::program_options::options_description& description)
{ {
boost::filesystem::path cfgFile(path); boost::filesystem::path cfgFile(path);
@ -238,20 +332,11 @@ bool ConfigurationManager::loadConfig(const boost::filesystem::path& path,
boost::filesystem::ifstream configFileStream(cfgFile); boost::filesystem::ifstream configFileStream(cfgFile);
if (configFileStream.is_open()) if (configFileStream.is_open())
{ return Files::parse_config_file(configFileStream, description, true);
parseConfig(configFileStream, variables, description); else if (!mSilent)
Log(Debug::Error) << "Loading failed.";
return true;
}
else
{
if (!mSilent)
Log(Debug::Error) << "Loading failed.";
return false;
}
} }
return false; return std::nullopt;
} }
const boost::filesystem::path& ConfigurationManager::getGlobalPath() const const boost::filesystem::path& ConfigurationManager::getGlobalPath() const
@ -261,12 +346,15 @@ const boost::filesystem::path& ConfigurationManager::getGlobalPath() const
const boost::filesystem::path& ConfigurationManager::getUserConfigPath() 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 const boost::filesystem::path& ConfigurationManager::getUserDataPath() const
{ {
return mFixedPath.getUserDataPath(); return mUserDataPath;
} }
const boost::filesystem::path& ConfigurationManager::getLocalPath() const const boost::filesystem::path& ConfigurationManager::getLocalPath() const
@ -344,4 +432,4 @@ PathContainer asPathContainer(const MaybeQuotedPathContainer& MaybeQuotedPathCon
return PathContainer(MaybeQuotedPathContainer.begin(), MaybeQuotedPathContainer.end()); return PathContainer(MaybeQuotedPathContainer.begin(), MaybeQuotedPathContainer.end());
} }
} /* namespace Cfg */ } /* namespace Files */

View file

@ -2,6 +2,8 @@
#define COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP #define COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP
#include <map> #include <map>
#include <optional>
#include <stack>
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
@ -25,39 +27,51 @@ struct ConfigurationManager
void readConfiguration(boost::program_options::variables_map& variables, void readConfiguration(boost::program_options::variables_map& variables,
boost::program_options::options_description& description, bool quiet=false); boost::program_options::options_description& description, bool quiet=false);
void processPaths(Files::PathContainer& dataDirs, bool create = 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. ///< \param create Try creating the directory, if it does not exist.
/**< Fixed paths */ /**< Fixed paths */
const boost::filesystem::path& getGlobalPath() const; const boost::filesystem::path& getGlobalPath() const;
const boost::filesystem::path& getUserConfigPath() const;
const boost::filesystem::path& getLocalPath() const; const boost::filesystem::path& getLocalPath() const;
const boost::filesystem::path& getGlobalDataPath() const; const boost::filesystem::path& getGlobalDataPath() const;
const boost::filesystem::path& getUserConfigPath() const;
const boost::filesystem::path& getUserDataPath() const; const boost::filesystem::path& getUserDataPath() const;
const boost::filesystem::path& getLocalDataPath() const; const boost::filesystem::path& getLocalDataPath() const;
const boost::filesystem::path& getInstallPath() const; const boost::filesystem::path& getInstallPath() const;
const std::vector<boost::filesystem::path>& getActiveConfigPaths() const { return mActiveConfigPaths; }
const boost::filesystem::path& getCachePath() const; const boost::filesystem::path& getCachePath() const;
const boost::filesystem::path& getLogPath() const; const boost::filesystem::path& getLogPath() const;
const boost::filesystem::path& getScreenshotPath() const; const boost::filesystem::path& getScreenshotPath() const;
static void addCommonOptions(boost::program_options::options_description& description);
private: private:
typedef Files::FixedPath<> FixedPathType; typedef Files::FixedPath<> FixedPathType;
typedef const boost::filesystem::path& (FixedPathType::*path_type_f)() const; typedef const boost::filesystem::path& (FixedPathType::*path_type_f)() const;
typedef std::map<std::string, path_type_f> TokensMappingContainer; typedef std::map<std::string, path_type_f> TokensMappingContainer;
bool loadConfig(const boost::filesystem::path& path, std::optional<boost::program_options::basic_parsed_options<char>> loadConfig(
boost::program_options::variables_map& variables, const boost::filesystem::path& path,
boost::program_options::options_description& description); boost::program_options::options_description& description);
void addExtraConfigDirs(std::stack<boost::filesystem::path>& dirs,
const boost::program_options::variables_map& variables) const;
void addExtraConfigDirs(std::stack<boost::filesystem::path>& dirs,
const boost::program_options::basic_parsed_options<char>& options) const;
void setupTokensMapping(); void setupTokensMapping();
std::vector<boost::filesystem::path> mActiveConfigPaths;
FixedPathType mFixedPath; FixedPathType mFixedPath;
boost::filesystem::path mLogPath; boost::filesystem::path mLogPath;
boost::filesystem::path mUserDataPath;
boost::filesystem::path mScreenshotPath; boost::filesystem::path mScreenshotPath;
TokensMappingContainer mTokensMapping; TokensMappingContainer mTokensMapping;

View file

@ -9,7 +9,8 @@
#include <Base64.h> #include <Base64.h>
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; mFile = file;
boost::filesystem::ifstream fstream; boost::filesystem::ifstream fstream;
@ -73,7 +74,9 @@ void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, Cat
std::string value = line.substr(valueBegin); std::string value = line.substr(valueBegin);
Misc::StringUtils::trim(value); 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)); fail(std::string("duplicate setting: [" + currentCategory + "] " + setting));
} }
} }

View file

@ -10,7 +10,8 @@ namespace Settings
class SettingsFileParser class SettingsFileParser
{ {
public: 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); void saveSettingsFile(const std::string& file, const CategorySettingValueMap& settings);

View file

@ -3,6 +3,7 @@
#include <sstream> #include <sstream>
#include <components/files/configurationmanager.hpp>
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
namespace Settings namespace Settings
@ -19,16 +20,33 @@ void Manager::clear()
mChangedSettings.clear(); mChangedSettings.clear();
} }
void Manager::loadDefault(const std::string &file) std::string Manager::load(const Files::ConfigurationManager& cfgMgr)
{ {
SettingsFileParser parser; SettingsFileParser parser;
parser.loadSettingsFile(file, mDefaultSettings, true); const std::vector<boost::filesystem::path>& 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) // Create the settings manager and load default settings file.
{ const std::string defaultsBin = (paths.front() / "defaults.bin").string();
SettingsFileParser parser; if (!boost::filesystem::exists(defaultsBin))
parser.loadSettingsFile(file, mUserSettings); 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<int>(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) void Manager::saveUser(const std::string &file)

View file

@ -9,6 +9,11 @@
#include <osg/Vec2f> #include <osg/Vec2f>
#include <osg/Vec3f> #include <osg/Vec3f>
namespace Files
{
struct ConfigurationManager;
}
namespace Settings namespace Settings
{ {
/// ///
@ -26,11 +31,8 @@ namespace Settings
void clear(); void clear();
///< clears all settings and default settings ///< clears all settings and default settings
void loadDefault (const std::string& file); std::string load(const Files::ConfigurationManager& cfgMgr);
///< load file as the default settings (can be overridden by user settings) ///< load settings from all active config dirs. Returns the path of the last loaded file.
void loadUser (const std::string& file);
///< load file as user settings
void saveUser (const std::string& file); void saveUser (const std::string& file);
///< save user settings to file ///< save user settings to file

View file

@ -4,6 +4,8 @@
content=builtin.omwscripts content=builtin.omwscripts
data-local="?userdata?data" data-local="?userdata?data"
user-data="?userdata?"
config="?userconfig?"
resources=${OPENMW_RESOURCE_FILES} resources=${OPENMW_RESOURCE_FILES}
script-blacklist=Museum script-blacklist=Museum
script-blacklist=MockChangeScript script-blacklist=MockChangeScript

View file

@ -4,6 +4,8 @@
content=builtin.omwscripts content=builtin.omwscripts
data-local="?userdata?data" data-local="?userdata?data"
user-data="?userdata?"
config="?userconfig?"
resources=./resources resources=./resources
script-blacklist=Museum script-blacklist=Museum
script-blacklist=MockChangeScript script-blacklist=MockChangeScript