diff options
Diffstat (limited to 'src')
87 files changed, 4208 insertions, 2583 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 94b75031..854f7667 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 @@ -243,7 +245,7 @@ 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 +266,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}") + +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}) 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..8d560f2b 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>746</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>
@@ -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/categories.cpp b/src/categories.cpp index 59291a49..50ba5271 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -188,7 +188,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 +317,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/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 76206f21..fedcfa26 100644 --- a/src/dlls.manifest.qt5 +++ b/src/dlls.manifest.qt5 @@ -4,6 +4,8 @@ <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"/>
diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index e327e55a..b92e171e 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,7 +752,7 @@ 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);
@@ -773,7 +769,7 @@ QString DownloadManager::getDisplayName(int index) const 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 +778,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 +792,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 +802,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 +812,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 +822,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 +837,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 +845,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 +854,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 +864,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 +879,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);
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 "Overwrite" 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/safewritefile.h b/src/eventfilter.h index 0af6bf98..2400a853 100644 --- a/src/safewritefile.h +++ b/src/eventfilter.h @@ -1,57 +1,44 @@ -/*
-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 <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/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..bf5ee91a 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -47,6 +47,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QApplication>
#include <QDateTime>
#include <QDirIterator>
+#include <QDebug>
#include <Shellapi.h>
@@ -67,7 +68,10 @@ 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;
}
@@ -77,12 +81,15 @@ InstallationManager::InstallationManager() : m_ParentWidget(nullptr)
, m_SupportedExtensions({ "zip", "rar", "7z", "fomod", "001" })
{
- 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()));
+ throw MyException(
+ 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 +97,6 @@ InstallationManager::InstallationManager() }
}
-
InstallationManager::~InstallationManager()
{
delete m_ArchiveHandler;
@@ -110,10 +116,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 +346,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 +639,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()) {
@@ -666,7 +674,8 @@ bool InstallationManager::install(const QString &fileName, GuessedValue<QString> 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();
}
@@ -703,7 +712,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 +726,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 +743,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 +762,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..d48d02d2 --- /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> + + +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) +{ +} + + +QString InstanceManager::currentInstance() const +{ + return m_AppSettings.value(INSTANCE_KEY, "").toString(); +} + +void InstanceManager::clearCurrentInstance() +{ + setCurrentInstance(""); +} + +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 +{ + SelectionDialog selection(QObject::tr("Choose Instance"), nullptr); + selection.disableCancel(); + for (const QString &instance : instanceList) { + selection.addChoice(instance, "", instance); + } + + selection.addChoice(QObject::tr("New"), + QObject::tr("Create a new instance."), + ""); + if (selection.exec() == QDialog::Rejected) { + qDebug("rejected"); + throw MOBase::MyException(QObject::tr("Canceled")); + } + + QString choice = selection.getChoiceData().toString(); + + if (choice.isEmpty()) { + return queryInstanceName(); + } else { + return choice; + } +} + + +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())); +} + + +InstanceManager::InstallationMode InstanceManager::queryInstallMode() const +{ + SelectionDialog selection(QObject::tr("Installation Mode"), nullptr); + selection.disableCancel(); + selection.addChoice(QObject::tr("Portable"), + QObject::tr("Everything in one directory, only one game per installation."), + 0); + selection.addChoice(QObject::tr("Regular"), + QObject::tr("Data in separate directory, multiple games supported."), + 1); + if (selection.exec() == QDialog::Rejected) { + throw MOBase::MyException(QObject::tr("Canceled")); + } + + switch (selection.getChoiceData().toInt()) { + case 0: return InstallationMode::PORTABLE; + default: return InstallationMode::REGULAR; + } +} + + +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()) { + // no portable install and no selected instance + + QStringList instanceList = instances(); + + if (instanceList.size() == 0) { + if (QFileInfo(qApp->applicationDirPath()).isWritable()) { + switch (queryInstallMode()) { + case InstallationMode::PORTABLE: { + instanceId = QString(); + } break; + case InstallationMode::REGULAR: { + instanceId = queryInstanceName(); + } break; + } + } else { + instanceId = queryInstanceName(); + } + } else { + // don't offer portable instance if we can't set one up. + instanceId = chooseInstance(instanceList); + } + } + + if (instanceId.isEmpty()) { + qDebug("portable mode"); + return qApp->applicationDirPath(); + } else { + setCurrentInstance(instanceId); + + QString dataPath = QDir::fromNativeSeparators( + QStandardPaths::writableLocation(QStandardPaths::DataLocation) + + "/" + instanceId); + + createDataPath(dataPath); + + return dataPath; + } +} + diff --git a/src/instancemanager.h b/src/instancemanager.h new file mode 100644 index 00000000..2e17d1c0 --- /dev/null +++ b/src/instancemanager.h @@ -0,0 +1,62 @@ +/* +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 { + + enum class InstallationMode { + PORTABLE, + REGULAR + }; + +public: + + InstanceManager(); + + QString determineDataPath(); + QStringList instances() const; + void clearCurrentInstance(); + +private: + + QString currentInstance() const; + QString instancePath() const; + + bool portableInstall() const; + + void setCurrentInstance(const QString &name); + + QString queryInstanceName() const; + QString chooseInstance(const QStringList &instanceList) const; + + void createDataPath(const QString &dataPath) const; + InstallationMode queryInstallMode() const; + +private: + + QSettings m_AppSettings; + +}; diff --git a/src/iuserinterface.h b/src/iuserinterface.h index e03bcde2..19e58c75 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..3cced2a1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -45,6 +45,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,6 +86,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. using namespace MOBase;
using namespace MOShared;
+
bool createAndMakeWritable(const std::wstring &subPath)
{
QString const dataPath = qApp->property("dataPath").toString();
@@ -101,7 +104,8 @@ bool createAndMakeWritable(const std::wstring &subPath) "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())) {
+ if (!Helper::init(qApp->applicationDirPath().toStdWString(),
+ dataPath.toStdWString())) {
return false;
}
} else {
@@ -124,7 +128,7 @@ bool bootstrap() // cycle logfile
removeOldFiles(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()),
- "ModOrganizer*.log", 5, QDir::Name);
+ "usvfs*.log", 5, QDir::Name);
createAndMakeWritable(AppConfig::profilesPath());
createAndMakeWritable(AppConfig::modsPath());
@@ -132,59 +136,9 @@ bool bootstrap() 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)");
- }
-
- HMODULE dllMod = ::LoadLibraryW(ToWString(dllName).c_str());
- if (dllMod == nullptr) {
- throw windows_error("hook.dll is missing or invalid");
- }
- ::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 +162,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 +173,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 +194,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 +404,106 @@ 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 path");
+ 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);
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 +511,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 +521,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());
+ } catch (const std::exception &e) {
+ reportError(
+ QObject::tr("failed to start application: %1").arg(e.what()));
+ return 1;
+ }
}
+ return 0;
}
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 +559,99 @@ 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().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 096ce94b..dcff139a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -71,14 +71,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>
@@ -142,15 +144,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>
@@ -180,8 +177,7 @@ using namespace MOBase; using namespace MOShared;
-MainWindow::MainWindow(const QString &exeName
- , QSettings &initSettings
+MainWindow::MainWindow(QSettings &initSettings
, OrganizerCore &organizerCore
, PluginContainer &pluginContainer
, QWidget *parent)
@@ -189,7 +185,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)
@@ -201,7 +196,6 @@ 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);
@@ -226,6 +220,7 @@ MainWindow::MainWindow(const QString &exeName m_RefreshProgress->setVisible(false);
statusBar()->addWidget(m_RefreshProgress, 1000);
statusBar()->clearMessage();
+ statusBar()->hide();
ui->actionEndorseMO->setVisible(false);
@@ -273,8 +268,6 @@ MainWindow::MainWindow(const QString &exeName }
ui->espList->installEventFilter(m_OrganizerCore.pluginList());
- ui->bsaList->setLocalMoveOnly(true);
-
ui->splitter->setStretchFactor(0, 3);
ui->splitter->setStretchFactor(1, 2);
@@ -293,6 +286,17 @@ 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)));
@@ -304,8 +308,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()));
@@ -327,11 +329,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)));
@@ -339,14 +338,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()));
@@ -388,6 +385,8 @@ MainWindow::MainWindow(const QString &exeName MainWindow::~MainWindow()
{
+ cleanup();
+
m_PluginContainer.setUserInterface(nullptr, nullptr);
m_OrganizerCore.setUserInterface(nullptr, nullptr);
m_IntegratedBrowser.close();
@@ -401,9 +400,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 ? "*" : ""));
}
@@ -574,7 +571,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"));
@@ -626,9 +623,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();
}
@@ -853,6 +850,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();
}
@@ -999,14 +1004,8 @@ void MainWindow::startExeAction() {
QAction *action = qobject_cast<QAction*>(sender());
if (action != nullptr) {
- 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);
+ m_OrganizerCore.executablesList()->find(action->text()));
} else {
qCritical("not an action?");
}
@@ -1067,9 +1066,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;
}
@@ -1164,7 +1161,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();
@@ -1201,7 +1198,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);
@@ -1319,9 +1316,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);
@@ -1344,163 +1342,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) {
@@ -1509,7 +1350,6 @@ void MainWindow::saveModMetas() }
}
-
void MainWindow::fixCategories()
{
for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
@@ -1580,10 +1420,6 @@ 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);
}
@@ -1601,7 +1437,6 @@ 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());
}
@@ -1641,12 +1476,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();
}
}
@@ -1678,15 +1511,7 @@ void MainWindow::installMod(QString fileName) 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);
+ m_OrganizerCore.spawnBinary(getSelectedExecutable());
}
@@ -1776,7 +1601,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;
@@ -1896,8 +1723,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);
@@ -1925,7 +1754,6 @@ void MainWindow::modorder_changed() }
m_OrganizerCore.refreshBSAList();
m_OrganizerCore.currentProfile()->modlistWriter().write();
- m_ArchiveListWriter.write();
m_OrganizerCore.directoryStructure()->getFileRegister()->sortOrigins();
{ // refresh selection
@@ -2116,11 +1944,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);
}
@@ -2256,7 +2087,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;
@@ -2268,7 +2099,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());
}
}
@@ -2582,7 +2413,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()
{
@@ -2661,7 +2517,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)) {
@@ -2695,7 +2551,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 {
@@ -2820,55 +2676,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();
@@ -2928,15 +2735,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) {
@@ -3045,6 +2858,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()));
@@ -3098,7 +2913,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();
@@ -3144,11 +2958,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()));
}
@@ -3175,7 +2985,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)) {
@@ -3191,7 +3001,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) {
@@ -3239,6 +3049,7 @@ void MainWindow::deleteSavegame_clicked() .arg(savesMsgLabel),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
shellDelete(deleteFiles, true); // recycle bin delete.
+ refreshSaveList();
}
}
@@ -3326,7 +3137,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);
@@ -3450,7 +3261,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();
@@ -3584,7 +3395,6 @@ void MainWindow::addAsExecutable() binaryInfo.absoluteFilePath(),
arguments,
targetInfo.absolutePath(),
- ExecutableInfo::CloseMOStyle::DEFAULT_STAY,
QString(),
Executable::CustomExecutable);
refreshExecutablesList();
@@ -3695,7 +3505,7 @@ void MainWindow::previewDataFile() };
addFunc(file->getOrigin());
- foreach (int i, file->getAlternatives()) {
+ for (int i : file->getAlternatives()) {
addFunc(i);
}
if (preview.numVariants() > 0) {
@@ -3713,10 +3523,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
@@ -3854,8 +3668,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) {
@@ -3872,7 +3685,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();
@@ -3926,7 +3739,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();
@@ -4049,7 +3862,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());
@@ -4061,32 +3874,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);
@@ -4404,10 +4191,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);
@@ -4502,6 +4294,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);
@@ -4515,11 +4308,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);
}
@@ -4531,10 +4320,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();
}
}
@@ -4576,7 +4362,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));
@@ -4662,17 +4448,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
@@ -4778,3 +4553,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 177048b4..43b2bede 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -105,9 +105,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();
@@ -120,15 +120,12 @@ public: virtual bool unlockClicked() 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);
@@ -151,9 +148,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);
@@ -167,7 +161,6 @@ public slots: signals:
-
/**
* @brief emitted after the information dialog has been closed
*/
@@ -190,8 +183,13 @@ protected: virtual void dragEnterEvent(QDragEnterEvent *event);
virtual void dropEvent(QDropEvent *event);
+private slots:
+ void on_clickBlankButton_clicked();
+
private:
+ void cleanup();
+
void actionToToolButton(QAction *&sourceAction);
void updateToolBar();
@@ -246,7 +244,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);
@@ -297,8 +295,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
@@ -345,14 +341,10 @@ private: std::vector<QTreeWidgetItem*> m_RemoveWidget;
- QByteArray m_ArchiveListHash;
-
bool m_DidUpdateMasterList;
LockedDialog *m_LockDialog { nullptr };
- MOBase::DelayedFileWriter m_ArchiveListWriter;
-
enum class ShortcutType {
Toolbar,
Desktop,
@@ -366,6 +358,8 @@ private: private slots:
+ void updateWindowTitle(const QString &accountName, bool premium);
+
void showMessage(const QString &message);
void showError(const QString &message);
@@ -378,6 +372,7 @@ private slots: // modlist context menu
void installMod_clicked();
+ void createEmptyMod_clicked();
void restoreBackup_clicked();
void renameMod_clicked();
void removeMod_clicked();
@@ -435,7 +430,6 @@ private slots: void addRemoveCategories_MenuHandler();
void replaceCategories_MenuHandler();
- void savePrimaryCategory();
void addPrimaryCategoryCandidates();
void modDetailsUpdated(bool success);
@@ -474,8 +468,6 @@ private slots: void startExeAction();
- void checkBSAList();
-
void updateProblemsButton();
void saveModMetas();
@@ -529,8 +521,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);
@@ -549,7 +539,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();
@@ -559,8 +548,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..f504994a 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><html><head/><body><p>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:</p><p>If archives are <span style=" font-weight:600;">managed</span>, 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.<br/></p><p>If archives are <span style=" font-weight:600;">not managed</span> 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.</p><p>In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.</p></body></html></string>
- </property>
- <property name="text">
- <string><html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html></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 "compete" with loose files in your data directory over which is loaded.
-By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored!
-
-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>
@@ -1487,11 +1396,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 Binary files differindex 59da6c2d..ba54d4dc 100644 --- a/src/mo_icon.ico +++ b/src/mo_icon.ico diff --git a/src/mo_icon.psd b/src/mo_icon.psd Binary files differdeleted file mode 100644 index 1f7b40c9..00000000 --- a/src/mo_icon.psd +++ /dev/null diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index d66c3ac5..5c4167ad 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -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..3c97ca85 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -112,7 +112,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 +121,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 +150,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]; @@ -289,7 +289,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/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..bf222166 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(); } diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index d0eb58a6..4a44c067 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -86,7 +86,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 +193,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("^([a-zA-Z0-9_'\"\\-.() ]*?)([-_ ][VvRr]?[0-9_]+)?-([1-9][0-9]+).*\\.(zip|rar|7z)");
static std::regex simpleexp("^([a-zA-Z0-9_]+)");
QByteArray fileNameUTF8 = fileName.toUtf8();
@@ -206,11 +206,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 +265,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,
@@ -564,8 +565,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/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 616b3806..a7dd6eef 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()
diff --git a/src/organizercore.cpp b/src/organizercore.cpp index cee367b0..9c900d53 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -18,6 +18,7 @@ #include "nxmaccessmanager.h"
#include <ipluginmodpage.h>
#include <dataarchives.h>
+#include <localsavegames.h>
#include <directoryentry.h>
#include <scopeguard.h>
#include <utility.h>
@@ -39,6 +40,7 @@ #include <exception>
#include <functional>
+#include <boost/algorithm/string/predicate.hpp>
#include <memory>
#include <set>
#include <utility>
@@ -47,16 +49,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;
@@ -71,7 +73,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);
@@ -99,11 +102,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;
@@ -117,8 +121,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."));
}
}
}
@@ -133,7 +138,6 @@ QStringList toStringList(InputIterator current, InputIterator end) return result;
}
-
OrganizerCore::OrganizerCore(const QSettings &initSettings)
: m_UserInterface(nullptr)
, m_PluginContainer(nullptr)
@@ -167,21 +171,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();
@@ -204,7 +220,7 @@ OrganizerCore::~OrganizerCore() ModInfo::clear();
LogBuffer::cleanQuit();
m_ModList.setProfile(nullptr);
- NexusInterface::instance()->cleanup();
+ // NexusInterface::instance()->cleanup();
delete m_DirectoryStructure;
}
@@ -213,7 +229,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);
}
@@ -228,7 +245,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);
@@ -248,7 +266,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);
}
}
@@ -262,31 +279,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));
}
}
@@ -298,7 +325,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;
}
@@ -313,17 +342,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;
}
}
@@ -348,53 +379,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 {
@@ -405,21 +447,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()
@@ -431,13 +466,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);
@@ -450,10 +485,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;
@@ -519,10 +554,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) {
@@ -550,19 +587,25 @@ InstallationManager *OrganizerCore::installationManager() 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::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;
+ QString profileDir = settings().getProfileDirectory() + "/" + profileName;
Profile *newProfile = new Profile(QDir(profileDir), managedGame());
delete m_CurrentProfile;
@@ -575,7 +618,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();
}
@@ -627,7 +671,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);
@@ -642,7 +689,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)
@@ -655,37 +703,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;
@@ -699,18 +754,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();
@@ -719,7 +777,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;
}
@@ -728,8 +787,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
@@ -737,7 +796,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();
}
@@ -749,7 +809,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);
@@ -757,12 +818,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);
@@ -773,8 +836,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());
@@ -786,7 +850,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 {
@@ -797,9 +862,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()));
@@ -808,10 +874,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) {
@@ -828,12 +897,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));
@@ -841,20 +913,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)) {
@@ -890,117 +969,164 @@ QStringList OrganizerCore::modsSortedByProfilePriority() const return res;
}
-void OrganizerCore::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, bool closeAfterStart, const QString &steamAppID)
+void OrganizerCore::spawnBinary(const Executable &exe)
+{
+ spawnBinary(
+ exe.m_BinaryInfo, exe.m_Arguments,
+ exe.m_WorkingDirectory.length() != 0 ? exe.m_WorkingDirectory
+ : exe.m_BinaryInfo.absolutePath(),
+ exe.m_SteamAppID,
+ m_CurrentProfile->setting("custom_overwrites", exe.m_Title).toString());
+}
+
+void OrganizerCore::spawnBinary(const QFileInfo &binary,
+ const QString &arguments,
+ const QDir ¤tDirectory,
+ const QString &steamAppID,
+ const QString &customOverwrite)
{
LockedDialog *dialog = new LockedDialog(qApp->activeWindow());
dialog->show();
- ON_BLOCK_EXIT([&] () { dialog->hide(); dialog->deleteLater(); });
+ ON_BLOCK_EXIT([&]() {
+ dialog->hide();
+ dialog->deleteLater();
+ });
- 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 {
- if (m_UserInterface != nullptr) {
- m_UserInterface->setWindowEnabled(false);
- }
- // re-enable the locked dialog because what'd be the point otherwise?
- dialog->setEnabled(true);
+ if (m_UserInterface != nullptr) {
+ m_UserInterface->setWindowEnabled(false);
+ }
+ // re-enable the locked dialog because what'd be the point otherwise?
+ dialog->setEnabled(true);
- QCoreApplication::processEvents();
+ QCoreApplication::processEvents();
- DWORD processExitCode;
- DWORD retLen;
- JOBOBJECT_BASIC_PROCESS_ID_LIST info;
+ DWORD processExitCode;
- {
- DWORD currentProcess = 0UL;
- bool isJobHandle = true;
+ dialog->setProcessName(
+ QString::fromStdWString(getProcessName(::GetProcessId(processHandle))));
- DWORD res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE);
- while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0) && !dialog->unlockClicked()) {
- if (isJobHandle) {
- if (::QueryInformationJobObject(processHandle, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) {
- if (info.NumberOfProcessIdsInList == 0) {
- break;
+ {
+ DWORD currentProcess = 0UL;
+
+ DWORD res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000,
+ QS_KEY | QS_MOUSE);
+ bool tryAgain = true;
+ while ((res != WAIT_FAILED) && !dialog->unlockClicked()) {
+ if (res == WAIT_OBJECT_0) {
+ // process ended, is there another one in the group?
+ static const DWORD maxCount = 5;
+ size_t numProcesses = maxCount;
+ LPDWORD processes = new DWORD[maxCount];
+ if (::GetVFSProcessList(&numProcesses, processes)) {
+ bool found = false;
+ for (size_t i = 0; i < std::min<DWORD>(maxCount, numProcesses);
+ ++i) {
+ std::wstring processName = getProcessName(processes[i]);
+ if (!boost::starts_with(processName, L"ModOrganizer.exe")) {
+ currentProcess = processes[i];
+ dialog->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 {
- if (info.ProcessIdList[0] != currentProcess) {
- currentProcess = info.ProcessIdList[0];
- dialog->setProcessName(ToQString(getProcessName(currentProcess)));
- }
+ break;
}
} 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;
- }
+ tryAgain = true;
}
+ } else {
+ break;
}
+ }
- // keep processing events so the app doesn't appear dead
- QCoreApplication::processEvents();
+ // keep processing events so the app doesn't appear dead
+ QCoreApplication::processEvents();
- res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE);
- }
- ::GetExitCodeProcess(processHandle, &processExitCode);
+ res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000,
+ QS_KEY | QS_MOUSE);
}
+ ::GetExitCodeProcess(processHandle, &processExitCode);
+
::CloseHandle(processHandle);
if (m_UserInterface != nullptr) {
m_UserInterface->setWindowEnabled(true);
}
- 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) {
+ // 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();
+
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();
- }
+ savePluginList();
m_FinishedRun(binary.absoluteFilePath(), processExitCode);
}
}
}
-HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName,
- const QDir ¤tDirectory, const QString &steamAppID)
+HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary,
+ const QString &arguments,
+ const QString &profileName,
+ const QDir ¤tDirectory,
+ 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();
if ((window != nullptr) && (!window->isVisible())) {
window = nullptr;
}
- if (QuestionBoxMemory::query(window, "steamQuery",
- tr("Start Steam?"),
- tr("Steam is required to be running already to correctly start the game. "
- "Should MO try to start steam now?"),
- QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::Yes) {
+ if (QuestionBoxMemory::query(window, "steamQuery", tr("Start Steam?"),
+ tr("Steam is required to be running already "
+ "to correctly start the game. "
+ "Should MO try to start steam now?"),
+ QDialogButtonBox::Yes | QDialogButtonBox::No)
+ == QDialogButtonBox::Yes) {
startSteam(qApp->activeWindow());
}
}
@@ -1016,19 +1142,52 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, const QString & m_CurrentProfile->modlistWriter().writeImmediately(true);
}
- // 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) {
@@ -1039,13 +1198,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();
@@ -1053,7 +1214,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 {
@@ -1061,6 +1225,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;
}
@@ -1068,20 +1235,22 @@ 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)
{
if (m_UserInterface != nullptr) {
m_UserInterface->lock();
- ON_BLOCK_EXIT([&] () { m_UserInterface->unlock(); });
+ ON_BLOCK_EXIT([&]() { m_UserInterface->unlock(); });
}
DWORD retLen;
@@ -1089,33 +1258,41 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) bool isJobHandle = true;
- ULONG lastProcessID = ULONG_MAX;
+ ULONG lastProcessID = ULONG_MAX;
HANDLE processHandle = handle;
- DWORD res = ::MsgWaitForMultipleObjects(1, &handle, false, 500, QS_KEY | QS_MOUSE);
- while ((res != WAIT_FAILED)
- && (res != WAIT_OBJECT_0)
- && ((m_UserInterface == nullptr) || !m_UserInterface->unlockClicked())) {
+ DWORD res
+ = ::MsgWaitForMultipleObjects(1, &handle, false, 500, QS_KEY | QS_MOUSE);
+ while (
+ (res != WAIT_FAILED) && (res != WAIT_OBJECT_0)
+ && ((m_UserInterface == nullptr) || !m_UserInterface->unlockClicked())) {
if (isJobHandle) {
- if (::QueryInformationJobObject(handle, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) {
+ 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.
+ // 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 (processHandle != handle) {
::CloseHandle(processHandle);
}
- processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, lastProcessID);
+ processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE,
+ lastProcessID);
}
}
} 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 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;
@@ -1126,7 +1303,8 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) // keep processing events so the app doesn't appear dead
QCoreApplication::processEvents();
- res = ::MsgWaitForMultipleObjects(1, &handle, false, 500, QS_KEY | QS_MOUSE);
+ res = ::MsgWaitForMultipleObjects(1, &handle, false, 500,
+ QS_KEY | QS_MOUSE);
}
if (exitCode != nullptr) {
@@ -1137,19 +1315,22 @@ bool OrganizerCore::waitForApplication(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();
@@ -1161,7 +1342,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();
@@ -1173,18 +1355,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()));
@@ -1198,8 +1380,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();
@@ -1207,16 +1391,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;
}
}
@@ -1226,17 +1406,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));
@@ -1252,49 +1434,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();
@@ -1306,7 +1489,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) {
@@ -1316,13 +1499,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());
}
}
@@ -1366,10 +1554,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()));
}
@@ -1383,13 +1572,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);
@@ -1398,12 +1585,18 @@ void OrganizerCore::directory_refreshed() for (auto task : m_PostRefreshTasks) {
task();
}
+
+ 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();
@@ -1419,12 +1612,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();
@@ -1432,14 +1623,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()));
}
}
@@ -1449,7 +1642,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();
@@ -1471,39 +1664,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();
@@ -1536,8 +1740,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");
@@ -1563,9 +1772,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()));
}
@@ -1577,18 +1783,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;
}
@@ -1599,21 +1806,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()),
+ QDir::toNativeSeparators(game->dataDirectory().absolutePath()),
+ 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;
@@ -1622,20 +1895,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 b50b3e9f..cd7af1b8 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>
@@ -85,7 +87,7 @@ public: void connectPlugins(PluginContainer *container);
void disconnectPlugins();
- void setManagedGame(const MOBase::IPluginGame *game);
+ void setManagedGame(MOBase::IPluginGame *game);
void updateExecutablesList(QSettings &settings);
@@ -127,8 +129,8 @@ public: void doAfterLogin(const std::function<void()> &function) { m_PostLoginTasks.append(function); }
- void spawnBinary(const QFileInfo &binary, const QString &arguments = "", const QDir ¤tDirectory = QDir(), bool closeAfterStart = true, const QString &steamAppID = "");
- HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID);
+ void spawnBinary(const Executable &exe);
+ HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID, const QString &customOverwrite);
void loginSuccessfulUpdate(bool necessary);
void loginFailedUpdate(const QString &message);
@@ -137,6 +139,8 @@ public: MOBase::DelayedFileWriter &pluginsWriter() { return m_PluginListsWriter; }
+ void prepareVFS();
+
public:
MOBase::IModRepositoryBridge *createNexusBridge() const;
QString profileName() const;
@@ -169,8 +173,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;
@@ -208,6 +210,8 @@ signals: void managedGameChanged(MOBase::IPluginGame const *gamePlugin);
+ void close();
+
private:
void storeSettings();
@@ -222,11 +226,22 @@ private: bool testForSteam();
- /*
- * std::vector<std::pair<QString, QString>> fileMapping(const QString &dataPath,
- const MOShared::DirectoryEntry *base,
- const MOShared::DirectoryEntry *directoryEntry);
-*/
+ /**
+ * @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);
+
+ void spawnBinary(const QFileInfo &binary, const QString &arguments = "",
+ const QDir ¤tDirectory = QDir(),
+ const QString &steamAppID = "",
+ const QString &customOverwrite = "");
private slots:
@@ -246,7 +261,7 @@ private: IUserInterface *m_UserInterface;
PluginContainer *m_PluginContainer;
QString m_GameName;
- MOBase::IPluginGame const *m_GamePlugin;
+ MOBase::IPluginGame *m_GamePlugin;
Profile *m_CurrentProfile;
@@ -282,6 +297,7 @@ private: bool m_ArchivesInit;
MOBase::DelayedFileWriter m_PluginListsWriter;
+ UsvfsConnector m_USVFS;
};
diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index b6dbd242..fe6809e3 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -152,6 +152,11 @@ QList<MOBase::IOrganizer::FileInfo> OrganizerProxy::findFileInfos(const QString return m_Proxied->findFileInfos(path, filter);
}
+MOBase::IProfile *OrganizerProxy::profile()
+{
+ return m_Proxied->currentProfile();
+}
+
MOBase::IDownloadManager *OrganizerProxy::downloadManager() const
{
return m_Proxied->downloadManager();
diff --git a/src/organizerproxy.h b/src/organizerproxy.h index 7ce8982e..77600671 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -34,6 +34,7 @@ public: virtual QStringList findFiles(const QString &path, const std::function<bool(const QString &)> &filter) const;
virtual QStringList getFileOrigins(const QString &fileName) const;
virtual QList<FileInfo> findFileInfos(const QString &path, const std::function<bool(const FileInfo&)> &filter) const;
+ virtual MOBase::IProfile *profile();
virtual MOBase::IDownloadManager *downloadManager() const;
virtual MOBase::IPluginList *pluginList() const;
@@ -59,5 +60,4 @@ private: };
-
#endif // ORGANIZERPROXY_H
diff --git a/src/persistentcookiejar.cpp b/src/persistentcookiejar.cpp index 6ca0de39..44537491 100644 --- a/src/persistentcookiejar.cpp +++ b/src/persistentcookiejar.cpp @@ -26,7 +26,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/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 bf5a09dc..5c21d0c8 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -20,7 +20,6 @@ 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 +27,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>
@@ -74,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()
@@ -117,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")) {
@@ -159,66 +154,70 @@ 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)
{
@@ -301,89 +300,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();
@@ -410,53 +326,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);
@@ -471,14 +340,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) {
@@ -552,7 +420,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;
}
@@ -606,7 +474,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
@@ -641,6 +509,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());
@@ -651,6 +530,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;
+ } 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());
@@ -688,7 +599,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;
@@ -730,10 +641,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;
}
}
@@ -741,7 +658,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;
}
@@ -818,8 +735,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) {
@@ -860,9 +775,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;
}
@@ -885,9 +797,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();
@@ -926,7 +835,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 (...) {
@@ -1002,8 +913,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;
@@ -1085,11 +998,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;
}
@@ -1196,7 +1109,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();
@@ -1206,11 +1118,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..9106cebe 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; } @@ -497,7 +502,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 +597,9 @@ bool Profile::invalidationActive(bool *supported) const } return false; } else { - *supported = false; + if (supported != nullptr) { + *supported = false; + } } return false; } @@ -633,11 +640,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 +659,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 +737,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 §ion, const QString &name, + const QVariant &fallback) +{ + return m_Settings->value(section + "/" + name, fallback); +} + +void Profile::storeSetting(const QString §ion, 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 §ion, const QString &name, + const QVariant &fallback = QVariant()); + + void storeSetting(const QString §ion, 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/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/savegame.h b/src/savegame.h new file mode 100644 index 00000000..e7220fcc --- /dev/null +++ b/src/savegame.h @@ -0,0 +1,121 @@ +/*
+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/>.
+*/
+
+#ifndef SAVEGAME_H
+#define SAVEGAME_H
+
+
+#include <QString>
+#include <QObject>
+#include <QImage>
+#include <QMetaType>
+#include <QFile>
+#include <QStringList>
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+namespace MOBase { class IPluginGame; }
+
+/**
+ * @brief represents a single save game
+ **/
+class SaveGame : public QObject {
+
+Q_OBJECT
+
+public:
+
+ /**
+ * @brief construct a save game and immediately read out information from the file
+ *
+ * @param filename absolute path of the save game file
+ **/
+ SaveGame(QObject *parent, const QString &filename, MOBase::IPluginGame const *game);
+
+ virtual ~SaveGame();
+
+ /**
+ * @brief read out information from a savegame
+ *
+ * @param fileName absolute path of the save game file
+ **/
+ virtual void readFile(const QString) { }
+
+ /**
+ * @return filename of this savegame
+ */
+ const QString &fileName() const { return m_FileName; }
+
+ /**
+ * @return a list of additional files that belong to this savegame
+ */
+ virtual QStringList attachedFiles() const;
+
+ /**
+ * @return a list of all files that belong to this savegame
+ */
+ virtual QStringList saveFiles() const;
+
+ /**
+ * @return name of the player character
+ **/
+ const QString &pcName() const { return m_PCName; }
+
+ /**
+ * @return level of the player character
+ **/
+ unsigned short pcLevel() const { return m_PCLevel; }
+
+ /**
+ * @return location of the player character
+ **/
+ const QString &pcLocation() const { return m_PCLocation; }
+
+ /**
+ * @return index of the save game
+ **/
+ unsigned long saveNumber() const { return m_SaveNumber; }
+
+ /**
+ * @return creation time of the save game
+ **/
+ SYSTEMTIME creationTime() const { return m_CreationTime; }
+
+ /**
+ * @return screenshot in the savegame
+ **/
+ const QImage &screenshot() const { return m_Screenshot; }
+
+protected:
+
+ QString m_FileName;
+ QString m_PCName { "N/A" };
+ unsigned short m_PCLevel { 0 };
+ QString m_PCLocation { "N/A" };
+ unsigned long m_SaveNumber { 0 };
+ SYSTEMTIME m_CreationTime;
+ QImage m_Screenshot;
+
+private:
+ MOBase::IPluginGame const * const m_Game;
+};
+
+
+#endif // SAVEGAME_H
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 7b812759..41d176ea 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -33,6 +33,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QLineEdit> #include <QMessageBox> #include <QRegExp> +#include <QDir> #include <memory> @@ -125,7 +126,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", @@ -179,11 +180,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; @@ -201,7 +207,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) { @@ -213,29 +219,55 @@ 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, + qApp->property("dataPath").toString() + "/" + def) + .toString()); + if (resolve) { + result.replace("%BASE_DIR%", getBaseDirectory()); + } + return result; +} + +QString Settings::getBaseDirectory() const +{ + return QDir::fromNativeSeparators(m_Settings.value( + "settings/base_directory", qApp->property("dataPath").toString()).toString()); +} + +QString Settings::getDownloadDirectory(bool resolve) const +{ + return getConfigurablePath("download_directory", ToQString(AppConfig::downloadPath()), resolve); +} + +QString Settings::getCacheDirectory(bool resolve) const { - return QDir::fromNativeSeparators(m_Settings.value(QString("settings/") + key, qApp->property("dataPath").toString() + "/" + def).toString()); + return getConfigurablePath("cache_directory", ToQString(AppConfig::cachePath()), resolve); } -QString Settings::getDownloadDirectory() const +QString Settings::getModDirectory(bool resolve) const { - return getConfigurablePath("download_directory", ToQString(AppConfig::downloadPath())); + return getConfigurablePath("mod_directory", ToQString(AppConfig::modsPath()), resolve); } -QString Settings::getCacheDirectory() const +QString Settings::getProfileDirectory(bool resolve) const { - return getConfigurablePath("cache_directory", ToQString(AppConfig::cachePath())); + return getConfigurablePath("profiles_directory", ToQString(AppConfig::profilesPath()), resolve); } -QString Settings::getModDirectory() const +QString Settings::getOverwriteDirectory(bool resolve) const { - return getConfigurablePath("mod_directory", ToQString(AppConfig::modsPath())); + 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; @@ -411,7 +443,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; @@ -433,7 +465,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) { @@ -457,7 +489,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); } @@ -503,12 +535,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()) { @@ -522,7 +549,7 @@ void Settings::resetDialogs() { m_Settings.beginGroup("DialogChoices"); QStringList keys = m_Settings.childKeys(); - foreach (QString key, keys) { + for (QString key : keys) { m_Settings.remove(key); } @@ -539,6 +566,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))); @@ -552,56 +580,56 @@ 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() @@ -622,68 +650,171 @@ 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())); +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()); + /* + m_downloadDirEdit->setText(m_parent->getDownloadDirectory(false)); + m_modDirEdit->setText(m_parent->getModDirectory(false)); + m_cacheDirEdit->setText(m_parent->getCacheDirectory(false)); + m_profilesDirEdit->setText(m_parent->getProfileDirectory(false)); + m_overwriteDirEdit->setText(m_parent->getOverwriteDirectory(false)); + */ + + 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); + qDebug("%s -> %s", qPrintable(dir.second), qPrintable(storePath)); + if (storePath.startsWith("..") || QDir::isAbsolutePath(storePath)) { + storePath = dir.second; } else { - m_Settings.remove("Settings/mod_directory"); + storePath = QString("%BASE_DIR%/") + storePath; } + dir.first->setText(storePath); + } +} + +void Settings::PathsTab::update() +{ + typedef std::tuple<QString, QString, std::wstring> Directory; + + QString basePath = m_parent->getBaseDirectory(); + + 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()); + } + + 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); - if (!QDir(m_cacheDirEdit->text()).exists()) { - QDir().mkpath(m_cacheDirEdit->text()); + QString realPath = path; + realPath.replace("%BASE_DIR%", m_parent->getBaseDirectory()); + + if (!QDir(realPath).exists()) { + QDir().mkpath(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())); + + qDebug("%s -> %s", qPrintable(path), qPrintable(realPath)); + + 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 (!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"); + } + + 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"); + } + + if (!QDir(m_cacheDirEdit->text()).exists()) { + QDir().mkpath(m_cacheDirEdit->text()); + } + 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())); + } else { + m_Settings.remove("Settings/cache_directory"); + } + + if (!QDir(m_profilesDirEdit->text()).exists()) { + QDir().mkpath(m_profilesDirEdit->text()); + } + if (QFileInfo(m_profilesDirEdit->text()) + != QFileInfo(qApp->property("dataPath").toString() + "/" + + QString::fromStdWString(AppConfig::profilesPath()))) { + m_Settings.setValue("Settings/profiles_directory", + QDir::toNativeSeparators(m_profilesDirEdit->text())); + } else { + m_Settings.remove("Settings/profiles_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"); @@ -743,11 +874,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()); @@ -763,10 +893,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) { @@ -799,20 +929,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 1ee16e76..9676df70 100644 --- a/src/settings.h +++ b/src/settings.h @@ -106,9 +106,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 @@ -118,7 +124,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 @@ -128,7 +134,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 @@ -290,6 +307,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, @@ -310,7 +329,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 { @@ -339,11 +358,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..e528f478 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -25,6 +25,7 @@ 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 <QDirIterator> #include <QFileDialog> @@ -39,11 +40,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,7 +57,7 @@ SettingsDialog::~SettingsDialog() void SettingsDialog::addPlugins(const std::vector<IPlugin*> &plugins) { - foreach (IPlugin *plugin, plugins) { + for (IPlugin *plugin : plugins) { ui->pluginsList->addItem(plugin->name()); } } @@ -89,13 +92,23 @@ 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()); @@ -106,7 +119,7 @@ void SettingsDialog::on_browseDownloadDirBtn_clicked() void SettingsDialog::on_browseModDirBtn_clicked() { - QString temp = QFileDialog::getExistingDirectory(this, tr("Select mod directory"), ui->downloadDirEdit->text()); + QString temp = QFileDialog::getExistingDirectory(this, tr("Select mod directory"), ui->modDirEdit->text()); if (!temp.isEmpty()) { ui->modDirEdit->setText(temp); } @@ -120,6 +133,22 @@ void SettingsDialog::on_browseCacheDirBtn_clicked() } } +void SettingsDialog::on_browseProfilesDirBtn_clicked() +{ + QString temp = QFileDialog::getExistingDirectory(this, tr("Select profiles directory"), ui->profilesDirEdit->text()); + if (!temp.isEmpty()) { + ui->profilesDirEdit->setText(temp); + } +} + +void SettingsDialog::on_browseOverwriteDirBtn_clicked() +{ + QString temp = QFileDialog::getExistingDirectory(this, tr("Select overwrite directory"), ui->overwriteDirEdit->text()); + if (!temp.isEmpty()) { + ui->overwriteDirEdit->setText(temp); + } +} + void SettingsDialog::on_resetDialogsButton_clicked() { if (QMessageBox::question(this, tr("Confirm?"), @@ -186,3 +215,15 @@ void SettingsDialog::on_associateButton_clicked() { Settings::instance().registerAsNXMHandler(true); } + +void SettingsDialog::on_changeInstanceButton_clicked() +{ + if (QMessageBox::question(this, tr("Are you sure?"), + tr("This will restart MO, continue?"), + QMessageBox::Yes | QMessageBox::Cancel) + == QMessageBox::Yes) { + InstanceManager().clearCurrentInstance(); + this->reject(); + qApp->exit(INT_MAX); + } +} diff --git a/src/settingsdialog.h b/src/settingsdialog.h index e99fb3e3..d266488d 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -52,6 +52,18 @@ signals: void resetDialogs();
+private slots:
+ void on_browseBaseDirBtn_clicked();
+
+private slots:
+ void on_browseOverwriteDirBtn_clicked();
+
+private slots:
+ void on_browseProfilesDirBtn_clicked();
+
+private slots:
+ void on_changeInstanceButton_clicked();
+
private:
void storeSettings(QListWidgetItem *pluginItem);
diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index b88885dc..808d8296 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -108,92 +108,36 @@ 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">
- <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="0" 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="0">
- <widget class="QLabel" name="label_8">
- <property name="text">
- <string>Mod Directory</string>
- </property>
- </widget>
- </item>
- <item row="2" 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="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>
- </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>
- </property>
- </widget>
- </item>
- </layout>
</widget>
</item>
<item>
+ <spacer name="verticalSpacer_7">
+ <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="QGroupBox" name="groupBox">
<property name="title">
<string>User interface</string>
@@ -219,30 +163,215 @@ p, li { white-space: pre-wrap; } </property>
</widget>
</item>
+ <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 "Remember selection"-box.</string>
+ </property>
+ <property name="text">
+ <string>Reset Dialogs</string>
+ </property>
+ </widget>
+ </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>
+ <property name="text">
+ <string>Configure Mod Categories</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
<item>
- <widget class="QPushButton" name="resetDialogsButton">
- <property name="maximumSize">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
<size>
- <width>16777215</width>
- <height>16777215</height>
+ <width>20</width>
+ <height>40</height>
</size>
</property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="changeInstanceButton">
<property name="toolTip">
- <string>Reset stored information from dialogs.</string>
+ <string>Restart MO and choose a different set of data paths.</string>
</property>
<property name="whatsThis">
- <string>This will make all dialogs show up again where you checked the "Remember selection"-box.</string>
+ <string>This will restart MO and give you opportunity to switch to a different set of data paths.</string>
</property>
<property name="text">
- <string>Reset Dialogs</string>
+ <string>Change Instance</string>
</property>
</widget>
</item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="pathsTab">
+ <attribute name="title">
+ <string>Paths</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_10">
<item>
- <spacer name="verticalSpacer">
+ <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>
+ <spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
@@ -255,15 +384,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>
@@ -614,7 +737,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">
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/fallout3info.cpp b/src/shared/fallout3info.cpp new file mode 100644 index 00000000..446d4afe --- /dev/null +++ b/src/shared/fallout3info.cpp @@ -0,0 +1,71 @@ +/*
+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 "fallout3info.h"
+#include "util.h"
+#include <tchar.h>
+#include <ShlObj.h>
+#include <sstream>
+#include "windows_error.h"
+#include "error_report.h"
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <boost/assign.hpp>
+
+namespace MOShared {
+
+Fallout3Info::Fallout3Info(const std::wstring &gameDirectory)
+ : GameInfo(gameDirectory)
+{
+ identifyMyGamesDirectory(L"fallout3");
+}
+
+bool Fallout3Info::identifyGame(const std::wstring &searchPath)
+{
+ return FileExists(searchPath, L"Fallout3.exe") &&
+ FileExists(searchPath, L"FalloutLauncher.exe");
+}
+
+std::wstring Fallout3Info::getRegPathStatic()
+{
+ HKEY key;
+ LONG errorcode = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Bethesda Softworks\\Fallout3",
+ 0, KEY_QUERY_VALUE, &key);
+
+ if (errorcode != ERROR_SUCCESS) {
+ return std::wstring();
+ }
+
+ WCHAR temp[MAX_PATH];
+ DWORD bufferSize = MAX_PATH;
+
+ if (::RegQueryValueExW(key, L"Installed Path", nullptr, nullptr, (LPBYTE)temp, &bufferSize) == ERROR_SUCCESS) {
+ return std::wstring(temp);
+ } else {
+ return std::wstring();
+ }
+}
+
+
+std::vector<std::wstring> Fallout3Info::getIniFileNames() const
+{
+ return boost::assign::list_of(L"fallout.ini")(L"falloutprefs.ini");
+}
+
+} // namespace MOShared
diff --git a/src/shared/fallout3info.h b/src/shared/fallout3info.h new file mode 100644 index 00000000..601fc346 --- /dev/null +++ b/src/shared/fallout3info.h @@ -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/>.
+*/
+
+#ifndef FALLOUT3INFO_H
+#define FALLOUT3INFO_H
+
+#include "gameinfo.h"
+
+namespace MOShared {
+
+
+class Fallout3Info : public GameInfo
+{
+
+ friend class GameInfo;
+
+public:
+
+ virtual ~Fallout3Info() {}
+
+ static std::wstring getRegPathStatic();
+ virtual std::wstring getRegPath() const { return getRegPathStatic(); }
+
+ // file name of this games ini (no path)
+ virtual std::vector<std::wstring> getIniFileNames() const;
+
+ virtual std::wstring archiveListKey() const { return L"SArchiveList"; }
+
+private:
+
+ Fallout3Info(const std::wstring &gameDirectory);
+
+ static bool identifyGame(const std::wstring &searchPath);
+
+};
+
+} // namespace MOShared
+
+#endif // FALLOUT3INFO_H
diff --git a/src/shared/fallout4info.cpp b/src/shared/fallout4info.cpp new file mode 100644 index 00000000..aab431ad --- /dev/null +++ b/src/shared/fallout4info.cpp @@ -0,0 +1,100 @@ +/* +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 "fallout4info.h" +#include "util.h" +#include <tchar.h> +#include <ShlObj.h> +#include <sstream> +#include "windows_error.h" +#include "error_report.h" +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#include <boost/assign.hpp> + +namespace MOShared { + +Fallout4Info::Fallout4Info(const std::wstring &gameDirectory) + : GameInfo(gameDirectory) +{ + identifyMyGamesDirectory(L"fallout4"); +} + +bool Fallout4Info::identifyGame(const std::wstring &searchPath) +{ + return FileExists(searchPath, L"Fallout4.exe") && + FileExists(searchPath, L"Fallout4Launcher.exe"); +} + +std::wstring Fallout4Info::getRegPathStatic() +{ + HKEY key; + LONG errorcode = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Bethesda Softworks\\Fallout4", + 0, KEY_QUERY_VALUE, &key); + + if (errorcode != ERROR_SUCCESS) { + return std::wstring(); + } + + WCHAR temp[MAX_PATH]; + DWORD bufferSize = MAX_PATH; + + if (::RegQueryValueExW(key, L"Installed Path", nullptr, nullptr, (LPBYTE)temp, &bufferSize) == ERROR_SUCCESS) { + return std::wstring(temp); + } else { + return std::wstring(); + } +} + + +std::vector<std::wstring> Fallout4Info::getDLCPlugins() +{ + return std::vector<std::wstring>(); +} + +std::vector<std::wstring> Fallout4Info::getSavegameAttachmentExtensions() +{ + return std::vector<std::wstring>(); +} + +std::vector<std::wstring> Fallout4Info::getIniFileNames() const +{ + return boost::assign::list_of(L"fallout4.ini")(L"fallout4prefs.ini"); +} + +std::wstring Fallout4Info::getNexusPage(bool nmmScheme) +{ + if (nmmScheme) { + return L"http://nmm.nexusmods.com/fallout4"; + } else { + return L"http://www.nexusmods.com/fallout4"; + } +} + +std::wstring Fallout4Info::getNexusInfoUrlStatic() +{ + return L"http://nmm.nexusmods.com/fallout4"; +} + +int Fallout4Info::getNexusModIDStatic() +{ + return 377160; +} + +} // namespace MOShared diff --git a/src/shared/fallout4info.h b/src/shared/fallout4info.h new file mode 100644 index 00000000..70c9b7d3 --- /dev/null +++ b/src/shared/fallout4info.h @@ -0,0 +1,75 @@ +/* +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/>. +*/ + +#ifndef FALLOUT4INFO_H +#define FALLOUT4INFO_H + + +#include "gameinfo.h" + +namespace MOShared { + + +class Fallout4Info : public GameInfo +{ + + friend class GameInfo; + +public: + + virtual ~Fallout4Info() {} + + static std::wstring getRegPathStatic(); + virtual std::wstring getRegPath() const { return getRegPathStatic(); } + virtual std::wstring getBinaryName() { return L"Fallout4.exe"; } + + virtual std::wstring getGameName() const { return L"Fallout 4"; } + virtual std::wstring getGameShortName() const { return L"Fallout4"; } + + virtual std::vector<std::wstring> getDLCPlugins(); + virtual std::vector<std::wstring> getSavegameAttachmentExtensions(); + + // file name of this games ini (no path) + virtual std::vector<std::wstring> getIniFileNames() const; + + virtual std::wstring getNexusPage(bool nmmScheme = true); + static std::wstring getNexusInfoUrlStatic(); + virtual std::wstring getNexusInfoUrl() { return getNexusInfoUrlStatic(); } + static int getNexusModIDStatic(); + virtual int getNexusModID() { return getNexusModIDStatic(); } + virtual int getNexusGameID() { return 1151; } + + // get a list of executables (game binary and known-to-work 3rd party tools). All of these are relative to + // the game directory + //virtual std::vector<ExecutableInfo> getExecutables(); + + virtual std::wstring archiveListKey() { return L"SArchiveList"; } + +private: + + Fallout4Info(const std::wstring &gameDirectory); + + static bool identifyGame(const std::wstring &searchPath); + + +}; + +} // namespace MOShared + +#endif // FALLOUT3INFO_H diff --git a/src/shared/falloutnvinfo.cpp b/src/shared/falloutnvinfo.cpp new file mode 100644 index 00000000..510dfa01 --- /dev/null +++ b/src/shared/falloutnvinfo.cpp @@ -0,0 +1,71 @@ +/*
+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 "falloutnvinfo.h"
+#include "util.h"
+#include <tchar.h>
+#include <ShlObj.h>
+#include <sstream>
+#include "windows_error.h"
+#include "error_report.h"
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <boost/assign.hpp>
+
+namespace MOShared {
+
+
+FalloutNVInfo::FalloutNVInfo(const std::wstring &gameDirectory)
+ : GameInfo(gameDirectory)
+{
+ identifyMyGamesDirectory(L"falloutnv");
+}
+
+bool FalloutNVInfo::identifyGame(const std::wstring &searchPath)
+{
+ return FileExists(searchPath, L"FalloutNV.exe") &&
+ FileExists(searchPath, L"FalloutNVLauncher.exe");
+}
+
+std::wstring FalloutNVInfo::getRegPathStatic()
+{
+ HKEY key;
+ LONG errorcode = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Bethesda Softworks\\FalloutNV",
+ 0, KEY_QUERY_VALUE, &key);
+
+ if (errorcode != ERROR_SUCCESS) {
+ return std::wstring();
+ }
+
+ WCHAR temp[MAX_PATH];
+ DWORD bufferSize = MAX_PATH;
+
+ if (::RegQueryValueExW(key, L"Installed Path", nullptr, nullptr, (LPBYTE)temp, &bufferSize) == ERROR_SUCCESS) {
+ return std::wstring(temp);
+ } else {
+ return std::wstring();
+ }
+}
+
+std::vector<std::wstring> FalloutNVInfo::getIniFileNames() const
+{
+ return boost::assign::list_of(L"fallout.ini")(L"falloutprefs.ini");
+}
+
+} // namespace MOShared
diff --git a/src/shared/falloutnvinfo.h b/src/shared/falloutnvinfo.h new file mode 100644 index 00000000..a9b3b9ec --- /dev/null +++ b/src/shared/falloutnvinfo.h @@ -0,0 +1,53 @@ +/*
+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/>.
+*/
+
+#ifndef FALLOUTNVINFO_H
+#define FALLOUTNVINFO_H
+
+
+#include "gameinfo.h"
+
+namespace MOShared {
+
+
+class FalloutNVInfo : public GameInfo
+{
+
+ friend class GameInfo;
+
+public:
+
+ virtual ~FalloutNVInfo() {}
+
+ static std::wstring getRegPathStatic();
+ virtual std::wstring getRegPath() const { return getRegPathStatic(); }
+
+ // file name of this games ini (no path)
+ virtual std::vector<std::wstring> getIniFileNames() const;
+
+private:
+
+ FalloutNVInfo(const std::wstring &gameDirectory);
+
+ static bool identifyGame(const std::wstring &searchPath);
+};
+
+} // namespace MOShared
+
+#endif // FALLOUTNVINFO_H
diff --git a/src/shared/gameinfo.cpp b/src/shared/gameinfo.cpp new file mode 100644 index 00000000..9b097053 --- /dev/null +++ b/src/shared/gameinfo.cpp @@ -0,0 +1,167 @@ +/*
+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 "gameinfo.h"
+
+#include "windows_error.h"
+
+#include "oblivioninfo.h"
+#include "fallout3info.h"
+#include "fallout4info.h"
+#include "falloutnvinfo.h"
+#include "skyriminfo.h"
+#include "util.h"
+
+#include <boost/assign.hpp>
+#include <boost/format.hpp>
+
+#include <shlobj.h>
+#include <sstream>
+#include <cassert>
+
+namespace MOShared {
+
+
+GameInfo* GameInfo::s_Instance = nullptr;
+
+
+GameInfo::GameInfo(const std::wstring &gameDirectory)
+ : m_GameDirectory(gameDirectory)
+{
+ atexit(&cleanup);
+}
+
+
+void GameInfo::cleanup() {
+ delete GameInfo::s_Instance;
+ GameInfo::s_Instance = nullptr;
+}
+
+
+void GameInfo::identifyMyGamesDirectory(const std::wstring &file)
+{
+ // this function attempts 3 (three!) ways to determine the correct "My Games" folder.
+ wchar_t myDocuments[MAX_PATH];
+ memset(myDocuments, '\0', MAX_PATH * sizeof(wchar_t));
+
+ m_MyGamesDirectory.clear();
+
+ // a) this is the way it should work. get the configured My Documents\My Games directory
+ if (::SHGetFolderPathW(nullptr, CSIDL_PERSONAL, nullptr, SHGFP_TYPE_CURRENT, myDocuments) == S_OK) {
+ m_MyGamesDirectory = std::wstring(myDocuments) + L"\\My Games";
+ }
+
+ // b) if there is no <game> directory there, look in the default directory
+ if (m_MyGamesDirectory.empty()
+ || !FileExists(m_MyGamesDirectory + L"\\" + file)) {
+ if (::SHGetFolderPathW(nullptr, CSIDL_PERSONAL, nullptr, SHGFP_TYPE_DEFAULT, myDocuments) == S_OK) {
+ std::wstring fromDefault = std::wstring(myDocuments) + L"\\My Games";
+ if (FileExists(fromDefault + L"\\" + file)) {
+ m_MyGamesDirectory = fromDefault;
+ }
+ }
+ }
+ // c) finally, look in the registry. This is discouraged
+ if (m_MyGamesDirectory.empty()
+ || !FileExists(m_MyGamesDirectory + L"\\" + file)) {
+ std::wstring fromRegistry = getSpecialPath(L"Personal") + L"\\My Games";
+ if (FileExists(fromRegistry + L"\\" + file)) {
+ m_MyGamesDirectory = fromRegistry;
+ }
+ }
+}
+
+bool GameInfo::identifyGame(const std::wstring &searchPath)
+{
+ if (OblivionInfo::identifyGame(searchPath)) {
+ s_Instance = new OblivionInfo(searchPath);
+ } else if (Fallout3Info::identifyGame(searchPath)) {
+ s_Instance = new Fallout3Info(searchPath);
+ } else if (FalloutNVInfo::identifyGame(searchPath)) {
+ s_Instance = new FalloutNVInfo(searchPath);
+ } else if (SkyrimInfo::identifyGame(searchPath)) {
+ s_Instance = new SkyrimInfo(searchPath);
+ } else if (Fallout4Info::identifyGame(searchPath)) {
+ s_Instance = new Fallout4Info(searchPath);
+ }
+
+ return s_Instance != nullptr;
+}
+
+
+bool GameInfo::init(const std::wstring &moDirectory, const std::wstring &gamePath)
+{
+ if (s_Instance == nullptr) {
+ if (gamePath.length() == 0) {
+ // search upward in the directory until a recognized game-binary is found
+ std::wstring searchPath(moDirectory);
+ while (!identifyGame(searchPath)) {
+ size_t lastSep = searchPath.find_last_of(L"/\\");
+ if (lastSep == std::string::npos) {
+ return false;
+ }
+ searchPath.erase(lastSep);
+ }
+ } else if (!identifyGame(gamePath)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+GameInfo &GameInfo::instance()
+{
+ assert(s_Instance != nullptr && "gameinfo not yet initialized");
+ return *s_Instance;
+}
+
+std::wstring GameInfo::getGameDirectory() const
+{
+ return m_GameDirectory;
+}
+
+std::wstring GameInfo::getSpecialPath(LPCWSTR name) const
+{
+ HKEY key;
+ LONG errorcode = ::RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders",
+ 0, KEY_QUERY_VALUE, &key);
+
+ if (errorcode != ERROR_SUCCESS) {
+ throw windows_error("failed to look up special folder (path)", errorcode);
+ }
+
+ WCHAR temp[MAX_PATH];
+ DWORD bufferSize = MAX_PATH;
+
+ errorcode = ::RegQueryValueExW(key, name, nullptr, nullptr, (LPBYTE)temp, &bufferSize);
+ if (errorcode != ERROR_SUCCESS) {
+ throw windows_error((boost::format("failed to look up special folder (%1%)") % ToString(name, true)).str(), errorcode);
+ }
+
+ WCHAR temp2[MAX_PATH];
+ // try to expand variables in the path, if any
+ if (::ExpandEnvironmentStringsW(temp, temp2, MAX_PATH) != 0) {
+ return temp2;
+ } else {
+ return temp;
+ }
+}
+
+} // namespace MOShared
diff --git a/src/shared/gameinfo.h b/src/shared/gameinfo.h new file mode 100644 index 00000000..c99ebef7 --- /dev/null +++ b/src/shared/gameinfo.h @@ -0,0 +1,89 @@ +/*
+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/>.
+*/
+
+#ifndef GAMEINFO_H
+#define GAMEINFO_H
+
+#include <string>
+#include <vector>
+#include <wchar.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+namespace MOShared {
+
+
+/**
+ Class to manage information that depends on the used game type. The intention is to keep
+ as much of the rest of omo is game-agnostic. It is mostly concerned with figuring out the
+ correct paths, filenames and data types.
+*/
+class GameInfo
+{
+
+public:
+
+ virtual ~GameInfo() {}
+
+ //**USED IN HOOKDLL and at startup to set up for hookdll to work
+ // initialise with the path to the mo directory (needs to be where hook.dll is stored). This
+ // needs to be called before the instance can be retrieved
+ static bool init(const std::wstring &moDirectory, const std::wstring &gamePath = L"");
+
+ //**USED ONLY IN HOOKDLL
+ static GameInfo& instance();
+
+ //**USED ONLY IN HOOKDLL
+ virtual std::wstring getGameDirectory() const;
+
+ //**USED ONLY IN HOOKDLL
+ virtual std::wstring getRegPath() const = 0;
+
+ //**USED ONLY IN HOOKDLL
+ // file name of this games ini file(s)
+ virtual std::vector<std::wstring> getIniFileNames() const = 0;
+
+protected:
+
+ GameInfo(const std::wstring &gameDirectory);
+
+ void identifyMyGamesDirectory(const std::wstring &file);
+
+private:
+
+ static bool identifyGame(const std::wstring &searchPath);
+ std::wstring getSpecialPath(LPCWSTR name) const;
+
+ static void cleanup();
+
+private:
+
+ static GameInfo *s_Instance;
+
+ std::wstring m_MyGamesDirectory;
+
+ std::wstring m_GameDirectory;
+
+};
+
+
+} // namespace MOShared
+
+#endif // GAMEINFO_H
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(¶meters, '\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, ¶meters, 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/oblivioninfo.cpp b/src/shared/oblivioninfo.cpp new file mode 100644 index 00000000..b290fb36 --- /dev/null +++ b/src/shared/oblivioninfo.cpp @@ -0,0 +1,71 @@ +/*
+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 "oblivioninfo.h"
+#include <tchar.h>
+#include <ShlObj.h>
+#include "util.h"
+#include <sstream>
+#include "windows_error.h"
+#include "error_report.h"
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <boost/assign.hpp>
+
+namespace MOShared {
+
+
+OblivionInfo::OblivionInfo(const std::wstring &gameDirectory)
+ : GameInfo(gameDirectory)
+{
+ identifyMyGamesDirectory(L"oblivion");
+}
+
+bool OblivionInfo::identifyGame(const std::wstring &searchPath)
+{
+ return FileExists(searchPath, L"Oblivion.exe") &&
+ FileExists(searchPath, L"OblivionLauncher.exe");
+}
+
+std::wstring OblivionInfo::getRegPathStatic()
+{
+ HKEY key;
+ LONG errorcode = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Bethesda Softworks\\Oblivion",
+ 0, KEY_QUERY_VALUE, &key);
+
+ if (errorcode != ERROR_SUCCESS) {
+ return std::wstring();
+ }
+
+ WCHAR temp[MAX_PATH];
+ DWORD bufferSize = MAX_PATH;
+
+ if (::RegQueryValueExW(key, L"Installed Path", nullptr, nullptr, (LPBYTE)temp, &bufferSize) == ERROR_SUCCESS) {
+ return std::wstring(temp);
+ } else {
+ return std::wstring();
+ }
+}
+
+std::vector<std::wstring> OblivionInfo::getIniFileNames() const
+{
+ return boost::assign::list_of(L"oblivion.ini")(L"oblivionprefs.ini");
+}
+
+} // namespace MOShared
diff --git a/src/shared/oblivioninfo.h b/src/shared/oblivioninfo.h new file mode 100644 index 00000000..6609921a --- /dev/null +++ b/src/shared/oblivioninfo.h @@ -0,0 +1,54 @@ +/*
+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/>.
+*/
+
+#ifndef OBLIVIONINFO_H
+#define OBLIVIONINFO_H
+
+#include "gameinfo.h"
+
+namespace MOShared {
+
+class OblivionInfo : public GameInfo
+{
+
+ friend class GameInfo;
+
+public:
+
+ virtual ~OblivionInfo() {}
+
+ static std::wstring getRegPathStatic();
+ virtual std::wstring getRegPath() const { return getRegPathStatic(); }
+
+ // file name of this games ini (no path)
+ virtual std::vector<std::wstring> getIniFileNames() const;
+
+ virtual std::wstring archiveListKey() const { return L"SArchiveList"; }
+
+private:
+
+ OblivionInfo(const std::wstring &gameDirectory);
+
+ static bool identifyGame(const std::wstring &searchPath);
+
+};
+
+} // namespace MOShared
+
+#endif // OBLIVIONINFO_H
diff --git a/src/shared/skyriminfo.cpp b/src/shared/skyriminfo.cpp new file mode 100644 index 00000000..fbe13ae0 --- /dev/null +++ b/src/shared/skyriminfo.cpp @@ -0,0 +1,81 @@ +/*
+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 "skyriminfo.h"
+
+#include "util.h"
+#include <tchar.h>
+#include <ShlObj.h>
+#include <sstream>
+#include "windows_error.h"
+#include "error_report.h"
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <Shlwapi.h>
+#include <boost/assign.hpp>
+
+namespace MOShared {
+
+
+SkyrimInfo::SkyrimInfo(const std::wstring &gameDirectory)
+ : GameInfo(gameDirectory)
+{
+ identifyMyGamesDirectory(L"skyrim");
+
+ wchar_t appDataPath[MAX_PATH];
+ if (SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA, nullptr, SHGFP_TYPE_CURRENT, appDataPath))) {
+ m_AppData = appDataPath;
+ }
+}
+
+bool SkyrimInfo::identifyGame(const std::wstring &searchPath)
+{
+ return FileExists(searchPath, L"TESV.exe") &&
+ FileExists(searchPath, L"SkyrimLauncher.exe");
+}
+
+
+
+
+std::wstring SkyrimInfo::getRegPathStatic()
+{
+ HKEY key;
+ LONG errorcode = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Bethesda Softworks\\Skyrim",
+ 0, KEY_QUERY_VALUE, &key);
+
+ if (errorcode != ERROR_SUCCESS) {
+ return std::wstring();
+ }
+
+ WCHAR temp[MAX_PATH];
+ DWORD bufferSize = MAX_PATH;
+
+ if (::RegQueryValueExW(key, L"Installed Path", nullptr, nullptr, (LPBYTE)temp, &bufferSize) == ERROR_SUCCESS) {
+ return std::wstring(temp);
+ } else {
+ return std::wstring();
+ }
+}
+
+std::vector<std::wstring> SkyrimInfo::getIniFileNames() const
+{
+ return boost::assign::list_of(L"skyrim.ini")(L"skyrimprefs.ini");
+}
+
+} // namespace MOShared
diff --git a/src/shared/skyriminfo.h b/src/shared/skyriminfo.h new file mode 100644 index 00000000..a13b834e --- /dev/null +++ b/src/shared/skyriminfo.h @@ -0,0 +1,58 @@ +/*
+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/>.
+*/
+
+#ifndef SKYRIMINFO_H
+#define SKYRIMINFO_H
+
+
+#include "gameinfo.h"
+
+namespace MOShared {
+
+
+class SkyrimInfo : public GameInfo
+{
+
+ friend class GameInfo;
+
+public:
+
+ virtual ~SkyrimInfo() {}
+
+ static std::wstring getRegPathStatic();
+ virtual std::wstring getRegPath() const { return getRegPathStatic(); }
+
+ // file name of this games ini (no path)
+ virtual std::vector<std::wstring> getIniFileNames() const;
+
+private:
+
+ SkyrimInfo(const std::wstring &gameDirectory);
+
+ static bool identifyGame(const std::wstring &searchPath);
+
+private:
+
+ std::wstring m_AppData;
+
+};
+
+} // namespace MOShared
+
+#endif // SKYRIMINFO_H
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..0ff64af0 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -23,6 +23,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "utility.h"
#include <report.h>
#include <inject.h>
+#include <usvfs.h>
+#include <Shellapi.h>
#include <appconfig.h>
#include <windows_error.h>
@@ -39,7 +41,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 +60,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 +90,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 +129,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 ¤tDirectory,
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 +169,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 ¤tDirectory, bool hooked,
HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE);
diff --git a/src/splash.png b/src/splash.png Binary files differindex 3eec8bed..d29a6711 100644 --- a/src/splash.png +++ b/src/splash.png 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..d8818a9a --- /dev/null +++ b/src/usvfsconnector.cpp @@ -0,0 +1,164 @@ +/* +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 <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; + USVFSInitParameters(¶ms, SHMID, false, + logLevel(Settings::instance().logLevel())); + InitLogging(false); + ConnectVFS(¶ms); + + 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()); + */ +} diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h new file mode 100644 index 00000000..78bb7a49 --- /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 <usvfs.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); + +private: + + LogWorker m_LogWorker; + QThread m_WorkerThread; + +}; + + +#endif // USVFSCONNECTOR_H diff --git a/src/version.rc b/src/version.rc index 50615bdc..445f7c19 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,6
+#define VER_FILEVERSION_STR "2.0.6beta\0"
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION
|
