From 7de78b6697b60ac20add32b5b9140b04da973be8 Mon Sep 17 00:00:00 2001 From: Silarn Date: Sun, 27 Jan 2019 03:43:18 -0600 Subject: Several Nexus API-related changes * Added 30-second ping so Nexus doesn't close our connection * Set an overall 5 minute timeout before we give up listening for auth * Fixed some old signal and slot connections * Added a button to revoke the current API authorization * Updated bulk endorsement function with new API changes --- src/main.cpp | 1480 ++++++++++++------------ src/mainwindow.cpp | 37 +- src/mainwindow.h | 1316 ++++++++++----------- src/nxmaccessmanager.cpp | 2 +- src/organizer_en.ts | 2819 ++++++++++++++++++++++++++------------------- src/organizercore.cpp | 4 +- src/settings.cpp | 8 + src/settings.h | 1 + src/settingsdialog.cpp | 48 +- src/settingsdialog.h | 10 +- src/settingsdialog.ui | 2837 +++++++++++++++++++++++----------------------- 11 files changed, 4554 insertions(+), 4008 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 631ec5b5..a8942b1a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,740 +1,740 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - - -#ifdef LEAK_CHECK_WITH_VLD -#include -#include -#endif // LEAK_CHECK_WITH_VLD - -#define WIN32_LEAN_AND_MEAN -#include -#include - -#include -#include -#include -#include "mainwindow.h" -#include -#include "modlist.h" -#include "profile.h" -#include "spawn.h" -#include "executableslist.h" -#include "singleinstance.h" -#include "utility.h" -#include "helper.h" -#include "logbuffer.h" -#include "selectiondialog.h" -#include "moapplication.h" -#include "tutorialmanager.h" -#include "nxmaccessmanager.h" -#include "instancemanager.h" -#include "moshortcut.h" -#include "organizercore.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include -#include - - -#pragma comment(linker, "/manifestDependency:\"name='dlls' processorArchitecture='x86' version='1.0.0.0' type='win32' \"") - - -using namespace MOBase; -using namespace MOShared; - -bool createAndMakeWritable(const std::wstring &subPath) { - QString const dataPath = qApp->property("dataPath").toString(); - QString fullPath = dataPath + "/" + QString::fromStdWString(subPath); - - if (!QDir(fullPath).exists() && !QDir().mkdir(fullPath)) { - QMessageBox::critical(nullptr, QObject::tr("Error"), - QObject::tr("Failed to create \"%1\". Your user " - "account probably lacks permission.") - .arg(fullPath)); - return false; - } else { - return true; - } -} - -bool bootstrap() -{ - // remove the temporary backup directory in case we're restarting after an update - QString backupDirectory = qApp->applicationDirPath() + "/update_backup"; - if (QDir(backupDirectory).exists()) { - shellDelete(QStringList(backupDirectory)); - } - - // cycle logfile - removeOldFiles(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()), - "usvfs*.log", 5, QDir::Name); - - if (!createAndMakeWritable(AppConfig::logPath())) { - return false; - } - - return true; -} - -LPTOP_LEVEL_EXCEPTION_FILTER prevUnhandledExceptionFilter = nullptr; - -static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionPtrs) -{ - const std::wstring& dumpPath = OrganizerCore::crashDumpsPath(); - int dumpRes = - CreateMiniDump(exceptionPtrs, OrganizerCore::getGlobalCrashDumpsType(), dumpPath.c_str()); - if (!dumpRes) - qCritical("ModOrganizer has crashed, crash dump created."); - else - qCritical("ModOrganizer has crashed, CreateMiniDump failed (%d, error %lu).", dumpRes, GetLastError()); - - if (prevUnhandledExceptionFilter) - return prevUnhandledExceptionFilter(exceptionPtrs); - else - return EXCEPTION_CONTINUE_SEARCH; -} - -// Parses the first parseArgCount arguments of the current process command line and returns -// them in parsedArgs, the rest of the command line is returned untouched. -LPCWSTR UntouchedCommandLineArguments(int parseArgCount, std::vector& parsedArgs) -{ - LPCWSTR cmd = GetCommandLineW(); - LPCWSTR arg = nullptr; // to skip executable name - for (; parseArgCount >= 0 && *cmd; ++cmd) - { - if (*cmd == '"') { - int escaped = 0; - for (++cmd; *cmd && (*cmd != '"' || escaped % 2 != 0); ++cmd) - escaped = *cmd == '\\' ? escaped + 1 : 0; - } - if (*cmd == ' ') { - if (arg) - if (cmd-1 > arg && *arg == '"' && *(cmd-1) == '"') - parsedArgs.push_back(std::wstring(arg+1, cmd-1)); - else - parsedArgs.push_back(std::wstring(arg, cmd)); - arg = cmd + 1; - --parseArgCount; - } - } - return cmd; -} - -static int SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine) { - PROCESS_INFORMATION pi{ 0 }; - STARTUPINFO si{ 0 }; - si.cb = sizeof(si); - std::wstring commandLineCopy = commandLine; - - if (!CreateProcessW(NULL, &commandLineCopy[0], NULL, NULL, FALSE, 0, NULL, workingDirectory, &si, &pi)) { - // A bit of a problem where to log the error message here, at least this way you can get the message - // using a either DebugView or a live debugger: - std::wostringstream ost; - ost << L"CreateProcess failed: " << commandLine << ", " << GetLastError(); - OutputDebugStringW(ost.str().c_str()); - return -1; - } - - WaitForSingleObject(pi.hProcess, INFINITE); - - DWORD exitCode = (DWORD)-1; - ::GetExitCodeProcess(pi.hProcess, &exitCode); - CloseHandle(pi.hThread); - CloseHandle(pi.hProcess); - return static_cast(exitCode); -} - -static DWORD WaitForProcess() { - -} - -static bool HaveWriteAccess(const std::wstring &path) -{ - bool writable = false; - - const static SECURITY_INFORMATION requestedFileInformation = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION; - - DWORD length = 0; - if (!::GetFileSecurityW(path.c_str(), requestedFileInformation, nullptr, 0UL, &length) - && (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)) { - std::string tempBuffer; - tempBuffer.reserve(length); - PSECURITY_DESCRIPTOR security = (PSECURITY_DESCRIPTOR)tempBuffer.data(); - if (security - && ::GetFileSecurity(path.c_str(), requestedFileInformation, security, length, &length)) { - HANDLE token = nullptr; - const static DWORD tokenDesiredAccess = TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ; - if (!::OpenThreadToken(::GetCurrentThread(), tokenDesiredAccess, TRUE, &token)) { - if (!::OpenProcessToken(::GetCurrentProcess(), tokenDesiredAccess, &token)) { - throw std::runtime_error("Unable to get any thread or process token"); - } - } - - HANDLE impersonatedToken = nullptr; - if (::DuplicateToken(token, SecurityImpersonation, &impersonatedToken)) { - GENERIC_MAPPING mapping = { 0xFFFFFFFF }; - mapping.GenericRead = FILE_GENERIC_READ; - mapping.GenericWrite = FILE_GENERIC_WRITE; - mapping.GenericExecute = FILE_GENERIC_EXECUTE; - mapping.GenericAll = FILE_ALL_ACCESS; - - DWORD genericAccessRights = FILE_GENERIC_WRITE; - ::MapGenericMask(&genericAccessRights, &mapping); - - PRIVILEGE_SET privileges = { 0 }; - DWORD grantedAccess = 0; - DWORD privilegesLength = sizeof(privileges); - BOOL result = 0; - if (::AccessCheck(security, impersonatedToken, genericAccessRights, &mapping, &privileges, &privilegesLength, &grantedAccess, &result)) { - writable = result != 0; - } - ::CloseHandle(impersonatedToken); - } - - ::CloseHandle(token); - } - } - return writable; -} - - -QString determineProfile(QStringList &arguments, const QSettings &settings) -{ - QString selectedProfileName = QString::fromUtf8(settings.value("selected_profile", "").toByteArray()); - { // see if there is a profile on the command line - int profileIndex = arguments.indexOf("-p", 1); - if ((profileIndex != -1) && (profileIndex < arguments.size() - 1)) { - qDebug("profile overwritten on command line"); - selectedProfileName = arguments.at(profileIndex + 1); - } - arguments.removeAt(profileIndex); - arguments.removeAt(profileIndex); - } - if (selectedProfileName.isEmpty()) { - qDebug("no configured profile"); - selectedProfileName = "Default"; - } else { - qDebug("configured profile: %s", qUtf8Printable(selectedProfileName)); - } - - return selectedProfileName; -} - -MOBase::IPluginGame *selectGame(QSettings &settings, QDir const &gamePath, MOBase::IPluginGame *game) -{ - settings.setValue("gameName", game->gameName()); - //Sadly, hookdll needs gamePath in order to run. So following code block is - //commented out - /*if (gamePath == game->gameDirectory()) { - settings.remove("gamePath"); - } else*/ { - QString gameDir = gamePath.absolutePath(); - game->setGamePath(gameDir); - settings.setValue("gamePath", QDir::toNativeSeparators(gameDir).toUtf8().constData()); - } - return game; //Woot -} - - -MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &settings, PluginContainer const &plugins) -{ - //Determine what game we are running where. Be very paranoid in case the - //user has done something odd. - //If the game name has been set up, use that. - QString gameName = settings.value("gameName", "").toString(); - if (!gameName.isEmpty()) { - MOBase::IPluginGame *game = plugins.managedGame(gameName); - if (game == nullptr) { - reportError(QObject::tr("Plugin to handle %1 no longer installed").arg(gameName)); - return nullptr; - } - QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray()); - if (gamePath == "") { - gamePath = game->gameDirectory().absolutePath(); - } - QDir gameDir(gamePath); - if (game->looksValid(gameDir)) { - return selectGame(settings, gameDir, game); - } - } - - //gameName wasn't set, or otherwise can't be found. Try looking through all - //the plugins using the gamePath - QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray()); - if (!gamePath.isEmpty()) { - QDir gameDir(gamePath); - //Look to see if one of the installed games binary file exists in the current - //game directory. - for (IPluginGame * const game : plugins.plugins()) { - if (game->looksValid(gameDir)) { - return selectGame(settings, gameDir, game); - } - } - } - - //The following code would try to determine the right game to mange but it would usually find the wrong one - //so it was commented out. - /* - //OK, we are in a new setup or existing info is useless. - //See if MO has been installed inside a game directory - for (IPluginGame * const game : plugins.plugins()) { - if (game->isInstalled() && moPath.startsWith(game->gameDirectory().absolutePath())) { - //Found it. - return selectGame(settings, game->gameDirectory(), game); - } - } - - //Try walking up the directory tree to see if MO has been installed inside a game - { - QDir gameDir(moPath); - do { - //Look to see if one of the installed games binary file exists in the current - //directory. - for (IPluginGame * const game : plugins.plugins()) { - if (game->looksValid(gameDir)) { - return selectGame(settings, gameDir, game); - } - } - //OK, chop off the last directory and try again - } while (gameDir.cdUp()); - } - */ - - //Then try a selection dialogue. - if (!gamePath.isEmpty() || !gameName.isEmpty()) { - reportError(QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\"."). - arg(gameName).arg(gamePath)); - } - - SelectionDialog selection(QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32)); - - for (IPluginGame *game : plugins.plugins()) { - if (game->isInstalled()) { - QString path = game->gameDirectory().absolutePath(); - selection.addChoice(game->gameIcon(), game->gameName(), path, QVariant::fromValue(game)); - } - } - - selection.addChoice(QString("Browse..."), QString(), QVariant::fromValue(static_cast(nullptr))); - - while (selection.exec() != QDialog::Rejected) { - IPluginGame * game = selection.getChoiceData().value(); - if (game != nullptr) { - return selectGame(settings, game->gameDirectory(), game); - } - - gamePath = QFileDialog::getExistingDirectory( - nullptr, QObject::tr("Please select the game to manage"), QString(), - QFileDialog::ShowDirsOnly); - - if (!gamePath.isEmpty()) { - QDir gameDir(gamePath); - QList possibleGames; - for (IPluginGame * const game : plugins.plugins()) { - if (game->looksValid(gameDir)) { - possibleGames.append(game); - //return selectGame(settings, gameDir, game); - } - } - if (possibleGames.count() > 1) { - SelectionDialog browseSelection(QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32)); - for (IPluginGame *game : possibleGames) { - QString path = game->gameDirectory().absolutePath(); - browseSelection.addChoice(game->gameIcon(), game->gameName(), path, QVariant::fromValue(game)); - } - if (browseSelection.exec() == QDialog::Accepted) { - return selectGame(settings, gameDir, browseSelection.getChoiceData().value()); - } else { - reportError(QObject::tr("Canceled finding game in \"%1\".").arg(gamePath)); - } - } else if(possibleGames.count() == 1) { - return selectGame(settings, gameDir, possibleGames[0]); - } else { - reportError(QObject::tr("No game identified in \"%1\". The directory is required to contain " - "the game binary.").arg(gamePath)); - } - } - } - - return nullptr; -} - - -// extend path to include dll directory so plugins don't need a manifest -// (using AddDllDirectory would be an alternative to this but it seems fairly -// complicated esp. -// since it isn't easily accessible on Windows < 8 -// SetDllDirectory replaces other search directories and this seems to -// propagate to child processes) -void setupPath() -{ - static const int BUFSIZE = 4096; - - qDebug("MO at: %s", qUtf8Printable(QDir::toNativeSeparators( - QCoreApplication::applicationDirPath()))); - - QCoreApplication::setLibraryPaths(QStringList(QCoreApplication::applicationDirPath() + "/dlls") + QCoreApplication::libraryPaths()); - - boost::scoped_array oldPath(new TCHAR[BUFSIZE]); - DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE); - if (offset > BUFSIZE) { - oldPath.reset(new TCHAR[offset]); - ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset); - } - - std::wstring newPath(ToWString(QDir::toNativeSeparators( - QCoreApplication::applicationDirPath())) + L"\\dlls"); - newPath += L";"; - newPath += oldPath.get(); - - ::SetEnvironmentVariableW(L"PATH", newPath.c_str()); -} - -static void preloadSsl() -{ - QString appPath = QDir::toNativeSeparators(QCoreApplication::applicationDirPath()); - if (GetModuleHandleA("libeay32.dll")) - qWarning("libeay32.dll already loaded?!"); - else { - QString libeay32 = appPath + "\\libeay32.dll"; - if (!QFile::exists(libeay32)) - qWarning("libeay32.dll not found: %s", qUtf8Printable(libeay32)); - else if (!LoadLibraryW(libeay32.toStdWString().c_str())) - qWarning("failed to load: %s, %d", qUtf8Printable(libeay32), GetLastError()); - } - if (GetModuleHandleA("ssleay32.dll")) - qWarning("ssleay32.dll already loaded?!"); - else { - QString ssleay32 = appPath + "\\ssleay32.dll"; - if (!QFile::exists(ssleay32)) - qWarning("ssleay32.dll not found: %s", qUtf8Printable(ssleay32)); - else if (!LoadLibraryW(ssleay32.toStdWString().c_str())) - qWarning("failed to load: %s, %d", qUtf8Printable(ssleay32), GetLastError()); - } -} - -static QString getVersionDisplayString() -{ - return createVersionInfo().displayString(3); -} - -int runApplication(MOApplication &application, SingleInstance &instance, - const QString &splashPath) -{ - - qDebug("Starting Mod Organizer version %s revision %s", qUtf8Printable(getVersionDisplayString()), -#if defined(HGID) - HGID -#elif defined(GITID) - GITID -#else - "unknown" -#endif - ); - -#if !defined(QT_NO_SSL) - preloadSsl(); - qDebug("ssl support: %d", QSslSocket::supportsSsl()); -#else - qDebug("non-ssl build"); -#endif - - QString dataPath = application.property("dataPath").toString(); - qDebug("data path: %s", qUtf8Printable(dataPath)); - - if (!bootstrap()) { - reportError("failed to set up data paths"); - return 1; - } - - QWindowsWindowFunctions::setWindowActivationBehavior(QWindowsWindowFunctions::AlwaysActivateWindow); - - QStringList arguments = application.arguments(); - - try { - qDebug("Working directory: %s", qUtf8Printable(QDir::toNativeSeparators(QDir::currentPath()))); - - QSettings settings(dataPath + "/" - + QString::fromStdWString(AppConfig::iniFileName()), - QSettings::IniFormat); - - // global crashDumpType sits in OrganizerCore to make a bit less ugly to update it when the settings are changed during runtime - OrganizerCore::setGlobalCrashDumpsType(settings.value("Settings/crash_dumps_type", static_cast(CrashDumpsType::Mini)).toInt()); - - qDebug("Loaded settings:"); - settings.beginGroup("Settings"); - for (auto k : settings.allKeys()) - if (!k.contains("username") && !k.contains("password")) - qDebug(" %s=%s", k.toUtf8().data(), settings.value(k).toString().toUtf8().data()); - settings.endGroup(); - - - qDebug("initializing core"); - OrganizerCore organizer(settings); - if (!organizer.bootstrap()) { - reportError("failed to set up data paths"); - return 1; - } - qDebug("initialize plugins"); - PluginContainer pluginContainer(&organizer); - pluginContainer.loadPlugins(); - - MOBase::IPluginGame *game = determineCurrentGame( - application.applicationDirPath(), settings, pluginContainer); - if (game == nullptr) { - return 1; - } - if (splashPath.startsWith(':')) { - // currently using MO splash, see if the plugin contains one - QString pluginSplash - = QString(":/%1/splash").arg(game->gameShortName()); - QImage image(pluginSplash); - if (!image.isNull()) { - image.save(dataPath + "/splash.png"); - } else { - qDebug("no plugin splash"); - } - } - - organizer.setManagedGame(game); - organizer.createDefaultProfile(); - - if (!settings.contains("game_edition")) { - QStringList editions = game->gameVariants(); - if (editions.size() > 1) { - SelectionDialog selection( - QObject::tr("Please select the game edition you have (MO can't " - "start the game correctly if this is set " - "incorrectly!)"), - nullptr); - selection.setWindowFlag(Qt::WindowStaysOnTopHint, true); - int index = 0; - for (const QString &edition : editions) { - selection.addChoice(edition, "", index++); - } - if (selection.exec() == QDialog::Rejected) { - return 1; - } else { - settings.setValue("game_edition", selection.getChoiceString()); - } - } - } - game->setGameVariant(settings.value("game_edition").toString()); - - qDebug("managing game at %s", qUtf8Printable(QDir::toNativeSeparators( - game->gameDirectory().absolutePath()))); - - organizer.updateExecutablesList(settings); - - QString selectedProfileName = determineProfile(arguments, settings); - organizer.setCurrentProfile(selectedProfileName); - - // if we have a command line parameter, it is either a nxm link or - // a binary to start - if (arguments.size() > 1) { - if (MOShortcut shortcut{ arguments.at(1) }) { - if (shortcut.hasExecutable()) { - try { - organizer.runShortcut(shortcut); - return 0; - } - catch (const std::exception &e) { - reportError( - QObject::tr("failed to start shortcut: %1").arg(e.what())); - return 1; - } - } - } - else if (OrganizerCore::isNxmLink(arguments.at(1))) { - qDebug("starting download from command line: %s", - qUtf8Printable(arguments.at(1))); - organizer.externalMessage(arguments.at(1)); - } - else { - QString exeName = arguments.at(1); - qDebug("starting %s from command line", qUtf8Printable(exeName)); - arguments.removeFirst(); // remove application name (ModOrganizer.exe) - arguments.removeFirst(); // remove binary name - // pass the remaining parameters to the binary - try { - organizer.startApplication(exeName, arguments, QString(), QString()); - return 0; - } - catch (const std::exception &e) { - reportError( - QObject::tr("failed to start application: %1").arg(e.what())); - return 1; - } - } - } - - QPixmap pixmap(splashPath); - QSplashScreen splash(pixmap); - splash.show(); - splash.activateWindow(); - - QString apiKey; - if (organizer.settings().getNexusApiKey(apiKey)) { - NexusInterface::instance(&pluginContainer)->getAccessManager()->apiCheck(apiKey); - } - - qDebug("initializing tutorials"); - TutorialManager::init( - qApp->applicationDirPath() + "/" - + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", - &organizer); - - if (!application.setStyleFile(settings.value("Settings/style", "").toString())) { - // disable invalid stylesheet - settings.setValue("Settings/style", ""); - } - - int res = 1; - { // scope to control lifetime of mainwindow - // set up main window and its data structures - MainWindow mainWindow(settings, organizer, pluginContainer); - - QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, - SLOT(setStyleFile(QString))); - QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer, - SLOT(externalMessage(QString))); - - mainWindow.processUpdates(); - mainWindow.readSettings(); - - qDebug("displaying main window"); - mainWindow.show(); - mainWindow.activateWindow(); - - splash.finish(&mainWindow); - return application.exec(); - } - } catch (const std::exception &e) { - reportError(e.what()); - return 1; - } -} - - -int main(int argc, char *argv[]) -{ - //Should allow for better scaling of ui with higher resolution displays - QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - - if (argc >= 4) { - std::vector arg; - auto args = UntouchedCommandLineArguments(2, arg); - if (arg[0] == L"launch") - return SpawnWaitProcess(arg[1].c_str(), args); - } - - MOApplication application(argc, argv); - QStringList arguments = application.arguments(); - - setupPath(); - - bool forcePrimary = false; - if (arguments.contains("update")) { - arguments.removeAll("update"); - forcePrimary = true; - } - - MOShortcut moshortcut{ arguments.size() > 1 ? arguments.at(1) : "" }; - - SingleInstance instance(forcePrimary); - if (!instance.primaryInstance()) { - if (moshortcut || - arguments.size() > 1 && OrganizerCore::isNxmLink(arguments.at(1))) - { - qDebug("not primary instance, sending shortcut/download message"); - instance.sendMessage(arguments.at(1)); - return 0; - } else if (arguments.size() == 1) { - QMessageBox::information( - nullptr, QObject::tr("Mod Organizer"), - QObject::tr("An instance of Mod Organizer is already running")); - return 0; - } - } // we continue for the primary instance OR if MO was called with parameters - - do { - QString dataPath; - - try { - InstanceManager& instanceManager = InstanceManager::instance(); - if (moshortcut && moshortcut.hasInstance()) - instanceManager.overrideInstance(moshortcut.instance()); - dataPath = instanceManager.determineDataPath(); - } catch (const std::exception &e) { - if (strcmp(e.what(),"Canceled")) - QMessageBox::critical(nullptr, QObject::tr("Failed to set up instance"), e.what()); - return 1; - } - application.setProperty("dataPath", dataPath); - - // initialize dump collection only after "dataPath" since the crashes are stored under it - prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); - - LogBuffer::init(1000, QtDebugMsg, qApp->property("dataPath").toString() + "/logs/mo_interface.log"); - - QString splash = dataPath + "/splash.png"; - if (!QFile::exists(dataPath + "/splash.png")) { - splash = ":/MO/gui/splash"; - } - - int result = runApplication(application, instance, splash); - if (result != INT_MAX) { - return result; - } - argc = 1; - moshortcut = MOShortcut(""); - } while (true); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + + +#ifdef LEAK_CHECK_WITH_VLD +#include +#include +#endif // LEAK_CHECK_WITH_VLD + +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include +#include +#include +#include "mainwindow.h" +#include +#include "modlist.h" +#include "profile.h" +#include "spawn.h" +#include "executableslist.h" +#include "singleinstance.h" +#include "utility.h" +#include "helper.h" +#include "logbuffer.h" +#include "selectiondialog.h" +#include "moapplication.h" +#include "tutorialmanager.h" +#include "nxmaccessmanager.h" +#include "instancemanager.h" +#include "moshortcut.h" +#include "organizercore.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + + +#pragma comment(linker, "/manifestDependency:\"name='dlls' processorArchitecture='x86' version='1.0.0.0' type='win32' \"") + + +using namespace MOBase; +using namespace MOShared; + +bool createAndMakeWritable(const std::wstring &subPath) { + QString const dataPath = qApp->property("dataPath").toString(); + QString fullPath = dataPath + "/" + QString::fromStdWString(subPath); + + if (!QDir(fullPath).exists() && !QDir().mkdir(fullPath)) { + QMessageBox::critical(nullptr, QObject::tr("Error"), + QObject::tr("Failed to create \"%1\". Your user " + "account probably lacks permission.") + .arg(fullPath)); + return false; + } else { + return true; + } +} + +bool bootstrap() +{ + // remove the temporary backup directory in case we're restarting after an update + QString backupDirectory = qApp->applicationDirPath() + "/update_backup"; + if (QDir(backupDirectory).exists()) { + shellDelete(QStringList(backupDirectory)); + } + + // cycle logfile + removeOldFiles(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()), + "usvfs*.log", 5, QDir::Name); + + if (!createAndMakeWritable(AppConfig::logPath())) { + return false; + } + + return true; +} + +LPTOP_LEVEL_EXCEPTION_FILTER prevUnhandledExceptionFilter = nullptr; + +static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionPtrs) +{ + const std::wstring& dumpPath = OrganizerCore::crashDumpsPath(); + int dumpRes = + CreateMiniDump(exceptionPtrs, OrganizerCore::getGlobalCrashDumpsType(), dumpPath.c_str()); + if (!dumpRes) + qCritical("ModOrganizer has crashed, crash dump created."); + else + qCritical("ModOrganizer has crashed, CreateMiniDump failed (%d, error %lu).", dumpRes, GetLastError()); + + if (prevUnhandledExceptionFilter) + return prevUnhandledExceptionFilter(exceptionPtrs); + else + return EXCEPTION_CONTINUE_SEARCH; +} + +// Parses the first parseArgCount arguments of the current process command line and returns +// them in parsedArgs, the rest of the command line is returned untouched. +LPCWSTR UntouchedCommandLineArguments(int parseArgCount, std::vector& parsedArgs) +{ + LPCWSTR cmd = GetCommandLineW(); + LPCWSTR arg = nullptr; // to skip executable name + for (; parseArgCount >= 0 && *cmd; ++cmd) + { + if (*cmd == '"') { + int escaped = 0; + for (++cmd; *cmd && (*cmd != '"' || escaped % 2 != 0); ++cmd) + escaped = *cmd == '\\' ? escaped + 1 : 0; + } + if (*cmd == ' ') { + if (arg) + if (cmd-1 > arg && *arg == '"' && *(cmd-1) == '"') + parsedArgs.push_back(std::wstring(arg+1, cmd-1)); + else + parsedArgs.push_back(std::wstring(arg, cmd)); + arg = cmd + 1; + --parseArgCount; + } + } + return cmd; +} + +static int SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine) { + PROCESS_INFORMATION pi{ 0 }; + STARTUPINFO si{ 0 }; + si.cb = sizeof(si); + std::wstring commandLineCopy = commandLine; + + if (!CreateProcessW(NULL, &commandLineCopy[0], NULL, NULL, FALSE, 0, NULL, workingDirectory, &si, &pi)) { + // A bit of a problem where to log the error message here, at least this way you can get the message + // using a either DebugView or a live debugger: + std::wostringstream ost; + ost << L"CreateProcess failed: " << commandLine << ", " << GetLastError(); + OutputDebugStringW(ost.str().c_str()); + return -1; + } + + WaitForSingleObject(pi.hProcess, INFINITE); + + DWORD exitCode = (DWORD)-1; + ::GetExitCodeProcess(pi.hProcess, &exitCode); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + return static_cast(exitCode); +} + +static DWORD WaitForProcess() { + +} + +static bool HaveWriteAccess(const std::wstring &path) +{ + bool writable = false; + + const static SECURITY_INFORMATION requestedFileInformation = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION; + + DWORD length = 0; + if (!::GetFileSecurityW(path.c_str(), requestedFileInformation, nullptr, 0UL, &length) + && (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)) { + std::string tempBuffer; + tempBuffer.reserve(length); + PSECURITY_DESCRIPTOR security = (PSECURITY_DESCRIPTOR)tempBuffer.data(); + if (security + && ::GetFileSecurity(path.c_str(), requestedFileInformation, security, length, &length)) { + HANDLE token = nullptr; + const static DWORD tokenDesiredAccess = TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ; + if (!::OpenThreadToken(::GetCurrentThread(), tokenDesiredAccess, TRUE, &token)) { + if (!::OpenProcessToken(::GetCurrentProcess(), tokenDesiredAccess, &token)) { + throw std::runtime_error("Unable to get any thread or process token"); + } + } + + HANDLE impersonatedToken = nullptr; + if (::DuplicateToken(token, SecurityImpersonation, &impersonatedToken)) { + GENERIC_MAPPING mapping = { 0xFFFFFFFF }; + mapping.GenericRead = FILE_GENERIC_READ; + mapping.GenericWrite = FILE_GENERIC_WRITE; + mapping.GenericExecute = FILE_GENERIC_EXECUTE; + mapping.GenericAll = FILE_ALL_ACCESS; + + DWORD genericAccessRights = FILE_GENERIC_WRITE; + ::MapGenericMask(&genericAccessRights, &mapping); + + PRIVILEGE_SET privileges = { 0 }; + DWORD grantedAccess = 0; + DWORD privilegesLength = sizeof(privileges); + BOOL result = 0; + if (::AccessCheck(security, impersonatedToken, genericAccessRights, &mapping, &privileges, &privilegesLength, &grantedAccess, &result)) { + writable = result != 0; + } + ::CloseHandle(impersonatedToken); + } + + ::CloseHandle(token); + } + } + return writable; +} + + +QString determineProfile(QStringList &arguments, const QSettings &settings) +{ + QString selectedProfileName = QString::fromUtf8(settings.value("selected_profile", "").toByteArray()); + { // see if there is a profile on the command line + int profileIndex = arguments.indexOf("-p", 1); + if ((profileIndex != -1) && (profileIndex < arguments.size() - 1)) { + qDebug("profile overwritten on command line"); + selectedProfileName = arguments.at(profileIndex + 1); + } + arguments.removeAt(profileIndex); + arguments.removeAt(profileIndex); + } + if (selectedProfileName.isEmpty()) { + qDebug("no configured profile"); + selectedProfileName = "Default"; + } else { + qDebug("configured profile: %s", qUtf8Printable(selectedProfileName)); + } + + return selectedProfileName; +} + +MOBase::IPluginGame *selectGame(QSettings &settings, QDir const &gamePath, MOBase::IPluginGame *game) +{ + settings.setValue("gameName", game->gameName()); + //Sadly, hookdll needs gamePath in order to run. So following code block is + //commented out + /*if (gamePath == game->gameDirectory()) { + settings.remove("gamePath"); + } else*/ { + QString gameDir = gamePath.absolutePath(); + game->setGamePath(gameDir); + settings.setValue("gamePath", QDir::toNativeSeparators(gameDir).toUtf8().constData()); + } + return game; //Woot +} + + +MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &settings, PluginContainer const &plugins) +{ + //Determine what game we are running where. Be very paranoid in case the + //user has done something odd. + //If the game name has been set up, use that. + QString gameName = settings.value("gameName", "").toString(); + if (!gameName.isEmpty()) { + MOBase::IPluginGame *game = plugins.managedGame(gameName); + if (game == nullptr) { + reportError(QObject::tr("Plugin to handle %1 no longer installed").arg(gameName)); + return nullptr; + } + QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray()); + if (gamePath == "") { + gamePath = game->gameDirectory().absolutePath(); + } + QDir gameDir(gamePath); + if (game->looksValid(gameDir)) { + return selectGame(settings, gameDir, game); + } + } + + //gameName wasn't set, or otherwise can't be found. Try looking through all + //the plugins using the gamePath + QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray()); + if (!gamePath.isEmpty()) { + QDir gameDir(gamePath); + //Look to see if one of the installed games binary file exists in the current + //game directory. + for (IPluginGame * const game : plugins.plugins()) { + if (game->looksValid(gameDir)) { + return selectGame(settings, gameDir, game); + } + } + } + + //The following code would try to determine the right game to mange but it would usually find the wrong one + //so it was commented out. + /* + //OK, we are in a new setup or existing info is useless. + //See if MO has been installed inside a game directory + for (IPluginGame * const game : plugins.plugins()) { + if (game->isInstalled() && moPath.startsWith(game->gameDirectory().absolutePath())) { + //Found it. + return selectGame(settings, game->gameDirectory(), game); + } + } + + //Try walking up the directory tree to see if MO has been installed inside a game + { + QDir gameDir(moPath); + do { + //Look to see if one of the installed games binary file exists in the current + //directory. + for (IPluginGame * const game : plugins.plugins()) { + if (game->looksValid(gameDir)) { + return selectGame(settings, gameDir, game); + } + } + //OK, chop off the last directory and try again + } while (gameDir.cdUp()); + } + */ + + //Then try a selection dialogue. + if (!gamePath.isEmpty() || !gameName.isEmpty()) { + reportError(QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\"."). + arg(gameName).arg(gamePath)); + } + + SelectionDialog selection(QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32)); + + for (IPluginGame *game : plugins.plugins()) { + if (game->isInstalled()) { + QString path = game->gameDirectory().absolutePath(); + selection.addChoice(game->gameIcon(), game->gameName(), path, QVariant::fromValue(game)); + } + } + + selection.addChoice(QString("Browse..."), QString(), QVariant::fromValue(static_cast(nullptr))); + + while (selection.exec() != QDialog::Rejected) { + IPluginGame * game = selection.getChoiceData().value(); + if (game != nullptr) { + return selectGame(settings, game->gameDirectory(), game); + } + + gamePath = QFileDialog::getExistingDirectory( + nullptr, QObject::tr("Please select the game to manage"), QString(), + QFileDialog::ShowDirsOnly); + + if (!gamePath.isEmpty()) { + QDir gameDir(gamePath); + QList possibleGames; + for (IPluginGame * const game : plugins.plugins()) { + if (game->looksValid(gameDir)) { + possibleGames.append(game); + //return selectGame(settings, gameDir, game); + } + } + if (possibleGames.count() > 1) { + SelectionDialog browseSelection(QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32)); + for (IPluginGame *game : possibleGames) { + QString path = game->gameDirectory().absolutePath(); + browseSelection.addChoice(game->gameIcon(), game->gameName(), path, QVariant::fromValue(game)); + } + if (browseSelection.exec() == QDialog::Accepted) { + return selectGame(settings, gameDir, browseSelection.getChoiceData().value()); + } else { + reportError(QObject::tr("Canceled finding game in \"%1\".").arg(gamePath)); + } + } else if(possibleGames.count() == 1) { + return selectGame(settings, gameDir, possibleGames[0]); + } else { + reportError(QObject::tr("No game identified in \"%1\". The directory is required to contain " + "the game binary.").arg(gamePath)); + } + } + } + + return nullptr; +} + + +// extend path to include dll directory so plugins don't need a manifest +// (using AddDllDirectory would be an alternative to this but it seems fairly +// complicated esp. +// since it isn't easily accessible on Windows < 8 +// SetDllDirectory replaces other search directories and this seems to +// propagate to child processes) +void setupPath() +{ + static const int BUFSIZE = 4096; + + qDebug("MO at: %s", qUtf8Printable(QDir::toNativeSeparators( + QCoreApplication::applicationDirPath()))); + + QCoreApplication::setLibraryPaths(QStringList(QCoreApplication::applicationDirPath() + "/dlls") + QCoreApplication::libraryPaths()); + + boost::scoped_array oldPath(new TCHAR[BUFSIZE]); + DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE); + if (offset > BUFSIZE) { + oldPath.reset(new TCHAR[offset]); + ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset); + } + + std::wstring newPath(ToWString(QDir::toNativeSeparators( + QCoreApplication::applicationDirPath())) + L"\\dlls"); + newPath += L";"; + newPath += oldPath.get(); + + ::SetEnvironmentVariableW(L"PATH", newPath.c_str()); +} + +static void preloadSsl() +{ + QString appPath = QDir::toNativeSeparators(QCoreApplication::applicationDirPath()); + if (GetModuleHandleA("libeay32.dll")) + qWarning("libeay32.dll already loaded?!"); + else { + QString libeay32 = appPath + "\\libeay32.dll"; + if (!QFile::exists(libeay32)) + qWarning("libeay32.dll not found: %s", qUtf8Printable(libeay32)); + else if (!LoadLibraryW(libeay32.toStdWString().c_str())) + qWarning("failed to load: %s, %d", qUtf8Printable(libeay32), GetLastError()); + } + if (GetModuleHandleA("ssleay32.dll")) + qWarning("ssleay32.dll already loaded?!"); + else { + QString ssleay32 = appPath + "\\ssleay32.dll"; + if (!QFile::exists(ssleay32)) + qWarning("ssleay32.dll not found: %s", qUtf8Printable(ssleay32)); + else if (!LoadLibraryW(ssleay32.toStdWString().c_str())) + qWarning("failed to load: %s, %d", qUtf8Printable(ssleay32), GetLastError()); + } +} + +static QString getVersionDisplayString() +{ + return createVersionInfo().displayString(3); +} + +int runApplication(MOApplication &application, SingleInstance &instance, + const QString &splashPath) +{ + + qDebug("Starting Mod Organizer version %s revision %s", qUtf8Printable(getVersionDisplayString()), +#if defined(HGID) + HGID +#elif defined(GITID) + GITID +#else + "unknown" +#endif + ); + +#if !defined(QT_NO_SSL) + preloadSsl(); + qDebug("ssl support: %d", QSslSocket::supportsSsl()); +#else + qDebug("non-ssl build"); +#endif + + QString dataPath = application.property("dataPath").toString(); + qDebug("data path: %s", qUtf8Printable(dataPath)); + + if (!bootstrap()) { + reportError("failed to set up data paths"); + return 1; + } + + QWindowsWindowFunctions::setWindowActivationBehavior(QWindowsWindowFunctions::AlwaysActivateWindow); + + QStringList arguments = application.arguments(); + + try { + qDebug("Working directory: %s", qUtf8Printable(QDir::toNativeSeparators(QDir::currentPath()))); + + QSettings settings(dataPath + "/" + + QString::fromStdWString(AppConfig::iniFileName()), + QSettings::IniFormat); + + // global crashDumpType sits in OrganizerCore to make a bit less ugly to update it when the settings are changed during runtime + OrganizerCore::setGlobalCrashDumpsType(settings.value("Settings/crash_dumps_type", static_cast(CrashDumpsType::Mini)).toInt()); + + qDebug("Loaded settings:"); + settings.beginGroup("Settings"); + for (auto k : settings.allKeys()) + if (!k.contains("username") && !k.contains("password") && !k.contains("nexus_api_key")) + qDebug(" %s=%s", k.toUtf8().data(), settings.value(k).toString().toUtf8().data()); + settings.endGroup(); + + + qDebug("initializing core"); + OrganizerCore organizer(settings); + if (!organizer.bootstrap()) { + reportError("failed to set up data paths"); + return 1; + } + qDebug("initialize plugins"); + PluginContainer pluginContainer(&organizer); + pluginContainer.loadPlugins(); + + MOBase::IPluginGame *game = determineCurrentGame( + application.applicationDirPath(), settings, pluginContainer); + if (game == nullptr) { + return 1; + } + if (splashPath.startsWith(':')) { + // currently using MO splash, see if the plugin contains one + QString pluginSplash + = QString(":/%1/splash").arg(game->gameShortName()); + QImage image(pluginSplash); + if (!image.isNull()) { + image.save(dataPath + "/splash.png"); + } else { + qDebug("no plugin splash"); + } + } + + organizer.setManagedGame(game); + organizer.createDefaultProfile(); + + if (!settings.contains("game_edition")) { + QStringList editions = game->gameVariants(); + if (editions.size() > 1) { + SelectionDialog selection( + QObject::tr("Please select the game edition you have (MO can't " + "start the game correctly if this is set " + "incorrectly!)"), + nullptr); + selection.setWindowFlag(Qt::WindowStaysOnTopHint, true); + int index = 0; + for (const QString &edition : editions) { + selection.addChoice(edition, "", index++); + } + if (selection.exec() == QDialog::Rejected) { + return 1; + } else { + settings.setValue("game_edition", selection.getChoiceString()); + } + } + } + game->setGameVariant(settings.value("game_edition").toString()); + + qDebug("managing game at %s", qUtf8Printable(QDir::toNativeSeparators( + game->gameDirectory().absolutePath()))); + + organizer.updateExecutablesList(settings); + + QString selectedProfileName = determineProfile(arguments, settings); + organizer.setCurrentProfile(selectedProfileName); + + // if we have a command line parameter, it is either a nxm link or + // a binary to start + if (arguments.size() > 1) { + if (MOShortcut shortcut{ arguments.at(1) }) { + if (shortcut.hasExecutable()) { + try { + organizer.runShortcut(shortcut); + return 0; + } + catch (const std::exception &e) { + reportError( + QObject::tr("failed to start shortcut: %1").arg(e.what())); + return 1; + } + } + } + else if (OrganizerCore::isNxmLink(arguments.at(1))) { + qDebug("starting download from command line: %s", + qUtf8Printable(arguments.at(1))); + organizer.externalMessage(arguments.at(1)); + } + else { + QString exeName = arguments.at(1); + qDebug("starting %s from command line", qUtf8Printable(exeName)); + arguments.removeFirst(); // remove application name (ModOrganizer.exe) + arguments.removeFirst(); // remove binary name + // pass the remaining parameters to the binary + try { + organizer.startApplication(exeName, arguments, QString(), QString()); + return 0; + } + catch (const std::exception &e) { + reportError( + QObject::tr("failed to start application: %1").arg(e.what())); + return 1; + } + } + } + + QPixmap pixmap(splashPath); + QSplashScreen splash(pixmap); + splash.show(); + splash.activateWindow(); + + QString apiKey; + if (organizer.settings().getNexusApiKey(apiKey)) { + NexusInterface::instance(&pluginContainer)->getAccessManager()->apiCheck(apiKey); + } + + qDebug("initializing tutorials"); + TutorialManager::init( + qApp->applicationDirPath() + "/" + + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", + &organizer); + + if (!application.setStyleFile(settings.value("Settings/style", "").toString())) { + // disable invalid stylesheet + settings.setValue("Settings/style", ""); + } + + int res = 1; + { // scope to control lifetime of mainwindow + // set up main window and its data structures + MainWindow mainWindow(settings, organizer, pluginContainer); + + QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, + SLOT(setStyleFile(QString))); + QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer, + SLOT(externalMessage(QString))); + + mainWindow.processUpdates(); + mainWindow.readSettings(); + + qDebug("displaying main window"); + mainWindow.show(); + mainWindow.activateWindow(); + + splash.finish(&mainWindow); + return application.exec(); + } + } catch (const std::exception &e) { + reportError(e.what()); + return 1; + } +} + + +int main(int argc, char *argv[]) +{ + //Should allow for better scaling of ui with higher resolution displays + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + + if (argc >= 4) { + std::vector arg; + auto args = UntouchedCommandLineArguments(2, arg); + if (arg[0] == L"launch") + return SpawnWaitProcess(arg[1].c_str(), args); + } + + MOApplication application(argc, argv); + QStringList arguments = application.arguments(); + + setupPath(); + + bool forcePrimary = false; + if (arguments.contains("update")) { + arguments.removeAll("update"); + forcePrimary = true; + } + + MOShortcut moshortcut{ arguments.size() > 1 ? arguments.at(1) : "" }; + + SingleInstance instance(forcePrimary); + if (!instance.primaryInstance()) { + if (moshortcut || + arguments.size() > 1 && OrganizerCore::isNxmLink(arguments.at(1))) + { + qDebug("not primary instance, sending shortcut/download message"); + instance.sendMessage(arguments.at(1)); + return 0; + } else if (arguments.size() == 1) { + QMessageBox::information( + nullptr, QObject::tr("Mod Organizer"), + QObject::tr("An instance of Mod Organizer is already running")); + return 0; + } + } // we continue for the primary instance OR if MO was called with parameters + + do { + QString dataPath; + + try { + InstanceManager& instanceManager = InstanceManager::instance(); + if (moshortcut && moshortcut.hasInstance()) + instanceManager.overrideInstance(moshortcut.instance()); + dataPath = instanceManager.determineDataPath(); + } catch (const std::exception &e) { + if (strcmp(e.what(),"Canceled")) + QMessageBox::critical(nullptr, QObject::tr("Failed to set up instance"), e.what()); + return 1; + } + application.setProperty("dataPath", dataPath); + + // initialize dump collection only after "dataPath" since the crashes are stored under it + prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); + + LogBuffer::init(1000, QtDebugMsg, qApp->property("dataPath").toString() + "/logs/mo_interface.log"); + + QString splash = dataPath + "/splash.png"; + if (!QFile::exists(dataPath + "/splash.png")) { + splash = ":/MO/gui/splash"; + } + + int result = runApplication(application, instance, splash); + if (result != INT_MAX) { + return result; + } + argc = 1; + moshortcut = MOShortcut(""); + } while (true); +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index add1ef7f..9003d6b2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -392,8 +392,8 @@ MainWindow::MainWindow(QSettings &initSettings connect(NexusInterface::instance(&pluginContainer), SIGNAL(requestNXMDownload(QString)), &m_OrganizerCore, SLOT(downloadRequestedNXM(QString))); connect(NexusInterface::instance(&pluginContainer), SIGNAL(nxmDownloadURLsAvailable(QString,int,int,QVariant,QVariant,int)), this, SLOT(nxmDownloadURLs(QString,int,int,QVariant,QVariant,int))); - connect(NexusInterface::instance(&pluginContainer), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusLogin())); - connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString))); + connect(NexusInterface::instance(&pluginContainer), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusApi())); + connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(validateFailed(QString)), this, SLOT(validationFailed(QString))); connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(credentialsReceived(const QString&, bool)), this, SLOT(updateWindowTitle(const QString&, bool))); @@ -2695,7 +2695,7 @@ void MainWindow::endorseMod(ModInfo::Ptr mod) } else { QString apiKey; if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - m_OrganizerCore.doAfterLogin(boost::bind(&MainWindow::endorseMod, this, mod)); + m_OrganizerCore.doAfterLogin([&]() { this->endorseMod(mod); }); NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else { MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); @@ -2708,21 +2708,21 @@ void MainWindow::endorse_clicked() { QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->loggedIn()) { + if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { MessageDialog::showMessage(tr("Endorsing multiple mods will take a while. Please wait..."), this); for (QModelIndex idx : selection->selectedRows()) { ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->endorse(true); } } else { - QString username, password; - if (m_OrganizerCore.settings().getNexusLogin(username, password)) { + QString apiKey; + if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { MessageDialog::showMessage(tr("Endorsing multiple mods will take a while. Please wait..."), this); for (QModelIndex idx : selection->selectedRows()) { ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - m_OrganizerCore.doAfterLogin(boost::bind(&MainWindow::endorseMod, this, modInfo)); + m_OrganizerCore.doAfterLogin([&]() { this->endorseMod(modInfo); }); } - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->login(username, password); + NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else { MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); return; @@ -2750,12 +2750,12 @@ void MainWindow::dontendorse_clicked() void MainWindow::unendorseMod(ModInfo::Ptr mod) { - QString apiKey; if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { ModInfo::getByIndex(m_ContextRow)->endorse(false); } else { + QString apiKey; if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - m_OrganizerCore.doAfterLogin([this] () { this->unendorse_clicked(); }); + m_OrganizerCore.doAfterLogin([&]() { this->unendorseMod(mod); }); NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else { MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); @@ -2768,21 +2768,20 @@ void MainWindow::unendorse_clicked() { QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->loggedIn()) { + if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { MessageDialog::showMessage(tr("Unendorsing multiple mods will take a while. Please wait..."), this); for (QModelIndex idx : selection->selectedRows()) { ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->endorse(false); } - } - else { - QString username, password; - if (m_OrganizerCore.settings().getNexusLogin(username, password)) { + } else { + QString apiKey; + if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { MessageDialog::showMessage(tr("Unendorsing multiple mods will take a while. Please wait..."), this); for (QModelIndex idx : selection->selectedRows()) { ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - m_OrganizerCore.doAfterLogin(boost::bind(&MainWindow::unendorseMod, this, modInfo)); + m_OrganizerCore.doAfterLogin([&]() { this->unendorseMod(modInfo); }); } - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->login(username, password); + NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else { MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); return; @@ -2794,9 +2793,9 @@ void MainWindow::unendorse_clicked() } } -void MainWindow::loginFailed(const QString &error) +void MainWindow::validationFailed(const QString &error) { - qDebug("login failed: %s", qUtf8Printable(error)); + qDebug("Nexus API validation failed: %s", qUtf8Printable(error)); statusBar()->hide(); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 0e39c613..caf94c0e 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -1,658 +1,658 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include "bsafolder.h" -#include "browserdialog.h" -#include "delayedfilewriter.h" -#include "errorcodes.h" -#include "imoinfo.h" -#include "iuserinterface.h" -#include "modinfo.h" -#include "modlistsortproxy.h" -#include "savegameinfo.h" -#include "tutorialcontrol.h" - -//Note the commented headers here can be replaced with forward references, -//when I get round to cleaning up main.cpp -struct Executable; -class CategoryFactory; -class LockedDialogBase; -class OrganizerCore; -#include "plugincontainer.h" //class PluginContainer; -class PluginListSortProxy; -namespace BSA { class Archive; } -#include "iplugingame.h" //namespace MOBase { class IPluginGame; } -namespace MOBase { class IPluginModPage; } -namespace MOBase { class IPluginTool; } -namespace MOBase { class ISaveGame; } - -namespace MOShared { class DirectoryEntry; } - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class QAction; -class QAbstractItemModel; -class QDateTime; -class QEvent; -class QFile; -class QListWidgetItem; -class QMenu; -class QModelIndex; -class QPoint; -class QProgressBar; -class QProgressDialog; -class QTranslator; -class QTreeWidgetItem; -class QUrl; -class QSettings; -class QWidget; - -#ifndef Q_MOC_RUN -#include -#endif - -//Sigh - just for HANDLE -#define WIN32_LEAN_AND_MEAN -#include - -#include -#include -#include -#include - -namespace Ui { - class MainWindow; -} - - - -class MainWindow : public QMainWindow, public IUserInterface -{ - Q_OBJECT - - friend class OrganizerProxy; - -public: - - explicit MainWindow(QSettings &initSettings, - OrganizerCore &organizerCore, PluginContainer &pluginContainer, - QWidget *parent = 0); - ~MainWindow(); - - void storeSettings(QSettings &settings) override; - void readSettings(); - void processUpdates(); - - virtual ILockedWaitingForProcess* lock() override; - virtual void unlock() override; - - bool addProfile(); - void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives); - void refreshDataTree(); - void refreshDataTreeKeepExpandedNodes(); - void refreshSaveList(); - - void setModListSorting(int index); - void setESPListSorting(int index); - - void saveArchiveList(); - - void registerPluginTool(MOBase::IPluginTool *tool, QString name = QString(), QMenu *menu = nullptr); - void registerPluginTools(std::vector toolPlugins); - void registerModPage(MOBase::IPluginModPage *modPage); - - void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info); - - void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite); - std::string readFromPipe(HANDLE stdOutRead); - void processLOOTOut(const std::string &lootOut, std::string &errorMessages, QProgressDialog &dialog); - - void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); - - QString getOriginDisplayName(int originID); - - void installTranslator(const QString &name); - - virtual void disconnectPlugins(); - - void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab); - - virtual bool closeWindow(); - virtual void setWindowEnabled(bool enabled); - - virtual MOBase::DelayedFileWriterBase &archivesWriter() override { return m_ArchiveListWriter; } - -public slots: - - void displayColumnSelection(const QPoint &pos); - - void modorder_changed(); - void esplist_changed(); - void refresher_progress(int percent); - void directory_refreshed(); - - void toolPluginInvoke(); - void modPagePluginInvoke(); - -signals: - - /** - * @brief emitted after the information dialog has been closed - */ - void modInfoDisplayed(); - - /** - * @brief emitted when the selected style changes - */ - void styleChanged(const QString &styleFile); - - - void modListDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); - -protected: - - virtual void showEvent(QShowEvent *event); - virtual void closeEvent(QCloseEvent *event); - virtual bool eventFilter(QObject *obj, QEvent *event); - virtual void resizeEvent(QResizeEvent *event); - virtual void dragEnterEvent(QDragEnterEvent *event); - virtual void dropEvent(QDropEvent *event); - -private slots: - void on_actionChange_Game_triggered(); - -private slots: - void on_clickBlankButton_clicked(); - -private: - - void cleanup(); - - void actionToToolButton(QAction *&sourceAction); - - void updateToolBar(); - void activateSelectedProfile(); - - void setExecutableIndex(int index); - - void startSteam(); - - void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const MOShared::DirectoryEntry &directoryEntry, bool conflictsOnly, QIcon *fileIcon, QIcon *folderIcon); - bool refreshProfiles(bool selectProfile = true); - void refreshExecutablesList(); - void installMod(QString fileName = ""); - - QList findFileInfos(const QString &path, const std::function &filter) const; - - bool modifyExecutablesDialog(); - void displayModInformation(int row, int tab = -1); - void testExtractBSA(int modIndex); - - void writeDataToFile(QFile &file, const QString &directory, const MOShared::DirectoryEntry &directoryEntry); - - void refreshFilters(); - - /** - * Sets category selections from menu; for multiple mods, this will only apply - * the changes made in the menu (which is the delta between the current menu selection and the reference mod) - * @param menu the menu after editing by the user - * @param modRow index of the mod to edit - * @param referenceRow row of the reference mod - */ - void addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int referenceRow); - - /** - * Sets category selections from menu; for multiple mods, this will completely - * replace the current set of categories on each selected with those selected in the menu - * @param menu the menu after editing by the user - * @param modRow index of the mod to edit - */ - void replaceCategoriesFromMenu(QMenu *menu, int modRow); - - bool populateMenuCategories(QMenu *menu, int targetID); - - void initDownloadView(); - void updateDownloadView(); - - // remove invalid category-references from mods - void fixCategories(); - - void createEndorseWidget(); - void createHelpWidget(); - - bool extractProgress(QProgressDialog &extractProgress, int percentage, std::string fileName); - - size_t checkForProblems(); - - int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments); - QTreeWidgetItem *addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID, ModListSortProxy::FilterType type); - void addContentFilters(); - void addCategoryFilters(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID); - - void setCategoryListVisible(bool visible); - - void displaySaveGameInfo(QListWidgetItem *newItem); - - HANDLE nextChildProcess(); - - bool errorReported(QString &logFile); - - void updateESPLock(bool locked); - - static void setupNetworkProxy(bool activate); - void activateProxy(bool activate); - void setBrowserGeometry(const QByteArray &geometry); - - bool createBackup(const QString &filePath, const QDateTime &time); - QString queryRestore(const QString &filePath); - - void initModListContextMenu(QMenu *menu); - void addModSendToContextMenu(QMenu *menu); - void addPluginSendToContextMenu(QMenu *menu); - - QMenu *openFolderMenu(); - - std::set enabledArchives(); - - void scheduleUpdateButton(); - - QDir currentSavesDir() const; - - void startMonitorSaves(); - void stopMonitorSaves(); - - void dropLocalFile(const QUrl &url, const QString &outputDir, bool move); - - bool registerWidgetState(const QString &name, QHeaderView *view, const char *oldSettingName = nullptr); - - void sendSelectedModsToPriority(int newPriority); - void sendSelectedPluginsToPriority(int newPriority); - -private: - - static const char *PATTERN_BACKUP_GLOB; - static const char *PATTERN_BACKUP_REGEX; - static const char *PATTERN_BACKUP_DATE; - -private: - - Ui::MainWindow *ui; - - QAction *m_Sep; // Executable Shortcuts are added after this. Non owning. - - bool m_WasVisible; - - MOBase::TutorialControl m_Tutorial; - - int m_OldProfileIndex; - - std::vector m_ModNameList; // the mod-list to go with the directory structure - QProgressBar *m_RefreshProgress; - bool m_Refreshing; - - QStringList m_DefaultArchives; - - QAbstractItemModel *m_ModListGroupingProxy; - ModListSortProxy *m_ModListSortProxy; - - PluginListSortProxy *m_PluginListSortProxy; - - int m_OldExecutableIndex; - - int m_ContextRow; - QPersistentModelIndex m_ContextIdx; - QTreeWidgetItem *m_ContextItem; - QAction *m_ContextAction; - - CategoryFactory &m_CategoryFactory; - - int m_ModsToUpdate; - - bool m_LoginAttempted; - - QTimer m_CheckBSATimer; - QTimer m_SaveMetaTimer; - QTimer m_UpdateProblemsTimer; - - QFuture m_MetaSave; - - QTime m_StartTime; - //SaveGameInfoWidget *m_CurrentSaveView; - MOBase::ISaveGameInfoWidget *m_CurrentSaveView; - - OrganizerCore &m_OrganizerCore; - PluginContainer &m_PluginContainer; - - QString m_CurrentLanguage; - std::vector m_Translators; - - BrowserDialog m_IntegratedBrowser; - - QFileSystemWatcher m_SavesWatcher; - - std::vector m_RemoveWidget; - - QByteArray m_ArchiveListHash; - - bool m_DidUpdateMasterList; - - LockedDialogBase *m_LockDialog { nullptr }; - uint64_t m_LockCount { 0 }; - - bool m_closing{ false }; - - bool m_showArchiveData{ true }; - - std::vector> m_PersistedGeometry; - - MOBase::DelayedFileWriter m_ArchiveListWriter; - - enum class ShortcutType { - Toolbar, - Desktop, - StartMenu - }; - - void addWindowsLink(ShortcutType const); - - Executable const &getSelectedExecutable() const; - Executable &getSelectedExecutable(); - -private slots: - - void updateWindowTitle(const QString &accountName, bool premium); - - void showMessage(const QString &message); - void showError(const QString &message); - - - // main window actions - void helpTriggered(); - void issueTriggered(); - void wikiTriggered(); - void discordTriggered(); - void tutorialTriggered(); - void extractBSATriggered(); - - //modlist shortcuts - void openExplorer_activated(); - void refreshProfile_activated(); - - // modlist context menu - void installMod_clicked(); - void createEmptyMod_clicked(); - void createSeparator_clicked(); - void restoreBackup_clicked(); - void renameMod_clicked(); - void removeMod_clicked(); - void setColor_clicked(); - void resetColor_clicked(); - void backupMod_clicked(); - void reinstallMod_clicked(); - void endorse_clicked(); - void dontendorse_clicked(); - void unendorse_clicked(); - void ignoreMissingData_clicked(); - void markConverted_clicked(); - void visitOnNexus_clicked(); - void visitWebPage_clicked(); - void openExplorer_clicked(); - void openOriginExplorer_clicked(); - void openOriginInformation_clicked(); - void information_clicked(); - void enableSelectedMods_clicked(); - void disableSelectedMods_clicked(); - void sendSelectedModsToTop_clicked(); - void sendSelectedModsToBottom_clicked(); - void sendSelectedModsToPriority_clicked(); - void sendSelectedModsToSeparator_clicked(); - // savegame context menu - void deleteSavegame_clicked(); - void fixMods_clicked(SaveGameInfo::MissingAssets const &missingAssets); - // data-tree context menu - void writeDataToFile(); - void openDataFile(); - void addAsExecutable(); - void previewDataFile(); - void hideFile(); - void unhideFile(); - - // pluginlist context menu - void enableSelectedPlugins_clicked(); - void disableSelectedPlugins_clicked(); - void sendSelectedPluginsToTop_clicked(); - void sendSelectedPluginsToBottom_clicked(); - void sendSelectedPluginsToPriority_clicked(); - - void linkToolbar(); - void linkDesktop(); - void linkMenu(); - - void languageChange(const QString &newLanguage); - void saveSelectionChanged(QListWidgetItem *newItem); - - void windowTutorialFinished(const QString &windowName); - - BSA::EErrorCode extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, QProgressDialog &extractProgress); - - void createModFromOverwrite(); - /** - * @brief sends the content of the overwrite folder to an already existing mod - */ - void moveOverwriteContentToExistingMod(); - /** - * @brief actually sends the content of the overwrite folder to specified mod - */ - void doMoveOverwriteContentToMod(const QString &modAbsolutePath); - void clearOverwrite(); - - void procError(QProcess::ProcessError error); - void procFinished(int exitCode, QProcess::ExitStatus exitStatus); - - // nexus related - void checkModsForUpdates(); - - void loginFailed(const QString &message); - - void linkClicked(const QString &url); - - void updateAvailable(); - - void motdReceived(const QString &motd); - void notEndorsedYet(); - void wontEndorse(); - - void originModified(int originID); - - void addRemoveCategories_MenuHandler(); - void replaceCategories_MenuHandler(); - - void addPrimaryCategoryCandidates(); - - void modDetailsUpdated(bool success); - - void modInstalled(const QString &modName); - - void nxmUpdatesAvailable(const std::vector &modIDs, QVariant userData, QVariant resultData, int requestID); - void nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int); - void nxmDownloadURLs(QString, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - void nxmRequestFailed(QString, int modID, int fileID, QVariant userData, int requestID, const QString &errorString); - - void editCategories(); - void deselectFilters(); - - void displayModInformation(const QString &modName, int tab); - void modOpenNext(int tab=-1); - void modOpenPrev(int tab=-1); - - void modRenamed(const QString &oldName, const QString &newName); - void modRemoved(const QString &fileName); - - void hideSaveGameInfo(); - - void hookUpWindowTutorials(); - - void resumeDownload(int downloadIndex); - void endorseMod(ModInfo::Ptr mod); - void unendorseMod(ModInfo::Ptr mod); - void cancelModListEditor(); - - void lockESPIndex(); - void unlockESPIndex(); - - void enableVisibleMods(); - void disableVisibleMods(); - void exportModListCSV(); - void openInstanceFolder(); - void openLogsFolder(); - void openInstallFolder(); - void openPluginsFolder(); - void openDownloadsFolder(); - void openModsFolder(); - void openProfileFolder(); - void openIniFolder(); - void openGameFolder(); - void openMyGamesFolder(); - void startExeAction(); - - void checkBSAList(); - - void updateProblemsButton(); - - void saveModMetas(); - - void updateStyle(const QString &style); - - void modlistChanged(const QModelIndex &index, int role); - void modlistChanged(const QModelIndexList &indicies, int role); - void fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName); - - - void modFilterActive(bool active); - void espFilterChanged(const QString &filter); - void downloadFilterChanged(const QString &filter); - - void expandModList(const QModelIndex &index); - - /** - * @brief resize columns in mod list and plugin list to content - */ - void resizeLists(bool modListCustom, bool pluginListCustom); - - /** - * @brief allow columns in mod list and plugin list to be resized - */ - void allowListResize(); - - void toolBar_customContextMenuRequested(const QPoint &point); - void removeFromToolbar(); - void overwriteClosed(int); - - void changeVersioningScheme(); - void ignoreUpdate(); - void unignoreUpdate(); - - void refreshSavesIfOpen(); - void expandDataTreeItem(QTreeWidgetItem *item); - void about(); - void delayedRemove(); - - void modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous); - void modListSortIndicatorChanged(int column, Qt::SortOrder order); - void modListSectionResized(int logicalIndex, int oldSize, int newSize); - - void modlistSelectionsChanged(const QItemSelection ¤t); - void esplistSelectionsChanged(const QItemSelection ¤t); - - void search_activated(); - void searchClear_activated(); - - void updateModCount(); - void updatePluginCount(); - -private slots: // ui slots - // actions - void on_actionAdd_Profile_triggered(); - void on_actionInstallMod_triggered(); - void on_actionModify_Executables_triggered(); - void on_actionNexus_triggered(); - void on_actionNotifications_triggered(); - void on_actionSettings_triggered(); - void on_actionUpdate_triggered(); - void on_actionEndorseMO_triggered(); - - void on_bsaList_customContextMenuRequested(const QPoint &pos); - void on_clearFiltersButton_clicked(); - void on_btnRefreshData_clicked(); - void on_btnRefreshDownloads_clicked(); - void on_categoriesList_customContextMenuRequested(const QPoint &pos); - void on_conflictsCheckBox_toggled(bool checked); - void on_showArchiveDataCheckBox_toggled(bool checked); - void on_dataTree_customContextMenuRequested(const QPoint &pos); - void on_executablesListBox_currentIndexChanged(int index); - void on_modList_customContextMenuRequested(const QPoint &pos); - void on_modList_doubleClicked(const QModelIndex &index); - void on_listOptionsBtn_pressed(); - void on_espList_doubleClicked(const QModelIndex &index); - void on_profileBox_currentIndexChanged(int index); - void on_savegameList_customContextMenuRequested(const QPoint &pos); - void on_startButton_clicked(); - void on_tabWidget_currentChanged(int index); - - void on_espList_customContextMenuRequested(const QPoint &pos); - void on_displayCategoriesBtn_toggled(bool checked); - void on_groupCombo_currentIndexChanged(int index); - void on_categoriesList_itemSelectionChanged(); - void on_linkButton_pressed(); - void on_showHiddenBox_toggled(bool checked); - void on_bsaList_itemChanged(QTreeWidgetItem *item, int column); - void on_bossButton_clicked(); - - void on_saveButton_clicked(); - void on_restoreButton_clicked(); - void on_restoreModsButton_clicked(); - void on_saveModsButton_clicked(); - void on_actionCopy_Log_to_Clipboard_triggered(); - void on_categoriesAndBtn_toggled(bool checked); - void on_categoriesOrBtn_toggled(bool checked); - void on_managedArchiveLabel_linkHovered(const QString &link); -}; - - - -#endif // MAINWINDOW_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "bsafolder.h" +#include "browserdialog.h" +#include "delayedfilewriter.h" +#include "errorcodes.h" +#include "imoinfo.h" +#include "iuserinterface.h" +#include "modinfo.h" +#include "modlistsortproxy.h" +#include "savegameinfo.h" +#include "tutorialcontrol.h" + +//Note the commented headers here can be replaced with forward references, +//when I get round to cleaning up main.cpp +struct Executable; +class CategoryFactory; +class LockedDialogBase; +class OrganizerCore; +#include "plugincontainer.h" //class PluginContainer; +class PluginListSortProxy; +namespace BSA { class Archive; } +#include "iplugingame.h" //namespace MOBase { class IPluginGame; } +namespace MOBase { class IPluginModPage; } +namespace MOBase { class IPluginTool; } +namespace MOBase { class ISaveGame; } + +namespace MOShared { class DirectoryEntry; } + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class QAction; +class QAbstractItemModel; +class QDateTime; +class QEvent; +class QFile; +class QListWidgetItem; +class QMenu; +class QModelIndex; +class QPoint; +class QProgressBar; +class QProgressDialog; +class QTranslator; +class QTreeWidgetItem; +class QUrl; +class QSettings; +class QWidget; + +#ifndef Q_MOC_RUN +#include +#endif + +//Sigh - just for HANDLE +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include +#include +#include + +namespace Ui { + class MainWindow; +} + + + +class MainWindow : public QMainWindow, public IUserInterface +{ + Q_OBJECT + + friend class OrganizerProxy; + +public: + + explicit MainWindow(QSettings &initSettings, + OrganizerCore &organizerCore, PluginContainer &pluginContainer, + QWidget *parent = 0); + ~MainWindow(); + + void storeSettings(QSettings &settings) override; + void readSettings(); + void processUpdates(); + + virtual ILockedWaitingForProcess* lock() override; + virtual void unlock() override; + + bool addProfile(); + void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives); + void refreshDataTree(); + void refreshDataTreeKeepExpandedNodes(); + void refreshSaveList(); + + void setModListSorting(int index); + void setESPListSorting(int index); + + void saveArchiveList(); + + void registerPluginTool(MOBase::IPluginTool *tool, QString name = QString(), QMenu *menu = nullptr); + void registerPluginTools(std::vector toolPlugins); + void registerModPage(MOBase::IPluginModPage *modPage); + + void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info); + + void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite); + std::string readFromPipe(HANDLE stdOutRead); + void processLOOTOut(const std::string &lootOut, std::string &errorMessages, QProgressDialog &dialog); + + void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); + + QString getOriginDisplayName(int originID); + + void installTranslator(const QString &name); + + virtual void disconnectPlugins(); + + void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab); + + virtual bool closeWindow(); + virtual void setWindowEnabled(bool enabled); + + virtual MOBase::DelayedFileWriterBase &archivesWriter() override { return m_ArchiveListWriter; } + +public slots: + + void displayColumnSelection(const QPoint &pos); + + void modorder_changed(); + void esplist_changed(); + void refresher_progress(int percent); + void directory_refreshed(); + + void toolPluginInvoke(); + void modPagePluginInvoke(); + +signals: + + /** + * @brief emitted after the information dialog has been closed + */ + void modInfoDisplayed(); + + /** + * @brief emitted when the selected style changes + */ + void styleChanged(const QString &styleFile); + + + void modListDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + +protected: + + virtual void showEvent(QShowEvent *event); + virtual void closeEvent(QCloseEvent *event); + virtual bool eventFilter(QObject *obj, QEvent *event); + virtual void resizeEvent(QResizeEvent *event); + virtual void dragEnterEvent(QDragEnterEvent *event); + virtual void dropEvent(QDropEvent *event); + +private slots: + void on_actionChange_Game_triggered(); + +private slots: + void on_clickBlankButton_clicked(); + +private: + + void cleanup(); + + void actionToToolButton(QAction *&sourceAction); + + void updateToolBar(); + void activateSelectedProfile(); + + void setExecutableIndex(int index); + + void startSteam(); + + void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const MOShared::DirectoryEntry &directoryEntry, bool conflictsOnly, QIcon *fileIcon, QIcon *folderIcon); + bool refreshProfiles(bool selectProfile = true); + void refreshExecutablesList(); + void installMod(QString fileName = ""); + + QList findFileInfos(const QString &path, const std::function &filter) const; + + bool modifyExecutablesDialog(); + void displayModInformation(int row, int tab = -1); + void testExtractBSA(int modIndex); + + void writeDataToFile(QFile &file, const QString &directory, const MOShared::DirectoryEntry &directoryEntry); + + void refreshFilters(); + + /** + * Sets category selections from menu; for multiple mods, this will only apply + * the changes made in the menu (which is the delta between the current menu selection and the reference mod) + * @param menu the menu after editing by the user + * @param modRow index of the mod to edit + * @param referenceRow row of the reference mod + */ + void addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int referenceRow); + + /** + * Sets category selections from menu; for multiple mods, this will completely + * replace the current set of categories on each selected with those selected in the menu + * @param menu the menu after editing by the user + * @param modRow index of the mod to edit + */ + void replaceCategoriesFromMenu(QMenu *menu, int modRow); + + bool populateMenuCategories(QMenu *menu, int targetID); + + void initDownloadView(); + void updateDownloadView(); + + // remove invalid category-references from mods + void fixCategories(); + + void createEndorseWidget(); + void createHelpWidget(); + + bool extractProgress(QProgressDialog &extractProgress, int percentage, std::string fileName); + + size_t checkForProblems(); + + int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments); + QTreeWidgetItem *addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID, ModListSortProxy::FilterType type); + void addContentFilters(); + void addCategoryFilters(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID); + + void setCategoryListVisible(bool visible); + + void displaySaveGameInfo(QListWidgetItem *newItem); + + HANDLE nextChildProcess(); + + bool errorReported(QString &logFile); + + void updateESPLock(bool locked); + + static void setupNetworkProxy(bool activate); + void activateProxy(bool activate); + void setBrowserGeometry(const QByteArray &geometry); + + bool createBackup(const QString &filePath, const QDateTime &time); + QString queryRestore(const QString &filePath); + + void initModListContextMenu(QMenu *menu); + void addModSendToContextMenu(QMenu *menu); + void addPluginSendToContextMenu(QMenu *menu); + + QMenu *openFolderMenu(); + + std::set enabledArchives(); + + void scheduleUpdateButton(); + + QDir currentSavesDir() const; + + void startMonitorSaves(); + void stopMonitorSaves(); + + void dropLocalFile(const QUrl &url, const QString &outputDir, bool move); + + bool registerWidgetState(const QString &name, QHeaderView *view, const char *oldSettingName = nullptr); + + void sendSelectedModsToPriority(int newPriority); + void sendSelectedPluginsToPriority(int newPriority); + +private: + + static const char *PATTERN_BACKUP_GLOB; + static const char *PATTERN_BACKUP_REGEX; + static const char *PATTERN_BACKUP_DATE; + +private: + + Ui::MainWindow *ui; + + QAction *m_Sep; // Executable Shortcuts are added after this. Non owning. + + bool m_WasVisible; + + MOBase::TutorialControl m_Tutorial; + + int m_OldProfileIndex; + + std::vector m_ModNameList; // the mod-list to go with the directory structure + QProgressBar *m_RefreshProgress; + bool m_Refreshing; + + QStringList m_DefaultArchives; + + QAbstractItemModel *m_ModListGroupingProxy; + ModListSortProxy *m_ModListSortProxy; + + PluginListSortProxy *m_PluginListSortProxy; + + int m_OldExecutableIndex; + + int m_ContextRow; + QPersistentModelIndex m_ContextIdx; + QTreeWidgetItem *m_ContextItem; + QAction *m_ContextAction; + + CategoryFactory &m_CategoryFactory; + + int m_ModsToUpdate; + + bool m_LoginAttempted; + + QTimer m_CheckBSATimer; + QTimer m_SaveMetaTimer; + QTimer m_UpdateProblemsTimer; + + QFuture m_MetaSave; + + QTime m_StartTime; + //SaveGameInfoWidget *m_CurrentSaveView; + MOBase::ISaveGameInfoWidget *m_CurrentSaveView; + + OrganizerCore &m_OrganizerCore; + PluginContainer &m_PluginContainer; + + QString m_CurrentLanguage; + std::vector m_Translators; + + BrowserDialog m_IntegratedBrowser; + + QFileSystemWatcher m_SavesWatcher; + + std::vector m_RemoveWidget; + + QByteArray m_ArchiveListHash; + + bool m_DidUpdateMasterList; + + LockedDialogBase *m_LockDialog { nullptr }; + uint64_t m_LockCount { 0 }; + + bool m_closing{ false }; + + bool m_showArchiveData{ true }; + + std::vector> m_PersistedGeometry; + + MOBase::DelayedFileWriter m_ArchiveListWriter; + + enum class ShortcutType { + Toolbar, + Desktop, + StartMenu + }; + + void addWindowsLink(ShortcutType const); + + Executable const &getSelectedExecutable() const; + Executable &getSelectedExecutable(); + +private slots: + + void updateWindowTitle(const QString &accountName, bool premium); + + void showMessage(const QString &message); + void showError(const QString &message); + + + // main window actions + void helpTriggered(); + void issueTriggered(); + void wikiTriggered(); + void discordTriggered(); + void tutorialTriggered(); + void extractBSATriggered(); + + //modlist shortcuts + void openExplorer_activated(); + void refreshProfile_activated(); + + // modlist context menu + void installMod_clicked(); + void createEmptyMod_clicked(); + void createSeparator_clicked(); + void restoreBackup_clicked(); + void renameMod_clicked(); + void removeMod_clicked(); + void setColor_clicked(); + void resetColor_clicked(); + void backupMod_clicked(); + void reinstallMod_clicked(); + void endorse_clicked(); + void dontendorse_clicked(); + void unendorse_clicked(); + void ignoreMissingData_clicked(); + void markConverted_clicked(); + void visitOnNexus_clicked(); + void visitWebPage_clicked(); + void openExplorer_clicked(); + void openOriginExplorer_clicked(); + void openOriginInformation_clicked(); + void information_clicked(); + void enableSelectedMods_clicked(); + void disableSelectedMods_clicked(); + void sendSelectedModsToTop_clicked(); + void sendSelectedModsToBottom_clicked(); + void sendSelectedModsToPriority_clicked(); + void sendSelectedModsToSeparator_clicked(); + // savegame context menu + void deleteSavegame_clicked(); + void fixMods_clicked(SaveGameInfo::MissingAssets const &missingAssets); + // data-tree context menu + void writeDataToFile(); + void openDataFile(); + void addAsExecutable(); + void previewDataFile(); + void hideFile(); + void unhideFile(); + + // pluginlist context menu + void enableSelectedPlugins_clicked(); + void disableSelectedPlugins_clicked(); + void sendSelectedPluginsToTop_clicked(); + void sendSelectedPluginsToBottom_clicked(); + void sendSelectedPluginsToPriority_clicked(); + + void linkToolbar(); + void linkDesktop(); + void linkMenu(); + + void languageChange(const QString &newLanguage); + void saveSelectionChanged(QListWidgetItem *newItem); + + void windowTutorialFinished(const QString &windowName); + + BSA::EErrorCode extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, QProgressDialog &extractProgress); + + void createModFromOverwrite(); + /** + * @brief sends the content of the overwrite folder to an already existing mod + */ + void moveOverwriteContentToExistingMod(); + /** + * @brief actually sends the content of the overwrite folder to specified mod + */ + void doMoveOverwriteContentToMod(const QString &modAbsolutePath); + void clearOverwrite(); + + void procError(QProcess::ProcessError error); + void procFinished(int exitCode, QProcess::ExitStatus exitStatus); + + // nexus related + void checkModsForUpdates(); + + void validationFailed(const QString &message); + + void linkClicked(const QString &url); + + void updateAvailable(); + + void motdReceived(const QString &motd); + void notEndorsedYet(); + void wontEndorse(); + + void originModified(int originID); + + void addRemoveCategories_MenuHandler(); + void replaceCategories_MenuHandler(); + + void addPrimaryCategoryCandidates(); + + void modDetailsUpdated(bool success); + + void modInstalled(const QString &modName); + + void nxmUpdatesAvailable(const std::vector &modIDs, QVariant userData, QVariant resultData, int requestID); + void nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int); + void nxmDownloadURLs(QString, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmRequestFailed(QString, int modID, int fileID, QVariant userData, int requestID, const QString &errorString); + + void editCategories(); + void deselectFilters(); + + void displayModInformation(const QString &modName, int tab); + void modOpenNext(int tab=-1); + void modOpenPrev(int tab=-1); + + void modRenamed(const QString &oldName, const QString &newName); + void modRemoved(const QString &fileName); + + void hideSaveGameInfo(); + + void hookUpWindowTutorials(); + + void resumeDownload(int downloadIndex); + void endorseMod(ModInfo::Ptr mod); + void unendorseMod(ModInfo::Ptr mod); + void cancelModListEditor(); + + void lockESPIndex(); + void unlockESPIndex(); + + void enableVisibleMods(); + void disableVisibleMods(); + void exportModListCSV(); + void openInstanceFolder(); + void openLogsFolder(); + void openInstallFolder(); + void openPluginsFolder(); + void openDownloadsFolder(); + void openModsFolder(); + void openProfileFolder(); + void openIniFolder(); + void openGameFolder(); + void openMyGamesFolder(); + void startExeAction(); + + void checkBSAList(); + + void updateProblemsButton(); + + void saveModMetas(); + + void updateStyle(const QString &style); + + void modlistChanged(const QModelIndex &index, int role); + void modlistChanged(const QModelIndexList &indicies, int role); + void fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName); + + + void modFilterActive(bool active); + void espFilterChanged(const QString &filter); + void downloadFilterChanged(const QString &filter); + + void expandModList(const QModelIndex &index); + + /** + * @brief resize columns in mod list and plugin list to content + */ + void resizeLists(bool modListCustom, bool pluginListCustom); + + /** + * @brief allow columns in mod list and plugin list to be resized + */ + void allowListResize(); + + void toolBar_customContextMenuRequested(const QPoint &point); + void removeFromToolbar(); + void overwriteClosed(int); + + void changeVersioningScheme(); + void ignoreUpdate(); + void unignoreUpdate(); + + void refreshSavesIfOpen(); + void expandDataTreeItem(QTreeWidgetItem *item); + void about(); + void delayedRemove(); + + void modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous); + void modListSortIndicatorChanged(int column, Qt::SortOrder order); + void modListSectionResized(int logicalIndex, int oldSize, int newSize); + + void modlistSelectionsChanged(const QItemSelection ¤t); + void esplistSelectionsChanged(const QItemSelection ¤t); + + void search_activated(); + void searchClear_activated(); + + void updateModCount(); + void updatePluginCount(); + +private slots: // ui slots + // actions + void on_actionAdd_Profile_triggered(); + void on_actionInstallMod_triggered(); + void on_actionModify_Executables_triggered(); + void on_actionNexus_triggered(); + void on_actionNotifications_triggered(); + void on_actionSettings_triggered(); + void on_actionUpdate_triggered(); + void on_actionEndorseMO_triggered(); + + void on_bsaList_customContextMenuRequested(const QPoint &pos); + void on_clearFiltersButton_clicked(); + void on_btnRefreshData_clicked(); + void on_btnRefreshDownloads_clicked(); + void on_categoriesList_customContextMenuRequested(const QPoint &pos); + void on_conflictsCheckBox_toggled(bool checked); + void on_showArchiveDataCheckBox_toggled(bool checked); + void on_dataTree_customContextMenuRequested(const QPoint &pos); + void on_executablesListBox_currentIndexChanged(int index); + void on_modList_customContextMenuRequested(const QPoint &pos); + void on_modList_doubleClicked(const QModelIndex &index); + void on_listOptionsBtn_pressed(); + void on_espList_doubleClicked(const QModelIndex &index); + void on_profileBox_currentIndexChanged(int index); + void on_savegameList_customContextMenuRequested(const QPoint &pos); + void on_startButton_clicked(); + void on_tabWidget_currentChanged(int index); + + void on_espList_customContextMenuRequested(const QPoint &pos); + void on_displayCategoriesBtn_toggled(bool checked); + void on_groupCombo_currentIndexChanged(int index); + void on_categoriesList_itemSelectionChanged(); + void on_linkButton_pressed(); + void on_showHiddenBox_toggled(bool checked); + void on_bsaList_itemChanged(QTreeWidgetItem *item, int column); + void on_bossButton_clicked(); + + void on_saveButton_clicked(); + void on_restoreButton_clicked(); + void on_restoreModsButton_clicked(); + void on_saveModsButton_clicked(); + void on_actionCopy_Log_to_Clipboard_triggered(); + void on_categoriesAndBtn_toggled(bool checked); + void on_categoriesOrBtn_toggled(bool checked); + void on_managedArchiveLabel_linkHovered(const QString &link); +}; + + + +#endif // MAINWINDOW_H diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index dc1eb2cb..5da7b6a3 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -215,7 +215,7 @@ void NXMAccessManager::validateTimeout() m_ApiKey.clear(); m_ValidateState = VALIDATE_NOT_VALID; - emit validateFailed(tr("timeout")); + emit validateFailed(tr("There was a timeout during the request")); } diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 43c01310..bcdbd069 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -26,7 +26,7 @@ - Current Maintainers + Lead Developers/ Maintainers @@ -35,67 +35,92 @@ - - Major Contributors + + Mo2 devs and Contributors - + + Project579 + + + + + przester + + + + Translators - + Cyb3r (Dutch) - + fruttyx (French) - + Yoplala (French) - + Faron (German) - + Mordan (Greek) - + Yoosk (Polish) - + Brgodfx (Portuguese) - + + zDas (Portuguese) + + + + Jax (Swedish) - + + yohru (Japanese) + + + + ...and all other Transifex contributors! - + Other Supporters && Contributors - + + Tannin (Original Creator) + + + + Close @@ -279,12 +304,12 @@ p, li { white-space: pre-wrap; } DirectoryRefresher - + failed to parse bsa %1: %2 - + failed to read mod (%1): %2 @@ -292,435 +317,251 @@ p, li { white-space: pre-wrap; } DownloadList - + Name - - Filetime - - - - + Size - - Done + + Status - - Information missing, please select "Query Info" from the context menu to re-retrieve. - - - - - pending download - - - - - DownloadListWidget - - - - Placeholder + + Filetime - - - - Done - Double Click to install + + < game %1 mod %2 file %3 > - - - Paused - Double Click to resume + + Unknown - - - Installed - Double Click to re-install + + Pending - - - Uninstalled - Double Click to re-install + + Started - - - DownloadListWidgetCompact - - - Placeholder + + Canceling - - Done + + Pausing - - - DownloadListWidgetCompactDelegate - - < game %1 mod %2 file %3 > + + Canceled - - Pending + + Paused - - Paused + + Error - + Fetching Info 1 - + Fetching Info 2 - - Installed - - - - - Uninstalled - - - - - Done - - - - - - - - - - - Are you sure? + + Downloaded - - This will permanently delete the selected download. - - - - - This will remove all finished downloads from this list and from disk. - - - - - This will remove all installed downloads from this list and from disk. - - - - - This will remove all uninstalled downloads from this list and from disk. + + Installed - - This will permanently remove all finished downloads from this list (but NOT from disk). + + Uninstalled - - This will permanently remove all installed downloads from this list (but NOT from disk). + + Pending download - - This will permanently remove all uninstalled downloads from this list (but NOT from disk). + + Information missing, please select "Query Info" from the context menu to re-retrieve. + + + DownloadListWidget - + Install - + Query Info - + Visit on Nexus - + Open File - - - + + + Show in Folder - + + Delete - + Un-Hide - + Hide - + Cancel - + Pause - - Remove - - - - + Resume - + Delete Installed... - + Delete Uninstalled... - + Delete All... - + Hide Installed... - + Hide Uninstalled... - + Hide All... - + Un-Hide All... - - - DownloadListWidgetDelegate - - - < game %1 mod %2 file %3 > - - - - - Pending - - - - - Fetching Info 1 - - - - Fetching Info 2 - - - - - - - + + + + Delete Files? - + This will permanently delete the selected download. - + This will remove all finished downloads from this list and from disk. - + This will remove all installed downloads from this list and from disk. - + This will remove all uninstalled downloads from this list and from disk. - - - + + + Are you sure? - + This will remove all finished downloads from this list (but NOT from disk). - + This will remove all installed downloads from this list (but NOT from disk). - + This will remove all uninstalled downloads from this list (but NOT from disk). - - - Install - - - - - Query Info - - - - - Visit on Nexus - - - - - Open File - - - - - - - Show in Folder - - - - - - Delete - - - - - Un-Hide - - - - - Hide - - - - - Cancel - - - - - Pause - - - - - Resume - - - - - Delete Installed... - - - - - Delete Uninstalled... - - - - - Delete All... - - - - - Hide Installed... - - - - - Hide Uninstalled... - - - - - Hide All... - - - - - Un-Hide All... - - DownloadManager @@ -746,7 +587,7 @@ p, li { white-space: pre-wrap; } - A file with the same name has already been downloaded. Do you want to download it again? The new file will receive a different name. + A file with the same name "%1" has already been downloaded. Do you want to download it again? The new file will receive a different name. @@ -777,7 +618,7 @@ p, li { white-space: pre-wrap; } - + remove: invalid download index %1 @@ -797,234 +638,234 @@ p, li { white-space: pre-wrap; } - + cancel: invalid download index %1 - + pause: invalid download index %1 - + resume: invalid download index %1 - + resume (int): invalid download index %1 - + No known download urls. Sorry, this download can't be resumed. - + query: invalid download index %1 - + Please enter the nexus mod id - + Mod ID: - + Please select the source game code for %1 - + VisitNexus: invalid download index %1 - + Nexus ID for this Mod is unknown - + OpenFile: invalid download index %1 - + OpenFileInDownloadsFolder: invalid download index %1 - + get pending: invalid download index %1 - + get path: invalid download index %1 - + Main - + Update - + Optional - + Old - + Misc - + Unknown - + display name: invalid download index %1 - + file name: invalid download index %1 - + file time: invalid download index %1 - + file size: invalid download index %1 - + progress: invalid download index %1 - + state: invalid download index %1 - + infocomplete: invalid download index %1 - - + + mod id: invalid download index %1 - + ishidden: invalid download index %1 - + file info: invalid download index %1 - + mark installed: invalid download index %1 - + mark uninstalled: invalid download index %1 - + Memory allocation error (in processing progress event). - + Memory allocation error (in processing downloaded data). - + Information updated - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. - + No download server available. Please try again later. - + Failed to request file info from nexus: %1 - + Warning: Content type is: %1 - + Download header content length: %1 downloaded file size: %2 - + Download failed: %1 (%2) - + We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers. - + failed to re-open %1 - + Unable to write download to drive (return %1). Check the drive's available storage. @@ -1143,92 +984,107 @@ Right now the only case I know of where this needs to be overwritten is for the - + + If this is enabled, the configured libraries will be automatically loaded when this executable is launched. + + + + + Force Load Libraries (*) + + + + + Configure Libraries + + + + Use Application's Icon for shortcuts - + (*) This setting is profile-specific - - + + Add an executable - - + + Add - - + + Remove the selected executable - + Remove - + Close - + Select a binary - + Executable (%1) - + Java (32-bit) required - + MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. - + Select a directory - + Confirm - + Really remove "%1" from executables? - + Modify - - + + Save Changes? - - + + You made changes to the current executable, do you want to save them? @@ -1280,6 +1136,85 @@ Right now the only case I know of where this needs to be overwritten is for the + + ForcedLoadDialog + + + Forced Load Settings + + + + + + Adds a row to the table. + + + + + Add Row + + + + + + Deletes the selected row from the table. + + + + + Delete Row + + + + + ForcedLoadDialogWidget + + + + If checked, the specified library will be force loaded for the specified process. + + + + + + The name of the process that should be forced to load a library. + + + + + Process name + + + + + + Browse for a process. + + + + + + ... + + + + + + The path to the library to be loaded. This may be a path relative to the managed game's directory or may be an absolute path to somewhere else. + + + + + Library to load + + + + + + Browse for a library. + + + InstallDialog @@ -1386,59 +1321,72 @@ p, li { white-space: pre-wrap; } - + File format "%1" not supported - + None of the available installer plugins were able to handle that archive. This is likely due to a corrupted or incompatible download or unrecognized archive format. - + no error - + 7z.dll not found - + 7z.dll isn't valid - + archive not found - + failed to open archive - + unsupported archive type - + internal library error - + archive invalid - - unknown archive error + + unknown archive error + + + + + ListDialog + + + Select an item... + + + + + Filter @@ -1499,17 +1447,17 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + failed to write to %1 - + file not found: %1 - + Save @@ -1534,47 +1482,47 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MainWindow - + Categories - + Clear - + If checked, only mods that match all selected categories are displayed. - + And - + If checked, all mods that match at least one of the selected categories are displayed. - + Or - + Profile - + Pick a module collection - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1584,76 +1532,84 @@ p, li { white-space: pre-wrap; } - + Open list options... - + Refresh list. This is usually not necessary unless you modified data outside the program. - + Show Open Folders menu... - - + + Restore Backup... - - + + + Create Backup - + + + Active: + + + + + This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter. + + + + List of available mods. - + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - + + + + Filter - + Clear all Filters - + No groups - + Nexus IDs - - - - Namefilter - - - - + Pick a program to run. - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1663,12 +1619,12 @@ p, li { white-space: pre-wrap; } - + Run program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1677,17 +1633,17 @@ p, li { white-space: pre-wrap; } - + Run - + Create a shortcut in your start menu or on the desktop to the specified program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1696,27 +1652,32 @@ p, li { white-space: pre-wrap; } - + Shortcut - + Plugins - + Sort + This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. + + + + List of available esp/esm files - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1725,27 +1686,27 @@ p, li { white-space: pre-wrap; } - + Archives - + <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - + <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> - + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! @@ -1753,219 +1714,236 @@ p, li { white-space: pre-wrap; } - + Data - + refresh data-directory overview - + Refresh the overview. This may take a moment. - - - + + + + Refresh - + This is an overview of your data directory as visible to the game (and tools). - + File - + Mod - - - Filter the above list so that only conflicts are displayed. + + + Filters the above list so that only conflicts are displayed. - + Show only conflicts - + + + Filters the above list so that files from archives are not shown + + + + + Show files from Archives + + + + Saves - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all save games for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> - + Downloads - + + Refresh downloads view + + + + This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - + Show Hidden - + Tool Bar - + Install Mod - + Install &Mod - + Install a new mod from an archive - + Ctrl+M - + Profiles - + &Profiles - + Configure Profiles - + Ctrl+P - + Executables - + &Executables - + Configure the executables that can be started through Mod Organizer - + Ctrl+E - - + + Tools - + &Tools - + Ctrl+I - + Settings - + &Settings - + Configure settings and workarounds - + Ctrl+S - + Nexus - + Search nexus network for more mods - + Ctrl+N - - + + Update - + Mod Organizer is up-to-date - - + + No Problems - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1973,747 +1951,917 @@ Right now this has very limited functionality - - + + Help - + Ctrl+H - + Endorse MO - - + + Endorse Mod Organizer - + Copy Log to Clipboard - + Change Game - + Open the Instance selection dialog to manage a different Game - + Toolbar - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + + Crash on exit + + + + + MO crashed while exiting. Some settings may not be saved. + +Error: %1 + + + + Problems - + There are potential problems with your setup - + Everything seems to be in order - + + + + Endorse + + + + + Won't Endorse + + + + Help on UI - - Documentation Wiki + + Documentation - + + Chat on Discord + + + + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - - Notice: Your current MO version (%1) is lower than the previous version (%2).<br>The GUI may not downgrade gracefully, so you may experience oddities.<br>However, there should be no serious issues. + + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to spawn notepad.exe: %1 - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Contains %1> - + <Checked> - + <Unchecked> - + <Update> - + + <Mod Backup> + + + + <Managed by MO> - + <Managed outside MO> - + <No category> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + + Failed to create backup. + + + + You need to be logged in with Nexus to resume a download - - + + + + You need to be logged in with Nexus to endorse - + + + Endorsing multiple mods will take a while. Please wait... + + + + + + Unendorsing multiple mods will take a while. Please wait... + + + + Failed to display overwrite dialog: %1 - + + Opening Nexus Links + + + + + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? + + + + Nexus ID for this Mod is unknown - + + Opening Web Pages + + + + + You are trying to open %1 Web Pages. Are you sure you want to do this? + + + + Web page for this mod is unknown - - - + + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> + + + + + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> + + + + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + + Create Separator... + + + + + This will create a new separator. +Please enter a name: + + + + + A separator with this name already exists + + + + This will move all files from overwrite into a new, regular mod. Please enter a name: - - + + Move successful. + + + + + Are you sure? - + About to recursively delete: - + Not logged in, endorsement information will be wrong - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - - + + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + + Notes_column + + + + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + + Open INIs folder + + + + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + + Create Separator + + + + Enable all visible - + Disable all visible - + Check all for update - + Export to csv... - + + + Send to + + + + + + Top + + + + + + Bottom + + + + + + Priority... + + + + + Separator... + + + + All Mods - + Sync to Mods... - + + Move content to Mod... + + + + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - + + Change Categories - + + Primary Category - + + Rename Separator... + + + + + Remove Separator... + + + + + Select Color... + + + + + Reset Color + + + + Change versioning scheme - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - - - Endorse - - - - + Won't endorse - + Endorsement state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit web page - + Information... - - + + Exception: - - + + Unknown exception - + <All> - + <Multiple> - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -2721,12 +2869,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -2734,322 +2882,371 @@ You can also use online editors and converters instead. - + failed to remove %1 - + failed to create %1 - + + Restarting MO + + + + + Changing the managed game directory requires restarting MO. +Any pending downloads will be paused. + +Click OK to restart MO now. + + + + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Select binary - + Binary - + Enter Name - + Please enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - + + + Set Priority + + + + + Set the priority of the selected plugins + + + + file not found: %1 - + failed to generate preview for %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Update available - + Open/Execute - + Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Thank you! - + Thank you for your endorsement! - + + Okay. + + + + + This mod will not be endorsed and will no longer ask you to endorse. + + + + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + Edit Categories... - + Deselect filter - + Remove - + Enable all - + Disable all - + Unlock load order - + Lock load order - + + Open Origin in Explorer + + + + + Open Origin Info... + + + + depends on missing "%1" - + incompatible with "%1" - + Please wait while LOOT is running - + loot failed. Exit code was: %1 - + failed to start loot - + failed to run loot: %1 - + Errors occured - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of modlist created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file + + + Set the priority of the selected mods + + MessageDialog @@ -3063,72 +3260,82 @@ You can also use online editors and converters instead. ModInfo - + Plugins - + Textures - + Meshes - + Bethesda Archive - + UI Changes - + Sound Effects - + Scripts - + Script Extender - + + Script Extender Files + + + + SkyProc Tools - + MCM Data - + INI files - + + ModGroup files + + + + invalid content type %1 - + invalid mod index %1 - + remove: invalid mod index %1 @@ -3398,37 +3605,56 @@ p, li { white-space: pre-wrap; } - + about:blank - + Endorse - + + Web page URL (only used if invalid NexusID) : + + + + Notes - + + + + Enter comments about the mod here. These are displayed in the notes column of the mod list. + + + + + + + Enter notes about the mod here. These can be viewed in the mod list by hovering over the notes column or the flags column. + + + + Filetree - + Open Mod in Explorer - + A directory view of this mod - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -3438,288 +3664,288 @@ p, li { white-space: pre-wrap; } - + Previous - + Next - + Close - + &Delete - + &Rename - + &Hide - + &Unhide - + &Open - + &New Folder - - + + Save changes? - - + + Save changes to "%1"? - + File Exists - + A file with that name exists, please enter a new one - + failed to move file - + failed to create directory "optional" - - + + Info requested, please wait - + Main - + Update - + Optional - + Old - + Misc - + Unknown - + Current Version: %1 - + No update available - + (description incomplete, please visit nexus) - + <a href="%1">Visit on Nexus</a> - + Failed to delete %1 - - - - + + + + Confirm - - + + Are sure you want to delete "%1"? - - + + Are sure you want to delete the selected files? - - + + New Folder - + Failed to create "%1" - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - - + + failed to rename %1 to %2 - + There already is a visible version of this file. Replace it? - + Select binary - + Binary - + file not found: %1 - + failed to generate preview for %1 - + Sorry - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Un-Hide - + Hide - - + + Open/Execute - - + + Preview - + Name - + Please enter a name - - + + Error - + Invalid name. Must be a valid file name - + A tweak by that name exists - + Create Tweak @@ -3758,300 +3984,328 @@ p, li { white-space: pre-wrap; } ModInfoRegular - - + + failed to write %1/meta.ini: error %2 - + %1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory - + Categories: <br> + + ModInfoSeparator + + + This is a Separator + + + ModList - + Game Plugins (ESP/ESM/ESL) - + Interface - + Meshes - + Bethesda Archive - + Scripts (Papyrus) - + Script Extender Plugin - + SkyProc Patcher - + Sound or Music - + Textures - + MCM Configuration - + INI files - + + ModGroup files + + + + This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit) - + Backup - + + Separator + + + + No valid game data - + Not endorsed yet - + Overwrites loose files - + Overwritten loose files - + Loose files Overwrites & Overwritten - + Redundant - + Overwrites an archive with loose files - + Archive is overwritten by loose files - + Overwrites another archive file - + Overwritten by another archive file - + Archive files overwrites & overwritten - - Alternate game source + + <br>This mod is for a different game, make sure it's compatible or it could cause crashes. - + Non-MO - + invalid - + installed version: "%1", newest version: "%2" - + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + Categories: <br> - + Invalid name - + Name is already in use by another mod - + drag&drop failed: %1 - + Confirm - + Are you sure you want to remove "%1"? - + Flags - + Content - + Mod Name - + Version - + Priority - + Category - + Source Game - + Nexus ID - + Installation - - + + Notes + + + + + unknown - + Name of your mods - + Version of the mod (if available) - + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. - + Category of the mod. - + The source game which was the origin of this mod. - + Id of the mod as used on Nexus. - + Emblemes to highlight things that might require attention. - - Depicts the content of the mod:<br><table cellspacing=7><tr><td><img src=":/MO/gui/content/plugin" width=32/></td><td>Game plugins (esp/esm/esl)</td></tr><tr><td><img src=":/MO/gui/content/interface" width=32/></td><td>Interface</td></tr><tr><td><img src=":/MO/gui/content/mesh" width=32/></td><td>Meshes</td></tr><tr><td><img src=":/MO/gui/content/bsa" width=32/></td><td>BSA</td></tr><tr><td><img src=":/MO/gui/content/texture" width=32/></td><td>Textures</td></tr><tr><td><img src=":/MO/gui/content/sound" width=32/></td><td>Sounds</td></tr><tr><td><img src=":/MO/gui/content/music" width=32/></td><td>Music</td></tr><tr><td><img src=":/MO/gui/content/string" width=32/></td><td>Strings</td></tr><tr><td><img src=":/MO/gui/content/script" width=32/></td><td>Scripts (Papyrus)</td></tr><tr><td><img src=":/MO/gui/content/skse" width=32/></td><td>Script Extender plugins</td></tr><tr><td><img src=":/MO/gui/content/skyproc" width=32/></td><td>SkyProc Patcher</td></tr><tr><td><img src=":/MO/gui/content/menu" width=32/></td><td>Mod Configuration Menu</td></tr><tr><td><img src=":/MO/gui/content/inifile" width=32/></td><td>INI files</td></tr></table> + + Depicts the content of the mod:<br><table cellspacing=7><tr><td><img src=":/MO/gui/content/plugin" width=32/></td><td>Game plugins (esp/esm/esl)</td></tr><tr><td><img src=":/MO/gui/content/interface" width=32/></td><td>Interface</td></tr><tr><td><img src=":/MO/gui/content/mesh" width=32/></td><td>Meshes</td></tr><tr><td><img src=":/MO/gui/content/bsa" width=32/></td><td>BSA</td></tr><tr><td><img src=":/MO/gui/content/texture" width=32/></td><td>Textures</td></tr><tr><td><img src=":/MO/gui/content/sound" width=32/></td><td>Sounds</td></tr><tr><td><img src=":/MO/gui/content/music" width=32/></td><td>Music</td></tr><tr><td><img src=":/MO/gui/content/string" width=32/></td><td>Strings</td></tr><tr><td><img src=":/MO/gui/content/script" width=32/></td><td>Scripts (Papyrus)</td></tr><tr><td><img src=":/MO/gui/content/skse" width=32/></td><td>Script Extender plugins</td></tr><tr><td><img src=":/MO/gui/content/skyproc" width=32/></td><td>SkyProc Patcher</td></tr><tr><td><img src=":/MO/gui/content/menu" width=32/></td><td>Mod Configuration Menu</td></tr><tr><td><img src=":/MO/gui/content/inifile" width=32/></td><td>INI files</td></tr><tr><td><img src=":/MO/gui/content/modgroup" width=32/></td><td>ModGroup files</td></tr></table> - + Time this mod was installed + + + User notes about the mod + + ModListSortProxy - + Drag&Drop is only supported when sorting by priority @@ -4085,22 +4339,22 @@ p, li { white-space: pre-wrap; } NXMAccessManager - + Validating Nexus Connection - + Verifying Nexus login - + timeout - + Unknown error @@ -4116,17 +4370,17 @@ p, li { white-space: pre-wrap; } NexusInterface - + Failed to guess mod id for "%1", please pick the correct one - + empty response - + invalid response @@ -4165,170 +4419,183 @@ p, li { white-space: pre-wrap; } - - + + Download started - + Download failed - - + + Installation successful - - + + Configure Mod - - + + This mod contains ini tweaks. Do you want to configure them now? - - + + mod "%1" not found - - + + Installation cancelled - - + + The mod was not installed completely. - + Executable "%1" not found - + Start Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? - + Error - + Windows Event Log Error - + The Windows Event Log service is disabled and/or not running. This prevents USVFS from running properly. Your mods may not be working in the executable that you are launching. Note that you may have to restart MO and/or your PC after the service is fixed. Continue launching %1? - + + Blacklisted Executable + + + + + The executable you are attempted to launch is blacklisted in the virtual file system. This will likely prevent the executable, and any executables that are launched by this one, from seeing any mods. This could extend to INI files, save games and any other virtualized files. + +Continue launching %1? + + + + No profile set - + Failed to refresh list of esps: %1 - + Multiple esps/esls activated, please check that they don't conflict. - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - + + failed to update mod list: %1 - - + + login successful - + Login failed - + Login failed, try again? - + login failed: %1. Download will not be associated with an account - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + MO1 "Script Extender" load mechanism has left hook.dll in your game folder - - + + Description missing - + <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn. - + failed to save load order: %1 - + The designated write target "%1" is not enabled. @@ -4434,135 +4701,135 @@ Continue? PluginList - + Name - + Priority - + Mod Index - + Flags - - + + unknown - + Name of your mods - + Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. - + The modindex determines the formids of objects originating from this mods. - + failed to update esp info for file %1 (source id: %2), error: %3 - + esp not found: %1 - - + + Confirm - + Really enable all plugins? - + Really disable all plugins? - + The file containing locked plugin indices is broken - - + + <b>Origin</b>: %1 - + <br><b><i>This plugin can't be disabled (enforced by the game).</i></b> - + Author - + Description - + Missing Masters - + Enabled Masters - + Loads Archives - + There are Archives connected to this plugin. Their assets will be added to your game, overwriting in case of conflicts following the plugin order. Loose files will always overwrite assets from Archives. (This flag only checks for Archives from the same mod as the plugin) - + Loads INI settings - + There is an ini file connected to this plugin. Its settings will be added to your game settings, overwriting in case of conflicts. - + This ESP is flagged as an ESL. It will adhere to the ESP load order but the records will be loaded in ESL space. - + failed to restore load order for %1 @@ -4570,7 +4837,7 @@ Continue? PluginListSortProxy - + Drag&Drop is only supported when sorting by priority or mod index @@ -4634,57 +4901,82 @@ p, li { white-space: pre-wrap; } - + failed to write mod list: %1 - + failed to update tweaked ini file, wrong settings may be used: %1 - + failed to create tweaked ini: %1 - + failed to open %1 - + "%1" is missing or inaccessible - - - - - + + + + + + + invalid index %1 - + Overwrite directory couldn't be parsed - + invalid priority %1 - - Delete savegames? + + Delete profile-specific save games? - - Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) + + Do you want to delete the profile-specific save games? (If you select "No", the save games will show up again if you re-enable profile-specific save games) + + + + + Missing profile-specific game INI files! + + + + + Some of your profile-specific game INI files were missing. They will now be copied from the vanilla game folder. You might want double-check your settings. + +Missing files: + + + + + + Delete profile-specific game INI files? + + + + + Do you want to delete the profile-specific game INI files? (If you select "No", the INI files will be used again if you re-enable profile-specific game INI files.) @@ -4702,17 +4994,17 @@ p, li { white-space: pre-wrap; } - If checked, the new profile will use the default game settings. + If checked, the new profile will use the default game INI settings. - If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. + If checked, the new profile will use the default game INI settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. - Default Game Settings + Default Game INI Settings @@ -4741,27 +5033,41 @@ p, li { white-space: pre-wrap; } + <html><head/><body><p>If checked, save games are stored locally to this profile and will not appear when starting with a different profile.</p></body></html> + + + - If checked, savegames are local to this profile and will not appear when starting with a different profile. + If checked, save games are local to this profile and will not appear when starting with a different profile. - Local Savegames + Use profile-specific Save Games - Local Game Settings + <html><head/><body><p>If checked MO2 will use his own profile-specific game INI files, so that the &quot;Global&quot; ones in MyGames can be left vanilla. This different set of INI files is then offered to the game instead of the default one.</p></body></html> + + + + + <html><head/><body><p>If checked, MO2 will use a local set of game INI files (configuration and settings files), different from the default ones found in MyGames. This way changes to the INI settings will only affect this profile and the Global INI files can remain vanilla. MO2 will then show the profile INI files to the game instead of the Global ones.</p></body></html> - + + Use profile-specific Game INI Files + + + + This ensures data files from mods are actually used. You want to enable this unless you use a different tool for Archive Invalidation. - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -4773,65 +5079,65 @@ p, li { white-space: pre-wrap; } - + Automatic Archive Invalidation - - + + Create a new profile from scratch - + Create - + Clone the selected profile - + This creates a new profile with the same settings and active mods as the selected one. - + Copy - - + + Delete the selected Profile. This can not be un-done! - + Remove - + Rename - - + + Transfer save games to the selected profile. - + Transfer Saves - + Close @@ -4888,7 +5194,7 @@ p, li { white-space: pre-wrap; } - Are you sure you want to remove this profile (including local savegames if any)? + Are you sure you want to remove this profile (including profile-specific save games, if any)? @@ -4922,13 +5228,32 @@ p, li { white-space: pre-wrap; } + + QApplication + + + INI file is read-only + + + + + + Mod Organizer is attempting to write to "%1" which is currently set to read-only. Clear the read-only flag to allow the write? + + + + + File is read-only + + + QObject - + Error @@ -5028,12 +5353,13 @@ p, li { white-space: pre-wrap; } - + + helper failed - + failed to determine account name @@ -5049,135 +5375,137 @@ p, li { white-space: pre-wrap; } - + Deleting folder - + I'm about to delete the following folder: "%1". Proceed? - + Choose Instance to Delete - + Be Carefull! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched. - + Are you sure? - + Are you really sure you want to delete the Instance "%1" with all its files? - + Failed to delete Instance - + Could not delete Instance "%1". If the folder was still in use, restart MO and try again. - + Enter a Name for the new Instance - - Enter a new name or select one from the suggested list: + + Enter a new name or select one from the suggested list: +(This is just a name for the Instance and can be whatever you wish, + the actual game selection will happen on the next screen regardless of chosen name) - - + + Canceled - + Invalid instance name - + The instance name "%1" is invalid. Use the name "%2" instead? - + The instance "%1" already exists. - + Please choose a different instance name, like: "%1 1" . - + Choose Instance - + Each Instance is a full set of MO data files (mods, downloads, profiles, configuration, ...). You can use multiple instances for different games. Instances are stored in Appdata and can be accessed by all MO installations. If your MO folder is writable, you can also store a single instance locally (called a Portable install, and all the MO data files will be inside the installation folder). - + New - + Create a new instance. - + Portable - + Use MO folder for data. - + Manage Instances - + Delete an Instance. - - + + failed to create %1 - + Data directory created - + New data directory created at %1. If you don't want to store a lot of data there, reconfigure the storage directories via settings. @@ -5247,7 +5575,7 @@ If the folder was still in use, restart MO and try again. - + Failed to create "%1". Your user account probably lacks permission. @@ -5269,7 +5597,7 @@ If the folder was still in use, restart MO and try again. - No game identified in "%1". The directory is required to contain the game binary and its launcher. + No game identified in "%1". The directory is required to contain the game binary. @@ -5288,34 +5616,34 @@ If the folder was still in use, restart MO and try again. - - + + Mod Organizer - + An instance of Mod Organizer is already running - + Failed to set up instance - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 @@ -5351,12 +5679,12 @@ If the folder was still in use, restart MO and try again. - + failed to access %1 - + failed to set file time %1 @@ -5366,37 +5694,39 @@ If the folder was still in use, restart MO and try again. - + Script Extender - + Proxy DLL - + failed to spawn "%1" - + Elevation required - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if "%1" can be installed to work without elevation. -Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe to make changes to the system) +Restart Mod Organizer as an elevated process? +You will be asked if you want to allow helper.exe to make changes to the system. You will need to relaunch the process above manually. - + + failed to spawn "%1": %2 @@ -5562,12 +5892,12 @@ Select Show Details option to see the full change-log. - + Failed to start %1: %2 - + Error @@ -5575,31 +5905,48 @@ Select Show Details option to see the full change-log. Settings - + Failed - + Sorry, failed to start the helper application - - + + attempt to store setting for unknown plugin "%1" - + + Error - + + Failed to retrieve a Nexus API key! Please try again.A browser window should open asking you to authorize. + + + + Failed to create "%1", you may not have the necessary permission. path remains unchanged. + + + Restart Mod Organizer? + + + + + In order to reset the window geometries, MO must be restarted. +Restart it now? + + SettingsDialog @@ -5673,136 +6020,188 @@ If you use pre-releases, never contact me directly by e-mail or via private mess - If checked, the download interface will be more compact. + Colors - - Compact Download Interface + + + When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. - - If checked, the download list will display meta information instead of file names. + + Show mod list separator colors on the scrollbar - - Download Meta Information + + Plugin is Contained in selected Mod + + + + + Is overwritten (loose files) + + + + + Is overwriting (loose files) + + + + + Reset Colors + + + + + Mod Contains selected Plugin - + + Is overwritten (archive files) + + + + + Is overwriting (archive files) + + + + + + Modify the categories available to arrange your mods. + + + + + Configure Mod Categories + + + + Reset stored information from dialogs. - + This will make all dialogs show up again where you checked the "Remember selection"-box. - + Reset Dialogs - - - Modify the categories available to arrange your mods. + + If checked, the download interface will be more compact. - - Configure Mod Categories + + Compact Download Interface + + + + + If checked, the download list will display meta information instead of file names. - + + Download Meta Information + + + + Paths - - - + + + + ... - + Caches - + Overwrite - - + + Directory where downloads are stored. - + Downloads - + Profiles - + Directory where mods are stored. - + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). - + Mods - + Managed Game - + Base Directory - + Use %BASE_DIR% to refer to the Base Directory. - + Important: All directories have to be writeable! - - + + Nexus - + Allows automatic log-in when the Nexus-Page for the game is clicked. - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -5811,137 +6210,142 @@ p, li { white-space: pre-wrap; } - + Connect to Nexus - + Remove cache and cookies. Forces a new login. - + Clear Cache - + Disable automatic internet features - + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) - + Offline Mode - + Use a proxy for network connections. - + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - + Use HTTP Proxy (Uses System Settings) - + + Endorsement Integration + + + + Associate with "Download with manager" links - + Known Servers (updated on download) - + Preferred Servers (Drag & Drop) - + Steam - + Username - + Password - + If you save your steam user ID and password here, they will be used when logging into steam. Note, however, your password will be stored unencrypted, so make sure your computer is secure. - + Plugins - + Author: - + Version: - + Description: - + Key - + Value - + Blacklisted Plugins (use <del> to remove): - + Workarounds - + Steam App ID - + The Steam AppID for your game - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -5957,17 +6361,17 @@ p, li { white-space: pre-wrap; } - + Load Mechanism - + Select loading mechanism. See help for details. - + Mod Organizer needs a dll to be injected into the game so all mods are visible to it. There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. @@ -5978,17 +6382,17 @@ If you use the Steam version of Oblivion the default will NOT work. In this case - + NMM Version - + The Version of Nexus Mod Manager to impersonate. - + Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. @@ -5997,127 +6401,178 @@ tl;dr-version: If Nexus-features don't work, insert the current version num - + Enforces that inactive ESPs and ESMs are never loaded. - + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. - + Hide inactive ESPs/ESMs - + + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. + + + + + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. +However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. + +If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps, esms, and esls) displayed in the right pane are completely unaffected by this feature. + + + + + Display mods installed outside MO + + + + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + Force-enable game files - - Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. + + + Disable this to prevent the GUI from being locked when running an executable. This may result in abnormal behavior. - - By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. -However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. - -If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps, esms, and esls) displayed in the right pane are completely unaffected by this feature. + + Lock GUI when running executable - - Display mods installed outside MO + + Enable parsing of Archives. Has negative effects on performance. - - + + <html><head/><body><p>By default, MO will parse archive files (BSA, BA2) to calculate conflicts between the contents of the archive files and other loose files. This process has a noticeable cost in performance.</p><p>This feature should not be confused with the archive management feature offered by MO1. MO2 will only show conflicts with archives and will NOT load them into the game or program.</p><p>If you disable this feature, MO will only display conflicts between loose files.</p></body></html> + + + + + Enable parsing of Archives + + + + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! - + Back-date BSAs - - These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. + + Add executables to the blacklist to prevent them from +accessing the virtual file system. This is useful to prevent +unintended programs from being hooked. Hooking unintended +programs may affect the execution of these programs or the +programs you are intentionally running. - - Diagnostics + + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended programs may affect the execution of these programs or the programs you are intentionally running. - - Log Level + + Configure Executables Blacklist - - Decides the amount of data printed to "ModOrganizer.log" + + + Resets the window geometries for all windows. This can be useful if a window becomes too small or too large, if a column becomes too thin or too wide, and in similar situations. - - - Decides the amount of data printed to "ModOrganizer.log". - "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. - + + Reset Window Geometries - - Debug + + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - - Info (recommended) + + Diagnostics - - Warning + + Max Dumps To Keep - - Error + + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. + + + + + + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. + Set "Crash Dumps" above to None to disable crash dump collection. + + + + + + Hint: right click link and copy link location + + + + + + Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> + and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. + Sending logs and/or crash dumps to the developers can help investigate issues. + It is recommended to compress large log and dmp files before sending. + - + Crash Dumps - + Decides which type of crash dumps are collected when injected processes crash. - + Decides which type of crash dumps are collected when injected processes crash. "None" Disables the generation of crash dumps by MO. @@ -6128,105 +6583,131 @@ For the other games this is not a sufficient replacement for AI! - + None - + Mini (recommended) - + Data - + Full - - Max Dumps To Keep + + Log Level - - Maximum number of crash dumps to keep on disk. Use 0 for unlimited. + + Decides the amount of data printed to "ModOrganizer.log" - + - Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - Set "Crash Dumps" above to None to disable crash dump collection. + Decides the amount of data printed to "ModOrganizer.log". + "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. - - Hint: right click link and copy link location + + Debug - - - Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> - and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. - Sending logs and/or crash dumps to the developers can help investigate issues. - It is recommended to compress large log and dmp files before sending. - + + Info (recommended) + + + + + Warning - + + Error + + + + Confirm - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? - + + Executables Blacklist + + + + + Enter one executable per line to be blacklisted from the virtual file system. +Mods and other virtualized files will not be visible to these executables and +any executables launched by them. + +Example: + Chrome.exe + Firefox.exe + + + + Select base directory - + Select download directory - + Select mod directory - + Select cache directory - + Select profiles directory - + Select overwrite directory - + + Select game executable + + + + Confirm? - + This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? @@ -6338,7 +6819,7 @@ For the other games this is not a sufficient replacement for AI! TransferSavesDialog - Transfer Savegames + Transfer Save Games @@ -6430,7 +6911,7 @@ On Windows XP: UsvfsConnector - + Preparing vfs diff --git a/src/organizercore.cpp b/src/organizercore.cpp index dca855c7..bfe36b24 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -307,9 +307,9 @@ OrganizerCore::OrganizerCore(const QSettings &initSettings) SLOT(removeOrigin(QString))); connect(NexusInterface::instance(m_PluginContainer)->getAccessManager(), - SIGNAL(loginSuccessful(bool)), this, SLOT(loginSuccessful(bool))); + SIGNAL(validateSuccessful(bool)), this, SLOT(loginSuccessful(bool))); connect(NexusInterface::instance(m_PluginContainer)->getAccessManager(), - SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString))); + SIGNAL(validateFailed(QString)), this, SLOT(loginFailed(QString))); // This seems awfully imperative connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)), diff --git a/src/settings.cpp b/src/settings.cpp index 7a1d6737..fdc767bf 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -674,6 +674,13 @@ void Settings::processApiKey(const QString &apiKey) m_Settings.setValue("Settings/nexus_api_key", obfuscate(apiKey)); } +void Settings::clearApiKey(QPushButton *nexusButton) +{ + m_Settings.remove("Settings/nexus_api_key"); + nexusButton->setEnabled(true); + nexusButton->setText("Connect to Nexus"); +} + void Settings::checkApiKey(QPushButton *nexusButton) { if (m_Settings.value("Settings/nexus_api_key", "").toString().isEmpty()) { @@ -692,6 +699,7 @@ void Settings::query(PluginContainer *pluginContainer, QWidget *parent) connect(&dialog, SIGNAL(resetDialogs()), this, SLOT(resetDialogs())); connect(&dialog, SIGNAL(processApiKey(const QString &)), this, SLOT(processApiKey(const QString &))); connect(&dialog, SIGNAL(closeApiConnection(QPushButton *)), this, SLOT(checkApiKey(QPushButton *))); + connect(&dialog, SIGNAL(revokeApiKey(QPushButton *)), this, SLOT(clearApiKey(QPushButton *))); std::vector> tabs; diff --git a/src/settings.h b/src/settings.h index 49e5a5b6..d692497e 100644 --- a/src/settings.h +++ b/src/settings.h @@ -536,6 +536,7 @@ private slots: void resetDialogs(); void processApiKey(const QString &); + void clearApiKey(QPushButton *nexusButton); void checkApiKey(QPushButton *nexusButton); signals: diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 4843dea5..f63b1a10 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -62,6 +62,7 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, QWidget *parent connect(m_nexusLogin, SIGNAL(connected()), this, SLOT(dispatchLogin())); connect(m_nexusLogin, SIGNAL(textMessageReceived(const QString &)), this, SLOT(receiveApiKey(const QString &))); connect(m_nexusLogin, SIGNAL(disconnected()), this, SLOT(completeApiConnection())); + m_loginTimer.callOnTimeout(this, &SettingsDialog::loginPing); } SettingsDialog::~SettingsDialog() @@ -124,18 +125,18 @@ bool SettingsDialog::getResetGeometries() return ui->resetGeometryBtn->isChecked(); } -void SettingsDialog::on_loginCheckBox_toggled(bool checked) -{ - QLineEdit *usernameEdit = findChild("usernameEdit"); - QLineEdit *passwordEdit = findChild("passwordEdit"); - if (checked) { - passwordEdit->setEnabled(true); - usernameEdit->setEnabled(true); - } else { - passwordEdit->setEnabled(false); - usernameEdit->setEnabled(false); - } -} +//void SettingsDialog::on_loginCheckBox_toggled(bool checked) +//{ +// QLineEdit *usernameEdit = findChild("usernameEdit"); +// QLineEdit *passwordEdit = findChild("passwordEdit"); +// if (checked) { +// passwordEdit->setEnabled(true); +// usernameEdit->setEnabled(true); +// } else { +// passwordEdit->setEnabled(false); +// usernameEdit->setEnabled(false); +// } +//} void SettingsDialog::on_categoriesBtn_clicked() { @@ -337,7 +338,7 @@ void SettingsDialog::on_resetDialogsButton_clicked() void SettingsDialog::on_nexusConnect_clicked() { - ui->nexusConnect->setText("Connecting the API..."); + ui->nexusConnect->setText("Connecting the API. Please login within the browser and accept the request. This will time out after 5 minutes."); ui->nexusConnect->setDisabled(true); QUrl url = QUrl("wss://sso.nexusmods.com:8443"); m_nexusLogin->open(url); @@ -354,6 +355,20 @@ void SettingsDialog::dispatchLogin() QString finalMessage(loginDoc.toJson()); m_nexusLogin->sendTextMessage(finalMessage); QDesktopServices::openUrl(QUrl(QString("https://www.nexusmods.com/sso?id=") + QString(boost::uuids::to_string(sessionId).c_str()))); + m_loginTimer.start(30000); +} + +void SettingsDialog::loginPing() +{ + if (m_nexusLogin->isValid()) { + m_nexusLogin->ping(); + m_totalPings++; + } + if (m_totalPings >= 10) { + m_loginTimer.stop(); + m_totalPings = 0; + m_nexusLogin->close(QWebSocketProtocol::CloseCodeGoingAway, "Timeout: No response received after five minutes. Cancelling request."); + } } void SettingsDialog::receiveApiKey(const QString &apiKey) @@ -361,6 +376,8 @@ void SettingsDialog::receiveApiKey(const QString &apiKey) emit processApiKey(apiKey); m_nexusLogin->close(); ui->nexusConnect->setText("Nexus API Key Stored"); + m_loginTimer.stop(); + m_totalPings = 0; } void SettingsDialog::completeApiConnection() @@ -435,6 +452,11 @@ void SettingsDialog::on_clearCacheButton_clicked() NexusInterface::instance(m_PluginContainer)->clearCache(); } +void SettingsDialog::on_revokeNexusAuthButton_clicked() +{ + emit revokeApiKey(ui->nexusConnect); +} + void SettingsDialog::normalizePath(QLineEdit *lineEdit) { QString text = lineEdit->text(); diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 90b60c91..d0f6a36c 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -26,6 +26,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include class PluginContainer; @@ -63,6 +64,7 @@ signals: void resetDialogs(); void processApiKey(const QString &); void closeApiConnection(QPushButton *); + void revokeApiKey(QPushButton *); private: @@ -90,7 +92,7 @@ public: private slots: - void on_loginCheckBox_toggled(bool checked); + //void on_loginCheckBox_toggled(bool checked); void on_categoriesBtn_clicked(); @@ -114,6 +116,8 @@ private slots: void on_clearCacheButton_clicked(); + void on_revokeNexusAuthButton_clicked(); + void on_browseBaseDirBtn_clicked(); void on_browseOverwriteDirBtn_clicked(); @@ -152,6 +156,8 @@ private slots: void dispatchLogin(); + void loginPing(); + void receiveApiKey(const QString &apiKey); void completeApiConnection(); @@ -170,6 +176,8 @@ private: QString m_ExecutableBlacklist; QWebSocket *m_nexusLogin; + QTimer m_loginTimer; + int m_totalPings = 0; }; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index d3628ab3..127c94c7 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1,1405 +1,1432 @@ - - - SettingsDialog - - - - 0 - 0 - 586 - 486 - - - - Settings - - - - - - 0 - - - - General - - - - - - - - Language - - - - - - - The display language - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> - - - - - - - - - - - Style - - - - - - - graphical style - - - graphical style of the MO user interface - - - - - - - - - Update to non-stable releases. - - - If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). - -Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. - -If you use pre-releases, never contact me directly by e-mail or via private messages! - - - Install Pre-releases (Betas) - - - - - - - User interface - - - - - - Colors - - - - - - When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. - - - When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. - - - Show mod list separator colors on the scrollbar - - - true - - - - - - - Plugin is Contained in selected Mod - - - - - - - Is overwritten (loose files) - - - - - - - Is overwriting (loose files) - - - - - - - Reset Colors - - - - - - - Mod Contains selected Plugin - - - - - - - Is overwritten (archive files) - - - - - - - Is overwriting (archive files) - - - - - - - - - - Modify the categories available to arrange your mods. - - - Modify the categories available to arrange your mods. - - - Configure Mod Categories - - - - - - - - 16777215 - 16777215 - - - - Reset stored information from dialogs. - - - This will make all dialogs show up again where you checked the "Remember selection"-box. - - - Reset Dialogs - - - - - - - If checked, the download interface will be more compact. - - - Compact Download Interface - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - If checked, the download list will display meta information instead of file names. - - - Download Meta Information - - - - - - - - - - - Paths - - - - - - - - ... - - - - - - - ... - - - - - - - - - - ... - - - - - - - - - - Caches - - - - - - - Overwrite - - - - - - - Directory where downloads are stored. - - - Directory where downloads are stored. - - - - - - - - - - ... - - - - - - - Downloads - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Profiles - - - - - - - - 0 - 0 - - - - ... - - - - - - - - - - Directory where mods are stored. - - - Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). - - - - - - - ... - - - - - - - false - - - true - - - - - - - Mods - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Managed Game - - - - - - - Base Directory - - - - - - - Use %BASE_DIR% to refer to the Base Directory. - - - - - - - ... - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Important: All directories have to be writeable! - - - - - - - - Nexus - - - - - - Allows automatic log-in when the Nexus-Page for the game is clicked. - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> - - - Nexus - - - - - - - - Connect to Nexus - - - - - - - - - - - Remove cache and cookies. Forces a new login. - - - Clear Cache - - - - :/MO/gui/edit_clear:/MO/gui/edit_clear - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - Disable automatic internet features - - - Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) - - - Offline Mode - - - - - - - Use a proxy for network connections. - - - Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - - - Use HTTP Proxy (Uses System Settings) - - - - - - - Endorsement Integration - - - true - - - - - - - - - - - Associate with "Download with manager" links - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 4 - - - - - - - Known Servers (updated on download) - - - - - - - QAbstractItemView::DragDrop - - - Qt::MoveAction - - - - - - - - - - - Preferred Servers (Drag & Drop) - - - - - - - QAbstractItemView::DragDrop - - - Qt::MoveAction - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Steam - - - - - - Username - - - - - - - - - - Password - - - - - - - QLineEdit::Password - - - - - - - Qt::Vertical - - - QSizePolicy::Minimum - - - - 20 - 40 - - - - - - - - If you save your steam user ID and password here, they will be used when logging into steam. Note, however, your password will be stored unencrypted, so make sure your computer is secure. - - - true - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 20 - 232 - - - - - - - - - Plugins - - - - - - - - - 0 - 0 - - - - true - - - - - - - - - - - Author: - - - - - - - - - - - - - - Version: - - - - - - - - - - - - - - Description: - - - - - - - - - - true - - - - - - - - - 0 - - - false - - - false - - - false - - - false - - - 170 - - - - Key - - - - - Value - - - - - - - - - - - - Blacklisted Plugins (use <del> to remove): - - - - - - - - - - - Workarounds - - - - - - - - Steam App ID - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - The Steam AppID for your game - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> - - - - - - - - - 7 - - - - - - 16777215 - 16777215 - - - - Load Mechanism - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Select loading mechanism. See help for details. - - - Mod Organizer needs a dll to be injected into the game so all mods are visible to it. -There are several means to do this: -*Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. -*Script Extender* In this mode, MO is installed as a Script Extender (obse, fose, nvse, skse) plugin. -*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. - -If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. - - - - - - - - - - - NMM Version - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - The Version of Nexus Mod Manager to impersonate. - - - Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. -On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. -Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. - -tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. - - - 009.009.009 - - - Qt::AlignCenter - - - - - - - - - - - - - - false - - - Enforces that inactive ESPs and ESMs are never loaded. - - - It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. -I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. - - - Hide inactive ESPs/ESMs - - - - - - - Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. - - - By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. -However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. - -If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps, esms, and esls) displayed in the right pane are completely unaffected by this feature. - - - Display mods installed outside MO - - - true - - - - - - - If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - - - If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) -Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - - - Force-enable game files - - - true - - - - - - - Disable this to prevent the GUI from being locked when running an executable. This may result in abnormal behavior. - - - Disable this to prevent the GUI from being locked when running an executable. This may result in abnormal behavior. - - - Lock GUI when running executable - - - true - - - - - - - Enable parsing of Archives. This is an Experimental Feature. Has negative effects on performance and known incorrectness. - - - <html><head/><body><p>By default, MO will parse archive files (BSA, BA2) to calculate conflicts between the contents of the archive files and other loose files. This process has a noticeable cost in performance.</p><p>This feature should not be confused with the archive management feature offered by MO1. MO2 will only show conflicts with archives and will NOT load them into the game or program.</p><p>If you disable this feature, MO will only display conflicts between loose files.</p></body></html> - - - Enable parsing of Archives (Experimental Feature) - - - true - - - - - - - - - For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. -For the other games this is not a sufficient replacement for AI! - - - For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. -For the other games this is not a sufficient replacement for AI! - - - Back-date BSAs - - - - :/MO/gui/resources/emblem-readonly.png:/MO/gui/resources/emblem-readonly.png - - - - - - - Add executables to the blacklist to prevent them from -accessing the virtual file system. This is useful to prevent -unintended programs from being hooked. Hooking unintended -programs may affect the execution of these programs or the -programs you are intentionally running. - - - Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended programs may affect the execution of these programs or the programs you are intentionally running. - - - Configure Executables Blacklist - - - false - - - - - - - Resets the window geometries for all windows. This can be useful if a window becomes too small or too large, if a column becomes too thin or too wide, and in similar situations. - - - Resets the window geometries for all windows. This can be useful if a window becomes too small or too large, if a column becomes too thin or too wide, and in similar situations. - - - Reset Window Geometries - - - true - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - - - true - - - - - - - - Diagnostics - - - - - - - - Max Dumps To Keep - - - - - - - Qt::Horizontal - - - - 60 - 20 - - - - - - - - Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - - - - Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - Set "Crash Dumps" above to None to disable crash dump collection. - - - - - - - - - - Hint: right click link and copy link location - - - - Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> - and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. - Sending logs and/or crash dumps to the developers can help investigate issues. - It is recommended to compress large log and dmp files before sending. - - - - true - - - - - - - - - Crash Dumps - - - - - - - Decides which type of crash dumps are collected when injected processes crash. - - - - Decides which type of crash dumps are collected when injected processes crash. - "None" Disables the generation of crash dumps by MO. - "Mini" Default level which generates small dumps (only stack traces). - "Data" Much larger dumps with additional information which may be need (also data segments). - "Full" Even larger dumps with a full memory dump of the process. - - - - - None - - - - - Mini (recommended) - - - - - Data - - - - - Full - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 20 - 232 - - - - - - - - - - Log Level - - - - - - - Decides the amount of data printed to "ModOrganizer.log" - - - - Decides the amount of data printed to "ModOrganizer.log". - "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. - - - - - Debug - - - - - Info (recommended) - - - - - Warning - - - - - Error - - - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - languageBox - styleBox - logLevelBox - usePrereleaseBox - compactBox - categoriesBtn - baseDirEdit - browseBaseDirBtn - downloadDirEdit - browseDownloadDirBtn - modDirEdit - browseModDirBtn - cacheDirEdit - browseCacheDirBtn - profilesDirEdit - browseProfilesDirBtn - overwriteDirEdit - browseOverwriteDirBtn - clearCacheButton - associateButton - knownServersList - preferredServersList - steamUserEdit - steamPassEdit - pluginsList - pluginSettingsList - pluginBlacklist - appIDEdit - mechanismBox - nmmVersionEdit - bsaDateBtn - tabWidget - - - - - - - buttonBox - accepted() - SettingsDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - SettingsDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - + + + SettingsDialog + + + + 0 + 0 + 586 + 486 + + + + Settings + + + + + + 0 + + + + General + + + + + + + + Language + + + + + + + The display language + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> + + + + + + + + + + + Style + + + + + + + graphical style + + + graphical style of the MO user interface + + + + + + + + + Update to non-stable releases. + + + If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). + +Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. + +If you use pre-releases, never contact me directly by e-mail or via private messages! + + + Install Pre-releases (Betas) + + + + + + + User interface + + + + + + Colors + + + + + + When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. + + + When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. + + + Show mod list separator colors on the scrollbar + + + true + + + + + + + Plugin is Contained in selected Mod + + + + + + + Is overwritten (loose files) + + + + + + + Is overwriting (loose files) + + + + + + + Reset Colors + + + + + + + Mod Contains selected Plugin + + + + + + + Is overwritten (archive files) + + + + + + + Is overwriting (archive files) + + + + + + + + + + Modify the categories available to arrange your mods. + + + Modify the categories available to arrange your mods. + + + Configure Mod Categories + + + + + + + + 16777215 + 16777215 + + + + Reset stored information from dialogs. + + + This will make all dialogs show up again where you checked the "Remember selection"-box. + + + Reset Dialogs + + + + + + + If checked, the download interface will be more compact. + + + Compact Download Interface + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + If checked, the download list will display meta information instead of file names. + + + Download Meta Information + + + + + + + + + + + Paths + + + + + + + + ... + + + + + + + ... + + + + + + + + + + ... + + + + + + + + + + Caches + + + + + + + Overwrite + + + + + + + Directory where downloads are stored. + + + Directory where downloads are stored. + + + + + + + + + + ... + + + + + + + Downloads + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Profiles + + + + + + + + 0 + 0 + + + + ... + + + + + + + + + + Directory where mods are stored. + + + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). + + + + + + + ... + + + + + + + false + + + true + + + + + + + Mods + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Managed Game + + + + + + + Base Directory + + + + + + + Use %BASE_DIR% to refer to the Base Directory. + + + + + + + ... + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Important: All directories have to be writeable! + + + + + + + + Nexus + + + + + + Allows automatic log-in when the Nexus-Page for the game is clicked. + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> + + + Nexus + + + + + + + + Connect to Nexus + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Clear the stored Nexus API key and force reauthorization. + + + Revoke Nexus Authorization + + + + :/MO/gui/edit_clear:/MO/gui/edit_clear + + + + + + + Remove cache and cookies. + + + Clear Cache + + + + :/MO/gui/edit_clear:/MO/gui/edit_clear + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + Disable automatic internet features + + + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) + + + Offline Mode + + + + + + + Use a proxy for network connections. + + + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. + + + Use HTTP Proxy (Uses System Settings) + + + + + + + Endorsement Integration + + + true + + + + + + + + + + + Associate with "Download with manager" links + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 4 + + + + + + + Known Servers (updated on download) + + + + + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + + + + + + + + + Preferred Servers (Drag & Drop) + + + + + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Steam + + + + + + Username + + + + + + + + + + Password + + + + + + + QLineEdit::Password + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 40 + + + + + + + + If you save your steam user ID and password here, they will be used when logging into steam. Note, however, your password will be stored unencrypted, so make sure your computer is secure. + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 232 + + + + + + + + + Plugins + + + + + + + + + 0 + 0 + + + + true + + + + + + + + + + + Author: + + + + + + + + + + + + + + Version: + + + + + + + + + + + + + + Description: + + + + + + + + + + true + + + + + + + + + 0 + + + false + + + false + + + false + + + false + + + 170 + + + + Key + + + + + Value + + + + + + + + + + + + Blacklisted Plugins (use <del> to remove): + + + + + + + + + + + Workarounds + + + + + + + + Steam App ID + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + The Steam AppID for your game + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> + + + + + + + + + 7 + + + + + + 16777215 + 16777215 + + + + Load Mechanism + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Select loading mechanism. See help for details. + + + Mod Organizer needs a dll to be injected into the game so all mods are visible to it. +There are several means to do this: +*Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. +*Script Extender* In this mode, MO is installed as a Script Extender (obse, fose, nvse, skse) plugin. +*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. + +If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. + + + + + + + + + + + NMM Version + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + The Version of Nexus Mod Manager to impersonate. + + + Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. +On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. +Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. + +tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. + + + 009.009.009 + + + Qt::AlignCenter + + + + + + + + + + + + + + false + + + Enforces that inactive ESPs and ESMs are never loaded. + + + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. +I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. + + + Hide inactive ESPs/ESMs + + + + + + + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. + + + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. +However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. + +If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps, esms, and esls) displayed in the right pane are completely unaffected by this feature. + + + Display mods installed outside MO + + + true + + + + + + + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) + + + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) +Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. + + + Force-enable game files + + + true + + + + + + + Disable this to prevent the GUI from being locked when running an executable. This may result in abnormal behavior. + + + Disable this to prevent the GUI from being locked when running an executable. This may result in abnormal behavior. + + + Lock GUI when running executable + + + true + + + + + + + Enable parsing of Archives. This is an Experimental Feature. Has negative effects on performance and known incorrectness. + + + <html><head/><body><p>By default, MO will parse archive files (BSA, BA2) to calculate conflicts between the contents of the archive files and other loose files. This process has a noticeable cost in performance.</p><p>This feature should not be confused with the archive management feature offered by MO1. MO2 will only show conflicts with archives and will NOT load them into the game or program.</p><p>If you disable this feature, MO will only display conflicts between loose files.</p></body></html> + + + Enable parsing of Archives (Experimental Feature) + + + true + + + + + + + + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. +For the other games this is not a sufficient replacement for AI! + + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. +For the other games this is not a sufficient replacement for AI! + + + Back-date BSAs + + + + :/MO/gui/resources/emblem-readonly.png:/MO/gui/resources/emblem-readonly.png + + + + + + + Add executables to the blacklist to prevent them from +accessing the virtual file system. This is useful to prevent +unintended programs from being hooked. Hooking unintended +programs may affect the execution of these programs or the +programs you are intentionally running. + + + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended programs may affect the execution of these programs or the programs you are intentionally running. + + + Configure Executables Blacklist + + + false + + + + + + + Resets the window geometries for all windows. This can be useful if a window becomes too small or too large, if a column becomes too thin or too wide, and in similar situations. + + + Resets the window geometries for all windows. This can be useful if a window becomes too small or too large, if a column becomes too thin or too wide, and in similar situations. + + + Reset Window Geometries + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. + + + true + + + + + + + + Diagnostics + + + + + + + + Max Dumps To Keep + + + + + + + Qt::Horizontal + + + + 60 + 20 + + + + + + + + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. + + + + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. + Set "Crash Dumps" above to None to disable crash dump collection. + + + + + + + + + + Hint: right click link and copy link location + + + + Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> + and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. + Sending logs and/or crash dumps to the developers can help investigate issues. + It is recommended to compress large log and dmp files before sending. + + + + true + + + + + + + + + Crash Dumps + + + + + + + Decides which type of crash dumps are collected when injected processes crash. + + + + Decides which type of crash dumps are collected when injected processes crash. + "None" Disables the generation of crash dumps by MO. + "Mini" Default level which generates small dumps (only stack traces). + "Data" Much larger dumps with additional information which may be need (also data segments). + "Full" Even larger dumps with a full memory dump of the process. + + + + + None + + + + + Mini (recommended) + + + + + Data + + + + + Full + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 232 + + + + + + + + + + Log Level + + + + + + + Decides the amount of data printed to "ModOrganizer.log" + + + + Decides the amount of data printed to "ModOrganizer.log". + "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. + + + + + Debug + + + + + Info (recommended) + + + + + Warning + + + + + Error + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + languageBox + styleBox + logLevelBox + usePrereleaseBox + compactBox + categoriesBtn + baseDirEdit + browseBaseDirBtn + downloadDirEdit + browseDownloadDirBtn + modDirEdit + browseModDirBtn + cacheDirEdit + browseCacheDirBtn + profilesDirEdit + browseProfilesDirBtn + overwriteDirEdit + browseOverwriteDirBtn + clearCacheButton + associateButton + knownServersList + preferredServersList + steamUserEdit + steamPassEdit + pluginsList + pluginSettingsList + pluginBlacklist + appIDEdit + mechanismBox + nmmVersionEdit + bsaDateBtn + tabWidget + + + + + + + buttonBox + accepted() + SettingsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SettingsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + -- cgit v1.3.1