mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-26 18:35:20 +00:00
29556a1802
A Warning indicates a potential problem in the content file(s) that the user told OpenMW to load. E.g. this might cause an object to not display at all or as intended, however the rest of the game will run fine. An Error, however, is more likely to be a bug with the engine itself - it means that basic assumptions have been violated and the engine might not run correctly anymore. The above mostly applies to errors/warnings during game-play; startup issues are handled differently: when a file is completely invalid/corrupted to the point that the engine can not start, that might cause messages that are worded as Error due to the severity of the issue but are not necessarily the engine's fault. Hopefully, being a little more consistent here will alleviate confusion among users as to when a log message should be reported and to whom.
390 lines
14 KiB
C++
390 lines
14 KiB
C++
#include <iostream>
|
|
#include <cstdio>
|
|
|
|
#include <components/version/version.hpp>
|
|
#include <components/files/configurationmanager.hpp>
|
|
#include <components/files/escape.hpp>
|
|
#include <components/fallback/validate.hpp>
|
|
|
|
#include <SDL_messagebox.h>
|
|
#include <SDL_main.h>
|
|
#include "engine.hpp"
|
|
|
|
#include <boost/iostreams/concepts.hpp>
|
|
#include <boost/iostreams/stream_buffer.hpp>
|
|
#include <boost/filesystem/fstream.hpp>
|
|
|
|
#if defined(_WIN32)
|
|
// For OutputDebugString
|
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#endif
|
|
#include <windows.h>
|
|
// makes __argc and __argv available on windows
|
|
#include <cstdlib>
|
|
#endif
|
|
|
|
|
|
#if (defined(__APPLE__) || (defined(__linux) && !defined(ANDROID)) || (defined(__unix) && !defined(ANDROID)) || defined(__posix))
|
|
#define USE_CRASH_CATCHER 1
|
|
#else
|
|
#define USE_CRASH_CATCHER 0
|
|
#endif
|
|
|
|
#if USE_CRASH_CATCHER
|
|
#include <csignal>
|
|
extern int cc_install_handlers(int argc, char **argv, int num_signals, int *sigs, const char *logfile, int (*user_info)(char*, char*));
|
|
extern int is_debugger_attached(void);
|
|
#endif
|
|
|
|
#include <boost/version.hpp>
|
|
/**
|
|
* Workaround for problems with whitespaces in paths in older versions of Boost library
|
|
*/
|
|
#if (BOOST_VERSION <= 104600)
|
|
namespace boost
|
|
{
|
|
|
|
template<>
|
|
inline boost::filesystem::path lexical_cast<boost::filesystem::path, std::string>(const std::string& arg)
|
|
{
|
|
return boost::filesystem::path(arg);
|
|
}
|
|
|
|
} /* namespace boost */
|
|
#endif /* (BOOST_VERSION <= 104600) */
|
|
|
|
|
|
using namespace Fallback;
|
|
|
|
/**
|
|
* \brief Parses application command line and calls \ref Cfg::ConfigurationManager
|
|
* to parse configuration files.
|
|
*
|
|
* Results are directly written to \ref Engine class.
|
|
*
|
|
* \retval true - Everything goes OK
|
|
* \retval false - Error
|
|
*/
|
|
bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::ConfigurationManager& cfgMgr)
|
|
{
|
|
// Create a local alias for brevity
|
|
namespace bpo = boost::program_options;
|
|
typedef std::vector<std::string> StringsVector;
|
|
|
|
bpo::options_description desc("Syntax: openmw <options>\nAllowed options");
|
|
|
|
desc.add_options()
|
|
("help", "print help message")
|
|
("version", "print version information and quit")
|
|
("data", bpo::value<Files::EscapePathContainer>()->default_value(Files::EscapePathContainer(), "data")
|
|
->multitoken()->composing(), "set data directories (later directories have higher priority)")
|
|
|
|
("data-local", bpo::value<Files::EscapeHashString>()->default_value(""),
|
|
"set local data directory (highest priority)")
|
|
|
|
("fallback-archive", bpo::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "fallback-archive")
|
|
->multitoken(), "set fallback BSA archives (later archives have higher priority)")
|
|
|
|
("resources", bpo::value<Files::EscapeHashString>()->default_value("resources"),
|
|
"set resources directory")
|
|
|
|
("start", bpo::value<Files::EscapeHashString>()->default_value(""),
|
|
"set initial cell")
|
|
|
|
("content", bpo::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
|
|
->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon")
|
|
|
|
("no-sound", bpo::value<bool>()->implicit_value(true)
|
|
->default_value(false), "disable all sounds")
|
|
|
|
("script-all", bpo::value<bool>()->implicit_value(true)
|
|
->default_value(false), "compile all scripts (excluding dialogue scripts) at startup")
|
|
|
|
("script-all-dialogue", bpo::value<bool>()->implicit_value(true)
|
|
->default_value(false), "compile all dialogue scripts at startup")
|
|
|
|
("script-console", bpo::value<bool>()->implicit_value(true)
|
|
->default_value(false), "enable console-only script functionality")
|
|
|
|
("script-run", bpo::value<Files::EscapeHashString>()->default_value(""),
|
|
"select a file containing a list of console commands that is executed on startup")
|
|
|
|
("script-warn", bpo::value<int>()->implicit_value (1)
|
|
->default_value (1),
|
|
"handling of warnings when compiling scripts\n"
|
|
"\t0 - ignore warning\n"
|
|
"\t1 - show warning but consider script as correctly compiled anyway\n"
|
|
"\t2 - treat warnings as errors")
|
|
|
|
("script-blacklist", bpo::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
|
|
->multitoken(), "ignore the specified script (if the use of the blacklist is enabled)")
|
|
|
|
("script-blacklist-use", bpo::value<bool>()->implicit_value(true)
|
|
->default_value(true), "enable script blacklisting")
|
|
|
|
("load-savegame", bpo::value<Files::EscapeHashString>()->default_value(""),
|
|
"load a save game file on game startup (specify an absolute filename or a filename relative to the current working directory)")
|
|
|
|
("skip-menu", bpo::value<bool>()->implicit_value(true)
|
|
->default_value(false), "skip main menu on game startup")
|
|
|
|
("new-game", bpo::value<bool>()->implicit_value(true)
|
|
->default_value(false), "run new game sequence (ignored if skip-menu=0)")
|
|
|
|
("fs-strict", bpo::value<bool>()->implicit_value(true)
|
|
->default_value(false), "strict file system handling (no case folding)")
|
|
|
|
("encoding", bpo::value<Files::EscapeHashString>()->
|
|
default_value("win1252"),
|
|
"Character encoding used in OpenMW game messages:\n"
|
|
"\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n"
|
|
"\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n"
|
|
"\n\twin1252 - Western European (Latin) alphabet, used by default")
|
|
|
|
("fallback", bpo::value<FallbackMap>()->default_value(FallbackMap(), "")
|
|
->multitoken()->composing(), "fallback values")
|
|
|
|
("no-grab", "Don't grab mouse cursor")
|
|
|
|
("export-fonts", bpo::value<bool>()->implicit_value(true)
|
|
->default_value(false), "Export Morrowind .fnt fonts to PNG image and XML file in current directory")
|
|
|
|
("activate-dist", bpo::value <int> ()->default_value (-1), "activation distance override");
|
|
|
|
bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv)
|
|
.options(desc).allow_unregistered().run();
|
|
|
|
bpo::variables_map variables;
|
|
|
|
// Runtime options override settings from all configs
|
|
bpo::store(valid_opts, variables);
|
|
bpo::notify(variables);
|
|
|
|
if (variables.count ("help"))
|
|
{
|
|
std::cout << desc << std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (variables.count ("version"))
|
|
{
|
|
cfgMgr.readConfiguration(variables, desc, true);
|
|
|
|
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::EscapeHashString>().toStdString());
|
|
std::cout << v.describe() << std::endl;
|
|
return false;
|
|
}
|
|
|
|
cfgMgr.readConfiguration(variables, desc);
|
|
|
|
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::EscapeHashString>().toStdString());
|
|
std::cout << v.describe() << std::endl;
|
|
|
|
engine.setGrabMouse(!variables.count("no-grab"));
|
|
|
|
// Font encoding settings
|
|
std::string encoding(variables["encoding"].as<Files::EscapeHashString>().toStdString());
|
|
std::cout << ToUTF8::encodingUsingMessage(encoding) << std::endl;
|
|
engine.setEncoding(ToUTF8::calculateEncoding(encoding));
|
|
|
|
// directory settings
|
|
engine.enableFSStrict(variables["fs-strict"].as<bool>());
|
|
|
|
Files::PathContainer dataDirs(Files::EscapePath::toPathContainer(variables["data"].as<Files::EscapePathContainer>()));
|
|
|
|
std::string local(variables["data-local"].as<Files::EscapeHashString>().toStdString());
|
|
if (!local.empty())
|
|
{
|
|
dataDirs.push_back(Files::PathContainer::value_type(local));
|
|
}
|
|
|
|
cfgMgr.processPaths(dataDirs);
|
|
|
|
engine.setDataDirs(dataDirs);
|
|
|
|
// fallback archives
|
|
StringsVector archives = variables["fallback-archive"].as<Files::EscapeStringVector>().toStdStringVector();
|
|
for (StringsVector::const_iterator it = archives.begin(); it != archives.end(); ++it)
|
|
{
|
|
engine.addArchive(*it);
|
|
}
|
|
|
|
engine.setResourceDir(variables["resources"].as<Files::EscapeHashString>().toStdString());
|
|
|
|
StringsVector content = variables["content"].as<Files::EscapeStringVector>().toStdStringVector();
|
|
if (content.empty())
|
|
{
|
|
std::cout << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting..." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
StringsVector::const_iterator it(content.begin());
|
|
StringsVector::const_iterator end(content.end());
|
|
for (; it != end; ++it)
|
|
{
|
|
engine.addContentFile(*it);
|
|
}
|
|
|
|
// startup-settings
|
|
engine.setCell(variables["start"].as<Files::EscapeHashString>().toStdString());
|
|
engine.setSkipMenu (variables["skip-menu"].as<bool>(), variables["new-game"].as<bool>());
|
|
if (!variables["skip-menu"].as<bool>() && variables["new-game"].as<bool>())
|
|
std::cerr << "Warning: new-game used without skip-menu -> ignoring it" << std::endl;
|
|
|
|
// scripts
|
|
engine.setCompileAll(variables["script-all"].as<bool>());
|
|
engine.setCompileAllDialogue(variables["script-all-dialogue"].as<bool>());
|
|
engine.setScriptConsoleMode (variables["script-console"].as<bool>());
|
|
engine.setStartupScript (variables["script-run"].as<Files::EscapeHashString>().toStdString());
|
|
engine.setWarningsMode (variables["script-warn"].as<int>());
|
|
engine.setScriptBlacklist (variables["script-blacklist"].as<Files::EscapeStringVector>().toStdStringVector());
|
|
engine.setScriptBlacklistUse (variables["script-blacklist-use"].as<bool>());
|
|
engine.setSaveGameFile (variables["load-savegame"].as<Files::EscapeHashString>().toStdString());
|
|
|
|
// other settings
|
|
engine.setSoundUsage(!variables["no-sound"].as<bool>());
|
|
engine.setFallbackValues(variables["fallback"].as<FallbackMap>().mMap);
|
|
engine.setActivationDistanceOverride (variables["activate-dist"].as<int>());
|
|
engine.enableFontExport(variables["export-fonts"].as<bool>());
|
|
|
|
return true;
|
|
}
|
|
|
|
#if defined(_WIN32) && defined(_DEBUG)
|
|
|
|
class DebugOutput : public boost::iostreams::sink
|
|
{
|
|
public:
|
|
std::streamsize write(const char *str, std::streamsize size)
|
|
{
|
|
// Make a copy for null termination
|
|
std::string tmp (str, static_cast<unsigned int>(size));
|
|
// Write string to Visual Studio Debug output
|
|
OutputDebugString (tmp.c_str ());
|
|
return size;
|
|
}
|
|
};
|
|
#else
|
|
class Tee : public boost::iostreams::sink
|
|
{
|
|
public:
|
|
Tee(std::ostream &stream, std::ostream &stream2)
|
|
: out(stream), out2(stream2)
|
|
{
|
|
}
|
|
|
|
std::streamsize write(const char *str, std::streamsize size)
|
|
{
|
|
out.write (str, size);
|
|
out.flush();
|
|
out2.write (str, size);
|
|
out2.flush();
|
|
return size;
|
|
}
|
|
|
|
private:
|
|
std::ostream &out;
|
|
std::ostream &out2;
|
|
};
|
|
#endif
|
|
|
|
int main(int argc, char**argv)
|
|
{
|
|
#if defined(__APPLE__)
|
|
setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0);
|
|
#endif
|
|
|
|
// 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<Tee> coutsb;
|
|
boost::iostreams::stream_buffer<Tee> cerrsb;
|
|
#endif
|
|
|
|
std::ostream oldcout(cout_rdbuf);
|
|
std::ostream oldcerr(cerr_rdbuf);
|
|
|
|
boost::filesystem::ofstream logfile;
|
|
|
|
std::auto_ptr<OMW::Engine> engine;
|
|
|
|
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
|
|
boost::iostreams::stream_buffer<DebugOutput> sb;
|
|
sb.open(DebugOutput());
|
|
std::cout.rdbuf (&sb);
|
|
std::cerr.rdbuf (&sb);
|
|
#else
|
|
// Redirect cout and cerr to openmw.log
|
|
logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / "/openmw.log"));
|
|
|
|
coutsb.open (Tee(logfile, oldcout));
|
|
cerrsb.open (Tee(logfile, oldcerr));
|
|
|
|
std::cout.rdbuf (&coutsb);
|
|
std::cerr.rdbuf (&cerrsb);
|
|
#endif
|
|
|
|
|
|
#if USE_CRASH_CATCHER
|
|
// Unix crash catcher
|
|
if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_attached())
|
|
{
|
|
int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT };
|
|
cc_install_handlers(argc, argv, 5, s, (cfgMgr.getLogPath() / "crash.log").string().c_str(), NULL);
|
|
std::cout << "Installing crash catcher" << std::endl;
|
|
}
|
|
else
|
|
std::cout << "Running in a debugger, not installing crash catcher" << std::endl;
|
|
#endif
|
|
|
|
#ifdef __APPLE__
|
|
boost::filesystem::path binary_path = boost::filesystem::system_complete(boost::filesystem::path(argv[0]));
|
|
boost::filesystem::current_path(binary_path.parent_path());
|
|
#endif
|
|
|
|
engine.reset(new OMW::Engine(cfgMgr));
|
|
|
|
if (parseOptions(argc, argv, *engine, cfgMgr))
|
|
{
|
|
engine->go();
|
|
}
|
|
}
|
|
catch (std::exception &e)
|
|
{
|
|
#if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix))
|
|
if (!isatty(fileno(stdin)))
|
|
#endif
|
|
SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL);
|
|
|
|
std::cerr << "\nERROR: " << e.what() << std::endl;
|
|
|
|
ret = 1;
|
|
}
|
|
|
|
// Restore cout and cerr
|
|
std::cout.rdbuf(cout_rdbuf);
|
|
std::cerr.rdbuf(cerr_rdbuf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Platform specific for Windows when there is no console built into the executable.
|
|
// Windows will call the WinMain function instead of main in this case, the normal
|
|
// main function is then called with the __argc and __argv parameters.
|
|
#if defined(_WIN32) && !defined(_CONSOLE)
|
|
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
|
|
{
|
|
return main(__argc, __argv);
|
|
}
|
|
#endif
|