summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt72
-rw-r--r--src/aboutdialog.cpp5
-rw-r--r--src/aboutdialog.ui54
-rw-r--r--src/activatemodsdialog.cpp5
-rw-r--r--src/categories.cpp10
-rw-r--r--src/categories.h5
-rw-r--r--src/credentialsdialog.ui16
-rw-r--r--src/directoryrefresher.cpp4
-rw-r--r--src/dlls.manifest.qt510
-rw-r--r--src/downloadmanager.cpp89
-rw-r--r--src/editexecutablesdialog.cpp79
-rw-r--r--src/editexecutablesdialog.h21
-rw-r--r--src/editexecutablesdialog.ui60
-rw-r--r--src/eventfilter.cpp (renamed from src/shared/inject.h)64
-rw-r--r--src/eventfilter.h44
-rw-r--r--src/executableslist.cpp7
-rw-r--r--src/executableslist.h9
-rw-r--r--src/gameinfoimpl.cpp55
-rw-r--r--src/genericicondelegate.cpp2
-rw-r--r--src/helper.cpp10
-rw-r--r--src/helper.h8
-rw-r--r--src/installationmanager.cpp89
-rw-r--r--src/instancemanager.cpp209
-rw-r--r--src/instancemanager.h (renamed from src/safewritefile.h)116
-rw-r--r--src/iuserinterface.h4
-rw-r--r--src/loadmechanism.cpp23
-rw-r--r--src/logbuffer.cpp149
-rw-r--r--src/logbuffer.h4
-rw-r--r--src/main.cpp396
-rw-r--r--src/mainwindow.cpp617
-rw-r--r--src/mainwindow.h41
-rw-r--r--src/mainwindow.ui167
-rw-r--r--src/mo_icon.icobin96367 -> 45874 bytes
-rw-r--r--src/mo_icon.psdbin979131 -> 0 bytes
-rw-r--r--src/moapplication.cpp9
-rw-r--r--src/modflagicondelegate.cpp12
-rw-r--r--src/modinfo.cpp49
-rw-r--r--src/modinfo.h8
-rw-r--r--src/modinfodialog.cpp10
-rw-r--r--src/modinfoforeign.cpp14
-rw-r--r--src/modinfoforeign.h6
-rw-r--r--src/modinfooverwrite.cpp3
-rw-r--r--src/modinforegular.cpp30
-rw-r--r--src/modlist.cpp4
-rw-r--r--src/nexusinterface.cpp32
-rw-r--r--src/nexusinterface.h5
-rw-r--r--src/nxmaccessmanager.cpp20
-rw-r--r--src/nxmaccessmanager.h2
-rw-r--r--src/organizer_en.ts6051
-rw-r--r--src/organizercore.cpp894
-rw-r--r--src/organizercore.h47
-rw-r--r--src/organizerproxy.h1
-rw-r--r--src/persistentcookiejar.cpp8
-rw-r--r--src/persistentcookiejar.h6
-rw-r--r--src/plugincontainer.cpp12
-rw-r--r--src/plugincontainer.h4
-rw-r--r--src/pluginlist.cpp348
-rw-r--r--src/pluginlist.h37
-rw-r--r--src/profile.cpp83
-rw-r--r--src/profile.h35
-rw-r--r--src/profilesdialog.cpp122
-rw-r--r--src/profilesdialog.h3
-rw-r--r--src/profilesdialog.ui14
-rw-r--r--src/resources.qrc3
-rw-r--r--src/resources/mo_icon.pngbin0 -> 1011 bytes
-rw-r--r--src/resources/package.pngbin0 -> 1067 bytes
-rw-r--r--src/safewritefile.cpp71
-rw-r--r--src/selectiondialog.cpp5
-rw-r--r--src/selectiondialog.h2
-rw-r--r--src/selfupdater.cpp341
-rw-r--r--src/selfupdater.h19
-rw-r--r--src/settings.cpp316
-rw-r--r--src/settings.h45
-rw-r--r--src/settingsdialog.cpp85
-rw-r--r--src/settingsdialog.h11
-rw-r--r--src/settingsdialog.ui392
-rw-r--r--src/shared/appconfig.inc1
-rw-r--r--src/shared/directoryentry.cpp9
-rw-r--r--src/shared/directoryentry.h9
-rw-r--r--src/shared/inject.cpp147
-rw-r--r--src/shared/leaktrace.cpp5
-rw-r--r--src/shared/util.cpp8
-rw-r--r--src/spawn.cpp92
-rw-r--r--src/spawn.h2
-rw-r--r--src/splash.pngbin230138 -> 74041 bytes
-rw-r--r--src/transfersavesdialog.cpp68
-rw-r--r--src/transfersavesdialog.h39
-rw-r--r--src/tutorials/tutorial_firststeps_main.js36
-rw-r--r--src/usvfsconnector.cpp176
-rw-r--r--src/usvfsconnector.h80
-rw-r--r--src/version.rc4
91 files changed, 9505 insertions, 2774 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 94b75031..500dc56b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -31,11 +31,11 @@ SET(organizer_SRCS
modlist.cpp
modinfodialog.cpp
modinfo.cpp
- modinfobackup.cpp
- modinfoforeign.cpp
- modinfooverwrite.cpp
- modinforegular.cpp
- modinfowithconflictinfo.cpp
+ modinfobackup.cpp
+ modinfoforeign.cpp
+ modinfooverwrite.cpp
+ modinforegular.cpp
+ modinfowithconflictinfo.cpp
messagedialog.cpp
mainwindow.cpp
main.cpp
@@ -76,15 +76,16 @@ SET(organizer_SRCS
previewdialog.cpp
aboutdialog.cpp
json.cpp
- safewritefile.cpp
modflagicondelegate.cpp
genericicondelegate.cpp
organizerproxy.cpp
viewmarkingscrollbar.cpp
plugincontainer.cpp
organizercore.cpp
+ instancemanager.cpp
+ usvfsconnector.cpp
+ eventfilter.cpp
- shared/inject.cpp
shared/windows_error.cpp
shared/error_report.cpp
shared/directoryentry.cpp
@@ -116,11 +117,11 @@ SET(organizer_HDRS
modlist.h
modinfodialog.h
modinfo.h
- modinfobackup.h
- modinfoforeign.h
- modinfooverwrite.h
- modinforegular.h
- modinfowithconflictinfo.h
+ modinfobackup.h
+ modinfoforeign.h
+ modinfooverwrite.h
+ modinforegular.h
+ modinfowithconflictinfo.h
messagedialog.h
mainwindow.h
loghighlighter.h
@@ -160,7 +161,6 @@ SET(organizer_HDRS
previewdialog.h
aboutdialog.h
json.h
- safewritefile.h
modflagicondelegate.h
genericicondelegate.h
organizerproxy.h
@@ -168,8 +168,10 @@ SET(organizer_HDRS
plugincontainer.h
organizercore.h
iuserinterface.h
+ instancemanager.h
+ usvfsconnector.h
+ eventfilter.h
- shared/inject.h
shared/windows_error.h
shared/error_report.h
shared/directoryentry.h
@@ -235,15 +237,17 @@ FIND_PACKAGE(Qt5Declarative REQUIRED)
FIND_PACKAGE(Qt5Network REQUIRED)
FIND_PACKAGE(Qt5WinExtras REQUIRED)
FIND_PACKAGE(Qt5WebKitWidgets REQUIRED)
+FIND_PACKAGE(Qt5LinguistTools)
QT5_WRAP_UI(organizer_UIHDRS ${organizer_UIS})
QT5_ADD_RESOURCES(organizer_RCCPPS ${organizer_QRCS})
+QT5_CREATE_TRANSLATION(organizer_translations_qm ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/organizer_en.ts)
INCLUDE_DIRECTORIES(${Qt5Declarative_INCLUDES})
ADD_DEFINITIONS(-DQT_MESSAGELOGCONTEXT)
# Boost
-SET(Boost_USE_STATIC_LIBS ON)
+SET(Boost_USE_STATIC_LIBS OFF)
SET(Boost_USE_MULTITHREADED ON)
SET(Boost_USE_STATIC_RUNTIME OFF)
FIND_PACKAGE(Boost REQUIRED)
@@ -264,31 +268,49 @@ SET(project_path "${default_project_path}" CACHE PATH "path to the other mo proj
SET(lib_path "${project_path}/../../install/libs")
-MESSAGE(STATUS ${lib_path})
-MESSAGE(STATUS ${project_path})
-
INCLUDE_DIRECTORIES(${project_path}/uibase/src
${project_path}/bsatk/src
${project_path}/esptk/src
${project_path}/archive/src
- ${project_path}/game_features/src)
+ ${project_path}/../usvfs/usvfs
+ ${project_path}/game_gamebryo/src
+ ${project_path}/game_features/src
+ ${project_path}/githubpp/src)
+
+
INCLUDE_DIRECTORIES(shared ${ZLIB_INCLUDE_DIRS})
LINK_DIRECTORIES(${lib_path}
${project_path}/../zlib/lib)
-ADD_DEFINITIONS(-D_UNICODE -DUNICODE -DNOMINMAX -D_CRT_SECURE_NO_WARNINGS)
+EXECUTE_PROCESS(
+ COMMAND git log -1 --format=%h
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ OUTPUT_VARIABLE GIT_COMMIT_HASH
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+)
+
+ADD_DEFINITIONS(-D_UNICODE -DUNICODE -DNOMINMAX -D_CRT_SECURE_NO_WARNINGS -DGITID="${GIT_COMMIT_HASH}")
-ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIHDRS} ${organizer_RCS} ${organizer_RCCPPS})
+IF("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
+ SET(usvfs_name usvfs_x64)
+ELSE()
+ SET(usvfs_name usvfs_x86)
+ENDIF()
+
+ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIHDRS} ${organizer_RCS} ${organizer_RCCPPS} ${organizer_translations_qm})
TARGET_LINK_LIBRARIES(ModOrganizer
Qt5::Widgets Qt5::WinExtras Qt5::WebKitWidgets
${Boost_LIBRARIES}
zlibstatic
- uibase esptk bsatk
+ uibase esptk bsatk githubpp
+ ${usvfs_name}
Dbghelp advapi32 Version Shlwapi)
-
-SET_TARGET_PROPERTIES(ModOrganizer PROPERTIES COMPILE_FLAGS /GL)
+IF (NOT "${OPTIMIZE_COMPILE_FLAGS}" STREQUAL "")
+ SET_TARGET_PROPERTIES(ModOrganizer PROPERTIES COMPILE_FLAGS_RELWITHDEBINFO
+ ${OPTIMIZE_COMPILE_FLAGS})
+ENDIF()
SET_TARGET_PROPERTIES(ModOrganizer PROPERTIES LINK_FLAGS_RELWITHDEBINFO
- "/LTCG /INCREMENTAL:NO /LARGEADDRESSAWARE /OPT:REF /OPT:ICF")
+ "/LARGEADDRESSAWARE ${OPTIMIZE_LINK_FLAGS}")
QT5_USE_MODULES(ModOrganizer Widgets Declarative Network WebKitWidgets)
diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp
index 3657a10d..ed57a217 100644
--- a/src/aboutdialog.cpp
+++ b/src/aboutdialog.cpp
@@ -54,10 +54,13 @@ AboutDialog::AboutDialog(const QString &version, QWidget *parent)
addLicense("RRZE Icon Set", LICENSE_CCBY3);
addLicense("Icons by Lorc, Delapouite and sbed available on http://game-icons.net", LICENSE_CCBY3);
addLicense("Castle Core", LICENSE_APACHE2);
+ addLicense("LOOT", LICENSE_GPL3);
ui->nameLabel->setText(QString("<span style=\"font-size:12pt; font-weight:600;\">%1 %2</span>").arg(ui->nameLabel->text()).arg(version));
-#ifdef HGID
+#if defined(HGID)
ui->revisionLabel->setText(ui->revisionLabel->text() + " " + HGID);
+#elif defined(GITID)
+ ui->revisionLabel->setText(ui->revisionLabel->text() + " " + GITID);
#else
ui->revisionLabel->setText(ui->revisionLabel->text() + " unknown");
#endif
diff --git a/src/aboutdialog.ui b/src/aboutdialog.ui
index ea5d2141..618077ed 100644
--- a/src/aboutdialog.ui
+++ b/src/aboutdialog.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>508</width>
- <height>335</height>
+ <width>604</width>
+ <height>369</height>
</rect>
</property>
<property name="windowTitle">
@@ -19,12 +19,49 @@
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
<widget class="QLabel" name="iconLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>250</width>
+ <height>250</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>250</width>
+ <height>250</height>
+ </size>
+ </property>
<property name="text">
<string/>
</property>
<property name="pixmap">
- <pixmap resource="resources.qrc">:/MO/gui/mo_icon.ico</pixmap>
+ <pixmap resource="resources.qrc">:/MO/gui/splash</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
@@ -83,7 +120,7 @@
<item>
<widget class="QLabel" name="label_3">
<property name="text">
- <string notr="true">Copyright 2011-2015 Sebastian Herbord</string>
+ <string notr="true">Copyright 2011-2016 Sebastian Herbord</string>
</property>
</widget>
</item>
@@ -114,7 +151,7 @@
</widget>
<widget class="QWidget" name="credits">
<attribute name="title">
- <string>Credits</string>
+ <string>Thanks</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
@@ -202,7 +239,7 @@
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
- <string>Others</string>
+ <string>Other Supporter</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
@@ -265,6 +302,11 @@
<string notr="true">thosrtanner</string>
</property>
</item>
+ <item>
+ <property name="text">
+ <string>ogrotten</string>
+ </property>
+ </item>
</widget>
</item>
</layout>
diff --git a/src/activatemodsdialog.cpp b/src/activatemodsdialog.cpp
index 5ef0a4e5..c7e3dca2 100644
--- a/src/activatemodsdialog.cpp
+++ b/src/activatemodsdialog.cpp
@@ -35,13 +35,8 @@ ActivateModsDialog::ActivateModsDialog(SaveGameInfo::MissingAssets const &missin
QTableWidget *modsTable = findChild<QTableWidget*>("modsTable");
QHeaderView *headerView = modsTable->horizontalHeader();
-#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
headerView->setSectionResizeMode(0, QHeaderView::Stretch);
headerView->setSectionResizeMode(1, QHeaderView::Interactive);
-#else
- headerView->setResizeMode(0, QHeaderView::Stretch);
- headerView->setResizeMode(1, QHeaderView::Interactive);
-#endif
int row = 0;
diff --git a/src/categories.cpp b/src/categories.cpp
index 59291a49..be6af9ae 100644
--- a/src/categories.cpp
+++ b/src/categories.cpp
@@ -44,6 +44,10 @@ QString CategoryFactory::categoriesFilePath()
CategoryFactory::CategoryFactory()
{
atexit(&cleanup);
+}
+
+void CategoryFactory::loadCategories()
+{
reset();
QFile categoryFile(categoriesFilePath());
@@ -188,7 +192,7 @@ int CategoryFactory::addCategory(const QString &name, const std::vector<int> &ne
void CategoryFactory::addCategory(int id, const QString &name, const std::vector<int> &nexusIDs, int parentID)
{
- int index = m_Categories.size();
+ int index = static_cast<int>(m_Categories.size());
m_Categories.push_back(Category(index, id, name, nexusIDs, parentID));
for (int nexusID : nexusIDs) {
m_NexusMap[nexusID] = index;
@@ -317,9 +321,11 @@ int CategoryFactory::getCategoryIndex(int ID) const
int CategoryFactory::getCategoryID(const QString &name) const
{
- auto iter = std::find_if(m_Categories.begin(), m_Categories.end(), [name] (const Category &cat) -> bool {
+ auto iter = std::find_if(m_Categories.begin(), m_Categories.end(),
+ [name] (const Category &cat) -> bool {
return cat.m_Name == name;
});
+
if (iter != m_Categories.end()) {
return iter->m_ID;
} else {
diff --git a/src/categories.h b/src/categories.h
index 29e794d8..66299c30 100644
--- a/src/categories.h
+++ b/src/categories.h
@@ -76,6 +76,11 @@ public:
void reset();
/**
+ * @brief read categories from file
+ */
+ void loadCategories();
+
+ /**
* @brief save the categories to the categories.dat file
**/
void saveCategories();
diff --git a/src/credentialsdialog.ui b/src/credentialsdialog.ui
index 3dd9a62a..918441d8 100644
--- a/src/credentialsdialog.ui
+++ b/src/credentialsdialog.ui
@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
- <width>282</width>
+ <width>294</width>
<height>156</height>
</rect>
</property>
@@ -25,29 +25,25 @@
</widget>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
- <item>
+ <item row="0" column="1">
<widget class="QLineEdit" name="usernameEdit"/>
</item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
+ <item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Password</string>
</property>
</widget>
</item>
- <item>
+ <item row="1" column="1">
<widget class="QLineEdit" name="passwordEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp
index f50a717e..0a8b82be 100644
--- a/src/directoryrefresher.cpp
+++ b/src/directoryrefresher.cpp
@@ -144,7 +144,7 @@ void DirectoryRefresher::refresh()
m_DirectoryStructure = new DirectoryEntry(L"data", nullptr, 0);
- IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+ IPluginGame *game = qApp->property("managed_game").value<IPluginGame*>();
std::wstring dataDirectory = QDir::toNativeSeparators(game->dataDirectory().absolutePath()).toStdWString();
m_DirectoryStructure->addFromOrigin(L"data", dataDirectory, 0);
@@ -159,7 +159,7 @@ void DirectoryRefresher::refresh()
} catch (const std::exception &e) {
emit error(tr("failed to read mod (%1): %2").arg(iter->modName, e.what()));
}
- emit progress((i * 100) / m_Mods.size() + 1);
+ emit progress((i * 100) / static_cast<int>(m_Mods.size()) + 1);
}
emit progress(100);
diff --git a/src/dlls.manifest.qt5 b/src/dlls.manifest.qt5
index 5c64e4f9..fedcfa26 100644
--- a/src/dlls.manifest.qt5
+++ b/src/dlls.manifest.qt5
@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="dlls" version="1.0.0.0" processorArchitecture="x86"/>
- <file name="icuin54.dll"/>
- <file name="icuuc54.dll"/>
- <file name="icudt54.dll"/>
+ <file name="icuin53.dll"/>
+ <file name="icuuc53.dll"/>
+ <file name="icudt53.dll"/>
+ <file name="libeay32.dll"/>
+ <file name="ssleay32.dll"/>
<file name="Qt5Core.dll"/>
<file name="Qt5Declarative.dll"/>
<file name="Qt5Gui.dll"/>
@@ -26,4 +28,4 @@
<file name="Qt5WinExtras.dll"/>
<file name="Qt5Xml.dll"/>
<file name="Qt5XmlPatterns.dll"/>
-</assembly>
+</assembly> \ No newline at end of file
diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp
index e327e55a..cb954ebe 100644
--- a/src/downloadmanager.cpp
+++ b/src/downloadmanager.cpp
@@ -88,7 +88,8 @@ DownloadManager::DownloadInfo *DownloadManager::DownloadInfo::createFromMeta(con
QString fileName = QFileInfo(filePath).fileName();
if (fileName.endsWith(UNFINISHED)) {
- info->m_FileName = fileName.mid(0, fileName.length() - strlen(UNFINISHED));
+ info->m_FileName = fileName.mid(
+ 0, fileName.length() - static_cast<int>(strlen(UNFINISHED)));
info->m_State = STATE_PAUSED;
} else {
info->m_FileName = fileName;
@@ -471,7 +472,7 @@ void DownloadManager::addNXMDownload(const QString &url)
void DownloadManager::removeFile(int index, bool deleteFile)
{
if (index >= m_ActiveDownloads.size()) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("remove: invalid download index %1").arg(index));
}
DownloadInfo *download = m_ActiveDownloads.at(index);
@@ -538,7 +539,7 @@ void DownloadManager::refreshAlphabeticalTranslation()
void DownloadManager::restoreDownload(int index)
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("restore: invalid download index: %1").arg(index));
}
DownloadInfo *download = m_ActiveDownloads.at(index);
@@ -571,7 +572,7 @@ void DownloadManager::removeDownload(int index, bool deleteFile)
}
} else {
if (index >= m_ActiveDownloads.size()) {
- reportError(tr("invalid index %1").arg(index));
+ reportError(tr("remove: invalid download index %1").arg(index));
return;
}
@@ -589,7 +590,7 @@ void DownloadManager::removeDownload(int index, bool deleteFile)
void DownloadManager::cancelDownload(int index)
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- reportError(tr("invalid index %1").arg(index));
+ reportError(tr("cancel: invalid download index %1").arg(index));
return;
}
@@ -602,7 +603,7 @@ void DownloadManager::cancelDownload(int index)
void DownloadManager::pauseDownload(int index)
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- reportError(tr("invalid index %1").arg(index));
+ reportError(tr("pause: invalid download index %1").arg(index));
return;
}
@@ -622,7 +623,7 @@ void DownloadManager::pauseDownload(int index)
void DownloadManager::resumeDownload(int index)
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- reportError(tr("invalid index %1").arg(index));
+ reportError(tr("resume: invalid download index %1").arg(index));
return;
}
DownloadInfo *info = m_ActiveDownloads[index];
@@ -633,7 +634,7 @@ void DownloadManager::resumeDownload(int index)
void DownloadManager::resumeDownloadInt(int index)
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- reportError(tr("invalid index %1").arg(index));
+ reportError(tr("resume (int): invalid download index %1").arg(index));
return;
}
DownloadInfo *info = m_ActiveDownloads[index];
@@ -673,7 +674,7 @@ DownloadManager::DownloadInfo *DownloadManager::downloadInfoByID(unsigned int id
void DownloadManager::queryInfo(int index)
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- reportError(tr("invalid index %1").arg(index));
+ reportError(tr("query: invalid download index %1").arg(index));
return;
}
DownloadInfo *info = m_ActiveDownloads[index];
@@ -693,19 +694,14 @@ void DownloadManager::queryInfo(int index)
QString ignore;
NexusInterface::interpretNexusFileName(fileName, ignore, info->m_FileInfo->modID, true);
if (info->m_FileInfo->modID < 0) {
- QString modIDString;
- while (modIDString.isEmpty()) {
- modIDString = QInputDialog::getText(nullptr, tr("Please enter the nexus mod id"), tr("Mod ID:"), QLineEdit::Normal,
- QString(), nullptr, 0, Qt::ImhFormattedNumbersOnly);
- if (modIDString.isNull()) {
- // canceled
- return;
- } else if (modIDString.contains(QRegExp("[^0-9]"))) {
- qDebug("illegal character in mod-id");
- modIDString.clear();
- }
- }
- info->m_FileInfo->modID = modIDString.toInt(nullptr, 10);
+ bool ok = false;
+ int modId = QInputDialog::getInt(
+ nullptr, tr("Please enter the nexus mod id"), tr("Mod ID:"), 1, 1,
+ std::numeric_limits<int>::max(), 1, &ok);
+ // careful now: while the dialog was displayed, events were processed.
+ // the download list might have changed and our info-ptr invalidated.
+ m_ActiveDownloads[index]->m_FileInfo->modID = modId;
+ return;
}
}
info->m_ReQueried = true;
@@ -726,7 +722,7 @@ int DownloadManager::numPendingDownloads() const
std::pair<int, int> DownloadManager::getPendingDownload(int index)
{
if ((index < 0) || (index >= m_PendingDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("get pending: invalid download index %1").arg(index));
}
return m_PendingDownloads.at(index);
@@ -735,7 +731,7 @@ std::pair<int, int> DownloadManager::getPendingDownload(int index)
QString DownloadManager::getFilePath(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("get path: invalid download index %1").arg(index));
}
return m_OutputDirectory + "/" + m_ActiveDownloads.at(index)->m_FileName;
@@ -756,24 +752,27 @@ QString DownloadManager::getFileTypeString(int fileType)
QString DownloadManager::getDisplayName(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("display name: invalid download index %1").arg(index));
}
DownloadInfo *info = m_ActiveDownloads.at(index);
+ QTextDocument doc;
if (!info->m_FileInfo->name.isEmpty()) {
- return QString("%1 (%2, v%3)").arg(info->m_FileInfo->name)
+ doc.setHtml(info->m_FileInfo->name);
+ return QString("%1 (%2, v%3)").arg(doc.toPlainText())
.arg(getFileTypeString(info->m_FileInfo->fileCategory))
.arg(info->m_FileInfo->version.displayString());
} else {
- return info->m_FileName;
+ doc.setHtml(info->m_FileName);
+ return doc.toPlainText();
}
}
QString DownloadManager::getFileName(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("file name: invalid download index %1").arg(index));
}
return m_ActiveDownloads.at(index)->m_FileName;
@@ -782,7 +781,7 @@ QString DownloadManager::getFileName(int index) const
QDateTime DownloadManager::getFileTime(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("file time: invalid download index %1").arg(index));
}
DownloadInfo *info = m_ActiveDownloads.at(index);
@@ -796,7 +795,7 @@ QDateTime DownloadManager::getFileTime(int index) const
qint64 DownloadManager::getFileSize(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("file size: invalid download index %1").arg(index));
}
return m_ActiveDownloads.at(index)->m_TotalSize;
@@ -806,7 +805,7 @@ qint64 DownloadManager::getFileSize(int index) const
int DownloadManager::getProgress(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("progress: invalid download index %1").arg(index));
}
return m_ActiveDownloads.at(index)->m_Progress;
@@ -816,7 +815,7 @@ int DownloadManager::getProgress(int index) const
DownloadManager::DownloadState DownloadManager::getState(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("state: invalid download index %1").arg(index));
}
return m_ActiveDownloads.at(index)->m_State;
@@ -826,7 +825,7 @@ DownloadManager::DownloadState DownloadManager::getState(int index) const
bool DownloadManager::isInfoIncomplete(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("infocomplete: invalid download index %1").arg(index));
}
DownloadInfo *info = m_ActiveDownloads.at(index);
@@ -841,7 +840,7 @@ bool DownloadManager::isInfoIncomplete(int index) const
int DownloadManager::getModID(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("mod id: invalid download index %1").arg(index));
}
return m_ActiveDownloads.at(index)->m_FileInfo->modID;
}
@@ -849,7 +848,7 @@ int DownloadManager::getModID(int index) const
bool DownloadManager::isHidden(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("ishidden: invalid download index %1").arg(index));
}
return m_ActiveDownloads.at(index)->m_Hidden;
}
@@ -858,7 +857,7 @@ bool DownloadManager::isHidden(int index) const
const ModRepositoryFileInfo *DownloadManager::getFileInfo(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("file info: invalid download index %1").arg(index));
}
return m_ActiveDownloads.at(index)->m_FileInfo;
@@ -868,7 +867,7 @@ const ModRepositoryFileInfo *DownloadManager::getFileInfo(int index) const
void DownloadManager::markInstalled(int index)
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("mark installed: invalid download index %1").arg(index));
}
DownloadInfo *info = m_ActiveDownloads.at(index);
@@ -883,7 +882,7 @@ void DownloadManager::markInstalled(int index)
void DownloadManager::markUninstalled(int index)
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("mark uninstalled: invalid download index %1").arg(index));
}
DownloadInfo *info = m_ActiveDownloads.at(index);
@@ -1062,7 +1061,9 @@ void DownloadManager::nxmDescriptionAvailable(int, QVariant userData, QVariant r
DownloadInfo *info = downloadInfoByID(userData.toInt());
if (info == nullptr) return;
info->m_FileInfo->categoryID = result["category_id"].toInt();
- info->m_FileInfo->modName = result["name"].toString().trimmed();
+ QTextDocument doc;
+ doc.setHtml(result["name"].toString().trimmed());
+ info->m_FileInfo->modName = doc.toPlainText();
info->m_FileInfo->newestVersion.parse(result["version"].toString());
if (info->m_FileInfo->fileID != 0) {
setState(info, STATE_READY);
@@ -1120,7 +1121,7 @@ void DownloadManager::nxmFilesAvailable(int, QVariant userData, QVariant resultD
bool found = false;
- foreach(QVariant file, result) {
+ for (QVariant file : result) {
QVariantMap fileInfo = file.toMap();
QString fileName = fileInfo["uri"].toString();
QString fileNameVariant = fileName.mid(0).replace(' ', '_');
@@ -1131,12 +1132,10 @@ void DownloadManager::nxmFilesAvailable(int, QVariant userData, QVariant resultD
if (!info->m_FileInfo->version.isValid()) {
info->m_FileInfo->version = info->m_FileInfo->newestVersion;
}
- //Nexus has HTMLd these so unhtml them if necessary
+ // we receive some names html-encoded. This is used to decode it
QTextDocument doc;
- doc.setHtml(info->m_FileInfo->modName);
+ doc.setHtml(fileInfo["modName"].toString());
info->m_FileInfo->modName = doc.toPlainText();
- doc.setHtml(info->m_FileInfo->name);
- info->m_FileInfo->name = doc.toPlainText();
info->m_FileInfo->fileCategory = convertFileCategory(fileInfo["category_id"].toInt());
info->m_FileInfo->fileTime = matchDate(fileInfo["date"].toString());
info->m_FileInfo->fileID = fileInfo["id"].toInt();
@@ -1152,7 +1151,7 @@ void DownloadManager::nxmFilesAvailable(int, QVariant userData, QVariant resultD
emit showMessage(tr("No matching file found on Nexus! Maybe this file is no longer available or it was renamed?"));
} else {
SelectionDialog selection(tr("No file on Nexus matches the selected file by name. Please manually choose the correct one."));
- foreach(QVariant file, result) {
+ for (QVariant file : result) {
QVariantMap fileInfo = file.toMap();
selection.addChoice(fileInfo["uri"].toString(), "", file);
}
diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp
index b9d548a6..f76323e8 100644
--- a/src/editexecutablesdialog.cpp
+++ b/src/editexecutablesdialog.cpp
@@ -21,6 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "ui_editexecutablesdialog.h"
#include "filedialogmemory.h"
#include "stackdata.h"
+#include "modlist.h"
#include <QMessageBox>
#include <Shellapi.h>
#include <utility.h>
@@ -29,15 +30,20 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
using namespace MOBase;
using namespace MOShared;
-EditExecutablesDialog::EditExecutablesDialog(const ExecutablesList &executablesList, QWidget *parent)
+EditExecutablesDialog::EditExecutablesDialog(
+ const ExecutablesList &executablesList, const ModList &modList,
+ Profile *profile, QWidget *parent)
: TutorableDialog("EditExecutables", parent)
, ui(new Ui::EditExecutablesDialog)
, m_CurrentItem(nullptr)
, m_ExecutablesList(executablesList)
+ , m_Profile(profile)
{
ui->setupUi(this);
refreshExecutablesWidget();
+
+ ui->newFilesModBox->addItems(modList.allMods());
}
EditExecutablesDialog::~EditExecutablesDialog()
@@ -85,28 +91,31 @@ void EditExecutablesDialog::resetInput()
ui->argumentsEdit->setText("");
ui->appIDOverwriteEdit->clear();
ui->overwriteAppIDBox->setChecked(false);
- ui->closeCheckBox->setChecked(false);
ui->useAppIconCheckBox->setChecked(false);
+ ui->newFilesModCheckBox->setChecked(false);
m_CurrentItem = nullptr;
}
void EditExecutablesDialog::saveExecutable()
{
- m_ExecutablesList.updateExecutable(ui->titleEdit->text(),
- QDir::fromNativeSeparators(ui->binaryEdit->text()),
- ui->argumentsEdit->text(),
- QDir::fromNativeSeparators(ui->workingDirEdit->text()),
- (ui->closeCheckBox->checkState() == Qt::Checked) ?
- ExecutableInfo::CloseMOStyle::DEFAULT_CLOSE
- : ExecutableInfo::CloseMOStyle::DEFAULT_STAY,
- ui->overwriteAppIDBox->isChecked() ?
- ui->appIDOverwriteEdit->text() : "",
- Executable::UseApplicationIcon | Executable::CustomExecutable,
- (ui->useAppIconCheckBox->isChecked() ?
- Executable::UseApplicationIcon : Executable::Flags())
- | Executable::CustomExecutable);
+ m_ExecutablesList.updateExecutable(
+ ui->titleEdit->text(),
+ QDir::fromNativeSeparators(ui->binaryEdit->text()),
+ ui->argumentsEdit->text(),
+ QDir::fromNativeSeparators(ui->workingDirEdit->text()),
+ ui->overwriteAppIDBox->isChecked() ?
+ ui->appIDOverwriteEdit->text() : "",
+ Executable::UseApplicationIcon | Executable::CustomExecutable,
+ (ui->useAppIconCheckBox->isChecked() ?
+ Executable::UseApplicationIcon : Executable::Flags())
+ | Executable::CustomExecutable);
+
+ if (ui->newFilesModCheckBox->isChecked()) {
+ m_Profile->storeSetting("custom_overwrites", ui->titleEdit->text(),
+ ui->newFilesModBox->currentText());
}
+}
void EditExecutablesDialog::delayedRefresh()
@@ -130,15 +139,17 @@ void EditExecutablesDialog::on_addButton_clicked()
void EditExecutablesDialog::on_browseButton_clicked()
{
- QString binaryName = FileDialogMemory::getOpenFileName("editExecutableBinary", this,
- tr("Select a binary"), QString(), tr("Executable (%1)").arg("*.exe *.bat *.jar"));
+ QString binaryName = FileDialogMemory::getOpenFileName(
+ "editExecutableBinary", this, tr("Select a binary"), QString(),
+ tr("Executable (%1)").arg("*.exe *.bat *.jar"));
if (binaryName.endsWith(".jar", Qt::CaseInsensitive)) {
QString binaryPath;
{ // try to find java automatically
std::wstring binaryNameW = ToWString(binaryName);
WCHAR buffer[MAX_PATH];
- if (::FindExecutableW(binaryNameW.c_str(), nullptr, buffer) > (HINSTANCE)32) {
+ if (::FindExecutableW(binaryNameW.c_str(), nullptr, buffer)
+ > reinterpret_cast<HINSTANCE>(32)) {
DWORD binaryType = 0UL;
if (!::GetBinaryTypeW(binaryNameW.c_str(), &binaryType)) {
qDebug("failed to determine binary type of \"%ls\": %lu", binaryNameW.c_str(), ::GetLastError());
@@ -214,11 +225,15 @@ bool EditExecutablesDialog::executableChanged()
{
if (m_CurrentItem != nullptr) {
Executable const &selectedExecutable(m_ExecutablesList.find(m_CurrentItem->text()));
+
+ QString storedCustomOverwrite = m_Profile->setting("custom_overwrites", selectedExecutable.m_Title).toString();
+
return selectedExecutable.m_Arguments != ui->argumentsEdit->text()
|| selectedExecutable.m_SteamAppID != ui->appIDOverwriteEdit->text()
+ || !storedCustomOverwrite.isEmpty() != ui->newFilesModCheckBox->isChecked()
+ || !storedCustomOverwrite.isEmpty() && (storedCustomOverwrite != ui->newFilesModBox->currentText())
|| selectedExecutable.m_WorkingDirectory != QDir::fromNativeSeparators(ui->workingDirEdit->text())
|| selectedExecutable.m_BinaryInfo.absoluteFilePath() != QDir::fromNativeSeparators(ui->binaryEdit->text())
- || (selectedExecutable.m_CloseMO == ExecutableInfo::CloseMOStyle::DEFAULT_CLOSE) != ui->closeCheckBox->isChecked()
|| selectedExecutable.usesOwnIcon() != ui->useAppIconCheckBox->isChecked();
} else {
QFileInfo fileInfo(ui->binaryEdit->text());
@@ -291,14 +306,6 @@ void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex &cur
ui->binaryEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath()));
ui->argumentsEdit->setText(selectedExecutable.m_Arguments);
ui->workingDirEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_WorkingDirectory));
- ui->closeCheckBox->setChecked(selectedExecutable.m_CloseMO == ExecutableInfo::CloseMOStyle::DEFAULT_CLOSE);
- if (selectedExecutable.m_CloseMO == ExecutableInfo::CloseMOStyle::NEVER_CLOSE) {
- ui->closeCheckBox->setEnabled(false);
- ui->closeCheckBox->setToolTip(tr("MO must be kept running or this application will not work correctly."));
- } else {
- ui->closeCheckBox->setEnabled(true);
- ui->closeCheckBox->setToolTip(tr("If checked, MO will be closed once the specified executable is run."));
- }
ui->removeButton->setEnabled(selectedExecutable.isCustom());
ui->overwriteAppIDBox->setChecked(!selectedExecutable.m_SteamAppID.isEmpty());
if (!selectedExecutable.m_SteamAppID.isEmpty()) {
@@ -307,5 +314,23 @@ void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex &cur
ui->appIDOverwriteEdit->clear();
}
ui->useAppIconCheckBox->setChecked(selectedExecutable.usesOwnIcon());
+
+ int index = -1;
+
+ QString customOverwrite = m_Profile->setting("custom_overwrites", selectedExecutable.m_Title).toString();
+ if (!customOverwrite.isEmpty()) {
+ index = ui->newFilesModBox->findText(customOverwrite);
+ qDebug("find %s -> %d", qPrintable(customOverwrite), index);
+ }
+
+ ui->newFilesModCheckBox->setChecked(index != -1);
+ if (index != -1) {
+ ui->newFilesModBox->setCurrentIndex(index);
+ }
}
}
+
+void EditExecutablesDialog::on_newFilesModCheckBox_toggled(bool checked)
+{
+ ui->newFilesModBox->setEnabled(checked);
+}
diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h
index 4f6c5315..0f3dbaff 100644
--- a/src/editexecutablesdialog.h
+++ b/src/editexecutablesdialog.h
@@ -24,11 +24,16 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QListWidgetItem>
#include <QTimer>
#include "executableslist.h"
+#include "profile.h"
namespace Ui {
class EditExecutablesDialog;
}
+
+class ModList;
+
+
/**
* @brief Dialog to manage the list of executables
**/
@@ -44,7 +49,10 @@ public:
* @param executablesList current list of executables
* @param parent parent widget
**/
- explicit EditExecutablesDialog(const ExecutablesList &executablesList, QWidget *parent = 0);
+ explicit EditExecutablesDialog(const ExecutablesList &executablesList,
+ const ModList &modList,
+ Profile *profile,
+ QWidget *parent = 0);
~EditExecutablesDialog();
@@ -56,6 +64,10 @@ public:
ExecutablesList getExecutablesList() const;
void saveExecutable();
+
+private slots:
+ void on_newFilesModCheckBox_toggled(bool checked);
+
private slots:
void on_binaryEdit_textChanged(const QString &arg1);
@@ -89,12 +101,13 @@ private:
bool executableChanged();
private:
- Ui::EditExecutablesDialog *ui;
+ Ui::EditExecutablesDialog *ui;
- QListWidgetItem *m_CurrentItem;
+ QListWidgetItem *m_CurrentItem;
- ExecutablesList m_ExecutablesList;
+ ExecutablesList m_ExecutablesList;
+ Profile *m_Profile;
};
#endif // EDITEXECUTABLESDIALOG_H
diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui
index abb6fe68..b3543c95 100644
--- a/src/editexecutablesdialog.ui
+++ b/src/editexecutablesdialog.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>384</width>
- <height>446</height>
+ <width>426</width>
+ <height>460</height>
</rect>
</property>
<property name="windowTitle">
@@ -165,22 +165,37 @@ Right now the only case I know of where this needs to be overwritten is for the
</layout>
</item>
<item>
- <widget class="QCheckBox" name="closeCheckBox">
- <property name="toolTip">
- <string>If checked, MO will be closed once the specified executable is run.</string>
- </property>
- <property name="whatsThis">
- <string>If checked, MO will be closed once the specified executable is run.</string>
- </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_9" stretch="0,1">
+ <item>
+ <widget class="QCheckBox" name="newFilesModCheckBox">
+ <property name="toolTip">
+ <string>If this is enabled, new files are created in the specified mod instead of the &quot;Overwrite&quot; mod.</string>
+ </property>
+ <property name="text">
+ <string>Create Files in Mod instead of Overwrite (*)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="newFilesModBox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="useAppIconCheckBox">
<property name="text">
- <string>Close MO when started</string>
+ <string>Use Application's Icon for shortcuts</string>
</property>
</widget>
</item>
<item>
- <widget class="QCheckBox" name="useAppIconCheckBox">
+ <widget class="QLabel" name="label_5">
<property name="text">
- <string>Use Application's Icon for shortcuts</string>
+ <string>(*) This setting is profile-specific</string>
</property>
</widget>
</item>
@@ -247,11 +262,24 @@ Right now the only case I know of where this needs to be overwritten is for the
</layout>
</item>
</layout>
- <zorder>executablesListBox</zorder>
- <zorder>closeButton</zorder>
- <zorder>closeCheckBox</zorder>
- <zorder>useAppIconCheckBox</zorder>
</widget>
+ <tabstops>
+ <tabstop>executablesListBox</tabstop>
+ <tabstop>titleEdit</tabstop>
+ <tabstop>binaryEdit</tabstop>
+ <tabstop>browseButton</tabstop>
+ <tabstop>workingDirEdit</tabstop>
+ <tabstop>browseDirButton</tabstop>
+ <tabstop>argumentsEdit</tabstop>
+ <tabstop>overwriteAppIDBox</tabstop>
+ <tabstop>appIDOverwriteEdit</tabstop>
+ <tabstop>newFilesModCheckBox</tabstop>
+ <tabstop>newFilesModBox</tabstop>
+ <tabstop>useAppIconCheckBox</tabstop>
+ <tabstop>addButton</tabstop>
+ <tabstop>removeButton</tabstop>
+ <tabstop>closeButton</tabstop>
+ </tabstops>
<resources/>
<connections/>
</ui>
diff --git a/src/shared/inject.h b/src/eventfilter.cpp
index 7f4f2962..69327f79 100644
--- a/src/shared/inject.h
+++ b/src/eventfilter.cpp
@@ -1,31 +1,33 @@
-/*
-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 <http://www.gnu.org/licenses/>.
-*/
-
-#pragma once
-
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-
-#include <string>
-
-namespace MOShared {
-
-void injectDLL(HANDLE processHandle, HANDLE threadHandle, const std::string &dllname, const std::wstring &profileName, int logLevel);
-
-}
+/*
+Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "eventfilter.h"
+
+EventFilter::EventFilter(QObject *parent,
+ const EventFilter::HandlerFunc &handler)
+ : QObject(parent)
+ , m_Handler(handler)
+{
+}
+
+bool EventFilter::eventFilter(QObject *obj, QEvent *event)
+{
+ return m_Handler(obj, event);
+}
diff --git a/src/eventfilter.h b/src/eventfilter.h
new file mode 100644
index 00000000..2400a853
--- /dev/null
+++ b/src/eventfilter.h
@@ -0,0 +1,44 @@
+/*
+Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+
+#include <QObject>
+#include <functional>
+
+
+class EventFilter : public QObject {
+
+ Q_OBJECT
+
+ typedef std::function<bool (QObject*, QEvent*)> HandlerFunc;
+
+public:
+
+ EventFilter(QObject *parent, const HandlerFunc &handler);
+
+ virtual bool eventFilter(QObject *obj , QEvent *event) override;
+
+private:
+
+ HandlerFunc m_Handler;
+
+};
+
diff --git a/src/executableslist.cpp b/src/executableslist.cpp
index a4511ade..9fe4596c 100644
--- a/src/executableslist.cpp
+++ b/src/executableslist.cpp
@@ -50,7 +50,6 @@ void ExecutablesList::init(IPluginGame const *game)
info.binary().absoluteFilePath(),
info.arguments().join(" "),
info.workingDirectory().absolutePath(),
- info.closeMO(),
info.steamAppID());
}
}
@@ -135,7 +134,6 @@ void ExecutablesList::updateExecutable(const QString &title,
const QString &executableName,
const QString &arguments,
const QString &workingDirectory,
- ExecutableInfo::CloseMOStyle closeMO,
const QString &steamAppID,
Executable::Flags mask,
Executable::Flags flags)
@@ -146,7 +144,6 @@ void ExecutablesList::updateExecutable(const QString &title,
if (existingExe != m_Executables.end()) {
existingExe->m_Title = title;
- existingExe->m_CloseMO = closeMO;
existingExe->m_Flags &= ~mask;
existingExe->m_Flags |= flags;
// for pre-configured executables don't overwrite settings we didn't store
@@ -162,7 +159,6 @@ void ExecutablesList::updateExecutable(const QString &title,
} else {
Executable newExe;
newExe.m_Title = title;
- newExe.m_CloseMO = closeMO;
newExe.m_BinaryInfo = file;
newExe.m_Arguments = arguments;
newExe.m_WorkingDirectory = workingDirectory;
@@ -186,12 +182,11 @@ void ExecutablesList::remove(const QString &title)
void ExecutablesList::addExecutableInternal(const QString &title, const QString &executableName,
const QString &arguments, const QString &workingDirectory,
- ExecutableInfo::CloseMOStyle closeMO, const QString &steamAppID)
+ const QString &steamAppID)
{
QFileInfo file(executableName);
if (file.exists()) {
Executable newExe;
- newExe.m_CloseMO = closeMO;
newExe.m_BinaryInfo = file;
newExe.m_Title = title;
newExe.m_Arguments = arguments;
diff --git a/src/executableslist.h b/src/executableslist.h
index 3d5ba0ed..0534c09e 100644
--- a/src/executableslist.h
+++ b/src/executableslist.h
@@ -36,7 +36,6 @@ struct Executable {
QString m_Title;
QFileInfo m_BinaryInfo;
QString m_Arguments;
- MOBase::ExecutableInfo::CloseMOStyle m_CloseMO;
QString m_SteamAppID;
QString m_WorkingDirectory;
@@ -126,17 +125,16 @@ public:
* @param title name displayed in the UI
* @param executableName the actual filename to execute
* @param arguments arguments to pass to the executable
- * @param closeMO if true, MO will be closed when the binary is started
**/
void addExecutable(const QString &title,
const QString &executableName,
const QString &arguments,
const QString &workingDirectory,
- MOBase::ExecutableInfo::CloseMOStyle closeMO,
const QString &steamAppID,
Executable::Flags flags)
{
- updateExecutable(title, executableName, arguments, workingDirectory, closeMO, steamAppID, Executable::AllFlags, flags);
+ updateExecutable(title, executableName, arguments, workingDirectory,
+ steamAppID, Executable::AllFlags, flags);
}
/**
@@ -151,7 +149,6 @@ public:
const QString &executableName,
const QString &arguments,
const QString &workingDirectory,
- MOBase::ExecutableInfo::CloseMOStyle closeMO,
const QString &steamAppID,
Executable::Flags mask,
Executable::Flags flags);
@@ -192,7 +189,7 @@ private:
std::vector<Executable>::iterator findExe(const QString &title);
void addExecutableInternal(const QString &title, const QString &executableName, const QString &arguments,
- const QString &workingDirectory, MOBase::ExecutableInfo::CloseMOStyle closeMO,
+ const QString &workingDirectory,
const QString &steamAppID);
private:
diff --git a/src/gameinfoimpl.cpp b/src/gameinfoimpl.cpp
new file mode 100644
index 00000000..333fd029
--- /dev/null
+++ b/src/gameinfoimpl.cpp
@@ -0,0 +1,55 @@
+/*
+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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "gameinfoimpl.h"
+#include <gameinfo.h>
+#include <utility.h>
+#include <QDir>
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+GameInfoImpl::GameInfoImpl()
+{
+}
+
+IGameInfo::Type GameInfoImpl::type() const
+{
+ switch (GameInfo::instance().getType()) {
+ case GameInfo::TYPE_OBLIVION: return IGameInfo::TYPE_OBLIVION;
+ case GameInfo::TYPE_FALLOUT3: return IGameInfo::TYPE_FALLOUT3;
+ case GameInfo::TYPE_FALLOUT4: return IGameInfo::TYPE_FALLOUT4;
+ case GameInfo::TYPE_FALLOUTNV: return IGameInfo::TYPE_FALLOUTNV;
+ case GameInfo::TYPE_SKYRIM: return IGameInfo::TYPE_SKYRIM;
+ default: throw MyException(QObject::tr("invalid game type %1").arg(GameInfo::instance().getType()));
+ }
+}
+
+
+QString GameInfoImpl::path() const
+{
+ return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory()));
+}
+
+QString GameInfoImpl::binaryName() const
+{
+ return ToQString(GameInfo::instance().getBinaryName());
+}
diff --git a/src/genericicondelegate.cpp b/src/genericicondelegate.cpp
index 3711b428..1e00f0e9 100644
--- a/src/genericicondelegate.cpp
+++ b/src/genericicondelegate.cpp
@@ -24,7 +24,7 @@ QList<QString> GenericIconDelegate::getIcons(const QModelIndex &index) const
{
QList<QString> result;
if (index.isValid()) {
- foreach (const QVariant &var, index.data(m_Role).toList()) {
+ for (const QVariant &var : index.data(m_Role).toList()) {
if (!m_Compact || !var.toString().isEmpty()) {
result.append(var.toString());
}
diff --git a/src/helper.cpp b/src/helper.cpp
index 41784fe5..b7fc866c 100644
--- a/src/helper.cpp
+++ b/src/helper.cpp
@@ -62,7 +62,7 @@ static bool helperExec(LPCWSTR moDirectory, LPCWSTR commandLine)
}
-bool init(const std::wstring &moDirectory)
+bool init(const std::wstring &moPath, const std::wstring &dataPath)
{
DWORD userNameLen = UNLEN + 1;
wchar_t userName[UNLEN + 1];
@@ -74,22 +74,22 @@ bool init(const std::wstring &moDirectory)
wchar_t *commandLine = new wchar_t[32768];
_snwprintf(commandLine, 32768, L"init \"%ls\" \"%ls\"",
- moDirectory.c_str(), userName);
+ dataPath.c_str(), userName);
- bool res = helperExec(moDirectory.c_str(), commandLine);
+ bool res = helperExec(moPath.c_str(), commandLine);
delete [] commandLine;
return res;
}
-bool backdateBSAs(const std::wstring &moDirectory, const std::wstring &dataPath)
+bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath)
{
wchar_t *commandLine = new wchar_t[32768];
_snwprintf(commandLine, 32768, L"backdateBSA \"%ls\"",
dataPath.c_str());
- bool res = helperExec(moDirectory.c_str(), commandLine);
+ bool res = helperExec(moPath.c_str(), commandLine);
delete [] commandLine;
return res;
diff --git a/src/helper.h b/src/helper.h
index 410e2527..cd4b7883 100644
--- a/src/helper.h
+++ b/src/helper.h
@@ -38,17 +38,17 @@ namespace Helper {
* This will create all required sub-directories and give the user running ModOrganizer
* write-access
*
- * @param moDirectory absolute path to the ModOrganizer base directory
+ * @param moPath absolute path to the ModOrganizer base directory
* @return true on success
**/
-bool init(const std::wstring &moDirectory);
+bool init(const std::wstring &moPath, const std::wstring &dataPath);
/**
* @brief sets the last modified time for all .bsa-files in the target directory well into the past
- * @param moDirectory absolute path to the modOrganizer base directory
+ * @param moPath absolute path to the modOrganizer base directory
* @param dataPath the path taht contains the .bsa-files, usually the data directory of the game
**/
-bool backdateBSAs(const std::wstring &moDirectory, const std::wstring &dataPath);
+bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath);
}
diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp
index 7b1f3f37..9be4cdd9 100644
--- a/src/installationmanager.cpp
+++ b/src/installationmanager.cpp
@@ -47,6 +47,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QApplication>
#include <QDateTime>
#include <QDirIterator>
+#include <QDebug>
+#include <QTextDocument>
#include <Shellapi.h>
@@ -67,22 +69,26 @@ static T resolveFunction(QLibrary &lib, const char *name)
{
T temp = reinterpret_cast<T>(lib.resolve(name));
if (temp == nullptr) {
- throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData());
+ throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1")
+ .arg(lib.errorString())
+ .toLatin1()
+ .constData());
}
return temp;
}
-
InstallationManager::InstallationManager()
- : m_ParentWidget(nullptr)
- , m_SupportedExtensions({ "zip", "rar", "7z", "fomod", "001" })
-{
- QLibrary archiveLib("dlls\\archive.dll");
+ : m_ParentWidget(nullptr),
+ m_SupportedExtensions({"zip", "rar", "7z", "fomod", "001"}) {
+ QLibrary archiveLib(QCoreApplication::applicationDirPath() +
+ "\\dlls\\archive.dll");
if (!archiveLib.load()) {
- throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString()));
+ throw MyException(QObject::tr("archive.dll not loaded: \"%1\"")
+ .arg(archiveLib.errorString()));
}
- CreateArchiveType CreateArchiveFunc = resolveFunction<CreateArchiveType>(archiveLib, "CreateArchive");
+ CreateArchiveType CreateArchiveFunc
+ = resolveFunction<CreateArchiveType>(archiveLib, "CreateArchive");
m_ArchiveHandler = CreateArchiveFunc();
if (!m_ArchiveHandler->isValid()) {
@@ -90,7 +96,6 @@ InstallationManager::InstallationManager()
}
}
-
InstallationManager::~InstallationManager()
{
delete m_ArchiveHandler;
@@ -110,10 +115,10 @@ void InstallationManager::setURL(QString const &url)
void InstallationManager::queryPassword(QString *password)
{
- *password = QInputDialog::getText(nullptr, tr("Password required"), tr("Password"), QLineEdit::Password);
+ *password = QInputDialog::getText(nullptr, tr("Password required"),
+ tr("Password"), QLineEdit::Password);
}
-
void InstallationManager::mapToArchive(const DirectoryTree::Node *node, QString path, FileData * const *data)
{
if (path.length() > 0) {
@@ -340,7 +345,8 @@ DirectoryTree *InstallationManager::createFilesTree()
// to uncheck all files in a directory while keeping the dir checked. Those directories are
// currently not installed.
DirectoryTree::Node *newNode = new DirectoryTree::Node;
- newNode->setData(DirectoryTreeInformation(*componentIter, i));
+ newNode->setData(
+ DirectoryTreeInformation(*componentIter, static_cast<int>(i)));
currentNode->addNode(newNode, false);
currentNode = newNode;
} else {
@@ -632,13 +638,14 @@ void InstallationManager::postInstallCleanup()
m_TempFilesToDelete.clear();
// try to delete each directory we had temporary files in. the call fails for non-empty directories which is ok
- foreach (const QString &dir, directoriesToRemove) {
+ for (const QString &dir : directoriesToRemove) {
QDir().rmdir(dir);
}
}
-
-bool InstallationManager::install(const QString &fileName, GuessedValue<QString> &modName, bool &hasIniTweaks)
+bool InstallationManager::install(const QString &fileName,
+ GuessedValue<QString> &modName,
+ bool &hasIniTweaks)
{
QFileInfo fileInfo(fileName);
if (m_SupportedExtensions.find(fileInfo.suffix()) == m_SupportedExtensions.end()) {
@@ -661,12 +668,15 @@ bool InstallationManager::install(const QString &fileName, GuessedValue<QString>
if (QFile(metaName).exists()) {
QSettings metaFile(metaName, QSettings::IniFormat);
modID = metaFile.value("modID", 0).toInt();
- modName.update(metaFile.value("name", "").toString(), GUESS_FALLBACK);
+ QTextDocument doc;
+ doc.setHtml(metaFile.value("name", "").toString());
+ modName.update(doc.toPlainText(), GUESS_FALLBACK);
modName.update(metaFile.value("modName", "").toString(), GUESS_META);
version = metaFile.value("version", "").toString();
newestVersion = metaFile.value("newestVersion", "").toString();
- unsigned int categoryIndex = CategoryFactory::instance().resolveNexusID(metaFile.value("category", 0).toInt());
+ unsigned int categoryIndex = CategoryFactory::instance().resolveNexusID(
+ metaFile.value("category", 0).toInt());
categoryID = CategoryFactory::instance().getCategoryID(categoryIndex);
repository = metaFile.value("repository", "").toString();
}
@@ -679,12 +689,13 @@ bool InstallationManager::install(const QString &fileName, GuessedValue<QString>
{ // guess the mod name and mod if from the file name if there was no meta information
QString guessedModName;
int guessedModID = modID;
- NexusInterface::interpretNexusFileName(QFileInfo(fileName).baseName(), guessedModName, guessedModID, false);
+ NexusInterface::interpretNexusFileName(QFileInfo(fileName).fileName(), guessedModName, guessedModID, false);
if ((modID == 0) && (guessedModID != -1)) {
modID = guessedModID;
} else if (modID != guessedModID) {
qDebug("passed mod id: %d, guessed id: %d", modID, guessedModID);
}
+
modName.update(guessedModName, GUESS_GOOD);
}
@@ -703,7 +714,10 @@ bool InstallationManager::install(const QString &fileName, GuessedValue<QString>
bool archiveOpen = m_ArchiveHandler->open(fileName,
new MethodCallback<InstallationManager, void, QString *>(this, &InstallationManager::queryPassword));
if (!archiveOpen) {
- qDebug("integrated archiver can't open %s. errorcode %d", qPrintable(fileName), m_ArchiveHandler->getLastError());
+ qDebug("integrated archiver can't open %s: %s (%d)",
+ qPrintable(fileName),
+ qPrintable(getErrorString(m_ArchiveHandler->getLastError())),
+ m_ArchiveHandler->getLastError());
}
ON_BLOCK_EXIT(std::bind(&InstallationManager::postInstallCleanup, this));
@@ -714,7 +728,7 @@ bool InstallationManager::install(const QString &fileName, GuessedValue<QString>
return LHS->priority() > RHS->priority();
});
- foreach (IPluginInstaller *installer, m_Installers) {
+ for (IPluginInstaller *installer : m_Installers) {
// don't use inactive installers (installer can't be null here but vc static code analysis thinks it could)
if ((installer == nullptr) || !installer->isActive()) {
continue;
@@ -731,14 +745,18 @@ bool InstallationManager::install(const QString &fileName, GuessedValue<QString>
try {
{ // simple case
- IPluginInstallerSimple *installerSimple = dynamic_cast<IPluginInstallerSimple*>(installer);
- if ((installerSimple != nullptr) &&
- (filesTree != nullptr) && (installer->isArchiveSupported(*filesTree))) {
- installResult = installerSimple->install(modName, *filesTree, version, modID);
+ IPluginInstallerSimple *installerSimple
+ = dynamic_cast<IPluginInstallerSimple *>(installer);
+ if ((installerSimple != nullptr) && (filesTree != nullptr)
+ && (installer->isArchiveSupported(*filesTree))) {
+ installResult
+ = installerSimple->install(modName, *filesTree, version, modID);
if (installResult == IPluginInstaller::RESULT_SUCCESS) {
mapToArchive(filesTree.data());
- // the simple installer only prepares the installation, the rest works the same for all installers
- if (!doInstall(modName, modID, version, newestVersion, categoryID, repository)) {
+ // the simple installer only prepares the installation, the rest
+ // works the same for all installers
+ if (!doInstall(modName, modID, version, newestVersion, categoryID,
+ repository)) {
installResult = IPluginInstaller::RESULT_FAILED;
}
}
@@ -746,13 +764,18 @@ bool InstallationManager::install(const QString &fileName, GuessedValue<QString>
}
{ // custom case
- IPluginInstallerCustom *installerCustom = dynamic_cast<IPluginInstallerCustom*>(installer);
- if ((installerCustom != nullptr) &&
- (((filesTree != nullptr) && installer->isArchiveSupported(*filesTree)) ||
- ((filesTree == nullptr) && installerCustom->isArchiveSupported(fileName)))) {
- std::set<QString> installerExtensions = installerCustom->supportedExtensions();
- if (installerExtensions.find(fileInfo.suffix()) != installerExtensions.end()) {
- installResult = installerCustom->install(modName, fileName, version, modID);
+ IPluginInstallerCustom *installerCustom
+ = dynamic_cast<IPluginInstallerCustom *>(installer);
+ if ((installerCustom != nullptr)
+ && (((filesTree != nullptr)
+ && installer->isArchiveSupported(*filesTree))
+ || ((filesTree == nullptr)
+ && installerCustom->isArchiveSupported(fileName)))) {
+ std::set<QString> installerExt
+ = installerCustom->supportedExtensions();
+ if (installerExt.find(fileInfo.suffix()) != installerExt.end()) {
+ installResult
+ = installerCustom->install(modName, fileName, version, modID);
unsigned int idx = ModInfo::getIndex(modName);
if (idx != UINT_MAX) {
ModInfo::Ptr info = ModInfo::getByIndex(idx);
diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp
new file mode 100644
index 00000000..a9182d52
--- /dev/null
+++ b/src/instancemanager.cpp
@@ -0,0 +1,209 @@
+/*
+Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "instancemanager.h"
+#include "selectiondialog.h"
+#include <utility.h>
+#include <appconfig.h>
+#include <QCoreApplication>
+#include <QDir>
+#include <QStandardPaths>
+#include <QInputDialog>
+#include <QMessageBox>
+#include <cstdint>
+
+
+static const char COMPANY_NAME[] = "Tannin";
+static const char APPLICATION_NAME[] = "Mod Organizer";
+static const char INSTANCE_KEY[] = "CurrentInstance";
+
+
+
+InstanceManager::InstanceManager()
+ : m_AppSettings(COMPANY_NAME, APPLICATION_NAME)
+{
+}
+
+InstanceManager &InstanceManager::instance()
+{
+ static InstanceManager s_Instance;
+ return s_Instance;
+}
+
+QString InstanceManager::currentInstance() const
+{
+ return m_AppSettings.value(INSTANCE_KEY, "").toString();
+}
+
+void InstanceManager::clearCurrentInstance()
+{
+ setCurrentInstance("");
+ m_Reset = true;
+}
+
+void InstanceManager::setCurrentInstance(const QString &name)
+{
+ m_AppSettings.setValue(INSTANCE_KEY, name);
+}
+
+
+QString InstanceManager::queryInstanceName() const
+{
+ QString instanceId;
+ while (instanceId.isEmpty()) {
+ QInputDialog dialog;
+ // would be neat if we could take the names from the game plugins but
+ // the required initialization order requires the ini file to be
+ // available *before* we load plugins
+ dialog.setComboBoxItems({ "Oblivion", "Skyrim", "Fallout 3",
+ "Fallout NV", "Fallout 4" });
+ dialog.setComboBoxEditable(true);
+ dialog.setWindowTitle(QObject::tr("Enter Instance Name"));
+ dialog.setLabelText(QObject::tr("Name"));
+ if (dialog.exec() == QDialog::Rejected) {
+ throw MOBase::MyException(QObject::tr("Canceled"));
+ }
+ instanceId = dialog.textValue().replace(QRegExp("[^0-9a-zA-Z ]"), "");
+ }
+ return instanceId;
+}
+
+
+QString InstanceManager::chooseInstance(const QStringList &instanceList) const
+{
+ enum class Special : uint8_t {
+ NewInstance,
+ Portable
+ };
+
+ SelectionDialog selection(
+ QString("<h3>%1</h3><br>%2")
+ .arg(QObject::tr("Choose Instance"))
+ .arg(QObject::tr(
+ "Each Instance is a full set of MO data files (mods, "
+ "downloads, profiles, configuration, ...). Use multiple "
+ "instances for different games. If your MO folder is "
+ "writable, you can also store a single instance locally (called "
+ "a portable install).")),
+ nullptr);
+ selection.disableCancel();
+ for (const QString &instance : instanceList) {
+ selection.addChoice(instance, "", instance);
+ }
+
+ selection.addChoice(QIcon(":/MO/gui/add"), QObject::tr("New"),
+ QObject::tr("Create a new instance."),
+ static_cast<uint8_t>(Special::NewInstance));
+
+ if (QFileInfo(qApp->applicationDirPath()).isWritable()) {
+ selection.addChoice(QIcon(":/MO/gui/package"), QObject::tr("Portable"),
+ QObject::tr("Use MO folder for data."),
+ static_cast<uint8_t>(Special::Portable));
+ }
+
+ if (selection.exec() == QDialog::Rejected) {
+ qDebug("rejected");
+ throw MOBase::MyException(QObject::tr("Canceled"));
+ }
+
+ QVariant choice = selection.getChoiceData();
+
+ if (choice.type() == QVariant::String) {
+ return choice.toString();
+ } else {
+ switch (choice.value<uint8_t>()) {
+ case Special::NewInstance: return queryInstanceName();
+ case Special::Portable: return QString();
+ default: throw std::runtime_error("invalid selection");
+ }
+ }
+}
+
+
+QString InstanceManager::instancePath() const
+{
+ return QDir::fromNativeSeparators(
+ QStandardPaths::writableLocation(QStandardPaths::DataLocation));
+}
+
+
+QStringList InstanceManager::instances() const
+{
+ return QDir(instancePath()).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+}
+
+
+bool InstanceManager::portableInstall() const
+{
+ return QFile::exists(qApp->applicationDirPath() + "/" +
+ QString::fromStdWString(AppConfig::iniFileName()));
+}
+
+
+void InstanceManager::createDataPath(const QString &dataPath) const
+{
+ if (!QDir(dataPath).exists()) {
+ if (!QDir().mkpath(dataPath)) {
+ throw MOBase::MyException(
+ QObject::tr("failed to create %1").arg(dataPath));
+ } else {
+ QMessageBox::information(
+ nullptr, QObject::tr("Data directory created"),
+ QObject::tr("New data directory created at %1. If you don't want to "
+ "store a lot of data there, reconfigure the storage "
+ "directories via settings.").arg(dataPath));
+ }
+ }
+}
+
+
+QString InstanceManager::determineDataPath()
+{
+ QString instanceId = currentInstance();
+ if (instanceId.isEmpty() && portableInstall() && !m_Reset) {
+ // startup, apparently using portable mode before
+ return qApp->applicationDirPath();
+ }
+
+ QString dataPath = QDir::fromNativeSeparators(
+ QStandardPaths::writableLocation(QStandardPaths::DataLocation)
+ + "/" + instanceId);
+
+
+ if (instanceId.isEmpty() || !QFileInfo::exists(dataPath)) {
+ instanceId = chooseInstance(instances());
+ if (!instanceId.isEmpty()) {
+ dataPath = QDir::fromNativeSeparators(
+ QStandardPaths::writableLocation(QStandardPaths::DataLocation)
+ + "/" + instanceId);
+ }
+ }
+
+ if (instanceId.isEmpty()) {
+ return qApp->applicationDirPath();
+ } else {
+ setCurrentInstance(instanceId);
+
+ createDataPath(dataPath);
+
+ return dataPath;
+ }
+}
+
diff --git a/src/safewritefile.h b/src/instancemanager.h
index 0af6bf98..a0a5df09 100644
--- a/src/safewritefile.h
+++ b/src/instancemanager.h
@@ -1,57 +1,59 @@
-/*
-Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
-*/
-
-
-#ifndef SAFEWRITEFILE_H
-#define SAFEWRITEFILE_H
-
-
-#include <utility.h>
-#include <QTemporaryFile>
-#include <QString>
-
-/**
- * @brief a wrapper for QFile that ensures the file is only actually (over-)written if writing was successful
- */
-class SafeWriteFile {
-public:
- SafeWriteFile(const QString &fileName);
-
- QFile *operator->();
-
- void commit();
-
- bool commitIfDifferent(QByteArray &hash);
-
-private:
-
- QByteArray hash();
-
-private:
- QString m_FileName;
- QTemporaryFile m_TempFile;
-};
-
-
-#endif // SAFEWRITEFILE_H
-
-
-
-
-
+/*
+Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
+*/
+
+
+#pragma once
+
+
+#include <QString>
+#include <QSettings>
+
+
+class InstanceManager {
+
+public:
+
+ static InstanceManager &instance();
+
+ QString determineDataPath();
+ void clearCurrentInstance();
+
+private:
+
+ InstanceManager();
+
+ QString currentInstance() const;
+ QString instancePath() const;
+
+ QStringList instances() const;
+
+ void setCurrentInstance(const QString &name);
+
+ QString queryInstanceName() const;
+ QString chooseInstance(const QStringList &instanceList) const;
+
+ void createDataPath(const QString &dataPath) const;
+ bool portableInstall() const;
+
+private:
+
+ QSettings m_AppSettings;
+ bool m_Reset {false};
+
+};
diff --git a/src/iuserinterface.h b/src/iuserinterface.h
index 540839c6..0af1c2ac 100644
--- a/src/iuserinterface.h
+++ b/src/iuserinterface.h
@@ -27,10 +27,6 @@ public:
virtual void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab) = 0;
- virtual void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives) = 0;
-
- virtual MOBase::DelayedFileWriterBase &archivesWriter() = 0;
-
virtual void lock() = 0;
virtual void unlock() = 0;
virtual bool unlockClicked() = 0;
diff --git a/src/loadmechanism.cpp b/src/loadmechanism.cpp
index 47587467..e476c56d 100644
--- a/src/loadmechanism.cpp
+++ b/src/loadmechanism.cpp
@@ -65,7 +65,7 @@ void LoadMechanism::removeHintFile(QDir targetDirectory)
bool LoadMechanism::isDirectLoadingSupported()
{
//FIXME: Seriously? isn't there a 'do i need steam' thing?
- IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+ IPluginGame const *game = qApp->property("managed_game").value<IPluginGame*>();
if (game->gameName().compare("oblivion", Qt::CaseInsensitive) == 0) {
// oblivion can be loaded directly if it's not the steam variant
return !game->gameDirectory().exists("steam_api.dll");
@@ -77,7 +77,7 @@ bool LoadMechanism::isDirectLoadingSupported()
bool LoadMechanism::isScriptExtenderSupported()
{
- IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+ IPluginGame const *game = qApp->property("managed_game").value<IPluginGame*>();
ScriptExtender *extender = game->feature<ScriptExtender>();
// test if there even is an extender for the managed game and if so whether it's installed
@@ -87,11 +87,11 @@ bool LoadMechanism::isScriptExtenderSupported()
bool LoadMechanism::isProxyDLLSupported()
{
// using steam_api.dll as the proxy is way too game specific as many games will have different
- // versions of that game.
+ // versions of that dll.
// plus: the proxy dll hasn't been working for at least the whole 1.12.x versions of MO and
// noone reported it so why maintain an unused feature?
return false;
-/* IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+/* IPluginGame const *game = qApp->property("managed_game").value<IPluginGame*>();
return game->gameDirectory().exists(QString::fromStdWString(AppConfig::proxyDLLTarget()));*/
}
@@ -123,7 +123,7 @@ bool LoadMechanism::hashIdentical(const QString &fileNameLHS, const QString &fil
void LoadMechanism::deactivateScriptExtender()
{
try {
- IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+ IPluginGame const *game = qApp->property("managed_game").value<IPluginGame*>();
ScriptExtender *extender = game->feature<ScriptExtender>();
if (extender == nullptr) {
throw MyException(QObject::tr("game doesn't support a script extender"));
@@ -131,6 +131,9 @@ void LoadMechanism::deactivateScriptExtender()
QDir pluginsDir(game->gameDirectory().absolutePath() + "/data/" + extender->name() + "/plugins");
+#pragma message("implement this for usvfs")
+
+ /*
QString hookDLLName = ToQString(AppConfig::hookDLLName());
if (QFile(pluginsDir.absoluteFilePath(hookDLLName)).exists()) {
// remove dll from SE plugins directory
@@ -140,6 +143,7 @@ void LoadMechanism::deactivateScriptExtender()
}
removeHintFile(pluginsDir);
+ */
} catch (const std::exception &e) {
QMessageBox::critical(nullptr, QObject::tr("Failed to deactivate script extender loading"), e.what());
}
@@ -149,7 +153,7 @@ void LoadMechanism::deactivateScriptExtender()
void LoadMechanism::deactivateProxyDLL()
{
try {
- IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+ IPluginGame const *game = qApp->property("managed_game").value<IPluginGame *>();
QString targetPath = game->gameDirectory().absoluteFilePath(QString::fromStdWString(AppConfig::proxyDLLTarget()));
@@ -178,7 +182,7 @@ void LoadMechanism::deactivateProxyDLL()
void LoadMechanism::activateScriptExtender()
{
try {
- IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+ IPluginGame const *game = qApp->property("managed_game").value<IPluginGame *>();
ScriptExtender *extender = game->feature<ScriptExtender>();
if (extender == nullptr) {
throw MyException(QObject::tr("game doesn't support a script extender"));
@@ -190,6 +194,8 @@ void LoadMechanism::activateScriptExtender()
pluginsDir.mkpath(".");
}
+#pragma message("implement this for usvfs")
+/*
QString targetPath = pluginsDir.absoluteFilePath(ToQString(AppConfig::hookDLLName()));
QString hookDLLPath = qApp->applicationDirPath() + "/" + QString::fromStdWString(AppConfig::hookDLLName());
@@ -209,6 +215,7 @@ void LoadMechanism::activateScriptExtender()
}
}
writeHintFile(pluginsDir);
+*/
} catch (const std::exception &e) {
QMessageBox::critical(nullptr, QObject::tr("Failed to set up script extender loading"), e.what());
}
@@ -218,7 +225,7 @@ void LoadMechanism::activateScriptExtender()
void LoadMechanism::activateProxyDLL()
{
try {
- IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+ IPluginGame const *game = qApp->property("managed_game").value<IPluginGame *>();
QString targetPath = game->gameDirectory().absoluteFilePath(QString::fromStdWString(AppConfig::proxyDLLTarget()));
diff --git a/src/logbuffer.cpp b/src/logbuffer.cpp
index a07ae264..522ce3c8 100644
--- a/src/logbuffer.cpp
+++ b/src/logbuffer.cpp
@@ -26,45 +26,42 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QDateTime>
#include <Windows.h>
-
using MOBase::reportError;
QScopedPointer<LogBuffer> LogBuffer::s_Instance;
QMutex LogBuffer::s_Mutex;
-
-LogBuffer::LogBuffer(int messageCount, QtMsgType minMsgType, const QString &outputFileName)
- : QAbstractItemModel(nullptr), m_OutFileName(outputFileName), m_ShutDown(false),
- m_MinMsgType(minMsgType), m_NumMessages(0)
+LogBuffer::LogBuffer(int messageCount, QtMsgType minMsgType,
+ const QString &outputFileName)
+ : QAbstractItemModel(nullptr)
+ , m_OutFileName(outputFileName)
+ , m_ShutDown(false)
+ , m_MinMsgType(minMsgType)
+ , m_NumMessages(0)
{
m_Messages.resize(messageCount);
}
LogBuffer::~LogBuffer()
{
-#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
qInstallMessageHandler(0);
-#else
- qInstallMsgHandler(0);
-#endif
-// if (!m_ShutDown) {
- write();
-// }
+ write();
}
-
void LogBuffer::logMessage(QtMsgType type, const QString &message)
{
if (type >= m_MinMsgType) {
- Message msg = { type, QTime::currentTime(), message };
+ Message msg = {type, QTime::currentTime(), message};
if (m_NumMessages < m_Messages.size()) {
- beginInsertRows(QModelIndex(), m_NumMessages, m_NumMessages + 1);
+ beginInsertRows(QModelIndex(), static_cast<int>(m_NumMessages),
+ static_cast<int>(m_NumMessages) + 1);
}
m_Messages.at(m_NumMessages % m_Messages.size()) = msg;
if (m_NumMessages < m_Messages.size()) {
endInsertRows();
} else {
- emit dataChanged(createIndex(0, 0), createIndex(m_Messages.size(), 0));
+ emit dataChanged(createIndex(0, 0),
+ createIndex(static_cast<int>(m_Messages.size()), 0));
}
++m_NumMessages;
if (type >= QtCriticalMsg) {
@@ -73,7 +70,6 @@ void LogBuffer::logMessage(QtMsgType type, const QString &message)
}
}
-
void LogBuffer::write() const
{
if (m_NumMessages == 0) {
@@ -84,12 +80,16 @@ void LogBuffer::write() const
QFile file(m_OutFileName);
if (!file.open(QIODevice::WriteOnly)) {
- reportError(tr("failed to write log to %1: %2").arg(m_OutFileName).arg(file.errorString()));
+ reportError(tr("failed to write log to %1: %2")
+ .arg(m_OutFileName)
+ .arg(file.errorString()));
return;
}
- unsigned int i = (m_NumMessages > m_Messages.size()) ? m_NumMessages - m_Messages.size()
- : 0U;
+ unsigned int i
+ = (m_NumMessages > m_Messages.size())
+ ? static_cast<unsigned int>(m_NumMessages - m_Messages.size())
+ : 0U;
for (; i < m_NumMessages; ++i) {
file.write(m_Messages.at(i % m_Messages.size()).toString().toUtf8());
file.write("\r\n");
@@ -97,84 +97,68 @@ void LogBuffer::write() const
::SetLastError(lastError);
}
-
-void LogBuffer::init(int messageCount, QtMsgType minMsgType, const QString &outputFileName)
+void LogBuffer::init(int messageCount, QtMsgType minMsgType,
+ const QString &outputFileName)
{
QMutexLocker guard(&s_Mutex);
- if (!s_Instance.isNull()) {
- s_Instance.reset();
- }
s_Instance.reset(new LogBuffer(messageCount, minMsgType, outputFileName));
-#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
qInstallMessageHandler(LogBuffer::log);
-#else
- qInstallMsgHandler(LogBuffer::log);
-#endif
}
char LogBuffer::msgTypeID(QtMsgType type)
{
switch (type) {
- case QtDebugMsg: return 'D';
- case QtWarningMsg: return 'W';
- case QtCriticalMsg: return 'C';
- case QtFatalMsg: return 'F';
- default: return '?';
+ case QtDebugMsg:
+ return 'D';
+ case QtWarningMsg:
+ return 'W';
+ case QtCriticalMsg:
+ return 'C';
+ case QtFatalMsg:
+ return 'F';
+ default:
+ return '?';
}
}
-#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
-
-void LogBuffer::log(QtMsgType type, const QMessageLogContext &context, const QString &message)
+void LogBuffer::log(QtMsgType type, const QMessageLogContext &context,
+ const QString &message)
{
// QMutexLocker doesn't support timeout...
if (!s_Mutex.tryLock(100)) {
fprintf(stderr, "failed to log: %s", qPrintable(message));
return;
}
- ON_BLOCK_EXIT([] () {
- s_Mutex.unlock();
- });
+ ON_BLOCK_EXIT([]() { s_Mutex.unlock(); });
if (!s_Instance.isNull()) {
s_Instance->logMessage(type, message);
}
-// fprintf(stdout, "(%s:%u) %s\n", context.file, context.line, qPrintable(message));
+
if (type == QtDebugMsg) {
- fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), qPrintable(message));
+ fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()),
+ msgTypeID(type), qPrintable(message));
} else {
if (context.line != 0) {
- fprintf(stdout, "%s [%c] (%s:%u) %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type),
+ fprintf(stdout, "%s [%c] (%s:%u) %s\n",
+ qPrintable(QTime::currentTime().toString()), msgTypeID(type),
context.file, context.line, qPrintable(message));
} else {
- fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), qPrintable(message));
+ fprintf(stdout, "%s [%c] %s\n",
+ qPrintable(QTime::currentTime().toString()), msgTypeID(type),
+ qPrintable(message));
}
}
fflush(stdout);
}
-#else
-
-void LogBuffer::log(QtMsgType type, const char *message)
-{
- QMutexLocker guard(&s_Mutex);
- if (!s_Instance.isNull()) {
- s_Instance->logMessage(type, message);
- }
- fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), message);
- fflush(stdout);
-}
-
-#endif
-
-
-QModelIndex LogBuffer::index(int row, int column, const QModelIndex&) const
+QModelIndex LogBuffer::index(int row, int column, const QModelIndex &) const
{
return createIndex(row, column, row);
}
-QModelIndex LogBuffer::parent(const QModelIndex&) const
+QModelIndex LogBuffer::parent(const QModelIndex &) const
{
return QModelIndex();
}
@@ -184,19 +168,20 @@ int LogBuffer::rowCount(const QModelIndex &parent) const
if (parent.isValid())
return 0;
else
- return std::min(m_NumMessages, m_Messages.size());
+ return static_cast<int>(std::min(m_NumMessages, m_Messages.size()));
}
-int LogBuffer::columnCount(const QModelIndex&) const
+int LogBuffer::columnCount(const QModelIndex &) const
{
return 2;
}
-
QVariant LogBuffer::data(const QModelIndex &index, int role) const
{
- unsigned offset = m_NumMessages < m_Messages.size() ? 0
- : m_NumMessages - m_Messages.size();
+ unsigned int offset
+ = m_NumMessages < m_Messages.size()
+ ? 0
+ : static_cast<unsigned int>(m_NumMessages - m_Messages.size());
unsigned int msgIndex = (offset + index.row() + 1) % m_Messages.size();
switch (role) {
case Qt::DisplayRole: {
@@ -214,20 +199,28 @@ QVariant LogBuffer::data(const QModelIndex &index, int role) const
case Qt::DecorationRole: {
if (index.column() == 1) {
switch (m_Messages[msgIndex].type) {
- case QtDebugMsg: return QIcon(":/MO/gui/information");
- case QtWarningMsg: return QIcon(":/MO/gui/warning");
- case QtCriticalMsg: return QIcon(":/MO/gui/important");
- case QtFatalMsg: return QIcon(":/MO/gui/problem");
+ case QtDebugMsg:
+ return QIcon(":/MO/gui/information");
+ case QtWarningMsg:
+ return QIcon(":/MO/gui/warning");
+ case QtCriticalMsg:
+ return QIcon(":/MO/gui/important");
+ case QtFatalMsg:
+ return QIcon(":/MO/gui/problem");
}
}
} break;
case Qt::UserRole: {
if (index.column() == 1) {
switch (m_Messages[msgIndex].type) {
- case QtDebugMsg: return "D";
- case QtWarningMsg: return "W";
- case QtCriticalMsg: return "C";
- case QtFatalMsg: return "F";
+ case QtDebugMsg:
+ return "D";
+ case QtWarningMsg:
+ return "W";
+ case QtCriticalMsg:
+ return "C";
+ case QtFatalMsg:
+ return "F";
}
}
} break;
@@ -243,7 +236,6 @@ void LogBuffer::writeNow()
}
}
-
void LogBuffer::cleanQuit()
{
QMutexLocker guard(&s_Mutex);
@@ -269,9 +261,10 @@ void log(const char *format, ...)
va_end(argList);
}
-
-
QString LogBuffer::Message::toString() const
{
- return QString("%1 [%2] %3").arg(time.toString()).arg(msgTypeID(type)).arg(message);
+ return QString("%1 [%2] %3")
+ .arg(time.toString())
+ .arg(msgTypeID(type))
+ .arg(message);
}
diff --git a/src/logbuffer.h b/src/logbuffer.h
index 748aaf31..0cfecfa2 100644
--- a/src/logbuffer.h
+++ b/src/logbuffer.h
@@ -35,11 +35,7 @@ class LogBuffer : public QAbstractItemModel
public:
static void init(int messageCount, QtMsgType minMsgType, const QString &outputFileName);
-#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
static void log(QtMsgType type, const QMessageLogContext &context, const QString &message);
-#else
- static void log(QtMsgType type, const char *message);
-#endif
static void writeNow();
static void cleanQuit();
diff --git a/src/main.cpp b/src/main.cpp
index 3469e9c3..16b6b80f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -27,7 +27,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <windows.h>
#include <DbgHelp.h>
-#include <inject.h>
#include <appconfig.h>
#include <utility.h>
#include <scopeguard.h>
@@ -45,6 +44,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "moapplication.h"
#include "tutorialmanager.h"
#include "nxmaccessmanager.h"
+#include "instancemanager.h"
+
#include <eh.h>
#include <windows_error.h>
@@ -84,36 +85,21 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
using namespace MOBase;
using namespace MOShared;
-bool createAndMakeWritable(const std::wstring &subPath)
-{
+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);
- }
-
- QFileInfo fileInfo(fullPath);
- if (!fileInfo.exists() || !fileInfo.isWritable()) {
- if (QMessageBox::question(nullptr, QObject::tr("Permissions required"),
- QObject::tr("The current user account doesn't have the required access rights to run "
- "Mod Organizer. The neccessary changes can be made automatically (the MO directory "
- "will be made writable for the current user account). You will be asked to run "
- "\"helper.exe\" with administrative rights."),
- QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) {
- if (!Helper::init(dataPath.toStdWString())) {
- return false;
- }
- } else {
- return false;
- }
- // no matter which directory didn't exist/wasn't writable, the helper
- // should have created them all so we don't have to worry this message box would appear repeatedly
+ 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;
}
- return true;
}
-
bool bootstrap()
{
// remove the temporary backup directory in case we're restarting after an update
@@ -124,67 +110,15 @@ bool bootstrap()
// cycle logfile
removeOldFiles(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()),
- "ModOrganizer*.log", 5, QDir::Name);
-
- createAndMakeWritable(AppConfig::profilesPath());
- createAndMakeWritable(AppConfig::modsPath());
- createAndMakeWritable(AppConfig::downloadPath());
- createAndMakeWritable(AppConfig::overwritePath());
- createAndMakeWritable(AppConfig::logPath());
-
- // verify the hook-dll exists
- QString dllName = qApp->applicationDirPath() + "/" + ToQString(AppConfig::hookDLLName());
-
- if (::GetModuleHandleW(ToWString(dllName).c_str()) != nullptr) {
- throw std::runtime_error("hook.dll already loaded! You can't start Mod Organizer from within itself (not even indirectly)");
- }
+ "usvfs*.log", 5, QDir::Name);
- HMODULE dllMod = ::LoadLibraryW(ToWString(dllName).c_str());
- if (dllMod == nullptr) {
- throw windows_error("hook.dll is missing or invalid");
+ if (!createAndMakeWritable(AppConfig::logPath())) {
+ return false;
}
- ::FreeLibrary(dllMod);
return true;
}
-void cleanupDir()
-{
- // files from previous versions of MO that are no longer
- // required (in that location)
- QStringList fileNames {
- "imageformats/",
- "loot/resources/",
- "plugins/previewDDS.dll",
- "dlls/boost_python-vc100-mt-1_55.dll",
- "dlls/QtCore4.dll",
- "dlls/QtDeclarative4.dll",
- "dlls/QtGui4.dll",
- "dlls/QtNetwork4.dll",
- "dlls/QtOpenGL4.dll",
- "dlls/QtScript4.dll",
- "dlls/QtSql4.dll",
- "dlls/QtSvg4.dll",
- "dlls/QtWebKit4.dll",
- "dlls/QtXml4.dll",
- "dlls/QtXmlPatterns4.dll",
- "msvcp100.dll",
- "msvcr100.dll",
- "proxy.dll"
- };
-
- for (const QString &fileName : fileNames) {
- QString fullPath = qApp->applicationDirPath() + "/" + fileName;
- if (QFile::exists(fullPath)) {
- if (shellDelete(QStringList(fullPath), true)) {
- qDebug("removed obsolete file %s", qPrintable(fullPath));
- } else {
- qDebug("failed to remove obsolete %s", qPrintable(fullPath));
- }
- }
- }
-}
-
bool isNxmLink(const QString &link)
{
@@ -208,6 +142,10 @@ static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *except
if (dbgDLL) {
FuncMiniDumpWriteDump funcDump = (FuncMiniDumpWriteDump)::GetProcAddress(dbgDLL, "MiniDumpWriteDump");
if (funcDump) {
+ QString dataPath = qApp->property("dataPath").toString();
+ QString exeName = QFileInfo(qApp->applicationFilePath()).fileName();
+ QString dumpName = dataPath + "/" + exeName + ".dmp";
+
if (QMessageBox::question(nullptr, QObject::tr("Woops"),
QObject::tr("ModOrganizer has crashed! "
"Should a diagnostic file be created? "
@@ -215,12 +153,10 @@ static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *except
"the bug is a lot more likely to be fixed. "
"Please include a short description of what you were "
"doing when the crash happened"
- ).arg(qApp->applicationFilePath().append(".dmp")),
+ ).arg(dumpName),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- std::wstring dumpName = ToWString(qApp->applicationFilePath().append(".dmp"));
-
- HANDLE dumpFile = ::CreateFile(dumpName.c_str(),
+ HANDLE dumpFile = ::CreateFile(dumpName.toStdWString().c_str(),
GENERIC_WRITE, FILE_SHARE_WRITE, nullptr,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (dumpFile != INVALID_HANDLE_VALUE) {
@@ -238,10 +174,10 @@ static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *except
return EXCEPTION_EXECUTE_HANDLER;
}
_snprintf(errorBuffer, errorLen, "failed to save minidump to %ls (error %lu)",
- dumpName.c_str(), ::GetLastError());
+ dumpName.toStdWString().c_str(), ::GetLastError());
} else {
_snprintf(errorBuffer, errorLen, "failed to create %ls (error %lu)",
- dumpName.c_str(), ::GetLastError());
+ dumpName.toStdWString().c_str(), ::GetLastError());
}
} else {
return result;
@@ -448,139 +384,110 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett
return nullptr;
}
-int main(int argc, char *argv[])
-{
- MOApplication application(argc, argv);
-
- qDebug("application name: %s", qPrintable(application.applicationName()));
- QString instanceID;
- QFile instanceFile(application.applicationDirPath() + "/INSTANCE");
- if (instanceFile.open(QIODevice::ReadOnly)) {
- instanceID = instanceFile.readAll().trimmed();
- }
+// 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;
- QString const dataPath =
- instanceID.isEmpty() ? application.applicationDirPath()
- : QDir::fromNativeSeparators(
- QStandardPaths::writableLocation(QStandardPaths::DataLocation)
- + "/" + instanceID
- );
- application.setProperty("dataPath", dataPath);
+ qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators(
+ QCoreApplication::applicationDirPath())));
- if (!QDir(dataPath).exists()) {
- if (!QDir().mkpath(dataPath)) {
- qCritical("failed to create %s", qPrintable(dataPath));
- return 1;
- }
+ boost::scoped_array<TCHAR> 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);
}
- SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
+ std::wstring newPath(oldPath.get());
+ newPath += L";";
+ newPath += ToWString(QDir::toNativeSeparators(
+ QCoreApplication::applicationDirPath()))
+ .c_str();
+ newPath += L"\\dlls";
- if (!HaveWriteAccess(ToWString(application.applicationDirPath()))) {
- QStringList arguments = application.arguments();
- arguments.pop_front();
- ::ShellExecuteW( nullptr
- , L"runas"
- , ToWString(QString("\"%1\"").arg(QCoreApplication::applicationFilePath())).c_str()
- , ToWString(arguments.join(" ")).c_str()
- , ToWString(QDir::currentPath()).c_str(), SW_SHOWNORMAL);
- return 1;
- }
+ ::SetEnvironmentVariableW(L"PATH", newPath.c_str());
+}
- QPixmap pixmap(":/MO/gui/splash");
+int runApplication(MOApplication &application, SingleInstance &instance,
+ const QString &splashPath)
+{
+ qDebug("start main application");
+ QPixmap pixmap(splashPath);
QSplashScreen splash(pixmap);
- try {
- if (!bootstrap()) {
- return -1;
- }
+ QString dataPath = application.property("dataPath").toString();
+ qDebug("data path: %s", qPrintable(dataPath));
- LogBuffer::init(100, QtDebugMsg, qApp->property("dataPath").toString() + "/logs/mo_interface.log");
+ if (!bootstrap()) {
+ reportError("failed to set up data paths");
+ return 1;
+ }
-#if QT_VERSION >= 0x050000 && !defined(QT_NO_SSL)
- qDebug("ssl support: %d", QSslSocket::supportsSsl());
-#endif
+ QStringList arguments = application.arguments();
+ try {
qDebug("Working directory: %s", qPrintable(QDir::toNativeSeparators(QDir::currentPath())));
- qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators(application.applicationDirPath())));
splash.show();
-
- cleanupDir();
} catch (const std::exception &e) {
reportError(e.what());
return 1;
}
- { // 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)
- static const int BUFSIZE = 4096;
-
- boost::scoped_array<TCHAR> 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(oldPath.get());
- newPath += L";";
- newPath += ToWString(QDir::toNativeSeparators(QCoreApplication::applicationDirPath())).c_str();
- newPath += L"\\dlls";
-
- ::SetEnvironmentVariableW(L"PATH", newPath.c_str());
- }
-
- QStringList arguments = application.arguments();
-
- bool forcePrimary = false;
- if (arguments.contains("update")) {
- arguments.removeAll("update");
- forcePrimary = true;
- }
-
try {
- SingleInstance instance(forcePrimary);
- if (!instance.primaryInstance()) {
- if ((arguments.size() == 2) && isNxmLink(arguments.at(1))) {
- qDebug("not primary instance, sending 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 has been called with parameters
-
- QSettings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()), QSettings::IniFormat);
+ QSettings settings(dataPath + "/"
+ + QString::fromStdWString(AppConfig::iniFileName()),
+ QSettings::IniFormat);
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);
+ 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();
- //See the pragma - we apparently don't use this so not sure why we check it
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);
+ SelectionDialog selection(
+ QObject::tr("Please select the game edition you have (MO can't "
+ "start the game correctly if this is set "
+ "incorrectly!)"),
+ nullptr);
int index = 0;
for (const QString &edition : editions) {
selection.addChoice(edition, "", index++);
}
if (selection.exec() == QDialog::Rejected) {
- return -1;
+ return 1;
} else {
settings.setValue("game_edition", selection.getChoiceString());
}
@@ -588,9 +495,8 @@ int main(int argc, char *argv[])
}
game->setGameVariant(settings.value("game_edition").toString());
-
-#pragma message("edition isn't used?")
- qDebug("managing game at %s", qPrintable(QDir::toNativeSeparators(game->gameDirectory().absolutePath())));
+ qDebug("managing game at %s", qPrintable(QDir::toNativeSeparators(
+ game->gameDirectory().absolutePath())));
organizer.updateExecutablesList(settings);
@@ -599,27 +505,35 @@ int main(int argc, char *argv[])
// if we have a command line parameter, it is either a nxm link or
// a binary to start
- if ((arguments.size() > 1)
- && !isNxmLink(arguments.at(1))) {
- QString exeName = arguments.at(1);
- qDebug("starting %s from command line", qPrintable(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;
+ if (arguments.size() > 1) {
+ if (isNxmLink(arguments.at(1))) {
+ qDebug("starting download from command line: %s",
+ qPrintable(arguments.at(1)));
+ organizer.externalMessage(arguments.at(1));
+ } else {
+ QString exeName = arguments.at(1);
+ qDebug("starting %s from command line", qPrintable(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;
+ }
}
}
NexusInterface::instance()->getAccessManager()->startLoginCheck();
qDebug("initializing tutorials");
- TutorialManager::init(qApp->applicationDirPath() + "/" + QString::fromStdWString(AppConfig::tutorialsPath()) + "/",
- &organizer);
+ TutorialManager::init(
+ qApp->applicationDirPath() + "/"
+ + QString::fromStdWString(AppConfig::tutorialsPath()) + "/",
+ &organizer);
if (!application.setStyleFile(settings.value("Settings/style", "").toString())) {
// disable invalid stylesheet
@@ -629,27 +543,97 @@ int main(int argc, char *argv[])
int res = 1;
{ // scope to control lifetime of mainwindow
// set up main window and its data structures
- MainWindow mainWindow(argv[0], settings, organizer, pluginContainer);
+ 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)));
+ QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application,
+ SLOT(setStyleFile(QString)));
+ QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer,
+ SLOT(externalMessage(QString)));
mainWindow.readSettings();
qDebug("displaying main window");
mainWindow.show();
- if ((arguments.size() > 1)
- && isNxmLink(arguments.at(1))) {
- qDebug("starting download from command line: %s", qPrintable(arguments.at(1)));
- organizer.externalMessage(arguments.at(1));
- }
splash.finish(&mainWindow);
- res = application.exec();
+ return application.exec();
}
- return res;
} catch (const std::exception &e) {
reportError(e.what());
return 1;
}
}
+
+
+int main(int argc, char *argv[])
+{
+ SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
+
+ MOApplication application(argc, argv);
+ QStringList arguments = application.arguments();
+
+ if ((arguments.length() >= 4) && (arguments.at(1) == "launch")) {
+ // all we're supposed to do is launch another process
+ QProcess process;
+ process.setWorkingDirectory(QDir::fromNativeSeparators(arguments.at(2)));
+ process.setProgram(QDir::fromNativeSeparators(arguments.at(3)));
+ process.setArguments(arguments.mid(4));
+ process.start();
+ process.waitForFinished(-1);
+ return process.exitCode();
+ }
+
+ setupPath();
+
+ #if !defined(QT_NO_SSL)
+ qDebug("ssl support: %d", QSslSocket::supportsSsl());
+ #else
+ qDebug("non-ssl build");
+ #endif
+
+ bool forcePrimary = false;
+ if (arguments.contains("update")) {
+ arguments.removeAll("update");
+ forcePrimary = true;
+ }
+
+ SingleInstance instance(forcePrimary);
+ if (!instance.primaryInstance()) {
+ if ((arguments.size() == 2) && isNxmLink(arguments.at(1))) {
+ qDebug("not primary instance, sending 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 {
+ dataPath = InstanceManager::instance().determineDataPath();
+ } catch (const std::exception &e) {
+ QMessageBox::critical(nullptr, QObject::tr("Failed to set up instance"),
+ e.what());
+ return 1;
+ }
+ application.setProperty("dataPath", dataPath);
+
+ LogBuffer::init(100, 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;
+ } while (true);
+}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 72ee8d6a..f554f0aa 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -38,6 +38,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "savegameinfo.h"
#include "spawn.h"
#include "versioninfo.h"
+#include "instancemanager.h"
#include "report.h"
#include "modlist.h"
@@ -72,14 +73,16 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "previewdialog.h"
#include "browserdialog.h"
#include "aboutdialog.h"
-#include "safewritefile.h"
+#include <safewritefile.h>
#include "nxmaccessmanager.h"
#include "appconfig.h"
+#include "eventfilter.h"
#include <utility.h>
#include <dataarchives.h>
#include <bsainvalidation.h>
#include <taskprogressmanager.h>
#include <scopeguard.h>
+#include <usvfs.h>
#include <QAbstractItemDelegate>
#include <QAbstractProxyModel>
@@ -143,15 +146,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QWhatsThis>
#include <QWidgetAction>
-#include <QtDebug>
+#include <QDebug>
#include <QtGlobal>
-
-#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtConcurrent/QtConcurrentRun>
-#else
-#include <QtConcurrentRun>
-#endif
#ifndef Q_MOC_RUN
#include <boost/thread.hpp>
@@ -181,8 +179,7 @@ using namespace MOBase;
using namespace MOShared;
-MainWindow::MainWindow(const QString &exeName
- , QSettings &initSettings
+MainWindow::MainWindow(QSettings &initSettings
, OrganizerCore &organizerCore
, PluginContainer &pluginContainer
, QWidget *parent)
@@ -190,7 +187,6 @@ MainWindow::MainWindow(const QString &exeName
, ui(new Ui::MainWindow)
, m_WasVisible(false)
, m_Tutorial(this, "MainWindow")
- , m_ExeName(exeName)
, m_OldProfileIndex(-1)
, m_ModListGroupingProxy(nullptr)
, m_ModListSortProxy(nullptr)
@@ -202,12 +198,14 @@ MainWindow::MainWindow(const QString &exeName
, m_OrganizerCore(organizerCore)
, m_PluginContainer(pluginContainer)
, m_DidUpdateMasterList(false)
- , m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this))
{
ui->setupUi(this);
updateWindowTitle(QString(), false);
+
languageChange(m_OrganizerCore.settings().language());
+ m_CategoryFactory.loadCategories();
+
ui->logList->setModel(LogBuffer::instance());
ui->logList->setColumnWidth(0, 100);
ui->logList->setAutoScroll(true);
@@ -227,6 +225,7 @@ MainWindow::MainWindow(const QString &exeName
m_RefreshProgress->setVisible(false);
statusBar()->addWidget(m_RefreshProgress, 1000);
statusBar()->clearMessage();
+ statusBar()->hide();
ui->actionEndorseMO->setVisible(false);
@@ -247,12 +246,14 @@ MainWindow::MainWindow(const QString &exeName
ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(ui->modList));
ui->modList->setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate);
ui->modList->header()->installEventFilter(m_OrganizerCore.modList());
- if (initSettings.contains("mod_list_state")) {
- ui->modList->header()->restoreState(initSettings.value("mod_list_state").toByteArray());
+ bool modListAdjusted = registerWidgetState(ui->modList->objectName(), ui->modList->header(), "mod_list_state");
+
+ if (modListAdjusted) {
// hack: force the resize-signal to be triggered because restoreState doesn't seem to do that
- ui->modList->header()->resizeSection(ModList::COL_CONTENT, ui->modList->header()->sectionSize(ModList::COL_CONTENT) + 1);
- ui->modList->header()->resizeSection(ModList::COL_CONTENT, ui->modList->header()->sectionSize(ModList::COL_CONTENT) - 1);
+ int sectionSize = ui->modList->header()->sectionSize(ModList::COL_CONTENT);
+ ui->modList->header()->resizeSection(ModList::COL_CONTENT, sectionSize + 1);
+ ui->modList->header()->resizeSection(ModList::COL_CONTENT, sectionSize);
} else {
// hide these columns by default
ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true);
@@ -269,17 +270,17 @@ MainWindow::MainWindow(const QString &exeName
ui->espList->setModel(m_PluginListSortProxy);
ui->espList->sortByColumn(PluginList::COL_PRIORITY, Qt::AscendingOrder);
ui->espList->setItemDelegateForColumn(PluginList::COL_FLAGS, new GenericIconDelegate(ui->espList));
- if (initSettings.contains("plugin_list_state")) {
- ui->espList->header()->restoreState(initSettings.value("plugin_list_state").toByteArray());
- }
ui->espList->installEventFilter(m_OrganizerCore.pluginList());
- ui->bsaList->setLocalMoveOnly(true);
+ bool pluginListAdjusted = registerWidgetState(ui->espList->objectName(), ui->espList->header(), "plugin_list_state");
+ registerWidgetState(ui->dataTree->objectName(), ui->dataTree->header());
+ registerWidgetState(ui->downloadView->objectName(),
+ ui->downloadView->header());
ui->splitter->setStretchFactor(0, 3);
ui->splitter->setStretchFactor(1, 2);
- resizeLists(initSettings.contains("mod_list_state"), initSettings.contains("plugin_list_state"));
+ resizeLists(modListAdjusted, pluginListAdjusted);
QMenu *linkMenu = new QMenu(this);
linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar"), this, SLOT(linkToolbar()));
@@ -294,6 +295,16 @@ MainWindow::MainWindow(const QString &exeName
ui->savegameList->installEventFilter(this);
ui->savegameList->setMouseTracking(true);
+ // don't allow mouse wheel to switch grouping, too many people accidentally
+ // turn on grouping and then don't understand what happened
+ EventFilter *noWheel
+ = new EventFilter(this, [](QObject *, QEvent *event) -> bool {
+ return event->type() == QEvent::Wheel;
+ });
+
+ ui->groupCombo->installEventFilter(noWheel);
+ ui->profileBox->installEventFilter(noWheel);
+
connect(ui->savegameList, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(saveSelectionChanged(QListWidgetItem*)));
connect(ui->modList, SIGNAL(dropModeUpdate(bool)), m_OrganizerCore.modList(), SLOT(dropModeUpdate(bool)));
@@ -305,8 +316,6 @@ MainWindow::MainWindow(const QString &exeName
connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), m_PluginListSortProxy, SLOT(updateFilter(QString)));
connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(QString)));
- connect(ui->bsaList, SIGNAL(itemsMoved()), this, SLOT(bsaList_itemMoved()));
-
connect(ui->dataTree, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(expandDataTreeItem(QTreeWidgetItem*)));
connect(m_OrganizerCore.directoryRefresher(), SIGNAL(refreshed()), this, SLOT(directory_refreshed()));
@@ -328,11 +337,8 @@ MainWindow::MainWindow(const QString &exeName
connect(NexusInterface::instance(), SIGNAL(nxmDownloadURLsAvailable(int,int,QVariant,QVariant,int)), this, SLOT(nxmDownloadURLs(int,int,QVariant,QVariant,int)));
connect(NexusInterface::instance(), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusLogin()));
connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString)));
- connect(NexusInterface::instance()->getAccessManager(), &NXMAccessManager::credentialsReceived,
- [this] (const QString &accountName, bool premium) {
- qDebug("user %s premium", premium ? "is" : "is not");
- updateWindowTitle(accountName, premium);
- });
+ connect(NexusInterface::instance()->getAccessManager(), SIGNAL(credentialsReceived(const QString&, bool)),
+ this, SLOT(updateWindowTitle(const QString&, bool)));
connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString)));
connect(ui->tabWidget, SIGNAL(currentChanged(int)), &TutorialManager::instance(), SIGNAL(tabChanged(int)));
@@ -340,14 +346,12 @@ MainWindow::MainWindow(const QString &exeName
connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint)));
connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled);
+ connect(&m_OrganizerCore, &OrganizerCore::close, this, &QMainWindow::close);
connect(&m_IntegratedBrowser, SIGNAL(requestDownload(QUrl,QNetworkReply*)), &m_OrganizerCore, SLOT(requestDownload(QUrl,QNetworkReply*)));
connect(this, SIGNAL(styleChanged(QString)), this, SLOT(updateStyle(QString)));
- m_CheckBSATimer.setSingleShot(true);
- connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList()));
-
m_UpdateProblemsTimer.setSingleShot(true);
connect(&m_UpdateProblemsTimer, SIGNAL(timeout()), this, SLOT(updateProblemsButton()));
@@ -389,6 +393,8 @@ MainWindow::MainWindow(const QString &exeName
MainWindow::~MainWindow()
{
+ cleanup();
+
m_PluginContainer.setUserInterface(nullptr, nullptr);
m_OrganizerCore.setUserInterface(nullptr, nullptr);
m_IntegratedBrowser.close();
@@ -402,9 +408,7 @@ void MainWindow::updateWindowTitle(const QString &accountName, bool premium)
m_OrganizerCore.managedGame()->gameName(),
m_OrganizerCore.getVersion().displayString());
- if (accountName.isEmpty()) {
- title.append(" (not logged in)");
- } else {
+ if (!accountName.isEmpty()) {
title.append(QString(" (%1%2)").arg(accountName, premium ? "*" : ""));
}
@@ -575,7 +579,7 @@ void MainWindow::scheduleUpdateButton()
void MainWindow::updateProblemsButton()
{
- int numProblems = checkForProblems();
+ size_t numProblems = checkForProblems();
if (numProblems > 0) {
ui->actionProblems->setEnabled(true);
ui->actionProblems->setIconText(tr("Problems"));
@@ -627,9 +631,9 @@ bool MainWindow::errorReported(QString &logFile)
}
-int MainWindow::checkForProblems()
+size_t MainWindow::checkForProblems()
{
- int numProblems = 0;
+ size_t numProblems = 0;
for (IPluginDiagnose *diagnose : m_PluginContainer.plugins<IPluginDiagnose>()) {
numProblems += diagnose->activeProblems().size();
}
@@ -854,6 +858,14 @@ void MainWindow::closeEvent(QCloseEvent* event)
}
setCursor(Qt::WaitCursor);
+}
+
+void MainWindow::cleanup()
+{
+ if (ui->logList->model() != nullptr) {
+ disconnect(ui->logList->model(), nullptr, nullptr, nullptr);
+ ui->logList->setModel(nullptr);
+ }
m_IntegratedBrowser.close();
}
@@ -1000,14 +1012,14 @@ void MainWindow::startExeAction()
{
QAction *action = qobject_cast<QAction*>(sender());
if (action != nullptr) {
- const Executable &selectedExecutable(m_OrganizerCore.executablesList()->find(action->text()));
+ const Executable &selectedExecutable(
+ m_OrganizerCore.executablesList()->find(action->text()));
m_OrganizerCore.spawnBinary(
- selectedExecutable.m_BinaryInfo,
- selectedExecutable.m_Arguments,
- selectedExecutable.m_WorkingDirectory.length() != 0 ? selectedExecutable.m_WorkingDirectory
- : selectedExecutable.m_BinaryInfo.absolutePath(),
- selectedExecutable.m_CloseMO == ExecutableInfo::CloseMOStyle::DEFAULT_CLOSE,
- selectedExecutable.m_SteamAppID);
+ selectedExecutable.m_BinaryInfo, selectedExecutable.m_Arguments,
+ selectedExecutable.m_WorkingDirectory.length() != 0
+ ? selectedExecutable.m_WorkingDirectory
+ : selectedExecutable.m_BinaryInfo.absolutePath(),
+ selectedExecutable.m_SteamAppID);
} else {
qCritical("not an action?");
}
@@ -1068,9 +1080,7 @@ void MainWindow::on_profileBox_currentIndexChanged(int index)
void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const DirectoryEntry &directoryEntry, bool conflictsOnly)
{
{
- std::vector<FileEntry::Ptr> files = directoryEntry.getFiles();
- for (auto iter = files.begin(); iter != files.end(); ++iter) {
- FileEntry::Ptr current = *iter;
+ for (const FileEntry::Ptr current : directoryEntry.getFiles()) {
if (conflictsOnly && (current->getAlternatives().size() == 0)) {
continue;
}
@@ -1165,7 +1175,7 @@ void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &director
void MainWindow::delayedRemove()
{
- foreach (QTreeWidgetItem *item, m_RemoveWidget) {
+ for (QTreeWidgetItem *item : m_RemoveWidget) {
item->removeChild(item->child(0));
}
m_RemoveWidget.clear();
@@ -1202,7 +1212,7 @@ bool MainWindow::refreshProfiles(bool selectProfile)
profileBox->clear();
profileBox->addItem(QObject::tr("<Manage...>"));
- QDir profilesDir(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::profilesPath()));
+ QDir profilesDir(Settings::instance().getProfileDirectory());
profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
QDirIterator profileIter(profilesDir);
@@ -1264,7 +1274,6 @@ void MainWindow::refreshDataTree()
updateTo(subTree, L"", *m_OrganizerCore.directoryStructure(), conflictsBox->isChecked());
tree->insertTopLevelItem(0, subTree);
subTree->setExpanded(true);
- tree->header()->resizeSection(0, 200);
}
@@ -1320,9 +1329,10 @@ void MainWindow::refreshSaveList()
QDir savesDir = currentSavesDir();
savesDir.setNameFilters(filters);
+ qDebug("reading save games from %s", qPrintable(savesDir.absolutePath()));
QFileInfoList files = savesDir.entryInfoList(QDir::Files, QDir::Time);
- foreach (const QFileInfo &file, files) {
+ for (const QFileInfo &file : files) {
QListWidgetItem *item = new QListWidgetItem(file.fileName());
item->setData(Qt::UserRole, file.absoluteFilePath());
ui->savegameList->addItem(item);
@@ -1345,163 +1355,6 @@ static QStringList toStringList(InputIterator current, InputIterator end)
return result;
}
-void MainWindow::updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives)
-{
- m_DefaultArchives = defaultArchives;
- ui->bsaList->clear();
-#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
- ui->bsaList->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
-#else
- ui->bsaList->header()->setResizeMode(QHeaderView::ResizeToContents);
-#endif
-
- std::vector<std::pair<UINT32, QTreeWidgetItem*>> items;
-
- BSAInvalidation *invalidation = m_OrganizerCore.managedGame()->feature<BSAInvalidation>();
- std::vector<FileEntry::Ptr> files = m_OrganizerCore.directoryStructure()->getFiles();
-
- QStringList plugins = m_OrganizerCore.findFiles("", [] (const QString &fileName) -> bool {
- return fileName.endsWith(".esp", Qt::CaseInsensitive)
- || fileName.endsWith(".esm", Qt::CaseInsensitive);
- });
-
- auto hasAssociatedPlugin = [&](const QString &bsaName) -> bool {
- for (const QString &pluginName : plugins) {
- QFileInfo pluginInfo(pluginName);
- if (bsaName.startsWith(QFileInfo(pluginName).baseName(), Qt::CaseInsensitive)
- && (m_OrganizerCore.pluginList()->state(pluginInfo.fileName()) == IPluginList::STATE_ACTIVE)) {
- return true;
- }
- }
- return false;
- };
-
- for (FileEntry::Ptr current : files) {
- QFileInfo fileInfo(ToQString(current->getName().c_str()));
-
- if (fileInfo.suffix().toLower() == "bsa") {
- int index = activeArchives.indexOf(fileInfo.fileName());
- if (index == -1) {
- index = 0xFFFF;
- } else {
- index += 2;
- }
-
- if ((invalidation != nullptr) && invalidation->isInvalidationBSA(fileInfo.fileName())) {
- index = 1;
- }
-
- int originId = current->getOrigin();
- FilesOrigin &origin = m_OrganizerCore.directoryStructure()->getOriginByID(originId);
-
- QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList()
- << fileInfo.fileName()
- << ToQString(origin.getName()));
- newItem->setData(0, Qt::UserRole, index);
- newItem->setData(1, Qt::UserRole, originId);
- newItem->setFlags(newItem->flags() & ~Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable);
- newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked);
- newItem->setData(0, Qt::UserRole, false);
- if (m_OrganizerCore.settings().forceEnableCoreFiles()
- && defaultArchives.contains(fileInfo.fileName())) {
- newItem->setCheckState(0, Qt::Checked);
- newItem->setDisabled(true);
- newItem->setData(0, Qt::UserRole, true);
- } else if (fileInfo.fileName().compare("update.bsa", Qt::CaseInsensitive) == 0) {
- newItem->setCheckState(0, Qt::Checked);
- newItem->setDisabled(true);
- } else if (hasAssociatedPlugin(fileInfo.fileName())) {
- newItem->setCheckState(0, Qt::Checked);
- newItem->setDisabled(true);
- } else {
- if (ui->manageArchivesBox->isChecked()) {
- newItem->setCheckState(0, (index != 0xFFFF) ? Qt::Checked : Qt::Unchecked);
- } else {
- newItem->setCheckState(0, Qt::Unchecked);
- newItem->setDisabled(true);
- }
- }
-
- if (index < 0) index = 0;
-
- UINT32 sortValue = ((origin.getPriority() & 0xFFFF) << 16) | (index & 0xFFFF);
- items.push_back(std::make_pair(sortValue, newItem));
- }
- }
-
- std::sort(items.begin(), items.end(), BySortValue);
-
- for (auto iter = items.begin(); iter != items.end(); ++iter) {
- int originID = iter->second->data(1, Qt::UserRole).toInt();
-
- FilesOrigin origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID);
- QString modName("data");
- unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName()));
- if (modIndex != UINT_MAX) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
- modName = modInfo->name();
- }
- QList<QTreeWidgetItem*> items = ui->bsaList->findItems(modName, Qt::MatchFixedString);
- QTreeWidgetItem *subItem = nullptr;
- if (items.length() > 0) {
- subItem = items.at(0);
- } else {
- subItem = new QTreeWidgetItem(QStringList(modName));
- subItem->setFlags(subItem->flags() & ~Qt::ItemIsDragEnabled);
- ui->bsaList->addTopLevelItem(subItem);
- }
- subItem->addChild(iter->second);
- subItem->setExpanded(true);
- }
-
- checkBSAList();
-}
-
-
-void MainWindow::checkBSAList()
-{
- DataArchives *archives = m_OrganizerCore.managedGame()->feature<DataArchives>();
-
- if (archives != nullptr) {
- ui->bsaList->blockSignals(true);
- ON_BLOCK_EXIT([&] () { ui->bsaList->blockSignals(false); });
-
- QStringList defaultArchives = archives->archives(m_OrganizerCore.currentProfile());
-
- bool warning = false;
-
- for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) {
- bool modWarning = false;
- QTreeWidgetItem *tlItem = ui->bsaList->topLevelItem(i);
- for (int j = 0; j < tlItem->childCount(); ++j) {
- QTreeWidgetItem *item = tlItem->child(j);
- QString filename = item->text(0);
- item->setIcon(0, QIcon());
- item->setToolTip(0, QString());
-
- if (item->checkState(0) == Qt::Unchecked) {
- if (defaultArchives.contains(filename)) {
- item->setIcon(0, QIcon(":/MO/gui/warning"));
- item->setToolTip(0, tr("This bsa is enabled in the ini file so it may be required!"));
- modWarning = true;
- }
- }
- }
- if (modWarning) {
- ui->bsaList->expandItem(ui->bsaList->topLevelItem(i));
- warning = true;
- }
- }
-
- if (warning) {
- ui->tabWidget->setTabIcon(1, QIcon(":/MO/gui/warning"));
- } else {
- ui->tabWidget->setTabIcon(1, QIcon());
- }
- }
-}
-
-
void MainWindow::saveModMetas()
{
for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
@@ -1510,7 +1363,6 @@ void MainWindow::saveModMetas()
}
}
-
void MainWindow::fixCategories()
{
for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
@@ -1581,18 +1433,9 @@ void MainWindow::readSettings()
if (settings.value("Settings/use_proxy", false).toBool()) {
activateProxy(true);
}
-
- ui->manageArchivesBox->blockSignals(true);
- ui->manageArchivesBox->setChecked(settings.value("manage_bsas", true).toBool());
- ui->manageArchivesBox->blockSignals(false);
}
-
-void MainWindow::storeSettings(QSettings &settings)
-{
- settings.setValue("mod_list_state", ui->modList->header()->saveState());
- settings.setValue("plugin_list_state", ui->espList->header()->saveState());
-
+void MainWindow::storeSettings(QSettings &settings) {
settings.setValue("group_state", ui->groupCombo->currentIndex());
settings.setValue("window_geometry", saveGeometry());
@@ -1602,9 +1445,14 @@ void MainWindow::storeSettings(QSettings &settings)
settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry());
settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked());
- settings.setValue("manage_bsas", ui->manageArchivesBox->isChecked());
- settings.setValue("selected_executable", ui->executablesListBox->currentIndex());
+ settings.setValue("selected_executable",
+ ui->executablesListBox->currentIndex());
+
+ for (const std::pair<QString, QHeaderView*> kv : m_PersistedGeometry) {
+ QString key = QString("geometry/") + kv.first;
+ settings.setValue(key, kv.second->saveState());
+ }
}
void MainWindow::lock()
@@ -1662,12 +1510,10 @@ void MainWindow::on_tabWidget_currentChanged(int index)
if (index == 0) {
m_OrganizerCore.refreshESPList();
} else if (index == 1) {
- m_OrganizerCore.refreshBSAList();
- } else if (index == 2) {
refreshDataTree();
- } else if (index == 3) {
+ } else if (index == 2) {
refreshSaveList();
- } else if (index == 4) {
+ } else if (index == 3) {
ui->downloadView->scrollToBottom();
}
}
@@ -1696,21 +1542,17 @@ void MainWindow::installMod(QString fileName)
}
}
-
-void MainWindow::on_startButton_clicked()
-{
+void MainWindow::on_startButton_clicked() {
const Executable &selectedExecutable(getSelectedExecutable());
m_OrganizerCore.spawnBinary(
- selectedExecutable.m_BinaryInfo,
- selectedExecutable.m_Arguments,
- selectedExecutable.m_WorkingDirectory.length() != 0 ? selectedExecutable.m_WorkingDirectory
- : selectedExecutable.m_BinaryInfo.absolutePath(),
- selectedExecutable.m_CloseMO == ExecutableInfo::CloseMOStyle::DEFAULT_CLOSE,
- selectedExecutable.m_SteamAppID);
+ selectedExecutable.m_BinaryInfo, selectedExecutable.m_Arguments,
+ selectedExecutable.m_WorkingDirectory.length() != 0
+ ? selectedExecutable.m_WorkingDirectory
+ : selectedExecutable.m_BinaryInfo.absolutePath(),
+ selectedExecutable.m_SteamAppID);
}
-
static HRESULT CreateShortcut(LPCWSTR targetFileName, LPCWSTR arguments,
LPCSTR linkFileName, LPCWSTR description,
LPCTSTR iconFileName, int iconNumber,
@@ -1797,7 +1639,9 @@ bool MainWindow::modifyExecutablesDialog()
{
bool result = false;
try {
- EditExecutablesDialog dialog(*m_OrganizerCore.executablesList());
+ EditExecutablesDialog dialog(*m_OrganizerCore.executablesList(),
+ *m_OrganizerCore.modList(),
+ m_OrganizerCore.currentProfile());
if (dialog.exec() == QDialog::Accepted) {
m_OrganizerCore.setExecutablesList(dialog.getExecutablesList());
result = true;
@@ -1917,8 +1761,10 @@ void MainWindow::setESPListSorting(int index)
void MainWindow::refresher_progress(int percent)
{
if (percent == 100) {
-// m_RefreshProgress->setVisible(false);
+ m_RefreshProgress->setVisible(false);
+ statusBar()->hide();
} else if (!m_RefreshProgress->isVisible()) {
+ statusBar()->show();
m_RefreshProgress->setVisible(true);
m_RefreshProgress->setRange(0, 100);
m_RefreshProgress->setValue(percent);
@@ -1946,7 +1792,6 @@ void MainWindow::modorder_changed()
}
m_OrganizerCore.refreshBSAList();
m_OrganizerCore.currentProfile()->modlistWriter().write();
- m_ArchiveListWriter.write();
m_OrganizerCore.directoryStructure()->getFileRegister()->sortOrigins();
{ // refresh selection
@@ -2137,11 +1982,14 @@ void MainWindow::addContentFilters()
void MainWindow::addCategoryFilters(QTreeWidgetItem *root, const std::set<int> &categoriesUsed, int targetID)
{
- for (size_t i = 1; i < m_CategoryFactory.numCategories(); ++i) {
+ for (unsigned int i = 1;
+ i < static_cast<unsigned int>(m_CategoryFactory.numCategories()); ++i) {
if ((m_CategoryFactory.getParentID(i) == targetID)) {
int categoryID = m_CategoryFactory.getCategoryID(i);
if (categoriesUsed.find(categoryID) != categoriesUsed.end()) {
- QTreeWidgetItem *item = addFilterItem(root, m_CategoryFactory.getCategoryName(i), categoryID, ModListSortProxy::TYPE_CATEGORY);
+ QTreeWidgetItem *item =
+ addFilterItem(root, m_CategoryFactory.getCategoryName(i),
+ categoryID, ModListSortProxy::TYPE_CATEGORY);
if (m_CategoryFactory.hasChildren(i)) {
addCategoryFilters(item, categoriesUsed, categoryID);
}
@@ -2277,7 +2125,7 @@ void MainWindow::removeMod_clicked()
if (selection->hasSelection() && selection->selectedRows().count() > 1) {
QString mods;
QStringList modNames;
- foreach (QModelIndex idx, selection->selectedRows()) {
+ for (QModelIndex idx : selection->selectedRows()) {
QString name = idx.data().toString();
if (!ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->isRegular()) {
continue;
@@ -2289,7 +2137,7 @@ void MainWindow::removeMod_clicked()
tr("Remove the following mods?<br><ul>%1</ul>").arg(mods),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
// use mod names instead of indexes because those become invalid during the removal
- foreach (QString name, modNames) {
+ for (QString name : modNames) {
m_OrganizerCore.modList()->removeRowForce(ModInfo::getIndex(name), QModelIndex());
}
}
@@ -2603,7 +2451,32 @@ void MainWindow::information_clicked()
}
}
+void MainWindow::createEmptyMod_clicked()
+{
+ GuessedValue<QString> name;
+ name.setFilter(&fixDirectoryName);
+ while (name->isEmpty()) {
+ bool ok;
+ name.update(QInputDialog::getText(this, tr("Create Mod..."),
+ tr("This will create an empty mod.\n"
+ "Please enter a name:"), QLineEdit::Normal, "", &ok),
+ GUESS_USER);
+ if (!ok) {
+ return;
+ }
+ }
+
+ if (m_OrganizerCore.getMod(name) != nullptr) {
+ reportError(tr("A mod with this name already exists"));
+ return;
+ }
+
+ IModInterface *newMod = m_OrganizerCore.createMod(name);
+ if (newMod == nullptr) {
+ return;
+ }
+}
void MainWindow::createModFromOverwrite()
{
@@ -2682,7 +2555,7 @@ bool MainWindow::populateMenuCategories(QMenu *menu, int targetID)
bool childEnabled = false;
- for (size_t i = 1; i < m_CategoryFactory.numCategories(); ++i) {
+ for (unsigned int i = 1; i < m_CategoryFactory.numCategories(); ++i) {
if (m_CategoryFactory.getParentID(i) == targetID) {
QMenu *targetMenu = menu;
if (m_CategoryFactory.hasChildren(i)) {
@@ -2716,7 +2589,7 @@ bool MainWindow::populateMenuCategories(QMenu *menu, int targetID)
void MainWindow::replaceCategoriesFromMenu(QMenu *menu, int modRow)
{
ModInfo::Ptr modInfo = ModInfo::getByIndex(modRow);
- foreach (QAction* action, menu->actions()) {
+ for (QAction* action : menu->actions()) {
if (action->menu() != nullptr) {
replaceCategoriesFromMenu(action->menu(), modRow);
} else {
@@ -2841,55 +2714,6 @@ void MainWindow::replaceCategories_MenuHandler() {
refreshFilters();
}
-void MainWindow::savePrimaryCategory()
-{
- QMenu *menu = qobject_cast<QMenu*>(sender());
- if (menu == nullptr) {
- qCritical("not a menu?");
- return;
- }
-
- foreach (QAction* action, menu->actions()) {
- QWidgetAction *widgetAction = qobject_cast<QWidgetAction*>(action);
- if (widgetAction != nullptr) {
- QRadioButton *btn = qobject_cast<QRadioButton*>(widgetAction->defaultWidget());
- if (btn->isChecked()) {
- QModelIndexList selected = ui->modList->selectionModel()->selectedRows();
- for (int i = 0; i < selected.size(); ++i) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ModListSortProxy->mapToSource(selected.at(i)).row());
- modInfo->setPrimaryCategory(widgetAction->data().toInt());
- }
- break;
- }
- }
- }
-}
-
-void MainWindow::saveArchiveList()
-{
- if (m_OrganizerCore.isArchivesInit()) {
- SafeWriteFile archiveFile(m_OrganizerCore.currentProfile()->getArchivesFileName());
- for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) {
- QTreeWidgetItem *tlItem = ui->bsaList->topLevelItem(i);
- for (int j = 0; j < tlItem->childCount(); ++j) {
- QTreeWidgetItem *item = tlItem->child(j);
- if (item->checkState(0) == Qt::Checked) {
- // in managed mode, "register" all enabled archives, otherwise register only the files registered in the ini
- if (ui->manageArchivesBox->isChecked()
- || item->data(0, Qt::UserRole).toBool()) {
- archiveFile->write(item->text(0).toUtf8().append("\r\n"));
- }
- }
- }
- }
- if (archiveFile.commitIfDifferent(m_ArchiveListHash)) {
- qDebug("%s saved", qPrintable(QDir::toNativeSeparators(m_OrganizerCore.currentProfile()->getArchivesFileName())));
- }
- } else {
- qWarning("archive list not initialised");
- }
-}
-
void MainWindow::checkModsForUpdates()
{
statusBar()->show();
@@ -2949,15 +2773,21 @@ void MainWindow::unignoreUpdate()
info->ignoreUpdate(false);
}
-void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info)
-{
+void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu,
+ ModInfo::Ptr info) {
const std::set<int> &categories = info->getCategories();
- foreach (int categoryID, categories) {
+ for (int categoryID : categories) {
int catIdx = m_CategoryFactory.getCategoryIndex(categoryID);
QWidgetAction *action = new QWidgetAction(primaryCategoryMenu);
try {
- QRadioButton *categoryBox = new QRadioButton(m_CategoryFactory.getCategoryName(catIdx).replace('&', "&&"),
- primaryCategoryMenu);
+ QRadioButton *categoryBox = new QRadioButton(
+ m_CategoryFactory.getCategoryName(catIdx).replace('&', "&&"),
+ primaryCategoryMenu);
+ connect(categoryBox, &QRadioButton::toggled, [info, categoryID](bool enable) {
+ if (enable) {
+ info->setPrimaryCategory(categoryID);
+ }
+ });
categoryBox->setChecked(categoryID == info->getPrimaryCategory());
action->setDefaultWidget(categoryBox);
} catch (const std::exception &e) {
@@ -3066,6 +2896,8 @@ QMenu *MainWindow::modListContextMenu()
QMenu *menu = new QMenu(this);
menu->addAction(tr("Install Mod..."), this, SLOT(installMod_clicked()));
+ menu->addAction(tr("Create empty mod"), this, SLOT(createEmptyMod_clicked()));
+
menu->addAction(tr("Enable all visible"), this, SLOT(enableVisibleMods()));
menu->addAction(tr("Disable all visible"), this, SLOT(disableVisibleMods()));
@@ -3119,7 +2951,6 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos)
QMenu *primaryCategoryMenu = new QMenu(tr("Primary Category"));
connect(primaryCategoryMenu, SIGNAL(aboutToShow()), this, SLOT(addPrimaryCategoryCandidates()));
- connect(primaryCategoryMenu, SIGNAL(aboutToHide()), this, SLOT(savePrimaryCategory()));
addMenuAsPushButton(menu, primaryCategoryMenu);
menu->addSeparator();
@@ -3165,11 +2996,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos)
if (info->getNexusID() > 0) {
menu->addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked()));
- }
-
- //If a URL is specified which is not the game's URL, pop up 'visit web page'
- if (info->getURL() != "" &&
- !NexusInterface::instance()->isModURL(info->getNexusID(), info->getURL())) {
+ } else if ((info->getURL() != "")) {
menu->addAction(tr("Visit web page"), this, SLOT(visitWebPage_clicked()));
}
@@ -3196,7 +3023,7 @@ void MainWindow::on_categoriesList_itemSelectionChanged()
QModelIndexList indices = ui->categoriesList->selectionModel()->selectedRows();
std::vector<int> categories;
std::vector<int> content;
- foreach (const QModelIndex &index, indices) {
+ for (const QModelIndex &index : indices) {
int filterType = index.data(Qt::UserRole + 1).toInt();
if ((filterType == ModListSortProxy::TYPE_CATEGORY)
|| (filterType == ModListSortProxy::TYPE_SPECIAL)) {
@@ -3212,7 +3039,7 @@ void MainWindow::on_categoriesList_itemSelectionChanged()
m_ModListSortProxy->setCategoryFilter(categories);
m_ModListSortProxy->setContentFilter(content);
- ui->clickBlankLabel->setEnabled(categories.size() > 0);
+ ui->clickBlankButton->setEnabled(categories.size() > 0);
if (indices.count() == 0) {
ui->currentCategoryLabel->setText(QString("(%1)").arg(tr("<All>")));
} else if (indices.count() > 1) {
@@ -3260,6 +3087,7 @@ void MainWindow::deleteSavegame_clicked()
.arg(savesMsgLabel),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
shellDelete(deleteFiles, true); // recycle bin delete.
+ refreshSaveList();
}
}
@@ -3347,7 +3175,7 @@ QString getStartMenuLinkfile(const Executable &exec)
void MainWindow::addWindowsLink(const ShortcutType mapping)
{
- Executable const &selectedExecutable(getSelectedExecutable());
+ const Executable &selectedExecutable(getSelectedExecutable());
QString const linkName = getLinkfile(mapping == ShortcutType::Desktop ? getDesktopDirectory() : getStartMenuDirectory(),
selectedExecutable);
@@ -3394,40 +3222,58 @@ void MainWindow::linkMenu()
void MainWindow::on_actionSettings_triggered()
{
- QString oldModDirectory(m_OrganizerCore.settings().getModDirectory());
- QString oldCacheDirectory(m_OrganizerCore.settings().getCacheDirectory());
- bool oldDisplayForeign(m_OrganizerCore.settings().displayForeign());
- bool proxy = m_OrganizerCore.settings().useProxy();
- m_OrganizerCore.settings().query(this);
- m_OrganizerCore.installationManager()->setModsDirectory(m_OrganizerCore.settings().getModDirectory());
- m_OrganizerCore.installationManager()->setDownloadDirectory(m_OrganizerCore.settings().getDownloadDirectory());
+ Settings &settings = m_OrganizerCore.settings();
+
+ QString oldModDirectory(settings.getModDirectory());
+ QString oldCacheDirectory(settings.getCacheDirectory());
+ QString oldProfilesDirectory(settings.getProfileDirectory());
+ bool oldDisplayForeign(settings.displayForeign());
+ bool proxy = settings.useProxy();
+
+ settings.query(this);
+
+ InstallationManager *instManager = m_OrganizerCore.installationManager();
+ instManager->setModsDirectory(settings.getModDirectory());
+ instManager->setDownloadDirectory(settings.getDownloadDirectory());
+
fixCategories();
refreshFilters();
- if (QDir::fromNativeSeparators(m_OrganizerCore.downloadManager()->getOutputDirectory()) != QDir::fromNativeSeparators(m_OrganizerCore.settings().getDownloadDirectory())) {
- if (m_OrganizerCore.downloadManager()->downloadsInProgress()) {
- MessageDialog::showMessage(tr("Can't change download directory while downloads are in progress!"), this);
+
+ if (settings.getProfileDirectory() != oldProfilesDirectory) {
+ refreshProfiles();
+ }
+
+ DownloadManager *dlManager = m_OrganizerCore.downloadManager();
+
+ if (dlManager->getOutputDirectory() != settings.getDownloadDirectory()) {
+ if (dlManager->downloadsInProgress()) {
+ MessageDialog::showMessage(tr("Can't change download directory while "
+ "downloads are in progress!"),
+ this);
} else {
- m_OrganizerCore.downloadManager()->setOutputDirectory(m_OrganizerCore.settings().getDownloadDirectory());
+ dlManager->setOutputDirectory(settings.getDownloadDirectory());
}
}
- m_OrganizerCore.downloadManager()->setPreferredServers(m_OrganizerCore.settings().getPreferredServers());
+ dlManager->setPreferredServers(settings.getPreferredServers());
- if ((m_OrganizerCore.settings().getModDirectory() != oldModDirectory)
- || (m_OrganizerCore.settings().displayForeign() != oldDisplayForeign)) {
+ if ((settings.getModDirectory() != oldModDirectory)
+ || (settings.displayForeign() != oldDisplayForeign)) {
m_OrganizerCore.profileRefresh();
}
- if (m_OrganizerCore.settings().getCacheDirectory() != oldCacheDirectory) {
- NexusInterface::instance()->setCacheDirectory(m_OrganizerCore.settings().getCacheDirectory());
+ if (settings.getCacheDirectory() != oldCacheDirectory) {
+ NexusInterface::instance()->setCacheDirectory(settings.getCacheDirectory());
}
- if (proxy != m_OrganizerCore.settings().useProxy()) {
- activateProxy(m_OrganizerCore.settings().useProxy());
+ if (proxy != settings.useProxy()) {
+ activateProxy(settings.useProxy());
}
- NexusInterface::instance()->setNMMVersion(m_OrganizerCore.settings().getNMMVersion());
+ NexusInterface::instance()->setNMMVersion(settings.getNMMVersion());
updateDownloadListDelegate();
+
+ m_OrganizerCore.setLogLevel(settings.logLevel());
}
@@ -3471,7 +3317,7 @@ void MainWindow::installTranslator(const QString &name)
void MainWindow::languageChange(const QString &newLanguage)
{
- foreach (QTranslator *trans, m_Translators) {
+ for (QTranslator *trans : m_Translators) {
qApp->removeTranslator(trans);
}
m_Translators.clear();
@@ -3605,7 +3451,6 @@ void MainWindow::addAsExecutable()
binaryInfo.absoluteFilePath(),
arguments,
targetInfo.absolutePath(),
- ExecutableInfo::CloseMOStyle::DEFAULT_STAY,
QString(),
Executable::CustomExecutable);
refreshExecutablesList();
@@ -3716,7 +3561,7 @@ void MainWindow::previewDataFile()
};
addFunc(file->getOrigin());
- foreach (int i, file->getAlternatives()) {
+ for (int i : file->getAlternatives()) {
addFunc(i);
}
if (preview.numVariants() > 0) {
@@ -3734,10 +3579,14 @@ void MainWindow::openDataFile()
QString arguments;
switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) {
case 1: {
- m_OrganizerCore.spawnBinaryDirect(binaryInfo, arguments, m_OrganizerCore.currentProfile()->name(), targetInfo.absolutePath(), "");
+ m_OrganizerCore.spawnBinaryDirect(
+ binaryInfo, arguments, m_OrganizerCore.currentProfile()->name(),
+ targetInfo.absolutePath(), "", "");
} break;
case 2: {
- ::ShellExecuteW(nullptr, L"open", ToWString(targetInfo.absoluteFilePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
+ ::ShellExecuteW(nullptr, L"open",
+ ToWString(targetInfo.absoluteFilePath()).c_str(),
+ nullptr, nullptr, SW_SHOWNORMAL);
} break;
default: {
// nop
@@ -3875,8 +3724,7 @@ void MainWindow::updateDownloadListDelegate()
void MainWindow::modDetailsUpdated(bool)
{
- --m_ModsToUpdate;
- if (m_ModsToUpdate == 0) {
+ if (--m_ModsToUpdate == 0) {
statusBar()->hide();
m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE));
for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) {
@@ -3893,7 +3741,7 @@ void MainWindow::modDetailsUpdated(bool)
void MainWindow::nxmUpdatesAvailable(const std::vector<int> &modIDs, QVariant userData, QVariant resultData, int)
{
- m_ModsToUpdate -= modIDs.size();
+ m_ModsToUpdate -= static_cast<int>(modIDs.size());
QVariantList resultList = resultData.toList();
for (auto iter = resultList.begin(); iter != resultList.end(); ++iter) {
QVariantMap result = iter->toMap();
@@ -3947,7 +3795,7 @@ void MainWindow::nxmDownloadURLs(int, int, QVariant, QVariant resultData, int)
QVariantList serverList = resultData.toList();
QList<ServerInfo> servers;
- foreach (const QVariant &server, serverList) {
+ for (const QVariant &server : serverList) {
QVariantMap serverInfo = server.toMap();
ServerInfo info;
info.name = serverInfo["Name"].toString();
@@ -4070,7 +3918,7 @@ void MainWindow::displayColumnSelection(const QPoint &pos)
// view/hide columns depending on check-state
int i = 1;
- foreach (const QAction *action, menu.actions()) {
+ for (const QAction *action : menu.actions()) {
const QWidgetAction *widgetAction = qobject_cast<const QWidgetAction*>(action);
if (widgetAction != nullptr) {
const QCheckBox *checkBox = qobject_cast<const QCheckBox*>(widgetAction->defaultWidget());
@@ -4082,32 +3930,6 @@ void MainWindow::displayColumnSelection(const QPoint &pos)
}
}
-
-void MainWindow::on_bsaList_customContextMenuRequested(const QPoint &pos)
-{
- m_ContextItem = ui->bsaList->itemAt(pos);
-
-// m_ContextRow = ui->bsaList->indexOfTopLevelItem(ui->bsaList->itemAt(pos));
-
- QMenu menu;
- menu.addAction(tr("Extract..."), this, SLOT(extractBSATriggered()));
-
- menu.exec(ui->bsaList->mapToGlobal(pos));
-}
-
-void MainWindow::bsaList_itemMoved()
-{
- m_ArchiveListWriter.write();
- m_CheckBSATimer.start(500);
-}
-
-
-void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int)
-{
- m_ArchiveListWriter.write();
- m_CheckBSATimer.start(500);
-}
-
void MainWindow::on_actionProblems_triggered()
{
ProblemsDialog problems(m_PluginContainer.plugins<IPluginDiagnose>(), this);
@@ -4117,6 +3939,17 @@ void MainWindow::on_actionProblems_triggered()
}
}
+void MainWindow::on_actionChange_Game_triggered()
+{
+ if (QMessageBox::question(this, tr("Are you sure?"),
+ tr("This will restart MO, continue?"),
+ QMessageBox::Yes | QMessageBox::Cancel)
+ == QMessageBox::Yes) {
+ InstanceManager::instance().clearCurrentInstance();
+ qApp->exit(INT_MAX);
+ }
+}
+
void MainWindow::setCategoryListVisible(bool visible)
{
if (visible) {
@@ -4425,10 +4258,15 @@ void MainWindow::on_bossButton_clicked()
HANDLE stdOutWrite = INVALID_HANDLE_VALUE;
HANDLE stdOutRead = INVALID_HANDLE_VALUE;
createStdoutPipe(&stdOutRead, &stdOutWrite);
+ try {
+ m_OrganizerCore.prepareVFS();
+ } catch (const std::exception &e) {
+ QMessageBox::warning(qApp->activeWindow(), tr("Error"), e.what());
+ return;
+ }
+
HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"),
parameters.join(" "),
- m_OrganizerCore.currentProfile()->name(),
- m_OrganizerCore.settings().logLevel(),
qApp->applicationDirPath() + "/loot",
true,
stdOutWrite);
@@ -4523,6 +4361,7 @@ void MainWindow::on_bossButton_clicked()
} catch (const std::exception &e) {
reportError(tr("failed to run loot: %1").arg(e.what()));
}
+
if (errorMessages.length() > 0) {
QMessageBox *warn = new QMessageBox(QMessageBox::Warning, tr("Errors occured"), errorMessages.c_str(), QMessageBox::Ok, this);
warn->setModal(false);
@@ -4536,11 +4375,7 @@ void MainWindow::on_bossButton_clicked()
QStringList temp = report.split("?");
QUrl url = QUrl::fromLocalFile(temp.at(0));
if (temp.size() > 1) {
-#if QT_VERSION >= 0x050000
url.setQuery(temp.at(1).toUtf8());
-#else
- url.setEncodedQuery(temp.at(1).toUtf8());
-#endif
}
m_IntegratedBrowser.openUrl(url);
}
@@ -4552,10 +4387,7 @@ void MainWindow::on_bossButton_clicked()
QFile::remove(m_OrganizerCore.currentProfile()->getLoadOrderFileName());
}
m_OrganizerCore.refreshESPList();
- if (m_OrganizerCore.managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) {
- // the load order should have been retrieved from file time, now save it to our own format
- m_OrganizerCore.savePluginList();
- }
+ m_OrganizerCore.savePluginList();
}
}
@@ -4597,7 +4429,7 @@ QString MainWindow::queryRestore(const QString &filePath)
SelectionDialog dialog(tr("Choose backup to restore"), this);
QRegExp exp(pluginFileInfo.fileName() + PATTERN_BACKUP_REGEX);
QRegExp exp2(pluginFileInfo.fileName() + "\\.(.*)");
- foreach(const QFileInfo &info, files) {
+ for(const QFileInfo &info : files) {
if (exp.exactMatch(info.fileName())) {
QDateTime time = QDateTime::fromString(exp.cap(1), PATTERN_BACKUP_DATE);
dialog.addChoice(time.toString(), "", exp.cap(1));
@@ -4683,17 +4515,6 @@ void MainWindow::on_categoriesOrBtn_toggled(bool checked)
}
}
-void MainWindow::on_managedArchiveLabel_linkHovered(const QString&)
-{
- QToolTip::showText(QCursor::pos(),
- ui->managedArchiveLabel->toolTip());
-}
-
-void MainWindow::on_manageArchivesBox_toggled(bool)
-{
- m_OrganizerCore.refreshBSAList();
-}
-
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
//Accept copy or move drags to the download window. Link drags are not
@@ -4781,6 +4602,31 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m
}
}
+bool MainWindow::registerWidgetState(const QString &name, QHeaderView *view, const char *oldSettingName) {
+ // register the view so it's geometry gets saved at exit
+ m_PersistedGeometry.push_back(std::make_pair(name, view));
+
+ // also, restore the geometry if it was saved before
+ QSettings &settings = m_OrganizerCore.settings().directInterface();
+
+ QString key = QString("geometry/%1").arg(name);
+ QByteArray data;
+
+ if ((oldSettingName != nullptr) && settings.contains(oldSettingName)) {
+ data = settings.value(oldSettingName).toByteArray();
+ settings.remove(oldSettingName);
+ } else if (settings.contains(key)) {
+ data = settings.value(key).toByteArray();
+ }
+
+ if (!data.isEmpty()) {
+ view->restoreState(data);
+ return true;
+ } else {
+ return false;
+ }
+}
+
void MainWindow::dropEvent(QDropEvent *event)
{
Qt::DropAction action = event->proposedAction();
@@ -4799,3 +4645,8 @@ void MainWindow::dropEvent(QDropEvent *event)
event->accept();
}
+
+void MainWindow::on_clickBlankButton_clicked()
+{
+ deselectFilters();
+}
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 8e63a14f..06c51203 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -60,6 +60,7 @@ namespace MOShared { class DirectoryEntry; }
#include <QStringList>
#include <QTime>
#include <QTimer>
+#include <QHeaderView>
#include <QVariant>
#include <Qt>
@@ -105,9 +106,9 @@ class MainWindow : public QMainWindow, public IUserInterface
friend class OrganizerProxy;
-
public:
- explicit MainWindow(const QString &exeName, QSettings &initSettings,
+
+ explicit MainWindow(QSettings &initSettings,
OrganizerCore &organizerCore, PluginContainer &pluginContainer,
QWidget *parent = 0);
~MainWindow();
@@ -121,15 +122,12 @@ public:
virtual void setProcessName(QString const &name) override;
bool addProfile();
- void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives);
void refreshDataTree();
void refreshSaveList();
void setModListSorting(int index);
void setESPListSorting(int index);
- void saveArchiveList();
-
void registerPluginTool(MOBase::IPluginTool *tool);
void registerModPage(MOBase::IPluginModPage *modPage);
@@ -152,9 +150,6 @@ public:
virtual bool closeWindow();
virtual void setWindowEnabled(bool enabled);
- virtual MOBase::DelayedFileWriterBase &archivesWriter() override { return m_ArchiveListWriter; }
-
- void updateWindowTitle(const QString &accountName, bool premium);
public slots:
void displayColumnSelection(const QPoint &pos);
@@ -168,7 +163,6 @@ public slots:
signals:
-
/**
* @brief emitted after the information dialog has been closed
*/
@@ -191,8 +185,16 @@ protected:
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();
@@ -247,7 +249,7 @@ private:
bool extractProgress(QProgressDialog &extractProgress, int percentage, std::string fileName);
- int checkForProblems();
+ size_t checkForProblems();
int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments);
QTreeWidgetItem *addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID, ModListSortProxy::FilterType type);
@@ -284,6 +286,8 @@ private:
void dropLocalFile(const QUrl &url, const QString &outputDir, bool move);
+ bool registerWidgetState(const QString &name, QHeaderView *view, const char *oldSettingName = nullptr);
+
private:
static const char *PATTERN_BACKUP_GLOB;
@@ -298,8 +302,6 @@ private:
MOBase::TutorialControl m_Tutorial;
- QString m_ExeName;
-
int m_OldProfileIndex;
std::vector<QString> m_ModNameList; // the mod-list to go with the directory structure
@@ -346,14 +348,12 @@ private:
std::vector<QTreeWidgetItem*> m_RemoveWidget;
- QByteArray m_ArchiveListHash;
-
bool m_DidUpdateMasterList;
LockedDialog *m_LockDialog { nullptr };
uint64_t m_LockCount { 0 };
- MOBase::DelayedFileWriter m_ArchiveListWriter;
+ std::vector<std::pair<QString, QHeaderView*>> m_PersistedGeometry;
enum class ShortcutType {
Toolbar,
@@ -368,6 +368,8 @@ private:
private slots:
+ void updateWindowTitle(const QString &accountName, bool premium);
+
void showMessage(const QString &message);
void showError(const QString &message);
@@ -380,6 +382,7 @@ private slots:
// modlist context menu
void installMod_clicked();
+ void createEmptyMod_clicked();
void restoreBackup_clicked();
void renameMod_clicked();
void removeMod_clicked();
@@ -437,7 +440,6 @@ private slots:
void addRemoveCategories_MenuHandler();
void replaceCategories_MenuHandler();
- void savePrimaryCategory();
void addPrimaryCategoryCandidates();
void modDetailsUpdated(bool success);
@@ -476,8 +478,6 @@ private slots:
void startExeAction();
- void checkBSAList();
-
void updateProblemsButton();
void saveModMetas();
@@ -531,8 +531,6 @@ private slots: // ui slots
void on_actionUpdate_triggered();
void on_actionEndorseMO_triggered();
- void on_bsaList_customContextMenuRequested(const QPoint &pos);
- void bsaList_itemMoved();
void on_btnRefreshData_clicked();
void on_categoriesList_customContextMenuRequested(const QPoint &pos);
void on_conflictsCheckBox_toggled(bool checked);
@@ -551,7 +549,6 @@ private slots: // ui slots
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();
@@ -561,8 +558,6 @@ private slots: // ui slots
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);
- void on_manageArchivesBox_toggled(bool checked);
};
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
index ca93ea54..e30a165d 100644
--- a/src/mainwindow.ui
+++ b/src/mainwindow.ui
@@ -45,7 +45,10 @@
<property name="title">
<string>Categories</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_10">
+ <layout class="QVBoxLayout" name="verticalLayout_10" stretch="6,0,0">
+ <property name="spacing">
+ <number>0</number>
+ </property>
<property name="leftMargin">
<number>3</number>
</property>
@@ -92,25 +95,34 @@
</widget>
</item>
<item>
- <widget class="QLabel" name="clickBlankLabel">
- <property name="enabled">
- <bool>false</bool>
+ <widget class="QPushButton" name="clickBlankButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>25</height>
+ </size>
</property>
<property name="text">
- <string>Click blank area to deselect</string>
+ <string>Clear</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
</property>
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox">
- <property name="title">
- <string/>
- </property>
- <property name="flat">
- <bool>false</bool>
- </property>
- <property name="checkable">
- <bool>false</bool>
+ <widget class="QWidget" name="widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
@@ -475,6 +487,9 @@ p, li { white-space: pre-wrap; }
</item>
<item>
<widget class="QComboBox" name="groupCombo">
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
<item>
<property name="text">
<string>No groups</string>
@@ -884,112 +899,6 @@ p, li { white-space: pre-wrap; }
</item>
</layout>
</widget>
- <widget class="QWidget" name="bsaTab">
- <attribute name="title">
- <string>Archives</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_9">
- <property name="leftMargin">
- <number>6</number>
- </property>
- <property name="topMargin">
- <number>6</number>
- </property>
- <property name="rightMargin">
- <number>6</number>
- </property>
- <property name="bottomMargin">
- <number>6</number>
- </property>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_9" stretch="0,1">
- <item>
- <widget class="QCheckBox" name="manageArchivesBox">
- <property name="text">
- <string/>
- </property>
- <property name="checked">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="managedArchiveLabel">
- <property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;BSAs are bundles of game assets (textures, scripts, ...). By default, the engine loads these bundles in a separate step from loose files. MO can manage those archives to align their load order with that of loose files:&lt;/p&gt;&lt;p&gt;If archives are &lt;span style=&quot; font-weight:600;&quot;&gt;managed&lt;/span&gt;, their load order is specified by the priority of the corresponding mod (left pane), the same as the loose files. You can manually enable any BSA that has no corresponding plugin active.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If archives are &lt;span style=&quot; font-weight:600;&quot;&gt;not managed&lt;/span&gt; their load order is specified by the priority of the corresponding plugin (right pane, plugins tab). You can then not manually enable BSAs where the plugin isn't active.&lt;/p&gt;&lt;p&gt;In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="text">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Have MO manage archives (&lt;a href=&quot;#&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;read more&lt;/span&gt;&lt;/a&gt;)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="MOBase::SortableTreeWidget" name="bsaList">
- <property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
- </property>
- <property name="toolTip">
- <string>List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order.</string>
- </property>
- <property name="whatsThis">
- <string>BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they &quot;compete&quot; 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!
-
-BSAs checked here are loaded in such a way that your installation order is obeyed properly.</string>
- </property>
- <property name="editTriggers">
- <set>QAbstractItemView::NoEditTriggers</set>
- </property>
- <property name="showDropIndicator" stdset="0">
- <bool>true</bool>
- </property>
- <property name="dragEnabled">
- <bool>false</bool>
- </property>
- <property name="dragDropOverwriteMode">
- <bool>false</bool>
- </property>
- <property name="dragDropMode">
- <enum>QAbstractItemView::DragDrop</enum>
- </property>
- <property name="defaultDropAction">
- <enum>Qt::MoveAction</enum>
- </property>
- <property name="selectionMode">
- <enum>QAbstractItemView::SingleSelection</enum>
- </property>
- <property name="selectionBehavior">
- <enum>QAbstractItemView::SelectRows</enum>
- </property>
- <property name="indentation">
- <number>20</number>
- </property>
- <property name="itemsExpandable">
- <bool>true</bool>
- </property>
- <property name="columnCount">
- <number>1</number>
- </property>
- <attribute name="headerVisible">
- <bool>false</bool>
- </attribute>
- <attribute name="headerDefaultSectionSize">
- <number>200</number>
- </attribute>
- <column>
- <property name="text">
- <string>File</string>
- </property>
- </column>
- </widget>
- </item>
- </layout>
- </widget>
<widget class="QWidget" name="dataTab">
<attribute name="title">
<string>Data</string>
@@ -1288,6 +1197,7 @@ p, li { white-space: pre-wrap; }
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
+ <addaction name="actionChange_Game"/>
<addaction name="actionInstallMod"/>
<addaction name="actionNexus"/>
<addaction name="actionAdd_Profile"/>
@@ -1474,6 +1384,18 @@ Right now this has very limited functionality</string>
<string>Ctrl+C</string>
</property>
</action>
+ <action name="actionChange_Game">
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/app_icon</normaloff>:/MO/gui/app_icon</iconset>
+ </property>
+ <property name="text">
+ <string>Change Game</string>
+ </property>
+ <property name="toolTip">
+ <string>Open the game selection dialog</string>
+ </property>
+ </action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
@@ -1487,11 +1409,6 @@ Right now this has very limited functionality</string>
<extends>QTreeView</extends>
<header>modlistview.h</header>
</customwidget>
- <customwidget>
- <class>MOBase::SortableTreeWidget</class>
- <extends>QTreeWidget</extends>
- <header>sortabletreewidget.h</header>
- </customwidget>
</customwidgets>
<resources>
<include location="resources.qrc"/>
diff --git a/src/mo_icon.ico b/src/mo_icon.ico
index 59da6c2d..ba54d4dc 100644
--- a/src/mo_icon.ico
+++ b/src/mo_icon.ico
Binary files differ
diff --git a/src/mo_icon.psd b/src/mo_icon.psd
deleted file mode 100644
index 1f7b40c9..00000000
--- a/src/mo_icon.psd
+++ /dev/null
Binary files differ
diff --git a/src/moapplication.cpp b/src/moapplication.cpp
index 46b9677b..9ddd9e54 100644
--- a/src/moapplication.cpp
+++ b/src/moapplication.cpp
@@ -129,18 +129,9 @@ bool MOApplication::notify(QObject *receiver, QEvent *event)
void MOApplication::updateStyle(const QString &fileName)
{
-#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
if (fileName == "Fusion") {
setStyle(QStyleFactory::create("fusion"));
setStyleSheet("");
-#else
- if (fileName == "Plastique") {
- setStyle(new ProxyStyle(new QPlastiqueStyle));
- setStyleSheet("");
- } else if (fileName == "Cleanlooks") {
- setStyle(new ProxyStyle(new QCleanlooksStyle));
- setStyleSheet("");
-#endif
} else {
setStyle(new ProxyStyle(QStyleFactory::create(m_DefaultStyle)));
if (QFile::exists(fileName)) {
diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp
index d66c3ac5..61df0a0d 100644
--- a/src/modflagicondelegate.cpp
+++ b/src/modflagicondelegate.cpp
@@ -12,8 +12,7 @@ ModFlagIconDelegate::ModFlagIconDelegate(QObject *parent)
{
}
-QList<QString> ModFlagIconDelegate::getIcons(const QModelIndex &index) const
-{
+QList<QString> ModFlagIconDelegate::getIcons(const QModelIndex &index) const {
QList<QString> result;
QVariant modid = index.data(Qt::UserRole + 1);
if (modid.isValid()) {
@@ -21,7 +20,8 @@ QList<QString> ModFlagIconDelegate::getIcons(const QModelIndex &index) const
std::vector<ModInfo::EFlag> flags = info->getFlags();
{ // insert conflict icon first to provide nicer alignment
- auto iter = std::find_first_of(flags.begin(), flags.end(), m_ConflictFlags, m_ConflictFlags + 4);
+ auto iter = std::find_first_of(flags.begin(), flags.end(),
+ m_ConflictFlags, m_ConflictFlags + 4);
if (iter != flags.end()) {
result.append(getFlagIcon(*iter));
flags.erase(iter);
@@ -58,7 +58,7 @@ size_t ModFlagIconDelegate::getNumIcons(const QModelIndex &index) const
if (modIdx < ModInfo::getNumMods()) {
ModInfo::Ptr info = ModInfo::getByIndex(modIdx);
std::vector<ModInfo::EFlag> flags = info->getFlags();
- int count = flags.size();
+ size_t count = flags.size();
if (std::find_first_of(flags.begin(), flags.end(), m_ConflictFlags, m_ConflictFlags + 4) == flags.end()) {
++count;
}
@@ -71,11 +71,11 @@ size_t ModFlagIconDelegate::getNumIcons(const QModelIndex &index) const
QSize ModFlagIconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const
{
- int count = getNumIcons(modelIndex);
+ size_t count = getNumIcons(modelIndex);
unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt();
QSize result;
if (index < ModInfo::getNumMods()) {
- result = QSize(count * 40, 20);
+ result = QSize(static_cast<int>(count) * 40, 20);
} else {
result = QSize(1, 20);
}
diff --git a/src/modinfo.cpp b/src/modinfo.cpp
index 1081daf8..7cb4b4ba 100644
--- a/src/modinfo.cpp
+++ b/src/modinfo.cpp
@@ -35,6 +35,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <versioninfo.h>
#include <appconfig.h>
#include <scriptextender.h>
+#include <unmanagedmods.h>
#include <QApplication>
#include <QDirIterator>
@@ -75,11 +76,13 @@ ModInfo::Ptr ModInfo::createFrom(const QDir &dir, DirectoryEntry **directoryStru
return result;
}
-ModInfo::Ptr ModInfo::createFromPlugin(const QString &espName, const QStringList &bsaNames
- , DirectoryEntry ** directoryStructure)
-{
+ModInfo::Ptr ModInfo::createFromPlugin(const QString &modName,
+ const QString &espName,
+ const QStringList &bsaNames,
+ DirectoryEntry **directoryStructure) {
QMutexLocker locker(&s_Mutex);
- ModInfo::Ptr result = ModInfo::Ptr(new ModInfoForeign(espName, bsaNames, directoryStructure));
+ ModInfo::Ptr result = ModInfo::Ptr(
+ new ModInfoForeign(modName, espName, bsaNames, directoryStructure));
s_Collection.push_back(result);
return result;
}
@@ -92,12 +95,10 @@ QString ModInfo::getContentTypeName(int contentType)
case CONTENT_MESH: return tr("Meshes");
case CONTENT_BSA: return tr("BSA");
case CONTENT_INTERFACE: return tr("UI Changes");
- case CONTENT_MUSIC: return tr("Music");
case CONTENT_SOUND: return tr("Sound Effects");
case CONTENT_SCRIPT: return tr("Scripts");
case CONTENT_SKSE: return tr("SKSE Plugins");
case CONTENT_SKYPROC: return tr("SkyProc Tools");
- case CONTENT_STRING: return tr("Strings");
default: throw MyException(tr("invalid content type %1").arg(contentType));
}
}
@@ -112,7 +113,7 @@ void ModInfo::createFromOverwrite()
unsigned int ModInfo::getNumMods()
{
QMutexLocker locker(&s_Mutex);
- return s_Collection.size();
+ return static_cast<unsigned int>(s_Collection.size());
}
@@ -121,7 +122,7 @@ ModInfo::Ptr ModInfo::getByIndex(unsigned int index)
QMutexLocker locker(&s_Mutex);
if (index >= s_Collection.size()) {
- throw MyException(tr("invalid index %1").arg(index));
+ throw MyException(tr("invalid mod index %1").arg(index));
}
return s_Collection[index];
}
@@ -150,7 +151,7 @@ bool ModInfo::removeMod(unsigned int index)
QMutexLocker locker(&s_Mutex);
if (index >= s_Collection.size()) {
- throw MyException(tr("invalid index %1").arg(index));
+ throw MyException(tr("remove: invalid mod index %1").arg(index));
}
// update the indices first
ModInfo::Ptr modInfo = s_Collection[index];
@@ -219,27 +220,13 @@ void ModInfo::updateFromDisc(const QString &modDirectory,
}
}
- { // list plugins in the data directory and make a foreign-managed mod out of each
- QStringList dlcPlugins = game->DLCPlugins();
- QStringList mainPlugins = game->primaryPlugins();
- QDir dataDir(game->dataDirectory());
- for (const QString &file : dataDir.entryList({ "*.esp", "*.esm" })) {
- if (std::find_if(mainPlugins.begin(), mainPlugins.end(),
- [&file](QString const &p) {
- return p.compare(file, Qt::CaseInsensitive) == 0; }) == mainPlugins.end()
- && (displayForeign // show non-dlc bundles only if the user wants them
- || std::find_if(dlcPlugins.begin(), dlcPlugins.end(),
- [&file](QString const &p) {
- return p.compare(file, Qt::CaseInsensitive) == 0; }) != dlcPlugins.end())) {
-
- QFileInfo f(file); //Just so I can get a basename...
- QStringList archives;
- for (const QString &archiveName : dataDir.entryList({ f.baseName() + "*.bsa" })) {
- archives.append(dataDir.absoluteFilePath(archiveName));
- }
-
- createFromPlugin(file, archives, directoryStructure);
- }
+ UnmanagedMods *unmanaged = game->feature<UnmanagedMods>();
+ if (unmanaged != nullptr) {
+ for (const QString &modName : unmanaged->mods(!displayForeign)) {
+ createFromPlugin(unmanaged->displayName(modName),
+ unmanaged->referenceFile(modName).absoluteFilePath(),
+ unmanaged->secondaryFiles(modName),
+ directoryStructure);
}
}
@@ -289,7 +276,7 @@ int ModInfo::checkAllForUpdate(QObject *receiver)
std::vector<int> modIDs;
//I ought to store this, it's used elsewhere
- IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+ IPluginGame const *game = qApp->property("managed_game").value<IPluginGame *>();
modIDs.push_back(game->nexusModOrganizerID());
diff --git a/src/modinfo.h b/src/modinfo.h
index c10232da..501588ec 100644
--- a/src/modinfo.h
+++ b/src/modinfo.h
@@ -76,15 +76,13 @@ public:
CONTENT_MESH,
CONTENT_BSA,
CONTENT_INTERFACE,
- CONTENT_MUSIC,
CONTENT_SOUND,
CONTENT_SCRIPT,
CONTENT_SKSE,
- CONTENT_SKYPROC,
- CONTENT_STRING
+ CONTENT_SKYPROC
};
- static const int NUM_CONTENT_TYPES = CONTENT_STRING + 1;
+ static const int NUM_CONTENT_TYPES = CONTENT_SKYPROC + 1;
enum EHighlight {
HIGHLIGHT_NONE = 0,
@@ -188,7 +186,7 @@ public:
* @param bsaNames names of archives
* @return a new mod
*/
- static ModInfo::Ptr createFromPlugin(const QString &espName, const QStringList &bsaNames, MOShared::DirectoryEntry **directoryStructure);
+ static ModInfo::Ptr createFromPlugin(const QString &modName, const QString &espName, const QStringList &bsaNames, MOShared::DirectoryEntry **directoryStructure);
/**
* @brief retieve a name for one of the CONTENT_ enums
diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp
index feaac2d4..e1a2183c 100644
--- a/src/modinfodialog.cpp
+++ b/src/modinfodialog.cpp
@@ -351,14 +351,18 @@ void ModInfoDialog::refreshLists()
void ModInfoDialog::addCategories(const CategoryFactory &factory, const std::set<int> &enabledCategories, QTreeWidgetItem *root, int rootLevel)
{
- for (size_t i = 0; i < factory.numCategories(); ++i) {
+ for (int i = 0; i < static_cast<int>(factory.numCategories()); ++i) {
if (factory.getParentID(i) != rootLevel) {
continue;
}
int categoryID = factory.getCategoryID(i);
- QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList(factory.getCategoryName(i)));
+ QTreeWidgetItem *newItem
+ = new QTreeWidgetItem(QStringList(factory.getCategoryName(i)));
newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable);
- newItem->setCheckState(0, enabledCategories.find(categoryID) != enabledCategories.end() ? Qt::Checked : Qt::Unchecked);
+ newItem->setCheckState(0, enabledCategories.find(categoryID)
+ != enabledCategories.end()
+ ? Qt::Checked
+ : Qt::Unchecked);
newItem->setData(0, Qt::UserRole, categoryID);
if (factory.hasChildren(i)) {
addCategories(factory, enabledCategories, newItem, categoryID);
diff --git a/src/modinfoforeign.cpp b/src/modinfoforeign.cpp
index 4dbe034b..4ba16bb1 100644
--- a/src/modinfoforeign.cpp
+++ b/src/modinfoforeign.cpp
@@ -21,7 +21,7 @@ QDateTime ModInfoForeign::creationTime() const
QString ModInfoForeign::absolutePath() const
{
//I ought to store this, it's used elsewhere
- IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+ IPluginGame const *game = qApp->property("managed_game").value<IPluginGame *>();
return game->dataDirectory().absolutePath();
}
@@ -43,12 +43,12 @@ QString ModInfoForeign::getDescription() const
return tr("This pseudo mod represents content managed outside MO. It isn't modified by MO.");
}
-ModInfoForeign::ModInfoForeign(const QString &referenceFile, const QStringList &archives,
+ModInfoForeign::ModInfoForeign(const QString &modName,
+ const QString &referenceFile,
+ const QStringList &archives,
DirectoryEntry **directoryStructure)
- : ModInfoWithConflictInfo(directoryStructure)
- , m_ReferenceFile(referenceFile)
- , m_Archives(archives)
-{
+ : ModInfoWithConflictInfo(directoryStructure),
+ m_ReferenceFile(referenceFile), m_Archives(archives) {
m_CreationTime = QFileInfo(referenceFile).created();
- m_Name = "Unmanaged: " + QFileInfo(m_ReferenceFile).baseName();
+ m_Name = "Unmanaged: " + modName;
}
diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h
index b35c099b..839bcdce 100644
--- a/src/modinfoforeign.h
+++ b/src/modinfoforeign.h
@@ -51,9 +51,9 @@ public:
virtual void addInstalledFile(int, int) {}
protected:
-
- ModInfoForeign(const QString &referenceFile, const QStringList &archives, MOShared::DirectoryEntry **directoryStructure);
-
+ ModInfoForeign(const QString &modName, const QString &referenceFile,
+ const QStringList &archives,
+ MOShared::DirectoryEntry **directoryStructure);
private:
QString m_Name;
diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp
index 4380a255..0104998a 100644
--- a/src/modinfooverwrite.cpp
+++ b/src/modinfooverwrite.cpp
@@ -1,6 +1,7 @@
#include "modinfooverwrite.h"
#include "appconfig.h"
+#include "settings.h"
#include <QApplication>
#include <QDirIterator>
@@ -21,7 +22,7 @@ bool ModInfoOverwrite::isEmpty() const
QString ModInfoOverwrite::absolutePath() const
{
- return QDir::fromNativeSeparators(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::overwritePath()));
+ return Settings::instance().getOverwriteDirectory();
}
std::vector<ModInfo::EFlag> ModInfoOverwrite::getFlags() const
diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp
index b1f73a37..231e5497 100644
--- a/src/modinforegular.cpp
+++ b/src/modinforegular.cpp
@@ -433,25 +433,31 @@ std::vector<ModInfo::EContent> ModInfoRegular::getContents() const
if (dir.entryList(QStringList() << "*.esp" << "*.esm").size() > 0) {
m_CachedContent.push_back(CONTENT_PLUGIN);
}
- if (dir.entryList(QStringList() << "*.bsa").size() > 0) {
+ if (dir.entryList(QStringList() << "*.bsa" << "*.ba2").size() > 0) {
m_CachedContent.push_back(CONTENT_BSA);
}
- ScriptExtender *extender = qApp->property("managed_game").value<IPluginGame*>()->feature<ScriptExtender>();
+ ScriptExtender *extender = qApp->property("managed_game")
+ .value<IPluginGame *>()
+ ->feature<ScriptExtender>();
if (extender != nullptr) {
QString sePluginPath = extender->name() + "/plugins";
- if (dir.exists(sePluginPath)) m_CachedContent.push_back(CONTENT_SKSE);
+ if (dir.exists(sePluginPath))
+ m_CachedContent.push_back(CONTENT_SKSE);
}
- if (dir.exists("textures")) m_CachedContent.push_back(CONTENT_TEXTURE);
- if (dir.exists("meshes")) m_CachedContent.push_back(CONTENT_MESH);
- if (dir.exists("interface")
- || dir.exists("menus")) m_CachedContent.push_back(CONTENT_INTERFACE);
- if (dir.exists("music")) m_CachedContent.push_back(CONTENT_MUSIC);
- if (dir.exists("sound")) m_CachedContent.push_back(CONTENT_SOUND);
- if (dir.exists("scripts")) m_CachedContent.push_back(CONTENT_SCRIPT);
- if (dir.exists("strings")) m_CachedContent.push_back(CONTENT_STRING);
- if (dir.exists("SkyProc Patchers")) m_CachedContent.push_back(CONTENT_SKYPROC);
+ if (dir.exists("textures"))
+ m_CachedContent.push_back(CONTENT_TEXTURE);
+ if (dir.exists("meshes"))
+ m_CachedContent.push_back(CONTENT_MESH);
+ if (dir.exists("interface") || dir.exists("menus"))
+ m_CachedContent.push_back(CONTENT_INTERFACE);
+ if (dir.exists("music") || dir.exists("sound"))
+ m_CachedContent.push_back(CONTENT_SOUND);
+ if (dir.exists("scripts"))
+ m_CachedContent.push_back(CONTENT_SCRIPT);
+ if (dir.exists("SkyProc Patchers"))
+ m_CachedContent.push_back(CONTENT_SKYPROC);
m_LastContentCheck = QTime::currentTime();
}
diff --git a/src/modlist.cpp b/src/modlist.cpp
index 9d7f32c8..fca98aac 100644
--- a/src/modlist.cpp
+++ b/src/modlist.cpp
@@ -64,12 +64,10 @@ ModList::ModList(QObject *parent)
m_ContentIcons[ModInfo::CONTENT_INTERFACE] = std::make_tuple(":/MO/gui/content/interface", tr("Interface"));
m_ContentIcons[ModInfo::CONTENT_MESH] = std::make_tuple(":/MO/gui/content/mesh", tr("Meshes"));
m_ContentIcons[ModInfo::CONTENT_BSA] = std::make_tuple(":/MO/gui/content/bsa", tr("BSA"));
- m_ContentIcons[ModInfo::CONTENT_MUSIC] = std::make_tuple(":/MO/gui/content/music", tr("Music"));
m_ContentIcons[ModInfo::CONTENT_SCRIPT] = std::make_tuple(":/MO/gui/content/script", tr("Scripts (Papyrus)"));
m_ContentIcons[ModInfo::CONTENT_SKSE] = std::make_tuple(":/MO/gui/content/skse", tr("Script Extender Plugin"));
m_ContentIcons[ModInfo::CONTENT_SKYPROC] = std::make_tuple(":/MO/gui/content/skyproc", tr("SkyProc Patcher"));
- m_ContentIcons[ModInfo::CONTENT_SOUND] = std::make_tuple(":/MO/gui/content/sound", tr("Sound"));
- m_ContentIcons[ModInfo::CONTENT_STRING] = std::make_tuple(":/MO/gui/content/string", tr("Strings"));
+ m_ContentIcons[ModInfo::CONTENT_SOUND] = std::make_tuple(":/MO/gui/content/sound", tr("Sound or Music"));
m_ContentIcons[ModInfo::CONTENT_TEXTURE] = std::make_tuple(":/MO/gui/content/texture", tr("Textures"));
m_LastCheck.start();
diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp
index d0eb58a6..6e989b0d 100644
--- a/src/nexusinterface.cpp
+++ b/src/nexusinterface.cpp
@@ -27,6 +27,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <util.h>
#include <QApplication>
+#include <QNetworkCookieJar>
#include <regex>
@@ -86,7 +87,7 @@ void NexusBridge::nxmFilesAvailable(int modID, QVariant userData, QVariant resul
QVariantList resultList = resultData.toList();
- foreach(QVariant file, resultList) {
+ for (const QVariant &file : resultList) {
ModRepositoryFileInfo temp;
QVariantMap fileInfo = file.toMap();
temp.uri = fileInfo["uri"].toString();
@@ -193,7 +194,7 @@ void NexusInterface::loginCompleted()
void NexusInterface::interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query)
{
//Look for something along the lines of modulename-Vn-m + any old rubbish.
- static std::regex exp("^([a-zA-Z0-9_'\"\\- ]*?)([-_ ][VvRr]?[0-9_]+)?-([1-9][0-9]+).*");
+ static std::regex exp(R"exp(^([a-zA-Z0-9_'"\-.() ]*?)([-_ ][VvRr]?[0-9_]+)?-([1-9][0-9]*).*\.(zip|rar|7z))exp");
static std::regex simpleexp("^([a-zA-Z0-9_]+)");
QByteArray fileNameUTF8 = fileName.toUtf8();
@@ -206,11 +207,12 @@ void NexusInterface::interpretNexusFileName(const QString &fileName, QString &mo
std::string candidate2 = result[2].str();
if (candidate2.length() != 0 && (candidate2.find_last_of("VvRr") == std::string::npos)) {
// well, that second match might be an id too...
- unsigned offset = strspn(candidate2.c_str(), "-_ ");
+ size_t offset = strspn(candidate2.c_str(), "-_ ");
if (offset < candidate2.length() && query) {
SelectionDialog selection(tr("Failed to guess mod id for \"%1\", please pick the correct one").arg(fileName));
QString r2Highlight(fileName);
- r2Highlight.insert(result.position(2) + result.length(2), "* ").insert(result.position(2) + offset, " *");
+ r2Highlight.insert(result.position(2) + result.length(2), "* ")
+ .insert(result.position(2) + static_cast<int>(offset), " *");
QString r3Highlight(fileName);
r3Highlight.insert(result.position(3) + result.length(3), "* ").insert(result.position(3), " *");
@@ -264,14 +266,14 @@ QString NexusInterface::getModURL(int modID) const
return QString("%1/mods/%2").arg(getGameURL()).arg(modID);
}
-bool NexusInterface::isModURL(int modID, QString const &url) const
+bool NexusInterface::isModURL(int modID, const QString &url) const
{
- if (url == getModURL(modID)) {
+ if (QUrl(url) == QUrl(getModURL(modID))) {
return true;
}
//Try the alternate (old style) mod name
QString alt = QString("%1/%2").arg(getOldModsURL()).arg(modID);
- return alt == url;
+ return QUrl(alt) == QUrl(url);
}
int NexusInterface::requestDescription(int modID, QObject *receiver, QVariant userData,
@@ -338,10 +340,6 @@ int NexusInterface::requestFiles(int modID, QObject *receiver, QVariant userData
connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)),
receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection);
-// QTimer::singleShot(1000, this, SLOT(fakeFiles()));
-// static int fID = 42;
-// return fID++;
-
nextRequest();
return requestInfo.m_ID;
}
@@ -412,6 +410,12 @@ void NexusInterface::cleanup()
m_DiskCache = nullptr;
}
+void NexusInterface::clearCache()
+{
+ m_DiskCache->clear();
+ m_AccessManager->clearCookies();
+}
+
void NexusInterface::nextRequest()
{
if ((m_ActiveRequest.size() >= MAX_ACTIVE_DOWNLOADS)
@@ -564,8 +568,10 @@ void NexusInterface::requestError(QNetworkReply::NetworkError)
return;
}
- qCritical("request (%s) error: %s",
- qPrintable(reply->url().toString()), qPrintable(reply->errorString()));
+ qCritical("request (%s) error: %s (%d)",
+ qPrintable(reply->url().toString()),
+ qPrintable(reply->errorString()),
+ reply->error());
}
diff --git a/src/nexusinterface.h b/src/nexusinterface.h
index c9a81134..9e7f3642 100644
--- a/src/nexusinterface.h
+++ b/src/nexusinterface.h
@@ -143,6 +143,11 @@ public:
void cleanup();
/**
+ * @brief clear webcache and cookies associated with this access manager
+ */
+ void clearCache();
+
+ /**
* @brief request description for a mod
*
* @param modID id of the mod caller is interested in (assumed to be for the current game)
diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp
index 616b3806..17c50e35 100644
--- a/src/nxmaccessmanager.cpp
+++ b/src/nxmaccessmanager.cpp
@@ -59,6 +59,11 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion)
m_LoginTimeout.setInterval(30000);
setCookieJar(new PersistentCookieJar(
QDir::fromNativeSeparators(Settings::instance().getCacheDirectory() + "/nexus_cookies.dat")));
+
+ if (networkAccessible() == QNetworkAccessManager::UnknownAccessibility) {
+ // why is this necessary all of a sudden?
+ setNetworkAccessible(QNetworkAccessManager::Accessible);
+ }
}
NXMAccessManager::~NXMAccessManager()
@@ -105,6 +110,15 @@ void NXMAccessManager::showCookies() const
}
}
+void NXMAccessManager::clearCookies()
+{
+ PersistentCookieJar *jar = qobject_cast<PersistentCookieJar*>(cookieJar());
+ if (jar != nullptr) {
+ jar->clear();
+ } else {
+ qWarning("failed to clear cookies, invalid cookie jar");
+ }
+}
void NXMAccessManager::startLoginCheck()
{
@@ -201,7 +215,6 @@ void NXMAccessManager::login(const QString &username, const QString &password)
emit loginSuccessful(false);
return;
}
-
m_Username = username;
m_Password = password;
pageLogin();
@@ -223,7 +236,6 @@ QString NXMAccessManager::userAgent(const QString &subModule) const
void NXMAccessManager::pageLogin()
{
qDebug("logging %s in on Nexus", qPrintable(m_Username));
-
QString requestString = (Nexus_Management_URL + "/Sessions/?Login&uri=%1")
.arg(QString(QUrl::toPercentEncoding(Nexus_Management_URL)));
@@ -233,8 +245,8 @@ void NXMAccessManager::pageLogin()
QByteArray postDataQuery;
QUrlQuery postData;
postData.addQueryItem("username", m_Username);
- postData.addQueryItem("password", m_Password);
- postDataQuery = postData.query(QUrl::FullyEncoded).toUtf8();
+ postData.addQueryItem("password", QUrl::toPercentEncoding(m_Password));
+ postDataQuery = postData.query(QUrl::EncodeReserved).toUtf8();
request.setRawHeader("User-Agent", userAgent().toUtf8());
diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h
index 82bd2bd5..c58c4cc3 100644
--- a/src/nxmaccessmanager.h
+++ b/src/nxmaccessmanager.h
@@ -52,6 +52,8 @@ public:
void showCookies() const;
+ void clearCookies();
+
QString userAgent(const QString &subModule = QString()) const;
void startLoginCheck();
diff --git a/src/organizer_en.ts b/src/organizer_en.ts
new file mode 100644
index 00000000..da3a3c6e
--- /dev/null
+++ b/src/organizer_en.ts
@@ -0,0 +1,6051 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="en_US">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="aboutdialog.ui" line="14"/>
+ <location filename="aboutdialog.ui" line="90"/>
+ <source>About</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="aboutdialog.ui" line="103"/>
+ <source>Revision:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="aboutdialog.ui" line="141"/>
+ <source>Used Software</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="aboutdialog.ui" line="154"/>
+ <source>Thanks</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="aboutdialog.ui" line="160"/>
+ <source>Translators</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="aboutdialog.ui" line="228"/>
+ <source>... more (Can&apos;t track)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="aboutdialog.ui" line="242"/>
+ <source>Other Supporter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="aboutdialog.ui" line="307"/>
+ <source>ogrotten</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="aboutdialog.ui" line="339"/>
+ <source>Close</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="aboutdialog.cpp" line="92"/>
+ <source>No license</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ActivateModsDialog</name>
+ <message>
+ <location filename="activatemodsdialog.ui" line="14"/>
+ <source>Activate Mods</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="activatemodsdialog.ui" line="20"/>
+ <source>This is a list of esps and esms that were active when the save game was created.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="activatemodsdialog.ui" line="23"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;This is a list of esps and esms that were active when the save game was created.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="activatemodsdialog.ui" line="37"/>
+ <source>Missing ESP</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="activatemodsdialog.ui" line="42"/>
+ <source>Mod</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="activatemodsdialog.cpp" line="49"/>
+ <source>not found</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>BrowserDialog</name>
+ <message>
+ <location filename="browserdialog.ui" line="14"/>
+ <source>Some Page</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="browserdialog.ui" line="281"/>
+ <source>Search</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="browserdialog.cpp" line="94"/>
+ <source>new</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="browserdialog.cpp" line="207"/>
+ <source>failed to start download</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>CategoriesDialog</name>
+ <message>
+ <location filename="categoriesdialog.ui" line="14"/>
+ <source>Categories</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="categoriesdialog.ui" line="66"/>
+ <source>ID</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="categoriesdialog.ui" line="69"/>
+ <source>Internal ID for the category.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="categoriesdialog.ui" line="72"/>
+ <source>Internal ID for the category. The categories a mod belongs to are stored by this ID. It is recommended you use new IDs for categories you add instead of re-using existing ones.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="categoriesdialog.ui" line="77"/>
+ <source>Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="categoriesdialog.ui" line="80"/>
+ <location filename="categoriesdialog.ui" line="83"/>
+ <source>Name of the Categorie used for display.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="categoriesdialog.ui" line="88"/>
+ <source>Nexus IDs</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="categoriesdialog.ui" line="91"/>
+ <source>Comma-Separated list of Nexus IDs to be matched to the internal ID.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="categoriesdialog.ui" line="94"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-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;&quot;&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="categoriesdialog.ui" line="105"/>
+ <source>Parent ID</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="categoriesdialog.ui" line="108"/>
+ <source>If set, the category is defined as a sub-category of another one. Parent ID needs to be a valid category ID.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="categoriesdialog.cpp" line="239"/>
+ <source>Add</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="categoriesdialog.cpp" line="240"/>
+ <source>Remove</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>CredentialsDialog</name>
+ <message>
+ <location filename="credentialsdialog.ui" line="14"/>
+ <source>Login</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="credentialsdialog.ui" line="20"/>
+ <source>This feature may not work unless you&apos;re logged in with Nexus</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="credentialsdialog.ui" line="32"/>
+ <source>Username</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="credentialsdialog.ui" line="42"/>
+ <source>Password</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="credentialsdialog.ui" line="60"/>
+ <source>Remember</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="credentialsdialog.ui" line="71"/>
+ <source>Never ask again</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>DirectoryRefresher</name>
+ <message>
+ <location filename="directoryrefresher.cpp" line="94"/>
+ <source>failed to parse bsa %1: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="directoryrefresher.cpp" line="160"/>
+ <source>failed to read mod (%1): %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>DownloadList</name>
+ <message>
+ <location filename="downloadlist.cpp" line="64"/>
+ <source>Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlist.cpp" line="65"/>
+ <source>Filetime</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlist.cpp" line="66"/>
+ <source>Done</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlist.cpp" line="82"/>
+ <source>Information missing, please select &quot;Query Info&quot; from the context menu to re-retrieve.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlist.cpp" line="89"/>
+ <source>pending download</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>DownloadListWidget</name>
+ <message>
+ <location filename="downloadlistwidget.ui" line="17"/>
+ <location filename="downloadlistwidget.ui" line="61"/>
+ <source>Placeholder</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.ui" line="99"/>
+ <location filename="downloadlistwidget.cpp" line="149"/>
+ <location filename="downloadlistwidget.cpp" line="151"/>
+ <source>Done - Double Click to install</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="115"/>
+ <location filename="downloadlistwidget.cpp" line="117"/>
+ <source>Paused - Double Click to resume</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="135"/>
+ <location filename="downloadlistwidget.cpp" line="137"/>
+ <source>Installed - Double Click to re-install</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="142"/>
+ <location filename="downloadlistwidget.cpp" line="144"/>
+ <source>Uninstalled - Double Click to re-install</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>DownloadListWidgetCompact</name>
+ <message>
+ <location filename="downloadlistwidgetcompact.ui" line="17"/>
+ <location filename="downloadlistwidgetcompact.ui" line="56"/>
+ <source>Placeholder</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.ui" line="122"/>
+ <source>Done</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>DownloadListWidgetCompactDelegate</name>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="92"/>
+ <source>&lt; mod %1 file %2 &gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="97"/>
+ <source>Pending</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="120"/>
+ <source>Paused</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="122"/>
+ <source>Fetching Info 1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="124"/>
+ <source>Fetching Info 2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="129"/>
+ <source>Installed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="131"/>
+ <source>Uninstalled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="133"/>
+ <source>Done</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="241"/>
+ <location filename="downloadlistwidgetcompact.cpp" line="250"/>
+ <location filename="downloadlistwidgetcompact.cpp" line="259"/>
+ <location filename="downloadlistwidgetcompact.cpp" line="268"/>
+ <source>Are you sure?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="242"/>
+ <source>This will remove all finished downloads from this list and from disk.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="251"/>
+ <source>This will remove all installed downloads from this list and from disk.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="260"/>
+ <source>This will permanently remove all finished downloads from this list (but NOT from disk).</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="269"/>
+ <source>This will permanently remove all installed downloads from this list (but NOT from disk).</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="298"/>
+ <source>Install</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="300"/>
+ <source>Query Info</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="302"/>
+ <source>Delete</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="304"/>
+ <source>Un-Hide</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="306"/>
+ <source>Remove from View</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="309"/>
+ <source>Cancel</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="310"/>
+ <source>Pause</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="312"/>
+ <source>Remove</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="313"/>
+ <source>Resume</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="318"/>
+ <source>Delete Installed...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="319"/>
+ <source>Delete All...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="322"/>
+ <source>Remove Installed...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidgetcompact.cpp" line="323"/>
+ <source>Remove All...</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>DownloadListWidgetDelegate</name>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="92"/>
+ <source>&lt; mod %1 file %2 &gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="95"/>
+ <source>Pending</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="122"/>
+ <source>Fetching Info 1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="125"/>
+ <source>Fetching Info 2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="257"/>
+ <location filename="downloadlistwidget.cpp" line="266"/>
+ <location filename="downloadlistwidget.cpp" line="275"/>
+ <location filename="downloadlistwidget.cpp" line="284"/>
+ <source>Are you sure?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="258"/>
+ <source>This will remove all finished downloads from this list and from disk.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="267"/>
+ <source>This will remove all installed downloads from this list and from disk.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="276"/>
+ <source>This will remove all finished downloads from this list (but NOT from disk).</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="285"/>
+ <source>This will remove all installed downloads from this list (but NOT from disk).</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="313"/>
+ <source>Install</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="315"/>
+ <source>Query Info</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="317"/>
+ <source>Delete</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="319"/>
+ <source>Un-Hide</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="321"/>
+ <source>Remove from View</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="324"/>
+ <source>Cancel</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="325"/>
+ <source>Pause</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="327"/>
+ <source>Remove</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="328"/>
+ <source>Resume</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="333"/>
+ <source>Delete Installed...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="334"/>
+ <source>Delete All...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="337"/>
+ <source>Remove Installed...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadlistwidget.cpp" line="338"/>
+ <source>Remove All...</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>DownloadManager</name>
+ <message>
+ <location filename="downloadmanager.cpp" line="148"/>
+ <source>failed to rename &quot;%1&quot; to &quot;%2&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="327"/>
+ <source>Memory allocation error (in refreshing directory).</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="375"/>
+ <source>Download again?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="375"/>
+ <source>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.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="421"/>
+ <source>failed to download %1: could not open output file: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="458"/>
+ <source>Wrong Game</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="458"/>
+ <source>The download link is for a mod for &quot;%1&quot; but this instance of MO has been set up for &quot;%2&quot;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="475"/>
+ <location filename="downloadmanager.cpp" line="575"/>
+ <source>remove: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="493"/>
+ <source>failed to delete %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="499"/>
+ <source>failed to delete meta file for %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="542"/>
+ <source>restore: invalid download index: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="593"/>
+ <source>cancel: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="606"/>
+ <source>pause: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="626"/>
+ <source>resume: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="637"/>
+ <source>resume (int): invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="644"/>
+ <source>No known download urls. Sorry, this download can&apos;t be resumed.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="677"/>
+ <source>query: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="699"/>
+ <source>Please enter the nexus mod id</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="699"/>
+ <source>Mod ID:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="725"/>
+ <source>get pending: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="734"/>
+ <source>get path: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="743"/>
+ <source>Main</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="744"/>
+ <source>Update</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="745"/>
+ <source>Optional</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="746"/>
+ <source>Old</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="747"/>
+ <source>Misc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="748"/>
+ <source>Unknown</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="755"/>
+ <source>display name: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="775"/>
+ <source>file name: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="784"/>
+ <source>file time: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="798"/>
+ <source>file size: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="808"/>
+ <source>progress: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="818"/>
+ <source>state: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="828"/>
+ <source>infocomplete: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="843"/>
+ <source>mod id: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="851"/>
+ <source>ishidden: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="860"/>
+ <source>file info: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="870"/>
+ <source>mark installed: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="885"/>
+ <source>mark uninstalled: invalid download index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="1001"/>
+ <source>Memory allocation error (in processing progress event).</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="1014"/>
+ <source>Memory allocation error (in processing downloaded data).</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="1149"/>
+ <source>Information updated</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="1151"/>
+ <location filename="downloadmanager.cpp" line="1165"/>
+ <source>No matching file found on Nexus! Maybe this file is no longer available or it was renamed?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="1153"/>
+ <source>No file on Nexus matches the selected file by name. Please manually choose the correct one.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="1281"/>
+ <source>No download server available. Please try again later.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="1324"/>
+ <source>Failed to request file info from nexus: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="1352"/>
+ <source>Download failed. Server reported: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="1354"/>
+ <source>Download failed: %1 (%2)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="downloadmanager.cpp" line="1458"/>
+ <source>failed to re-open %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>EditExecutablesDialog</name>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="14"/>
+ <source>Modify Executables</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="20"/>
+ <source>List of configured executables</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="23"/>
+ <source>This is a list of your configured executables. Executables in grey are automatically recognised and can not be modified.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="41"/>
+ <source>Title</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="48"/>
+ <location filename="editexecutablesdialog.ui" line="51"/>
+ <source>Name of the executable. This is only for display purposes.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="62"/>
+ <source>Binary</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="69"/>
+ <location filename="editexecutablesdialog.ui" line="72"/>
+ <source>Binary to run</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="79"/>
+ <source>Browse filesystem</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="82"/>
+ <source>Browse filesystem for the executable to run.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="85"/>
+ <location filename="editexecutablesdialog.ui" line="106"/>
+ <source>...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="96"/>
+ <source>Start in</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="117"/>
+ <source>Arguments</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="124"/>
+ <location filename="editexecutablesdialog.ui" line="127"/>
+ <source>Arguments to pass to the application</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="138"/>
+ <source>Allow the Steam AppID to be used for this executable to be changed.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="141"/>
+ <source>Allow the Steam AppID to be used for this executable to be changed.
+Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game.
+Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID. This overwrite is already preconfigured.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="146"/>
+ <source>Overwrite Steam AppID</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="156"/>
+ <source>Steam AppID to use for this executable that differs from the games AppID.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="159"/>
+ <source>Steam AppID to use for this executable that differs from the games AppID.
+Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game (usually 72850).
+Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID (usually 202480). This overwrite is already preconfigured.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="172"/>
+ <source>If this is enabled, new files are created in the specified mod instead of the &quot;Overwrite&quot; mod.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="175"/>
+ <source>Create Files in Mod instead of Overwrite (*)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="191"/>
+ <source>Use Application&apos;s Icon for shortcuts</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="198"/>
+ <source>(*) This setting is profile-specific</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="207"/>
+ <location filename="editexecutablesdialog.ui" line="210"/>
+ <source>Add an executable</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="213"/>
+ <location filename="editexecutablesdialog.cpp" line="214"/>
+ <source>Add</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="224"/>
+ <location filename="editexecutablesdialog.ui" line="227"/>
+ <source>Remove the selected executable</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="230"/>
+ <source>Remove</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.ui" line="258"/>
+ <source>Close</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.cpp" line="143"/>
+ <source>Select a binary</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.cpp" line="144"/>
+ <source>Executable (%1)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.cpp" line="169"/>
+ <source>Java (32-bit) required</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.cpp" line="170"/>
+ <source>MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.cpp" line="186"/>
+ <source>Select a directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.cpp" line="193"/>
+ <source>Confirm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.cpp" line="193"/>
+ <source>Really remove &quot;%1&quot; from executables?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.cpp" line="218"/>
+ <source>Modify</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.cpp" line="263"/>
+ <location filename="editexecutablesdialog.cpp" line="283"/>
+ <source>Save Changes?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="editexecutablesdialog.cpp" line="264"/>
+ <location filename="editexecutablesdialog.cpp" line="284"/>
+ <source>You made changes to the current executable, do you want to save them?</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>FindDialog</name>
+ <message>
+ <location filename="finddialog.ui" line="14"/>
+ <source>Find</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="finddialog.ui" line="24"/>
+ <source>Find what:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="finddialog.ui" line="31"/>
+ <location filename="finddialog.ui" line="34"/>
+ <source>Search term</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="finddialog.ui" line="47"/>
+ <location filename="finddialog.ui" line="50"/>
+ <source>Find next occurence from current file position.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="finddialog.ui" line="53"/>
+ <source>&amp;Find Next</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="finddialog.ui" line="60"/>
+ <location filename="finddialog.ui" line="63"/>
+ <location filename="finddialog.ui" line="66"/>
+ <source>Close</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="installdialog.ui" line="20"/>
+ <source>Install Mods</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installdialog.ui" line="32"/>
+ <source>New Mod</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installdialog.ui" line="46"/>
+ <source>Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installdialog.ui" line="53"/>
+ <source>Pick a name for the mod</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installdialog.ui" line="56"/>
+ <source>Pick a name for the mod. This is also used as a directory name, so please don&apos;t use characters that are illegal in file names.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installdialog.ui" line="65"/>
+ <source>Content</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installdialog.ui" line="75"/>
+ <source>Content of the archive. You can change the directory structure by using drag&amp;drop. Hint: Also try right clicking...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installdialog.ui" line="78"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;This displays the content of the archive. &amp;lt;data&amp;gt; represents the base directory which will map to the game&apos;s data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;amp;drop&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installdialog.ui" line="121"/>
+ <source>Placeholder</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installdialog.ui" line="141"/>
+ <source>OK</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installdialog.ui" line="148"/>
+ <source>Cancel</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>InstallationManager</name>
+ <message>
+ <location filename="installationmanager.cpp" line="118"/>
+ <source>Password required</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="119"/>
+ <source>Password</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="188"/>
+ <location filename="installationmanager.cpp" line="275"/>
+ <source>Extracting files</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="464"/>
+ <source>failed to create backup</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="473"/>
+ <source>Mod Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="473"/>
+ <source>Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="526"/>
+ <source>Invalid name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="527"/>
+ <source>The name you entered is invalid, please enter a different one.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="652"/>
+ <source>File format &quot;%1&quot; not supported</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="813"/>
+ <source>None of the available installer plugins were able to handle that archive</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="823"/>
+ <source>no error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="826"/>
+ <source>7z.dll not found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="829"/>
+ <source>7z.dll isn&apos;t valid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="832"/>
+ <source>archive not found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="835"/>
+ <source>failed to open archive</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="838"/>
+ <source>unsupported archive type</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="841"/>
+ <source>internal library error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="844"/>
+ <source>archive invalid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="848"/>
+ <source>unknown archive error</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>LockedDialog</name>
+ <message>
+ <location filename="lockeddialog.ui" line="14"/>
+ <source>Locked</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="lockeddialog.ui" line="20"/>
+ <source>This dialog should disappear automatically if the application/game is done. Click unlock if it didn&apos;t.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="lockeddialog.ui" line="23"/>
+ <source>MO is locked while the executable is running.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="lockeddialog.ui" line="54"/>
+ <source>Unlock</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>LogBuffer</name>
+ <message>
+ <location filename="logbuffer.cpp" line="83"/>
+ <source>failed to write log to %1: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>MOApplication</name>
+ <message>
+ <location filename="moapplication.cpp" line="119"/>
+ <source>an error occured: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="moapplication.cpp" line="124"/>
+ <source>an error occured</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="mainwindow.ui" line="46"/>
+ <location filename="mainwindow.ui" line="500"/>
+ <source>Categories</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="112"/>
+ <source>Clear</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="131"/>
+ <source>If checked, only mods that match all selected categories are displayed.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="134"/>
+ <source>And</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="144"/>
+ <source>If checked, all mods that match at least one of the selected categories are displayed.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="147"/>
+ <source>Or</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="186"/>
+ <source>Profile</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="196"/>
+ <source>Pick a module collection</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="199"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Please note that right now your esp load order is not kept seperate for different profiles.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="230"/>
+ <source>Open list options...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="233"/>
+ <source>Refresh list. This is usually not necessary unless you modified data outside the program.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="253"/>
+ <location filename="mainwindow.ui" line="757"/>
+ <source>Restore Backup...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="267"/>
+ <location filename="mainwindow.ui" line="777"/>
+ <source>Create Backup</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="383"/>
+ <source>List of available mods.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="386"/>
+ <source>This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag &amp; drop mods to change their &quot;installation&quot; orders.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="471"/>
+ <source>Filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="495"/>
+ <source>No groups</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="505"/>
+ <source>Nexus IDs</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="513"/>
+ <location filename="mainwindow.ui" line="894"/>
+ <location filename="mainwindow.ui" line="1135"/>
+ <source>Namefilter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="548"/>
+ <source>Pick a program to run.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="551"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;You can add new Tools to this list, but I can&apos;t promise tools I haven&apos;t tested will work.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="599"/>
+ <source>Run program</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="602"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Run the selected program with ModOrganizer enabled.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="612"/>
+ <source>Run</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="653"/>
+ <source>Create a shortcut in your start menu or on the desktop to the specified program</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="656"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;This creates a start menu shortcut that directly starts the selected program with the MO active.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="663"/>
+ <source>Shortcut</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="713"/>
+ <source>Plugins</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="733"/>
+ <source>Sort</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="839"/>
+ <source>List of available esp/esm files</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="842"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.&lt;br /&gt;There is a great tool named &amp;quot;BOSS&amp;quot; to automatically sort these files.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="904"/>
+ <source>Data</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="922"/>
+ <source>refresh data-directory overview</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="925"/>
+ <source>Refresh the overview. This may take a moment.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="928"/>
+ <location filename="mainwindow.cpp" line="2896"/>
+ <location filename="mainwindow.cpp" line="3651"/>
+ <source>Refresh</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="944"/>
+ <source>This is an overview of your data directory as visible to the game (and tools). </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="954"/>
+ <source>File</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="959"/>
+ <source>Mod</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="969"/>
+ <location filename="mainwindow.ui" line="972"/>
+ <source>Filter the above list so that only conflicts are displayed.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="975"/>
+ <source>Show only conflicts</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="983"/>
+ <source>Saves</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1007"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;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&apos;t active now.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-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;&quot;&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;If you click &amp;quot;Fix Mods...&amp;quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1027"/>
+ <source>Downloads</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1062"/>
+ <source>This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1115"/>
+ <source>Show Hidden</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1177"/>
+ <source>Tool Bar</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1220"/>
+ <source>Install Mod</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1223"/>
+ <source>Install &amp;Mod</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1226"/>
+ <source>Install a new mod from an archive</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1229"/>
+ <source>Ctrl+M</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1238"/>
+ <source>Profiles</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1241"/>
+ <source>&amp;Profiles</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1244"/>
+ <source>Configure Profiles</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1247"/>
+ <source>Ctrl+P</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1256"/>
+ <source>Executables</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1259"/>
+ <source>&amp;Executables</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1262"/>
+ <source>Configure the executables that can be started through Mod Organizer</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1265"/>
+ <source>Ctrl+E</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1274"/>
+ <location filename="mainwindow.ui" line="1280"/>
+ <source>Tools</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1277"/>
+ <source>&amp;Tools</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1283"/>
+ <source>Ctrl+I</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1292"/>
+ <source>Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1295"/>
+ <source>&amp;Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1298"/>
+ <source>Configure settings and workarounds</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1301"/>
+ <source>Ctrl+S</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1310"/>
+ <source>Nexus</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1313"/>
+ <source>Search nexus network for more mods</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1316"/>
+ <source>Ctrl+N</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1328"/>
+ <location filename="mainwindow.cpp" line="3592"/>
+ <source>Update</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1331"/>
+ <source>Mod Organizer is up-to-date</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1343"/>
+ <location filename="mainwindow.cpp" line="597"/>
+ <source>No Problems</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1346"/>
+ <source>This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them.
+
+!Work in progress!
+Right now this has very limited functionality</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1358"/>
+ <location filename="mainwindow.ui" line="1361"/>
+ <source>Help</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1364"/>
+ <source>Ctrl+H</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1373"/>
+ <source>Endorse MO</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1376"/>
+ <location filename="mainwindow.cpp" line="3670"/>
+ <source>Endorse Mod Organizer</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1381"/>
+ <source>Copy Log to Clipboard</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1384"/>
+ <source>Ctrl+C</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1393"/>
+ <source>Change Game</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.ui" line="1396"/>
+ <source>Open the game selection dialog</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="286"/>
+ <source>Toolbar</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="287"/>
+ <source>Desktop</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="288"/>
+ <source>Start Menu</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="585"/>
+ <source>Problems</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="586"/>
+ <source>There are potential problems with your setup</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="598"/>
+ <source>Everything seems to be in order</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="659"/>
+ <source>Help on UI</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="663"/>
+ <source>Documentation Wiki</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="667"/>
+ <source>Report Issue</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="671"/>
+ <source>Tutorials</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="710"/>
+ <source>About</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="711"/>
+ <source>About Qt</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="763"/>
+ <source>Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="764"/>
+ <source>Please enter a name for the new profile</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="772"/>
+ <source>failed to create profile: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="816"/>
+ <source>Show tutorial?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="817"/>
+ <source>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 &quot;Help&quot;-menu.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="850"/>
+ <source>Downloads in progress</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="851"/>
+ <source>There are still downloads in progress, do you really want to quit?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="953"/>
+ <source>Plugin &quot;%1&quot; failed: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="955"/>
+ <source>Plugin &quot;%1&quot; failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="992"/>
+ <source>Browse Mod Page</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="1120"/>
+ <source>Also in: &lt;br&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="1131"/>
+ <source>No conflict</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="1243"/>
+ <source>&lt;Edit...&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="1392"/>
+ <source>Activating Network Proxy</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="1525"/>
+ <source>Choose Mod</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="1526"/>
+ <source>Mod Archive</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="1684"/>
+ <source>Start Tutorial?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="1685"/>
+ <source>You&apos;re about to start a tutorial. For technical reasons it&apos;s not possible to end the tutorial early. Continue?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="1820"/>
+ <source>failed to spawn notepad.exe: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="1848"/>
+ <source>failed to open %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="1922"/>
+ <source>failed to change origin name: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="1945"/>
+ <source>failed to move &quot;%1&quot; from mod &quot;%2&quot; to &quot;%3&quot;: %4</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="1969"/>
+ <source>&lt;Contains %1&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2004"/>
+ <source>&lt;Checked&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2005"/>
+ <source>&lt;Unchecked&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2006"/>
+ <source>&lt;Update&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2007"/>
+ <source>&lt;Managed by MO&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2008"/>
+ <source>&lt;Managed outside MO&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2009"/>
+ <source>&lt;No category&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2010"/>
+ <source>&lt;Conflicted&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2011"/>
+ <source>&lt;Not Endorsed&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2057"/>
+ <source>failed to rename mod: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2070"/>
+ <source>Overwrite?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2071"/>
+ <source>This will replace the existing mod &quot;%1&quot;. Continue?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2074"/>
+ <source>failed to remove mod &quot;%1&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2078"/>
+ <location filename="mainwindow.cpp" line="3490"/>
+ <location filename="mainwindow.cpp" line="3514"/>
+ <source>failed to rename &quot;%1&quot; to &quot;%2&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2126"/>
+ <location filename="mainwindow.cpp" line="2807"/>
+ <location filename="mainwindow.cpp" line="2815"/>
+ <location filename="mainwindow.cpp" line="3073"/>
+ <source>Confirm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2127"/>
+ <source>Remove the following mods?&lt;br&gt;&lt;ul&gt;%1&lt;/ul&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2138"/>
+ <source>failed to remove mod: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2173"/>
+ <location filename="mainwindow.cpp" line="2176"/>
+ <source>Failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2173"/>
+ <source>Installation file no longer exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2177"/>
+ <source>Mods installed with old versions of MO can&apos;t be reinstalled in this way.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2194"/>
+ <source>You need to be logged in with Nexus to resume a download</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2210"/>
+ <location filename="mainwindow.cpp" line="2237"/>
+ <source>You need to be logged in with Nexus to endorse</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2285"/>
+ <source>Failed to display overwrite dialog: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2414"/>
+ <source>Nexus ID for this Mod is unknown</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2424"/>
+ <source>Web page for this mod is unknown</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2451"/>
+ <location filename="mainwindow.cpp" line="2478"/>
+ <location filename="mainwindow.cpp" line="2924"/>
+ <source>Create Mod...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2452"/>
+ <source>This will create an empty mod.
+Please enter a name:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2461"/>
+ <location filename="mainwindow.cpp" line="2488"/>
+ <source>A mod with this name already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2479"/>
+ <source>This will move all files from overwrite into a new, regular mod.
+Please enter a name:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2719"/>
+ <source>Not logged in, endorsement information will be wrong</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2727"/>
+ <source>Continue?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2728"/>
+ <source>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.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2748"/>
+ <location filename="mainwindow.cpp" line="3560"/>
+ <source>Sorry</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2749"/>
+ <source>I don&apos;t know a versioning scheme where %1 is newer than %2.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2807"/>
+ <source>Really enable all visible mods?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2815"/>
+ <source>Really disable all visible mods?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2823"/>
+ <source>Choose what to export</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2825"/>
+ <source>Everything</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2825"/>
+ <source>All installed mods are included in the list</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2826"/>
+ <source>Active Mods</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2826"/>
+ <source>Only active (checked) mods from your current profile are included</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2827"/>
+ <source>Visible</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2827"/>
+ <source>All mods visible in the mod list are included</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2870"/>
+ <source>export failed: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2887"/>
+ <source>Install Mod...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2889"/>
+ <source>Create empty mod</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2891"/>
+ <source>Enable all visible</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2892"/>
+ <source>Disable all visible</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2894"/>
+ <source>Check all for update</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2898"/>
+ <source>Export to csv...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2917"/>
+ <source>All Mods</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2923"/>
+ <source>Sync to Mods...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2927"/>
+ <source>Restore Backup</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2928"/>
+ <source>Remove Backup...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2932"/>
+ <source>Add/Remove Categories</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2937"/>
+ <source>Replace Categories</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2942"/>
+ <source>Primary Category</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2948"/>
+ <source>Change versioning scheme</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2952"/>
+ <source>Un-ignore update</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2954"/>
+ <source>Ignore update</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2959"/>
+ <source>Rename Mod...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2960"/>
+ <source>Remove Mod...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2961"/>
+ <source>Reinstall Mod</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2965"/>
+ <source>Un-Endorse</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2968"/>
+ <location filename="mainwindow.cpp" line="2972"/>
+ <source>Endorse</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2969"/>
+ <source>Won&apos;t endorse</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2975"/>
+ <source>Endorsement state unknown</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2984"/>
+ <source>Ignore missing data</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2988"/>
+ <source>Visit on Nexus</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2990"/>
+ <source>Visit web page</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2993"/>
+ <source>Open in explorer</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="2997"/>
+ <source>Information...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3004"/>
+ <location filename="mainwindow.cpp" line="4067"/>
+ <source>Exception: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3006"/>
+ <location filename="mainwindow.cpp" line="4069"/>
+ <source>Unknown exception</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3034"/>
+ <source>&lt;All&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3036"/>
+ <source>&lt;Multiple&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3070"/>
+ <source>%1 more</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="mainwindow.cpp" line="3074"/>
+ <source>Are you sure you want to remove the following %n save(s)?&lt;br&gt;&lt;ul&gt;%1&lt;/ul&gt;&lt;br&gt;Removed saves will be sent to the Recycle Bin.</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3119"/>
+ <source>Enable Mods...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="mainwindow.cpp" line="3134"/>
+ <source>Delete %n save(s)</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3176"/>
+ <source>failed to remove %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3198"/>
+ <source>failed to create %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3240"/>
+ <source>Can&apos;t change download directory while downloads are in progress!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3362"/>
+ <source>failed to write to file %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3368"/>
+ <source>%1 written</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3409"/>
+ <source>Select binary</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3409"/>
+ <source>Binary</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3435"/>
+ <source>Enter Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3436"/>
+ <source>Please enter a name for the executable</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3450"/>
+ <source>Not an executable</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3450"/>
+ <source>This is not a recognized executable.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3475"/>
+ <location filename="mainwindow.cpp" line="3500"/>
+ <source>Replace file?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3475"/>
+ <source>There already is a hidden version of this file. Replace it?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3478"/>
+ <location filename="mainwindow.cpp" line="3503"/>
+ <source>File operation failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3478"/>
+ <location filename="mainwindow.cpp" line="3503"/>
+ <source>Failed to remove &quot;%1&quot;. Maybe you lack the required file permissions?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3500"/>
+ <source>There already is a visible version of this file. Replace it?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3533"/>
+ <source>file not found: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3546"/>
+ <source>failed to generate preview for %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3560"/>
+ <source>Sorry, can&apos;t preview anything. This function currently does not support extracting from bsas.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3594"/>
+ <source>Update available</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3631"/>
+ <source>Open/Execute</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3632"/>
+ <source>Add as Executable</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3636"/>
+ <source>Preview</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3642"/>
+ <source>Un-Hide</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3644"/>
+ <source>Hide</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3650"/>
+ <source>Write To File...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3671"/>
+ <source>Do you want to endorse Mod Organizer on %1 now?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3774"/>
+ <source>Thank you!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3774"/>
+ <source>Thank you for your endorsement!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3809"/>
+ <source>Request to Nexus failed: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3824"/>
+ <location filename="mainwindow.cpp" line="3875"/>
+ <source>failed to read %1: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3836"/>
+ <location filename="mainwindow.cpp" line="4254"/>
+ <source>Error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3836"/>
+ <source>failed to extract %1 (errorcode %2)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3867"/>
+ <source>Extract BSA</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3886"/>
+ <source>This archive contains invalid hashes. Some files may be broken.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3934"/>
+ <source>Are you sure?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3935"/>
+ <source>This will restart MO, continue?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3975"/>
+ <source>Edit Categories...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="3976"/>
+ <source>Deselect filter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4027"/>
+ <source>Remove</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4038"/>
+ <source>Enable all</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4039"/>
+ <source>Disable all</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4058"/>
+ <source>Unlock load order</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4061"/>
+ <source>Lock load order</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4203"/>
+ <source>depends on missing &quot;%1&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4207"/>
+ <source>incompatible with &quot;%1&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4229"/>
+ <source>Please wait while LOOT is running</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4327"/>
+ <source>loot failed. Exit code was: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4349"/>
+ <source>failed to start loot</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4352"/>
+ <source>failed to run loot: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4356"/>
+ <source>Errors occured</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4409"/>
+ <source>Backup of load order created</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4419"/>
+ <source>Choose backup to restore</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4432"/>
+ <source>No Backups</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4432"/>
+ <source>There are no backups to restore</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4453"/>
+ <location filename="mainwindow.cpp" line="4475"/>
+ <source>Restore failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4454"/>
+ <location filename="mainwindow.cpp" line="4476"/>
+ <source>Failed to restore the backup. Errorcode: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4465"/>
+ <source>Backup of modlist created</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4565"/>
+ <source>A file with the same name has already been downloaded. What would you like to do?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4567"/>
+ <source>Overwrite</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4568"/>
+ <source>Rename new file</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="4569"/>
+ <source>Ignore file</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>MessageDialog</name>
+ <message>
+ <location filename="messagedialog.ui" line="150"/>
+ <location filename="messagedialog.ui" line="180"/>
+ <source>Placeholder</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ModInfo</name>
+ <message>
+ <location filename="modinfo.cpp" line="93"/>
+ <source>Plugins</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfo.cpp" line="94"/>
+ <source>Textures</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfo.cpp" line="95"/>
+ <source>Meshes</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfo.cpp" line="96"/>
+ <source>BSA</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfo.cpp" line="97"/>
+ <source>UI Changes</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfo.cpp" line="98"/>
+ <source>Sound Effects</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfo.cpp" line="99"/>
+ <source>Scripts</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfo.cpp" line="100"/>
+ <source>SKSE Plugins</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfo.cpp" line="101"/>
+ <source>SkyProc Tools</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfo.cpp" line="102"/>
+ <source>invalid content type %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfo.cpp" line="125"/>
+ <source>invalid mod index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfo.cpp" line="154"/>
+ <source>remove: invalid mod index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ModInfoBackup</name>
+ <message>
+ <location filename="modinfobackup.cpp" line="13"/>
+ <source>This is the backup of a mod</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ModInfoDialog</name>
+ <message>
+ <location filename="modinfodialog.ui" line="14"/>
+ <source>Mod Info</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="30"/>
+ <source>Textfiles</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="42"/>
+ <source>A list of text-files in the mod directory.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="45"/>
+ <source>A list of text-files in the mod directory like readmes. </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="67"/>
+ <location filename="modinfodialog.ui" line="175"/>
+ <source>Save</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="77"/>
+ <source>INI-Files</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="91"/>
+ <source>Ini Files</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="104"/>
+ <source>This is a list of .ini files in the mod.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="107"/>
+ <source>This is a list of .ini files in the mod. These are usually used to configure the behaviour of mods if there are configurable parameters.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="114"/>
+ <source>Ini Tweaks</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="130"/>
+ <source>This is a list of ini tweaks (ini modifications that can be toggled).</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="133"/>
+ <source>This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="169"/>
+ <source>Save changes to the file.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="172"/>
+ <source>Save changes to the file. This overwrites the original. There is no automatic backup!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="185"/>
+ <source>Images</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="240"/>
+ <source>Images located in the mod.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="243"/>
+ <source>This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="276"/>
+ <location filename="modinfodialog.ui" line="295"/>
+ <source>Optional ESPs</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="282"/>
+ <source>List of esps and esms that can not be loaded by the game.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="285"/>
+ <source>List of esps and esms contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window.
+They usually contain optional functionality, see the readme.
+
+Most mods do not have optional esps, so chances are good you are looking at an empty list.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="310"/>
+ <source>Make the selected mod in the lower list unavailable.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="313"/>
+ <source>The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become &quot;invisible&quot; to the game. It can then no longer be activated.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="339"/>
+ <source>Move a file to the data directory.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="342"/>
+ <source>This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes &quot;available&quot;, it will not necessarily be loaded! That is configured in the main window of omo.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="377"/>
+ <source>ESPs in the data directory and thus visible to the game.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="380"/>
+ <source>These are the mod files that are in the (virtual) data directory of your game and will thus be selecteable in the esp list in the main window.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="387"/>
+ <source>Available ESPs</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="395"/>
+ <source>Conflicts</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="403"/>
+ <source>The following conflicted files are provided by this mod</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="453"/>
+ <location filename="modinfodialog.ui" line="503"/>
+ <source>File</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="458"/>
+ <source>Overwritten Mods</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="468"/>
+ <source>The following conflicted files are provided by other mods</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="508"/>
+ <source>Providing Mod</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="518"/>
+ <source>Non-Conflicted files</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="538"/>
+ <source>Categories</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="561"/>
+ <source>Primary Category</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="578"/>
+ <source>Nexus Info</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="586"/>
+ <source>Mod ID</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="593"/>
+ <source>Mod ID for this mod on Nexus.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="596"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: &lt;/span&gt;&lt;a href=&quot; http://www.skyrimnexus.com/downloads/file.php?id=1334&quot;&gt;&lt;span style=&quot; font-size:8pt; text-decoration: underline; color:#0000ff;&quot;&gt;http://skyrim.nexusmods.com/downloads/file.php?id=1334&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;http://www.skyrimnexus.com/downloads/file.php?id=1334&quot;&gt;&lt;span style=&quot; font-size:8pt; color:#000000;&quot;&gt;. In this example, 1334 is the id you&apos;re looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="620"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="627"/>
+ <source>Version</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="657"/>
+ <source>Refresh</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="660"/>
+ <source>Refresh all information from Nexus.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="674"/>
+ <source>Description</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="684"/>
+ <source>about:blank</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="710"/>
+ <source>Endorse</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="724"/>
+ <source>Notes</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="734"/>
+ <source>Filetree</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="743"/>
+ <source>A directory view of this mod</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="746"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;This is a modifiable directory view of the mod directory. You can move around files using drag &amp;amp; drop and rename them (double click).&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Changes happen immediately on disc, so do&lt;/span&gt;&lt;span style=&quot; font-size:8pt; font-weight:600;&quot;&gt; be careful&lt;/span&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="773"/>
+ <source>Previous</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="780"/>
+ <source>Next</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.ui" line="800"/>
+ <source>Close</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="175"/>
+ <source>&amp;Delete</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="176"/>
+ <source>&amp;Rename</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="177"/>
+ <source>&amp;Hide</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="178"/>
+ <source>&amp;Unhide</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="179"/>
+ <source>&amp;Open</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="180"/>
+ <source>&amp;New Folder</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="429"/>
+ <location filename="modinfodialog.cpp" line="444"/>
+ <source>Save changes?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="429"/>
+ <location filename="modinfodialog.cpp" line="444"/>
+ <source>Save changes to &quot;%1&quot;?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="634"/>
+ <source>File Exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="634"/>
+ <source>A file with that name exists, please enter a new one</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="651"/>
+ <source>failed to move file</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="676"/>
+ <source>failed to create directory &quot;optional&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="716"/>
+ <location filename="modinfodialog.cpp" line="1220"/>
+ <source>Info requested, please wait</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="770"/>
+ <source>Main</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="771"/>
+ <source>Update</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="772"/>
+ <source>Optional</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="773"/>
+ <source>Old</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="774"/>
+ <source>Misc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="775"/>
+ <source>Unknown</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="786"/>
+ <source>Current Version: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="790"/>
+ <source>No update available</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="831"/>
+ <source>(description incomplete, please visit nexus)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="846"/>
+ <source>&lt;a href=&quot;%1&quot;&gt;Visit on Nexus&lt;/a&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="924"/>
+ <source>Failed to delete %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="935"/>
+ <location filename="modinfodialog.cpp" line="940"/>
+ <source>Confirm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="935"/>
+ <source>Are sure you want to delete &quot;%1&quot;?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="940"/>
+ <source>Are sure you want to delete the selected files?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="1014"/>
+ <location filename="modinfodialog.cpp" line="1020"/>
+ <source>New Folder</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="1026"/>
+ <source>Failed to create &quot;%1&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="1130"/>
+ <location filename="modinfodialog.cpp" line="1154"/>
+ <source>Replace file?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="1130"/>
+ <source>There already is a hidden version of this file. Replace it?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="1133"/>
+ <location filename="modinfodialog.cpp" line="1157"/>
+ <source>File operation failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="1133"/>
+ <location filename="modinfodialog.cpp" line="1157"/>
+ <source>Failed to remove &quot;%1&quot;. Maybe you lack the required file permissions?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="1144"/>
+ <location filename="modinfodialog.cpp" line="1167"/>
+ <source>failed to rename %1 to %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="1154"/>
+ <source>There already is a visible version of this file. Replace it?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="1200"/>
+ <source>Un-Hide</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="1202"/>
+ <source>Hide</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="1243"/>
+ <source>Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="1243"/>
+ <source>Please enter a name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="1247"/>
+ <location filename="modinfodialog.cpp" line="1250"/>
+ <source>Error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="1247"/>
+ <source>Invalid name. Must be a valid file name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="1250"/>
+ <source>A tweak by that name exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinfodialog.cpp" line="1264"/>
+ <source>Create Tweak</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ModInfoForeign</name>
+ <message>
+ <location filename="modinfoforeign.cpp" line="43"/>
+ <source>This pseudo mod represents content managed outside MO. It isn&apos;t modified by MO.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ModInfoOverwrite</name>
+ <message>
+ <location filename="modinfooverwrite.cpp" line="42"/>
+ <source>This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ModInfoRegular</name>
+ <message>
+ <location filename="modinforegular.cpp" line="161"/>
+ <location filename="modinforegular.cpp" line="164"/>
+ <source>failed to write %1/meta.ini: error %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinforegular.cpp" line="479"/>
+ <source>%1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modinforegular.cpp" line="483"/>
+ <source>Categories: &lt;br&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ModList</name>
+ <message>
+ <location filename="modlist.cpp" line="63"/>
+ <source>Game plugins (esp/esm)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="64"/>
+ <source>Interface</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="65"/>
+ <source>Meshes</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="66"/>
+ <source>BSA</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="67"/>
+ <source>Scripts (Papyrus)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="68"/>
+ <source>Script Extender Plugin</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="69"/>
+ <source>SkyProc Patcher</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="70"/>
+ <source>Sound or Music</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="71"/>
+ <source>Textures</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="132"/>
+ <source>This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="141"/>
+ <source>Backup</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="142"/>
+ <source>No valid game data</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="143"/>
+ <source>Not endorsed yet</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="145"/>
+ <source>Overwrites files</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="146"/>
+ <source>Overwritten files</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="147"/>
+ <source>Overwrites &amp; Overwritten</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="148"/>
+ <source>Redundant</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="225"/>
+ <source>Non-MO</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="255"/>
+ <source>invalid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="379"/>
+ <source>installed version: &quot;%1&quot;, newest version: &quot;%2&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="381"/>
+ <source>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 &quot;upgrade&quot;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="389"/>
+ <source>Categories: &lt;br&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="418"/>
+ <source>Invalid name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="870"/>
+ <source>drag&amp;drop failed: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="940"/>
+ <source>Confirm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="941"/>
+ <source>Are you sure you want to remove &quot;%1&quot;?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="1004"/>
+ <source>Flags</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="1005"/>
+ <source>Content</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="1006"/>
+ <source>Mod Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="1007"/>
+ <source>Version</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="1008"/>
+ <source>Priority</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="1009"/>
+ <source>Category</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="1010"/>
+ <source>Nexus ID</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="1011"/>
+ <source>Installation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="1012"/>
+ <location filename="modlist.cpp" line="1042"/>
+ <source>unknown</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="1020"/>
+ <source>Name of your mods</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="1021"/>
+ <source>Version of the mod (if available)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="1022"/>
+ <source>Installation priority of your mod. The higher, the more &quot;important&quot; it is and thus overwrites files from mods with lower priority.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="1024"/>
+ <source>Category of the mod.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="1025"/>
+ <source>Id of the mod as used on Nexus.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="1026"/>
+ <source>Emblemes to highlight things that might require attention.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="1027"/>
+ <source>Depicts the content of the mod:&lt;br&gt;&lt;table cellspacing=7&gt;&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;:/MO/gui/content/plugin&quot; width=32/&gt;&lt;/td&gt;&lt;td&gt;Game plugins (esp/esm)&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;:/MO/gui/content/interface&quot; width=32/&gt;&lt;/td&gt;&lt;td&gt;Interface&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;:/MO/gui/content/mesh&quot; width=32/&gt;&lt;/td&gt;&lt;td&gt;Meshes&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;:/MO/gui/content/bsa&quot; width=32/&gt;&lt;/td&gt;&lt;td&gt;BSA&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;:/MO/gui/content/texture&quot; width=32/&gt;&lt;/td&gt;&lt;td&gt;Textures&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;:/MO/gui/content/sound&quot; width=32/&gt;&lt;/td&gt;&lt;td&gt;Sounds&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;:/MO/gui/content/music&quot; width=32/&gt;&lt;/td&gt;&lt;td&gt;Music&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;:/MO/gui/content/string&quot; width=32/&gt;&lt;/td&gt;&lt;td&gt;Strings&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;:/MO/gui/content/script&quot; width=32/&gt;&lt;/td&gt;&lt;td&gt;Scripts (Papyrus)&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;:/MO/gui/content/skse&quot; width=32/&gt;&lt;/td&gt;&lt;td&gt;Script Extender plugins&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;:/MO/gui/content/skyproc&quot; width=32/&gt;&lt;/td&gt;&lt;td&gt;SkyProc Patcher&lt;/tr&gt;&lt;/table&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="modlist.cpp" line="1041"/>
+ <source>Time this mod was installed</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ModListSortProxy</name>
+ <message>
+ <location filename="modlistsortproxy.cpp" line="383"/>
+ <source>Drag&amp;Drop is only supported when sorting by priority</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>MotDDialog</name>
+ <message>
+ <location filename="motddialog.ui" line="14"/>
+ <source>Message of the Day</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="motddialog.ui" line="42"/>
+ <source>OK</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>MyFileSystemModel</name>
+ <message>
+ <location filename="overwriteinfodialog.cpp" line="47"/>
+ <source>Overwrites</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="overwriteinfodialog.cpp" line="59"/>
+ <source>not implemented</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>NXMAccessManager</name>
+ <message>
+ <location filename="nxmaccessmanager.cpp" line="177"/>
+ <source>Verifying Nexus login</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="nxmaccessmanager.cpp" line="254"/>
+ <source>Logging into Nexus</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="nxmaccessmanager.cpp" line="277"/>
+ <source>timeout</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="nxmaccessmanager.cpp" line="297"/>
+ <source>Unknown error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="nxmaccessmanager.cpp" line="333"/>
+ <source>Please check your password</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>NexusInterface</name>
+ <message>
+ <location filename="nexusinterface.cpp" line="212"/>
+ <source>Failed to guess mod id for &quot;%1&quot;, please pick the correct one</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="nexusinterface.cpp" line="510"/>
+ <source>empty response</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="nexusinterface.cpp" line="539"/>
+ <source>invalid response</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>OrganizerCore</name>
+ <message>
+ <location filename="organizercore.cpp" line="304"/>
+ <location filename="organizercore.cpp" line="331"/>
+ <source>Failed to write settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="305"/>
+ <source>An error occured trying to update MO settings to %1: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="326"/>
+ <source>File is write protected</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="328"/>
+ <source>Invalid file format (probably a bug)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="329"/>
+ <source>Unknown error %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="332"/>
+ <source>An error occured trying to write back MO settings to %1: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="569"/>
+ <location filename="organizercore.cpp" line="580"/>
+ <source>Download started</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="583"/>
+ <source>Download failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="809"/>
+ <location filename="organizercore.cpp" line="864"/>
+ <source>Installation successful</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="817"/>
+ <location filename="organizercore.cpp" line="874"/>
+ <source>Configure Mod</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="818"/>
+ <location filename="organizercore.cpp" line="875"/>
+ <source>This mod contains ini tweaks. Do you want to configure them now?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="828"/>
+ <location filename="organizercore.cpp" line="885"/>
+ <source>mod &quot;%1&quot; not found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="831"/>
+ <location filename="organizercore.cpp" line="892"/>
+ <source>Installation cancelled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="832"/>
+ <location filename="organizercore.cpp" line="893"/>
+ <source>The mod was not installed completely.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1066"/>
+ <source>Executable &quot;%1&quot; not found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1092"/>
+ <source>Start Steam?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1093"/>
+ <source>Steam is required to be running already to correctly start the game. Should MO try to start steam now?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1116"/>
+ <source>Error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1164"/>
+ <source>No profile set</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1354"/>
+ <source>Failed to refresh list of esps: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1420"/>
+ <source>Multiple esps activated, please check that they don&apos;t conflict.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1487"/>
+ <source>Download?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1488"/>
+ <source>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?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1619"/>
+ <source>failed to update mod list: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1626"/>
+ <location filename="organizercore.cpp" line="1643"/>
+ <source>login successful</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1650"/>
+ <source>Login failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1651"/>
+ <source>Login failed, try again?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1660"/>
+ <source>login failed: %1. Download will not be associated with an account</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1668"/>
+ <source>login failed: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1678"/>
+ <source>login failed: %1. You need to log-in with Nexus to update MO.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1714"/>
+ <source>Too many esps and esms enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1717"/>
+ <location filename="organizercore.cpp" line="1735"/>
+ <source>Description missing</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1726"/>
+ <source>The game doesn&apos;t allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: &lt;a href=&quot;http://wiki.step-project.com/Guide:Merging_Plugins&quot;&gt;http://wiki.step-project.com/Guide:Merging_Plugins&lt;/a&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1759"/>
+ <source>failed to save load order: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="1831"/>
+ <source>The designated write target &quot;%1&quot; is not enabled.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>OverwriteInfoDialog</name>
+ <message>
+ <location filename="overwriteinfodialog.ui" line="14"/>
+ <source>Overwrite</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="overwriteinfodialog.ui" line="42"/>
+ <source>You can use drag&amp;drop to move files and directories to regular mods.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="overwriteinfodialog.cpp" line="88"/>
+ <source>&amp;Delete</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="overwriteinfodialog.cpp" line="89"/>
+ <source>&amp;Rename</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="overwriteinfodialog.cpp" line="90"/>
+ <source>&amp;Open</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="overwriteinfodialog.cpp" line="91"/>
+ <source>&amp;New Folder</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="overwriteinfodialog.cpp" line="109"/>
+ <source>%1 not found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="overwriteinfodialog.cpp" line="144"/>
+ <source>Failed to delete &quot;%1&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="overwriteinfodialog.cpp" line="155"/>
+ <location filename="overwriteinfodialog.cpp" line="160"/>
+ <source>Confirm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="overwriteinfodialog.cpp" line="155"/>
+ <source>Are sure you want to delete &quot;%1&quot;?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="overwriteinfodialog.cpp" line="160"/>
+ <source>Are sure you want to delete the selected files?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="overwriteinfodialog.cpp" line="210"/>
+ <location filename="overwriteinfodialog.cpp" line="216"/>
+ <source>New Folder</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="overwriteinfodialog.cpp" line="222"/>
+ <source>Failed to create &quot;%1&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>PluginContainer</name>
+ <message>
+ <location filename="plugincontainer.cpp" line="314"/>
+ <source>Some plugins could not be loaded</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="plugincontainer.cpp" line="317"/>
+ <location filename="plugincontainer.cpp" line="335"/>
+ <source>Description missing</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="plugincontainer.cpp" line="326"/>
+ <source>The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version:</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>PluginList</name>
+ <message>
+ <location filename="pluginlist.cpp" line="90"/>
+ <source>Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="91"/>
+ <source>Priority</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="92"/>
+ <source>Mod Index</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="93"/>
+ <source>Flags</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="94"/>
+ <location filename="pluginlist.cpp" line="106"/>
+ <source>unknown</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="102"/>
+ <source>Name of your mods</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="103"/>
+ <source>Load priority of your mod. The higher, the more &quot;important&quot; it is and thus overwrites data from plugins with lower priority.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="105"/>
+ <source>The modindex determins the formids of objects originating from this mods.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="159"/>
+ <source>failed to update esp info for file %1 (source id: %2), error: %3</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="232"/>
+ <source>esp not found: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="239"/>
+ <location filename="pluginlist.cpp" line="251"/>
+ <source>Confirm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="239"/>
+ <source>Really enable all plugins?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="251"/>
+ <source>Really disable all plugins?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="322"/>
+ <source>The file containing locked plugin indices is broken</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="757"/>
+ <source>This plugin can&apos;t be disabled (enforced by the game)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="759"/>
+ <source>&lt;b&gt;Origin&lt;/b&gt;: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="761"/>
+ <source>Author</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="764"/>
+ <source>Description</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="767"/>
+ <source>Missing Masters</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="774"/>
+ <source>Enabled Masters</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="943"/>
+ <source>failed to restore load order for %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>PluginListSortProxy</name>
+ <message>
+ <location filename="pluginlistsortproxy.cpp" line="120"/>
+ <source>Drag&amp;Drop is only supported when sorting by priority or mod index</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>PreviewDialog</name>
+ <message>
+ <location filename="previewdialog.ui" line="14"/>
+ <source>Preview</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="previewdialog.ui" line="78"/>
+ <source>Close</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ProblemsDialog</name>
+ <message>
+ <location filename="problemsdialog.ui" line="14"/>
+ <source>Problems</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="problemsdialog.ui" line="49"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-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:7.8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="problemsdialog.ui" line="75"/>
+ <source>Close</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="problemsdialog.cpp" line="42"/>
+ <location filename="problemsdialog.cpp" line="43"/>
+ <source>Fix</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="problemsdialog.cpp" line="49"/>
+ <source>No guided fix</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Profile</name>
+ <message>
+ <location filename="profile.cpp" line="79"/>
+ <source>invalid profile name %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profile.cpp" line="83"/>
+ <source>failed to create %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profile.cpp" line="194"/>
+ <source>failed to write mod list: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profile.cpp" line="205"/>
+ <source>failed to update tweaked ini file, wrong settings may be used: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profile.cpp" line="237"/>
+ <source>failed to create tweaked ini: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profile.cpp" line="247"/>
+ <source>&quot;%1&quot; is missing or inaccessible</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profile.cpp" line="292"/>
+ <location filename="profile.cpp" line="331"/>
+ <location filename="profile.cpp" line="433"/>
+ <location filename="profile.cpp" line="452"/>
+ <location filename="profile.cpp" line="462"/>
+ <source>invalid index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profile.cpp" line="414"/>
+ <source>Overwrite directory couldn&apos;t be parsed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profile.cpp" line="423"/>
+ <source>invalid priority %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profile.cpp" line="646"/>
+ <source>Delete savegames?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profile.cpp" line="647"/>
+ <source>Do you want to delete local savegames? (If you select &quot;No&quot;, the save games will show up again if you re-enable local savegames)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ProfileInputDialog</name>
+ <message>
+ <location filename="profileinputdialog.ui" line="14"/>
+ <source>Dialog</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profileinputdialog.ui" line="20"/>
+ <source>Please enter a name for the new profile</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profileinputdialog.ui" line="30"/>
+ <source>If checked, the new profile will use the default game settings.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profileinputdialog.ui" line="33"/>
+ <source>If checked, the new profile will use the default game settings instead of the &quot;global&quot; settings. Global settings are the settings you configure when running the game launcher directly, without MO.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profileinputdialog.ui" line="36"/>
+ <source>Default Game Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ProfilesDialog</name>
+ <message>
+ <location filename="profilesdialog.ui" line="14"/>
+ <source>Profiles</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="22"/>
+ <source>List of Profiles</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="25"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Note&lt;/span&gt; For technical reasons it&apos;s currently not possible to have seperate load-orders for esps. This means you can&apos;t load moda.esp before modb.esp in one profile and the other way around in another.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="38"/>
+ <location filename="profilesdialog.ui" line="41"/>
+ <source>If checked, savegames are local to this profile and will not appear when starting with a different profile.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="44"/>
+ <source>Local Savegames</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="51"/>
+ <source>Local Game Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="61"/>
+ <source>This ensures data files from mods are actually used. You want to enable this unless you use a different tool for Archive Invalidation.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="64"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;The Mod Organizer uses a workaround called &amp;quot;BSA redirection&amp;quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-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;&quot;&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="74"/>
+ <source>Automatic Archive Invalidation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="85"/>
+ <location filename="profilesdialog.ui" line="88"/>
+ <source>Create a new profile from scratch</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="91"/>
+ <source>Create</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="101"/>
+ <source>Clone the selected profile</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="104"/>
+ <source>This creates a new profile with the same settings and active mods as the selected one.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="107"/>
+ <source>Copy</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="117"/>
+ <location filename="profilesdialog.ui" line="120"/>
+ <source>Delete the selected Profile. This can not be un-done!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="123"/>
+ <source>Remove</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="133"/>
+ <source>Rename</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="143"/>
+ <location filename="profilesdialog.ui" line="146"/>
+ <source>Transfer save games to the selected profile.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="149"/>
+ <source>Transfer Saves</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.ui" line="172"/>
+ <source>Close</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.cpp" line="75"/>
+ <source>Archive invalidation isn&apos;t required for this game.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.cpp" line="113"/>
+ <location filename="profilesdialog.cpp" line="154"/>
+ <source>failed to create profile: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.cpp" line="163"/>
+ <source>Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.cpp" line="163"/>
+ <source>Please enter a name for the new profile</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.cpp" line="174"/>
+ <source>failed to copy profile: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.cpp" line="177"/>
+ <source>Invalid name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.cpp" line="178"/>
+ <source>Invalid profile name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.cpp" line="185"/>
+ <source>Confirm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.cpp" line="185"/>
+ <source>Are you sure you want to remove this profile (including local savegames if any)?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.cpp" line="194"/>
+ <source>Profile broken</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.cpp" line="195"/>
+ <source>This profile you&apos;re about to delete seems to be broken or the path is invalid. I&apos;m about to delete the following folder: &quot;%1&quot;. Proceed?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.cpp" line="227"/>
+ <source>Rename Profile</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.cpp" line="227"/>
+ <source>New Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.cpp" line="262"/>
+ <source>failed to change archive invalidation state: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.cpp" line="296"/>
+ <source>failed to determine if invalidation is active: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="categories.cpp" line="149"/>
+ <source>Failed to save custom categories</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="categories.cpp" line="250"/>
+ <location filename="categories.cpp" line="285"/>
+ <location filename="categories.cpp" line="295"/>
+ <location filename="categories.cpp" line="305"/>
+ <source>invalid index %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="categories.cpp" line="316"/>
+ <source>invalid category id %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="csvbuilder.cpp" line="70"/>
+ <source>invalid field name &quot;%1&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="csvbuilder.cpp" line="76"/>
+ <source>invalid type for &quot;%1&quot; (should be integer)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="csvbuilder.cpp" line="81"/>
+ <source>invalid type for &quot;%1&quot; (should be string)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="csvbuilder.cpp" line="86"/>
+ <source>invalid type for &quot;%1&quot; (should be float)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="csvbuilder.cpp" line="103"/>
+ <source>no fields set up yet!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="csvbuilder.cpp" line="140"/>
+ <source>field not set &quot;%1&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="csvbuilder.cpp" line="237"/>
+ <source>invalid character in field &quot;%1&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="csvbuilder.cpp" line="240"/>
+ <source>empty field name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="gameinfoimpl.cpp" line="42"/>
+ <source>invalid game type %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="helper.cpp" line="55"/>
+ <source>helper failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="helper.cpp" line="71"/>
+ <source>failed to determine account name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="72"/>
+ <location filename="selfupdater.cpp" line="80"/>
+ <source>invalid 7-zip32.dll: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="installationmanager.cpp" line="86"/>
+ <source>archive.dll not loaded: &quot;%1&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="instancemanager.cpp" line="78"/>
+ <source>Enter Instance Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="instancemanager.cpp" line="79"/>
+ <source>Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="instancemanager.cpp" line="81"/>
+ <location filename="instancemanager.cpp" line="123"/>
+ <source>Canceled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="instancemanager.cpp" line="98"/>
+ <source>Choose Instance</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="instancemanager.cpp" line="99"/>
+ <source>Each Instance is a full set of MO data files (mods, downloads, profiles, configuration, ...). Use multiple instances for different games. If your MO folder is writable, you can also store a single instance locally (called a portable install).</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="instancemanager.cpp" line="111"/>
+ <source>New</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="instancemanager.cpp" line="112"/>
+ <source>Create a new instance.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="instancemanager.cpp" line="116"/>
+ <source>Portable</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="instancemanager.cpp" line="117"/>
+ <source>Use MO folder for data.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="instancemanager.cpp" line="165"/>
+ <location filename="profile.cpp" line="64"/>
+ <source>failed to create %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="instancemanager.cpp" line="168"/>
+ <source>Data directory created</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="instancemanager.cpp" line="169"/>
+ <source>New data directory created at %1. If you don&apos;t want to store a lot of data there, reconfigure the storage directories via settings.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="loadmechanism.cpp" line="52"/>
+ <source>failed to open %1: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="loadmechanism.cpp" line="103"/>
+ <location filename="loadmechanism.cpp" line="112"/>
+ <source>%1 not found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="loadmechanism.cpp" line="129"/>
+ <location filename="loadmechanism.cpp" line="188"/>
+ <source>game doesn&apos;t support a script extender</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="loadmechanism.cpp" line="148"/>
+ <source>Failed to deactivate script extender loading</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="loadmechanism.cpp" line="168"/>
+ <source>Failed to remove %1: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="loadmechanism.cpp" line="170"/>
+ <location filename="loadmechanism.cpp" line="264"/>
+ <source>Failed to rename %1 to %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="loadmechanism.cpp" line="177"/>
+ <source>Failed to deactivate proxy-dll loading</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="loadmechanism.cpp" line="220"/>
+ <source>Failed to set up script extender loading</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="loadmechanism.cpp" line="245"/>
+ <source>Failed to delete old proxy-dll %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="loadmechanism.cpp" line="248"/>
+ <location filename="loadmechanism.cpp" line="267"/>
+ <source>Failed to copy %1 to %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="loadmechanism.cpp" line="260"/>
+ <source>Failed to overwrite %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="loadmechanism.cpp" line="272"/>
+ <source>Failed to set up proxy-dll loading</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="main.cpp" line="93"/>
+ <location filename="organizercore.cpp" line="607"/>
+ <source>Error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="main.cpp" line="94"/>
+ <location filename="organizercore.cpp" line="608"/>
+ <source>Failed to create &quot;%1&quot;. Your user account probably lacks permission.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="main.cpp" line="149"/>
+ <location filename="main.cpp" line="192"/>
+ <source>Woops</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="main.cpp" line="150"/>
+ <source>ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file (%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. Please include a short description of what you were doing when the crash happened</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="main.cpp" line="193"/>
+ <source>ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="main.cpp" line="294"/>
+ <source>Plugin to handle %1 no longer installed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="main.cpp" line="347"/>
+ <source>Could not use configuration settings for game &quot;%1&quot;, path &quot;%2&quot;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="main.cpp" line="351"/>
+ <location filename="main.cpp" line="369"/>
+ <source>Please select the game to manage</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="main.cpp" line="379"/>
+ <source>No game identified in &quot;%1&quot;. The directory is required to contain the game binary and its launcher.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="main.cpp" line="481"/>
+ <source>Please select the game edition you have (MO can&apos;t start the game correctly if this is set incorrectly!)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="main.cpp" line="524"/>
+ <source>failed to start application: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="main.cpp" line="608"/>
+ <location filename="settings.cpp" line="901"/>
+ <source>Mod Organizer</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="main.cpp" line="609"/>
+ <source>An instance of Mod Organizer is already running</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="main.cpp" line="620"/>
+ <source>Failed to set up instance</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="829"/>
+ <source>Please use &quot;Help&quot; from the toolbar to get usage instructions to all elements</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="1207"/>
+ <location filename="mainwindow.cpp" line="3322"/>
+ <source>&lt;Manage...&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="mainwindow.cpp" line="1219"/>
+ <source>failed to parse profile %1: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="139"/>
+ <source>Failed to start &quot;%1&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="142"/>
+ <source>Waiting</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="organizercore.cpp" line="143"/>
+ <source>Please press OK once you&apos;re logged into steam.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="plugincontainer.cpp" line="162"/>
+ <source>failed to initialize plugin %1: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="plugincontainer.cpp" line="248"/>
+ <source>Plugin error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="plugincontainer.cpp" line="249"/>
+ <source>It appears the plugin &quot;%1&quot; failed to load last startup and caused MO to crash. Do you want to disable it?
+(Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="398"/>
+ <source>failed to access %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pluginlist.cpp" line="412"/>
+ <source>failed to set file time %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="profilesdialog.cpp" line="94"/>
+ <source>Before you can use ModOrganizer, you need to create at least one profile. ATTENTION: Run the game at least once before creating a profile!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settings.cpp" line="908"/>
+ <source>Script Extender</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settings.cpp" line="915"/>
+ <source>Proxy DLL</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="spawn.cpp" line="143"/>
+ <source>failed to spawn &quot;%1&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="spawn.cpp" line="150"/>
+ <source>Elevation required</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="spawn.cpp" line="151"/>
+ <source>This process requires elevation to run.
+This is a potential security risk so I highly advice you to investigate if
+&quot;%1&quot;
+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)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="spawn.cpp" line="166"/>
+ <source>failed to spawn &quot;%1&quot;: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>QueryOverwriteDialog</name>
+ <message>
+ <location filename="queryoverwritedialog.ui" line="14"/>
+ <source>Mod Exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="queryoverwritedialog.ui" line="45"/>
+ <source>This mod seems to be installed already. Do you want to add files from this archive (overwriting existing ones) or do you want to completely replace the existing files (old files are deleted)? Alternatively you can install this mod under a different name.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="queryoverwritedialog.ui" line="63"/>
+ <source>Keep Backup</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="queryoverwritedialog.ui" line="70"/>
+ <source>Merge</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="queryoverwritedialog.ui" line="77"/>
+ <source>Replace</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="queryoverwritedialog.ui" line="84"/>
+ <source>Rename</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="queryoverwritedialog.ui" line="91"/>
+ <source>Cancel</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>SaveTextAsDialog</name>
+ <message>
+ <location filename="savetextasdialog.ui" line="14"/>
+ <source>Dialog</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="savetextasdialog.ui" line="32"/>
+ <source>Copy To Clipboard</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="savetextasdialog.ui" line="39"/>
+ <source>Save As...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="savetextasdialog.ui" line="59"/>
+ <source>Close</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="savetextasdialog.cpp" line="40"/>
+ <source>Save CSV</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="savetextasdialog.cpp" line="40"/>
+ <source>Text Files</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="savetextasdialog.cpp" line="44"/>
+ <source>failed to open &quot;%1&quot; for writing</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>SelectionDialog</name>
+ <message>
+ <location filename="selectiondialog.ui" line="14"/>
+ <source>Select</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="selectiondialog.ui" line="23"/>
+ <source>Placeholder</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="selectiondialog.ui" line="77"/>
+ <source>Cancel</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>SelfUpdater</name>
+ <message>
+ <location filename="selfupdater.cpp" line="94"/>
+ <source>archive.dll not loaded: &quot;%1&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="selfupdater.cpp" line="165"/>
+ <source>New update available (%1)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="selfupdater.cpp" line="170"/>
+ <source>Install</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="selfupdater.cpp" line="187"/>
+ <source>Download failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="selfupdater.cpp" line="188"/>
+ <source>Failed to find correct download, please try again later.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="selfupdater.cpp" line="203"/>
+ <source>Update</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="selfupdater.cpp" line="204"/>
+ <source>Download in progress</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="selfupdater.cpp" line="295"/>
+ <source>Download failed: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="selfupdater.cpp" line="306"/>
+ <source>Failed to install update: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="selfupdater.cpp" line="329"/>
+ <source>Failed to start %1: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="selfupdater.cpp" line="339"/>
+ <source>Error</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Settings</name>
+ <message>
+ <location filename="settings.cpp" line="135"/>
+ <source>Failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settings.cpp" line="136"/>
+ <source>Sorry, failed to start the helper application</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settings.cpp" line="418"/>
+ <location filename="settings.cpp" line="437"/>
+ <source>attempt to store setting for unknown plugin &quot;%1&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settings.cpp" line="723"/>
+ <source>Error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settings.cpp" line="724"/>
+ <source>Failed to create &quot;%1&quot;, you may not have the necessary permission. path remains unchanged.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>SettingsDialog</name>
+ <message>
+ <location filename="settingsdialog.ui" line="14"/>
+ <source>Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="24"/>
+ <source>General</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="32"/>
+ <source>Language</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="39"/>
+ <source>The display language</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="42"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;The display language. This will only displaye languages for which you have a translation installed.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="57"/>
+ <source>Style</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="64"/>
+ <source>graphical style</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="67"/>
+ <source>graphical style of the MO user interface</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="78"/>
+ <source>Log Level</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="85"/>
+ <source>Decides the amount of data printed to &quot;ModOrganizer.log&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="88"/>
+ <source>Decides the amount of data printed to &quot;ModOrganizer.log&quot;.
+&quot;Debug&quot; 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 &quot;Info&quot; level for regluar use. On the &quot;Error&quot; level the log file usually remains empty.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="93"/>
+ <source>Debug</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="98"/>
+ <source>Info</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="103"/>
+ <source>Warning</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="108"/>
+ <source>Error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="118"/>
+ <source>Update to non-stable releases.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="121"/>
+ <source>If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas).
+
+Please use this only if you&apos;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!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="128"/>
+ <source>Install Pre-releases (Betas)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="135"/>
+ <source>User interface</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="141"/>
+ <source>If checked, the download interface will be more compact.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="144"/>
+ <source>Compact Download Interface</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="151"/>
+ <source>If checked, the download list will display meta information instead of file names.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="154"/>
+ <source>Download Meta Information</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="167"/>
+ <source>Reset stored information from dialogs.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="170"/>
+ <source>This will make all dialogs show up again where you checked the &quot;Remember selection&quot;-box.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="173"/>
+ <source>Reset Dialogs</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="193"/>
+ <location filename="settingsdialog.ui" line="196"/>
+ <source>Modify the categories available to arrange your mods.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="199"/>
+ <source>Configure Mod Categories</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="210"/>
+ <source>Paths</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="218"/>
+ <source>Base Directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="225"/>
+ <location filename="settingsdialog.ui" line="238"/>
+ <location filename="settingsdialog.ui" line="323"/>
+ <source>...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="245"/>
+ <source>Overwrite</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="252"/>
+ <source>Caches</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="269"/>
+ <source>Downloads</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="283"/>
+ <source>Directory where mods are stored.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="286"/>
+ <source>Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don&apos;t exist in the new location (with the same name).</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="306"/>
+ <location filename="settingsdialog.ui" line="309"/>
+ <source>Directory where downloads are stored.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="316"/>
+ <source>Mods</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="333"/>
+ <source>Profiles</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="355"/>
+ <source>Use %BASE_DIR% to refer to the Base Directory.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="375"/>
+ <source>Important: All directories have to be writeable!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="383"/>
+ <location filename="settingsdialog.ui" line="399"/>
+ <source>Nexus</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="389"/>
+ <source>Allows automatic log-in when the Nexus-Page for the game is clicked.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="392"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;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&apos;re worried someone might steal your password, don&apos;t store it here.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="408"/>
+ <source>If checked and if correct credentials are entered below, log-in to Nexus (for browsing and downloading) is automatic.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="411"/>
+ <source>Automatically Log-In to Nexus</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="420"/>
+ <location filename="settingsdialog.ui" line="606"/>
+ <source>Username</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="434"/>
+ <location filename="settingsdialog.ui" line="616"/>
+ <source>Password</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="455"/>
+ <source>Remove cache and cookies. Forces a new login.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="458"/>
+ <source>Clear Cache</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="487"/>
+ <source>Disable automatic internet features</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="490"/>
+ <source>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)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="493"/>
+ <source>Offline Mode</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="500"/>
+ <source>Use a proxy for network connections.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="503"/>
+ <source>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.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="506"/>
+ <source>Use HTTP Proxy (Uses System Settings)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="515"/>
+ <source>Associate with &quot;Download with manager&quot; links</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="544"/>
+ <source>Known Servers (updated on download)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="565"/>
+ <source>Preferred Servers (Drag &amp; Drop)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="600"/>
+ <source>Steam</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="646"/>
+ <source>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.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="673"/>
+ <source>Plugins</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="695"/>
+ <source>Author:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="709"/>
+ <source>Version:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="723"/>
+ <source>Description:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="761"/>
+ <source>Key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="766"/>
+ <source>Value</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="778"/>
+ <source>Blacklisted Plugins (use &lt;del&gt; to remove):</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="789"/>
+ <source>Workarounds</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="797"/>
+ <source>Steam App ID</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="817"/>
+ <source>The Steam AppID for your game</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="820"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &amp;quot;Mod Organizer&amp;quot; load mechanism may not work properly.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;The preset for this is the App ID of the &amp;quot;regular&amp;quot; version so in most cases, you should be set.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;If you think you have a different version (GotY or something), follow these steps to get to the id:&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;1. Navigate to the game library in steam&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;2. right-click on the game you need the id for and choose &lt;/span&gt;&lt;span style=&quot; font-size:8pt; font-weight:600;&quot;&gt;Create desktop shortcut&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;3. right-click on the newly created shortcut on your desktop and select &lt;/span&gt;&lt;span style=&quot; font-size:8pt; font-weight:600;&quot;&gt;Properties&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;4. in the URL-field you should see something like this: &lt;/span&gt;&lt;span style=&quot; font-size:8pt; font-style:italic;&quot;&gt;steam://rungameid/22380&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;22380 is the id you&apos;re looking for.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="851"/>
+ <source>Load Mechanism</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="871"/>
+ <source>Select loading mechanism. See help for details.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="874"/>
+ <source>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&apos;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&apos;t work.
+
+If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use &quot;Script Extender&quot; 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.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="891"/>
+ <source>NMM Version</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="911"/>
+ <source>The Version of Nexus Mod Manager to impersonate.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="914"/>
+ <source>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&apos;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&apos;s not lying about what it is. It is merely adding a &quot;compatible&quot; NMM version to the user agent.
+
+tl;dr-version: If Nexus-features don&apos;t work, insert the current version number of NMM here and try again.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="936"/>
+ <source>Enforces that inactive ESPs and ESMs are never loaded.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="939"/>
+ <source>It seems that the Games occasionally load ESP or ESM files even if they haven&apos;t been activated as plugins.
+I don&apos;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.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="943"/>
+ <source>Hide inactive ESPs/ESMs</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="950"/>
+ <source>If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="953"/>
+ <source>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.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="957"/>
+ <source>Force-enable game files</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="967"/>
+ <source>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.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="970"/>
+ <source>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&apos;t be resolved correctly.
+
+If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps and esms) displayed in the right pane are completely unaffected by this feature.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="976"/>
+ <source>Display mods installed outside MO</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="986"/>
+ <location filename="settingsdialog.ui" line="990"/>
+ <source>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!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="994"/>
+ <source>Back-date BSAs</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.ui" line="1018"/>
+ <source>These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.cpp" line="75"/>
+ <source>Confirm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.cpp" line="76"/>
+ <source>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?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.cpp" line="124"/>
+ <source>Select base directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.cpp" line="135"/>
+ <source>Select download directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.cpp" line="146"/>
+ <source>Select mod directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.cpp" line="157"/>
+ <source>Select cache directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.cpp" line="168"/>
+ <source>Select profiles directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.cpp" line="179"/>
+ <source>Select overwrite directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.cpp" line="187"/>
+ <source>Confirm?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="settingsdialog.cpp" line="188"/>
+ <source>This will make all dialogs show up again where you checked the &quot;Remember selection&quot;-box. Continue?</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>SimpleInstallDialog</name>
+ <message>
+ <location filename="simpleinstalldialog.ui" line="14"/>
+ <source>Quick Install</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="simpleinstalldialog.ui" line="22"/>
+ <source>Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="simpleinstalldialog.ui" line="49"/>
+ <location filename="simpleinstalldialog.ui" line="52"/>
+ <source>Opens a Dialog that allows custom modifications.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="simpleinstalldialog.ui" line="55"/>
+ <source>Manual</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="simpleinstalldialog.ui" line="62"/>
+ <source>OK</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="simpleinstalldialog.ui" line="72"/>
+ <source>Cancel</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>SingleInstance</name>
+ <message>
+ <location filename="singleinstance.cpp" line="51"/>
+ <source>SHM error: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="singleinstance.cpp" line="85"/>
+ <source>failed to connect to running instance: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="singleinstance.cpp" line="91"/>
+ <source>failed to communicate with running instance: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="singleinstance.cpp" line="103"/>
+ <source>failed to receive data from secondary instance: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>SyncOverwriteDialog</name>
+ <message>
+ <location filename="syncoverwritedialog.ui" line="14"/>
+ <source>Sync Overwrite</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="syncoverwritedialog.ui" line="27"/>
+ <source>Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="syncoverwritedialog.ui" line="32"/>
+ <source>Sync To</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="syncoverwritedialog.cpp" line="96"/>
+ <source>&lt;don&apos;t sync&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="syncoverwritedialog.cpp" line="148"/>
+ <source>failed to remove %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="syncoverwritedialog.cpp" line="150"/>
+ <source>failed to move %1 to %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>TransferSavesDialog</name>
+ <message>
+ <location filename="transfersavesdialog.ui" line="14"/>
+ <source>Transfer Savegames</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="transfersavesdialog.ui" line="22"/>
+ <source>Global Characters</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="transfersavesdialog.ui" line="29"/>
+ <source>This is a list of characters in the global location.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="transfersavesdialog.ui" line="32"/>
+ <source>This is a list of characters in the global location.
+
+On Windows Vista/Windows 7:
+ C:\Users\[UserName]\Documents\My Games\Skyrim\Saves
+
+On Windows XP:
+ C:\Documents and Settings\[UserName]\My Documents\My Games\Skyrim\Saves
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="transfersavesdialog.ui" line="46"/>
+ <source>This is a list of save games for the selected character in the global location.
+
+On Windows Vista/Windows 7:
+ C:\Users\[UserName]\Documents\My Games\Skyrim\Saves
+
+On Windows XP:
+ C:\Documents and Settings[UserName]\My Documents\My Games\Skyrim\Saves
+
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="transfersavesdialog.ui" line="87"/>
+ <source>Move -&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="transfersavesdialog.ui" line="97"/>
+ <source>Copy -&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="transfersavesdialog.ui" line="123"/>
+ <source>&lt;- Move</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="transfersavesdialog.ui" line="133"/>
+ <source>&lt;- Copy</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="transfersavesdialog.ui" line="156"/>
+ <source>Done</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="transfersavesdialog.ui" line="167"/>
+ <source>Profile Characters</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="transfersavesdialog.cpp" line="109"/>
+ <source>Characters for profile %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="transfersavesdialog.cpp" line="149"/>
+ <source>Overwrite</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="transfersavesdialog.cpp" line="150"/>
+ <source>Overwrite the file &quot;%1&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="transfersavesdialog.cpp" line="309"/>
+ <source>Confirm</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>UsvfsConnector</name>
+ <message>
+ <location filename="usvfsconnector.cpp" line="135"/>
+ <source>Preparing vfs</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>tutorial_conflictresolution_main</name>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="5"/>
+ <source>There are multiple types of conflicts you may encounter when dealing with Mods. This tutorial will try to cover and explain how to deal with all of them.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="10"/>
+ <source>First up are file conflicts. These occur when two mods contain the same file. Most commonly this happens when several mods replace the same standard asset from the game, like the texture of an armor.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="16"/>
+ <source>As an example, say you install &quot;ModA&quot; which contains stylish new iron and leather armor. Then you install &quot;ModB&quot; which contains sexy ebony and leather armor. Obviously there is a conflict now: which leather armor to use?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="22"/>
+ <source>If you were to install the mods manually, when installing &quot;ModB&quot; you would be asked if you want to overwrite conflicting files. If you choose yes, you get the leather armor from &quot;ModB&quot; otherwise you keep the one from &quot;ModA&quot;. If you later decide you made the wrong choice, you have to reinstall one of the mods.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="29"/>
+ <source>With MO, both ModA and ModB get installed completely (no overwrite dialog) and by default &quot;ModB&quot; gets to provide the leather armor because it&apos;s automatically assigned the higher priority.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="34"/>
+ <source>However, you can change the mod priority at any time using drag&amp;drop on this list. If you assign &quot;ModA&quot; a higher priority, it provides the leather armor, no re-installation required. Since the priorities of mods in this list are treated as if the mods were installed in that order, I tend to talk about &quot;installation order&quot;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="42"/>
+ <source>If the &quot;Flags&quot;-column is enabled in the mod list, it will show you which mods are involved in a conflict and how...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="47"/>
+ <source>&lt;img src=&quot;:/MO/gui/emblem_conflict_overwrite&quot; /&gt; indicates that the mod overwrites files that are also available in another mod.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="51"/>
+ <source>&lt;img src=&quot;:/MO/gui/emblem_conflict_overwritten&quot; /&gt; indicates that the mod is &lt;b&gt;partially&lt;/b&gt; overwritten by another.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="55"/>
+ <source>&lt;img src=&quot;:/MO/gui/emblem_conflict_mixed&quot; /&gt; indicates that both of the above is true.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="59"/>
+ <source>&lt;img src=&quot;:/MO/gui/emblem_conflict_redundant&quot; /&gt; indicates that the mod is completely overwrtten by another. You could as well disable it.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="63"/>
+ <source>There are two ways to see the individual files involved in a conflict:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="67"/>
+ <source>Option A: Switch to the &quot;Data&quot;-tab if necessary</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="76"/>
+ <source>... here, if you mark the highlighted control, the tree will only display files in conflict. In the right column, it says which mod currently provides the mod (because it has highest priority) and if you hover your mouse over that info, it will list which other mods contains it.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="83"/>
+ <source>Option B: Open the &lt;i&gt;Information&lt;/i&gt;-Dialog of an &lt;b&gt;enabled&lt;/b&gt; mod you&apos;re interested in by either double-clicking it or selecting &lt;i&gt;Information...&lt;/i&gt; from the right-click menu</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="91"/>
+ <source>This was everything to know about file conflicts. The second type of conflict we have to deal with are &quot;record conflicts&quot;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="96"/>
+ <source>I told you in the &quot;First Steps&quot; tutorial how the esp/esm plugins contain changes to the game world like modifications to the terrain or existing NPCs. Each change like this is stored in a record, hence the name &quot;record conflict&quot;. For example when two mods try to change the same location, only one change can become active.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="102"/>
+ <source>As with file conflicts you can&apos;t really fix these conflicts, you have to choose which change you want. This time around however, if you choose wrong, your game may become unstable because there may be dependencies between the records of a mod.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="108"/>
+ <source>Please open the &quot;ESPs&quot;-tab...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="115"/>
+ <source>Again you can use drag&amp;drop to change priorities of plugins, thus deciding which plugin takes precedence in regards to conflicts. This is commonly called the &quot;load order&quot;. But how do you know how to order the plugins?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="122"/>
+ <source>Unlike with file conflicts, MO does not provide help on finding conflicts. The good news is, there already is a perfect tool for that called BOSS. BOSS is available on the Nexus and integrates neatly with MO. Basically, if you don&apos;t have BOSS yet, install it once this tutorial is over.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="128"/>
+ <source>After you installed BOSS in the default location (follow its instructions), start MO again and BOSS should automatically appear as an Executable...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="133"/>
+ <source>When you run BOSS, it will automatically re-organize plugins for best compatibility (overwriting your manual changes). It will also open a report in your browser that warns about incompatibilities. You should read the report, at least for new mods.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="140"/>
+ <source>The final type of conflicts are also &quot;record conflicts&quot;. Like the previous type. It&apos;s confusing, so I&apos;ll just call them &quot;lists conflicts&quot; instead. The difference is the types of records in conflict. The ones in question here can be merged so you may be able to get all modifications in your game.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="146"/>
+ <source>One common example of such records are leveled lists that contain all the items that may spawn at a specific character level. Traditionally, if multiple mods add items to such a list, only one is in effect...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="151"/>
+ <source>... but there are tools to merge those mods so you can have the effects of all of them. Again, this functionality is not integrated with MO because there are already great tools. For Oblivion and Skyrim look for wrye bash, for fallout 3/nv it&apos;s wrye flash. For Skyrim there is also &quot;SkyBash&quot;. All of these can create a so-called &quot;bashed patch&quot; which is a plugin that contains the combined mergeable records from all your mods.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_main.js" line="159"/>
+ <source>This completes the tutorial.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>tutorial_conflictresolution_modinfo</name>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_modinfo.js" line="4"/>
+ <source>Please switch to the &quot;Conflicts&quot;-Tab.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_modinfo.js" line="11"/>
+ <source>Here you can see a list of files in this mod that out-prioritize others in a file conflict and one with files where this mod is overridden.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_conflictresolution_modinfo.js" line="16"/>
+ <source>Please close the information dialog again.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>tutorial_firststeps_browser</name>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_browser.js" line="5"/>
+ <source>This is a fully featured browser that is set up to open the correct nexus page for your game. You can download any mod using the &quot;DOWNLOAD WITH MANAGER&quot;-button or the &quot;manual&quot;-link and it will be downloaded by MO.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>tutorial_firststeps_main</name>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="6"/>
+ <source>Welcome to the ModOrganizer Tutorial! This will guide you through the most common features of MO.
+Please go along with the tutorial because some things can&apos;t be demonstrated if you skip something.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="12"/>
+ <source>Before we continue with the step-by-step tutorial, I&apos;d like to tell you about the other ways you can receive help on ModOrganizer.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="17"/>
+ <source>The highlighted button provides hints on solving problems MO recognized automatically.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="19"/>
+ <source>
+There IS a problem now but you may want to hold off on fixing it until after completing the tutorial.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="29"/>
+ <source>This button provides multiple sources of information and further tutorials.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="39"/>
+ <source>Finally there are tooltips on almost every part of Mod Organizer. If there is a control in MO you don&apos;t understand, please try hovering over it to get a short description or use &quot;Help on UI&quot; from the help menu to get a longer explanation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="46"/>
+ <source>This list displays all mods installed through MO. It also displays installed DLCs and some mods installed outside MO but you have limited control over those in MO.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="53"/>
+ <source>Before we start installing mods, let&apos;s have a quick look at the settings.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="65"/>
+ <source>Now it&apos;s time to install a few mods!Please go along with this because we need a few mods installed to demonstrate other features</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="71"/>
+ <source>There are a few ways to get mods into ModOrganizer. If you associated MO with NXM links in the settings you can now use your regular browser to send downloads from Nexus to MO. Click on &quot;Nexus&quot; to open nexus, find a mod and click the green download buttons on Nexus saying &quot;Download with Manager&quot;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="83"/>
+ <source>You can also install mods from disk using the &quot;Install Mod&quot; button.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="89"/>
+ <source>Downloads will appear on the &quot;Downloads&quot;-tab here. You have to download and install at least one mod to proceed.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="97"/>
+ <source>Great, you just installed your first mod. Please note that the installation procedure may differ based on how a mod was packaged.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="103"/>
+ <source>Now you know all about downloading and installing mods but they are not enabled yet...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="108"/>
+ <source>Install a few more mods if you want, then enable mods by checking them in the left pane. Mods that aren&apos;t enabled have no effect on the game whatsoever. </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="117"/>
+ <source>For some mods, enabling it on the left pane is all you have to do...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="122"/>
+ <source>...but most contain plugins. These are plugins for the game and are required to add stuff to the game (new weapons, armors, quests, areas, ...). Please open the &quot;Plugins&quot;-tab to get a list of plugins.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="133"/>
+ <source>You will notice some plugins are grayed out. These are part of the main game and can&apos;t be disabled.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="139"/>
+ <source>A single mod may contain zero, one or multiple esps. Some or all may be optional. If in doubt, please consult the documentation of the indiviual mod. To do so, right-click the mod and select &quot;Information&quot;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="148"/>
+ <source>Now you know how to download, install and enable mods.
+It&apos;s important you always start the game from inside MO, otherwise the mods you installed here won&apos;t work.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="156"/>
+ <source>This combobox lets you choose &lt;i&gt;what&lt;/i&gt; to start. This way you can start the game, Launcher, Script Extender, the Creation Kit or other tools. If you miss a tool you can also configure this list but that is an advanced topic.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_main.js" line="164"/>
+ <source>This completes the basic tutorial. As homework go play a bit! After you have installed more mods you may want to read the tutorial on conflict resolution.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>tutorial_firststeps_modinfo</name>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_modinfo.js" line="5"/>
+ <source>This dialog tries to expose as much information about a mod as possible. Depending on the mod this may include readmes, screenshots, optional plugins and so on. If a certain type of information was not found in a mod, the corresponding tab is grayed out.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_modinfo.js" line="13"/>
+ <source>If you installed the mod from Nexus, the corresponding tab should give you direct access to the mod page.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_modinfo.js" line="19"/>
+ <source>We may re-visit this screen in later tutorials.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>tutorial_firststeps_settings</name>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_settings.js" line="6"/>
+ <source>You can use your regular browser to download from Nexus.
+Please open the &quot;Nexus&quot;-tab</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_settings.js" line="12"/>
+ <source>Click this button so that &quot;DOWNLOAD WITH MANAGER&quot;-buttons are download with Mod Organizer.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_firststeps_settings.js" line="19"/>
+ <source>You can also store your Nexus-credentials here for automatic login. The password is stored unencrypted on your disk!</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>tutorial_primer_main</name>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="60"/>
+ <source>This window shows all the mods that are installed. The column headers can be used for sorting. Only checked mods are active in the current profile.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="61"/>
+ <source>Each profile is a separate set of enabled mods and ini settings.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="62"/>
+ <source>The dropdown allows various ways of grouping the mods shown in the mod list.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="63"/>
+ <source>Show/hide the category pane.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="64"/>
+ <source>Quickly filter the mod list as you type.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="65"/>
+ <source>Switch between information views.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="66"/>
+ <source>This shows mod categories and some meta categories (in angle-brackets). Select some to filter the mod list. For example select &quot;&lt;Checked&gt;&quot; to show only active mods.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="67"/>
+ <source>Customizable list for choosing the program to run.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="68"/>
+ <source>When this button is clicked, Mod Organizer creates a virtual directory structure then runs the program selected to the left.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="69"/>
+ <source>Will create a shortcut for quick access. The shortcut can be placed in the toolbar at the top, in the Start Menu or on the Windows Desktop.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="70"/>
+ <source>Log messages produced by MO. Please note that messages with a light bulb usually don&apos;t require your attention.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="72"/>
+ <source>Configure Mod Organizer.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="73"/>
+ <source>Reports potential Problems about the current setup.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="74"/>
+ <source>Activates if there is an update for MO. Please note that if, for any reason, MO can&apos;t communicate with NMM, this will not work either.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="78"/>
+ <source>Plugins (esp/esm files) of the mods in the current profile. They need to be checked to be loaded.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="79"/>
+ <source>Automatically sort plugins using the bundled LOOT application.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="80"/>
+ <source>Quickly filter plugin list as you type.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="83"/>
+ <source>All the asset archives (.bsa files) for all active mods.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="86"/>
+ <source>The directory tree and all files that the program will see.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="89"/>
+ <source>Save game browser. Shows all the saves for the current profile and whether or not the current mod-load status is correct.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="92"/>
+ <source>Shows the mods that have been downloaded and if they’ve been installed.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_primer_main.js" line="100"/>
+ <source>Click to quit</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>tutorial_window_installer</name>
+ <message>
+ <location filename="tutorials/tutorial_window_installer.js" line="6"/>
+ <source>This mod has been packaged in a way that Mod Organizer did not automatically recognize...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_window_installer.js" line="11"/>
+ <source>You can use drag&amp;drop on this list to fix the structure of the mod.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_window_installer.js" line="16"/>
+ <source>The correct structure replicates the data-directory of the game. That means esps, the &quot;meshes&quot;- or &quot;textures&quot;-directory and so on should be directly below &quot;&lt;data&gt;&quot;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_window_installer.js" line="22"/>
+ <source>You can also disable files and directories that you don&apos;t want to unpack.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_window_installer.js" line="26"/>
+ <source>From the context menu (right-click) you can open textfiles, in case you want to access a readme.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tutorials/tutorial_window_installer.js" line="31"/>
+ <source>This text will turn green if MO thinks the structure looks good.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 196ef6bf..d91e2423 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -17,16 +17,17 @@
#include "logbuffer.h"
#include "credentialsdialog.h"
#include "filedialogmemory.h"
-#include "lockeddialog.h"
#include "modinfodialog.h"
#include "spawn.h"
#include "syncoverwritedialog.h"
#include "nxmaccessmanager.h"
#include <ipluginmodpage.h>
#include <dataarchives.h>
+#include <localsavegames.h>
#include <directoryentry.h>
#include <scopeguard.h>
#include <utility.h>
+#include <usvfs.h>
#include "appconfig.h"
#include <report.h>
#include <questionboxmemory.h>
@@ -54,6 +55,7 @@
#include <exception>
#include <functional>
+#include <boost/algorithm/string/predicate.hpp>
#include <memory>
#include <set>
#include <string> //for wstring
@@ -64,16 +66,16 @@
using namespace MOShared;
using namespace MOBase;
-
static bool isOnline()
{
QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
bool connected = false;
- for (auto iter = interfaces.begin(); iter != interfaces.end() && !connected; ++iter) {
- if ( (iter->flags() & QNetworkInterface::IsUp) &&
- (iter->flags() & QNetworkInterface::IsRunning) &&
- !(iter->flags() & QNetworkInterface::IsLoopBack)) {
+ for (auto iter = interfaces.begin(); iter != interfaces.end() && !connected;
+ ++iter) {
+ if ((iter->flags() & QNetworkInterface::IsUp)
+ && (iter->flags() & QNetworkInterface::IsRunning)
+ && !(iter->flags() & QNetworkInterface::IsLoopBack)) {
auto addresses = iter->addressEntries();
if (addresses.count() == 0) {
continue;
@@ -88,7 +90,8 @@ static bool isOnline()
return connected;
}
-static bool renameFile(const QString &oldName, const QString &newName, bool overwrite = true)
+static bool renameFile(const QString &oldName, const QString &newName,
+ bool overwrite = true)
{
if (overwrite && QFile::exists(newName)) {
QFile::remove(newName);
@@ -116,11 +119,12 @@ static std::wstring getProcessName(DWORD processId)
static void startSteam(QWidget *widget)
{
- QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam", QSettings::NativeFormat);
+ QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam",
+ QSettings::NativeFormat);
QString exe = steamSettings.value("SteamExe", "").toString();
if (!exe.isEmpty()) {
exe = QString("\"%1\"").arg(exe);
- //See if username and password supplied. If so, pass them into steam.
+ // See if username and password supplied. If so, pass them into steam.
QStringList args;
QString username;
QString password;
@@ -134,8 +138,9 @@ static void startSteam(QWidget *widget)
if (!QProcess::startDetached(exe, args)) {
reportError(QObject::tr("Failed to start \"%1\"").arg(exe));
} else {
- QMessageBox::information(widget, QObject::tr("Waiting"),
- QObject::tr("Please press OK once you're logged into steam."));
+ QMessageBox::information(
+ widget, QObject::tr("Waiting"),
+ QObject::tr("Please press OK once you're logged into steam."));
}
}
}
@@ -150,7 +155,6 @@ QStringList toStringList(InputIterator current, InputIterator end)
return result;
}
-
OrganizerCore::OrganizerCore(const QSettings &initSettings)
: m_UserInterface(nullptr)
, m_PluginContainer(nullptr)
@@ -184,21 +188,33 @@ OrganizerCore::OrganizerCore(const QSettings &initSettings)
m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory());
- connect(&m_DownloadManager, SIGNAL(downloadSpeed(QString,int)), this, SLOT(downloadSpeed(QString,int)));
- connect(&m_DirectoryRefresher, SIGNAL(refreshed()), this, SLOT(directory_refreshed()));
+ connect(&m_DownloadManager, SIGNAL(downloadSpeed(QString, int)), this,
+ SLOT(downloadSpeed(QString, int)));
+ connect(&m_DirectoryRefresher, SIGNAL(refreshed()), this,
+ SLOT(directory_refreshed()));
- connect(&m_ModList, SIGNAL(removeOrigin(QString)), this, SLOT(removeOrigin(QString)));
+ connect(&m_ModList, SIGNAL(removeOrigin(QString)), this,
+ SLOT(removeOrigin(QString)));
- connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginSuccessful(bool)), this, SLOT(loginSuccessful(bool)));
- connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString)));
+ connect(NexusInterface::instance()->getAccessManager(),
+ SIGNAL(loginSuccessful(bool)), this, SLOT(loginSuccessful(bool)));
+ connect(NexusInterface::instance()->getAccessManager(),
+ SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString)));
- //This seems awfully imperative
- connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)), &m_Settings, SLOT(managedGameChanged(MOBase::IPluginGame const *)));
- connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)), &m_DownloadManager, SLOT(managedGameChanged(MOBase::IPluginGame const *)));
- connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)), &m_PluginList, SLOT(managedGameChanged(MOBase::IPluginGame const *)));
- connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)), NexusInterface::instance(), SLOT(managedGameChanged(MOBase::IPluginGame const *)));
+ // This seems awfully imperative
+ connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)),
+ &m_Settings, SLOT(managedGameChanged(MOBase::IPluginGame const *)));
+ connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)),
+ &m_DownloadManager,
+ SLOT(managedGameChanged(MOBase::IPluginGame const *)));
+ connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)),
+ &m_PluginList, SLOT(managedGameChanged(MOBase::IPluginGame const *)));
+ connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)),
+ NexusInterface::instance(),
+ SLOT(managedGameChanged(MOBase::IPluginGame const *)));
- connect(&m_PluginList, &PluginList::writePluginsList, &m_PluginListsWriter, &DelayedFileWriterBase::write);
+ connect(&m_PluginList, &PluginList::writePluginsList, &m_PluginListsWriter,
+ &DelayedFileWriterBase::write);
// make directory refresher run in a separate thread
m_RefresherThread.start();
@@ -221,7 +237,7 @@ OrganizerCore::~OrganizerCore()
ModInfo::clear();
LogBuffer::cleanQuit();
m_ModList.setProfile(nullptr);
- NexusInterface::instance()->cleanup();
+ // NexusInterface::instance()->cleanup();
delete m_DirectoryStructure;
}
@@ -230,7 +246,8 @@ QString OrganizerCore::commitSettings(const QString &iniFile)
{
if (!shellRename(iniFile + ".new", iniFile, true, qApp->activeWindow())) {
DWORD err = ::GetLastError();
- // make a second attempt using qt functions but if that fails print the error from the first attempt
+ // make a second attempt using qt functions but if that fails print the
+ // error from the first attempt
if (!renameFile(iniFile + ".new", iniFile)) {
return windowsErrorString(err);
}
@@ -245,7 +262,8 @@ QSettings::Status OrganizerCore::storeSettings(const QString &fileName)
m_UserInterface->storeSettings(settings);
}
if (m_CurrentProfile != nullptr) {
- settings.setValue("selected_profile", m_CurrentProfile->name().toUtf8().constData());
+ settings.setValue("selected_profile",
+ m_CurrentProfile->name().toUtf8().constData());
}
settings.setValue("ask_for_nexuspw", m_AskForNexusPW);
@@ -265,7 +283,6 @@ QSettings::Status OrganizerCore::storeSettings(const QString &fileName)
settings.setValue("binary", item.m_BinaryInfo.absoluteFilePath());
settings.setValue("arguments", item.m_Arguments);
settings.setValue("workingDirectory", item.m_WorkingDirectory);
- settings.setValue("closeOnStart", item.m_CloseMO == ExecutableInfo::CloseMOStyle::DEFAULT_CLOSE);
settings.setValue("steamAppID", item.m_SteamAppID);
}
}
@@ -279,31 +296,41 @@ QSettings::Status OrganizerCore::storeSettings(const QString &fileName)
void OrganizerCore::storeSettings()
{
- QString iniFile = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::iniFileName());
+ QString iniFile = qApp->property("dataPath").toString() + "/"
+ + QString::fromStdWString(AppConfig::iniFileName());
if (QFileInfo(iniFile).exists()) {
if (!shellCopy(iniFile, iniFile + ".new", true, qApp->activeWindow())) {
- QMessageBox::critical(qApp->activeWindow(), tr("Failed to write settings"),
- tr("An error occured trying to update MO settings to %1: %2").arg(
- iniFile, windowsErrorString(::GetLastError())));
+ QMessageBox::critical(
+ qApp->activeWindow(), tr("Failed to write settings"),
+ tr("An error occured trying to update MO settings to %1: %2")
+ .arg(iniFile, windowsErrorString(::GetLastError())));
return;
}
}
- QSettings::Status result = storeSettings(iniFile + ".new");
+ QString writeTarget = iniFile + ".new";
+
+ QSettings::Status result = storeSettings(writeTarget);
if (result == QSettings::NoError) {
QString errMsg = commitSettings(iniFile);
if (!errMsg.isEmpty()) {
- qWarning("settings file not writable, may be locked by another application, trying direct write");
+ qWarning("settings file not writable, may be locked by another "
+ "application, trying direct write");
+ writeTarget = iniFile;
result = storeSettings(iniFile);
}
}
if (result != QSettings::NoError) {
- QString reason = result == QSettings::AccessError ? tr("File is write protected")
- : result == QSettings::FormatError ? tr("Invalid file format (probably a bug)")
- : tr("Unknown error %1").arg(result);
- QMessageBox::critical(qApp->activeWindow(), tr("Failed to write settings"),
- tr("An error occured trying to write back MO settings to %1: %2").arg(iniFile + ".new", reason));
+ QString reason = result == QSettings::AccessError
+ ? tr("File is write protected")
+ : result == QSettings::FormatError
+ ? tr("Invalid file format (probably a bug)")
+ : tr("Unknown error %1").arg(result);
+ QMessageBox::critical(
+ qApp->activeWindow(), tr("Failed to write settings"),
+ tr("An error occured trying to write back MO settings to %1: %2")
+ .arg(writeTarget, reason));
}
}
@@ -315,7 +342,9 @@ bool OrganizerCore::testForSteam()
bool success = false;
while (!success) {
processIDs.reset(new DWORD[currentSize]);
- if (!::EnumProcesses(processIDs.get(), currentSize * sizeof(DWORD), &bytesReturned)) {
+ if (!::EnumProcesses(processIDs.get(),
+ static_cast<DWORD>(currentSize) * sizeof(DWORD),
+ &bytesReturned)) {
qWarning("failed to determine if steam is running");
return true;
}
@@ -330,17 +359,19 @@ bool OrganizerCore::testForSteam()
for (unsigned int i = 0; i < bytesReturned / sizeof(DWORD); ++i) {
memset(processName, '\0', sizeof(TCHAR) * MAX_PATH);
if (processIDs[i] != 0) {
- HANDLE process = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processIDs[i]);
+ HANDLE process = ::OpenProcess(
+ PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processIDs[i]);
if (process != nullptr) {
HMODULE module;
DWORD ignore;
// first module in a process is always the binary
- if (EnumProcessModules(process, &module, sizeof(HMODULE) * 1, &ignore)) {
+ if (EnumProcessModules(process, &module, sizeof(HMODULE) * 1,
+ &ignore)) {
::GetModuleBaseName(process, module, processName, MAX_PATH);
- if ((_tcsicmp(processName, TEXT("steam.exe")) == 0) ||
- (_tcsicmp(processName, TEXT("steamservice.exe")) == 0)) {
+ if ((_tcsicmp(processName, TEXT("steam.exe")) == 0)
+ || (_tcsicmp(processName, TEXT("steamservice.exe")) == 0)) {
return true;
}
}
@@ -365,53 +396,64 @@ void OrganizerCore::updateExecutablesList(QSettings &settings)
int numCustomExecutables = settings.beginReadArray("customExecutables");
for (int i = 0; i < numCustomExecutables; ++i) {
settings.setArrayIndex(i);
- ExecutableInfo::CloseMOStyle closeMO =
- settings.value("closeOnStart").toBool() ? ExecutableInfo::CloseMOStyle::DEFAULT_CLOSE
- : ExecutableInfo::CloseMOStyle::DEFAULT_STAY;
Executable::Flags flags;
- if (settings.value("custom", true).toBool()) flags |= Executable::CustomExecutable;
- if (settings.value("toolbar", false).toBool()) flags |= Executable::ShowInToolbar;
- if (settings.value("ownicon", false).toBool()) flags |= Executable::UseApplicationIcon;
+ if (settings.value("custom", true).toBool())
+ flags |= Executable::CustomExecutable;
+ if (settings.value("toolbar", false).toBool())
+ flags |= Executable::ShowInToolbar;
+ if (settings.value("ownicon", false).toBool())
+ flags |= Executable::UseApplicationIcon;
- m_ExecutablesList.addExecutable(settings.value("title").toString(),
- settings.value("binary").toString(),
- settings.value("arguments").toString(),
- settings.value("workingDirectory", "").toString(),
- closeMO,
- settings.value("steamAppID", "").toString(),
- flags);
+ m_ExecutablesList.addExecutable(
+ settings.value("title").toString(), settings.value("binary").toString(),
+ settings.value("arguments").toString(),
+ settings.value("workingDirectory", "").toString(),
+ settings.value("steamAppID", "").toString(), flags);
}
settings.endArray();
- // TODO this has nothing to do with executables list move to an appropriate function!
- ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign(), managedGame());
+ // TODO this has nothing to do with executables list move to an appropriate
+ // function!
+ ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure,
+ m_Settings.displayForeign(), managedGame());
}
-void OrganizerCore::setUserInterface(IUserInterface *userInterface, QWidget *widget)
+void OrganizerCore::setUserInterface(IUserInterface *userInterface,
+ QWidget *widget)
{
storeSettings();
m_UserInterface = userInterface;
if (widget != nullptr) {
- connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), widget, SLOT(modlistChanged(QModelIndex, int)));
- connect(&m_ModList, SIGNAL(showMessage(QString)), widget, SLOT(showMessage(QString)));
- connect(&m_ModList, SIGNAL(modRenamed(QString,QString)), widget, SLOT(modRenamed(QString,QString)));
- connect(&m_ModList, SIGNAL(modUninstalled(QString)), widget, SLOT(modRemoved(QString)));
- connect(&m_ModList, SIGNAL(removeSelectedMods()), widget, SLOT(removeMod_clicked()));
- connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), widget, SLOT(displayColumnSelection(QPoint)));
- connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), widget, SLOT(fileMoved(QString, QString, QString)));
- connect(&m_ModList, SIGNAL(modorder_changed()), widget, SLOT(modorder_changed()));
- connect(&m_DownloadManager, SIGNAL(showMessage(QString)), widget, SLOT(showMessage(QString)));
+ connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), widget,
+ SLOT(modlistChanged(QModelIndex, int)));
+ connect(&m_ModList, SIGNAL(showMessage(QString)), widget,
+ SLOT(showMessage(QString)));
+ connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), widget,
+ SLOT(modRenamed(QString, QString)));
+ connect(&m_ModList, SIGNAL(modUninstalled(QString)), widget,
+ SLOT(modRemoved(QString)));
+ connect(&m_ModList, SIGNAL(removeSelectedMods()), widget,
+ SLOT(removeMod_clicked()));
+ connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), widget,
+ SLOT(displayColumnSelection(QPoint)));
+ connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), widget,
+ SLOT(fileMoved(QString, QString, QString)));
+ connect(&m_ModList, SIGNAL(modorder_changed()), widget,
+ SLOT(modorder_changed()));
+ connect(&m_DownloadManager, SIGNAL(showMessage(QString)), widget,
+ SLOT(showMessage(QString)));
}
m_InstallationManager.setParentWidget(widget);
m_Updater.setUserInterface(widget);
if (userInterface != nullptr) {
- // this currently wouldn't work reliably if the ui isn't initialized yet to display the result
+ // this currently wouldn't work reliably if the ui isn't initialized yet to
+ // display the result
if (isOnline() && !m_Settings.offlineMode()) {
m_Updater.testForUpdate();
} else {
@@ -422,21 +464,14 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface, QWidget *wid
void OrganizerCore::connectPlugins(PluginContainer *container)
{
- m_DownloadManager.setSupportedExtensions(m_InstallationManager.getSupportedExtensions());
+ m_DownloadManager.setSupportedExtensions(
+ m_InstallationManager.getSupportedExtensions());
m_PluginContainer = container;
if (!m_GameName.isEmpty()) {
m_GamePlugin = m_PluginContainer->managedGame(m_GameName);
emit managedGameChanged(m_GamePlugin);
}
- //Do this the hard way
- for (const IPluginGame * const game : container->plugins<IPluginGame>()) {
- QString n = game->gameShortName();
- if (game->gameShortName() == "Skyrim") {
- m_Updater.setNexusDownload(game);
- break;
- }
- }
}
void OrganizerCore::disconnectPlugins()
@@ -448,13 +483,13 @@ void OrganizerCore::disconnectPlugins()
m_PluginList.disconnectSlots();
m_Settings.clearPlugins();
- m_GamePlugin = nullptr;
+ m_GamePlugin = nullptr;
m_PluginContainer = nullptr;
}
-void OrganizerCore::setManagedGame(MOBase::IPluginGame const *game)
+void OrganizerCore::setManagedGame(MOBase::IPluginGame *game)
{
- m_GameName = game->gameName();
+ m_GameName = game->gameName();
m_GamePlugin = game;
qApp->setProperty("managed_game", QVariant::fromValue(m_GamePlugin));
emit managedGameChanged(m_GamePlugin);
@@ -467,10 +502,10 @@ Settings &OrganizerCore::settings()
bool OrganizerCore::nexusLogin(bool retry)
{
- NXMAccessManager *accessManager = NexusInterface::instance()->getAccessManager();
+ NXMAccessManager *accessManager
+ = NexusInterface::instance()->getAccessManager();
- if ((accessManager->loginAttempted()
- || accessManager->loggedIn())
+ if ((accessManager->loginAttempted() || accessManager->loggedIn())
&& !retry) {
// previous attempt, maybe even successful
return false;
@@ -536,10 +571,12 @@ void OrganizerCore::externalMessage(const QString &message)
}
}
-void OrganizerCore::downloadRequested(QNetworkReply *reply, int modID, const QString &fileName)
+void OrganizerCore::downloadRequested(QNetworkReply *reply, int modID,
+ const QString &fileName)
{
try {
- if (m_DownloadManager.addDownload(reply, QStringList(), fileName, modID, 0, new ModRepositoryFileInfo(modID))) {
+ if (m_DownloadManager.addDownload(reply, QStringList(), fileName, modID, 0,
+ new ModRepositoryFileInfo(modID))) {
MessageDialog::showMessage(tr("Download started"), qApp->activeWindow());
}
} catch (const std::exception &e) {
@@ -565,21 +602,62 @@ InstallationManager *OrganizerCore::installationManager()
return &m_InstallationManager;
}
+bool OrganizerCore::createDirectory(const QString &path) {
+ if (!QDir(path).exists() && !QDir().mkpath(path)) {
+ QMessageBox::critical(nullptr, QObject::tr("Error"),
+ QObject::tr("Failed to create \"%1\". Your user "
+ "account probably lacks permission.")
+ .arg(QDir::toNativeSeparators(path)));
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool OrganizerCore::bootstrap() {
+ return createDirectory(m_Settings.getProfileDirectory()) &&
+ createDirectory(m_Settings.getModDirectory()) &&
+ createDirectory(m_Settings.getDownloadDirectory()) &&
+ createDirectory(m_Settings.getOverwriteDirectory());
+}
+
void OrganizerCore::createDefaultProfile()
{
- QString profilesPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::profilesPath());
- if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size() == 0) {
+ QString profilesPath = settings().getProfileDirectory();
+ if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size()
+ == 0) {
Profile newProf("Default", managedGame(), false);
}
}
+void OrganizerCore::prepareVFS()
+{
+ m_USVFS.updateMapping(fileMapping(m_CurrentProfile->name(), QString()));
+}
+
+void OrganizerCore::setLogLevel(int logLevel) {
+ m_USVFS.setLogLevel(logLevel);
+}
+
void OrganizerCore::setCurrentProfile(const QString &profileName)
{
- if ((m_CurrentProfile != nullptr) &&
- (profileName == m_CurrentProfile->name())) {
+ if ((m_CurrentProfile != nullptr)
+ && (profileName == m_CurrentProfile->name())) {
return;
}
- QString profileDir = qApp->property("dataPath").toString() + "/" + ToQString(AppConfig::profilesPath()) + "/" + profileName;
+
+ QDir profileBaseDir(settings().getProfileDirectory());
+ QString profileDir = profileBaseDir.absoluteFilePath(profileName);
+
+ if (!QDir(profileDir).exists()) {
+ // selected profile doesn't exist. Ensure there is at least one profile,
+ // then pick any one
+ createDefaultProfile();
+
+ profileDir = profileBaseDir.absoluteFilePath(
+ profileBaseDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot).at(0));
+ }
+
Profile *newProfile = new Profile(QDir(profileDir), managedGame());
delete m_CurrentProfile;
@@ -592,7 +670,8 @@ void OrganizerCore::setCurrentProfile(const QString &profileName)
m_CurrentProfile->deactivateInvalidation();
}
- connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint)));
+ connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this,
+ SLOT(modStatusChanged(uint)));
refreshDirectoryStructure();
}
@@ -644,7 +723,10 @@ MOBase::IModInterface *OrganizerCore::createMod(GuessedValue<QString> &name)
m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
- QString targetDirectory = QDir::fromNativeSeparators(m_Settings.getModDirectory()).append("/").append(name);
+ QString targetDirectory
+ = QDir::fromNativeSeparators(m_Settings.getModDirectory())
+ .append("/")
+ .append(name);
QSettings settingsFile(targetDirectory + "/meta.ini", QSettings::IniFormat);
@@ -659,7 +741,8 @@ MOBase::IModInterface *OrganizerCore::createMod(GuessedValue<QString> &name)
settingsFile.endArray();
}
- return ModInfo::createFrom(QDir(targetDirectory), &m_DirectoryStructure).data();
+ return ModInfo::createFrom(QDir(targetDirectory), &m_DirectoryStructure)
+ .data();
}
bool OrganizerCore::removeMod(MOBase::IModInterface *mod)
@@ -672,37 +755,44 @@ bool OrganizerCore::removeMod(MOBase::IModInterface *mod)
}
}
-void OrganizerCore::modDataChanged(MOBase::IModInterface*)
+void OrganizerCore::modDataChanged(MOBase::IModInterface *)
{
refreshModList(false);
}
-QVariant OrganizerCore::pluginSetting(const QString &pluginName, const QString &key) const
+QVariant OrganizerCore::pluginSetting(const QString &pluginName,
+ const QString &key) const
{
return m_Settings.pluginSetting(pluginName, key);
}
-void OrganizerCore::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value)
+void OrganizerCore::setPluginSetting(const QString &pluginName,
+ const QString &key, const QVariant &value)
{
m_Settings.setPluginSetting(pluginName, key, value);
}
-QVariant OrganizerCore::persistent(const QString &pluginName, const QString &key, const QVariant &def) const
+QVariant OrganizerCore::persistent(const QString &pluginName,
+ const QString &key,
+ const QVariant &def) const
{
return m_Settings.pluginPersistent(pluginName, key, def);
}
-void OrganizerCore::setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync)
+void OrganizerCore::setPersistent(const QString &pluginName, const QString &key,
+ const QVariant &value, bool sync)
{
m_Settings.setPluginPersistent(pluginName, key, value, sync);
}
QString OrganizerCore::pluginDataPath() const
{
- return qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()) + "/data";
+ return qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())
+ + "/data";
}
-MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, const QString &initModName)
+MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName,
+ const QString &initModName)
{
if (m_CurrentProfile == nullptr) {
return nullptr;
@@ -716,18 +806,21 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, const
m_CurrentProfile->writeModlistNow();
m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) {
- MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow());
+ MessageDialog::showMessage(tr("Installation successful"),
+ qApp->activeWindow());
refreshModList();
int modIndex = ModInfo::getIndex(modName);
if (modIndex != UINT_MAX) {
ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
- if (hasIniTweaks
- && (m_UserInterface != nullptr)
+ if (hasIniTweaks && (m_UserInterface != nullptr)
&& (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"),
- tr("This mod contains ini tweaks. Do you want to configure them now?"),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) {
- m_UserInterface->displayModInformation(modInfo, modIndex, ModInfoDialog::TAB_INIFILES);
+ tr("This mod contains ini tweaks. Do you "
+ "want to configure them now?"),
+ QMessageBox::Yes | QMessageBox::No)
+ == QMessageBox::Yes)) {
+ m_UserInterface->displayModInformation(modInfo, modIndex,
+ ModInfoDialog::TAB_INIFILES);
}
m_ModInstalled(modName);
return modInfo.data();
@@ -736,7 +829,8 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, const
}
} else if (m_InstallationManager.wasCancelled()) {
QMessageBox::information(qApp->activeWindow(), tr("Installation cancelled"),
- tr("The mod was not installed completely."), QMessageBox::Ok);
+ tr("The mod was not installed completely."),
+ QMessageBox::Ok);
}
return nullptr;
}
@@ -745,8 +839,8 @@ void OrganizerCore::installDownload(int index)
{
try {
QString fileName = m_DownloadManager.getFilePath(index);
- int modID = m_DownloadManager.getModID(index);
- int fileID = m_DownloadManager.getFileInfo(index)->fileID;
+ int modID = m_DownloadManager.getModID(index);
+ int fileID = m_DownloadManager.getFileInfo(index)->fileID;
GuessedValue<QString> modName;
// see if there already are mods with the specified mod id
@@ -754,7 +848,8 @@ void OrganizerCore::installDownload(int index)
std::vector<ModInfo::Ptr> modInfo = ModInfo::getByModID(modID);
for (auto iter = modInfo.begin(); iter != modInfo.end(); ++iter) {
std::vector<ModInfo::EFlag> flags = (*iter)->getFlags();
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end()) {
+ if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP)
+ == flags.end()) {
modName.update((*iter)->name(), GUESS_PRESET);
(*iter)->saveMeta();
}
@@ -766,7 +861,8 @@ void OrganizerCore::installDownload(int index)
bool hasIniTweaks = false;
m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) {
- MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow());
+ MessageDialog::showMessage(tr("Installation successful"),
+ qApp->activeWindow());
refreshModList();
int modIndex = ModInfo::getIndex(modName);
@@ -774,12 +870,14 @@ void OrganizerCore::installDownload(int index)
ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
modInfo->addInstalledFile(modID, fileID);
- if (hasIniTweaks
- && m_UserInterface != nullptr
+ if (hasIniTweaks && m_UserInterface != nullptr
&& (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"),
- tr("This mod contains ini tweaks. Do you want to configure them now?"),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) {
- m_UserInterface->displayModInformation(modInfo, modIndex, ModInfoDialog::TAB_INIFILES);
+ tr("This mod contains ini tweaks. Do you "
+ "want to configure them now?"),
+ QMessageBox::Yes | QMessageBox::No)
+ == QMessageBox::Yes)) {
+ m_UserInterface->displayModInformation(modInfo, modIndex,
+ ModInfoDialog::TAB_INIFILES);
}
m_ModInstalled(modName);
@@ -790,8 +888,9 @@ void OrganizerCore::installDownload(int index)
emit modInstalled(modName);
} else if (m_InstallationManager.wasCancelled()) {
- QMessageBox::information(qApp->activeWindow(), tr("Installation cancelled"),
- tr("The mod was not installed completely."), QMessageBox::Ok);
+ QMessageBox::information(
+ qApp->activeWindow(), tr("Installation cancelled"),
+ tr("The mod was not installed completely."), QMessageBox::Ok);
}
} catch (const std::exception &e) {
reportError(e.what());
@@ -803,7 +902,8 @@ QString OrganizerCore::resolvePath(const QString &fileName) const
if (m_DirectoryStructure == nullptr) {
return QString();
}
- const FileEntry::Ptr file = m_DirectoryStructure->searchFile(ToWString(fileName), nullptr);
+ const FileEntry::Ptr file
+ = m_DirectoryStructure->searchFile(ToWString(fileName), nullptr);
if (file.get() != nullptr) {
return ToQString(file->getFullPath());
} else {
@@ -814,9 +914,10 @@ QString OrganizerCore::resolvePath(const QString &fileName) const
QStringList OrganizerCore::listDirectories(const QString &directoryName) const
{
QStringList result;
- DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(directoryName));
+ DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(
+ ToWString(directoryName));
if (dir != nullptr) {
- std::vector<DirectoryEntry*>::iterator current, end;
+ std::vector<DirectoryEntry *>::iterator current, end;
dir->getSubDirectories(current, end);
for (; current != end; ++current) {
result.append(ToQString((*current)->getName()));
@@ -825,10 +926,13 @@ QStringList OrganizerCore::listDirectories(const QString &directoryName) const
return result;
}
-QStringList OrganizerCore::findFiles(const QString &path, const std::function<bool (const QString &)> &filter) const
+QStringList OrganizerCore::findFiles(
+ const QString &path,
+ const std::function<bool(const QString &)> &filter) const
{
QStringList result;
- DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path));
+ DirectoryEntry *dir
+ = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path));
if (dir != nullptr) {
std::vector<FileEntry::Ptr> files = dir->getFiles();
foreach (FileEntry::Ptr file, files) {
@@ -845,12 +949,15 @@ QStringList OrganizerCore::findFiles(const QString &path, const std::function<bo
QStringList OrganizerCore::getFileOrigins(const QString &fileName) const
{
QStringList result;
- const FileEntry::Ptr file = m_DirectoryStructure->searchFile(ToWString(QFileInfo(fileName).fileName()), nullptr);
+ const FileEntry::Ptr file = m_DirectoryStructure->searchFile(
+ ToWString(QFileInfo(fileName).fileName()), nullptr);
if (file.get() != nullptr) {
- result.append(ToQString(m_DirectoryStructure->getOriginByID(file->getOrigin()).getName()));
+ result.append(ToQString(
+ m_DirectoryStructure->getOriginByID(file->getOrigin()).getName()));
foreach (int i, file->getAlternatives()) {
- result.append(ToQString(m_DirectoryStructure->getOriginByID(i).getName()));
+ result.append(
+ ToQString(m_DirectoryStructure->getOriginByID(i).getName()));
}
} else {
qDebug("%s not found", qPrintable(fileName));
@@ -858,20 +965,27 @@ QStringList OrganizerCore::getFileOrigins(const QString &fileName) const
return result;
}
-QList<MOBase::IOrganizer::FileInfo> OrganizerCore::findFileInfos(const QString &path, const std::function<bool (const MOBase::IOrganizer::FileInfo &)> &filter) const
+QList<MOBase::IOrganizer::FileInfo> OrganizerCore::findFileInfos(
+ const QString &path,
+ const std::function<bool(const MOBase::IOrganizer::FileInfo &)> &filter)
+ const
{
QList<IOrganizer::FileInfo> result;
- DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path));
+ DirectoryEntry *dir
+ = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path));
if (dir != nullptr) {
std::vector<FileEntry::Ptr> files = dir->getFiles();
foreach (FileEntry::Ptr file, files) {
IOrganizer::FileInfo info;
- info.filePath = ToQString(file->getFullPath());
+ info.filePath = ToQString(file->getFullPath());
bool fromArchive = false;
- info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(file->getOrigin(fromArchive)).getName()));
+ info.origins.append(ToQString(
+ m_DirectoryStructure->getOriginByID(file->getOrigin(fromArchive))
+ .getName()));
info.archive = fromArchive ? ToQString(file->getArchive()) : "";
foreach (int idx, file->getAlternatives()) {
- info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(idx).getName()));
+ info.origins.append(
+ ToQString(m_DirectoryStructure->getOriginByID(idx).getName()));
}
if (filter(info)) {
@@ -907,7 +1021,7 @@ QStringList OrganizerCore::modsSortedByProfilePriority() const
return res;
}
-void OrganizerCore::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir &currentDirectory, bool closeAfterStart, const QString &steamAppID)
+void OrganizerCore::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir &currentDirectory, const QString &steamAppID, const QString &customOverwrite)
{
if (m_UserInterface != nullptr) {
m_UserInterface->lock();
@@ -916,54 +1030,58 @@ void OrganizerCore::spawnBinary(const QFileInfo &binary, const QString &argument
if (m_UserInterface != nullptr) { m_UserInterface->unlock(); }
});
- HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID);
+ HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, customOverwrite);
if (processHandle != INVALID_HANDLE_VALUE) {
- if (closeAfterStart && (m_UserInterface != nullptr)) {
- m_UserInterface->closeWindow();
- } else {
+ DWORD processExitCode;
+ (void)waitForProcessCompletion(processHandle, &processExitCode);
- DWORD processExitCode;
- (void)waitForProcessCompletion(processHandle, &processExitCode);
+ refreshDirectoryStructure();
+ // need to remove our stored load order because it may be outdated if a foreign tool changed the
+ // file time. After removing that file, refreshESPList will use the file time as the order
+ if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) {
+ qDebug("removing loadorder.txt");
+ QFile::remove(m_CurrentProfile->getLoadOrderFileName());
+ }
+ refreshDirectoryStructure();
- refreshDirectoryStructure();
- // need to remove our stored load order because it may be outdated if a foreign tool changed the
- // file time. After removing that file, refreshESPList will use the file time as the order
- if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) {
- qDebug("removing loadorder.txt");
- QFile::remove(m_CurrentProfile->getLoadOrderFileName());
- }
- refreshESPList();
- if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) {
- // the load order should have been retrieved from file time, now save it to our own format
- savePluginList();
- }
+ refreshESPList();
+ savePluginList();
- //These callbacks should not fiddle with directoy structure and ESPs.
- m_FinishedRun(binary.absoluteFilePath(), processExitCode);
- }
+ //These callbacks should not fiddle with directoy structure and ESPs.
+ m_FinishedRun(binary.absoluteFilePath(), processExitCode);
}
}
-HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName,
- const QDir &currentDirectory, const QString &steamAppID)
+HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary,
+ const QString &arguments,
+ const QString &profileName,
+ const QDir &currentDirectory,
+ const QString &steamAppID,
+ const QString &customOverwrite)
{
prepareStart();
if (!binary.exists()) {
- reportError(tr("Executable \"%1\" not found").arg(binary.absoluteFilePath()));
+ reportError(
+ tr("Executable \"%1\" not found").arg(binary.absoluteFilePath()));
return INVALID_HANDLE_VALUE;
}
if (!steamAppID.isEmpty()) {
::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str());
} else {
- ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(m_Settings.getSteamAppID()).c_str());
+ ::SetEnvironmentVariableW(L"SteamAPPId",
+ ToWString(m_Settings.getSteamAppID()).c_str());
}
-
- //This could possibly be extracted somewhere else but it's probably for when
- //we have more than one provider of game registration.
- if (QFileInfo(managedGame()->gameDirectory().absoluteFilePath("steam_api.dll")).exists()
+ // This could possibly be extracted somewhere else but it's probably for when
+ // we have more than one provider of game registration.
+ if ((QFileInfo(
+ managedGame()->gameDirectory().absoluteFilePath("steam_api.dll"))
+ .exists()
+ || QFileInfo(managedGame()->gameDirectory().absoluteFilePath(
+ "steam_api64.dll"))
+ .exists())
&& (m_Settings.getLoadMechanism() == LoadMechanism::LOAD_MODORGANIZER)) {
if (!testForSteam()) {
QWidget *window = qApp->activeWindow();
@@ -992,17 +1110,51 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, const QString &
// TODO: should also pass arguments
if (m_AboutToRun(binary.absoluteFilePath())) {
- return startBinary(binary, arguments, profileName, m_Settings.logLevel(), currentDirectory, true);
+ try {
+ m_USVFS.updateMapping(fileMapping(profileName, customOverwrite));
+ } catch (const std::exception &e) {
+ QMessageBox::warning(qApp->activeWindow(), tr("Error"), e.what());
+ return INVALID_HANDLE_VALUE;
+ }
+
+ QString modsPath = settings().getModDirectory();
+
+ QString binPath = binary.absoluteFilePath();
+ if (binPath.startsWith(modsPath, Qt::CaseInsensitive)) {
+ // binary was installed as a MO mod. Need to start it through a (hooked)
+ // proxy to ensure pathes are correct
+
+ QString cwdPath = currentDirectory.absolutePath();
+
+ int binOffset = binPath.indexOf('/', modsPath.length() + 1);
+ int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1);
+ QString dataBinPath = m_GamePlugin->dataDirectory().absolutePath()
+ + binPath.mid(binOffset, -1);
+ QString dataCwd = m_GamePlugin->dataDirectory().absolutePath()
+ + cwdPath.mid(cwdOffset, -1);
+ QString cmdline
+ = QString("launch \"%1\" \"%2\" %3")
+ .arg(QDir::toNativeSeparators(dataCwd),
+ QDir::toNativeSeparators(dataBinPath), arguments);
+ return startBinary(QFileInfo(QCoreApplication::applicationFilePath()),
+ cmdline, QCoreApplication::applicationDirPath(), true);
+ } else {
+ return startBinary(binary, arguments, currentDirectory, true);
+ }
} else {
- qDebug("start of \"%s\" canceled by plugin", qPrintable(binary.absoluteFilePath()));
+ qDebug("start of \"%s\" canceled by plugin",
+ qPrintable(binary.absoluteFilePath()));
return INVALID_HANDLE_VALUE;
}
}
-HANDLE OrganizerCore::startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile)
+HANDLE OrganizerCore::startApplication(const QString &executable,
+ const QStringList &args,
+ const QString &cwd,
+ const QString &profile)
{
QFileInfo binary;
- QString arguments = args.join(" ");
+ QString arguments = args.join(" ");
QString currentDirectory = cwd;
QString profileName = profile;
if (profile.length() == 0) {
@@ -1013,13 +1165,15 @@ HANDLE OrganizerCore::startApplication(const QString &executable, const QStringL
}
}
QString steamAppID;
+ QString customOverwrite;
if (executable.contains('\\') || executable.contains('/')) {
// file path
binary = QFileInfo(executable);
if (binary.isRelative()) {
// relative path, should be relative to game directory
- binary = QFileInfo(managedGame()->gameDirectory().absoluteFilePath(executable));
+ binary = QFileInfo(
+ managedGame()->gameDirectory().absoluteFilePath(executable));
}
if (cwd.length() == 0) {
currentDirectory = binary.absolutePath();
@@ -1027,7 +1181,10 @@ HANDLE OrganizerCore::startApplication(const QString &executable, const QStringL
try {
const Executable &exe = m_ExecutablesList.findByBinary(binary);
steamAppID = exe.m_SteamAppID;
- } catch (const std::runtime_error&) {
+ customOverwrite
+ = m_CurrentProfile->setting("custom_overwrites", exe.m_Title)
+ .toString();
+ } catch (const std::runtime_error &) {
// nop
}
} else {
@@ -1035,6 +1192,9 @@ HANDLE OrganizerCore::startApplication(const QString &executable, const QStringL
try {
const Executable &exe = m_ExecutablesList.find(executable);
steamAppID = exe.m_SteamAppID;
+ customOverwrite
+ = m_CurrentProfile->setting("custom_overwrites", exe.m_Title)
+ .toString();
if (arguments == "") {
arguments = exe.m_Arguments;
}
@@ -1042,13 +1202,15 @@ HANDLE OrganizerCore::startApplication(const QString &executable, const QStringL
if (cwd.length() == 0) {
currentDirectory = exe.m_WorkingDirectory;
}
- } catch (const std::runtime_error&) {
- qWarning("\"%s\" not set up as executable", executable.toUtf8().constData());
+ } catch (const std::runtime_error &) {
+ qWarning("\"%s\" not set up as executable",
+ executable.toUtf8().constData());
binary = QFileInfo(executable);
}
}
- return spawnBinaryDirect(binary, arguments, profileName, currentDirectory, steamAppID);
+ return spawnBinaryDirect(binary, arguments, profileName, currentDirectory,
+ steamAppID, customOverwrite);
}
bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode)
@@ -1066,46 +1228,52 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode)
bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode)
{
- bool isJobHandle = true;
-
- ULONG lastProcessID = ULONG_MAX;
HANDLE processHandle = handle;
+ static const DWORD maxCount = 5;
+ size_t numProcesses = maxCount;
+ LPDWORD processes = new DWORD[maxCount];
+
+ DWORD currentProcess = 0UL;
+ bool tryAgain = true;
+
DWORD res;
- //Wait for a an event on the handle, a key press, mouse click or timeout
- while (res = ::MsgWaitForMultipleObjects(1, &handle, false, 500, QS_KEY | QS_MOUSE),
- (res != WAIT_FAILED && res != WAIT_OBJECT_0
- && (m_UserInterface == nullptr || !m_UserInterface->unlockClicked()))) {
- if (isJobHandle) {
- DWORD retLen;
- JOBOBJECT_BASIC_PROCESS_ID_LIST info;
- if (::QueryInformationJobObject(handle, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) {
- if (info.NumberOfProcessIdsInList == 0) {
- // fake signaled state
- res = WAIT_OBJECT_0;
- break;
- } else {
- // this is indeed a job handle. Figure out one of the process handles as well.
- if (lastProcessID != info.ProcessIdList[0]) {
- lastProcessID = info.ProcessIdList[0];
- if (m_UserInterface != nullptr) {
- m_UserInterface->setProcessName(ToQString(getProcessName(lastProcessID)));
- }
- if (processHandle != handle) {
- ::CloseHandle(processHandle);
- }
- processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, lastProcessID);
- }
- }
+ // Wait for a an event on the handle, a key press, mouse click or timeout
+ while (
+ res = ::MsgWaitForMultipleObjects(1, &handle, false, 500,
+ QS_KEY | QS_MOUSE),
+ (MOBase::isOneOf(res, {WAIT_FAILED, WAIT_OBJECT_0}) &&
+ ((m_UserInterface == nullptr) || !m_UserInterface->unlockClicked()))) {
+
+ if (!::GetVFSProcessList(&numProcesses, processes)) {
+ break;
+ }
+
+ bool found = false;
+ size_t count =
+ std::min<size_t>(static_cast<size_t>(maxCount), numProcesses);
+ for (size_t i = 0; i < count; ++i) {
+ std::wstring processName = getProcessName(processes[i]);
+ if (!boost::starts_with(processName, L"ModOrganizer.exe")) {
+ currentProcess = processes[i];
+ m_UserInterface->setProcessName(QString::fromStdWString(processName));
+ processHandle = ::OpenProcess(SYNCHRONIZE, FALSE, currentProcess);
+ found = true;
+ }
+ }
+ if (!found) {
+ // it's possible the previous process has deregistered before
+ // the new one has registered, so we should try one more time
+ // with a little delay
+ if (tryAgain) {
+ tryAgain = false;
+ QThread::msleep(500);
+ continue;
} else {
- // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there
- // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running.
- // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without
- // the right to break out.
- if (::GetLastError() != ERROR_MORE_DATA) {
- isJobHandle = false;
- }
+ break;
}
+ } else {
+ tryAgain = true;
}
// keep processing events so the app doesn't appear dead
@@ -1130,19 +1298,22 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode)
return res == WAIT_OBJECT_0;
}
-bool OrganizerCore::onAboutToRun(const std::function<bool (const QString &)> &func)
+bool OrganizerCore::onAboutToRun(
+ const std::function<bool(const QString &)> &func)
{
auto conn = m_AboutToRun.connect(func);
return conn.connected();
}
-bool OrganizerCore::onFinishedRun(const std::function<void (const QString &, unsigned int)> &func)
+bool OrganizerCore::onFinishedRun(
+ const std::function<void(const QString &, unsigned int)> &func)
{
auto conn = m_FinishedRun.connect(func);
return conn.connected();
}
-bool OrganizerCore::onModInstalled(const std::function<void (const QString &)> &func)
+bool OrganizerCore::onModInstalled(
+ const std::function<void(const QString &)> &func)
{
auto conn = m_ModInstalled.connect(func);
return conn.connected();
@@ -1154,7 +1325,8 @@ void OrganizerCore::refreshModList(bool saveChanges)
if (saveChanges) {
m_CurrentProfile->modlistWriter().writeImmediately(true);
}
- ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign(), managedGame());
+ ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure,
+ m_Settings.displayForeign(), managedGame());
m_CurrentProfile->refreshModStatus();
@@ -1166,18 +1338,18 @@ void OrganizerCore::refreshModList(bool saveChanges)
void OrganizerCore::refreshESPList()
{
if (m_DirectoryUpdate) {
- // don't mess up the esp list if we're currently updating the directory structure
- m_PostRefreshTasks.append([this] () { this->refreshESPList(); });
+ // don't mess up the esp list if we're currently updating the directory
+ // structure
+ m_PostRefreshTasks.append([this]() {
+ this->refreshESPList();
+ });
return;
}
m_CurrentProfile->modlistWriter().write();
// clear list
try {
- m_PluginList.refresh(m_CurrentProfile->name(),
- *m_DirectoryStructure,
- m_CurrentProfile->getPluginsFileName(),
- m_CurrentProfile->getLoadOrderFileName(),
+ m_PluginList.refresh(m_CurrentProfile->name(), *m_DirectoryStructure,
m_CurrentProfile->getLockedOrderFileName());
} catch (const std::exception &e) {
reportError(tr("Failed to refresh list of esps: %1").arg(e.what()));
@@ -1191,8 +1363,10 @@ void OrganizerCore::refreshBSAList()
if (archives != nullptr) {
m_ArchivesInit = false;
- // default archives are the ones enabled outside MO. if the list can't be found (which might
- // happen if ini files are missing) use hard-coded defaults (preferrably the same the game would use)
+ // default archives are the ones enabled outside MO. if the list can't be
+ // found (which might
+ // happen if ini files are missing) use hard-coded defaults (preferrably the
+ // same the game would use)
m_DefaultArchives = archives->archives(m_CurrentProfile);
if (m_DefaultArchives.length() == 0) {
m_DefaultArchives = archives->vanillaArchives();
@@ -1200,16 +1374,12 @@ void OrganizerCore::refreshBSAList()
m_ActiveArchives.clear();
- auto iter = enabledArchives();
+ auto iter = enabledArchives();
m_ActiveArchives = toStringList(iter.begin(), iter.end());
if (m_ActiveArchives.isEmpty()) {
m_ActiveArchives = m_DefaultArchives;
}
- if (m_UserInterface != nullptr) {
- m_UserInterface->updateBSAList(m_DefaultArchives, m_ActiveArchives);
- }
-
m_ArchivesInit = true;
}
}
@@ -1219,17 +1389,19 @@ void OrganizerCore::refreshLists()
if ((m_CurrentProfile != nullptr) && m_DirectoryStructure->isPopulated()) {
refreshESPList();
refreshBSAList();
- } // no point in refreshing lists if no files have been added to the directory tree
+ } // no point in refreshing lists if no files have been added to the directory
+ // tree
}
void OrganizerCore::updateModActiveState(int index, bool active)
{
ModInfo::Ptr modInfo = ModInfo::getByIndex(index);
QDir dir(modInfo->absolutePath());
- for (const QString &esm : dir.entryList(QStringList() << "*.esm", QDir::Files)) {
+ for (const QString &esm :
+ dir.entryList(QStringList() << "*.esm", QDir::Files)) {
m_PluginList.enableESP(esm, active);
}
- int enabled = 0;
+ int enabled = 0;
QStringList esps = dir.entryList(QStringList() << "*.esp", QDir::Files);
for (const QString &esp : esps) {
const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esp));
@@ -1245,49 +1417,50 @@ void OrganizerCore::updateModActiveState(int index, bool active)
}
}
if (active && (enabled > 1)) {
- MessageDialog::showMessage(tr("Multiple esps activated, please check that they don't conflict."), qApp->activeWindow());
+ MessageDialog::showMessage(
+ tr("Multiple esps activated, please check that they don't conflict."),
+ qApp->activeWindow());
}
m_PluginList.refreshLoadOrder();
// immediately save affected lists
m_PluginListsWriter.writeImmediately(false);
}
-void OrganizerCore::updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo)
+void OrganizerCore::updateModInDirectoryStructure(unsigned int index,
+ ModInfo::Ptr modInfo)
{
// add files of the bsa to the directory structure
- m_DirectoryRefresher.addModFilesToStructure(m_DirectoryStructure
- , modInfo->name()
- , m_CurrentProfile->getModPriority(index)
- , modInfo->absolutePath()
- , modInfo->stealFiles()
- );
+ m_DirectoryRefresher.addModFilesToStructure(
+ m_DirectoryStructure, modInfo->name(),
+ m_CurrentProfile->getModPriority(index), modInfo->absolutePath(),
+ modInfo->stealFiles());
DirectoryRefresher::cleanStructure(m_DirectoryStructure);
// need to refresh plugin list now so we can activate esps
refreshESPList();
- // activate all esps of the specified mod so the bsas get activated along with it
+ // activate all esps of the specified mod so the bsas get activated along with
+ // it
updateModActiveState(index, true);
- // now we need to refresh the bsa list and save it so there is no confusion about what archives are avaiable and active
+ // now we need to refresh the bsa list and save it so there is no confusion
+ // about what archives are avaiable and active
refreshBSAList();
- if (m_UserInterface != nullptr) {
- m_UserInterface->archivesWriter().write();
- }
+
std::vector<QString> archives = enabledArchives();
- m_DirectoryRefresher.setMods(m_CurrentProfile->getActiveMods(),
- std::set<QString>(archives.begin(), archives.end()));
+ m_DirectoryRefresher.setMods(
+ m_CurrentProfile->getActiveMods(),
+ std::set<QString>(archives.begin(), archives.end()));
// finally also add files from bsas to the directory structure
- m_DirectoryRefresher.addModBSAToStructure(m_DirectoryStructure
- , modInfo->name()
- , m_CurrentProfile->getModPriority(index)
- , modInfo->absolutePath()
- , modInfo->archives()
- );
+ m_DirectoryRefresher.addModBSAToStructure(
+ m_DirectoryStructure, modInfo->name(),
+ m_CurrentProfile->getModPriority(index), modInfo->absolutePath(),
+ modInfo->archives());
}
void OrganizerCore::requestDownload(const QUrl &url, QNetworkReply *reply)
{
if (m_PluginContainer != nullptr) {
- for (IPluginModPage *modPage : m_PluginContainer->plugins<MOBase::IPluginModPage>()) {
+ for (IPluginModPage *modPage :
+ m_PluginContainer->plugins<MOBase::IPluginModPage>()) {
ModRepositoryFileInfo *fileInfo = new ModRepositoryFileInfo();
if (modPage->handlesDownload(url, reply->url(), *fileInfo)) {
fileInfo->repository = modPage->name();
@@ -1299,7 +1472,7 @@ void OrganizerCore::requestDownload(const QUrl &url, QNetworkReply *reply)
// no mod found that could handle the download. Is it a nexus mod?
if (url.host() == "www.nexusmods.com") {
- int modID = 0;
+ int modID = 0;
int fileID = 0;
QRegExp modExp("mods/(\\d+)");
if (modExp.indexIn(url.toString()) != -1) {
@@ -1309,13 +1482,18 @@ void OrganizerCore::requestDownload(const QUrl &url, QNetworkReply *reply)
if (fileExp.indexIn(reply->url().toString()) != -1) {
fileID = fileExp.cap(1).toInt();
}
- m_DownloadManager.addDownload(reply, new ModRepositoryFileInfo(modID, fileID));
+ m_DownloadManager.addDownload(reply,
+ new ModRepositoryFileInfo(modID, fileID));
} else {
if (QMessageBox::question(qApp->activeWindow(), tr("Download?"),
- tr("A download has been started but no installed page plugin recognizes it.\n"
- "If you download anyway no information (i.e. version) will be associated with the download.\n"
- "Continue?"),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+ tr("A download has been started but no installed "
+ "page plugin recognizes it.\n"
+ "If you download anyway no information (i.e. "
+ "version) will be associated with the "
+ "download.\n"
+ "Continue?"),
+ QMessageBox::Yes | QMessageBox::No)
+ == QMessageBox::Yes) {
m_DownloadManager.addDownload(reply, new ModRepositoryFileInfo());
}
}
@@ -1359,10 +1537,11 @@ void OrganizerCore::refreshDirectoryStructure()
m_CurrentProfile->modlistWriter().writeImmediately(true);
m_DirectoryUpdate = true;
- std::vector<std::tuple<QString, QString, int> > activeModList = m_CurrentProfile->getActiveMods();
+ std::vector<std::tuple<QString, QString, int>> activeModList
+ = m_CurrentProfile->getActiveMods();
auto archives = enabledArchives();
- m_DirectoryRefresher.setMods(activeModList,
- std::set<QString>(archives.begin(), archives.end()));
+ m_DirectoryRefresher.setMods(
+ activeModList, std::set<QString>(archives.begin(), archives.end()));
QTimer::singleShot(0, &m_DirectoryRefresher, SLOT(refresh()));
}
@@ -1376,13 +1555,11 @@ void OrganizerCore::directory_refreshed()
std::swap(m_DirectoryStructure, newStructure);
delete newStructure;
} else {
- // TODO: don't know why this happens, this slot seems to get called twice with only one emit
+ // TODO: don't know why this happens, this slot seems to get called twice
+ // with only one emit
return;
}
m_DirectoryUpdate = false;
- if (m_CurrentProfile != nullptr) {
- refreshLists();
- }
for (int i = 0; i < m_ModList.rowCount(); ++i) {
ModInfo::Ptr modInfo = ModInfo::getByIndex(i);
@@ -1391,12 +1568,19 @@ void OrganizerCore::directory_refreshed()
for (auto task : m_PostRefreshTasks) {
task();
}
+ m_PostRefreshTasks.clear();
+
+ if (m_CurrentProfile != nullptr) {
+ refreshLists();
+ }
}
void OrganizerCore::profileRefresh()
{
- // have to refresh mods twice (again in refreshModList), otherwise the refresh isn't complete. Not sure why
- ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign(), managedGame());
+ // have to refresh mods twice (again in refreshModList), otherwise the refresh
+ // isn't complete. Not sure why
+ ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure,
+ m_Settings.displayForeign(), managedGame());
m_CurrentProfile->refreshModStatus();
refreshModList();
@@ -1412,12 +1596,10 @@ void OrganizerCore::modStatusChanged(unsigned int index)
updateModActiveState(index, false);
refreshESPList();
if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) {
- FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name()));
+ FilesOrigin &origin
+ = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name()));
origin.enable(false);
}
- if (m_UserInterface != nullptr) {
- m_UserInterface->archivesWriter().write();
- }
}
modInfo->clearCaches();
@@ -1425,14 +1607,16 @@ void OrganizerCore::modStatusChanged(unsigned int index)
ModInfo::Ptr modInfo = ModInfo::getByIndex(i);
int priority = m_CurrentProfile->getModPriority(i);
if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) {
- // priorities in the directory structure are one higher because data is 0
- m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())).setPriority(priority + 1);
+ // priorities in the directory structure are one higher because data is
+ // 0
+ m_DirectoryStructure->getOriginByName(ToWString(modInfo->name()))
+ .setPriority(priority + 1);
}
}
m_DirectoryStructure->getFileRegister()->sortOrigins();
refreshLists();
- } catch (const std::exception& e) {
+ } catch (const std::exception &e) {
reportError(tr("failed to update mod list: %1").arg(e.what()));
}
}
@@ -1442,7 +1626,7 @@ void OrganizerCore::loginSuccessful(bool necessary)
if (necessary) {
MessageDialog::showMessage(tr("login successful"), qApp->activeWindow());
}
- foreach (QString url, m_PendingDownloads) {
+ for (QString url : m_PendingDownloads) {
downloadRequestedNXM(url);
}
m_PendingDownloads.clear();
@@ -1464,39 +1648,50 @@ void OrganizerCore::loginSuccessfulUpdate(bool necessary)
void OrganizerCore::loginFailed(const QString &message)
{
- if (QMessageBox::question(qApp->activeWindow(), tr("Login failed"), tr("Login failed, try again?")) == QMessageBox::Yes) {
+ if (QMessageBox::question(qApp->activeWindow(), tr("Login failed"),
+ tr("Login failed, try again?"))
+ == QMessageBox::Yes) {
if (nexusLogin(true)) {
return;
}
}
if (!m_PendingDownloads.isEmpty()) {
- MessageDialog::showMessage(tr("login failed: %1. Download will not be associated with an account").arg(message), qApp->activeWindow());
+ MessageDialog::showMessage(
+ tr("login failed: %1. Download will not be associated with an account")
+ .arg(message),
+ qApp->activeWindow());
for (QString url : m_PendingDownloads) {
downloadRequestedNXM(url);
}
m_PendingDownloads.clear();
} else {
- MessageDialog::showMessage(tr("login failed: %1").arg(message), qApp->activeWindow());
+ MessageDialog::showMessage(tr("login failed: %1").arg(message),
+ qApp->activeWindow());
m_PostLoginTasks.clear();
}
NexusInterface::instance()->loginCompleted();
}
-
void OrganizerCore::loginFailedUpdate(const QString &message)
{
- MessageDialog::showMessage(tr("login failed: %1. You need to log-in with Nexus to update MO.").arg(message), qApp->activeWindow());
+ MessageDialog::showMessage(
+ tr("login failed: %1. You need to log-in with Nexus to update MO.")
+ .arg(message),
+ qApp->activeWindow());
}
void OrganizerCore::syncOverwrite()
{
- unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool {
+ unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool {
std::vector<ModInfo::EFlag> flags = mod->getFlags();
- return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); });
+ return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE)
+ != flags.end();
+ });
ModInfo::Ptr modInfo = ModInfo::getByIndex(overwriteIndex);
- SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure, qApp->activeWindow());
+ SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure,
+ qApp->activeWindow());
if (syncDialog.exec() == QDialog::Accepted) {
syncDialog.apply(QDir::fromNativeSeparators(m_Settings.getModDirectory()));
modInfo->testValid();
@@ -1529,8 +1724,13 @@ QString OrganizerCore::fullDescription(unsigned int key) const
{
switch (key) {
case PROBLEM_TOOMANYPLUGINS: {
- return tr("The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or "
- "merge some plugins into one. You can find a guide here: <a href=\"http://wiki.step-project.com/Guide:Merging_Plugins\">http://wiki.step-project.com/Guide:Merging_Plugins</a>");
+ return tr("The game doesn't allow more than 255 active plugins "
+ "(including the official ones) to be loaded. You have to "
+ "disable some unused plugins or "
+ "merge some plugins into one. You can find a guide here: <a "
+ "href=\"http://wiki.step-project.com/"
+ "Guide:Merging_Plugins\">http://wiki.step-project.com/"
+ "Guide:Merging_Plugins</a>");
} break;
default: {
return tr("Description missing");
@@ -1556,9 +1756,6 @@ bool OrganizerCore::saveCurrentLists()
try {
savePluginList();
- if (m_UserInterface != nullptr) {
- m_UserInterface->archivesWriter().write();
- }
} catch (const std::exception &e) {
reportError(tr("failed to save load order: %1").arg(e.what()));
}
@@ -1570,18 +1767,19 @@ void OrganizerCore::savePluginList()
{
if (m_DirectoryUpdate) {
// delay save till after directory update
- m_PostRefreshTasks.append([&] () { this->savePluginList(); });
+ m_PostRefreshTasks.append([this]() {
+ this->savePluginList();
+ });
return;
}
- m_PluginList.saveTo(m_CurrentProfile->getPluginsFileName(),
- m_CurrentProfile->getLoadOrderFileName(),
- m_CurrentProfile->getLockedOrderFileName(),
+ m_PluginList.saveTo(m_CurrentProfile->getLockedOrderFileName(),
m_CurrentProfile->getDeleterFileName(),
m_Settings.hideUncheckedPlugins());
m_PluginList.saveLoadOrder(*m_DirectoryStructure);
}
-void OrganizerCore::prepareStart() {
+void OrganizerCore::prepareStart()
+{
if (m_CurrentProfile == nullptr) {
return;
}
@@ -1592,21 +1790,87 @@ void OrganizerCore::prepareStart() {
storeSettings();
}
-/*
-std::vector<std::pair<QString, QString>> OrganizerCore::fileMapping()
+std::vector<Mapping> OrganizerCore::fileMapping(const QString &profileName,
+ const QString &customOverwrite)
{
- return fileMapping(managedGame()->dataDirectory().absolutePath(),
- directoryStructure(),
- directoryStructure());
+ // need to wait until directory structure
+ while (m_DirectoryUpdate) {
+ ::Sleep(100);
+ QCoreApplication::processEvents();
+ }
+
+ IPluginGame *game = qApp->property("managed_game").value<IPluginGame *>();
+ Profile profile(QDir(m_Settings.getProfileDirectory() + "/" + profileName),
+ game);
+
+ MappingType result;
+
+ QString dataPath
+ = QDir::toNativeSeparators(game->dataDirectory().absolutePath());
+
+ bool overwriteActive = false;
+
+ for (auto mod : profile.getActiveMods()) {
+ if (std::get<0>(mod).compare("overwrite", Qt::CaseInsensitive) == 0) {
+ continue;
+ }
+
+ unsigned int modIndex = ModInfo::getIndex(std::get<0>(mod));
+ ModInfo::Ptr modPtr = ModInfo::getByIndex(modIndex);
+
+ bool createTarget = customOverwrite == std::get<0>(mod);
+
+ overwriteActive |= createTarget;
+
+ if (modPtr->isRegular()) {
+ result.insert(result.end(), {QDir::toNativeSeparators(std::get<1>(mod)),
+ dataPath, true, createTarget});
+ }
+ }
+
+ if (!overwriteActive && !customOverwrite.isEmpty()) {
+ throw MyException(tr("The designated write target \"%1\" is not enabled.")
+ .arg(customOverwrite));
+ }
+
+ if (m_CurrentProfile->localSavesEnabled()) {
+ LocalSavegames *localSaves = game->feature<LocalSavegames>();
+ if (localSaves != nullptr) {
+ MappingType saveMap
+ = localSaves->mappings(currentProfile()->absolutePath() + "/saves");
+ result.reserve(result.size() + saveMap.size());
+ result.insert(result.end(), saveMap.begin(), saveMap.end());
+ } else {
+ qWarning("local save games not supported by this game plugin");
+ }
+ }
+
+ result.insert(result.end(), {
+ QDir::toNativeSeparators(m_Settings.getOverwriteDirectory()),
+ dataPath,
+ true,
+ customOverwrite.isEmpty()
+ });
+
+ for (MOBase::IPluginFileMapper *mapper :
+ m_PluginContainer->plugins<MOBase::IPluginFileMapper>()) {
+ IPlugin *plugin = dynamic_cast<IPlugin *>(mapper);
+ if (plugin->isActive()) {
+ MappingType pluginMap = mapper->mappings();
+ result.reserve(result.size() + pluginMap.size());
+ result.insert(result.end(), pluginMap.begin(), pluginMap.end());
+ }
+ }
+
+ return result;
}
-std::vector<std::pair<QString, QString>> OrganizerCore::fileMapping(
- const QString &dataPath,
- const DirectoryEntry *base,
- const DirectoryEntry *directoryEntry)
+std::vector<Mapping> OrganizerCore::fileMapping(
+ const QString &dataPath, const QString &relPath, const DirectoryEntry *base,
+ const DirectoryEntry *directoryEntry, int createDestination)
{
- std::vector<std::pair<QString, QString>> result;
+ std::vector<Mapping> result;
for (FileEntry::Ptr current : directoryEntry->getFiles()) {
bool isArchive = false;
@@ -1615,20 +1879,36 @@ std::vector<std::pair<QString, QString>> OrganizerCore::fileMapping(
continue;
}
- QString fileName = ToQString(current->getRelativePath());
- QString source = ToQString(base->getOriginByID(origin).getPath()) + fileName;
- QString target = QDir::toNativeSeparators(dataPath) + fileName;
- result.push_back(std::make_pair(source, target));
+ QString originPath
+ = QString::fromStdWString(base->getOriginByID(origin).getPath());
+ QString fileName = QString::fromStdWString(current->getName());
+// QString fileName = ToQString(current->getName());
+ QString source = originPath + relPath + fileName;
+ QString target = dataPath + relPath + fileName;
+ if (source != target) {
+ result.push_back({source, target, false, false});
+ }
}
// recurse into subdirectories
- std::vector<DirectoryEntry*>::const_iterator current, end;
+ std::vector<DirectoryEntry *>::const_iterator current, end;
directoryEntry->getSubDirectories(current, end);
for (; current != end; ++current) {
- std::vector<std::pair<QString, QString>> subRes = fileMapping(dataPath, base, *current);
+ int origin = (*current)->anyOrigin();
+
+ QString originPath
+ = QString::fromStdWString(base->getOriginByID(origin).getPath());
+ QString dirName = QString::fromStdWString((*current)->getName());
+ QString source = originPath + relPath + dirName;
+ QString target = dataPath + relPath + dirName;
+
+ bool writeDestination
+ = (base == directoryEntry) && (origin == createDestination);
+
+ result.push_back({source, target, true, writeDestination});
+ std::vector<Mapping> subRes = fileMapping(
+ dataPath, relPath + dirName + "\\", base, *current, createDestination);
result.insert(result.end(), subRes.begin(), subRes.end());
}
return result;
}
-
-*/
diff --git a/src/organizercore.h b/src/organizercore.h
index 28fc14ca..e994d9b1 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -12,6 +12,8 @@
#include "installationmanager.h"
#include "downloadmanager.h"
#include "executableslist.h"
+#include "usvfsconnector.h"
+#include <directoryentry.h>
#include <imoinfo.h>
#include <iplugindiagnose.h>
#include <versioninfo.h>
@@ -92,7 +94,7 @@ public:
void connectPlugins(PluginContainer *container);
void disconnectPlugins();
- void setManagedGame(const MOBase::IPluginGame *game);
+ void setManagedGame(MOBase::IPluginGame *game);
void updateExecutablesList(QSettings &settings);
@@ -134,16 +136,30 @@ public:
void doAfterLogin(const std::function<void()> &function) { m_PostLoginTasks.append(function); }
- void spawnBinary(const QFileInfo &binary, const QString &arguments = "", const QDir &currentDirectory = QDir(), bool closeAfterStart = true, const QString &steamAppID = "");
- HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir &currentDirectory, const QString &steamAppID);
+ void spawnBinary(const QFileInfo &binary, const QString &arguments = "",
+ const QDir &currentDirectory = QDir(),
+ const QString &steamAppID = "",
+ const QString &customOverwrite = "");
+
+ HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments,
+ const QString &profileName,
+ const QDir &currentDirectory,
+ const QString &steamAppID,
+ const QString &customOverwrite);
void loginSuccessfulUpdate(bool necessary);
void loginFailedUpdate(const QString &message);
+ static bool createAndMakeWritable(const QString &path);
+ bool bootstrap();
void createDefaultProfile();
MOBase::DelayedFileWriter &pluginsWriter() { return m_PluginListsWriter; }
+ void prepareVFS();
+
+ void setLogLevel(int logLevel);
+
public:
MOBase::IModRepositoryBridge *createNexusBridge() const;
QString profileName() const;
@@ -176,8 +192,6 @@ public:
void refreshModList(bool saveChanges = true);
QStringList modsSortedByProfilePriority() const;
- //std::vector<std::pair<QString, QString> > fileMapping();
-
public: // IPluginDiagnose interface
virtual std::vector<unsigned int> activeProblems() const;
@@ -215,6 +229,8 @@ signals:
void managedGameChanged(MOBase::IPluginGame const *gamePlugin);
+ void close();
+
private:
void storeSettings();
@@ -229,11 +245,19 @@ private:
bool testForSteam();
- /*
- * std::vector<std::pair<QString, QString>> fileMapping(const QString &dataPath,
- const MOShared::DirectoryEntry *base,
- const MOShared::DirectoryEntry *directoryEntry);
-*/
+ bool createDirectory(const QString &path);
+
+ /**
+ * @brief return a descriptor of the mappings real file->virtual file
+ */
+ std::vector<Mapping> fileMapping(const QString &profile,
+ const QString &customOverwrite);
+
+ std::vector<Mapping>
+ fileMapping(const QString &dataPath, const QString &relPath,
+ const MOShared::DirectoryEntry *base,
+ const MOShared::DirectoryEntry *directoryEntry,
+ int createDestination);
bool waitForProcessCompletion(HANDLE handle, LPDWORD exitCode);
@@ -255,7 +279,7 @@ private:
IUserInterface *m_UserInterface;
PluginContainer *m_PluginContainer;
QString m_GameName;
- MOBase::IPluginGame const *m_GamePlugin;
+ MOBase::IPluginGame *m_GamePlugin;
Profile *m_CurrentProfile;
@@ -291,6 +315,7 @@ private:
bool m_ArchivesInit;
MOBase::DelayedFileWriter m_PluginListsWriter;
+ UsvfsConnector m_USVFS;
};
diff --git a/src/organizerproxy.h b/src/organizerproxy.h
index 7ce8982e..7d925482 100644
--- a/src/organizerproxy.h
+++ b/src/organizerproxy.h
@@ -59,5 +59,4 @@ private:
};
-
#endif // ORGANIZERPROXY_H
diff --git a/src/persistentcookiejar.cpp b/src/persistentcookiejar.cpp
index 6ca0de39..a6eb78fa 100644
--- a/src/persistentcookiejar.cpp
+++ b/src/persistentcookiejar.cpp
@@ -15,6 +15,12 @@ PersistentCookieJar::~PersistentCookieJar() {
save();
}
+void PersistentCookieJar::clear() {
+ for (const QNetworkCookie &cookie : allCookies()) {
+ deleteCookie(cookie);
+ }
+}
+
void PersistentCookieJar::save() {
QTemporaryFile file;
if (!file.open()) {
@@ -26,7 +32,7 @@ void PersistentCookieJar::save() {
QList<QNetworkCookie> cookies = allCookies();
data << static_cast<quint32>(cookies.size());
- foreach (const QNetworkCookie &cookie, allCookies()) {
+ for (const QNetworkCookie &cookie : allCookies()) {
data << cookie.toRawForm();
}
diff --git a/src/persistentcookiejar.h b/src/persistentcookiejar.h
index 812b785c..0ff747ee 100644
--- a/src/persistentcookiejar.h
+++ b/src/persistentcookiejar.h
@@ -5,9 +5,15 @@
class PersistentCookieJar : public QNetworkCookieJar {
+
+ Q_OBJECT
+
public:
PersistentCookieJar(const QString &fileName, QObject *parent = 0);
virtual ~PersistentCookieJar();
+
+ void clear();
+
private:
void save();
diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp
index 7a059cd1..c4976d2c 100644
--- a/src/plugincontainer.cpp
+++ b/src/plugincontainer.cpp
@@ -87,7 +87,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName)
m_Organizer->settings().registerPlugin(pluginObj);
}
- { // diagnosis plugins
+ { // diagnosis plugin
IPluginDiagnose *diagnose = qobject_cast<IPluginDiagnose*>(plugin);
if (diagnose != nullptr) {
bf::at_key<IPluginDiagnose>(m_Plugins).push_back(diagnose);
@@ -96,6 +96,12 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName)
);
}
}
+ { // file mapper plugin
+ IPluginFileMapper *mapper = qobject_cast<IPluginFileMapper*>(plugin);
+ if (mapper != nullptr) {
+ bf::at_key<IPluginFileMapper>(m_Plugins).push_back(mapper);
+ }
+ }
{ // mod page plugin
IPluginModPage *modPage = qobject_cast<IPluginModPage*>(plugin);
if (verifyPlugin(modPage)) {
@@ -194,7 +200,7 @@ void PluginContainer::unloadPlugins()
bf::for_each(m_Plugins, clearPlugins());
- foreach (const boost::signals2::connection &connection, m_DiagnosisConnections) {
+ for (const boost::signals2::connection &connection : m_DiagnosisConnections) {
connection.disconnect();
}
m_DiagnosisConnections.clear();
@@ -277,7 +283,7 @@ void PluginContainer::loadPlugins()
m_PluginLoaders.push_back(pluginLoader.release());
} else {
m_FailedPlugins.push_back(pluginName);
- qWarning("plugin \"%s\" failed to load", qPrintable(pluginName));
+ qWarning("plugin \"%s\" failed to load (may be outdated)", qPrintable(pluginName));
}
}
}
diff --git a/src/plugincontainer.h b/src/plugincontainer.h
index 02978e3e..70392d0b 100644
--- a/src/plugincontainer.h
+++ b/src/plugincontainer.h
@@ -10,6 +10,7 @@
#include <iplugintool.h>
#include <ipluginproxy.h>
#include <iplugininstaller.h>
+#include <ipluginfilemapper.h>
#include <QtPlugin>
#include <QPluginLoader>
#include <QFile>
@@ -36,7 +37,8 @@ private:
boost::fusion::pair<MOBase::IPluginModPage, std::vector<MOBase::IPluginModPage*>>,
boost::fusion::pair<MOBase::IPluginPreview, std::vector<MOBase::IPluginPreview*>>,
boost::fusion::pair<MOBase::IPluginTool, std::vector<MOBase::IPluginTool*>>,
- boost::fusion::pair<MOBase::IPluginProxy, std::vector<MOBase::IPluginProxy*>>
+ boost::fusion::pair<MOBase::IPluginProxy, std::vector<MOBase::IPluginProxy*>>,
+ boost::fusion::pair<MOBase::IPluginFileMapper, std::vector<MOBase::IPluginFileMapper*>>
> PluginMap;
static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1;
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp
index 96757ec6..a4d8561c 100644
--- a/src/pluginlist.cpp
+++ b/src/pluginlist.cpp
@@ -18,9 +18,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "pluginlist.h"
-#include "inject.h"
#include "settings.h"
-#include "safewritefile.h"
#include "scopeguard.h"
#include "modinfo.h"
#include <utility.h>
@@ -28,6 +26,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <espfile.h>
#include <report.h>
#include <windows_error.h>
+#include <safewritefile.h>
+#include <gameplugins.h>
#include <QtDebug>
#include <QMessageBox>
@@ -75,14 +75,6 @@ PluginList::PluginList(QObject *parent)
: QAbstractItemModel(parent)
, m_FontMetrics(QFont())
{
- m_Utf8Codec = QTextCodec::codecForName("utf-8");
- m_LocalCodec = QTextCodec::codecForName("Windows-1252");
-
- if (m_LocalCodec == nullptr) {
- qCritical("required 8-bit string-encoding not supported.");
- m_LocalCodec = m_Utf8Codec;
- }
-
}
PluginList::~PluginList()
@@ -118,27 +110,29 @@ QString PluginList::getColumnToolTip(int column)
void PluginList::refresh(const QString &profileName
, const DirectoryEntry &baseDirectory
- , const QString &pluginsFile
- , const QString &loadOrderFile
, const QString &lockedOrderFile)
{
ChangeBracket<PluginList> layoutChange(this);
- m_ESPsByName.clear();
- m_ESPsByPriority.clear();
- m_ESPs.clear();
-
QStringList primaryPlugins = m_GamePlugin->primaryPlugins();
m_CurrentProfile = profileName;
+ QStringList availablePlugins;
+
std::vector<FileEntry::Ptr> files = baseDirectory.getFiles();
- for (auto iter = files.begin(); iter != files.end(); ++iter) {
- FileEntry::Ptr current = *iter;
+ for (FileEntry::Ptr current : files) {
if (current.get() == nullptr) {
continue;
}
QString filename = ToQString(current->getName());
+
+ availablePlugins.append(filename.toLower());
+
+ if (m_ESPsByName.find(filename.toLower()) != m_ESPsByName.end()) {
+ continue;
+ }
+
QString extension = filename.right(3).toLower();
if ((extension == "esp") || (extension == "esm")) {
@@ -160,73 +154,78 @@ void PluginList::refresh(const QString &profileName
}
m_ESPs.push_back(ESPInfo(filename, forceEnabled, originName, ToQString(current->getFullPath()), hasIni));
+ m_ESPs.rbegin()->m_Priority = -1;
} catch (const std::exception &e) {
reportError(tr("failed to update esp info for file %1 (source id: %2), error: %3").arg(filename).arg(current->getOrigin(archive)).arg(e.what()));
}
}
}
- if (readLoadOrder(loadOrderFile)) {
- int maxPriority = 0;
- // assign known load orders
- for (std::vector<ESPInfo>::iterator espIter = m_ESPs.begin(); espIter != m_ESPs.end(); ++espIter) {
- std::map<QString, int>::const_iterator priorityIter = m_ESPLoadOrder.find(espIter->m_Name.toLower());
- if (priorityIter != m_ESPLoadOrder.end()) {
- if (priorityIter->second > maxPriority) {
- maxPriority = priorityIter->second;
- }
- espIter->m_Priority = priorityIter->second;
- } else {
- espIter->m_Priority = -1;
- }
+ for (const auto &espName : m_ESPsByName) {
+ if (!availablePlugins.contains(espName.first)) {
+ m_ESPs[espName.second].m_Name = "";
}
+ }
- ++maxPriority;
+ m_ESPs.erase(std::remove_if(m_ESPs.begin(), m_ESPs.end(),
+ [](const ESPInfo &info) -> bool {
+ return info.m_Name.isEmpty();
+ }),
+ m_ESPs.end());
- // assign maximum priorities for plugins with unknown priority
- for (std::vector<ESPInfo>::iterator espIter = m_ESPs.begin(); espIter != m_ESPs.end(); ++espIter) {
- if (espIter->m_Priority == -1) {
- espIter->m_Priority = maxPriority++;
- }
- }
- } else {
- // no load order stored, determine by date
- std::sort(m_ESPs.begin(), m_ESPs.end(), ByDate);
+ fixPriorities();
- for (size_t i = 0; i < m_ESPs.size(); ++i) {
- m_ESPs[i].m_Priority = i;
- }
- }
+ // functions in GamePlugins will use the IPluginList interface of this, so
+ // indices need to work. priority will be off however
+ updateIndices();
- std::sort(m_ESPs.begin(), m_ESPs.end(), ByPriority); // first, sort by priority
- // remove gaps from the priorities so we can use them as array indices without overflow
- for (int i = 0; i < static_cast<int>(m_ESPs.size()); ++i) {
- m_ESPs[i].m_Priority = i;
- }
+ GamePlugins *gamePlugins = m_GamePlugin->feature<GamePlugins>();
+ gamePlugins->readPluginLists(this);
- std::sort(m_ESPs.begin(), m_ESPs.end(), ByName); // sort by name so alphabetical sorting works
+ testMasters();
updateIndices();
- readEnabledFrom(pluginsFile);
-
readLockedOrderFrom(lockedOrderFile);
layoutChange.finish();
refreshLoadOrder();
- emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount()));
+ emit dataChanged(this->index(0, 0),
+ this->index(static_cast<int>(m_ESPs.size()), columnCount()));
m_Refreshed();
}
+void PluginList::fixPriorities()
+{
+ std::vector<std::pair<int, int>> espPrios;
+
+ for (int i = 0; i < m_ESPs.size(); ++i) {
+ int prio = m_ESPs[i].m_Priority;
+ if (prio == -1) {
+ prio = INT_MAX;
+ }
+ espPrios.push_back(std::make_pair(prio, i));
+ }
+
+ std::sort(espPrios.begin(), espPrios.end(),
+ [](const std::pair<int, int> &lhs, const std::pair<int, int> &rhs) {
+ return lhs.first < rhs.first;
+ });
+
+ for (int i = 0; i < espPrios.size(); ++i) {
+ m_ESPs[espPrios[i].second].m_Priority = i;
+ }
+}
void PluginList::enableESP(const QString &name, bool enable)
{
std::map<QString, int>::iterator iter = m_ESPsByName.find(name.toLower());
if (iter != m_ESPsByName.end()) {
- m_ESPs[iter->second].m_Enabled = enable;
+ m_ESPs[iter->second].m_Enabled =
+ enable | m_ESPs[iter->second].m_ForceEnabled;
emit writePluginsList();
} else {
@@ -239,8 +238,8 @@ void PluginList::enableAll()
{
if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really enable all plugins?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
- iter->m_Enabled = true;
+ for (ESPInfo &info : m_ESPs) {
+ info.m_Enabled = true;
}
emit writePluginsList();
}
@@ -251,9 +250,9 @@ void PluginList::disableAll()
{
if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really disable all plugins?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
- if (!iter->m_ForceEnabled) {
- iter->m_Enabled = false;
+ for (ESPInfo &info : m_ESPs) {
+ if (!info.m_ForceEnabled) {
+ info.m_Enabled = false;
}
}
emit writePluginsList();
@@ -302,89 +301,6 @@ bool PluginList::isEnabled(int index)
return m_ESPs.at(index).m_Enabled;
}
-bool PluginList::readLoadOrder(const QString &fileName)
-{
- std::set<QString> availableESPs;
- for (std::vector<ESPInfo>::const_iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
- availableESPs.insert(iter->m_Name.toLower());
- }
-
- m_ESPLoadOrder.clear();
-
- int priority = 0;
-
- QStringList primaryPlugins = m_GamePlugin->primaryPlugins();
- for (const QString &plugin : primaryPlugins) {
- if (availableESPs.find(plugin) != availableESPs.end()) {
- m_ESPLoadOrder[plugin] = priority++;
- }
- }
-
- QFile file(fileName);
- if (!file.open(QIODevice::ReadOnly)) {
- return false;
- }
- if (file.size() == 0) {
- // MO stores at least a header in the file. if it's completely empty the file is broken
- return false;
- }
- while (!file.atEnd()) {
- QByteArray line = file.readLine().trimmed();
- QString modName;
- if ((line.size() > 0) && (line.at(0) != '#')) {
- modName = QString::fromUtf8(line.constData()).toLower();
- }
-
- if ((modName.size() > 0) &&
- (m_ESPLoadOrder.find(modName) == m_ESPLoadOrder.end()) &&
- (availableESPs.find(modName) != availableESPs.end())) {
- m_ESPLoadOrder[modName] = priority++;
- }
- }
-
- file.close();
- return true;
-}
-
-
-void PluginList::readEnabledFrom(const QString &fileName)
-{
- for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
- if (!iter->m_ForceEnabled) {
- iter->m_Enabled = false;
- }
- iter->m_LoadOrder = -1;
- }
-
- QFile file(fileName);
- if (!file.exists()) {
- throw std::runtime_error(QObject::tr("failed to find \"%1\"").arg(fileName).toUtf8().constData());
- }
-
- file.open(QIODevice::ReadOnly);
- while (!file.atEnd()) {
- QByteArray line = file.readLine();
- QString modName;
- if ((line.size() > 0) && (line.at(0) != '#')) {
- modName = m_LocalCodec->toUnicode(line.trimmed().constData());
- }
- if (modName.size() > 0) {
- std::map<QString, int>::iterator iter = m_ESPsByName.find(modName.toLower());
- if (iter != m_ESPsByName.end()) {
- m_ESPs[iter->second].m_Enabled = true;
- } else {
- qWarning("plugin %s not found", modName.toUtf8().constData());
- emit writePluginsList();
- }
- }
- }
-
- file.close();
-
- testMasters();
-}
-
-
void PluginList::readLockedOrderFrom(const QString &fileName)
{
m_LockedOrder.clear();
@@ -411,53 +327,6 @@ void PluginList::readLockedOrderFrom(const QString &fileName)
file.close();
}
-
-
-void PluginList::writePlugins(const QString &fileName, bool writeUnchecked) const
-{
- SafeWriteFile file(fileName);
-
- QTextCodec *textCodec = writeUnchecked ? m_Utf8Codec : m_LocalCodec;
-
- file->resize(0);
-
- file->write(textCodec->fromUnicode("# This file was automatically generated by Mod Organizer.\r\n"));
-
- QStringList saveList;
-
- bool invalidFileNames = false;
- int writtenCount = 0;
- for (size_t i = 0; i < m_ESPs.size(); ++i) {
- int priority = m_ESPsByPriority[i];
- if (m_ESPs[priority].m_Enabled || writeUnchecked) {
- //file.write(m_ESPs[priority].m_Name.toUtf8());
- if (!textCodec->canEncode(m_ESPs[priority].m_Name)) {
- invalidFileNames = true;
- qCritical("invalid plugin name %s", m_ESPs[priority].m_Name.toUtf8().constData());
- } else {
- saveList << m_ESPs[priority].m_Name;
- file->write(textCodec->fromUnicode(m_ESPs[priority].m_Name));
- }
- file->write("\r\n");
- ++writtenCount;
- }
- }
-
- if (invalidFileNames) {
- reportError(tr("Some of your plugins have invalid names! These plugins can not be loaded by the game. "
- "Please see mo_interface.log for a list of affected plugins and rename them."));
- }
-
- if (writtenCount == 0) {
- qWarning("plugin list would be empty, this is almost certainly wrong. Not saving.");
- } else {
- if (file.commitIfDifferent(m_LastSaveHash[fileName])) {
- qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData());
- }
- }
-}
-
-
void PluginList::writeLockedOrder(const QString &fileName) const
{
SafeWriteFile file(fileName);
@@ -472,14 +341,13 @@ void PluginList::writeLockedOrder(const QString &fileName) const
}
-void PluginList::saveTo(const QString &pluginFileName
- , const QString &loadOrderFileName
- , const QString &lockedOrderFileName
+void PluginList::saveTo(const QString &lockedOrderFileName
, const QString& deleterFileName
, bool hideUnchecked) const
{
- writePlugins(pluginFileName, false);
- writePlugins(loadOrderFileName, true);
+ GamePlugins *gamePlugins = m_GamePlugin->feature<GamePlugins>();
+ gamePlugins->writePluginLists(this);
+
writeLockedOrder(lockedOrderFileName);
if (hideUnchecked) {
@@ -553,7 +421,7 @@ bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure)
int PluginList::enabledCount() const
{
int enabled = 0;
- foreach (auto info, m_ESPs) {
+ for (const auto &info : m_ESPs) {
if (info.m_Enabled) {
++enabled;
}
@@ -607,7 +475,7 @@ void PluginList::refreshLoadOrder()
bool savePluginsList = false;
// this is guaranteed to iterate from lowest key (load order) to highest
for (auto iter = lockedLoadOrder.begin(); iter != lockedLoadOrder.end(); ++iter) {
- auto nameIter = m_ESPsByName.find(iter->second);
+ auto nameIter = m_ESPsByName.find(iter->second.toLower());
if (nameIter != m_ESPsByName.end()) {
// locked esp exists
@@ -642,6 +510,17 @@ void PluginList::disconnectSlots() {
m_PluginStateChanged.disconnect_all_slots();
}
+QStringList PluginList::pluginNames() const
+{
+ QStringList result;
+
+ for (const ESPInfo &info : m_ESPs) {
+ result.append(info.m_Name);
+ }
+
+ return result;
+}
+
IPluginList::PluginStates PluginList::state(const QString &name) const
{
auto iter = m_ESPsByName.find(name.toLower());
@@ -652,6 +531,38 @@ IPluginList::PluginStates PluginList::state(const QString &name) const
}
}
+void PluginList::setState(const QString &name, PluginStates state) {
+ auto iter = m_ESPsByName.find(name.toLower());
+ if (iter != m_ESPsByName.end()) {
+ m_ESPs[iter->second].m_Enabled = (state == IPluginList::STATE_ACTIVE) ||
+ m_ESPs[iter->second].m_ForceEnabled;
+ } else {
+ qWarning("plugin %s not found", qPrintable(name));
+ }
+}
+
+void PluginList::setLoadOrder(const QStringList &pluginList)
+{
+ for (ESPInfo &info : m_ESPs) {
+ info.m_Priority = -1;
+ }
+ int maxPriority = 0;
+ for (const QString &plugin : pluginList) {
+ auto iter = m_ESPsByName.find(plugin.toLower());
+ if (iter !=m_ESPsByName.end()) {
+ m_ESPs[iter->second].m_Priority = maxPriority++;
+ }
+ }
+
+ // use old priorities
+ for (ESPInfo &info : m_ESPs) {
+ if (info.m_Priority == -1) {
+ info.m_Priority = maxPriority++;
+ }
+ }
+ updateIndices();
+}
+
int PluginList::priority(const QString &name) const
{
auto iter = m_ESPsByName.find(name.toLower());
@@ -689,7 +600,7 @@ QStringList PluginList::masters(const QString &name) const
return QStringList();
} else {
QStringList result;
- foreach (const QString &master, m_ESPs[iter->second].m_Masters) {
+ for (const QString &master : m_ESPs[iter->second].m_Masters) {
result.append(master);
}
return result;
@@ -731,10 +642,16 @@ void PluginList::updateIndices()
m_ESPsByName.clear();
m_ESPsByPriority.clear();
m_ESPsByPriority.resize(m_ESPs.size());
-
for (unsigned int i = 0; i < m_ESPs.size(); ++i) {
+ if (m_ESPs[i].m_Priority < 0) {
+ continue;
+ }
+ if (m_ESPs[i].m_Priority >= static_cast<int>(m_ESPs.size())) {
+ qCritical("invalid priority %d", m_ESPs[i].m_Priority);
+ continue;
+ }
m_ESPsByName[m_ESPs[i].m_Name.toLower()] = i;
- m_ESPsByPriority[m_ESPs[i].m_Priority] = i;
+ m_ESPsByPriority.at(static_cast<size_t>(m_ESPs[i].m_Priority)) = i;
}
}
@@ -742,7 +659,7 @@ void PluginList::updateIndices()
int PluginList::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid()) {
- return m_ESPs.size();
+ return static_cast<int>(m_ESPs.size());
} else {
return 0;
}
@@ -819,8 +736,6 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const
if (m_ESPs[index].m_IsMaster) {
result.setItalic(true);
result.setWeight(QFont::Bold);
- } else if (m_ESPs[index].m_IsDummy) {
- result.setItalic(true);
}
return result;
} else if (role == Qt::TextAlignmentRole) {
@@ -861,9 +776,6 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const
if (m_ESPs[index].m_HasIni) {
text += "<br>There is an ini file connected to this esp. Its settings will be added to your game settings, overwriting "
"in case of conflicts.";
- } else if (m_ESPs[index].m_IsDummy) {
- text += "<br>This file is a dummy! It exists only so the bsa with the same name gets loaded. If you let MO manage archives you "
- "don't need this: Enable the archive with the same name in the \"Archive\" tab and disable this plugin.";
}
toolTip += text;
}
@@ -886,9 +798,6 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const
if (m_ESPs[index].m_HasIni) {
result.append(":/MO/gui/attachment");
}
- if (m_ESPs[index].m_IsDummy && m_ESPs[index].m_Enabled && !m_ESPs[index].m_HasIni) {
- result.append(":/MO/gui/edit_clear");
- }
return result;
}
return QVariant();
@@ -903,7 +812,8 @@ bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int
bool result = false;
if (role == Qt::CheckStateRole) {
- m_ESPs[modIndex.row()].m_Enabled = value.toInt() == Qt::Checked;
+ m_ESPs[modIndex.row()].m_Enabled =
+ value.toInt() == Qt::Checked || m_ESPs[modIndex.row()].m_ForceEnabled;
emit dataChanged(modIndex, modIndex);
refreshLoadOrder();
@@ -927,7 +837,9 @@ bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int
try {
m_PluginStateChanged(modName, newState);
testMasters();
- emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount()));
+ emit dataChanged(
+ this->index(0, 0),
+ this->index(static_cast<int>(m_ESPs.size()), columnCount()));
} catch (const std::exception &e) {
qCritical("failed to invoke state changed notification: %s", e.what());
} catch (...) {
@@ -1003,8 +915,10 @@ void PluginList::setPluginPriority(int row, int &newPriority)
}
// enforce valid range
- if (newPriorityTemp < 0) newPriorityTemp = 0;
- else if (newPriorityTemp >= static_cast<int>(m_ESPsByPriority.size())) newPriorityTemp = m_ESPsByPriority.size() - 1;
+ if (newPriorityTemp < 0)
+ newPriorityTemp = 0;
+ else if (newPriorityTemp >= static_cast<int>(m_ESPsByPriority.size()))
+ newPriorityTemp = static_cast<int>(m_ESPsByPriority.size()) - 1;
try {
int oldPriority = m_ESPs.at(row).m_Priority;
@@ -1086,11 +1000,11 @@ bool PluginList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action,
row = parent.row();
}
- int newPriority = 0;
+ int newPriority;
if ((row < 0) ||
(row >= static_cast<int>(m_ESPs.size()))) {
- newPriority = m_ESPs.size();
+ newPriority = static_cast<int>(m_ESPs.size());
} else {
newPriority = m_ESPs[row].m_Priority;
}
@@ -1197,7 +1111,6 @@ PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled,
try {
ESP::File file(ToWString(fullPath));
m_IsMaster = file.isMaster();
- m_IsDummy = file.isDummy();
m_Author = QString::fromLatin1(file.author().c_str());
m_Description = QString::fromLatin1(file.description().c_str());
std::set<std::string> masters = file.masters();
@@ -1207,11 +1120,10 @@ PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled,
} catch (const std::exception &e) {
qCritical("failed to parse esp file %s: %s", qPrintable(fullPath), e.what());
m_IsMaster = false;
- m_IsDummy = false;
}
}
-void PluginList::managedGameChanged(IPluginGame const *gamePlugin)
+void PluginList::managedGameChanged(const IPluginGame *gamePlugin)
{
m_GamePlugin = gamePlugin;
}
diff --git a/src/pluginlist.h b/src/pluginlist.h
index 9fe6eeac..78623cae 100644
--- a/src/pluginlist.h
+++ b/src/pluginlist.h
@@ -110,15 +110,11 @@ public:
*
* @param profileName name of the current profile
* @param baseDirectory the root directory structure representing the virtual data directory
- * @param pluginsFile file that stores the list of enabled plugins
- * @param loadOrderFile file that stored the load order (not an official file but used by many tools for skyrim)
* @param lockedOrderFile list of plugins that shouldn't change load order
* @todo the profile is not used? If it was, we should pass the Profile-object instead
**/
void refresh(const QString &profileName
, const MOShared::DirectoryEntry &baseDirectory
- , const QString &pluginsFile
- , const QString &loadOrderFile
, const QString &lockedOrderFile);
/**
@@ -165,25 +161,13 @@ public:
bool isEnabled(int index);
/**
- * @brief update the plugin status (enabled/disabled) from the specified file
- *
- * @param fileName path of the file to load. the filename should be "plugin.txt"
- * @todo it would make sense to move this into the Profile-class
- **/
- void readEnabledFrom(const QString &fileName);
-
- /**
* @brief save the plugin status to the specified file
*
- * @param pluginFileName path of the plugin.txt to write to
- * @param loadOrderFileName path of the loadorder.txt to write to
* @param lockedOrderFileName path of the lockedorder.txt to write to
* @param deleterFileName file to receive a list of files to hide from the virtual data tree. This is used to hide unchecked plugins if "hideUnchecked" is true
* @param hideUnchecked if true, plugins that aren't enabled will be hidden from the virtual data directory
**/
- void saveTo(const QString &pluginFileName
- , const QString &loadOrderFileName
- , const QString &lockedOrderFileName
+ void saveTo(const QString &lockedOrderFileName
, const QString &deleterFileName
, bool hideUnchecked) const;
@@ -222,13 +206,16 @@ public:
public:
+ virtual QStringList pluginNames() const override;
virtual PluginStates state(const QString &name) const;
+ virtual void setState(const QString &name, PluginStates state) override;
virtual int priority(const QString &name) const;
virtual int loadOrder(const QString &name) const;
+ virtual bool onRefreshed(const std::function<void()> &callback);
virtual bool isMaster(const QString &name) const;
virtual QStringList masters(const QString &name) const;
virtual QString origin(const QString &name) const;
- virtual bool onRefreshed(const std::function<void()> &callback);
+ virtual void setLoadOrder(const QStringList &pluginList) override;
virtual bool onPluginMoved(const std::function<void (const QString &, int, int)> &func);
virtual bool onPluginStateChanged(const std::function<void (const QString &, PluginStates)> &func) override;
@@ -288,7 +275,6 @@ private:
FILETIME m_Time;
QString m_OriginName;
bool m_IsMaster;
- bool m_IsDummy;
QString m_Author;
QString m_Description;
bool m_HasIni;
@@ -309,16 +295,16 @@ private:
void syncLoadOrder();
void updateIndices();
- void writePlugins(const QString &fileName, bool writeUnchecked) const;
void writeLockedOrder(const QString &fileName) const;
- bool readLoadOrder(const QString &fileName);
void readLockedOrderFrom(const QString &fileName);
void setPluginPriority(int row, int &newPriority);
void changePluginPriority(std::vector<int> rows, int newPriority);
void testMasters();
+ void fixPriorities();
+
private:
std::vector<ESPInfo> m_ESPs;
@@ -327,10 +313,6 @@ private:
std::map<QString, int> m_ESPsByName;
std::vector<int> m_ESPsByPriority;
- // maps esp names to the priority specified in loadorder.txt. The esp names are
- // all lowercase!! This is to work around the fact that BOSS for some reason writes some file with
- // capitalization that doesn't match the actual name
- std::map<QString, int> m_ESPLoadOrder;
std::map<QString, int> m_LockedOrder;
std::map<QString, AdditionalInfo> m_AdditionalInfo; // maps esp names to boss information
@@ -338,16 +320,13 @@ private:
QString m_CurrentProfile;
QFontMetrics m_FontMetrics;
- QTextCodec *m_Utf8Codec;
- QTextCodec *m_LocalCodec;
-
SignalRefreshed m_Refreshed;
SignalPluginMoved m_PluginMoved;
SignalPluginStateChanged m_PluginStateChanged;
QTemporaryFile m_TempFile;
- MOBase::IPluginGame const *m_GamePlugin;
+ const MOBase::IPluginGame *m_GamePlugin;
};
diff --git a/src/profile.cpp b/src/profile.cpp
index 21fb0b7a..558d9114 100644
--- a/src/profile.cpp
+++ b/src/profile.cpp
@@ -20,12 +20,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "profile.h"
#include "modinfo.h"
-#include "safewritefile.h"
+#include "settings.h"
#include <utility.h>
#include <error_report.h>
#include "appconfig.h"
#include <iplugingame.h>
#include <report.h>
+#include <safewritefile.h>
#include <bsainvalidation.h>
#include <dataarchives.h>
@@ -68,9 +69,11 @@ Profile::Profile(const QString &name, IPluginGame const *gamePlugin, bool useDef
: m_ModListWriter(std::bind(&Profile::writeModlistNow, this))
, m_GamePlugin(gamePlugin)
{
- QString profilesDir = qApp->property("dataPath").toString() + "/" + ToQString(AppConfig::profilesPath());
+ QString profilesDir = Settings::instance().getProfileDirectory();
QDir profileBase(profilesDir);
+ m_Settings = new QSettings(profileBase.absoluteFilePath("settings.ini"));
+
QString fixedName = name;
if (!fixDirectoryName(fixedName)) {
throw MyException(tr("invalid profile name %1").arg(name));
@@ -112,19 +115,18 @@ Profile::Profile(const QDir &directory, IPluginGame const *gamePlugin)
{
assert(gamePlugin != nullptr);
+ m_Settings = new QSettings(directory.absoluteFilePath("settings.ini"),
+ QSettings::IniFormat);
+
if (!QFile::exists(m_Directory.filePath("modlist.txt"))) {
qWarning("missing modlist.txt in %s", qPrintable(directory.path()));
touchFile(m_Directory.filePath("modlist.txt"));
}
- IPluginGame::ProfileSettings settings = IPluginGame::CONFIGURATION
- | IPluginGame::MODS
+ IPluginGame::ProfileSettings settings = IPluginGame::MODS
| IPluginGame::SAVEGAMES;
gamePlugin->initializeProfile(directory, settings);
- if (!QFile::exists(getIniFileName())) {
- reportError(QObject::tr("\"%1\" is missing or inaccessible").arg(getIniFileName()));
- }
refreshModStatus();
}
@@ -135,12 +137,15 @@ Profile::Profile(const Profile &reference)
, m_GamePlugin(reference.m_GamePlugin)
{
+ m_Settings = new QSettings(m_Directory.absoluteFilePath("settings.ini"),
+ QSettings::IniFormat);
refreshModStatus();
}
Profile::~Profile()
{
+ delete m_Settings;
m_ModListWriter.writeImmediately(true);
}
@@ -162,7 +167,7 @@ void Profile::writeModlistNow()
return;
}
- for (int i = m_ModStatus.size() - 1; i >= 0; --i) {
+ for (int i = static_cast<int>(m_ModStatus.size()) - 1; i >= 0; --i) {
// the priority order was inverted on load so it has to be inverted again
unsigned int index = m_ModIndexByPriority[i];
if (index != UINT_MAX) {
@@ -310,7 +315,7 @@ void Profile::refreshModStatus()
// invert priority order to match that of the pluginlist. Also
// give priorities to mods not referenced in the profile
for (size_t i = 0; i < m_ModStatus.size(); ++i) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(i);
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(static_cast<int>(i));
if (modInfo->alwaysEnabled()) {
m_ModStatus[i].m_Enabled = true;
}
@@ -339,7 +344,7 @@ void Profile::refreshModStatus()
if (topInsert < 0) {
int offset = topInsert * -1;
for (size_t i = 0; i < m_ModStatus.size(); ++i) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(i);
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(static_cast<unsigned int>(i));
if (modInfo->getFixedPriority() == INT_MAX) {
continue;
}
@@ -468,7 +473,9 @@ void Profile::setModPriority(unsigned int index, int &newPriority)
return;
}
- int newPriorityTemp = (std::max)(0, (std::min<int>)(m_ModStatus.size() - 1, newPriority));
+ int newPriorityTemp =
+ (std::max)(0, (std::min<int>)(static_cast<int>(m_ModStatus.size()) - 1,
+ newPriority));
// don't try to place below overwrite
while ((m_ModIndexByPriority.at(newPriorityTemp) >= m_ModStatus.size()) ||
@@ -497,7 +504,7 @@ void Profile::setModPriority(unsigned int index, int &newPriority)
Profile *Profile::createPtrFrom(const QString &name, const Profile &reference, MOBase::IPluginGame const *gamePlugin)
{
- QString profileDirectory = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::profilesPath()) + "/" + name;
+ QString profileDirectory = Settings::instance().getProfileDirectory() + "/" + name;
reference.copyFilesTo(profileDirectory);
return new Profile(QDir(profileDirectory), gamePlugin);
}
@@ -592,7 +599,9 @@ bool Profile::invalidationActive(bool *supported) const
}
return false;
} else {
- *supported = false;
+ if (supported != nullptr) {
+ *supported = false;
+ }
}
return false;
}
@@ -633,11 +642,12 @@ bool Profile::enableLocalSaves(bool enable)
m_Directory.mkdir("saves");
}
} else {
- QMessageBox::StandardButton res = QMessageBox::question(QApplication::activeModalWidget(), tr("Delete savegames?"),
- tr("Do you want to delete local savegames? (If you select \"No\", the save games "
- "will show up again if you re-enable local savegames)"),
- QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
- QMessageBox::Cancel);
+ QMessageBox::StandardButton res = QMessageBox::question(
+ QApplication::activeModalWidget(), tr("Delete savegames?"),
+ tr("Do you want to delete local savegames? (If you select \"No\", the "
+ "save games will show up again if you re-enable local savegames)"),
+ QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
+ QMessageBox::Cancel);
if (res == QMessageBox::Yes) {
shellDelete(QStringList(m_Directory.absoluteFilePath("saves")));
} else if (res == QMessageBox::No) {
@@ -651,6 +661,30 @@ bool Profile::enableLocalSaves(bool enable)
return true;
}
+bool Profile::localSettingsEnabled() const
+{
+ return m_Directory.exists(getIniFileName());
+}
+
+bool Profile::enableLocalSettings(bool enable)
+{
+ // TODO: this currently assumes game settings are stored in an ini file.
+ // This shall become very interesting when a game stores its settings in the
+ // registry
+ QString backupFile = getIniFileName() + "_";
+ if (enable) {
+ if (m_Directory.exists(backupFile)) {
+ shellRename(backupFile, getIniFileName());
+ } else {
+ IPluginGame *game = qApp->property("managed_game").value<IPluginGame *>();
+ game->initializeProfile(m_Directory, IPluginGame::CONFIGURATION);
+ }
+ } else {
+ shellRename(getIniFileName(), backupFile);
+ }
+
+ return true;
+}
QString Profile::getModlistFileName() const
{
@@ -705,8 +739,19 @@ QString Profile::savePath() const
void Profile::rename(const QString &newName)
{
- QDir profileDir(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::profilesPath()));
+ QDir profileDir(Settings::instance().getProfileDirectory());
profileDir.rename(name(), newName);
m_Directory = profileDir.absoluteFilePath(newName);
}
+QVariant Profile::setting(const QString &section, const QString &name,
+ const QVariant &fallback)
+{
+ return m_Settings->value(section + "/" + name, fallback);
+}
+
+void Profile::storeSetting(const QString &section, const QString &name,
+ const QVariant &value)
+{
+ m_Settings->setValue(section + "/" + name, value);
+}
diff --git a/src/profile.h b/src/profile.h
index 39e8ff5f..996e561f 100644
--- a/src/profile.h
+++ b/src/profile.h
@@ -29,6 +29,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QDir>
#include <QObject>
#include <QString>
+#include <QSettings>
#include <boost/shared_ptr.hpp>
@@ -112,20 +113,32 @@ public:
/**
* @return true if this profile uses local save games
*/
- bool localSavesEnabled() const;
+ virtual bool localSavesEnabled() const override;
/**
* @brief enables or disables the use of local save games for this profile
- * disabling this does not delete exising local saves but they will not be visible
- * in the game
+ * when disabling the user will be asked if he wants to remove the save games
+ * in the profile
* @param enable if true, local saves are enabled, otherewise they are disabled
*/
bool enableLocalSaves(bool enable);
/**
+ * @return true if this profile uses local ini files
+ */
+ virtual bool localSettingsEnabled() const override;
+
+ /**
+ * @brief enables or disables the use of local ini files for this profile
+ * disabling this does not delete existing ini files but the global ones will be used
+ * @param enable
+ */
+ bool enableLocalSettings(bool enable);
+
+ /**
* @return name of the profile (this is identical to its directory name)
**/
- QString name() const { return m_Directory.dirName(); }
+ virtual QString name() const override { return m_Directory.dirName(); }
/**
* @return the path of the plugins file in this profile
@@ -174,7 +187,7 @@ public:
/**
* @return path to this profile
**/
- QString absolutePath() const;
+ virtual QString absolutePath() const override;
/**
* @return path to this profile's save games
@@ -213,7 +226,7 @@ public:
*
* @return number of mods for which the profile has status information
**/
- unsigned int numMods() const { return m_ModStatus.size(); }
+ size_t numMods() const { return m_ModStatus.size(); }
/**
* @return the number of mods that can be enabled and where the priority can be modified
@@ -267,6 +280,12 @@ public:
void dumpModStatus() const;
+ QVariant setting(const QString &section, const QString &name,
+ const QVariant &fallback = QVariant());
+
+ void storeSetting(const QString &section, const QString &name,
+ const QVariant &value);
+
signals:
/**
@@ -311,7 +330,9 @@ private:
QDir m_Directory;
- MOBase::IPluginGame const * const m_GamePlugin;
+ QSettings *m_Settings;
+
+ const MOBase::IPluginGame *m_GamePlugin;
mutable QByteArray m_LastModlistHash;
std::vector<ModStatus> m_ModStatus;
diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp
index 4d0b3389..04c1876d 100644
--- a/src/profilesdialog.cpp
+++ b/src/profilesdialog.cpp
@@ -28,8 +28,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "report.h"
#include "transfersavesdialog.h"
#include "utility.h"
+#include "settings.h"
-#include <Qt>
#include <QDir>
#include <QDirIterator>
#include <QInputDialog>
@@ -56,9 +56,8 @@ ProfilesDialog::ProfilesDialog(const QString &profileName, MOBase::IPluginGame c
{
ui->setupUi(this);
- QDir profilesDir(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::profilesPath()));
+ QDir profilesDir(Settings::instance().getProfileDirectory());
profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
- m_ProfilesList = findChild<QListWidget*>("profilesList");
QDirIterator profileIter(profilesDir);
@@ -66,17 +65,15 @@ ProfilesDialog::ProfilesDialog(const QString &profileName, MOBase::IPluginGame c
profileIter.next();
QListWidgetItem *item = addItem(profileIter.filePath());
if (profileName == profileIter.fileName()) {
- m_ProfilesList->setCurrentItem(item);
+ ui->profilesList->setCurrentItem(item);
}
}
- QCheckBox *invalidationBox = findChild<QCheckBox*>("invalidationBox");
-
BSAInvalidation *invalidation = game->feature<BSAInvalidation>();
if (invalidation == nullptr) {
- invalidationBox->setToolTip(tr("Archive invalidation isn't required for this game."));
- invalidationBox->setEnabled(false);
+ ui->invalidationBox->setToolTip(tr("Archive invalidation isn't required for this game."));
+ ui->invalidationBox->setEnabled(false);
}
}
@@ -89,13 +86,13 @@ void ProfilesDialog::showEvent(QShowEvent *event)
{
TutorableDialog::showEvent(event);
- if (m_ProfilesList->count() == 0) {
- QPoint pos = m_ProfilesList->mapToGlobal(QPoint(0, 0));
- pos.rx() += m_ProfilesList->width() / 2;
- pos.ry() += (m_ProfilesList->height() / 2) - 20;
+ if (ui->profilesList->count() == 0) {
+ QPoint pos = ui->profilesList->mapToGlobal(QPoint(0, 0));
+ pos.rx() += ui->profilesList->width() / 2;
+ pos.ry() += (ui->profilesList->height() / 2) - 20;
QWhatsThis::showText(pos,
QObject::tr("Before you can use ModOrganizer, you need to create at least one profile. "
- "ATTENTION: Run the game at least once before creating a profile!"), m_ProfilesList);
+ "ATTENTION: Run the game at least once before creating a profile!"), ui->profilesList);
}
}
@@ -108,7 +105,7 @@ void ProfilesDialog::on_closeButton_clicked()
QListWidgetItem *ProfilesDialog::addItem(const QString &name)
{
QDir profileDir(name);
- QListWidgetItem *newItem = new QListWidgetItem(profileDir.dirName(), m_ProfilesList);
+ QListWidgetItem *newItem = new QListWidgetItem(profileDir.dirName(), ui->profilesList);
try {
newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(profileDir, m_Game))));
m_FailState = false;
@@ -121,10 +118,9 @@ QListWidgetItem *ProfilesDialog::addItem(const QString &name)
void ProfilesDialog::createProfile(const QString &name, bool useDefaultSettings)
{
try {
- QListWidget *profilesList = findChild<QListWidget*>("profilesList");
- QListWidgetItem *newItem = new QListWidgetItem(name, profilesList);
+ QListWidgetItem *newItem = new QListWidgetItem(name, ui->profilesList);
newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(name, m_Game, useDefaultSettings))));
- profilesList->addItem(newItem);
+ ui->profilesList->addItem(newItem);
m_FailState = false;
} catch (const std::exception&) {
m_FailState = true;
@@ -135,10 +131,9 @@ void ProfilesDialog::createProfile(const QString &name, bool useDefaultSettings)
void ProfilesDialog::createProfile(const QString &name, const Profile &reference)
{
try {
- QListWidget *profilesList = findChild<QListWidget*>("profilesList");
- QListWidgetItem *newItem = new QListWidgetItem(name, profilesList);
+ QListWidgetItem *newItem = new QListWidgetItem(name, ui->profilesList);
newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(Profile::createPtrFrom(name, reference, m_Game))));
- profilesList->addItem(newItem);
+ ui->profilesList->addItem(newItem);
m_FailState = false;
} catch (const std::exception&) {
m_FailState = true;
@@ -164,20 +159,23 @@ void ProfilesDialog::on_addProfileButton_clicked()
void ProfilesDialog::on_copyProfileButton_clicked()
{
bool okClicked;
- QString name = QInputDialog::getText(this, tr("Name"), tr("Please enter a name for the new profile"), QLineEdit::Normal, QString(), &okClicked);
+ QString name = QInputDialog::getText(
+ this, tr("Name"), tr("Please enter a name for the new profile"),
+ QLineEdit::Normal, QString(), &okClicked);
fixDirectoryName(name);
if (okClicked) {
if (name.size() > 0) {
- QListWidget *profilesList = findChild<QListWidget*>("profilesList");
-
try {
- const Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
+ const Profile::Ptr currentProfile = ui->profilesList->currentItem()
+ ->data(Qt::UserRole)
+ .value<Profile::Ptr>();
createProfile(name, *currentProfile);
} catch (const std::exception &e) {
reportError(tr("failed to copy profile: %1").arg(e.what()));
}
} else {
- QMessageBox::warning(this, tr("Invalid name"), tr("Invalid profile name"));
+ QMessageBox::warning(this, tr("Invalid name"),
+ tr("Invalid profile name"));
}
}
}
@@ -188,14 +186,11 @@ void ProfilesDialog::on_removeProfileButton_clicked()
QMessageBox::Yes | QMessageBox::No);
if (confirmBox.exec() == QMessageBox::Yes) {
- QListWidget *profilesList = findChild<QListWidget*>("profilesList");
-
- Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
+ Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
QString profilePath;
if (currentProfile.get() == nullptr) {
- profilePath = qApp->property("dataPath").toString()
- + "/" + QString::fromStdWString(AppConfig::profilesPath())
- + "/" + profilesList->currentItem()->text();
+ profilePath = Settings::instance().getProfileDirectory()
+ + "/" + ui->profilesList->currentItem()->text();
if (QMessageBox::question(this, tr("Profile broken"),
tr("This profile you're about to delete seems to be broken or the path is invalid. "
"I'm about to delete the following folder: \"%1\". Proceed?").arg(profilePath), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
@@ -206,7 +201,7 @@ void ProfilesDialog::on_removeProfileButton_clicked()
// we have to get rid of the it before deleting the directory
profilePath = currentProfile->absolutePath();
}
- QListWidgetItem* item = profilesList->takeItem(profilesList->currentRow());
+ QListWidgetItem* item = ui->profilesList->takeItem(ui->profilesList->currentRow());
if (item != nullptr) {
delete item;
}
@@ -271,53 +266,50 @@ void ProfilesDialog::on_invalidationBox_stateChanged(int state)
void ProfilesDialog::on_profilesList_currentItemChanged(QListWidgetItem *current, QListWidgetItem*)
{
- QCheckBox *invalidationBox = findChild<QCheckBox*>("invalidationBox");
- QCheckBox *localSavesBox = findChild<QCheckBox*>("localSavesBox");
- QPushButton *copyButton = findChild<QPushButton*>("copyProfileButton");
- QPushButton *removeButton = findChild<QPushButton*>("removeProfileButton");
- QPushButton *transferButton = findChild<QPushButton*>("transferButton");
- QPushButton *renameButton = findChild<QPushButton*>("renameButton");
-
if (current != nullptr) {
if (!current->data(Qt::UserRole).isValid()) return;
const Profile::Ptr currentProfile = current->data(Qt::UserRole).value<Profile::Ptr>();
try {
bool invalidationSupported = false;
- invalidationBox->blockSignals(true);
- invalidationBox->setChecked(currentProfile->invalidationActive(&invalidationSupported));
- invalidationBox->setEnabled(invalidationSupported);
- invalidationBox->blockSignals(false);
+ ui->invalidationBox->blockSignals(true);
+ ui->invalidationBox->setChecked(currentProfile->invalidationActive(&invalidationSupported));
+ ui->invalidationBox->setEnabled(invalidationSupported);
+ ui->invalidationBox->blockSignals(false);
bool localSaves = currentProfile->localSavesEnabled();
- transferButton->setEnabled(localSaves);
+ ui->transferButton->setEnabled(localSaves);
// prevent the stateChanged-event for the saves-box from triggering, otherwise it may think local saves
// were disabled and delete the files/rename the dir
- localSavesBox->blockSignals(true);
- localSavesBox->setChecked(localSaves);
- localSavesBox->blockSignals(false);
+ ui->localSavesBox->blockSignals(true);
+ ui->localSavesBox->setChecked(localSaves);
+ ui->localSavesBox->blockSignals(false);
- copyButton->setEnabled(true);
- removeButton->setEnabled(true);
- renameButton->setEnabled(true);
+ ui->copyProfileButton->setEnabled(true);
+ ui->removeProfileButton->setEnabled(true);
+ ui->renameButton->setEnabled(true);
+
+ ui->localIniFilesBox->blockSignals(true);
+ ui->localIniFilesBox->setChecked(currentProfile->localSettingsEnabled());
+ ui->localIniFilesBox->blockSignals(false);
} catch (const std::exception& E) {
reportError(tr("failed to determine if invalidation is active: %1").arg(E.what()));
- copyButton->setEnabled(false);
- removeButton->setEnabled(false);
- renameButton->setEnabled(false);
- invalidationBox->setChecked(false);
+ ui->copyProfileButton->setEnabled(false);
+ ui->removeProfileButton->setEnabled(false);
+ ui->renameButton->setEnabled(false);
+ ui->invalidationBox->setChecked(false);
}
} else {
- invalidationBox->setChecked(false);
- copyButton->setEnabled(false);
- removeButton->setEnabled(false);
- renameButton->setEnabled(false);
+ ui->invalidationBox->setChecked(false);
+ ui->copyProfileButton->setEnabled(false);
+ ui->removeProfileButton->setEnabled(false);
+ ui->renameButton->setEnabled(false);
}
}
void ProfilesDialog::on_localSavesBox_stateChanged(int state)
{
- Profile::Ptr currentProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
+ Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
if (currentProfile->enableLocalSaves(state == Qt::Checked)) {
ui->transferButton->setEnabled(state == Qt::Checked);
@@ -329,7 +321,17 @@ void ProfilesDialog::on_localSavesBox_stateChanged(int state)
void ProfilesDialog::on_transferButton_clicked()
{
- const Profile::Ptr currentProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
+ const Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
TransferSavesDialog transferDialog(*currentProfile, m_Game, this);
transferDialog.exec();
}
+
+void ProfilesDialog::on_localIniFilesBox_stateChanged(int state)
+{
+ Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
+
+ if (!currentProfile->enableLocalSettings(state == Qt::Checked)) {
+ // revert checkbox-state
+ ui->localIniFilesBox->setChecked(state != Qt::Checked);
+ }
+}
diff --git a/src/profilesdialog.h b/src/profilesdialog.h
index 0e79b94b..cab25f6a 100644
--- a/src/profilesdialog.h
+++ b/src/profilesdialog.h
@@ -61,6 +61,9 @@ protected:
virtual void showEvent(QShowEvent *event);
+private slots:
+ void on_localIniFilesBox_stateChanged(int state);
+
private:
QListWidgetItem *addItem(const QString &name);
diff --git a/src/profilesdialog.ui b/src/profilesdialog.ui
index 0c952877..9b4e7e62 100644
--- a/src/profilesdialog.ui
+++ b/src/profilesdialog.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>471</width>
- <height>318</height>
+ <width>482</width>
+ <height>332</height>
</rect>
</property>
<property name="windowTitle">
@@ -46,6 +46,16 @@ p, li { white-space: pre-wrap; }
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="localIniFilesBox">
+ <property name="text">
+ <string>Local Game Settings</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QCheckBox" name="invalidationBox">
<property name="toolTip">
<string>This ensures data files from mods are actually used. You want to enable this unless you use a different tool for Archive Invalidation.</string>
diff --git a/src/resources.qrc b/src/resources.qrc
index 8434b367..d357801c 100644
--- a/src/resources.qrc
+++ b/src/resources.qrc
@@ -26,7 +26,6 @@
<file alias="update_available">resources/software-update-available.png</file>
<file alias="important">resources/emblem-important.png</file>
<file alias="check">resources/check.png</file>
- <file>mo_icon.ico</file>
<file alias="warning">resources/dialog-warning.png</file>
<file alias="emblem_backup">resources/symbol-backup.png</file>
<file alias="icon_tools">resources/applications-accessories.png</file>
@@ -68,6 +67,8 @@
<file alias="active">resources/status_active.png</file>
<file alias="awaiting">resources/status_awaiting.png</file>
<file alias="inactive">resources/status_inactive.png</file>
+ <file alias="app_icon">resources/mo_icon.png</file>
+ <file alias="package">resources/package.png</file>
</qresource>
<qresource prefix="/MO/gui/content">
<file alias="plugin">resources/contents/jigsaw-piece.png</file>
diff --git a/src/resources/mo_icon.png b/src/resources/mo_icon.png
new file mode 100644
index 00000000..c926d722
--- /dev/null
+++ b/src/resources/mo_icon.png
Binary files differ
diff --git a/src/resources/package.png b/src/resources/package.png
new file mode 100644
index 00000000..4b55b504
--- /dev/null
+++ b/src/resources/package.png
Binary files differ
diff --git a/src/safewritefile.cpp b/src/safewritefile.cpp
deleted file mode 100644
index 007b3da9..00000000
--- a/src/safewritefile.cpp
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
-Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
-*/
-
-
-#include "safewritefile.h"
-#include <QStringList>
-#include <QCryptographicHash>
-
-
-using namespace MOBase;
-
-
-SafeWriteFile::SafeWriteFile(const QString &fileName)
-: m_FileName(fileName)
-{
- if (!m_TempFile.open()) {
- throw MyException(QObject::tr("failed to open temporary file"));
- }
-}
-
-
-QFile *SafeWriteFile::operator->() {
- Q_ASSERT(m_TempFile.isOpen());
- return &m_TempFile;
-}
-
-
-void SafeWriteFile::commit() {
- shellDeleteQuiet(m_FileName);
- m_TempFile.rename(m_FileName);
- m_TempFile.setAutoRemove(false);
- m_TempFile.close();
-}
-
-bool SafeWriteFile::commitIfDifferent(QByteArray &inHash) {
- QByteArray newHash = hash();
- if (newHash != inHash
- || !QFile::exists(m_FileName)) {
- commit();
- inHash = newHash;
- return true;
- } else {
- return false;
- }
-}
-
-QByteArray SafeWriteFile::hash()
-{
-
- qint64 pos = m_TempFile.pos();
- m_TempFile.seek(0);
- QByteArray data = m_TempFile.readAll();
- m_TempFile.seek(pos);
- return QCryptographicHash::hash(data, QCryptographicHash::Md5);
-}
diff --git a/src/selectiondialog.cpp b/src/selectiondialog.cpp
index aae95f56..55728751 100644
--- a/src/selectiondialog.cpp
+++ b/src/selectiondialog.cpp
@@ -79,6 +79,11 @@ QString SelectionDialog::getChoiceString()
}
}
+void SelectionDialog::disableCancel()
+{
+ ui->cancelButton->setEnabled(false);
+ ui->cancelButton->setHidden(true);
+}
void SelectionDialog::on_buttonBox_clicked(QAbstractButton *button)
{
diff --git a/src/selectiondialog.h b/src/selectiondialog.h
index 43ba9767..3c4b25df 100644
--- a/src/selectiondialog.h
+++ b/src/selectiondialog.h
@@ -53,6 +53,8 @@ public:
QVariant getChoiceData();
QString getChoiceString();
+ void disableCancel();
+
private slots:
void on_buttonBox_clicked(QAbstractButton *button);
diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp
index bc8908bc..c16be545 100644
--- a/src/selfupdater.cpp
+++ b/src/selfupdater.cpp
@@ -28,6 +28,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "downloadmanager.h"
#include "nexusinterface.h"
#include "nxmaccessmanager.h"
+#include "settings.h"
+#include "bbcode.h"
#include <versioninfo.h>
#include <report.h>
#include <util.h>
@@ -49,6 +51,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QUrl>
#include <QVariantList>
#include <QVariantMap>
+#include <QAbstractButton>
#include <Qt>
#include <QtDebug>
@@ -83,12 +86,10 @@ template <typename T> static T resolveFunction(QLibrary &lib, const char *name)
SelfUpdater::SelfUpdater(NexusInterface *nexusInterface)
: m_Parent(nullptr)
, m_Interface(nexusInterface)
- , m_UpdateRequestID(-1)
, m_Reply(nullptr)
, m_Attempts(3)
- , m_NexusDownload(nullptr)
{
- QLibrary archiveLib("dlls\\archive.dll");
+ QLibrary archiveLib(QCoreApplication::applicationDirPath() + "\\dlls\\archive.dll");
if (!archiveLib.load()) {
throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString()));
}
@@ -120,33 +121,71 @@ void SelfUpdater::setUserInterface(QWidget *widget)
void SelfUpdater::testForUpdate()
{
- if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) {
- emit updateAvailable();
- return;
- }
- if (m_UpdateRequestID == -1 && m_NexusDownload != nullptr) {
- m_UpdateRequestID = m_Interface->requestDescription(
- m_NexusDownload->nexusModOrganizerID(), this, QVariant(),
- QString(), m_NexusDownload);
- }
+ // TODO: if prereleases are disabled we could just request the latest release
+ // directly
+ m_GitHub.releases(GitHub::Repository("TanninOne", "modorganizer"),
+ [this](const QJsonArray &releases) {
+ QJsonObject newest;
+ for (const QJsonValue &releaseVal : releases) {
+ QJsonObject release = releaseVal.toObject();
+ if (!release["draft"].toBool() && (Settings::instance().usePrereleases()
+ || !release["prerelease"].toBool())) {
+ if (newest.empty() || (VersionInfo(release["tag_name"].toString())
+ > VersionInfo(newest["tag_name"].toString()))) {
+ newest = release;
+ }
+ }
+ }
+
+ if (!newest.empty()) {
+ VersionInfo newestVer(newest["tag_name"].toString());
+ if (newestVer > this->m_MOVersion) {
+ m_UpdateCandidate = newest;
+ qDebug("update available: %s -> %s",
+ qPrintable(this->m_MOVersion.displayString()),
+ qPrintable(newestVer.displayString()));
+ emit updateAvailable();
+ } else if (newestVer < this->m_MOVersion) {
+ // this could happen if the user switches from using prereleases to
+ // stable builds. Should we downgrade?
+ qDebug("this version is newer than the newest installed one: %s -> %s",
+ qPrintable(this->m_MOVersion.displayString()),
+ qPrintable(newestVer.displayString()));
+ }
+ }
+ });
}
void SelfUpdater::startUpdate()
{
- if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) {
- m_UpdateFile.setFileName(QCoreApplication::applicationDirPath() + "/mo_test_update.7z");
- installUpdate();
- return;
- }
+ // the button can't be pressed if there isn't an update candidate
+ Q_ASSERT(!m_UpdateCandidate.empty());
+
+ QMessageBox query(QMessageBox::Question,
+ tr("New update available (%1)")
+ .arg(m_UpdateCandidate["tag_name"].toString()),
+ BBCode::convertToHTML(m_UpdateCandidate["body"].toString()),
+ QMessageBox::Yes | QMessageBox::Cancel, m_Parent);
- if ((m_UpdateRequestID == -1) &&
- (!m_NewestVersion.isEmpty())) {
- if (QMessageBox::question(m_Parent, tr("Update"),
- tr("An update is available (newest version: %1), do you want to install it?").arg(m_NewestVersion),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- m_UpdateRequestID = m_Interface->requestFiles(m_NexusDownload->nexusModOrganizerID(),
- this, m_NewestVersion, "",
- m_NexusDownload);
+ query.button(QMessageBox::Yes)->setText(tr("Install"));
+
+ int res = query.exec();
+
+ if (query.result() == QMessageBox::Yes) {
+ bool found = false;
+ for (const QJsonValue &assetVal : m_UpdateCandidate["assets"].toArray()) {
+ QJsonObject asset = assetVal.toObject();
+ if (asset["content_type"].toString() == "application/x-msdownload") {
+ openOutputFile(asset["name"].toString());
+ download(asset["browser_download_url"].toString());
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ QMessageBox::warning(
+ m_Parent, tr("Download failed"),
+ tr("Failed to find correct download, please try again later."));
}
}
}
@@ -174,15 +213,21 @@ void SelfUpdater::closeProgress()
}
}
-void SelfUpdater::download(const QString &downloadLink, const QString &fileName)
+void SelfUpdater::openOutputFile(const QString &fileName)
+{
+ QString outputPath = QDir::fromNativeSeparators(qApp->property("dataPath").toString()) + "/" + fileName;
+ qDebug("downloading to %s", qPrintable(outputPath));
+ m_UpdateFile.setFileName(outputPath);
+ m_UpdateFile.open(QIODevice::WriteOnly);
+}
+
+void SelfUpdater::download(const QString &downloadLink)
{
QNetworkAccessManager *accessManager = m_Interface->getAccessManager();
QUrl dlUrl(downloadLink);
QNetworkRequest request(dlUrl);
m_Canceled = false;
m_Reply = accessManager->get(request);
- m_UpdateFile.setFileName(QDir::fromNativeSeparators(qApp->property("dataPath").toString()).append("/").append(fileName));
- m_UpdateFile.open(QIODevice::WriteOnly);
showProgress();
connect(m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64)));
@@ -220,6 +265,12 @@ void SelfUpdater::downloadFinished()
int error = QNetworkReply::NoError;
if (m_Reply != nullptr) {
+ if (m_Reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 302) {
+ QUrl url = m_Reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
+ m_UpdateFile.reset();
+ download(url.toString());
+ return;
+ }
m_UpdateFile.write(m_Reply->readAll());
error = m_Reply->error();
@@ -265,237 +316,25 @@ void SelfUpdater::downloadCancel()
void SelfUpdater::installUpdate()
{
- const QString mopath = QDir::fromNativeSeparators(qApp->property("dataPath").toString());
-
- QString backupPath = mopath + "/update_backup";
- QDir().mkdir(backupPath);
+ const QString mopath
+ = QDir::fromNativeSeparators(qApp->property("dataPath").toString());
- // rename files that are currently open so we can unpack the update
- if (!m_ArchiveHandler->open(m_UpdateFile.fileName(), nullptr)) {
- throw MyException(tr("failed to open archive \"%1\": %2")
- .arg(m_UpdateFile.fileName())
- .arg(InstallationManager::getErrorString(m_ArchiveHandler->getLastError())));
- }
+ HINSTANCE res = ::ShellExecuteW(
+ nullptr, L"open", m_UpdateFile.fileName().toStdWString().c_str(), nullptr,
+ nullptr, SW_SHOW);
- // move all files contained in the archive out of the way,
- // otherwise we can't overwrite everything
- FileData* const *data;
- size_t size;
- m_ArchiveHandler->getFileList(data, size);
-
- for (size_t i = 0; i < size; ++i) {
- QString outputName = data[i]->getFileName();
- if (outputName.startsWith("ModOrganizer\\", Qt::CaseInsensitive)) {
- outputName = outputName.mid(13);
- data[i]->addOutputFileName(outputName);
- } else if (outputName != "ModOrganizer") {
- data[i]->addOutputFileName(outputName);
- }
- QFileInfo file(mopath + "/" + outputName);
- if (file.exists() && file.isFile()) {
- if (!shellMove(QStringList(mopath + "/" + outputName),
- QStringList(backupPath + "/" + outputName))) {
- reportError(tr("failed to move outdated files: %1. Please update manually.").arg(windowsErrorString(::GetLastError())));
- return;
- }
- }
- }
-
- // now unpack the archive into the mo directory
- if (!m_ArchiveHandler->extract(mopath, nullptr, nullptr,
- new MethodCallback<SelfUpdater, void, QString const &>(this, &SelfUpdater::report7ZipError))) {
- throw std::runtime_error("extracting failed");
+ if (res > (HINSTANCE)32) {
+ QCoreApplication::quit();
+ } else {
+ reportError(tr("Failed to start %1: %2")
+ .arg(m_UpdateFile.fileName())
+ .arg((int)res));
}
- m_ArchiveHandler->close();
-
m_UpdateFile.remove();
-
- QMessageBox::information(m_Parent, tr("Update"), tr("Update installed, Mod Organizer will now be restarted."));
-
- QProcess newProcess;
- if (QFile::exists(mopath + "/ModOrganizer.exe")) {
- newProcess.startDetached(mopath + "/ModOrganizer.exe", QStringList("update"));
- } else {
- newProcess.startDetached(mopath + "/ModOrganiser.exe", QStringList("update"));
- }
- emit restart();
}
void SelfUpdater::report7ZipError(QString const &errorMessage)
{
QMessageBox::critical(m_Parent, tr("Error"), errorMessage);
}
-
-
-QString SelfUpdater::retrieveNews(const QString &description)
-{
- QStringList temp = description.split("[s][/s]");
- if (temp.length() < 2) {
- return QString();
- } else {
- return temp.at(1);
- }
-}
-
-
-void SelfUpdater::nxmDescriptionAvailable(int, QVariant, QVariant resultData, int requestID)
-{
- if (requestID == m_UpdateRequestID) {
- m_UpdateRequestID = -1;
-
- QVariantMap result = resultData.toMap();
- QString motd = retrieveNews(result["description"].toString()).trimmed();
- if (motd.length() != 0) {
- emit motdAvailable(motd);
- }
-
- m_NewestVersion = result["version"].toString();
- if (m_NewestVersion.isEmpty()) {
- QTimer::singleShot(5000, this, SLOT(testForUpdate()));
- }
- VersionInfo currentVersion(m_MOVersion);
- VersionInfo newestVersion(m_NewestVersion);
-
- if (!m_NewestVersion.isEmpty() && (currentVersion < newestVersion)) {
- emit updateAvailable();
- } else if (newestVersion < currentVersion) {
- qDebug("this version is newer than the current version on nexus (%s vs %s)",
- currentVersion.canonicalString().toUtf8().constData(),
- newestVersion.canonicalString().toUtf8().constData());
- }
- }
-}
-
-
-void SelfUpdater::nxmFilesAvailable(int, QVariant userData, QVariant resultData, int requestID)
-{
- if (requestID != m_UpdateRequestID) {
- return;
- }
- QString version = userData.toString();
-
- m_UpdateRequestID = -1;
-
- if (!resultData.canConvert<QVariantList>()) {
- qCritical("invalid files result: %s", resultData.toString().toUtf8().constData());
- reportError(tr("Failed to parse response. Please report this as a bug and include the file mo_interface.log."));
- return;
- }
-
- QVariantList result = resultData.toList();
-
- QRegExp updateExpList(QString("updates version ([0-9., ]*) to %1").arg(version));
- QRegExp updateExpRange(QString("updates version ([0-9.]*) - ([0-9.]*) to %1").arg(version));
- int updateFileID = -1;
- QString updateFileName;
- int mainFileID = -1;
- QString mainFileName;
- int mainFileSize = 0;
-
- for(QVariant file : result) {
- QVariantMap fileInfo = file.toMap();
- if (!fileInfo["uri"].toString().endsWith(".7z")) {
- continue;
- }
-
- if (fileInfo["version"].toString() == version) {
- if (fileInfo["category_id"].toInt() == 2) {
- QString description = fileInfo["description"].toString();
- // update
- if (updateExpList.indexIn(description) != -1) {
- // there is an update for the newest version of MO, but does
- // it apply to the current version?
- QStringList supportedVersions = updateExpList.cap(1).split(QRegExp(",[ ]*"), QString::SkipEmptyParts);
- if (supportedVersions.contains(m_MOVersion.canonicalString())) {
- updateFileID = fileInfo["id"].toInt();
- updateFileName = fileInfo["uri"].toString();
- } else {
- qDebug("update not supported from %s", m_MOVersion.canonicalString().toUtf8().constData());
- }
- } else if (updateExpRange.indexIn(description) != -1) {
- VersionInfo rangeLowEnd(updateExpRange.cap(1));
- VersionInfo rangeHighEnd(updateExpRange.cap(2));
- if ((rangeLowEnd <= m_MOVersion) &&
- (m_MOVersion <= rangeHighEnd)) {
- updateFileID = fileInfo["id"].toInt();
- updateFileName = fileInfo["uri"].toString();
- break;
- } else {
- qDebug("update not supported from %s", m_MOVersion.canonicalString().toUtf8().constData());
- }
- } else {
- qWarning("invalid update description: %s",
- description.toUtf8().constData());
- }
- } else if (fileInfo["category_id"].toInt() == 1) {
- mainFileID = fileInfo["id"].toInt();
- mainFileName = fileInfo["uri"].toString();
- mainFileSize = fileInfo["size"].toInt();
- }
- }
- }
-
- if (updateFileID != -1) {
- qDebug("update available: %d", updateFileID);
- m_UpdateRequestID = m_Interface->requestDownloadURL(m_NexusDownload->nexusModOrganizerID(),
- updateFileID, this, updateFileName, "",
- m_NexusDownload);
- } else if (mainFileID != -1) {
- qDebug("full download required: %d", mainFileID);
- if (QMessageBox::question(m_Parent, tr("Update"),
- tr("No incremental update available for this version, "
- "the complete package needs to be downloaded (%1 kB)").arg(mainFileSize),
- QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
- m_UpdateRequestID = m_Interface->requestDownloadURL(m_NexusDownload->nexusModOrganizerID(),
- mainFileID, this, mainFileName, "",
- m_NexusDownload);
- }
- } else {
- qCritical("no file for update found");
- MessageDialog::showMessage(tr("no file for update found. Please update manually."), m_Parent);
- closeProgress();
- }
-}
-
-
-void SelfUpdater::nxmRequestFailed(int, int, QVariant, int requestID, const QString &errorMessage)
-{
- if (requestID == m_UpdateRequestID) {
- m_UpdateRequestID = -1;
- if (m_Attempts > 0) {
- QTimer::singleShot(60000, this, SLOT(testForUpdate()));
- --m_Attempts;
- } else {
- qWarning("Failed to retrieve update information: %s", qPrintable(errorMessage));
- MessageDialog::showMessage(tr("Failed to retrieve update information: %1").arg(errorMessage), m_Parent, false);
- }
- }
-}
-
-
-void SelfUpdater::nxmDownloadURLsAvailable(int, int, QVariant userData, QVariant resultData, int requestID)
-{
- if (requestID == m_UpdateRequestID) {
- m_UpdateRequestID = -1;
- QVariantList serverList = resultData.toList();
- if (serverList.count() != 0) {
- std::map<QString, int> dummy;
- qSort(serverList.begin(), serverList.end(), boost::bind(&DownloadManager::ServerByPreference, dummy, _1, _2));
-
-
- QVariantMap dlServer = serverList.first().toMap();
-
- download(dlServer["URI"].toString(), userData.toString());
- } else {
- MessageDialog::showMessage(tr("No download server available. Please try again later."), m_Parent);
- closeProgress();
- }
- }
-}
-
-/** Set the game check for updates */
-void SelfUpdater::setNexusDownload(MOBase::IPluginGame const *game)
-{
- m_NexusDownload = game;
-}
diff --git a/src/selfupdater.h b/src/selfupdater.h
index 37021b42..4743f6a4 100644
--- a/src/selfupdater.h
+++ b/src/selfupdater.h
@@ -22,6 +22,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <versioninfo.h>
+#include <github.h>
class Archive;
class NexusInterface;
@@ -87,9 +88,6 @@ public:
**/
MOBase::VersionInfo getVersion() const { return m_MOVersion; }
- /** Set the game check for updates */
- void setNexusDownload(MOBase::IPluginGame const *game);
-
public slots:
/**
@@ -97,11 +95,6 @@ public slots:
**/
void testForUpdate();
- void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID);
- void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID);
- void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorMessage);
- void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID);
-
signals:
/**
@@ -121,10 +114,10 @@ signals:
private:
- void download(const QString &downloadLink, const QString &fileName);
+ void openOutputFile(const QString &fileName);
+ void download(const QString &downloadLink);
void installUpdate();
void report7ZipError(const QString &errorMessage);
- QString retrieveNews(const QString &description);
void showProgress();
void closeProgress();
@@ -140,8 +133,6 @@ private:
QWidget *m_Parent;
MOBase::VersionInfo m_MOVersion;
NexusInterface *m_Interface;
- int m_UpdateRequestID;
- QString m_NewestVersion;
QFile m_UpdateFile;
QNetworkReply *m_Reply;
QProgressDialog *m_Progress { nullptr };
@@ -150,7 +141,9 @@ private:
Archive *m_ArchiveHandler;
- MOBase::IPluginGame const *m_NexusDownload;
+ GitHub m_GitHub;
+ QJsonObject m_UpdateCandidate;
+
};
diff --git a/src/settings.cpp b/src/settings.cpp
index 03e13826..fc740302 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -41,7 +41,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QListWidgetItem>
#include <QLocale>
#include <QMessageBox>
+#include <QApplication>
#include <QRegExp>
+#include <QDir>
#include <QStringList>
#include <QVariantMap>
@@ -145,7 +147,7 @@ void Settings::registerPlugin(IPlugin *plugin)
m_Plugins.push_back(plugin);
m_PluginSettings.insert(plugin->name(), QMap<QString, QVariant>());
m_PluginDescriptions.insert(plugin->name(), QMap<QString, QVariant>());
- foreach (const PluginSetting &setting, plugin->settings()) {
+ for (const PluginSetting &setting : plugin->settings()) {
QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue);
if (!temp.convert(setting.defaultValue.type())) {
qWarning("failed to interpret \"%s\" as correct type for \"%s\" in plugin \"%s\", using default",
@@ -199,11 +201,16 @@ QString Settings::getSteamAppID() const
return m_Settings.value("Settings/app_id", m_GamePlugin->steamAPPId()).toString();
}
+bool Settings::usePrereleases() const
+{
+ return m_Settings.value("Settings/use_prereleases", false).toBool();
+}
+
void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond)
{
m_Settings.beginGroup("Servers");
- foreach (const QString &serverKey, m_Settings.childKeys()) {
+ for (const QString &serverKey : m_Settings.childKeys()) {
QVariantMap data = m_Settings.value(serverKey).toMap();
if (serverKey == serverName) {
data["downloadCount"] = data["downloadCount"].toInt() + 1;
@@ -221,7 +228,7 @@ std::map<QString, int> Settings::getPreferredServers()
std::map<QString, int> result;
m_Settings.beginGroup("Servers");
- foreach (const QString &serverKey, m_Settings.childKeys()) {
+ for (const QString &serverKey : m_Settings.childKeys()) {
QVariantMap data = m_Settings.value(serverKey).toMap();
int preference = data["preferred"].toInt();
if (preference > 0) {
@@ -233,29 +240,54 @@ std::map<QString, int> Settings::getPreferredServers()
return result;
}
-QString Settings::getConfigurablePath(const QString &key, const QString &def) const
+QString Settings::getConfigurablePath(const QString &key,
+ const QString &def,
+ bool resolve) const
+{
+ QString result = QDir::fromNativeSeparators(
+ m_Settings.value(QString("settings/") + key, QString("%BASE_DIR%/") + def)
+ .toString());
+ if (resolve) {
+ result.replace("%BASE_DIR%", getBaseDirectory());
+ }
+ return result;
+}
+
+QString Settings::getBaseDirectory() const
{
- return QDir::fromNativeSeparators(m_Settings.value(QString("settings/") + key, qApp->property("dataPath").toString() + "/" + def).toString());
+ return QDir::fromNativeSeparators(m_Settings.value(
+ "settings/base_directory", qApp->property("dataPath").toString()).toString());
}
-QString Settings::getDownloadDirectory() const
+QString Settings::getDownloadDirectory(bool resolve) const
{
- return getConfigurablePath("download_directory", ToQString(AppConfig::downloadPath()));
+ return getConfigurablePath("download_directory", ToQString(AppConfig::downloadPath()), resolve);
}
-QString Settings::getCacheDirectory() const
+QString Settings::getCacheDirectory(bool resolve) const
{
- return getConfigurablePath("cache_directory", ToQString(AppConfig::cachePath()));
+ return getConfigurablePath("cache_directory", ToQString(AppConfig::cachePath()), resolve);
}
-QString Settings::getModDirectory() const
+QString Settings::getModDirectory(bool resolve) const
{
- return getConfigurablePath("mod_directory", ToQString(AppConfig::modsPath()));
+ return getConfigurablePath("mod_directory", ToQString(AppConfig::modsPath()), resolve);
+}
+
+QString Settings::getProfileDirectory(bool resolve) const
+{
+ return getConfigurablePath("profiles_directory", ToQString(AppConfig::profilesPath()), resolve);
+}
+
+QString Settings::getOverwriteDirectory(bool resolve) const
+{
+ return getConfigurablePath("overwrite_directory",
+ ToQString(AppConfig::overwritePath()), resolve);
}
QString Settings::getNMMVersion() const
{
- static const QString MIN_NMM_VERSION = "0.52.3";
+ static const QString MIN_NMM_VERSION = "0.61.13";
QString result = m_Settings.value("Settings/nmm_version", MIN_NMM_VERSION).toString();
if (VersionInfo(result) < VersionInfo(MIN_NMM_VERSION)) {
result = MIN_NMM_VERSION;
@@ -431,7 +463,7 @@ void Settings::updateServers(const QList<ServerInfo> &servers)
m_Settings.beginGroup("Servers");
QStringList oldServerKeys = m_Settings.childKeys();
- foreach (const ServerInfo &server, servers) {
+ for (const ServerInfo &server : servers) {
if (!oldServerKeys.contains(server.name)) {
// not yet known server
QVariantMap newVal;
@@ -453,7 +485,7 @@ void Settings::updateServers(const QList<ServerInfo> &servers)
// clean up unavailable servers
QDate now = QDate::currentDate();
- foreach (const QString &key, m_Settings.childKeys()) {
+ for (const QString &key : m_Settings.childKeys()) {
QVariantMap val = m_Settings.value(key).toMap();
QDate lastSeen = val["lastSeen"].toDate();
if (lastSeen.daysTo(now) > 30) {
@@ -477,7 +509,7 @@ void Settings::writePluginBlacklist()
{
m_Settings.beginWriteArray("pluginBlacklist");
int idx = 0;
- foreach (const QString &plugin, m_PluginBlacklist) {
+ for (const QString &plugin : m_PluginBlacklist) {
m_Settings.setArrayIndex(idx++);
m_Settings.setValue("name", plugin);
}
@@ -523,12 +555,7 @@ void Settings::addLanguages(QComboBox *languageBox)
void Settings::addStyles(QComboBox *styleBox)
{
styleBox->addItem("None", "");
-#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
styleBox->addItem("Fusion", "Fusion");
-#else
- styleBox->addItem("Plastique", "Plastique");
- styleBox->addItem("Cleanlooks", "Cleanlooks");
-#endif
QDirIterator langIter(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()), QStringList("*.qss"), QDir::Files);
while (langIter.hasNext()) {
@@ -553,6 +580,7 @@ void Settings::query(QWidget *parent)
std::vector<std::unique_ptr<SettingsTab>> tabs;
tabs.push_back(std::unique_ptr<SettingsTab>(new GeneralTab(this, dialog)));
+ tabs.push_back(std::unique_ptr<SettingsTab>(new PathsTab(this, dialog)));
tabs.push_back(std::unique_ptr<SettingsTab>(new NexusTab(this, dialog)));
tabs.push_back(std::unique_ptr<SettingsTab>(new SteamTab(this, dialog)));
tabs.push_back(std::unique_ptr<SettingsTab>(new PluginsTab(this, dialog)));
@@ -566,61 +594,61 @@ void Settings::query(QWidget *parent)
}
}
-Settings::SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog) :
- m_parent(m_parent),
- m_Settings(m_parent->m_Settings),
- m_dialog(m_dialog)
-{}
+Settings::SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog)
+ : m_parent(m_parent)
+ , m_Settings(m_parent->m_Settings)
+ , m_dialog(m_dialog)
+{
+}
Settings::SettingsTab::~SettingsTab()
{}
-Settings::GeneralTab::GeneralTab(Settings *m_parent, SettingsDialog &m_dialog) :
- Settings::SettingsTab(m_parent, m_dialog),
- m_languageBox(m_dialog.findChild<QComboBox*>("languageBox")),
- m_styleBox(m_dialog.findChild<QComboBox*>("styleBox")),
- m_logLevelBox(m_dialog.findChild<QComboBox*>("logLevelBox")),
- m_downloadDirEdit(m_dialog.findChild<QLineEdit*>("downloadDirEdit")),
- m_modDirEdit(m_dialog.findChild<QLineEdit*>("modDirEdit")),
- m_cacheDirEdit(m_dialog.findChild<QLineEdit*>("cacheDirEdit")),
- m_compactBox(m_dialog.findChild<QCheckBox*>("compactBox")),
- m_showMetaBox(m_dialog.findChild<QCheckBox*>("showMetaBox"))
+Settings::GeneralTab::GeneralTab(Settings *m_parent, SettingsDialog &m_dialog)
+ : Settings::SettingsTab(m_parent, m_dialog)
+ , m_languageBox(m_dialog.findChild<QComboBox *>("languageBox"))
+ , m_styleBox(m_dialog.findChild<QComboBox *>("styleBox"))
+ , m_logLevelBox(m_dialog.findChild<QComboBox *>("logLevelBox"))
+ , m_compactBox(m_dialog.findChild<QCheckBox *>("compactBox"))
+ , m_showMetaBox(m_dialog.findChild<QCheckBox *>("showMetaBox"))
+ , m_usePrereleaseBox(m_dialog.findChild<QCheckBox *>("usePrereleaseBox"))
+{
+ // FIXME I think 'addLanguages' lives in here not in parent
+ m_parent->addLanguages(m_languageBox);
{
- //FIXME I think 'addLanguages' lives in here not in parent
- m_parent->addLanguages(m_languageBox);
- {
- QString languageCode = m_parent->language();
- int currentID = m_languageBox->findData(languageCode);
- // I made a mess. :( Most languages are stored with only the iso country code (2 characters like "de") but chinese
- // with the exact language variant (zh_TW) so I have to search for both variants
- if (currentID == -1) {
- currentID = m_languageBox->findData(languageCode.mid(0, 2));
- }
- if (currentID != -1) {
- m_languageBox->setCurrentIndex(currentID);
- }
+ QString languageCode = m_parent->language();
+ int currentID = m_languageBox->findData(languageCode);
+ // I made a mess. :( Most languages are stored with only the iso country
+ // code (2 characters like "de") but chinese
+ // with the exact language variant (zh_TW) so I have to search for both
+ // variants
+ if (currentID == -1) {
+ currentID = m_languageBox->findData(languageCode.mid(0, 2));
+ }
+ if (currentID != -1) {
+ m_languageBox->setCurrentIndex(currentID);
}
+ }
- //FIXME I think addStyles lives in here not in parent
- m_parent->addStyles(m_styleBox);
- {
- int currentID = m_styleBox->findData(m_Settings.value("Settings/style", "").toString());
- if (currentID != -1) {
- m_styleBox->setCurrentIndex(currentID);
- }
+ // FIXME I think addStyles lives in here not in parent
+ m_parent->addStyles(m_styleBox);
+ {
+ int currentID = m_styleBox->findData(
+ m_Settings.value("Settings/style", "").toString());
+ if (currentID != -1) {
+ m_styleBox->setCurrentIndex(currentID);
}
+ }
- m_logLevelBox->setCurrentIndex(m_parent->logLevel());
- m_downloadDirEdit->setText(m_parent->getDownloadDirectory());
- m_modDirEdit->setText(m_parent->getModDirectory());
- m_cacheDirEdit->setText(m_parent->getCacheDirectory());
- m_compactBox->setChecked(m_parent->compactDownloads());
- m_showMetaBox->setChecked(m_parent->metaDownloads());
+ m_logLevelBox->setCurrentIndex(m_parent->logLevel());
+ m_compactBox->setChecked(m_parent->compactDownloads());
+ m_showMetaBox->setChecked(m_parent->metaDownloads());
+ m_usePrereleaseBox->setChecked(m_parent->usePrereleases());
}
void Settings::GeneralTab::update()
{
- QString oldLanguage = m_Settings.value("Settings/language", "en_US").toString();
+ QString oldLanguage = m_parent->language();
QString newLanguage = m_languageBox->itemData(m_languageBox->currentIndex()).toString();
if (newLanguage != oldLanguage) {
m_Settings.setValue("Settings/language", newLanguage);
@@ -636,68 +664,104 @@ void Settings::GeneralTab::update()
m_Settings.setValue("Settings/log_level", m_logLevelBox->currentIndex());
- { // advanced settings
- if ((QDir::fromNativeSeparators(m_modDirEdit->text()) != QDir::fromNativeSeparators(m_parent->getModDirectory())) &&
- (QMessageBox::question(nullptr, tr("Confirm"), tr("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?"),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)) {
- m_modDirEdit->setText(m_parent->getModDirectory());
- }
- if (!QDir(m_downloadDirEdit->text()).exists()) {
- QDir().mkpath(m_downloadDirEdit->text());
- }
- if (QFileInfo(m_downloadDirEdit->text()) !=
- QFileInfo(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::downloadPath()))) {
- m_Settings.setValue("Settings/download_directory", QDir::toNativeSeparators(m_downloadDirEdit->text()));
- } else {
- m_Settings.remove("Settings/download_directory");
- }
+ m_Settings.setValue("Settings/compact_downloads", m_compactBox->isChecked());
+ m_Settings.setValue("Settings/meta_downloads", m_showMetaBox->isChecked());
+ m_Settings.setValue("Settings/use_prereleases", m_usePrereleaseBox->isChecked());
+}
- if (!QDir(m_modDirEdit->text()).exists()) {
- QDir().mkpath(m_modDirEdit->text());
- }
- if (QFileInfo(m_modDirEdit->text()) !=
- QFileInfo(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::modsPath()))) {
- m_Settings.setValue("Settings/mod_directory", QDir::toNativeSeparators(m_modDirEdit->text()));
- } else {
- m_Settings.remove("Settings/mod_directory");
- }
+Settings::PathsTab::PathsTab(Settings *parent, SettingsDialog &dialog)
+ : SettingsTab(parent, dialog)
+ , m_baseDirEdit(m_dialog.findChild<QLineEdit *>("baseDirEdit"))
+ , m_downloadDirEdit(m_dialog.findChild<QLineEdit *>("downloadDirEdit"))
+ , m_modDirEdit(m_dialog.findChild<QLineEdit *>("modDirEdit"))
+ , m_cacheDirEdit(m_dialog.findChild<QLineEdit *>("cacheDirEdit"))
+ , m_profilesDirEdit(m_dialog.findChild<QLineEdit *>("profilesDirEdit"))
+ , m_overwriteDirEdit(m_dialog.findChild<QLineEdit *>("overwriteDirEdit"))
+{
+ m_baseDirEdit->setText(m_parent->getBaseDirectory());
+
+ QString basePath = parent->getBaseDirectory();
+ QDir baseDir(basePath);
+ for (const auto &dir : {
+ std::make_pair(m_downloadDirEdit, m_parent->getDownloadDirectory(false)),
+ std::make_pair(m_modDirEdit, m_parent->getModDirectory(false)),
+ std::make_pair(m_cacheDirEdit, m_parent->getCacheDirectory(false)),
+ std::make_pair(m_profilesDirEdit, m_parent->getProfileDirectory(false)),
+ std::make_pair(m_overwriteDirEdit, m_parent->getOverwriteDirectory(false))
+ }) {
+ QString storePath = baseDir.relativeFilePath(dir.second);
+ storePath = dir.second;
+ dir.first->setText(storePath);
+ }
+}
- if (!QDir(m_cacheDirEdit->text()).exists()) {
- QDir().mkpath(m_cacheDirEdit->text());
+void Settings::PathsTab::update()
+{
+ typedef std::tuple<QString, QString, std::wstring> Directory;
+
+ QString basePath = m_parent->getBaseDirectory();
+
+ for (const Directory &dir :{
+ Directory{m_downloadDirEdit->text(), "download_directory", AppConfig::downloadPath()},
+ Directory{m_cacheDirEdit->text(), "cache_directory", AppConfig::cachePath()},
+ Directory{m_modDirEdit->text(), "mod_directory", AppConfig::modsPath()},
+ Directory{m_overwriteDirEdit->text(), "overwrite_directory", AppConfig::overwritePath()},
+ Directory{m_profilesDirEdit->text(), "profiles_directory", AppConfig::profilesPath()}
+ }) {
+ QString path, settingsKey;
+ std::wstring defaultName;
+ std::tie(path, settingsKey, defaultName) = dir;
+
+ settingsKey = QString("Settings/%1").arg(settingsKey);
+
+ QString realPath = path;
+ realPath.replace("%BASE_DIR%", m_baseDirEdit->text());
+
+ if (!QDir(realPath).exists()) {
+ if (!QDir().mkpath(realPath)) {
+ QMessageBox::warning(qApp->activeWindow(), tr("Error"),
+ tr("Failed to create \"%1\", you may not have the "
+ "necessary permission. path remains unchanged.")
+ .arg(realPath));
+ }
}
- if (QFileInfo(m_cacheDirEdit->text()) !=
- QFileInfo(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::cachePath()))) {
- m_Settings.setValue("Settings/cache_directory", QDir::toNativeSeparators(m_cacheDirEdit->text()));
+
+ if (QFileInfo(realPath)
+ != QFileInfo(basePath + "/" + QString::fromStdWString(defaultName))) {
+ m_Settings.setValue(settingsKey, path);
} else {
- m_Settings.remove("Settings/cache_directory");
+ m_Settings.remove(settingsKey);
}
}
- m_Settings.setValue("Settings/compact_downloads", m_compactBox->isChecked());
- m_Settings.setValue("Settings/meta_downloads", m_showMetaBox->isChecked());
+ if (QFileInfo(m_baseDirEdit->text()) !=
+ QFileInfo(qApp->property("dataPath").toString())) {
+ m_Settings.setValue("Settings/base_directory", m_baseDirEdit->text());
+ } else {
+ m_Settings.remove("Settings/base_directory");
+ }
}
-Settings::NexusTab::NexusTab(Settings *m_parent, SettingsDialog &m_dialog) :
- Settings::SettingsTab(m_parent, m_dialog),
- m_loginCheckBox(m_dialog.findChild<QCheckBox*>("loginCheckBox")),
- m_usernameEdit(m_dialog.findChild<QLineEdit*>("usernameEdit")),
- m_passwordEdit(m_dialog.findChild<QLineEdit*>("passwordEdit")),
- m_offlineBox(m_dialog.findChild<QCheckBox*>("offlineBox")),
- m_proxyBox(m_dialog.findChild<QCheckBox*>("proxyBox")),
- m_knownServersList(m_dialog.findChild<QListWidget*>("knownServersList")),
- m_preferredServersList(m_dialog.findChild<QListWidget*>("preferredServersList"))
+Settings::NexusTab::NexusTab(Settings *parent, SettingsDialog &dialog)
+ : Settings::SettingsTab(parent, dialog)
+ , m_loginCheckBox(dialog.findChild<QCheckBox *>("loginCheckBox"))
+ , m_usernameEdit(dialog.findChild<QLineEdit *>("usernameEdit"))
+ , m_passwordEdit(dialog.findChild<QLineEdit *>("passwordEdit"))
+ , m_offlineBox(dialog.findChild<QCheckBox *>("offlineBox"))
+ , m_proxyBox(dialog.findChild<QCheckBox *>("proxyBox"))
+ , m_knownServersList(dialog.findChild<QListWidget *>("knownServersList"))
+ , m_preferredServersList(
+ dialog.findChild<QListWidget *>("preferredServersList"))
{
- if (m_parent->automaticLoginEnabled()) {
+ if (parent->automaticLoginEnabled()) {
m_loginCheckBox->setChecked(true);
m_usernameEdit->setText(m_Settings.value("Settings/nexus_username", "").toString());
m_passwordEdit->setText(deObfuscate(m_Settings.value("Settings/nexus_password", "").toString()));
}
- m_offlineBox->setChecked(m_parent->offlineMode());
- m_proxyBox->setChecked(m_parent->useProxy());
+ m_offlineBox->setChecked(parent->offlineMode());
+ m_proxyBox->setChecked(parent->useProxy());
// display server preferences
m_Settings.beginGroup("Servers");
@@ -757,11 +821,10 @@ void Settings::NexusTab::update()
m_Settings.endGroup();
}
-
-Settings::SteamTab::SteamTab(Settings *m_parent, SettingsDialog &m_dialog) :
- Settings::SettingsTab(m_parent, m_dialog),
- m_steamUserEdit(m_dialog.findChild<QLineEdit*>("steamUserEdit")),
- m_steamPassEdit(m_dialog.findChild<QLineEdit*>("steamPassEdit"))
+Settings::SteamTab::SteamTab(Settings *m_parent, SettingsDialog &m_dialog)
+ : Settings::SettingsTab(m_parent, m_dialog)
+ , m_steamUserEdit(m_dialog.findChild<QLineEdit *>("steamUserEdit"))
+ , m_steamPassEdit(m_dialog.findChild<QLineEdit *>("steamPassEdit"))
{
if (m_Settings.contains("Settings/steam_username")) {
m_steamUserEdit->setText(m_Settings.value("Settings/steam_username", "").toString());
@@ -777,10 +840,10 @@ void Settings::SteamTab::update()
m_parent->setSteamLogin(m_steamUserEdit->text(), m_steamPassEdit->text());
}
-Settings::PluginsTab::PluginsTab(Settings *m_parent, SettingsDialog &m_dialog) :
- Settings::SettingsTab(m_parent, m_dialog),
- m_pluginsList(m_dialog.findChild<QListWidget*>("pluginsList")),
- m_pluginBlacklistList(m_dialog.findChild<QListWidget*>("pluginBlacklist"))
+Settings::PluginsTab::PluginsTab(Settings *m_parent, SettingsDialog &m_dialog)
+ : Settings::SettingsTab(m_parent, m_dialog)
+ , m_pluginsList(m_dialog.findChild<QListWidget *>("pluginsList"))
+ , m_pluginBlacklistList(m_dialog.findChild<QListWidget *>("pluginBlacklist"))
{
// display plugin settings
for (IPlugin *plugin : m_parent->m_Plugins) {
@@ -813,20 +876,21 @@ void Settings::PluginsTab::update()
// store plugin blacklist
m_parent->m_PluginBlacklist.clear();
- foreach (QListWidgetItem *item, m_pluginBlacklistList->findItems("*", Qt::MatchWildcard)) {
+ for (QListWidgetItem *item : m_pluginBlacklistList->findItems("*", Qt::MatchWildcard)) {
m_parent->m_PluginBlacklist.insert(item->text());
}
m_parent->writePluginBlacklist();
}
-Settings::WorkaroundsTab::WorkaroundsTab(Settings *m_parent, SettingsDialog &m_dialog) :
- Settings::SettingsTab(m_parent, m_dialog),
- m_appIDEdit(m_dialog.findChild<QLineEdit*>("appIDEdit")),
- m_mechanismBox(m_dialog.findChild<QComboBox*>("mechanismBox")),
- m_nmmVersionEdit(m_dialog.findChild<QLineEdit*>("nmmVersionEdit")),
- m_hideUncheckedBox(m_dialog.findChild<QCheckBox*>("hideUncheckedBox")),
- m_forceEnableBox(m_dialog.findChild<QCheckBox*>("forceEnableBox")),
- m_displayForeignBox(m_dialog.findChild<QCheckBox*>("displayForeignBox"))
+Settings::WorkaroundsTab::WorkaroundsTab(Settings *m_parent,
+ SettingsDialog &m_dialog)
+ : Settings::SettingsTab(m_parent, m_dialog)
+ , m_appIDEdit(m_dialog.findChild<QLineEdit *>("appIDEdit"))
+ , m_mechanismBox(m_dialog.findChild<QComboBox *>("mechanismBox"))
+ , m_nmmVersionEdit(m_dialog.findChild<QLineEdit *>("nmmVersionEdit"))
+ , m_hideUncheckedBox(m_dialog.findChild<QCheckBox *>("hideUncheckedBox"))
+ , m_forceEnableBox(m_dialog.findChild<QCheckBox *>("forceEnableBox"))
+ , m_displayForeignBox(m_dialog.findChild<QCheckBox *>("displayForeignBox"))
{
m_appIDEdit->setText(m_parent->getSteamAppID());
diff --git a/src/settings.h b/src/settings.h
index 9f6adaa7..d316c82d 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -120,9 +120,15 @@ public:
QString getSteamAppID() const;
/**
+ * retrieves the base directory under which the other directories usually
+ * reside
+ */
+ QString getBaseDirectory() const;
+
+ /**
* retrieve the directory where downloads are stored (with native separators)
**/
- QString getDownloadDirectory() const;
+ QString getDownloadDirectory(bool resolve = true) const;
/**
* retrieve a sorted list of preferred servers
@@ -132,7 +138,7 @@ public:
/**
* retrieve the directory where mods are stored (with native separators)
**/
- QString getModDirectory() const;
+ QString getModDirectory(bool resolve = true) const;
/**
* returns the version of nmm to impersonate when connecting to nexus
@@ -142,7 +148,18 @@ public:
/**
* retrieve the directory where the web cache is stored (with native separators)
**/
- QString getCacheDirectory() const;
+ QString getCacheDirectory(bool resolve = true) const;
+
+ /**
+ * retrieve the directory where profiles stored (with native separators)
+ **/
+ QString getProfileDirectory(bool resolve = true) const;
+
+ /**
+ * retrieve the directory were new files are stored that can't be assigned
+ * to a mod (with native separators)
+ */
+ QString getOverwriteDirectory(bool resolve = true) const;
/**
* @return true if the user has set up automatic login to nexus
@@ -304,6 +321,8 @@ public:
*/
std::vector<MOBase::IPlugin*> plugins() const { return m_Plugins; }
+ bool usePrereleases() const;
+
/**
* @brief register MO as the handler for nxm links
* @param force set to true to enforce the registration dialog to show up,
@@ -324,7 +343,7 @@ private:
void addStyles(QComboBox *styleBox);
void readPluginBlacklist();
void writePluginBlacklist();
- QString getConfigurablePath(const QString &key, const QString &def) const;
+ QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const;
class SettingsTab
{
@@ -353,11 +372,25 @@ private:
QComboBox *m_languageBox;
QComboBox *m_styleBox;
QComboBox *m_logLevelBox;
+ QCheckBox *m_compactBox;
+ QCheckBox *m_showMetaBox;
+ QCheckBox *m_usePrereleaseBox;
+ };
+
+ class PathsTab : public SettingsTab
+ {
+ public:
+ PathsTab(Settings *parent, SettingsDialog &dialog);
+
+ void update();
+
+ private:
+ QLineEdit *m_baseDirEdit;
QLineEdit *m_downloadDirEdit;
QLineEdit *m_modDirEdit;
QLineEdit *m_cacheDirEdit;
- QCheckBox *m_compactBox;
- QCheckBox *m_showMetaBox;
+ QLineEdit *m_profilesDirEdit;
+ QLineEdit *m_overwriteDirEdit;
};
/** Display/store the configuration in the 'nexus' tab of the settings dialogue */
diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp
index 8bc1dbc6..62134c9b 100644
--- a/src/settingsdialog.cpp
+++ b/src/settingsdialog.cpp
@@ -25,6 +25,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "noeditdelegate.h"
#include "iplugingame.h"
#include "settings.h"
+#include "instancemanager.h"
+#include "nexusinterface.h"
#include <QDirIterator>
#include <QFileDialog>
@@ -39,11 +41,13 @@ using namespace MOBase;
SettingsDialog::SettingsDialog(QWidget *parent)
- : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog)
+ : TutorableDialog("SettingsDialog", parent)
+ , ui(new Ui::SettingsDialog)
{
ui->setupUi(this);
- QShortcut *delShortcut = new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist);
+ QShortcut *delShortcut
+ = new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist);
connect(delShortcut, SIGNAL(activated()), this, SLOT(deleteBlacklistItem()));
}
@@ -54,13 +58,30 @@ SettingsDialog::~SettingsDialog()
void SettingsDialog::addPlugins(const std::vector<IPlugin*> &plugins)
{
- foreach (IPlugin *plugin, plugins) {
+ for (IPlugin *plugin : plugins) {
ui->pluginsList->addItem(plugin->name());
}
}
void SettingsDialog::accept()
{
+ QString newModPath = ui->modDirEdit->text();
+ newModPath.replace("%BASE_DIR%", ui->baseDirEdit->text());
+
+ if ((QDir::fromNativeSeparators(newModPath) !=
+ QDir::fromNativeSeparators(
+ Settings::instance().getModDirectory(true))) &&
+ (QMessageBox::question(
+ nullptr, tr("Confirm"),
+ tr("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?"),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)) {
+ return;
+ }
+
storeSettings(ui->pluginsList->currentItem());
TutorableDialog::accept();
}
@@ -89,16 +110,29 @@ void SettingsDialog::on_categoriesBtn_clicked()
void SettingsDialog::on_bsaDateBtn_clicked()
{
- IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+ IPluginGame const *game
+ = qApp->property("managed_game").value<IPluginGame *>();
QDir dir = game->dataDirectory();
- Helper::backdateBSAs(qApp->property("dataPath").toString().toStdWString(),
+ Helper::backdateBSAs(qApp->applicationDirPath().toStdWString(),
dir.absolutePath().toStdWString());
}
+void SettingsDialog::on_browseBaseDirBtn_clicked()
+{
+ QString temp = QFileDialog::getExistingDirectory(
+ this, tr("Select base directory"), ui->baseDirEdit->text());
+ if (!temp.isEmpty()) {
+ ui->baseDirEdit->setText(temp);
+ }
+}
+
void SettingsDialog::on_browseDownloadDirBtn_clicked()
{
- QString temp = QFileDialog::getExistingDirectory(this, tr("Select download directory"), ui->downloadDirEdit->text());
+ QString searchPath = ui->downloadDirEdit->text();
+ searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text());
+
+ QString temp = QFileDialog::getExistingDirectory(this, tr("Select download directory"), searchPath);
if (!temp.isEmpty()) {
ui->downloadDirEdit->setText(temp);
}
@@ -106,7 +140,10 @@ void SettingsDialog::on_browseDownloadDirBtn_clicked()
void SettingsDialog::on_browseModDirBtn_clicked()
{
- QString temp = QFileDialog::getExistingDirectory(this, tr("Select mod directory"), ui->downloadDirEdit->text());
+ QString searchPath = ui->modDirEdit->text();
+ searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text());
+
+ QString temp = QFileDialog::getExistingDirectory(this, tr("Select mod directory"), searchPath);
if (!temp.isEmpty()) {
ui->modDirEdit->setText(temp);
}
@@ -114,12 +151,37 @@ void SettingsDialog::on_browseModDirBtn_clicked()
void SettingsDialog::on_browseCacheDirBtn_clicked()
{
- QString temp = QFileDialog::getExistingDirectory(this, tr("Select cache directory"), ui->cacheDirEdit->text());
+ QString searchPath = ui->cacheDirEdit->text();
+ searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text());
+
+ QString temp = QFileDialog::getExistingDirectory(this, tr("Select cache directory"), searchPath);
if (!temp.isEmpty()) {
ui->cacheDirEdit->setText(temp);
}
}
+void SettingsDialog::on_browseProfilesDirBtn_clicked()
+{
+ QString searchPath = ui->profilesDirEdit->text();
+ searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text());
+
+ QString temp = QFileDialog::getExistingDirectory(this, tr("Select profiles directory"), searchPath);
+ if (!temp.isEmpty()) {
+ ui->profilesDirEdit->setText(temp);
+ }
+}
+
+void SettingsDialog::on_browseOverwriteDirBtn_clicked()
+{
+ QString searchPath = ui->overwriteDirEdit->text();
+ searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text());
+
+ QString temp = QFileDialog::getExistingDirectory(this, tr("Select overwrite directory"), searchPath);
+ if (!temp.isEmpty()) {
+ ui->overwriteDirEdit->setText(temp);
+ }
+}
+
void SettingsDialog::on_resetDialogsButton_clicked()
{
if (QMessageBox::question(this, tr("Confirm?"),
@@ -186,3 +248,10 @@ void SettingsDialog::on_associateButton_clicked()
{
Settings::instance().registerAsNXMHandler(true);
}
+
+void SettingsDialog::on_clearCacheButton_clicked()
+{
+ QDir(Settings::instance().getCacheDirectory()).removeRecursively();
+ NexusInterface::instance()->clearCache();
+}
+
diff --git a/src/settingsdialog.h b/src/settingsdialog.h
index e99fb3e3..e9d995d3 100644
--- a/src/settingsdialog.h
+++ b/src/settingsdialog.h
@@ -77,8 +77,19 @@ private slots:
void on_associateButton_clicked();
+ void on_clearCacheButton_clicked();
+
+ void on_browseBaseDirBtn_clicked();
+
+ void on_browseOverwriteDirBtn_clicked();
+
+ void on_browseProfilesDirBtn_clicked();
+
private:
Ui::SettingsDialog *ui;
+
};
+
+
#endif // WORKAROUNDDIALOG_H
diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui
index b88885dc..a38530da 100644
--- a/src/settingsdialog.ui
+++ b/src/settingsdialog.ui
@@ -100,6 +100,11 @@ p, li { white-space: pre-wrap; }
</item>
<item>
<property name="text">
+ <string>Warning</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
<string>Error</string>
</property>
</item>
@@ -108,141 +113,251 @@ p, li { white-space: pre-wrap; }
</layout>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_2">
- <property name="title">
- <string>Advanced</string>
+ <widget class="QCheckBox" name="usePrereleaseBox">
+ <property name="toolTip">
+ <string>Update to non-stable releases.</string>
</property>
- <property name="checkable">
- <bool>true</bool>
+ <property name="whatsThis">
+ <string>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!</string>
</property>
- <property name="checked">
- <bool>false</bool>
+ <property name="text">
+ <string>Install Pre-releases (Betas)</string>
</property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="1">
- <widget class="QLineEdit" name="downloadDirEdit">
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>User interface</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_11">
+ <item>
+ <widget class="QCheckBox" name="compactBox">
<property name="toolTip">
- <string>Directory where downloads are stored.</string>
+ <string>If checked, the download interface will be more compact.</string>
</property>
- <property name="whatsThis">
- <string>Directory where downloads are stored.</string>
+ <property name="text">
+ <string>Compact Download Interface</string>
</property>
</widget>
</item>
- <item row="0" column="2">
- <widget class="QPushButton" name="browseDownloadDirBtn">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
+ <item>
+ <widget class="QCheckBox" name="showMetaBox">
+ <property name="toolTip">
+ <string>If checked, the download list will display meta information instead of file names.</string>
</property>
<property name="text">
- <string notr="true">...</string>
+ <string>Download Meta Information</string>
</property>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_8">
- <property name="text">
- <string>Mod Directory</string>
+ <item>
+ <widget class="QPushButton" name="resetDialogsButton">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
</property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLineEdit" name="modDirEdit">
<property name="toolTip">
- <string>Directory where mods are stored.</string>
+ <string>Reset stored information from dialogs.</string>
</property>
<property name="whatsThis">
- <string>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).</string>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
- <widget class="QPushButton" name="browseModDirBtn">
- <property name="text">
- <string notr="true">...</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="label_7">
- <property name="text">
- <string>Download Directory</string>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_9">
- <property name="text">
- <string>Cache Directory</string>
+ <string>This will make all dialogs show up again where you checked the &quot;Remember selection&quot;-box.</string>
</property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QLineEdit" name="cacheDirEdit"/>
- </item>
- <item row="3" column="2">
- <widget class="QPushButton" name="browseCacheDirBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Reset Dialogs</string>
</property>
</widget>
</item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox">
- <property name="title">
- <string>User interface</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_11">
<item>
- <widget class="QCheckBox" name="compactBox">
- <property name="toolTip">
- <string>If checked, the download interface will be more compact.</string>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
</property>
- <property name="text">
- <string>Compact Download Interface</string>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
</property>
- </widget>
+ </spacer>
</item>
<item>
- <widget class="QCheckBox" name="showMetaBox">
+ <widget class="QPushButton" name="categoriesBtn">
<property name="toolTip">
- <string>If checked, the download list will display meta information instead of file names.</string>
+ <string>Modify the categories available to arrange your mods.</string>
+ </property>
+ <property name="whatsThis">
+ <string>Modify the categories available to arrange your mods.</string>
</property>
<property name="text">
- <string>Download Meta Information</string>
+ <string>Configure Mod Categories</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="pathsTab">
+ <attribute name="title">
+ <string>Paths</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_10">
<item>
- <widget class="QPushButton" name="resetDialogsButton">
- <property name="maximumSize">
- <size>
- <width>16777215</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="toolTip">
- <string>Reset stored information from dialogs.</string>
- </property>
- <property name="whatsThis">
- <string>This will make all dialogs show up again where you checked the &quot;Remember selection&quot;-box.</string>
- </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_25">
+ <property name="text">
+ <string>Base Directory</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="2">
+ <widget class="QPushButton" name="browseOverwriteDirBtn">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QLineEdit" name="overwriteDirEdit"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="baseDirEdit"/>
+ </item>
+ <item row="0" column="2">
+ <widget class="QPushButton" name="browseBaseDirBtn">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="label_24">
+ <property name="text">
+ <string>Overwrite</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_9">
+ <property name="text">
+ <string>Caches</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QPushButton" name="browseModDirBtn">
+ <property name="text">
+ <string notr="true">...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QLineEdit" name="cacheDirEdit"/>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>Downloads</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="2">
+ <widget class="QPushButton" name="browseCacheDirBtn">
+ <property name="text">
+ <string notr="true">...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="modDirEdit">
+ <property name="toolTip">
+ <string>Directory where mods are stored.</string>
+ </property>
+ <property name="whatsThis">
+ <string>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).</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QPushButton" name="browseDownloadDirBtn">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string notr="true">...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="downloadDirEdit">
+ <property name="toolTip">
+ <string>Directory where downloads are stored.</string>
+ </property>
+ <property name="whatsThis">
+ <string>Directory where downloads are stored.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>Mods</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="2">
+ <widget class="QPushButton" name="browseProfilesDirBtn">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QLineEdit" name="profilesDirEdit"/>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="label_22">
+ <property name="text">
+ <string>Profiles</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <spacer name="verticalSpacer_8">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_26">
<property name="text">
- <string>Reset Dialogs</string>
+ <string>Use %BASE_DIR% to refer to the Base Directory.</string>
</property>
</widget>
</item>
<item>
- <spacer name="verticalSpacer">
+ <spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
@@ -255,15 +370,9 @@ p, li { white-space: pre-wrap; }
</spacer>
</item>
<item>
- <widget class="QPushButton" name="categoriesBtn">
- <property name="toolTip">
- <string>Modify the categories available to arrange your mods.</string>
- </property>
- <property name="whatsThis">
- <string>Modify the categories available to arrange your mods.</string>
- </property>
+ <widget class="QLabel" name="label_23">
<property name="text">
- <string>Configure Mod Categories</string>
+ <string>Important: All directories have to be writeable!</string>
</property>
</widget>
</item>
@@ -338,6 +447,37 @@ p, li { white-space: pre-wrap; }
</item>
</layout>
</item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <item>
+ <widget class="QPushButton" name="clearCacheButton">
+ <property name="toolTip">
+ <string>Remove cache and cookies. Forces a new login.</string>
+ </property>
+ <property name="text">
+ <string>Clear Cache</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/edit_clear</normaloff>:/MO/gui/edit_clear</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
</item>
@@ -614,7 +754,7 @@ p, li { white-space: pre-wrap; }
<bool>false</bool>
</attribute>
<attribute name="headerDefaultSectionSize">
- <number>100</number>
+ <number>170</number>
</attribute>
<column>
<property name="text">
@@ -898,7 +1038,53 @@ For the other games this is not a sufficient replacement for AI!</string>
</item>
</layout>
</widget>
- <resources/>
+ <tabstops>
+ <tabstop>languageBox</tabstop>
+ <tabstop>styleBox</tabstop>
+ <tabstop>logLevelBox</tabstop>
+ <tabstop>usePrereleaseBox</tabstop>
+ <tabstop>compactBox</tabstop>
+ <tabstop>showMetaBox</tabstop>
+ <tabstop>resetDialogsButton</tabstop>
+ <tabstop>categoriesBtn</tabstop>
+ <tabstop>baseDirEdit</tabstop>
+ <tabstop>browseBaseDirBtn</tabstop>
+ <tabstop>downloadDirEdit</tabstop>
+ <tabstop>browseDownloadDirBtn</tabstop>
+ <tabstop>modDirEdit</tabstop>
+ <tabstop>browseModDirBtn</tabstop>
+ <tabstop>cacheDirEdit</tabstop>
+ <tabstop>browseCacheDirBtn</tabstop>
+ <tabstop>profilesDirEdit</tabstop>
+ <tabstop>browseProfilesDirBtn</tabstop>
+ <tabstop>overwriteDirEdit</tabstop>
+ <tabstop>browseOverwriteDirBtn</tabstop>
+ <tabstop>loginCheckBox</tabstop>
+ <tabstop>usernameEdit</tabstop>
+ <tabstop>passwordEdit</tabstop>
+ <tabstop>clearCacheButton</tabstop>
+ <tabstop>offlineBox</tabstop>
+ <tabstop>proxyBox</tabstop>
+ <tabstop>associateButton</tabstop>
+ <tabstop>knownServersList</tabstop>
+ <tabstop>preferredServersList</tabstop>
+ <tabstop>steamUserEdit</tabstop>
+ <tabstop>steamPassEdit</tabstop>
+ <tabstop>pluginsList</tabstop>
+ <tabstop>pluginSettingsList</tabstop>
+ <tabstop>pluginBlacklist</tabstop>
+ <tabstop>appIDEdit</tabstop>
+ <tabstop>mechanismBox</tabstop>
+ <tabstop>nmmVersionEdit</tabstop>
+ <tabstop>hideUncheckedBox</tabstop>
+ <tabstop>forceEnableBox</tabstop>
+ <tabstop>displayForeignBox</tabstop>
+ <tabstop>bsaDateBtn</tabstop>
+ <tabstop>tabWidget</tabstop>
+ </tabstops>
+ <resources>
+ <include location="resources.qrc"/>
+ </resources>
<connections>
<connection>
<sender>buttonBox</sender>
diff --git a/src/shared/appconfig.inc b/src/shared/appconfig.inc
index e2dbfe20..ac6c210f 100644
--- a/src/shared/appconfig.inc
+++ b/src/shared/appconfig.inc
@@ -14,7 +14,6 @@ APPPARAM(std::wstring, iniFileName, L"ModOrganizer.ini")
APPPARAM(std::wstring, proxyDLLTarget, L"steam_api.dll")
APPPARAM(std::wstring, proxyDLLOrig, L"steam_api_orig.dll") // needs to be identical to the value used in proxydll-project
APPPARAM(std::wstring, proxyDLLSource, L"proxy.dll")
-APPPARAM(std::wstring, hookDLLName, L"hook.dll") // needs to be identical to the value used in proxydll-project
APPPARAM(const wchar_t*, localSavePlaceholder, L"__MOProfileSave__\\")
APPPARAM(std::wstring, firstStepsTutorial, L"tutorial_firststeps_main.js")
diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp
index 54b6efff..df6a7b5a 100644
--- a/src/shared/directoryentry.cpp
+++ b/src/shared/directoryentry.cpp
@@ -24,7 +24,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <bsatk.h>
#include <boost/bind.hpp>
#include <boost/scoped_array.hpp>
-#include <boost/foreach.hpp>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <sstream>
@@ -704,7 +703,7 @@ FilesOrigin &DirectoryEntry::getOriginByName(const std::wstring &name) const
return m_OriginConnection->getByName(name);
}
-
+/*
int DirectoryEntry::getOrigin(const std::wstring &path, bool &archive)
{
const DirectoryEntry *directory = nullptr;
@@ -718,7 +717,7 @@ int DirectoryEntry::getOrigin(const std::wstring &path, bool &archive)
return -1;
}
}
-}
+}*/
std::vector<FileEntry::Ptr> DirectoryEntry::getFiles() const
{
@@ -950,12 +949,12 @@ void FileRegister::removeOriginMulti(std::set<FileEntry::Index> indices, int ori
// the latter should be faster when there are many files in few directories. since this is called
// only when disabling an origin that is probably frequently the case
std::set<DirectoryEntry*> parents;
- BOOST_FOREACH (const FileEntry::Ptr &file, removedFiles) {
+ for (const FileEntry::Ptr &file : removedFiles) {
if (file->getParent() != nullptr) {
parents.insert(file->getParent());
}
}
- BOOST_FOREACH (DirectoryEntry *parent, parents) {
+ for (DirectoryEntry *parent : parents) {
parent->removeFiles(indices);
}
}
diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h
index 2d32450a..04b8782b 100644
--- a/src/shared/directoryentry.h
+++ b/src/shared/directoryentry.h
@@ -207,7 +207,8 @@ public:
DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID);
DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID,
- boost::shared_ptr<FileRegister> fileRegister, boost::shared_ptr<OriginConnection> originConnection);
+ boost::shared_ptr<FileRegister> fileRegister,
+ boost::shared_ptr<OriginConnection> originConnection);
~DirectoryEntry();
@@ -232,7 +233,9 @@ public:
FilesOrigin &getOriginByID(int ID) const;
FilesOrigin &getOriginByName(const std::wstring &name) const;
- int getOrigin(const std::wstring &path, bool &archive);
+ int anyOrigin() const;
+
+ //int getOrigin(const std::wstring &path, bool &archive);
std::vector<FileEntry::Ptr> getFiles() const;
@@ -316,8 +319,6 @@ private:
DirectoryEntry *getSubDirectoryRecursive(const std::wstring &path, bool create, int originID = -1);
- int anyOrigin() const;
-
void removeDirRecursive();
private:
diff --git a/src/shared/inject.cpp b/src/shared/inject.cpp
deleted file mode 100644
index fcd4cfab..00000000
--- a/src/shared/inject.cpp
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
-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 <http://www.gnu.org/licenses/>.
-*/
-
-#include "inject.h"
-/*#if defined UNICODE && !defined _UNICODE
-#define _UNICODE 1
-#endif*/
-#include <wchar.h>
-#include <cstdio>
-#include <tchar.h>
-#include <cstdlib>
-#include <exception>
-#include <stdexcept>
-#include <string>
-#include "windows_error.h"
-#include "error_report.h"
-
-namespace MOShared {
-
-
-struct TParameters {
- char dllname[MAX_PATH];
- wchar_t profileName[101];
- char initstr[5];
- int logLevel;
-};
-
-
-typedef HMODULE (WINAPI *TLoadLibraryType)(LPCTSTR);
-typedef FARPROC (WINAPI *TGetProcAddressType)(HMODULE, LPCSTR);
-
-
-
-void injectDLL(HANDLE processHandle, HANDLE threadHandle, const std::string &dllname, const std::wstring &profileName, int logLevel)
-{
- // prepare parameters that are to be passed to the injected dll
- TParameters parameters;
- memset(&parameters, '\0', sizeof(TParameters));
- strncpy(parameters.dllname, dllname.c_str(), MAX_PATH - 1);
- wcsncpy(parameters.profileName, profileName.c_str(), 100);
- _snprintf(parameters.initstr, 5, "Init"); //this is the name of thie initialisation function we want to call in the target process
-
- HMODULE k32mod = ::LoadLibrary(__TEXT("kernel32.dll"));
- TLoadLibraryType loadLibraryFunc = nullptr;
- TGetProcAddressType getProcAddressFunc = nullptr;
- // ansi binaries
- if (k32mod != nullptr) {
- loadLibraryFunc = reinterpret_cast<TLoadLibraryType>(::GetProcAddress(k32mod, "LoadLibraryA"));
- getProcAddressFunc = reinterpret_cast<TGetProcAddressType>(::GetProcAddress(k32mod, "GetProcAddress"));
- if ((loadLibraryFunc == nullptr) || (getProcAddressFunc == nullptr)) {
- throw windows_error("failed to determine address for required functions");
- }
- } else {
- throw windows_error("kernel32.dll not loaded??");
- }
-
- // allocate memory in the target process and write the parameter-block there
- LPVOID remoteMem = ::VirtualAllocEx(processHandle, nullptr, sizeof(TParameters), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
- if (remoteMem == nullptr) {
- throw windows_error("failed to allocate memory in target process");
- }
- SIZE_T written;
- if (!::WriteProcessMemory(processHandle, remoteMem, &parameters, sizeof(TParameters), &written) ||
- (written != sizeof(TParameters))) {
- throw windows_error("failed to write parameters to target process");
- }
-
- // now for the interesting part: write a stub into the target process that is run before any code of the original binary. This code will load
- // our injected dll into the process and run its Init-function which takes the profile name as its parameter
- // obviously this code is ultra-hacky
-
- // construct the stub in beautiful assembler with placeholders
- BYTE stubLocal[] = { 0x60, // PUSHAD
- 0xB8, 0xBA, 0xAD, 0xF0, 0x0D, // MOV EAX, imm32 (LoadLibrary)
- 0x68, 0xBA, 0xAD, 0xF0, 0x0D, // PUSH imm32 (dllname)
- 0xFF, 0xD0, // CALL EAX (=LoadLibrary, leaves module handle of our dll in eax)
- 0x68, 0xBA, 0xAD, 0xF0, 0x0D, // PUSH imm32 ("Init")
- 0x50, // PUSH EAX
- 0xB8, 0xBA, 0xAD, 0xF0, 0x0D, // MOVE EAX, imm32 (GetProcAddress)
- 0xFF, 0xD0, // CALL EAX (=GetProcAddress, leaves address of init function in eax)
- 0x68, 0xBA, 0xAD, 0xF0, 0x0D, // PUSH imm32 (profile name)
- 0x68, 0xBA, 0xAD, 0xF0, 0x0D, // PUSH imm32 (log level)
- 0xFF, 0xD0, // CALL EAX (=InitFunction)
- 0x58, // POP EAX (init function is defined cdecl)
- 0x58, // POP EAX (init function is defined cdecl)
- 0x61, // POPAD
- 0xE9, 0xBA, 0xAD, 0xF0, 0x0D // JMP near, relative (=original entry point)
- };
-
- // reserve memory for the stub
- PBYTE stubRemote = reinterpret_cast<PBYTE>(::VirtualAllocEx(processHandle, nullptr, sizeof(stubLocal), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
- if (stubRemote == nullptr) {
- throw windows_error("failed to allocate memory for stub");
- }
- TParameters *remoteParams = reinterpret_cast<TParameters*>(remoteMem);
-
- // dizzy yet? we still have to calculate the entry point as an address relative to our stub
- ULONG entryPoint = 0;
-
- // not implemented on 64 bit systems
-#ifdef _X86_
- CONTEXT threadContext;
- threadContext.ContextFlags = CONTEXT_CONTROL;
- if (::GetThreadContext(threadHandle, &threadContext) == 0) {
- throw windows_error("failed to access thread context. Please note that Mod Organizer does not support 64bit binaries!");
- } else {
- entryPoint = threadContext.Eip - (reinterpret_cast<ULONG>(stubRemote) + sizeof(stubLocal));
- }
- // now replace each baadf00d by the correct value. The pointers need to be the pointers in the REMOTE memory of course, that's why we copied them there
- *(PULONG)&stubLocal[2] = reinterpret_cast<ULONG>(*loadLibraryFunc);
- *(PULONG)&stubLocal[7] = reinterpret_cast<ULONG>(remoteParams->dllname);
- *(PULONG)&stubLocal[14] = reinterpret_cast<ULONG>(remoteParams->initstr);
- *(PULONG)&stubLocal[20] = reinterpret_cast<ULONG>(*getProcAddressFunc);
- *(PULONG)&stubLocal[27] = reinterpret_cast<ULONG>(remoteParams->profileName);
- *(PULONG)&stubLocal[32] = logLevel;
- *(PULONG)&stubLocal[42] = entryPoint;
- // almost there. copy stub to target process
- if (!::WriteProcessMemory(processHandle, stubRemote, reinterpret_cast<LPCVOID>(stubLocal), sizeof(stubLocal), &written) ||
- (written != sizeof(stubLocal))) {
- throw windows_error("failed to write stub to target process");
- }
-
- // finally, make the stub the new next thing for the thread to execute
- threadContext.Eip = (ULONG)stubRemote;
- if (::SetThreadContext(threadHandle, &threadContext) == 0) {
- throw windows_error("failed to overwrite thread context");
- }
-#endif
-}
-
-} // namespace MOShared
diff --git a/src/shared/leaktrace.cpp b/src/shared/leaktrace.cpp
index 73786c6b..1ff1fa9d 100644
--- a/src/shared/leaktrace.cpp
+++ b/src/shared/leaktrace.cpp
@@ -33,8 +33,9 @@ static struct __TraceData {
"%d objects not freed, allocated at:\n%s",
iter->second.size(), iter->first.toString().c_str());
printf("Addresses: ");
- for (int i = 0; i < (std::min<int>)(5, iter->second.size()); ++i) {
- printf("%p, ", reinterpret_cast<void*>(iter->second[i]));
+ for (int i = 0;
+ i < (std::min<int>)(5, static_cast<int>(iter->second.size())); ++i) {
+ printf("%p, ", reinterpret_cast<void *>(iter->second[i]));
}
printf("\n");
}
diff --git a/src/shared/util.cpp b/src/shared/util.cpp
index 3692aae1..5491a9e6 100644
--- a/src/shared/util.cpp
+++ b/src/shared/util.cpp
@@ -81,12 +81,16 @@ std::wstring ToWString(const std::string &source, bool utf8)
if (!utf8) {
codepage = AreFileApisANSI() ? GetACP() : GetOEMCP();
}
- int sizeRequired = ::MultiByteToWideChar(codepage, 0, source.c_str(), source.length(), nullptr, 0);
+ int sizeRequired
+ = ::MultiByteToWideChar(codepage, 0, source.c_str(),
+ static_cast<int>(source.length()), nullptr, 0);
if (sizeRequired == 0) {
throw windows_error("failed to convert string to wide character");
}
result.resize(sizeRequired, L'\0');
- ::MultiByteToWideChar(codepage, 0, source.c_str(), source.length(), &result[0], sizeRequired);
+ ::MultiByteToWideChar(codepage, 0, source.c_str(),
+ static_cast<int>(source.length()), &result[0],
+ sizeRequired);
}
return result;
diff --git a/src/spawn.cpp b/src/spawn.cpp
index 49e89a8b..ac8ccf30 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -22,7 +22,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "report.h"
#include "utility.h"
#include <report.h>
-#include <inject.h>
+#include <usvfs.h>
+#include <Shellapi.h>
#include <appconfig.h>
#include <windows_error.h>
@@ -39,7 +40,8 @@ using namespace MOShared;
static const int BUFSIZE = 4096;
-static bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool suspended,
+static bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory,
+ bool suspended, bool hooked,
HANDLE stdOut, HANDLE stdErr,
HANDLE& processHandle, HANDLE& threadHandle)
{
@@ -57,7 +59,7 @@ static bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, b
si.dwFlags |= STARTF_USESTDHANDLES;
}
si.cb = sizeof(si);
- int length = wcslen(binary) + wcslen(arguments) + 4;
+ size_t length = wcslen(binary) + wcslen(arguments) + 4;
wchar_t *commandLine = nullptr;
if (arguments[0] != L'\0') {
commandLine = new wchar_t[length];
@@ -87,15 +89,28 @@ static bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, b
}
PROCESS_INFORMATION pi;
- BOOL success = ::CreateProcess(nullptr,
- commandLine,
- nullptr, nullptr, // no special process or thread attributes
- inheritHandles, // inherit handles if we plan to use stdout or stderr reroute
- CREATE_BREAKAWAY_FROM_JOB | (suspended ? CREATE_SUSPENDED : 0), // create suspended so I have time to inject the DLL
- nullptr, // same environment as parent
- currentDirectory, // current directory
- &si, &pi // startup and process information
- );
+ BOOL success = FALSE;
+ if (hooked) {
+ success = ::CreateProcessHooked(nullptr,
+ commandLine,
+ nullptr, nullptr, // no special process or thread attributes
+ inheritHandles, // inherit handles if we plan to use stdout or stderr reroute
+ CREATE_BREAKAWAY_FROM_JOB,
+ nullptr, // same environment as parent
+ currentDirectory, // current directory
+ &si, &pi // startup and process information
+ );
+ } else {
+ success = ::CreateProcess(nullptr,
+ commandLine,
+ nullptr, nullptr, // no special process or thread attributes
+ inheritHandles, // inherit handles if we plan to use stdout or stderr reroute
+ CREATE_BREAKAWAY_FROM_JOB,
+ nullptr, // same environment as parent
+ currentDirectory, // current directory
+ &si, &pi // startup and process information
+ );
+ }
::SetEnvironmentVariable(TEXT("PATH"), oldPath.get());
@@ -113,33 +128,18 @@ static bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, b
HANDLE startBinary(const QFileInfo &binary,
const QString &arguments,
- const QString& profileName,
- int logLevel,
const QDir &currentDirectory,
bool hooked,
HANDLE stdOut,
HANDLE stdErr)
{
- JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo;
-
- ::QueryInformationJobObject(nullptr, JobObjectExtendedLimitInformation, &jobInfo, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION), nullptr);
- jobInfo.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK;
-
- HANDLE jobObject = ::CreateJobObject(nullptr, nullptr);
-
- if (jobObject == nullptr) {
- qWarning("failed to create job object: %lu", ::GetLastError());
- } else {
- ::SetInformationJobObject(jobObject, JobObjectExtendedLimitInformation, &jobInfo, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
- }
-
HANDLE processHandle, threadHandle;
std::wstring binaryName = ToWString(QDir::toNativeSeparators(binary.absoluteFilePath()));
std::wstring currentDirectoryName = ToWString(QDir::toNativeSeparators(currentDirectory.absolutePath()));
try {
- if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(), true,
- stdOut, stdErr, processHandle, threadHandle)) {
+ if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(),
+ true, hooked, stdOut, stdErr, processHandle, threadHandle)) {
reportError(QObject::tr("failed to spawn \"%1\"").arg(binary.fileName()));
return INVALID_HANDLE_VALUE;
}
@@ -168,38 +168,6 @@ HANDLE startBinary(const QFileInfo &binary,
}
}
- if (hooked) {
- try {
- QFileInfo dllInfo(QApplication::applicationDirPath() + "/" + ToQString(AppConfig::hookDLLName()));
- if (!dllInfo.exists()) {
- reportError(QObject::tr("\"%1\" doesn't exist").arg(dllInfo.fileName()));
- return INVALID_HANDLE_VALUE;
- }
- injectDLL(processHandle, threadHandle,
- QDir::toNativeSeparators(dllInfo.canonicalFilePath()).toLocal8Bit().constData(),
- ToWString(profileName).c_str(), logLevel);
- } catch (const windows_error& e) {
- reportError(QObject::tr("failed to inject dll into \"%1\": %2").arg(binary.fileName()).arg(e.what()));
- ::TerminateProcess(processHandle, 1);
- return INVALID_HANDLE_VALUE;
- }
-#ifdef _DEBUG
- reportError("ready?");
-#endif // DEBUG
- }
-
- if (::AssignProcessToJobObject(jobObject, processHandle) == 0) {
- qWarning("failed to assign to job object: %lu", ::GetLastError());
- ::CloseHandle(jobObject);
- jobObject = processHandle;
- } else {
- ::CloseHandle(processHandle);
- }
-
- if (::ResumeThread(threadHandle) == (DWORD)-1) {
- reportError(QObject::tr("failed to run \"%1\"").arg(binary.fileName()));
- return INVALID_HANDLE_VALUE;
- }
::CloseHandle(threadHandle);
- return jobObject;
+ return processHandle;
}
diff --git a/src/spawn.h b/src/spawn.h
index 63eda18d..c2d99bdb 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -60,7 +60,7 @@ private:
* @todo is the profile name even used any more?
* @todo is the hooked parameter used?
**/
-HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString &profileName, int logLevel,
+HANDLE startBinary(const QFileInfo &binary, const QString &arguments,
const QDir &currentDirectory, bool hooked,
HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE);
diff --git a/src/splash.png b/src/splash.png
index 3eec8bed..d29a6711 100644
--- a/src/splash.png
+++ b/src/splash.png
Binary files differ
diff --git a/src/transfersavesdialog.cpp b/src/transfersavesdialog.cpp
index cb3d0d61..e130b1ae 100644
--- a/src/transfersavesdialog.cpp
+++ b/src/transfersavesdialog.cpp
@@ -23,6 +23,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "iplugingame.h"
#include "isavegame.h"
#include "savegameinfo.h"
+#include <utility.h>
#include <QtDebug>
#include <QDateTime>
@@ -159,6 +160,7 @@ bool TransferSavesDialog::testOverwrite(OverwriteMode &overwriteMode, const QStr
return res == QMessageBox::Yes;
}
+
#define MOVE_SAVES "Move all save games of character \"%1\""
#define COPY_SAVES "Copy all save games of character \"%1\""
@@ -168,13 +170,13 @@ bool TransferSavesDialog::testOverwrite(OverwriteMode &overwriteMode, const QStr
void TransferSavesDialog::on_moveToLocalBtn_clicked()
{
QString character = ui->globalCharacterList->currentItem()->text();
- if (transferCharacters(character,
- MOVE_SAVES TO_PROFILE,
- m_GlobalSaves[character],
- m_Profile.savePath(),
- QFile::rename,
- "Failed to move %s to %s"))
- {
+ if (transferCharacters(
+ character, MOVE_SAVES TO_PROFILE, m_GlobalSaves[character],
+ m_Profile.savePath(),
+ [this](const QString &source, const QString &destination) -> bool {
+ return shellMove(source, destination, this);
+ },
+ "Failed to move %s to %s")) {
refreshGlobalSaves();
refreshGlobalCharacters();
refreshLocalSaves();
@@ -185,12 +187,13 @@ void TransferSavesDialog::on_moveToLocalBtn_clicked()
void TransferSavesDialog::on_copyToLocalBtn_clicked()
{
QString character = ui->globalCharacterList->currentItem()->text();
- if (transferCharacters(character,
- COPY_SAVES TO_PROFILE,
- m_GlobalSaves[character],
- m_Profile.savePath(),
- QFile::copy,
- "Failed to copy %s to %s")) {
+ if (transferCharacters(
+ character, COPY_SAVES TO_PROFILE, m_GlobalSaves[character],
+ m_Profile.savePath(),
+ [this](const QString &source, const QString &destination) -> bool {
+ return shellCopy(source, destination, this);
+ },
+ "Failed to copy %s to %s")) {
refreshLocalSaves();
refreshLocalCharacters();
}
@@ -199,12 +202,13 @@ void TransferSavesDialog::on_copyToLocalBtn_clicked()
void TransferSavesDialog::on_moveToGlobalBtn_clicked()
{
QString character = ui->localCharacterList->currentItem()->text();
- if (transferCharacters(character,
- MOVE_SAVES TO_GLOBAL,
- m_LocalSaves[character],
- m_GamePlugin->savesDirectory().absolutePath(),
- QFile::rename,
- "Failed to move %s to %s")) {
+ if (transferCharacters(
+ character, MOVE_SAVES TO_GLOBAL, m_LocalSaves[character],
+ m_GamePlugin->savesDirectory().absolutePath(),
+ [this](const QString &source, const QString &destination) -> bool {
+ return shellMove(source, destination, this);
+ },
+ "Failed to move %s to %s")) {
refreshGlobalSaves();
refreshGlobalCharacters();
refreshLocalSaves();
@@ -215,12 +219,13 @@ void TransferSavesDialog::on_moveToGlobalBtn_clicked()
void TransferSavesDialog::on_copyToGlobalBtn_clicked()
{
QString character = ui->localCharacterList->currentItem()->text();
- if (transferCharacters(character,
- COPY_SAVES TO_GLOBAL,
- m_LocalSaves[character],
- m_GamePlugin->savesDirectory().absolutePath(),
- QFile::copy,
- "Failed to copy %s to %s")) {
+ if (transferCharacters(
+ character, COPY_SAVES TO_GLOBAL, m_LocalSaves[character],
+ m_GamePlugin->savesDirectory().absolutePath(),
+ [this](const QString &source, const QString &destination) -> bool {
+ return shellCopy(source, destination, this);
+ },
+ "Failed to copy %s to %s")) {
refreshGlobalSaves();
refreshGlobalCharacters();
}
@@ -295,12 +300,11 @@ void TransferSavesDialog::refreshCharacters(const SaveCollection &saveCollection
}
}
-bool TransferSavesDialog::transferCharacters(QString const &character,
- char const *message,
- SaveList &saves,
- QString const &dest,
- bool (method)(QString const &, QString const &),
- char const *errmsg)
+bool TransferSavesDialog::transferCharacters(
+ QString const &character, char const *message, SaveList &saves,
+ QString const &dest,
+ const std::function<bool(const QString &, const QString &)> &method,
+ char const *errmsg)
{
if (QMessageBox::question(this, tr("Confirm"),
tr(message).arg(character),
@@ -325,7 +329,7 @@ bool TransferSavesDialog::transferCharacters(QString const &character,
QFile::remove(destinationFile);
}
- if (! method(sourceFile.absoluteFilePath(), destinationFile)) {
+ if (!method(sourceFile.absoluteFilePath(), destinationFile)) {
qCritical(errmsg,
sourceFile.absoluteFilePath().toUtf8().constData(),
destinationFile.toUtf8().constData());
diff --git a/src/transfersavesdialog.h b/src/transfersavesdialog.h
index d3f079e3..dee41c72 100644
--- a/src/transfersavesdialog.h
+++ b/src/transfersavesdialog.h
@@ -37,14 +37,15 @@ namespace Ui { class TransferSavesDialog; }
namespace MOBase { class IPluginGame; }
namespace MOBase { class ISaveGame; }
-class TransferSavesDialog : public MOBase::TutorableDialog
-{
+class TransferSavesDialog : public MOBase::TutorableDialog {
Q_OBJECT
-
+
public:
- explicit TransferSavesDialog(const Profile &profile, MOBase::IPluginGame const *gamePlugin, QWidget *parent = 0);
+ explicit TransferSavesDialog(const Profile &profile,
+ MOBase::IPluginGame const *gamePlugin,
+ QWidget *parent = 0);
~TransferSavesDialog();
-
+
private slots:
void on_moveToLocalBtn_clicked();
@@ -62,23 +63,17 @@ private slots:
void on_copyToGlobalBtn_clicked();
private:
-
- enum OverwriteMode {
- OVERWRITE_ASK,
- OVERWRITE_YES,
- OVERWRITE_NO
- };
+ enum OverwriteMode { OVERWRITE_ASK, OVERWRITE_YES, OVERWRITE_NO };
private:
-
void refreshGlobalCharacters();
void refreshLocalCharacters();
void refreshGlobalSaves();
void refreshLocalSaves();
- bool testOverwrite(OverwriteMode &overwriteMode, const QString &destinationFile);
+ bool testOverwrite(OverwriteMode &overwriteMode,
+ const QString &destinationFile);
private:
-
Ui::TransferSavesDialog *ui;
Profile m_Profile;
@@ -93,18 +88,14 @@ private:
void refreshSaves(SaveCollection &saveCollection, const QString &savedir);
void refreshCharacters(SaveCollection const &saveCollection,
- QListWidget *charList,
- QPushButton *copy,
+ QListWidget *charList, QPushButton *copy,
QPushButton *move);
- bool transferCharacters(QString const &character,
- char const *message,
- SaveList &saves,
- QString const &dest,
- bool (method)(QString const &, QString const &),
- char const *errmsg
- );
-
+ bool transferCharacters(
+ QString const &character, char const *message, SaveList &saves,
+ QString const &dest,
+ const std::function<bool(const QString &, const QString &)> &method,
+ char const *errmsg);
};
#endif // TRANSFERSAVESDIALOG_H
diff --git a/src/tutorials/tutorial_firststeps_main.js b/src/tutorials/tutorial_firststeps_main.js
index 2bc270d7..c47b5c0d 100644
--- a/src/tutorials/tutorial_firststeps_main.js
+++ b/src/tutorials/tutorial_firststeps_main.js
@@ -145,42 +145,6 @@ function getTutorialSteps()
},
function() {
- tutorial.text = qsTr("Another special type of files are BSAs. These are bundles of game resources. "
- + "Please open the \"Archives\"-tab.")
- if (tutorialControl.waitForTabOpen("tabWidget", 1)) {
- highlightItem("tabWidget", true)
- } else {
- waitForClick()
- }
- },
-
- function() {
- tutorial.text = qsTr("These archives can be a real headache because the way bsas interact "
- + "with non-bundled resources is complicated. The game can even crash if required "
- + "archives are not loaded or ordered incorrectly.")
- waitForClick()
- },
-
- function() {
- tutorial.text = qsTr("MO applies some \"magic\" to make all BSAs that are checked in this list load in "
- + "the correct order interleaved with the non-bundled resources.")
- waitForClick()
- },
-
- function() {
- tutorial.text = qsTr("You can disable this magic to make MO behave more like other tools. In this case "
- + "their load order follows that of the corresponding plugin (.esp).")
- highlightItem("managedArchiveLabel", false)
- waitForClick()
- },
-
- function() {
- tutorial.text = qsTr("Many BSAs will appear grayed out and enabled. These mods are loaded by the game engine "
- + "automatically so they can't be disabled here.")
- waitForClick()
- },
-
- function() {
tutorial.text = qsTr("Now you know how to download, install and enable mods.\n"
+ "It's important you always start the game from inside MO, otherwise "
+ "the mods you installed here won't work.")
diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp
new file mode 100644
index 00000000..0420ddc2
--- /dev/null
+++ b/src/usvfsconnector.cpp
@@ -0,0 +1,176 @@
+/*
+Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "usvfsconnector.h"
+#include "settings.h"
+#include <memory>
+#include <sstream>
+#include <iomanip>
+#include <usvfs.h>
+#include <QTemporaryFile>
+#include <QProgressDialog>
+#include <QDateTime>
+#include <QCoreApplication>
+
+static const char SHMID[] = "mod_organizer_instance";
+
+
+std::string to_hex(void *bufferIn, size_t bufferSize)
+{
+ unsigned char *buffer = static_cast<unsigned char *>(bufferIn);
+ std::ostringstream temp;
+ temp << std::hex;
+ for (size_t i = 0; i < bufferSize; ++i) {
+ temp << std::setfill('0') << std::setw(2) << (unsigned int)buffer[i];
+ if ((i % 16) == 15) {
+ temp << "\n";
+ } else {
+ temp << " ";
+ }
+ }
+ return temp.str();
+}
+
+
+LogWorker::LogWorker()
+ : m_Buffer(1024, '\0')
+ , m_QuitRequested(false)
+ , m_LogFile(qApp->property("dataPath").toString()
+ + QString("/logs/usvfs-%1.log")
+ .arg(QDateTime::currentDateTimeUtc().toString(
+ "yyyy-MM-dd_hh-mm-ss")))
+{
+ m_LogFile.open(QIODevice::WriteOnly);
+ qDebug("usvfs log messages are written to %s",
+ qPrintable(m_LogFile.fileName()));
+}
+
+LogWorker::~LogWorker()
+{
+}
+
+void LogWorker::process()
+{
+ int noLogCycles = 0;
+ while (!m_QuitRequested) {
+ if (GetLogMessages(&m_Buffer[0], m_Buffer.size(), false)) {
+ m_LogFile.write(m_Buffer.c_str());
+ m_LogFile.write("\n");
+ m_LogFile.flush();
+ noLogCycles = 0;
+ } else {
+ QThread::msleep(std::min(40, noLogCycles) * 5);
+ ++noLogCycles;
+ }
+ }
+ emit finished();
+}
+
+void LogWorker::exit()
+{
+ m_QuitRequested = true;
+}
+
+LogLevel logLevel(int level)
+{
+ switch (level) {
+ case LogLevel::Info:
+ return LogLevel::Info;
+ case LogLevel::Warning:
+ return LogLevel::Warning;
+ case LogLevel::Error:
+ return LogLevel::Error;
+ default:
+ return LogLevel::Debug;
+ }
+}
+
+UsvfsConnector::UsvfsConnector()
+{
+ USVFSParameters params;
+ LogLevel level = logLevel(Settings::instance().logLevel());
+ USVFSInitParameters(&params, SHMID, false, level);
+ InitLogging(false);
+ CreateVFS(&params);
+ SetLogLevel(level);
+
+ BlacklistExecutable(L"TSVNCache.exe");
+
+ m_LogWorker.moveToThread(&m_WorkerThread);
+
+ connect(&m_WorkerThread, SIGNAL(started()), &m_LogWorker, SLOT(process()));
+ connect(&m_LogWorker, SIGNAL(finished()), &m_WorkerThread, SLOT(quit()));
+
+ m_WorkerThread.start(QThread::LowestPriority);
+}
+
+UsvfsConnector::~UsvfsConnector()
+{
+ DisconnectVFS();
+ m_LogWorker.exit();
+ m_WorkerThread.quit();
+ m_WorkerThread.wait();
+}
+
+
+void UsvfsConnector::updateMapping(const MappingType &mapping)
+{
+ QProgressDialog progress;
+ progress.setLabelText(tr("Preparing vfs"));
+ progress.setMaximum(static_cast<int>(mapping.size()));
+ progress.show();
+ int value = 0;
+
+ ClearVirtualMappings();
+
+ for (auto map : mapping) {
+ progress.setValue(value++);
+ if (value % 10 == 0) {
+ QCoreApplication::processEvents();
+ }
+
+ if (map.isDirectory) {
+ VirtualLinkDirectoryStatic(map.source.toStdWString().c_str(),
+ map.destination.toStdWString().c_str(),
+ (map.createTarget ? LINKFLAG_CREATETARGET : 0)
+ | LINKFLAG_RECURSIVE
+ );
+ } else {
+ VirtualLinkFile(map.source.toStdWString().c_str(),
+ map.destination.toStdWString().c_str(), 0);
+ }
+ }
+ /*
+ size_t dumpSize = 0;
+ CreateVFSDump(nullptr, &dumpSize);
+ std::unique_ptr<char[]> buffer(new char[dumpSize]);
+ CreateVFSDump(buffer.get(), &dumpSize);
+ qDebug(buffer.get());
+ */
+}
+
+void UsvfsConnector::setLogLevel(int logLevel) {
+ switch (logLevel) {
+ case LogLevel::Debug: SetLogLevel(LogLevel::Debug); break;
+ case LogLevel::Info: SetLogLevel(LogLevel::Info); break;
+ case LogLevel::Warning: SetLogLevel(LogLevel::Warning); break;
+ case LogLevel::Error: SetLogLevel(LogLevel::Error); break;
+ default: SetLogLevel(LogLevel::Debug); break;
+ }
+}
diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h
new file mode 100644
index 00000000..8f723a01
--- /dev/null
+++ b/src/usvfsconnector.h
@@ -0,0 +1,80 @@
+/*
+Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
+*/
+
+
+#ifndef USVFSCONNECTOR_H
+#define USVFSCONNECTOR_H
+
+
+#include <filemapping.h>
+#include <QString>
+#include <QThread>
+#include <QFile>
+#include <QDebug>
+
+
+class LogWorker : public QThread {
+
+ Q_OBJECT
+
+public:
+
+ LogWorker();
+ ~LogWorker();
+
+public slots:
+
+ void process();
+ void exit();
+
+signals:
+
+ void outputLog(const QString &message);
+ void finished();
+
+private:
+
+ std::string m_Buffer;
+ bool m_QuitRequested;
+ QFile m_LogFile;
+
+};
+
+
+class UsvfsConnector : public QObject {
+
+ Q_OBJECT
+
+public:
+
+ UsvfsConnector();
+ ~UsvfsConnector();
+
+ void updateMapping(const MappingType &mapping);
+ void setLogLevel(int logLevel);
+
+private:
+
+ LogWorker m_LogWorker;
+ QThread m_WorkerThread;
+
+};
+
+
+#endif // USVFSCONNECTOR_H
diff --git a/src/version.rc b/src/version.rc
index 50615bdc..34f9b7dd 100644
--- a/src/version.rc
+++ b/src/version.rc
@@ -1,7 +1,7 @@
#include "Winver.h"
-#define VER_FILEVERSION 1,3,11,0
-#define VER_FILEVERSION_STR "1,3,11,0\0"
+#define VER_FILEVERSION 2,0,7
+#define VER_FILEVERSION_STR "2.0.7beta\0"
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION