From fed4ea257ab7ae7c154c4c4e3ac6ec8fd4093f59 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Tue, 26 Dec 2017 19:07:55 +0200 Subject: [PATCH 1/9] Fix mods priorities getting messed up when renaming a mod --- src/modlist.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/modlist.cpp b/src/modlist.cpp index 046e22804..4002a424b 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -426,13 +426,16 @@ bool ModList::renameMod(int index, const QString &newName) ModInfo::Ptr modInfo = ModInfo::getByIndex(index); QString oldName = modInfo->name(); - if (newName != oldName && modInfo->setName(nameFixed)) { - // before we rename, write back the current profile so we don't lose changes and to ensure - // there is no scheduled asynchronous rewrite anytime soon - m_Profile->writeModlistNow(); + if (newName != oldName) { + // before we rename, ensure there is no scheduled asynchronous to rewrite + m_Profile->cancelModlistWrite(); - // this just disabled the mod in all profiles. The recipient of modRenamed must fix that - emit modRenamed(oldName, nameFixed); + + if (modInfo->setName(nameFixed)) + // Notice there is a good chance that setName() updated the modinfo indexes + // the modRenamed() call will refresh the indexes in the current profile + // and update the modlists in all profiles + emit modRenamed(oldName, nameFixed); } // invalidate the currently displayed state of this list From 482f3d1f6d790ebd411aba4552ff4dcd936a2955 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Tue, 26 Dec 2017 19:38:37 +0200 Subject: [PATCH 2/9] Show the newly added mod when creating an empty mod --- src/mainwindow.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1c5e321de..3e1c6e048 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2595,6 +2595,8 @@ void MainWindow::createEmptyMod_clicked() if (newMod == nullptr) { return; } + + m_OrganizerCore.refreshModList(); } void MainWindow::createModFromOverwrite() From eb3cfe259430c374ad8aa33151a79002687691c8 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Tue, 26 Dec 2017 21:01:57 +0200 Subject: [PATCH 3/9] Default to sorting newest download first --- src/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3e1c6e048..263a29845 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3852,7 +3852,7 @@ void MainWindow::updateDownloadListDelegate() connect(ui->downloadFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(downloadFilterChanged(QString))); ui->downloadView->setModel(sortProxy); - ui->downloadView->sortByColumn(1, Qt::AscendingOrder); + ui->downloadView->sortByColumn(1, Qt::DescendingOrder); ui->downloadView->header()->resizeSections(QHeaderView::Fixed); connect(ui->downloadView->itemDelegate(), SIGNAL(installDownload(int)), &m_OrganizerCore, SLOT(installDownload(int))); From 9c9a43ec8da94a374e4a8ea3a7a5cfd2de341162 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Tue, 26 Dec 2017 21:03:57 +0200 Subject: [PATCH 4/9] print settings loading and changes to log for diagnostic purposes --- src/main.cpp | 7 +++++++ src/settings.cpp | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 3d315f1db..99ff641a9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -389,6 +389,13 @@ int runApplication(MOApplication &application, SingleInstance &instance, // global crashDumpType sits in OrganizerCore to make a bit less ugly to update it when the settings are changed during runtime OrganizerCore::setGlobalCrashDumpsType(settings.value("Settings/crash_dumps_type", static_cast(CrashDumpsType::Mini)).toInt()); + qDebug("Loaded settings:"); + settings.beginGroup("Settings"); + for (auto k : settings.allKeys()) + qDebug(" %s=%s", k.toUtf8().data(), settings.value(k).toString().toUtf8().data()); + settings.endGroup(); + + qDebug("initializing core"); OrganizerCore organizer(settings); if (!organizer.bootstrap()) { diff --git a/src/settings.cpp b/src/settings.cpp index b2fc8939c..028f88da1 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -601,10 +601,31 @@ void Settings::query(QWidget *parent) tabs.push_back(std::unique_ptr(new WorkaroundsTab(this, dialog))); if (dialog.exec() == QDialog::Accepted) { + // remember settings before change + QMap before; + m_Settings.beginGroup("Settings"); + for (auto k : m_Settings.allKeys()) + before[k] = m_Settings.value(k).toString(); + m_Settings.endGroup(); + // transfer modified settings to configuration file for (std::unique_ptr const &tab: tabs) { tab->update(); } + + // print "changed" settings + m_Settings.beginGroup("Settings"); + bool first_update = true; + for (auto k : m_Settings.allKeys()) + if (m_Settings.value(k).toString() != before[k]) + { + if (first_update) { + qDebug("Changed settings:"); + first_update = false; + } + qDebug(" %s=%s", k.toUtf8().data(), m_Settings.value(k).toString().toUtf8().data()); + } + m_Settings.endGroup(); } } From e623c13400a3cc43e564c0e00b85a21aeefe84dd Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Tue, 26 Dec 2017 23:15:21 +0200 Subject: [PATCH 5/9] Add clear overwrite option --- src/mainwindow.cpp | 26 ++++++++++++++++++++++++++ src/mainwindow.h | 1 + 2 files changed, 27 insertions(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 263a29845..8871075f1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2636,6 +2636,31 @@ void MainWindow::createModFromOverwrite() m_OrganizerCore.refreshModList(); } +void MainWindow::clearOverwrite() +{ + unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { + std::vector flags = mod->getFlags(); + return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) + != flags.end(); + }); + + ModInfo::Ptr modInfo = ModInfo::getByIndex(overwriteIndex); + if (modInfo) + { + QDir overwriteDir(modInfo->absolutePath()); + if (QMessageBox::question(this, tr("Are you sure?"), + tr("About to recursively delete:\n") + overwriteDir.absolutePath(), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) + { + QStringList delList; + for (auto f : overwriteDir.entryList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot)) + delList.push_back(overwriteDir.absoluteFilePath(f)); + shellDelete(delList, true); + updateProblemsButton(); + } + } +} + void MainWindow::cancelModListEditor() { ui->modList->setEnabled(false); @@ -3074,6 +3099,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) if (QDir(info->absolutePath()).count() > 2) { menu->addAction(tr("Sync to Mods..."), &m_OrganizerCore, SLOT(syncOverwrite())); menu->addAction(tr("Create Mod..."), this, SLOT(createModFromOverwrite())); + menu->addAction(tr("Clear Overwrite..."), this, SLOT(clearOverwrite())); } } else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) { menu->addAction(tr("Restore Backup"), this, SLOT(restoreBackup_clicked())); diff --git a/src/mainwindow.h b/src/mainwindow.h index a4ded6887..c9ba170c3 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -425,6 +425,7 @@ private slots: BSA::EErrorCode extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, QProgressDialog &extractProgress); void createModFromOverwrite(); + void clearOverwrite(); void procError(QProcess::ProcessError error); void procFinished(int exitCode, QProcess::ExitStatus exitStatus); From 9c82a1102e54b7fb350e18bd52cbf81083f42ca7 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Tue, 26 Dec 2017 23:23:04 +0200 Subject: [PATCH 6/9] Enable open in explorer option also for overwrite --- src/mainwindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8871075f1..c29e4ca48 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3100,6 +3100,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu->addAction(tr("Sync to Mods..."), &m_OrganizerCore, SLOT(syncOverwrite())); menu->addAction(tr("Create Mod..."), this, SLOT(createModFromOverwrite())); menu->addAction(tr("Clear Overwrite..."), this, SLOT(clearOverwrite())); + menu->addAction(tr("Open in explorer"), this, SLOT(openExplorer_clicked())); } } else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) { menu->addAction(tr("Restore Backup"), this, SLOT(restoreBackup_clicked())); From d41cd8d877223db1f35c12667bf59966d4d377cc Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Wed, 27 Dec 2017 00:17:18 +0200 Subject: [PATCH 7/9] Virtualize spawning of executable also if working directory is under mods folder --- src/organizercore.cpp | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 7668485cf..121de21a7 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1196,30 +1196,35 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, QString modsPath = settings().getModDirectory(); + // Check if this a request with either an executable or a working directory under our mods folder + // then will start the processs in a virtualized "environment" with the appropriate paths fixed: + // (i.e. mods\FNIS\path\exe => game\data\path\exe) + QString cwdPath = currentDirectory.absolutePath(); + bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); 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); + bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); + if (virtualizedCwd || virtualizedBin) { + if (virtualizedCwd) { + int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); + cwdPath = m_GamePlugin->dataDirectory().absolutePath() + cwdPath.mid(cwdOffset, -1); + } + + if (virtualizedBin) { + int binOffset = binPath.indexOf('/', modsPath.length() + 1); + binPath = m_GamePlugin->dataDirectory().absolutePath() + binPath.mid(binOffset, -1); + } + QString cmdline = QString("launch \"%1\" \"%2\" %3") - .arg(QDir::toNativeSeparators(dataCwd), - QDir::toNativeSeparators(dataBinPath), arguments); + .arg(QDir::toNativeSeparators(cwdPath), + QDir::toNativeSeparators(binPath), arguments); qDebug() << "Spawning proxyed process <" << cmdline << ">"; return startBinary(QFileInfo(QCoreApplication::applicationFilePath()), cmdline, QCoreApplication::applicationDirPath(), true); } else { - qDebug() << "Spawning direct process <" << binary.absoluteFilePath() << "," << arguments << "," << currentDirectory.absolutePath() << ">"; + qDebug() << "Spawning direct process <" << binPath << "," << arguments << "," << cwdPath << ">"; return startBinary(binary, arguments, currentDirectory, true); } } else { From 57e65abf75f62bf07880e0a2f888eaf5f14f0871 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Wed, 27 Dec 2017 01:31:43 +0200 Subject: [PATCH 8/9] Clean up proxy spawning a process (cleaner args forwarding and QProcess had some problems even without arguments) --- src/main.cpp | 73 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 99ff641a9..5418abc2e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -78,6 +78,7 @@ along with Mod Organizer. If not, see . #include #include +#include #include @@ -139,6 +140,60 @@ static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *except return EXCEPTION_CONTINUE_SEARCH; } +// Parses the first parseArgCount arguments of the current process command line and returns +// them in parsedArgs, the rest of the command line is returned untouched. +LPCWSTR UntouchedCommandLineArguments(int parseArgCount, std::vector& parsedArgs) +{ + LPCWSTR cmd = GetCommandLineW(); + LPCWSTR arg = nullptr; // to skip executable name + for (; parseArgCount >= 0 && *cmd; ++cmd) + { + if (*cmd == '"') { + int escaped = 0; + for (++cmd; *cmd && (*cmd != '"' || escaped % 2 != 0); ++cmd) + escaped = *cmd == '\\' ? escaped + 1 : 0; + } + if (*cmd == ' ') { + if (arg) + if (cmd-1 > arg && *arg == '"' && *(cmd-1) == '"') + parsedArgs.push_back(std::wstring(arg+1, cmd-1)); + else + parsedArgs.push_back(std::wstring(arg, cmd)); + arg = cmd + 1; + --parseArgCount; + } + } + return cmd; +} + +static int SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine) { + PROCESS_INFORMATION pi{ 0 }; + STARTUPINFO si{ 0 }; + si.cb = sizeof(si); + std::wstring commandLineCopy = commandLine; + + if (!CreateProcessW(NULL, &commandLineCopy[0], NULL, NULL, FALSE, 0, NULL, workingDirectory, &si, &pi)) { + // A bit of a problem where to log the error message here, at least this way you can get the message + // using a either DebugView or a live debugger: + std::wostringstream ost; + ost << L"CreateProcess failed: " << commandLine << ", " << GetLastError(); + OutputDebugStringW(ost.str().c_str()); + return -1; + } + + WaitForSingleObject(pi.hProcess, INFINITE); + + DWORD exitCode = (DWORD)-1; + ::GetExitCodeProcess(pi.hProcess, &exitCode); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + return static_cast(exitCode); +} + +static DWORD WaitForProcess() { + +} + static bool HaveWriteAccess(const std::wstring &path) { bool writable = false; @@ -532,20 +587,16 @@ int runApplication(MOApplication &application, SingleInstance &instance, int main(int argc, char *argv[]) { + if (argc >= 4) { + std::vector arg; + auto args = UntouchedCommandLineArguments(2, arg); + if (arg[0] == L"launch") + return SpawnWaitProcess(arg[1].c_str(), args); + } + MOApplication application(argc, argv); QStringList arguments = application.arguments(); - 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) From 548744d746223f0f96b33587ca41d2ed6a314f33 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Wed, 27 Dec 2017 04:18:46 +0200 Subject: [PATCH 9/9] Don't log username and password --- src/main.cpp | 3 ++- src/settings.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 5418abc2e..4c5ac75cc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -447,7 +447,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, qDebug("Loaded settings:"); settings.beginGroup("Settings"); for (auto k : settings.allKeys()) - qDebug(" %s=%s", k.toUtf8().data(), settings.value(k).toString().toUtf8().data()); + if (!k.contains("username") && !k.contains("password")) + qDebug(" %s=%s", k.toUtf8().data(), settings.value(k).toString().toUtf8().data()); settings.endGroup(); diff --git a/src/settings.cpp b/src/settings.cpp index 028f88da1..d1130e05a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -617,7 +617,7 @@ void Settings::query(QWidget *parent) m_Settings.beginGroup("Settings"); bool first_update = true; for (auto k : m_Settings.allKeys()) - if (m_Settings.value(k).toString() != before[k]) + if (m_Settings.value(k).toString() != before[k] && !k.contains("username") && !k.contains("password")) { if (first_update) { qDebug("Changed settings:");