summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Rimpo <jeremy.rimpo@servermonkey.com>2017-12-09 23:44:21 -0600
committerGitHub <noreply@github.com>2017-12-09 23:44:21 -0600
commitb2d7181ca77980622f27a5e2a27c636d25a0a598 (patch)
treec3e2280cf8e2caa025a0856d4f8a7165149e76ac
parent0aaece55d8b5bb392523db4ee96029d0567294e8 (diff)
parent689248619ada12eb9e50ea40963a04c946cb3e13 (diff)
Merge pull request #139 from erasmux/better_diagnostics_and_shortcuts_fix
Better diagnostics and shortcuts fix
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/ilockedwaitingforprocess.h13
-rw-r--r--src/iuserinterface.h7
-rw-r--r--src/lockeddialog.h7
-rw-r--r--src/main.cpp85
-rw-r--r--src/mainwindow.cpp24
-rw-r--r--src/mainwindow.h4
-rw-r--r--src/organizercore.cpp233
-rw-r--r--src/organizercore.h11
-rw-r--r--src/settings.cpp37
-rw-r--r--src/settings.h25
-rw-r--r--src/settingsdialog.ui225
-rw-r--r--src/usvfsconnector.cpp44
-rw-r--r--src/usvfsconnector.h4
14 files changed, 468 insertions, 252 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d603336c..39aabb91 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -169,6 +169,7 @@ SET(organizer_HDRS
viewmarkingscrollbar.h
plugincontainer.h
organizercore.h
+ ilockedwaitingforprocess.h
iuserinterface.h
instancemanager.h
usvfsconnector.h
diff --git a/src/ilockedwaitingforprocess.h b/src/ilockedwaitingforprocess.h
new file mode 100644
index 00000000..6a4267d4
--- /dev/null
+++ b/src/ilockedwaitingforprocess.h
@@ -0,0 +1,13 @@
+#ifndef ILOCKEDWAITINGFORPROCESS_H
+#define ILOCKEDWAITINGFORPROCESS_H
+
+class QString;
+
+class ILockedWaitingForProcess
+{
+public:
+ virtual bool unlockClicked() = 0;
+ virtual void setProcessName(QString const &) = 0;
+};
+
+#endif // ILOCKEDWAITINGFORPROCESS_H
diff --git a/src/iuserinterface.h b/src/iuserinterface.h
index 0af1c2ac..255c7ac0 100644
--- a/src/iuserinterface.h
+++ b/src/iuserinterface.h
@@ -3,6 +3,7 @@
#include "modinfo.h"
+#include "ilockedwaitingforprocess.h"
#include <iplugintool.h>
#include <ipluginmodpage.h>
#include <delayedfilewriter.h>
@@ -27,12 +28,8 @@ public:
virtual void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab) = 0;
- virtual void lock() = 0;
+ virtual ILockedWaitingForProcess* lock() = 0;
virtual void unlock() = 0;
- virtual bool unlockClicked() = 0;
- virtual void setProcessName(QString const &) = 0;
-
-
};
#endif // IUSERINTERFACE_H
diff --git a/src/lockeddialog.h b/src/lockeddialog.h
index 29ac459b..8803efae 100644
--- a/src/lockeddialog.h
+++ b/src/lockeddialog.h
@@ -20,6 +20,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#ifndef LOCKEDDIALOG_H
#define LOCKEDDIALOG_H
+#include "ilockedwaitingforprocess.h"
#include <QDialog> // for QDialog
#include <QObject> // for Q_OBJECT, slots
#include <QString> // for QString
@@ -39,7 +40,7 @@ namespace Ui {
* data on which Mod Organizer works. After the UI is unlocked (manually or after the
* external application closed) MO will refresh all of its data sources
**/
-class LockedDialog : public QDialog
+class LockedDialog : public QDialog, public ILockedWaitingForProcess
{
Q_OBJECT
@@ -52,13 +53,13 @@ public:
*
* @return true if the user clicked the unlock button
**/
- bool unlockClicked() const { return m_UnlockClicked; }
+ bool unlockClicked() override { return m_UnlockClicked; }
/**
* @brief set the name of the process being run
* @param name of process
*/
- void setProcessName(const QString &name);
+ void setProcessName(const QString &name) override;
protected:
diff --git a/src/main.cpp b/src/main.cpp
index 526563a2..40224170 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -48,6 +48,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <eh.h>
#include <windows_error.h>
+#include <usvfs.h>
#include <QApplication>
#include <QPushButton>
@@ -127,71 +128,23 @@ bool isNxmLink(const QString &link)
static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionPtrs)
{
- typedef BOOL (WINAPI *FuncMiniDumpWriteDump)(HANDLE process, DWORD pid, HANDLE file, MINIDUMP_TYPE dumpType,
- const PMINIDUMP_EXCEPTION_INFORMATION exceptionParam,
- const PMINIDUMP_USER_STREAM_INFORMATION userStreamParam,
- const PMINIDUMP_CALLBACK_INFORMATION callbackParam);
- LONG result = EXCEPTION_CONTINUE_SEARCH;
-
- HMODULE dbgDLL = ::LoadLibrary(L"dbghelp.dll");
-
- static const int errorLen = 200;
- char errorBuffer[errorLen + 1];
- memset(errorBuffer, '\0', errorLen + 1);
-
- 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? "
- "If you send me this file (%1) to modorganizer@gmail.com, "
- "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(dumpName),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
-
- HANDLE dumpFile = ::CreateFile(dumpName.toStdWString().c_str(),
- GENERIC_WRITE, FILE_SHARE_WRITE, nullptr,
- CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
- if (dumpFile != INVALID_HANDLE_VALUE) {
- _MINIDUMP_EXCEPTION_INFORMATION exceptionInfo;
- exceptionInfo.ThreadId = ::GetCurrentThreadId();
- exceptionInfo.ExceptionPointers = exceptionPtrs;
- exceptionInfo.ClientPointers = false;
-
- BOOL success = funcDump(::GetCurrentProcess(), ::GetCurrentProcessId(), dumpFile,
- MiniDumpNormal, &exceptionInfo, nullptr, nullptr);
-
- ::FlushFileBuffers(dumpFile);
- ::CloseHandle(dumpFile);
- if (success) {
- return EXCEPTION_EXECUTE_HANDLER;
- }
- _snprintf(errorBuffer, errorLen, "failed to save minidump to %ls (error %lu)",
- dumpName.toStdWString().c_str(), ::GetLastError());
- } else {
- _snprintf(errorBuffer, errorLen, "failed to create %ls (error %lu)",
- dumpName.toStdWString().c_str(), ::GetLastError());
- }
- } else {
- return result;
- }
- } else {
- _snprintf(errorBuffer, errorLen, "dbghelp.dll outdated");
- }
- } else {
- _snprintf(errorBuffer, errorLen, "dbghelp.dll not found");
+ if ((exceptionPtrs->ExceptionRecord->ExceptionCode < 0x80000000) // non-critical
+ || (exceptionPtrs->ExceptionRecord->ExceptionCode == 0xe06d7363)) { // cpp exception
+ // don't report non-critical exceptions
+ return EXCEPTION_CONTINUE_SEARCH;
}
- QMessageBox::critical(nullptr, QObject::tr("Woops"),
- QObject::tr("ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1").arg(errorBuffer));
- return result;
+ const std::wstring& dumpPath = OrganizerCore::crashDumpsPath();
+ int dumpRes =
+ CreateMiniDump(exceptionPtrs, OrganizerCore::getGlobalCrashDumpsType(), dumpPath.c_str());
+ if (!dumpRes) {
+ qCritical("ModOrganizer has crashed, crash dump created.");
+ return EXCEPTION_EXECUTE_HANDLER;
+ }
+ else {
+ qCritical("ModOrganizer has crashed, CreateMiniDump failed (%d, error %lu).", dumpRes, GetLastError());
+ return EXCEPTION_CONTINUE_SEARCH;
+ }
}
static bool HaveWriteAccess(const std::wstring &path)
@@ -448,6 +401,10 @@ int runApplication(MOApplication &application, SingleInstance &instance,
QSettings settings(dataPath + "/"
+ QString::fromStdWString(AppConfig::iniFileName()),
QSettings::IniFormat);
+
+ // global crashDumpType sits in OrganizerCore to make a bit less ugly to update it when the settings are changed during runtime
+ OrganizerCore::setGlobalCrashDumpsType(settings.value("Settings/crash_dumps_type", static_cast<int>(CrashDumpsType::Mini)).toInt());
+
qDebug("initializing core");
OrganizerCore organizer(settings);
if (!organizer.bootstrap()) {
@@ -571,7 +528,7 @@ int runApplication(MOApplication &application, SingleInstance &instance,
int main(int argc, char *argv[])
{
- SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
+ AddVectoredExceptionHandler(0, MyUnhandledExceptionFilter);
MOApplication application(argc, argv);
QStringList arguments = application.arguments();
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 48dfdfbd..1db895fc 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1465,17 +1465,18 @@ void MainWindow::storeSettings(QSettings &settings) {
}
}
-void MainWindow::lock()
+ILockedWaitingForProcess* MainWindow::lock()
{
if (m_LockDialog != nullptr) {
++m_LockCount;
- return;
+ return m_LockDialog;
}
m_LockDialog = new LockedDialog(qApp->activeWindow());
m_LockDialog->show();
setEnabled(false);
m_LockDialog->setEnabled(true); //What's the point otherwise?
++m_LockCount;
+ return m_LockDialog;
}
void MainWindow::unlock()
@@ -1494,22 +1495,6 @@ void MainWindow::unlock()
}
}
-bool MainWindow::unlockClicked()
-{
- if (m_LockDialog != nullptr) {
- return m_LockDialog->unlockClicked();
- } else {
- return false;
- }
-}
-
-void MainWindow::setProcessName(QString const &name)
-{
- if (m_LockDialog != nullptr) {
- m_LockDialog->setProcessName(name);
- }
-}
-
void MainWindow::on_btnRefreshData_clicked()
{
m_OrganizerCore.refreshDirectoryStructure();
@@ -3296,7 +3281,8 @@ void MainWindow::on_actionSettings_triggered()
updateDownloadListDelegate();
- m_OrganizerCore.setLogLevel(settings.logLevel());
+ m_OrganizerCore.updateVFSParams(settings.logLevel(), settings.crashDumpsType());
+ m_OrganizerCore.cycleDiagnostics();
}
diff --git a/src/mainwindow.h b/src/mainwindow.h
index cec6c407..f6f11157 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -116,10 +116,8 @@ public:
void storeSettings(QSettings &settings) override;
void readSettings();
- virtual void lock() override;
+ virtual ILockedWaitingForProcess* lock() override;
virtual void unlock() override;
- virtual bool unlockClicked() override;
- virtual void setProcessName(QString const &name) override;
bool addProfile();
void refreshDataTree();
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index f8a368c9..91330d3a 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -31,6 +31,7 @@
#include "appconfig.h"
#include <report.h>
#include <questionboxmemory.h>
+#include "lockeddialog.h"
#include <QApplication>
#include <QCoreApplication>
@@ -47,6 +48,7 @@
#include <QtGlobal> // for qPrintable, etc
#include <Psapi.h>
+#include <Shlobj.h>
#include <tchar.h> // for _tcsicmp
#include <limits.h>
@@ -66,6 +68,9 @@
using namespace MOShared;
using namespace MOBase;
+//static
+CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None;
+
static bool isOnline()
{
QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
@@ -626,7 +631,8 @@ bool OrganizerCore::bootstrap() {
return createDirectory(m_Settings.getProfileDirectory()) &&
createDirectory(m_Settings.getModDirectory()) &&
createDirectory(m_Settings.getDownloadDirectory()) &&
- createDirectory(m_Settings.getOverwriteDirectory());
+ createDirectory(m_Settings.getOverwriteDirectory()) &&
+ createDirectory(QString::fromStdWString(crashDumpsPath())) && cycleDiagnostics();
}
void OrganizerCore::createDefaultProfile()
@@ -643,8 +649,29 @@ void OrganizerCore::prepareVFS()
m_USVFS.updateMapping(fileMapping(m_CurrentProfile->name(), QString()));
}
-void OrganizerCore::setLogLevel(int logLevel) {
- m_USVFS.setLogLevel(logLevel);
+void OrganizerCore::updateVFSParams(int logLevel, int crashDumpsType) {
+ setGlobalCrashDumpsType(crashDumpsType);
+ m_USVFS.updateParams(logLevel, crashDumpsType);
+}
+
+bool OrganizerCore::cycleDiagnostics() {
+ if (int maxDumps = settings().crashDumpsMax())
+ removeOldFiles(QString::fromStdWString(crashDumpsPath()), "*.dmp", maxDumps, QDir::Time|QDir::Reversed);
+ return true;
+}
+
+//static
+void OrganizerCore::setGlobalCrashDumpsType(int crashDumpsType) {
+ m_globalCrashDumpsType = ::crashDumpsType(crashDumpsType);
+}
+
+//static
+std::wstring OrganizerCore::crashDumpsPath() {
+ wchar_t appDataLocal[MAX_PATH]{ 0 };
+ ::SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, appDataLocal);
+ std::wstring dumpPath{ appDataLocal };
+ dumpPath += L"\\modorganizer";
+ return dumpPath;
}
void OrganizerCore::setCurrentProfile(const QString &profileName)
@@ -1041,8 +1068,9 @@ QStringList OrganizerCore::modsSortedByProfilePriority() const
void OrganizerCore::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir &currentDirectory, const QString &steamAppID, const QString &customOverwrite)
{
+ ILockedWaitingForProcess* uilock = nullptr;
if (m_UserInterface != nullptr) {
- m_UserInterface->lock();
+ uilock = m_UserInterface->lock();
}
ON_BLOCK_EXIT([&] () {
if (m_UserInterface != nullptr) { m_UserInterface->unlock(); }
@@ -1051,7 +1079,7 @@ void OrganizerCore::spawnBinary(const QFileInfo &binary, const QString &argument
HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, customOverwrite);
if (processHandle != INVALID_HANDLE_VALUE) {
DWORD processExitCode;
- (void)waitForProcessCompletion(processHandle, &processExitCode);
+ (void)waitForProcessCompletion(processHandle, &processExitCode, uilock);
refreshDirectoryStructure();
// need to remove our stored load order because it may be outdated if a foreign tool changed the
@@ -1064,6 +1092,7 @@ void OrganizerCore::spawnBinary(const QFileInfo &binary, const QString &argument
refreshESPList();
savePluginList();
+ cycleDiagnostics();
//These callbacks should not fiddle with directoy structure and ESPs.
m_FinishedRun(binary.absoluteFilePath(), processExitCode);
@@ -1154,9 +1183,13 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary,
= QString("launch \"%1\" \"%2\" %3")
.arg(QDir::toNativeSeparators(dataCwd),
QDir::toNativeSeparators(dataBinPath), 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() << ">";
return startBinary(binary, arguments, currentDirectory, true);
}
} else {
@@ -1227,121 +1260,131 @@ HANDLE OrganizerCore::startApplication(const QString &executable,
}
}
- return spawnBinaryDirect(binary, arguments, profileName, currentDirectory,
- steamAppID, customOverwrite);
+ HANDLE processHandle = spawnBinaryDirect(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite);
+ if (processHandle != INVALID_HANDLE_VALUE) {
+ std::unique_ptr<LockedDialog> dlg;
+ ILockedWaitingForProcess* uilock = nullptr;
+
+ if (m_UserInterface != nullptr) {
+ uilock = m_UserInterface->lock();
+ }
+ else {
+ // i.e. when running command line shortcuts there is no m_UserInterface
+ dlg.reset(new LockedDialog);
+ dlg->show();
+ dlg->setEnabled(true);
+ uilock = dlg.get();
+ }
+
+ ON_BLOCK_EXIT([&]() {
+ if (m_UserInterface != nullptr) {
+ m_UserInterface->unlock();
+ } });
+
+ DWORD processExitCode;
+ waitForProcessCompletion(processHandle, &processExitCode, uilock);
+ cycleDiagnostics();
+ }
+
+ return processHandle;
}
bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode)
{
+ ILockedWaitingForProcess* uilock = nullptr;
if (m_UserInterface != nullptr) {
- m_UserInterface->lock();
+ uilock = m_UserInterface->lock();
}
ON_BLOCK_EXIT([&] () {
if (m_UserInterface != nullptr) {
m_UserInterface->unlock();
} });
- return waitForProcessCompletion(handle, exitCode);
+ return waitForProcessCompletion(handle, exitCode, uilock);
}
-bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode)
+bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock)
{
- DWORD startPID = ::GetProcessId(handle);
+ bool originalHandle = true;
+ bool newHandle = true;
+ bool uiunlocked = false;
- static const DWORD maxCount = 5;
- size_t numProcesses = maxCount;
- LPDWORD processes = new DWORD[maxCount];
- std::map<DWORD, HANDLE> handles;
-
- bool tryAgain = true;
- DWORD moProcess = -1;
+ constexpr DWORD INPUT_EVENT = WAIT_OBJECT_0 + 1;
+ DWORD res = WAIT_TIMEOUT;
+ while (handle != INVALID_HANDLE_VALUE && (newHandle || res == WAIT_TIMEOUT || res == INPUT_EVENT))
+ {
+ if (newHandle) {
+ QString processName = QString::fromStdWString(getProcessName(handle));
+ processName += QString(" (%1)").arg(GetProcessId(handle));
+ if (uilock)
+ uilock->setProcessName(processName);
+ qDebug() << "Waiting for"
+ << (originalHandle ? "spawned" : "usvfs")
+ << "process completion :" << processName.toUtf8().constData();
+ newHandle = false;
+ }
- DWORD res;
- // Wait for a an event on the handle, a key press, mouse click or timeout
- while (
- res = ::MsgWaitForMultipleObjects(1, &handle, false, 500,
- QS_KEY | QS_MOUSEBUTTON),
- ((m_UserInterface == nullptr) || !m_UserInterface->unlockClicked())) {
+ // keep processing events so the app doesn't appear dead
+ QCoreApplication::processEvents();
- if (!::GetVFSProcessList(&numProcesses, processes)) {
- break;
- }
+ // Wait for a an event on the handle, a key press, mouse click or timeout
+ res = MsgWaitForMultipleObjects(1, &handle, FALSE, 500, QS_KEY | QS_MOUSEBUTTON);
+ if (res == WAIT_FAILED) {
+ qWarning() << "Failed waiting for process completion : MsgWaitForMultipleObjects WAIT_FAILED" << GetLastError();
+ break;
+ }
- // Get USvFS processes, build a handle map, and allow to continue if invalid PIDs are supplied
- bool found = false;
- size_t count =
- std::min<size_t>(static_cast<size_t>(maxCount), numProcesses);
- for (size_t i = 0; i < count; ++i) {
- DWORD currentProcess = processes[i];
- if (currentProcess != moProcess && handles.count(currentProcess) == 0) {
- HANDLE currentHandle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, currentProcess);
- std::wstring processName = getProcessName(currentHandle);
- if (!boost::starts_with(processName, L"ModOrganizer.exe")) {
- found = true;
- if (currentHandle == nullptr || currentHandle == INVALID_HANDLE_VALUE) continue;
- handles.insert(std::pair<DWORD, HANDLE>(currentProcess, currentHandle));
- }
- else
- {
- moProcess = processes[i];
- ::CloseHandle(currentHandle);
- }
- }
- }
+ if (uilock && uilock->unlockClicked()) {
+ uiunlocked = true;
+ break;
+ }
- // Clean up tracked handles
- for (std::map<DWORD, HANDLE>::iterator checkHandle = handles.begin(); checkHandle != handles.end(); ++checkHandle) {
- if (checkHandle->second != nullptr && checkHandle->second != INVALID_HANDLE_VALUE) {
- DWORD processExit;
- BOOL codeCheck = ::GetExitCodeProcess(checkHandle->second, &processExit);
- if (!codeCheck || processExit != STILL_ACTIVE) {
- if (!codeCheck) qDebug() << "Checking the process failed: Error Code " << ::GetLastError();
- ::CloseHandle(checkHandle->second);
- checkHandle = handles.erase(checkHandle);
- }
- }
- }
+ if (res == WAIT_OBJECT_0) {
+ // process we were waiting on has completed
+ if (originalHandle && !::GetExitCodeProcess(handle, exitCode))
+ qWarning() << "Failed getting exit code of complete process :" << GetLastError();
+ CloseHandle(handle);
+ handle = INVALID_HANDLE_VALUE;
+ originalHandle = false;
- // Update the lock process name with the name of the lowest active PID - though this may not actually be the main process
- if (handles.size() > 0)
- m_UserInterface->setProcessName(QString::fromStdWString(getProcessName(handles.begin()->second)));
+ // if the previous process spawned a child process and immediately exits we may miss it if we check immediately
+ QThread::msleep(500);
- // If the main wait process dies, we need a backup wait process until the subprocesses close
- if ((res == WAIT_FAILED) || (res == WAIT_OBJECT_0)) {
- if (handles.size() > 0) {
- // By the time we get here, the main wait function should always immediately continue
- // Passing in a handle doesn't seem to work for subprocesses
- ::MsgWaitForMultipleObjects(0, NULL, FALSE, 500, QS_KEY | QS_MOUSEBUTTON);
- }
- }
+ // search if there is another usvfs process active and if so wait for it
+ // in theory a querySize of 1 is probably enough since the MO process doesn't seem to be returned by GetVFSProcessList
+ constexpr size_t querySize = 2; // just to be on the safe side
+ DWORD pids[querySize];
+ size_t found = querySize;
+ if (!::GetVFSProcessList(&found, pids)) {
+ qWarning() << "Failed waiting for process completion : GetVFSProcessList failed?!";
+ break;
+ }
- // Give the process list a short time to populate
- // Required for initial USVFS boot and process switching
- if (handles.size() == 0 && !found) {
- if (tryAgain) {
- tryAgain = false;
- QThread::msleep(500);
- continue;
- }
- else {
- break;
- }
- }
- else {
- tryAgain = true;
- }
+ for (size_t i = 0; i < found; ++i) {
+ if (pids[i] == GetCurrentProcessId())
+ continue; // obviously don't wait for MO process
+ handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION|SYNCHRONIZE, FALSE, pids[i]);
+ if (handle == INVALID_HANDLE_VALUE) {
+ qWarning() << "Failed waiting for process completion : OpenProcess failed" << GetLastError();
+ continue;
+ }
+ newHandle = true;
+ break;
+ }
+ }
+ }
- // keep processing events so the app doesn't appear dead
- QCoreApplication::processEvents();
- }
+ if (res == WAIT_OBJECT_0)
+ qDebug() << "Waiting for process completion successfull";
+ else if (uiunlocked)
+ qDebug() << "Waiting for process completion aborted by UI";
+ else
+ qDebug() << "Waiting for process completion not successfull :" << res;
- //Cleanup
- if (handle != INVALID_HANDLE_VALUE) {
- ::CloseHandle(handle);
- }
- delete[] processes;
+ if (handle != INVALID_HANDLE_VALUE)
+ ::CloseHandle(handle);
- return res == WAIT_OBJECT_0;
+ return res == WAIT_OBJECT_0;
}
bool OrganizerCore::onAboutToRun(
diff --git a/src/organizercore.h b/src/organizercore.h
index 297b94f6..0927c88e 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -158,7 +158,13 @@ public:
void prepareVFS();
- void setLogLevel(int logLevel);
+ void updateVFSParams(int logLevel, int crashDumpsType);
+
+ bool cycleDiagnostics();
+
+ static CrashDumpsType getGlobalCrashDumpsType() { return m_globalCrashDumpsType; }
+ static void setGlobalCrashDumpsType(int crashDumpsType);
+ static std::wstring crashDumpsPath();
public:
MOBase::IModRepositoryBridge *createNexusBridge() const;
@@ -261,7 +267,7 @@ private:
const MOShared::DirectoryEntry *directoryEntry,
int createDestination);
- bool waitForProcessCompletion(HANDLE handle, LPDWORD exitCode);
+ bool waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock);
private slots:
@@ -319,6 +325,7 @@ private:
MOBase::DelayedFileWriter m_PluginListsWriter;
UsvfsConnector m_USVFS;
+ static CrashDumpsType m_globalCrashDumpsType;
};
#endif // ORGANIZERCORE_H
diff --git a/src/settings.cpp b/src/settings.cpp
index fc740302..05a62591 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -28,6 +28,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <iplugin.h>
#include <iplugingame.h>
#include <questionboxmemory.h>
+#include <usvfsparameters.h>
#include <QCheckBox>
#include <QCoreApplication>
@@ -38,6 +39,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QDirIterator>
#include <QFileInfo>
#include <QLineEdit>
+#include <QSpinBox>
#include <QListWidgetItem>
#include <QLocale>
#include <QMessageBox>
@@ -334,9 +336,18 @@ bool Settings::offlineMode() const
int Settings::logLevel() const
{
- return m_Settings.value("Settings/log_level", 0).toInt();
+ return m_Settings.value("Settings/log_level", static_cast<int>(LogLevel::Info)).toInt();
}
+int Settings::crashDumpsType() const
+{
+ return m_Settings.value("Settings/crash_dumps_type", static_cast<int>(CrashDumpsType::Mini)).toInt();
+}
+
+int Settings::crashDumpsMax() const
+{
+ return m_Settings.value("Settings/crash_dumps_max", 5).toInt();
+}
void Settings::setNexusLogin(QString username, QString password)
{
@@ -581,6 +592,7 @@ void Settings::query(QWidget *parent)
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 DiagnosticsTab(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)));
@@ -608,7 +620,6 @@ 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"))
@@ -640,7 +651,6 @@ Settings::GeneralTab::GeneralTab(Settings *m_parent, SettingsDialog &m_dialog)
}
}
- m_logLevelBox->setCurrentIndex(m_parent->logLevel());
m_compactBox->setChecked(m_parent->compactDownloads());
m_showMetaBox->setChecked(m_parent->metaDownloads());
m_usePrereleaseBox->setChecked(m_parent->usePrereleases());
@@ -662,9 +672,6 @@ void Settings::GeneralTab::update()
emit m_parent->styleChanged(newStyle);
}
- m_Settings.setValue("Settings/log_level", m_logLevelBox->currentIndex());
-
-
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());
@@ -743,6 +750,24 @@ void Settings::PathsTab::update()
}
}
+Settings::DiagnosticsTab::DiagnosticsTab(Settings *m_parent, SettingsDialog &m_dialog)
+ : Settings::SettingsTab(m_parent, m_dialog)
+ , m_logLevelBox(m_dialog.findChild<QComboBox *>("logLevelBox"))
+ , m_dumpsTypeBox(m_dialog.findChild<QComboBox *>("dumpsTypeBox"))
+ , m_dumpsMaxEdit(m_dialog.findChild<QSpinBox *>("dumpsMaxEdit"))
+{
+ m_logLevelBox->setCurrentIndex(m_parent->logLevel());
+ m_dumpsTypeBox->setCurrentIndex(m_parent->crashDumpsType());
+ m_dumpsMaxEdit->setValue(m_parent->crashDumpsMax());
+}
+
+void Settings::DiagnosticsTab::update()
+{
+ m_Settings.setValue("Settings/log_level", m_logLevelBox->currentIndex());
+ m_Settings.setValue("Settings/crash_dumps_type", m_dumpsTypeBox->currentIndex());
+ m_Settings.setValue("Settings/crash_dumps_max", m_dumpsMaxEdit->value());
+}
+
Settings::NexusTab::NexusTab(Settings *parent, SettingsDialog &dialog)
: Settings::SettingsTab(parent, dialog)
, m_loginCheckBox(dialog.findChild<QCheckBox *>("loginCheckBox"))
diff --git a/src/settings.h b/src/settings.h
index d316c82d..9ee29ba3 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -38,6 +38,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
class QCheckBox;
class QComboBox;
class QLineEdit;
+class QSpinBox;
class QListWidget;
class QWidget;
@@ -205,6 +206,16 @@ public:
int logLevel() const;
/**
+ * @return the configured crash dumps type
+ */
+ int crashDumpsType() const;
+
+ /**
+ * @return the configured crash dumps max
+ */
+ int crashDumpsMax() const;
+
+ /**
* @brief set the nexus login information
*
* @param username username
@@ -371,7 +382,6 @@ private:
private:
QComboBox *m_languageBox;
QComboBox *m_styleBox;
- QComboBox *m_logLevelBox;
QCheckBox *m_compactBox;
QCheckBox *m_showMetaBox;
QCheckBox *m_usePrereleaseBox;
@@ -393,6 +403,19 @@ private:
QLineEdit *m_overwriteDirEdit;
};
+ class DiagnosticsTab : public SettingsTab
+ {
+ public:
+ DiagnosticsTab(Settings *parent, SettingsDialog &dialog);
+
+ void update();
+
+ private:
+ QComboBox *m_logLevelBox;
+ QComboBox *m_dumpsTypeBox;
+ QSpinBox *m_dumpsMaxEdit;
+ };
+
/** Display/store the configuration in the 'nexus' tab of the settings dialogue */
class NexusTab : public SettingsTab
{
diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui
index f5e37ea6..632dc6c9 100644
--- a/src/settingsdialog.ui
+++ b/src/settingsdialog.ui
@@ -71,48 +71,6 @@ p, li { white-space: pre-wrap; }
</layout>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_6">
- <item>
- <widget class="QLabel" name="label_12">
- <property name="text">
- <string>Log Level</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="logLevelBox">
- <property name="toolTip">
- <string>Decides the amount of data printed to &quot;ModOrganizer.log&quot;</string>
- </property>
- <property name="whatsThis">
- <string>Decides the amount of data printed to &quot;ModOrganizer.log&quot;.
-&quot;Debug&quot; produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the &quot;Info&quot; level for regluar use. On the &quot;Error&quot; level the log file usually remains empty.</string>
- </property>
- <item>
- <property name="text">
- <string>Debug</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Info</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Warning</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Error</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </item>
- <item>
<widget class="QCheckBox" name="usePrereleaseBox">
<property name="toolTip">
<string>Update to non-stable releases.</string>
@@ -378,7 +336,188 @@ If you use pre-releases, never contact me directly by e-mail or via private mess
</item>
</layout>
</widget>
- <widget class="QWidget" name="nexusTab">
+ <widget class="QWidget" name="diagnosticsTab">
+ <attribute name="title">
+ <string>Diagnostics</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="0" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <item>
+ <widget class="QLabel" name="label_12">
+ <property name="text">
+ <string>Log Level</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="logLevelBox">
+ <property name="toolTip">
+ <string>Decides the amount of data printed to &quot;ModOrganizer.log&quot;</string>
+ </property>
+ <property name="whatsThis">
+ <string>
+ Decides the amount of data printed to &quot;ModOrganizer.log&quot;.
+ &quot;Debug&quot; produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the &quot;Info&quot; level for regluar use. On the &quot;Error&quot; level the log file usually remains empty.
+ </string>
+ </property>
+ <item>
+ <property name="text">
+ <string>Debug</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Info (recommended)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Warning</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Error</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0">
+ <spacer name="verticalSpacer_9">
+ <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 row="2" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout_12">
+ <item>
+ <widget class="QLabel" name="label_27">
+ <property name="text">
+ <string>Crash Dumps</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="dumpsTypeBox">
+ <property name="toolTip">
+ <string>Decides which type of crash dumps are collected when injected processes crash.</string>
+ </property>
+ <property name="whatsThis">
+ <string>
+ Decides which type of crash dumps are collected when injected processes crash.
+ &quot;None&quot; Disables the generation of crash dumps by MO.
+ &quot;Mini&quot; Default level which generates small dumps (only stack traces).
+ &quot;Data&quot; Much larger dumps with additional information which may be need (also data segments).
+ &quot;Full&quot; Even larger dumps with a full memory dump of the process.
+ </string>
+ </property>
+ <item>
+ <property name="text">
+ <string>None</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Mini (recommended)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Data</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Full</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="3" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout_13">
+ <item>
+ <widget class="QLabel" name="label_28">
+ <property name="text">
+ <string>Max Dumps To Keep</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_6">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>60</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="dumpsMaxEdit">
+ <property name="toolTip">
+ <string>Maximum number of crash dumps to keep on disk. Use 0 for unlimited.</string>
+ </property>
+ <property name="whatsThis">
+ <string>
+ Maximum number of crash dumps to keep on disk. Use 0 for unlimited.
+ Set "Crash Dumps" above to None to disable crash dump collection.
+ </string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_29">
+ <property name="text">
+ <string>
+ Dumps are stored in &lt;a href=&quot;%LOCALAPPDATA%\modorganizer&quot;&gt;%LOCALAPPDATA%\modorganizer&lt;/a&gt;.
+ Sending such dumps to the developers can help solve crashes caused by MO.
+ It is recommended to compress the dumps before sending, especially on the larger settings.
+ </string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>Hint: right click link and copy link location</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <spacer name="verticalSpacer_10">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>232</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="nexusTab">
<attribute name="title">
<string>Nexus</string>
</attribute>
diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp
index 0420ddc2..ffbdf3aa 100644
--- a/src/usvfsconnector.cpp
+++ b/src/usvfsconnector.cpp
@@ -19,6 +19,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "usvfsconnector.h"
#include "settings.h"
+#include "organizercore.h"
+#include "shared/util.h"
#include <memory>
#include <sstream>
#include <iomanip>
@@ -27,6 +29,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QProgressDialog>
#include <QDateTime>
#include <QCoreApplication>
+#include <qstandardpaths.h>
static const char SHMID[] = "mod_organizer_instance";
@@ -101,14 +104,33 @@ LogLevel logLevel(int level)
}
}
+CrashDumpsType crashDumpsType(int type)
+{
+ switch (type) {
+ case CrashDumpsType::Mini:
+ return CrashDumpsType::Mini;
+ case CrashDumpsType::Data:
+ return CrashDumpsType::Data;
+ case CrashDumpsType::Full:
+ return CrashDumpsType::Full;
+ default:
+ return CrashDumpsType::None;
+ }
+}
+
UsvfsConnector::UsvfsConnector()
{
USVFSParameters params;
LogLevel level = logLevel(Settings::instance().logLevel());
- USVFSInitParameters(&params, SHMID, false, level);
+ CrashDumpsType dumpType = crashDumpsType(Settings::instance().crashDumpsType());
+
+ std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true);
+ USVFSInitParameters(&params, SHMID, false, level, dumpType, dumpPath.c_str());
InitLogging(false);
+
+ qDebug("Initializing VFS <%s, %d, %d, %s>", params.instanceName, params.logLevel, params.crashDumpsType, params.crashDumpsPath);
+
CreateVFS(&params);
- SetLogLevel(level);
BlacklistExecutable(L"TSVNCache.exe");
@@ -136,6 +158,10 @@ void UsvfsConnector::updateMapping(const MappingType &mapping)
progress.setMaximum(static_cast<int>(mapping.size()));
progress.show();
int value = 0;
+ int files = 0;
+ int dirs = 0;
+
+ qDebug("Updating VFS mappings...");
ClearVirtualMappings();
@@ -151,11 +177,15 @@ void UsvfsConnector::updateMapping(const MappingType &mapping)
(map.createTarget ? LINKFLAG_CREATETARGET : 0)
| LINKFLAG_RECURSIVE
);
+ ++dirs;
} else {
VirtualLinkFile(map.source.toStdWString().c_str(),
map.destination.toStdWString().c_str(), 0);
+ ++files;
}
}
+
+ qDebug("VFS mappings updated <linked %d dirs, %d files>", dirs, files);
/*
size_t dumpSize = 0;
CreateVFSDump(nullptr, &dumpSize);
@@ -165,12 +195,6 @@ void UsvfsConnector::updateMapping(const MappingType &mapping)
*/
}
-void UsvfsConnector::setLogLevel(int logLevel) {
- switch (logLevel) {
- case LogLevel::Debug: SetLogLevel(LogLevel::Debug); break;
- case LogLevel::Info: SetLogLevel(LogLevel::Info); break;
- case LogLevel::Warning: SetLogLevel(LogLevel::Warning); break;
- case LogLevel::Error: SetLogLevel(LogLevel::Error); break;
- default: SetLogLevel(LogLevel::Debug); break;
- }
+void UsvfsConnector::updateParams(int logLevel, int crashDumpsType) {
+ USVFSUpdateParams(::logLevel(logLevel), ::crashDumpsType(crashDumpsType));
}
diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h
index 8f723a01..0935bac1 100644
--- a/src/usvfsconnector.h
+++ b/src/usvfsconnector.h
@@ -27,6 +27,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QThread>
#include <QFile>
#include <QDebug>
+#include <usvfsparameters.h>
class LogWorker : public QThread {
@@ -67,7 +68,7 @@ public:
~UsvfsConnector();
void updateMapping(const MappingType &mapping);
- void setLogLevel(int logLevel);
+ void updateParams(int logLevel, int crashDumpsType);
private:
@@ -76,5 +77,6 @@ private:
};
+CrashDumpsType crashDumpsType(int type);
#endif // USVFSCONNECTOR_H