mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-04-28 21:07:59 +03:00

Setup logging after setting up paths but before settings loading to get proper log records earlier. Read configuration by navmeshtool and bulletobjecttool the same way editor and engine do to properly handle --replace config and --config arguments. Remove mode and autoSetupLogging arguments from setupLogging since they are no longer used. Use temp path to write crash logs because default paths might not be available for portable setup.
443 lines
14 KiB
C++
443 lines
14 KiB
C++
#include "editor.hpp"
|
|
|
|
#include <QApplication>
|
|
#include <QLocalServer>
|
|
#include <QLocalSocket>
|
|
#include <QMessageBox>
|
|
|
|
#include <boost/program_options/options_description.hpp>
|
|
|
|
#include <components/debug/debugging.hpp>
|
|
#include <components/debug/debuglog.hpp>
|
|
#include <components/fallback/validate.hpp>
|
|
#include <components/misc/rng.hpp>
|
|
#include <components/nifosg/nifloader.hpp>
|
|
#include <components/settings/settings.hpp>
|
|
|
|
#include "model/doc/document.hpp"
|
|
#include "model/world/data.hpp"
|
|
|
|
#ifdef _WIN32
|
|
#include <components/windows.hpp>
|
|
#endif
|
|
|
|
using namespace Fallback;
|
|
|
|
CS::Editor::Editor (int argc, char **argv)
|
|
: mConfigVariables(readConfiguration()), mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr),
|
|
mPid(""), mLock(), mMerge (mDocumentManager),
|
|
mIpcServerName ("org.openmw.OpenCS"), mServer(nullptr), mClientSocket(nullptr)
|
|
{
|
|
std::pair<Files::PathContainer, std::vector<std::string> > config = readConfig();
|
|
|
|
mViewManager = new CSVDoc::ViewManager(mDocumentManager);
|
|
if (argc > 1)
|
|
{
|
|
mFileToLoad = argv[1];
|
|
mDataDirs = config.first;
|
|
}
|
|
|
|
NifOsg::Loader::setShowMarkers(true);
|
|
|
|
mDocumentManager.setFileData(mFsStrict, config.first, config.second);
|
|
|
|
mNewGame.setLocalData (mLocal);
|
|
mFileDialog.setLocalData (mLocal);
|
|
mMerge.setLocalData (mLocal);
|
|
|
|
connect (&mDocumentManager, SIGNAL (documentAdded (CSMDoc::Document *)),
|
|
this, SLOT (documentAdded (CSMDoc::Document *)));
|
|
connect (&mDocumentManager, SIGNAL (documentAboutToBeRemoved (CSMDoc::Document *)),
|
|
this, SLOT (documentAboutToBeRemoved (CSMDoc::Document *)));
|
|
connect (&mDocumentManager, SIGNAL (lastDocumentDeleted()),
|
|
this, SLOT (lastDocumentDeleted()));
|
|
|
|
connect (mViewManager, SIGNAL (newGameRequest ()), this, SLOT (createGame ()));
|
|
connect (mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ()));
|
|
connect (mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ()));
|
|
connect (mViewManager, SIGNAL (editSettingsRequest()), this, SLOT (showSettings ()));
|
|
connect (mViewManager, SIGNAL (mergeDocument (CSMDoc::Document *)), this, SLOT (mergeDocument (CSMDoc::Document *)));
|
|
|
|
connect (&mStartup, SIGNAL (createGame()), this, SLOT (createGame ()));
|
|
connect (&mStartup, SIGNAL (createAddon()), this, SLOT (createAddon ()));
|
|
connect (&mStartup, SIGNAL (loadDocument()), this, SLOT (loadDocument ()));
|
|
connect (&mStartup, SIGNAL (editConfig()), this, SLOT (showSettings ()));
|
|
|
|
connect (&mFileDialog, SIGNAL(signalOpenFiles (const boost::filesystem::path&)),
|
|
this, SLOT(openFiles (const boost::filesystem::path&)));
|
|
|
|
connect (&mFileDialog, SIGNAL(signalCreateNewFile (const boost::filesystem::path&)),
|
|
this, SLOT(createNewFile (const boost::filesystem::path&)));
|
|
connect (&mFileDialog, SIGNAL (rejected()), this, SLOT (cancelFileDialog ()));
|
|
|
|
connect (&mNewGame, SIGNAL (createRequest (const boost::filesystem::path&)),
|
|
this, SLOT (createNewGame (const boost::filesystem::path&)));
|
|
connect (&mNewGame, SIGNAL (cancelCreateGame()), this, SLOT (cancelCreateGame ()));
|
|
}
|
|
|
|
CS::Editor::~Editor ()
|
|
{
|
|
delete mViewManager;
|
|
|
|
mPidFile.close();
|
|
|
|
if(mServer && boost::filesystem::exists(mPid))
|
|
static_cast<void> ( // silence coverity warning
|
|
remove(mPid.string().c_str())); // ignore any error
|
|
}
|
|
|
|
boost::program_options::variables_map CS::Editor::readConfiguration()
|
|
{
|
|
boost::program_options::variables_map variables;
|
|
boost::program_options::options_description desc("Syntax: openmw-cs <options>\nAllowed options");
|
|
|
|
desc.add_options()
|
|
("data", boost::program_options::value<Files::MaybeQuotedPathContainer>()->default_value(Files::MaybeQuotedPathContainer(), "data")->multitoken()->composing())
|
|
("data-local", boost::program_options::value<Files::MaybeQuotedPathContainer::value_type>()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""))
|
|
("fs-strict", boost::program_options::value<bool>()->implicit_value(true)->default_value(false))
|
|
("encoding", boost::program_options::value<std::string>()->default_value("win1252"))
|
|
("resources", boost::program_options::value<Files::MaybeQuotedPath>()->default_value(Files::MaybeQuotedPath(), "resources"))
|
|
("fallback-archive", boost::program_options::value<std::vector<std::string>>()->
|
|
default_value(std::vector<std::string>(), "fallback-archive")->multitoken())
|
|
("fallback", boost::program_options::value<FallbackMap>()->default_value(FallbackMap(), "")
|
|
->multitoken()->composing(), "fallback values")
|
|
("script-blacklist", boost::program_options::value<std::vector<std::string>>()->default_value(std::vector<std::string>(), "")
|
|
->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)
|
|
->default_value(true), "enable script blacklisting");
|
|
Files::ConfigurationManager::addCommonOptions(desc);
|
|
|
|
boost::program_options::notify(variables);
|
|
|
|
mCfgMgr.readConfiguration(variables, desc, false);
|
|
Settings::Manager::load(mCfgMgr, true);
|
|
setupLogging(mCfgMgr.getLogPath().string(), applicationName);
|
|
|
|
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);
|
|
|
|
mEncodingName = variables["encoding"].as<std::string>();
|
|
mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName));
|
|
mFileDialog.setEncoding (QString::fromUtf8(mEncodingName.c_str()));
|
|
|
|
mDocumentManager.setResourceDir (mResources = variables["resources"].as<Files::MaybeQuotedPath>());
|
|
|
|
if (variables["script-blacklist-use"].as<bool>())
|
|
mDocumentManager.setBlacklistedScripts (
|
|
variables["script-blacklist"].as<std::vector<std::string>>());
|
|
|
|
mFsStrict = variables["fs-strict"].as<bool>();
|
|
|
|
Files::PathContainer dataDirs, dataLocal;
|
|
if (!variables["data"].empty()) {
|
|
dataDirs = asPathContainer(variables["data"].as<Files::MaybeQuotedPathContainer>());
|
|
}
|
|
|
|
Files::PathContainer::value_type local(variables["data-local"].as<Files::MaybeQuotedPathContainer::value_type>());
|
|
if (!local.empty())
|
|
{
|
|
boost::filesystem::create_directories(local);
|
|
dataLocal.push_back(local);
|
|
}
|
|
mCfgMgr.filterOutNonExistingPaths(dataDirs);
|
|
mCfgMgr.filterOutNonExistingPaths(dataLocal);
|
|
|
|
if (!dataLocal.empty())
|
|
mLocal = dataLocal[0];
|
|
else
|
|
{
|
|
QMessageBox messageBox;
|
|
messageBox.setWindowTitle (tr ("No local data path available"));
|
|
messageBox.setIcon (QMessageBox::Critical);
|
|
messageBox.setStandardButtons (QMessageBox::Ok);
|
|
messageBox.setText(tr("<br><b>OpenCS is unable to access the local data directory. This may indicate a faulty configuration or a broken install.</b>"));
|
|
messageBox.exec();
|
|
|
|
QApplication::exit (1);
|
|
}
|
|
|
|
dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end());
|
|
|
|
//iterate the data directories and add them to the file dialog for loading
|
|
mFileDialog.addFiles(dataDirs);
|
|
|
|
return std::make_pair (dataDirs, variables["fallback-archive"].as<std::vector<std::string>>());
|
|
}
|
|
|
|
void CS::Editor::createGame()
|
|
{
|
|
mStartup.hide();
|
|
|
|
if (mNewGame.isHidden())
|
|
mNewGame.show();
|
|
|
|
mNewGame.raise();
|
|
mNewGame.activateWindow();
|
|
}
|
|
|
|
void CS::Editor::cancelCreateGame()
|
|
{
|
|
if (!mDocumentManager.isEmpty())
|
|
return;
|
|
|
|
mNewGame.hide();
|
|
|
|
if (mStartup.isHidden())
|
|
mStartup.show();
|
|
|
|
mStartup.raise();
|
|
mStartup.activateWindow();
|
|
}
|
|
|
|
void CS::Editor::createAddon()
|
|
{
|
|
mStartup.hide();
|
|
|
|
mFileDialog.clearFiles();
|
|
readConfig(/*quiet*/true);
|
|
|
|
mFileDialog.showDialog (CSVDoc::ContentAction_New);
|
|
}
|
|
|
|
void CS::Editor::cancelFileDialog()
|
|
{
|
|
if (!mDocumentManager.isEmpty())
|
|
return;
|
|
|
|
mFileDialog.hide();
|
|
|
|
if (mStartup.isHidden())
|
|
mStartup.show();
|
|
|
|
mStartup.raise();
|
|
mStartup.activateWindow();
|
|
}
|
|
|
|
void CS::Editor::loadDocument()
|
|
{
|
|
mStartup.hide();
|
|
|
|
mFileDialog.clearFiles();
|
|
readConfig(/*quiet*/true);
|
|
|
|
mFileDialog.showDialog (CSVDoc::ContentAction_Edit);
|
|
}
|
|
|
|
void CS::Editor::openFiles (const boost::filesystem::path &savePath, const std::vector<boost::filesystem::path> &discoveredFiles)
|
|
{
|
|
std::vector<boost::filesystem::path> files;
|
|
|
|
if(discoveredFiles.empty())
|
|
{
|
|
for (const QString &path : mFileDialog.selectedFilePaths())
|
|
files.emplace_back(path.toUtf8().constData());
|
|
}
|
|
else
|
|
{
|
|
files = discoveredFiles;
|
|
}
|
|
|
|
mDocumentManager.addDocument (files, savePath, false);
|
|
|
|
mFileDialog.hide();
|
|
}
|
|
|
|
void CS::Editor::createNewFile (const boost::filesystem::path &savePath)
|
|
{
|
|
std::vector<boost::filesystem::path> files;
|
|
|
|
for (const QString &path : mFileDialog.selectedFilePaths()) {
|
|
files.emplace_back(path.toUtf8().constData());
|
|
}
|
|
|
|
files.push_back (savePath);
|
|
|
|
mDocumentManager.addDocument (files, savePath, true);
|
|
|
|
mFileDialog.hide();
|
|
}
|
|
|
|
void CS::Editor::createNewGame (const boost::filesystem::path& file)
|
|
{
|
|
std::vector<boost::filesystem::path> files;
|
|
|
|
files.push_back (file);
|
|
|
|
mDocumentManager.addDocument (files, file, true);
|
|
|
|
mNewGame.hide();
|
|
}
|
|
|
|
void CS::Editor::showStartup()
|
|
{
|
|
if(mStartup.isHidden())
|
|
mStartup.show();
|
|
mStartup.raise();
|
|
mStartup.activateWindow();
|
|
}
|
|
|
|
void CS::Editor::showSettings()
|
|
{
|
|
if (mSettings.isHidden())
|
|
mSettings.show();
|
|
|
|
mSettings.move (QCursor::pos());
|
|
mSettings.raise();
|
|
mSettings.activateWindow();
|
|
}
|
|
|
|
bool CS::Editor::makeIPCServer()
|
|
{
|
|
try
|
|
{
|
|
mPid = boost::filesystem::temp_directory_path();
|
|
mPid /= "openmw-cs.pid";
|
|
bool pidExists = boost::filesystem::exists(mPid);
|
|
|
|
mPidFile.open(mPid);
|
|
|
|
mLock = boost::interprocess::file_lock(mPid.string().c_str());
|
|
if(!mLock.try_lock())
|
|
{
|
|
Log(Debug::Error) << "Error: OpenMW-CS is already running.";
|
|
return false;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
mPidFile << GetCurrentProcessId() << std::endl;
|
|
#else
|
|
mPidFile << getpid() << std::endl;
|
|
#endif
|
|
|
|
mServer = new QLocalServer(this);
|
|
|
|
if(pidExists)
|
|
{
|
|
// hack to get the temp directory path
|
|
mServer->listen("dummy");
|
|
QString fullPath = mServer->fullServerName();
|
|
mServer->close();
|
|
fullPath.remove(QRegExp("dummy$"));
|
|
fullPath += mIpcServerName;
|
|
if(boost::filesystem::exists(fullPath.toUtf8().constData()))
|
|
{
|
|
// TODO: compare pid of the current process with that in the file
|
|
Log(Debug::Info) << "Detected unclean shutdown.";
|
|
// delete the stale file
|
|
if(remove(fullPath.toUtf8().constData()))
|
|
Log(Debug::Error) << "Error: can not remove stale connection file.";
|
|
}
|
|
}
|
|
}
|
|
|
|
catch(const std::exception& e)
|
|
{
|
|
Log(Debug::Error) << "Error: " << e.what();
|
|
return false;
|
|
}
|
|
|
|
if(mServer->listen(mIpcServerName))
|
|
{
|
|
connect(mServer, SIGNAL(newConnection()), this, SLOT(showStartup()));
|
|
return true;
|
|
}
|
|
|
|
mServer->close();
|
|
mServer = nullptr;
|
|
return false;
|
|
}
|
|
|
|
void CS::Editor::connectToIPCServer()
|
|
{
|
|
mClientSocket = new QLocalSocket(this);
|
|
mClientSocket->connectToServer(mIpcServerName);
|
|
mClientSocket->close();
|
|
}
|
|
|
|
int CS::Editor::run()
|
|
{
|
|
if (mLocal.empty())
|
|
return 1;
|
|
|
|
Misc::Rng::init();
|
|
|
|
QApplication::setQuitOnLastWindowClosed(true);
|
|
|
|
if (mFileToLoad.empty())
|
|
{
|
|
mStartup.show();
|
|
}
|
|
else
|
|
{
|
|
ESM::ESMReader fileReader;
|
|
ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncodingName));
|
|
fileReader.setEncoder(&encoder);
|
|
fileReader.open(mFileToLoad.string());
|
|
|
|
std::vector<boost::filesystem::path> discoveredFiles;
|
|
|
|
for (std::vector<ESM::Header::MasterData>::const_iterator itemIter = fileReader.getGameFiles().begin();
|
|
itemIter != fileReader.getGameFiles().end(); ++itemIter)
|
|
{
|
|
for (Files::PathContainer::const_iterator pathIter = mDataDirs.begin();
|
|
pathIter != mDataDirs.end(); ++pathIter)
|
|
{
|
|
const boost::filesystem::path masterPath = *pathIter / itemIter->name;
|
|
if (boost::filesystem::exists(masterPath))
|
|
{
|
|
discoveredFiles.push_back(masterPath);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
discoveredFiles.push_back(mFileToLoad);
|
|
|
|
QString extension = QString::fromStdString(mFileToLoad.extension().string()).toLower();
|
|
if (extension == ".esm")
|
|
{
|
|
mFileToLoad.replace_extension(".omwgame");
|
|
mDocumentManager.addDocument(discoveredFiles, mFileToLoad, false);
|
|
}
|
|
else if (extension == ".esp")
|
|
{
|
|
mFileToLoad.replace_extension(".omwaddon");
|
|
mDocumentManager.addDocument(discoveredFiles, mFileToLoad, false);
|
|
}
|
|
else
|
|
{
|
|
openFiles(mFileToLoad, discoveredFiles);
|
|
}
|
|
}
|
|
|
|
return QApplication::exec();
|
|
}
|
|
|
|
void CS::Editor::documentAdded (CSMDoc::Document *document)
|
|
{
|
|
mViewManager->addView (document);
|
|
}
|
|
|
|
void CS::Editor::documentAboutToBeRemoved (CSMDoc::Document *document)
|
|
{
|
|
if (mMerge.getDocument()==document)
|
|
mMerge.cancel();
|
|
}
|
|
|
|
void CS::Editor::lastDocumentDeleted()
|
|
{
|
|
QApplication::quit();
|
|
}
|
|
|
|
void CS::Editor::mergeDocument (CSMDoc::Document *document)
|
|
{
|
|
mMerge.configure (document);
|
|
mMerge.show();
|
|
mMerge.raise();
|
|
mMerge.activateWindow();
|
|
}
|