summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorisanae <14251494+isanae@users.noreply.github.com>2019-09-22 15:14:53 -0400
committerGitHub <noreply@github.com>2019-09-22 15:14:53 -0400
commit5044a6cd2c7d75e2505e5abc6645fc990f343dc9 (patch)
treee06b037a1d44876a895fd5ecc47071477e52101b
parentf69559fe0bd40629e66ecde6e362b73595d9bd2e (diff)
parentc1a5f2ef73f4435c08876155d4551c045d5e7594 (diff)
Merge pull request #839 from isanae/even-more-logging
Even more logging
-rw-r--r--src/CMakeLists.txt6
-rw-r--r--src/env.cpp437
-rw-r--r--src/env.h109
-rw-r--r--src/envmetrics.cpp12
-rw-r--r--src/envmetrics.h9
-rw-r--r--src/envmodule.cpp81
-rw-r--r--src/envmodule.h29
-rw-r--r--src/envsecurity.cpp472
-rw-r--r--src/envsecurity.h26
-rw-r--r--src/envshortcut.h5
-rw-r--r--src/envwindows.h5
-rw-r--r--src/expanderwidget.cpp81
-rw-r--r--src/expanderwidget.h51
-rw-r--r--src/helper.cpp125
-rw-r--r--src/helper.h64
-rw-r--r--src/main.cpp16
-rw-r--r--src/mainwindow.cpp23
-rw-r--r--src/mainwindow.h3
-rw-r--r--src/modinfodialogconflicts.h2
-rw-r--r--src/organizercore.cpp299
-rw-r--r--src/pch.h1
-rw-r--r--src/pluginlist.cpp4
-rw-r--r--src/settings.cpp14
-rw-r--r--src/settings.h7
-rw-r--r--src/settingsdialog.cpp44
-rw-r--r--src/settingsdialog.h20
-rw-r--r--src/settingsdialog.ui2
-rw-r--r--src/settingsdialoggeneral.cpp7
-rw-r--r--src/settingsdialognexus.cpp14
-rw-r--r--src/settingsdialogpaths.cpp6
-rw-r--r--src/settingsdialogworkarounds.cpp59
-rw-r--r--src/settingsdialogworkarounds.h15
-rw-r--r--src/settingsutilities.cpp144
-rw-r--r--src/settingsutilities.h10
-rw-r--r--src/spawn.cpp926
-rw-r--r--src/spawn.h85
36 files changed, 2183 insertions, 1030 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ea27184b..7c29ff48 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -87,7 +87,6 @@ SET(organizer_SRCS
waitingonclosedialog.cpp
loadmechanism.cpp
installationmanager.cpp
- helper.cpp
filedialogmemory.cpp
executableslist.cpp
editexecutablesdialog.cpp
@@ -136,7 +135,6 @@ SET(organizer_SRCS
apiuseraccount.cpp
filerenamer.cpp
texteditor.cpp
- expanderwidget.cpp
env.cpp
envmetrics.cpp
envmodule.cpp
@@ -209,7 +207,6 @@ SET(organizer_HDRS
waitingonclosedialog.h
loadmechanism.h
installationmanager.h
- helper.h
filedialogmemory.h
executableslist.h
editexecutablesdialog.h
@@ -260,7 +257,6 @@ SET(organizer_HDRS
apiuseraccount.h
filerenamer.h
texteditor.h
- expanderwidget.h
env.h
envmetrics.h
envmodule.h
@@ -466,7 +462,6 @@ set(utilities
csvbuilder
shared/error_report
eventfilter
- helper
shared/leaktrace
persistentcookiejar
serverinfo
@@ -478,7 +473,6 @@ set(utilities
)
set(widgets
- expanderwidget
genericicondelegate
filerenamer
filterwidget
diff --git a/src/env.cpp b/src/env.cpp
index 641eb4a7..78b5dc96 100644
--- a/src/env.cpp
+++ b/src/env.cpp
@@ -4,6 +4,7 @@
#include "envsecurity.h"
#include "envshortcut.h"
#include "envwindows.h"
+#include "settings.h"
#include <log.h>
#include <utility.h>
@@ -56,10 +57,7 @@ Console::~Console()
Environment::Environment()
- : m_windows(new WindowsInfo), m_metrics(new Metrics)
{
- m_modules = getLoadedModules();
- m_security = getSecurityProducts();
}
// anchor
@@ -67,59 +65,380 @@ Environment::~Environment() = default;
const std::vector<Module>& Environment::loadedModules() const
{
+ if (m_modules.empty()){
+ m_modules = getLoadedModules();
+ }
+
return m_modules;
}
+std::vector<Process> Environment::runningProcesses() const
+{
+ return getRunningProcesses();
+}
+
const WindowsInfo& Environment::windowsInfo() const
{
+ if (!m_windows) {
+ m_windows.reset(new WindowsInfo);
+ }
+
return *m_windows;
}
const std::vector<SecurityProduct>& Environment::securityProducts() const
{
+ if (m_security.empty()) {
+ m_security = getSecurityProducts();
+ }
+
return m_security;
}
const Metrics& Environment::metrics() const
{
+ if (!m_metrics) {
+ m_metrics.reset(new Metrics);
+ }
+
return *m_metrics;
}
-void Environment::dump() const
+void Environment::dump(const Settings& s) const
{
- log::debug("windows: {}", m_windows->toString());
+ log::debug("windows: {}", windowsInfo().toString());
- if (m_windows->compatibilityMode()) {
+ if (windowsInfo().compatibilityMode()) {
log::warn("MO seems to be running in compatibility mode");
}
log::debug("security products:");
- for (const auto& sp : m_security) {
+ for (const auto& sp : securityProducts()) {
log::debug(" . {}", sp.toString());
}
log::debug("modules loaded in process:");
- for (const auto& m : m_modules) {
+ for (const auto& m : loadedModules()) {
log::debug(" . {}", m.toString());
}
log::debug("displays:");
- for (const auto& d : m_metrics->displays()) {
+ for (const auto& d : metrics().displays()) {
log::debug(" . {}", d.toString());
}
+
+ const auto r = metrics().desktopGeometry();
+ log::debug(
+ "desktop geometry: ({},{})-({},{})",
+ r.left(), r.top(), r.right(), r.bottom());
+
+ dumpDisks(s);
+}
+
+void Environment::dumpDisks(const Settings& s) const
+{
+ std::set<QString> rootPaths;
+
+ auto dump = [&](auto&& path) {
+ const QFileInfo fi(path);
+ const QStorageInfo si(fi.absoluteFilePath());
+
+ if (rootPaths.contains(si.rootPath())) {
+ // already seen
+ return;
+ }
+
+ // remember
+ rootPaths.insert(si.rootPath());
+
+ log::debug(
+ " . {} free={} MB{}",
+ si.rootPath(),
+ (si.bytesFree() / 1000 / 1000),
+ (si.isReadOnly() ? " (readonly)" : ""));
+ };
+
+ log::debug("drives:");
+
+ dump(QStorageInfo::root().rootPath());
+ dump(s.paths().base());
+ dump(s.paths().downloads());
+ dump(s.paths().mods());
+ dump(s.paths().cache());
+ dump(s.paths().profiles());
+ dump(s.paths().overwrite());
+ dump(QCoreApplication::applicationDirPath());
}
-struct Process
+QString path()
+{
+ return get("PATH");
+}
+
+QString addPath(const QString& s)
+{
+ auto old = path();
+ set("PATH", get("PATH") + ";" + s);
+ return old;
+}
+
+QString setPath(const QString& s)
+{
+ return set("PATH", s);
+}
+
+QString get(const QString& name)
+{
+ std::wstring s(4000, L' ');
+
+ DWORD realSize = ::GetEnvironmentVariableW(
+ name.toStdWString().c_str(), s.data(), static_cast<DWORD>(s.size()));
+
+ if (realSize > s.size()) {
+ s.resize(realSize);
+
+ ::GetEnvironmentVariableW(
+ name.toStdWString().c_str(), s.data(), static_cast<DWORD>(s.size()));
+ }
+
+ return QString::fromStdWString(s);
+}
+
+QString set(const QString& n, const QString& v)
+{
+ auto old = get(n);
+ ::SetEnvironmentVariableW(n.toStdWString().c_str(), v.toStdWString().c_str());
+ return old;
+}
+
+
+Service::Service(QString name)
+ : Service(std::move(name), StartType::None, Status::None)
+{
+}
+
+Service::Service(QString name, StartType st, Status s)
+ : m_name(std::move(name)), m_startType(st), m_status(s)
+{
+}
+
+const QString& Service::name() const
+{
+ return m_name;
+}
+
+bool Service::isValid() const
+{
+ return (m_startType != StartType::None) && (m_status != Status::None);
+}
+
+Service::StartType Service::startType() const
+{
+ return m_startType;
+}
+
+Service::Status Service::status() const
+{
+ return m_status;
+}
+
+QString Service::toString() const
+{
+ return QString("service '%1', start=%2, status=%3")
+ .arg(m_name)
+ .arg(env::toString(m_startType))
+ .arg(env::toString(m_status));
+}
+
+
+QString toString(Service::StartType st)
+{
+ using ST = Service::StartType;
+
+ switch (st)
+ {
+ case ST::None:
+ return "none";
+
+ case ST::Disabled:
+ return "disabled";
+
+ case ST::Enabled:
+ return "enabled";
+
+ default:
+ return QString("unknown %1").arg(static_cast<int>(st));
+ }
+}
+
+QString toString(Service::Status st)
+{
+ using S = Service::Status;
+
+ switch (st)
+ {
+ case S::None:
+ return "none";
+
+ case S::Stopped:
+ return "stopped";
+
+ case S::Running:
+ return "running";
+
+ default:
+ return QString("unknown %1").arg(static_cast<int>(st));
+ }
+}
+
+Service::StartType getServiceStartType(SC_HANDLE s, const QString& name)
+{
+ DWORD needed = 0;
+
+ if (!QueryServiceConfig(s, NULL, 0, &needed)) {
+ const auto e = GetLastError();
+
+ if (e != ERROR_INSUFFICIENT_BUFFER) {
+ log::error(
+ "QueryServiceConfig() for size for '{}' failed, {}",
+ name, GetLastError());
+
+ return Service::StartType::None;
+ }
+ }
+
+ const auto size = needed;
+ MallocPtr<QUERY_SERVICE_CONFIG> config(
+ static_cast<QUERY_SERVICE_CONFIG*>(std::malloc(size)));
+
+ if (!QueryServiceConfig(s, config.get(), size, &needed)) {
+ const auto e = GetLastError();
+
+ log::error(
+ "QueryServiceConfig() for '{}' failed", name, formatSystemMessage(e));
+
+ return Service::StartType::None;
+ }
+
+
+ switch (config->dwStartType)
+ {
+ case SERVICE_AUTO_START: // fall-through
+ case SERVICE_BOOT_START:
+ case SERVICE_DEMAND_START:
+ case SERVICE_SYSTEM_START:
+ {
+ return Service::StartType::Enabled;
+ }
+
+ case SERVICE_DISABLED:
+ {
+ return Service::StartType::Disabled;
+ }
+
+ default:
+ {
+ log::error(
+ "unknown service start type {} for '{}'",
+ config->dwStartType, name);
+
+ return Service::StartType::None;
+ }
+ }
+}
+
+Service::Status getServiceStatus(SC_HANDLE s, const QString& name)
{
- std::wstring filename;
- DWORD pid;
+ DWORD needed = 0;
+
+ if (!QueryServiceStatusEx(s, SC_STATUS_PROCESS_INFO, NULL, 0, &needed)) {
+ const auto e = GetLastError();
- Process(std::wstring f, DWORD id)
- : filename(std::move(f)), pid(id)
+ if (e != ERROR_INSUFFICIENT_BUFFER) {
+ log::error(
+ "QueryServiceStatusEx() for size for '{}' failed, {}",
+ name, GetLastError());
+
+ return Service::Status::None;
+ }
+ }
+
+ const auto size = needed;
+ MallocPtr<SERVICE_STATUS_PROCESS> status(
+ static_cast<SERVICE_STATUS_PROCESS*>(std::malloc(size)));
+
+ const auto r = QueryServiceStatusEx(
+ s, SC_STATUS_PROCESS_INFO, reinterpret_cast<BYTE*>(status.get()),
+ size, &needed);
+
+ if (!r) {
+ const auto e = GetLastError();
+
+ log::error(
+ "QueryServiceStatusEx() failed for '{}', {}",
+ name, formatSystemMessage(e));
+
+ return Service::Status::None;
+ }
+
+
+ switch (status->dwCurrentState)
{
+ case SERVICE_START_PENDING: // fall-through
+ case SERVICE_CONTINUE_PENDING:
+ case SERVICE_RUNNING:
+ {
+ return Service::Status::Running;
+ }
+
+ case SERVICE_STOPPED: // fall-through
+ case SERVICE_STOP_PENDING:
+ case SERVICE_PAUSE_PENDING:
+ case SERVICE_PAUSED:
+ {
+ return Service::Status::Stopped;
+ }
+
+ default:
+ {
+ log::error(
+ "unknown service status {} for '{}'",
+ status->dwCurrentState, name);
+
+ return Service::Status::None;
+ }
}
-};
+}
+
+Service getService(const QString& name)
+{
+ // service manager
+ const LocalPtr<SC_HANDLE> scm(OpenSCManager(
+ NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG));
+
+ if (!scm) {
+ const auto e = GetLastError();
+ log::error("OpenSCManager() failed, {}", formatSystemMessage(e));
+ return Service(name);
+ }
+
+ // service
+ const LocalPtr<SC_HANDLE> s(OpenService(
+ scm.get(), name.toStdWString().c_str(),
+ SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG));
+
+ if (!s) {
+ const auto e = GetLastError();
+ log::error("OpenService() failed for '{}', {}", name, formatSystemMessage(e));
+ return Service(name);
+ }
+
+ const auto startType = getServiceStartType(s.get(), name);
+ const auto status = getServiceStatus(s.get(), name);
+
+ return {name, startType, status};
+}
// returns the filename of the given process or the current one
@@ -177,84 +496,6 @@ std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE)
return {};
}
-std::vector<DWORD> runningProcessesIds()
-{
- // double the buffer size 10 times
- const int MaxTries = 10;
-
- // initial size of 300 processes, unlikely to be more than that
- std::size_t size = 300;
-
- for (int tries=0; tries<MaxTries; ++tries) {
- auto ids = std::make_unique<DWORD[]>(size);
- std::fill(ids.get(), ids.get() + size, 0);
-
- DWORD bytesGiven = static_cast<DWORD>(size * sizeof(ids[0]));
- DWORD bytesWritten = 0;
-
- if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten))
- {
- const auto e = GetLastError();
-
- std::wcerr
- << L"failed to enumerate processes, "
- << formatSystemMessage(e) << L"\n";
-
- return {};
- }
-
- if (bytesWritten == bytesGiven) {
- // no way to distinguish between an exact fit and not enough space,
- // just try again
- size *= 2;
- continue;
- }
-
- const auto count = bytesWritten / sizeof(ids[0]);
- return std::vector<DWORD>(ids.get(), ids.get() + count);
- }
-
- std::cerr << L"too many processes to enumerate";
- return {};
-}
-
-std::vector<Process> runningProcesses()
-{
- const auto pids = runningProcessesIds();
- std::vector<Process> v;
-
- for (const auto& pid : pids) {
- if (pid == 0) {
- // the idle process has pid 0 and seems to be picked up by EnumProcesses()
- continue;
- }
-
- HandlePtr h(OpenProcess(
- PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid));
-
- if (!h) {
- const auto e = GetLastError();
-
- if (e != ERROR_ACCESS_DENIED) {
- // don't log access denied, will happen a lot for system processes, even
- // when elevated
- std::wcerr
- << L"failed to open process " << pid << L", "
- << formatSystemMessage(e) << L"\n";
- }
-
- continue;
- }
-
- auto filename = processFilename(h.get());
- if (!filename.empty()) {
- v.emplace_back(std::move(filename), pid);
- }
- }
-
- return v;
-}
-
DWORD findOtherPid()
{
const std::wstring defaultName = L"ModOrganizer.exe";
@@ -266,7 +507,7 @@ DWORD findOtherPid()
std::wclog << L"this process id is " << thisPid << L"\n";
// getting the filename for this process, assumes the other process has the
- // smae one
+ // same one
auto filename = processFilename();
if (filename.empty()) {
std::wcerr
@@ -279,15 +520,15 @@ DWORD findOtherPid()
}
// getting all running processes
- const auto processes = runningProcesses();
+ const auto processes = getRunningProcesses();
std::wclog << L"there are " << processes.size() << L" processes running\n";
// going through processes, trying to find one with the same name and a
// different pid than this process has
for (const auto& p : processes) {
- if (p.filename == filename) {
- if (p.pid != thisPid) {
- return p.pid;
+ if (p.name() == filename) {
+ if (p.pid() != thisPid) {
+ return p.pid();
}
}
}
diff --git a/src/env.h b/src/env.h
index 0e88263b..1760c7fe 100644
--- a/src/env.h
+++ b/src/env.h
@@ -1,7 +1,13 @@
+#ifndef ENV_ENV_H
+#define ENV_ENV_H
+
+class Settings;
+
namespace env
{
class Module;
+class Process;
class SecurityProduct;
class WindowsInfo;
class Metrics;
@@ -74,6 +80,37 @@ template <class T>
using COMPtr = std::unique_ptr<T, COMReleaser>;
+// used by MallocPtr, calls std::free() as the deleter
+//
+struct MallocFreer
+{
+ void operator()(void* p)
+ {
+ std::free(p);
+ }
+};
+
+template <class T>
+using MallocPtr = std::unique_ptr<T, MallocFreer>;
+
+
+// used by LocalPtr, calls LocalFree() as the deleter
+//
+template <class T>
+struct LocalFreer
+{
+ using pointer = T;
+
+ void operator()(T p)
+ {
+ ::LocalFree(p);
+ }
+};
+
+template <class T>
+using LocalPtr = std::unique_ptr<T, LocalFreer<T>>;
+
+
// creates a console in the constructor and destroys it in the destructor,
// also redirects standard streams
//
@@ -111,6 +148,10 @@ public:
//
const std::vector<Module>& loadedModules() const;
+ // list of running processes; not cached
+ //
+ std::vector<Process> runningProcesses() const;
+
// information about the operating system
//
const WindowsInfo& windowsInfo() const;
@@ -125,16 +166,70 @@ public:
// logs the environment
//
- void dump() const;
+ void dump(const Settings& s) const;
private:
- std::vector<Module> m_modules;
- std::unique_ptr<WindowsInfo> m_windows;
- std::vector<SecurityProduct> m_security;
- std::unique_ptr<Metrics> m_metrics;
+ mutable std::vector<Module> m_modules;
+ mutable std::unique_ptr<WindowsInfo> m_windows;
+ mutable std::vector<SecurityProduct> m_security;
+ mutable std::unique_ptr<Metrics> m_metrics;
+
+ // dumps all the disks involved in the settings
+ //
+ void dumpDisks(const Settings& s) const;
};
+// environment variables
+//
+QString get(const QString& name);
+QString set(const QString& name, const QString& value);
+
+QString path();
+QString addPath(const QString& s);
+QString setPath(const QString& s);
+
+
+class Service
+{
+public:
+ enum class StartType
+ {
+ None = 0,
+ Disabled,
+ Enabled
+ };
+
+ enum class Status
+ {
+ None = 0,
+ Stopped,
+ Running
+ };
+
+
+ explicit Service(QString name);
+ Service(QString name, StartType st, Status s);
+
+ bool isValid() const;
+
+ const QString& name() const;
+ StartType startType() const;
+ Status status() const;
+
+ QString toString() const;
+
+private:
+ QString m_name;
+ StartType m_startType;
+ Status m_status;
+};
+
+
+Service getService(const QString& name);
+QString toString(Service::StartType st);
+QString toString(Service::Status st);
+
enum class CoreDumpTypes
{
Mini = 1,
@@ -142,7 +237,7 @@ enum class CoreDumpTypes
Full
};
-// creates a minidump file for the given process
+// creates a minidump file for this process
//
bool coredump(CoreDumpTypes type);
@@ -152,3 +247,5 @@ bool coredump(CoreDumpTypes type);
bool coredumpOther(CoreDumpTypes type);
} // namespace env
+
+#endif // ENV_ENV_H
diff --git a/src/envmetrics.cpp b/src/envmetrics.cpp
index b1b9bd2e..5fb80449 100644
--- a/src/envmetrics.cpp
+++ b/src/envmetrics.cpp
@@ -4,6 +4,7 @@
#include <shellscalingapi.h>
#include <log.h>
#include <utility.h>
+#include <QDesktopWidget>
namespace env
{
@@ -225,6 +226,17 @@ const std::vector<Display>& Metrics::displays() const
return m_displays;
}
+QRect Metrics::desktopGeometry() const
+{
+ QRect r;
+
+ for (auto* s : QGuiApplication::screens()) {
+ r = r.united(s->geometry());
+ }
+
+ return r;
+}
+
void Metrics::getDisplays()
{
// don't bother if it goes over 100
diff --git a/src/envmetrics.h b/src/envmetrics.h
index bede36fc..8dfdb087 100644
--- a/src/envmetrics.h
+++ b/src/envmetrics.h
@@ -1,3 +1,6 @@
+#ifndef ENV_METRICS_H
+#define ENV_METRICS_H
+
#include <QString>
#include <vector>
@@ -63,6 +66,10 @@ public:
//
const std::vector<Display>& displays() const;
+ // full resolution
+ //
+ QRect desktopGeometry() const;
+
private:
std::vector<Display> m_displays;
@@ -70,3 +77,5 @@ private:
};
} // namespace
+
+#endif // ENV_METRICS_H
diff --git a/src/envmodule.cpp b/src/envmodule.cpp
index 8cea414a..3f1f8912 100644
--- a/src/envmodule.cpp
+++ b/src/envmodule.cpp
@@ -320,6 +320,40 @@ QString Module::getMD5() const
}
+Process::Process(DWORD pid, QString name)
+ : m_pid(pid), m_name(std::move(name))
+{
+}
+
+DWORD Process::pid() const
+{
+ return m_pid;
+}
+
+const QString& Process::name() const
+{
+ return m_name;
+}
+
+// whether this process can be accessed; fails if the current process doesn't
+// have the proper permissions
+//
+bool Process::canAccess() const
+{
+ HandlePtr h(OpenProcess(
+ PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, m_pid));
+
+ if (!h) {
+ const auto e = GetLastError();
+ if (e == ERROR_ACCESS_DENIED) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
std::vector<Module> getLoadedModules()
{
HandlePtr snapshot(CreateToolhelp32Snapshot(
@@ -373,4 +407,51 @@ std::vector<Module> getLoadedModules()
return v;
}
+
+std::vector<Process> getRunningProcesses()
+{
+ HandlePtr snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
+
+ if (snapshot.get() == INVALID_HANDLE_VALUE)
+ {
+ const auto e = GetLastError();
+ log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessage(e));
+ return {};
+ }
+
+ PROCESSENTRY32 entry = {};
+ entry.dwSize = sizeof(entry);
+
+ // first process, this shouldn't fail because there's at least one process
+ // running
+ if (!Process32First(snapshot.get(), &entry)) {
+ const auto e = GetLastError();
+ log::error("Process32First() failed, {}", formatSystemMessage(e));
+ return {};
+ }
+
+ std::vector<Process> v;
+
+ for (;;)
+ {
+ v.push_back(Process(
+ entry.th32ProcessID,
+ QString::fromStdWString(entry.szExeFile)));
+
+ // next process
+ if (!Process32Next(snapshot.get(), &entry))
+ {
+ const auto e = GetLastError();
+
+ // no more processes is not an error
+ if (e != ERROR_NO_MORE_FILES)
+ log::error("Process32Next() failed, {}", formatSystemMessage(e));
+
+ break;
+ }
+ }
+
+ return v;
+}
+
} // namespace
diff --git a/src/envmodule.h b/src/envmodule.h
index ea1156bd..deb7520f 100644
--- a/src/envmodule.h
+++ b/src/envmodule.h
@@ -1,3 +1,6 @@
+#ifndef ENV_MODULE_H
+#define ENV_MODULE_H
+
#include <QString>
#include <QDateTime>
@@ -9,7 +12,7 @@ namespace env
class Module
{
public:
- explicit Module(QString path, std::size_t fileSize);
+ Module(QString path, std::size_t fileSize);
// returns the module's path
//
@@ -93,6 +96,30 @@ private:
};
+// represents one process
+//
+class Process
+{
+public:
+ Process(DWORD pid, QString name);
+
+ DWORD pid() const;
+ const QString& name() const;
+
+ // whether this process can be accessed; fails if the current process doesn't
+ // have the proper permissions
+ //
+ bool canAccess() const;
+
+private:
+ DWORD m_pid;
+ QString m_name;
+};
+
+
+std::vector<Process> getRunningProcesses();
std::vector<Module> getLoadedModules();
} // namespace env
+
+#endif // ENV_MODULE_H
diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp
index 376be4df..ffb17c42 100644
--- a/src/envsecurity.cpp
+++ b/src/envsecurity.cpp
@@ -9,6 +9,11 @@
#include <netfw.h>
#pragma comment(lib, "Wbemuuid.lib")
+#include <accctrl.h>
+#include <aclapi.h>
+#include <sddl.h>
+#pragma comment(lib, "advapi32.lib")
+
namespace env
{
@@ -156,6 +161,11 @@ SecurityProduct::SecurityProduct(
{
}
+const QUuid& SecurityProduct::guid() const
+{
+ return m_guid;
+}
+
const QString& SecurityProduct::name() const
{
return m_name;
@@ -180,7 +190,13 @@ QString SecurityProduct::toString() const
{
QString s;
- s += m_name + " (" + providerToString() + ")";
+ if (m_name.isEmpty()) {
+ s += "(no name)";
+ } else {
+ s += m_name;
+ }
+
+ s += " (" + providerToString() + ")";
if (!m_active) {
s += ", inactive";
@@ -190,7 +206,9 @@ QString SecurityProduct::toString() const
s += ", definitions outdated";
}
- if (!m_guid.isNull()) {
+ if (m_guid.isNull()) {
+ s += ", (no guid)";
+ } else {
s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces);
}
@@ -237,91 +255,98 @@ QString SecurityProduct::providerToString() const
}
-std::vector<SecurityProduct> getSecurityProductsFromWMI()
+std::optional<SecurityProduct> handleProduct(IWbemClassObject* o)
{
- // some products may be present in multiple queries, such as a product marked
- // as both antivirus and antispyware, but they'll have the same GUID, so use
- // that to avoid duplicating entries
- std::map<QUuid, SecurityProduct> map;
+ VARIANT prop;
- auto handleProduct = [&](auto* o) {
- VARIANT prop;
- // display name
- auto ret = o->Get(L"displayName", 0, &prop, 0, 0);
- if (FAILED(ret)) {
- log::error("failed to get displayName, {}", formatSystemMessage(ret));
- return;
- }
+ // guid
+ auto ret = o->Get(L"instanceGuid", 0, &prop, 0, 0);
+ if (FAILED(ret)) {
+ log::error("failed to get instanceGuid, {}", formatSystemMessage(ret));
+ return {};
+ }
- if (prop.vt != VT_BSTR) {
- log::error("displayName is a {}, not a bstr", prop.vt);
- return;
- }
+ if (prop.vt != VT_BSTR) {
+ log::error("instanceGuid is a {}, not a bstr", prop.vt);
+ return {};
+ }
- const std::wstring name = prop.bstrVal;
- VariantClear(&prop);
+ const QUuid guid(QString::fromWCharArray(prop.bstrVal));
+ VariantClear(&prop);
- // product state
- ret = o->Get(L"productState", 0, &prop, 0, 0);
- if (FAILED(ret)) {
- log::error("failed to get productState, {}", formatSystemMessage(ret));
- return;
- }
- if (prop.vt != VT_UI4 && prop.vt != VT_I4) {
- log::error("productState is a {}, is not a VT_UI4", prop.vt);
- return;
- }
+ // display name
+ QString displayName;
+ ret = o->Get(L"displayName", 0, &prop, 0, 0);
- DWORD state = 0;
+ if (FAILED(ret)) {
+ log::error("failed to get displayName, {}", formatSystemMessage(ret));
+ } else if (prop.vt != VT_BSTR) {
+ log::error("displayName is a {}, not a bstr", prop.vt);
+ } else {
+ displayName = QString::fromWCharArray(prop.bstrVal);
+ }
+
+ VariantClear(&prop);
+
+
+ // product state
+ DWORD state = 0;
+ ret = o->Get(L"productState", 0, &prop, 0, 0);
+
+ if (FAILED(ret)) {
+ log::error("failed to get productState, {}", formatSystemMessage(ret));
+ } else {
if (prop.vt == VT_I4) {
state = prop.lVal;
- } else {
+ } else if (prop.vt == VT_UI4) {
state = prop.ulVal;
+ } else if (prop.vt == VT_NULL) {
+ log::warn("productState is null");
+ } else {
+ log::error("productState is a {}, not a VT_I4 or a VT_UI4", prop.vt);
}
+ }
- VariantClear(&prop);
+ VariantClear(&prop);
- // guid
- ret = o->Get(L"instanceGuid", 0, &prop, 0, 0);
- if (FAILED(ret)) {
- log::error("failed to get instanceGuid, {}", formatSystemMessage(ret));
- return;
- }
- if (prop.vt != VT_BSTR) {
- log::error("instanceGuid is a {}, is not a bstr", prop.vt);
- return;
- }
+ const auto provider = static_cast<int>((state >> 16) & 0xff);
+ const auto scanner = (state >> 8) & 0xff;
+ const auto definitions = state & 0xff;
- const QUuid guid(QString::fromWCharArray(prop.bstrVal));
- VariantClear(&prop);
+ const bool active = ((scanner & 0x10) != 0);
+ const bool upToDate = (definitions == 0);
- const auto provider = static_cast<int>((state >> 16) & 0xff);
- const auto scanner = (state >> 8) & 0xff;
- const auto definitions = state & 0xff;
+ return SecurityProduct(guid, displayName, provider, active, upToDate);
+}
- const bool active = ((scanner & 0x10) != 0);
- const bool upToDate = (definitions == 0);
+std::vector<SecurityProduct> getSecurityProductsFromWMI()
+{
+ // some products may be present in multiple queries, such as a product marked
+ // as both antivirus and antispyware, but they'll have the same GUID, so use
+ // that to avoid duplicating entries
+ std::map<QUuid, SecurityProduct> map;
- map.insert({
- guid,
- {guid, QString::fromStdWString(name), provider, active, upToDate}});
+ auto f = [&](auto* o) {
+ if (auto p=handleProduct(o)) {
+ map.emplace(p->guid(), std::move(*p));
+ }
};
{
WMI wmi("root\\SecurityCenter2");
- wmi.query("select * from AntivirusProduct", handleProduct);
- wmi.query("select * from FirewallProduct", handleProduct);
- wmi.query("select * from AntiSpywareProduct", handleProduct);
+ wmi.query("select * from AntivirusProduct", f);
+ wmi.query("select * from FirewallProduct", f);
+ wmi.query("select * from AntiSpywareProduct", f);
}
{
WMI wmi("root\\SecurityCenter");
- wmi.query("select * from AntivirusProduct", handleProduct);
- wmi.query("select * from FirewallProduct", handleProduct);
- wmi.query("select * from AntiSpywareProduct", handleProduct);
+ wmi.query("select * from AntivirusProduct", f);
+ wmi.query("select * from FirewallProduct", f);
+ wmi.query("select * from AntiSpywareProduct", f);
}
std::vector<SecurityProduct> v;
@@ -397,4 +422,331 @@ std::vector<SecurityProduct> getSecurityProducts()
return v;
}
+
+class failed
+{
+public:
+ failed(DWORD e, QString what)
+ : m_what(what + ", " + QString::fromStdWString(formatSystemMessage(e)))
+ {
+ }
+
+ QString what() const
+ {
+ return m_what;
+ }
+
+private:
+ QString m_what;
+};
+
+
+MallocPtr<SECURITY_DESCRIPTOR> getSecurityDescriptor(const QString& path)
+{
+ const auto wpath = path.toStdWString();
+ BOOL ret = FALSE;
+
+ DWORD length = 0;
+ ret = ::GetFileSecurityW(
+ wpath.c_str(), DACL_SECURITY_INFORMATION|OWNER_SECURITY_INFORMATION,
+ nullptr, 0, &length);
+
+ if (!ret || length == 0) {
+ const auto e = GetLastError();
+
+ if (e != ERROR_INSUFFICIENT_BUFFER) {
+ if (e == ERROR_ACCESS_DENIED) {
+ // if this fails, the user doesn't even have permissions to get the
+ // security descriptor, which probably means they're not the owner and
+ // their effective access is none
+ throw failed(e, "cannot get security descriptor");
+ } else {
+ // other error
+ throw failed(e, "GetFileSecurity() for length failed");
+ }
+ }
+ }
+
+ MallocPtr<SECURITY_DESCRIPTOR> sd(
+ static_cast<SECURITY_DESCRIPTOR*>(std::malloc(length)));
+
+ std::memset(sd.get(), 0, length);
+
+ ret = ::GetFileSecurityW(
+ wpath.c_str(), DACL_SECURITY_INFORMATION|OWNER_SECURITY_INFORMATION,
+ sd.get(), length, &length);
+
+ if (!ret) {
+ const auto e = GetLastError();
+ throw failed(e, "GetFileSecurity()");
+ }
+
+ return sd;
+}
+
+PACL getDacl(SECURITY_DESCRIPTOR* sd)
+{
+ BOOL present = FALSE;
+ BOOL daclDefaulted = FALSE;
+ PACL acl = nullptr;
+
+ BOOL ret = ::GetSecurityDescriptorDacl(sd, &present, &acl, &daclDefaulted);
+
+ if (!ret) {
+ const auto e = GetLastError();
+ throw failed(e, "GetSecurityDescriptorDacl()");
+ }
+
+ if (!present) {
+ return nullptr;
+ }
+
+ return acl;
+}
+
+PSID getFileOwner(SECURITY_DESCRIPTOR* sd)
+{
+ BOOL ownerDefaulted = FALSE;
+ PSID owner;
+
+ BOOL ret = ::GetSecurityDescriptorOwner(sd, &owner, &ownerDefaulted);
+
+ if (!ret) {
+ const auto e = GetLastError();
+ throw failed(e, "GetSecurityDescriptionOwner()");
+ }
+
+ return owner;
+}
+
+MallocPtr<void> getCurrentUser()
+{
+ HANDLE hnd = ::GetCurrentProcess();
+ HANDLE rawToken = 0;
+
+ BOOL ret = ::OpenProcessToken(hnd, TOKEN_QUERY, &rawToken);
+ if (!ret) {
+ const auto e = GetLastError();
+ throw(e, "OpenProcessToken()");
+ }
+
+ HandlePtr token(rawToken);
+
+ DWORD retsize = 0;
+ ret = ::GetTokenInformation(token.get(), TokenUser, 0, 0, &retsize);
+
+ if (!ret) {
+ const auto e = GetLastError();
+ if (e != ERROR_INSUFFICIENT_BUFFER) {
+ throw failed(e, "GetTokenInformation() for length");
+ }
+ }
+
+ MallocPtr<void> tokenBuffer(std::malloc(retsize));
+ ret = ::GetTokenInformation(
+ token.get(), TokenUser, tokenBuffer.get(), retsize, &retsize);
+
+ if (!ret) {
+ const auto e = GetLastError();
+ throw failed(e, "GetTokenInformation()");
+ }
+
+ PSID tokenSid = ((PTOKEN_USER)(tokenBuffer.get()))->User.Sid;
+ DWORD sidLen = ::GetLengthSid(tokenSid);
+ MallocPtr<void> currentUserSID((SID*)(malloc(sidLen)));
+
+ ret = ::CopySid(sidLen, currentUserSID.get(), tokenSid);
+
+ if (!ret) {
+ const auto e = GetLastError();
+ throw failed(e, "CopySid()");
+ }
+
+ return currentUserSID;
+}
+
+ACCESS_MASK getEffectiveRights(ACL* dacl, PSID sid)
+{
+ TRUSTEEW trustee = {};
+ BuildTrusteeWithSid(&trustee, sid);
+
+ ACCESS_MASK access = 0;
+ DWORD ret = ::GetEffectiveRightsFromAclW(dacl, &trustee, &access);
+
+ if (ret != ERROR_SUCCESS) {
+ throw failed(ret, "GetEffectiveRightsFromAclW()");
+ }
+
+ return access;
+}
+
+QString getUsername(PSID owner)
+{
+ DWORD nameSize=0, domainSize=0;
+ auto use = SidTypeUnknown;
+
+ BOOL ret = LookupAccountSidW(
+ nullptr, owner, nullptr, &nameSize, nullptr, &domainSize, &use);
+
+ if (!ret) {
+ const auto e = GetLastError();
+
+ if (e != ERROR_INSUFFICIENT_BUFFER) {
+ throw failed(e, "LookupAccountSid() for sizes");
+ }
+ }
+
+ auto wsName = std::make_unique<wchar_t[]>(nameSize);
+ auto wsDomain = std::make_unique<wchar_t[]>(domainSize);
+
+ ret = LookupAccountSidW(
+ nullptr, owner, wsName.get(), &nameSize, wsDomain.get(), &domainSize, &use);
+
+ if (!ret) {
+ const auto e = GetLastError();
+ throw failed(e, "LookupAccountSid()");
+ }
+
+ const QString name = QString::fromWCharArray(wsName.get(), nameSize);
+ const QString domain = QString::fromWCharArray(wsDomain.get(), domainSize);
+
+ if (!name.isEmpty() && !domain.isEmpty()) {
+ return domain + "\\" + name;
+ } else {
+ // either or both are empty
+ return name + domain;
+ }
+}
+
+FileRights makeFileRights(ACCESS_MASK m)
+{
+ FileRights fr;
+
+ if (m & FILE_GENERIC_READ) {
+ fr.list.push_back("file_generic_read");
+ } else {
+ if (m & READ_CONTROL) {
+ fr.list.push_back("read_ctrl");
+ }
+
+ if (m & FILE_READ_DATA) {
+ fr.list.push_back("read_data");
+ }
+
+ if (m & FILE_READ_ATTRIBUTES) {
+ fr.list.push_back("read_atts");
+ }
+
+ if (m & FILE_READ_EA) {
+ fr.list.push_back("read_ex_atts");
+ }
+
+ if (m & SYNCHRONIZE) {
+ fr.list.push_back("sync");
+ }
+ }
+
+ if (m & FILE_GENERIC_WRITE) {
+ fr.list.push_back("file_generic_write");
+ } else {
+ // READ_CONTROL handled above
+
+ if (m & FILE_WRITE_DATA) {
+ fr.list.push_back("write_data");
+ }
+
+ if (m & FILE_WRITE_ATTRIBUTES) {
+ fr.list.push_back("write_atts");
+ }
+
+ if (m & FILE_WRITE_EA) {
+ fr.list.push_back("write_ex_atts");
+ }
+
+ if (m & FILE_APPEND_DATA) {
+ fr.list.push_back("append_data");
+ }
+
+ // SYNCHRONIZE handled above
+ }
+
+ if (m & FILE_GENERIC_EXECUTE) {
+ fr.list.push_back("file_generic_execute");
+ fr.hasExecute = true;
+ } else {
+ // READ_CONTROL handled above
+ // FILE_READ_ATTRIBUTES handled above
+
+ if (m & FILE_EXECUTE) {
+ fr.list.push_back("execute");
+ fr.hasExecute = true;
+ }
+
+ // SYNCHRONIZE handled above
+ }
+
+ if (m & DELETE) {
+ fr.list.push_back("delete");
+ }
+
+ if (m & WRITE_DAC) {
+ fr.list.push_back("write_dac");
+ }
+
+ if (m & WRITE_OWNER) {
+ fr.list.push_back("write_owner");
+ }
+
+ if (m & GENERIC_ALL) {
+ fr.list.push_back("generic_all");
+ }
+
+ if (m & GENERIC_WRITE) {
+ fr.list.push_back("generic_write");
+ }
+
+ if (m & GENERIC_READ) {
+ fr.list.push_back("generic_read");
+ }
+
+ // 0x001f01ff
+ const auto normalRights =
+ STANDARD_RIGHTS_ALL |
+ FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE |
+ FILE_DELETE_CHILD;
+
+ if (m == normalRights) {
+ fr.normalRights = true;
+ }
+
+ return fr;
+}
+
+FileSecurity getFileSecurity(const QString& path)
+{
+ FileSecurity fs;
+
+ try
+ {
+ auto sd = getSecurityDescriptor(path);
+ auto dacl = getDacl(sd.get());
+ auto currentUser = getCurrentUser();
+ auto owner = getFileOwner(sd.get());
+ auto access = getEffectiveRights(dacl, currentUser.get());
+
+ fs.rights = makeFileRights(access);
+
+ if (EqualSid(owner, currentUser.get())) {
+ fs.owner = "(this user)";
+ } else {
+ fs.owner = getUsername(owner);
+ }
+ }
+ catch(failed& f)
+ {
+ fs.error = f.what();
+ }
+
+ return fs;
+}
} // namespace
diff --git a/src/envsecurity.h b/src/envsecurity.h
index 200cb531..5f9e5332 100644
--- a/src/envsecurity.h
+++ b/src/envsecurity.h
@@ -1,3 +1,6 @@
+#ifndef ENV_SECURITY_H
+#define ENV_SECURITY_H
+
#include <QUuid>
#include <QString>
@@ -13,6 +16,10 @@ public:
QUuid guid, QString name, int provider,
bool active, bool upToDate);
+ // guid
+ //
+ const QUuid& guid() const;
+
// display name of the product
//
const QString& name() const;
@@ -46,4 +53,23 @@ private:
std::vector<SecurityProduct> getSecurityProducts();
+
+struct FileRights
+{
+ QStringList list;
+ bool hasExecute = false;
+ bool normalRights = false;
+};
+
+struct FileSecurity
+{
+ QString owner;
+ FileRights rights;
+ QString error;
+};
+
+FileSecurity getFileSecurity(const QString& file);
+
} // namespace env
+
+#endif // ENV_SECURITY_H
diff --git a/src/envshortcut.h b/src/envshortcut.h
index 82eea191..a05528d9 100644
--- a/src/envshortcut.h
+++ b/src/envshortcut.h
@@ -1,3 +1,6 @@
+#ifndef ENV_SHORTCUT_H
+#define ENV_SHORTCUT_H
+
#include <QString>
class Executable;
@@ -103,3 +106,5 @@ private:
QString toString(Shortcut::Locations loc);
} // namespace
+
+#endif // ENV_SHORTCUT_H
diff --git a/src/envwindows.h b/src/envwindows.h
index c23f99f4..90655e49 100644
--- a/src/envwindows.h
+++ b/src/envwindows.h
@@ -1,3 +1,6 @@
+#ifndef ENV_WINDOWS_H
+#define ENV_WINDOWS_H
+
#include <QString>
#include <optional>
@@ -104,3 +107,5 @@ private:
};
} // namespace
+
+#endif // ENV_WINDOWS_H
diff --git a/src/expanderwidget.cpp b/src/expanderwidget.cpp
deleted file mode 100644
index a9d045a5..00000000
--- a/src/expanderwidget.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-#include "expanderwidget.h"
-
-ExpanderWidget::ExpanderWidget()
- : m_button(nullptr), m_content(nullptr), opened_(false)
-{
-}
-
-ExpanderWidget::ExpanderWidget(QToolButton* button, QWidget* content)
- : ExpanderWidget()
-{
- set(button, content);
-}
-
-void ExpanderWidget::set(QToolButton* button, QWidget* content, bool o)
-{
- m_button = button;
- m_content = content;
-
- m_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
- QObject::connect(m_button, &QToolButton::clicked, [&]{ toggle(); });
-
- toggle(o);
-}
-
-void ExpanderWidget::toggle()
-{
- if (opened()) {
- toggle(false);
- }
- else {
- toggle(true);
- }
-}
-
-void ExpanderWidget::toggle(bool b)
-{
- if (b) {
- m_button->setArrowType(Qt::DownArrow);
- m_content->show();
- } else {
- m_button->setArrowType(Qt::RightArrow);
- m_content->hide();
- }
-
- // the state has to be remembered instead of using m_content's visibility
- // because saving the state in saveConflictExpandersState() happens after the
- // dialog is closed, which marks all the widgets hidden
- opened_ = b;
-}
-
-bool ExpanderWidget::opened() const
-{
- return opened_;
-}
-
-QByteArray ExpanderWidget::saveState() const
-{
- QByteArray result;
- QDataStream stream(&result, QIODevice::WriteOnly);
-
- stream << opened();
-
- return result;
-}
-
-void ExpanderWidget::restoreState(const QByteArray& a)
-{
- QDataStream stream(a);
-
- bool opened = false;
- stream >> opened;
-
- if (stream.status() == QDataStream::Ok) {
- toggle(opened);
- }
-}
-
-QToolButton* ExpanderWidget::button() const
-{
- return m_button;
-}
diff --git a/src/expanderwidget.h b/src/expanderwidget.h
deleted file mode 100644
index 99b2d303..00000000
--- a/src/expanderwidget.h
+++ /dev/null
@@ -1,51 +0,0 @@
-#ifndef EXPANDERWIDGET_H
-#define EXPANDERWIDGET_H
-
-#include <QToolButton>
-
-/* Takes a QToolButton and a widget and creates an expandable widget.
-**/
-class ExpanderWidget
-{
-public:
- /** empty expander, use set()
- **/
- ExpanderWidget();
-
- /** see set()
- **/
- ExpanderWidget(QToolButton* button, QWidget* content);
-
- /** @brief sets the button and content widgets to use
- * the button will be given an arrow icon, clicking it will toggle the
- * visibility of the given widget
- * @param button the button that toggles the content
- * @param content the widget that will be shown or hidden
- * @param opened initial state, defaults to closed
- **/
- void set(QToolButton* button, QWidget* content, bool opened=false);
-
- /** either opens or closes the expander depending on the current state
- **/
- void toggle();
-
- /** sets the current state of the expander
- **/
- void toggle(bool b);
-
- /** returns whether the expander is currently opened
- **/
- bool opened() const;
-
- QByteArray saveState() const;
- void restoreState(const QByteArray& a);
-
- QToolButton* button() const;
-
-private:
- QToolButton* m_button;
- QWidget* m_content;
- bool opened_;
-};
-
-#endif // EXPANDERWIDGET_H
diff --git a/src/helper.cpp b/src/helper.cpp
deleted file mode 100644
index 59a2d3d1..00000000
--- a/src/helper.cpp
+++ /dev/null
@@ -1,125 +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 "helper.h"
-#include "utility.h"
-#include <report.h>
-#include <LMCons.h>
-
-#define WIN32_LEAN_AND_MEAN
-#include <Windows.h>
-
-#include <QDir>
-#include <QApplication>
-
-using MOBase::reportError;
-
-
-namespace Helper {
-
-
-static bool helperExec(LPCWSTR moDirectory, LPCWSTR commandLine, BOOL async)
-{
- wchar_t fileName[MAX_PATH];
- _snwprintf(fileName, MAX_PATH, L"%ls\\helper.exe", moDirectory);
-
- SHELLEXECUTEINFOW execInfo = {0};
-
- execInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
- execInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
- execInfo.hwnd = nullptr;
- execInfo.lpVerb = L"runas";
- execInfo.lpFile = fileName;
- execInfo.lpParameters = commandLine;
- execInfo.lpDirectory = moDirectory;
- execInfo.nShow = SW_SHOW;
-
- ::ShellExecuteExW(&execInfo);
-
- if (execInfo.hProcess == 0) {
- reportError(QObject::tr("helper failed"));
- return false;
- }
-
- if (async) {
- return true;
- }
-
- if (::WaitForSingleObject(execInfo.hProcess, INFINITE) != WAIT_OBJECT_0) {
- reportError(QObject::tr("helper failed"));
- return false;
- }
-
- DWORD exitCode;
- GetExitCodeProcess(execInfo.hProcess, &exitCode);
- return exitCode == NOERROR;
-}
-
-
-bool init(const std::wstring &moPath, const std::wstring &dataPath)
-{
- DWORD userNameLen = UNLEN + 1;
- wchar_t userName[UNLEN + 1];
-
- if (!GetUserName(userName, &userNameLen)) {
- reportError(QObject::tr("failed to determine account name"));
- return false;
- }
- wchar_t *commandLine = new wchar_t[32768];
-
- _snwprintf(commandLine, 32768, L"init \"%ls\" \"%ls\"",
- dataPath.c_str(), userName);
-
- bool res = helperExec(moPath.c_str(), commandLine, FALSE);
- delete [] commandLine;
-
- return res;
-}
-
-
-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(moPath.c_str(), commandLine, FALSE);
- delete [] commandLine;
-
- return res;
-}
-
-
-bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir)
-{
- wchar_t *commandLine = new wchar_t[32768];
- _snwprintf(commandLine, 32768, L"adminLaunch %d \"%ls\" \"%ls\"",
- ::GetCurrentProcessId(),
- moFile.c_str(),
- workingDir.c_str()
- );
-
- bool res = helperExec(moPath.c_str(), commandLine, TRUE);
- delete [] commandLine;
-
- return res;
-}
-
-
-} // namespace
diff --git a/src/helper.h b/src/helper.h
deleted file mode 100644
index f6667a84..00000000
--- a/src/helper.h
+++ /dev/null
@@ -1,64 +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/>.
-*/
-
-#ifndef HELPER_H
-#define HELPER_H
-
-
-#include <string>
-
-
-/**
- * @brief Convenience functions to work with the external helper program.
- *
- * The mo_helper program is used to make changes on the system that require administrative
- * rights, so that ModOrganizer itself can run without special privileges
- **/
-namespace Helper {
-
-/**
- * @brief initialise the specified directory for use with mod organizer.
- *
- * This will create all required sub-directories and give the user running ModOrganizer
- * write-access
- *
- * @param moPath absolute path to the ModOrganizer base directory
- * @return true on success
- **/
-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 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 &moPath, const std::wstring &dataPath);
-
-/**
- * @brief waits for the current process to exit and restarts it as an administrator
- * @param moPath absolute path to the modOrganizer base directory
- * @param moFile file name of modOrganizer
- * @param workingDir current working directory
- **/
-bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir);
-
-}
-
-
-#endif // HELPER_H
diff --git a/src/main.cpp b/src/main.cpp
index b5568fec..ba988ae3 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -38,7 +38,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "executableslist.h"
#include "singleinstance.h"
#include "utility.h"
-#include "helper.h"
#include "loglist.h"
#include "selectiondialog.h"
#include "moapplication.h"
@@ -577,8 +576,7 @@ int runApplication(MOApplication &application, SingleInstance &instance,
OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType());
env::Environment env;
-
- env.dump();
+ env.dump(settings);
settings.dump();
sanityChecks(env);
@@ -649,7 +647,10 @@ int runApplication(MOApplication &application, SingleInstance &instance,
game->setGameVariant(edition);
- log::info("managing game at {}", game->gameDirectory().absolutePath());
+ log::info(
+ "using game plugin '{}' ('{}', steam id '{}') at {}",
+ game->gameName(), game->gameShortName(), game->steamAPPId(),
+ game->gameDirectory().absolutePath());
organizer.updateExecutablesList();
@@ -825,7 +826,12 @@ void initLogging()
{
LogModel::create();
- log::createDefault(MOBase::log::Debug, "%^[%m-%d %H:%M:%S.%e %L] %v%$");
+ log::LoggerConfiguration conf;
+ conf.maxLevel = MOBase::log::Debug;
+ conf.pattern = "%^[%Y-%m-%d %H:%M:%S.%e %L] %v%$";
+ conf.utc = true;
+
+ log::createDefault(conf);
log::getDefault().setCallback(
[](log::Entry e){ LogModel::instance().add(e); });
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index bd9f1486..42cbe919 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -2565,17 +2565,6 @@ void MainWindow::modInstalled(const QString &modName)
modUpdateCheck(IDs);
}
-void MainWindow::procError(QProcess::ProcessError error)
-{
- reportError(tr("failed to spawn notepad.exe: %1").arg(error));
- this->sender()->deleteLater();
-}
-
-void MainWindow::procFinished(int, QProcess::ExitStatus)
-{
- this->sender()->deleteLater();
-}
-
void MainWindow::showMessage(const QString &message)
{
MessageDialog::showMessage(message, this);
@@ -6449,11 +6438,13 @@ void MainWindow::on_bossButton_clicked()
return;
}
- HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"),
- parameters.join(" "),
- qApp->applicationDirPath() + "/loot",
- true,
- stdOutWrite);
+ spawn::SpawnParameters sp;
+ sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe");
+ sp.arguments = parameters.join(" ");
+ sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot");
+ sp.stdOut = stdOutWrite;
+
+ HANDLE loot = spawn::startBinary(this, sp);
// we don't use the write end
::CloseHandle(stdOutWrite);
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 6f06b9d5..1f997ab1 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -489,9 +489,6 @@ private slots:
void doMoveOverwriteContentToMod(const QString &modAbsolutePath);
void clearOverwrite();
- void procError(QProcess::ProcessError error);
- void procFinished(int exitCode, QProcess::ExitStatus exitStatus);
-
// nexus related
void checkModsForUpdates();
diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h
index a77c2ac9..ad305dfc 100644
--- a/src/modinfodialogconflicts.h
+++ b/src/modinfodialogconflicts.h
@@ -33,7 +33,7 @@ signals:
private:
struct Expanders
{
- ExpanderWidget overwrite, overwritten, nonconflict;
+ MOBase::ExpanderWidget overwrite, overwritten, nonconflict;
};
ConflictsTab* m_tab;
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 91e16716..0da5b604 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -33,7 +33,6 @@
#include "lockeddialog.h"
#include "instancemanager.h"
#include <scriptextender.h>
-#include "helper.h"
#include "previewdialog.h"
#include <QApplication>
@@ -135,34 +134,6 @@ static DWORD getProcessParentID(DWORD pid)
return res;
}
-static void startSteam(QWidget *widget)
-{
- 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.
- QStringList args;
- QString username;
- QString password;
- if (Settings::instance().steam().login(username, password)) {
- args << "-login";
- args << username;
- if (password != "") {
- args << password;
- }
- }
- 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."));
- }
- }
-}
-
template <typename InputIterator>
QStringList toStringList(InputIterator current, InputIterator end)
{
@@ -173,86 +144,6 @@ QStringList toStringList(InputIterator current, InputIterator end)
return result;
}
-bool checkService()
-{
- SC_HANDLE serviceManagerHandle = NULL;
- SC_HANDLE serviceHandle = NULL;
- LPSERVICE_STATUS_PROCESS serviceStatus = NULL;
- LPQUERY_SERVICE_CONFIG serviceConfig = NULL;
- bool serviceRunning = true;
-
- DWORD bytesNeeded;
-
- try {
- serviceManagerHandle = OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);
- if (!serviceManagerHandle) {
- log::warn("failed to open service manager (query status) (error {})", GetLastError());
- throw 1;
- }
-
- serviceHandle = OpenService(serviceManagerHandle, L"EventLog", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);
- if (!serviceHandle) {
- log::warn("failed to open EventLog service (query status) (error {})", GetLastError());
- throw 2;
- }
-
- if (QueryServiceConfig(serviceHandle, NULL, 0, &bytesNeeded)
- || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
- log::warn("failed to get size of service config (error {})", GetLastError());
- throw 3;
- }
-
- DWORD serviceConfigSize = bytesNeeded;
- serviceConfig = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, serviceConfigSize);
- if (!QueryServiceConfig(serviceHandle, serviceConfig, serviceConfigSize, &bytesNeeded)) {
- log::warn("failed to query service config (error {})", GetLastError());
- throw 4;
- }
-
- if (serviceConfig->dwStartType == SERVICE_DISABLED) {
- log::error("Windows Event Log service is disabled!");
- serviceRunning = false;
- }
-
- if (QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, NULL, 0, &bytesNeeded)
- || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
- log::warn("failed to get size of service status (error {})", GetLastError());
- throw 5;
- }
-
- DWORD serviceStatusSize = bytesNeeded;
- serviceStatus = (LPSERVICE_STATUS_PROCESS)LocalAlloc(LMEM_FIXED, serviceStatusSize);
- if (!QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, (LPBYTE)serviceStatus, serviceStatusSize, &bytesNeeded)) {
- log::warn("failed to query service status (error {})", GetLastError());
- throw 6;
- }
-
- if (serviceStatus->dwCurrentState != SERVICE_RUNNING) {
- log::error("Windows Event Log service is not running");
- serviceRunning = false;
- }
- }
- catch (int e) {
- UNUSED_VAR(e);
- serviceRunning = false;
- }
-
- if (serviceStatus) {
- LocalFree(serviceStatus);
- }
- if (serviceConfig) {
- LocalFree(serviceConfig);
- }
- if (serviceHandle) {
- CloseServiceHandle(serviceHandle);
- }
- if (serviceManagerHandle) {
- CloseServiceHandle(serviceManagerHandle);
- }
-
- return serviceRunning;
-}
-
OrganizerCore::OrganizerCore(Settings &settings)
: m_UserInterface(nullptr)
@@ -361,67 +252,6 @@ void OrganizerCore::storeSettings()
}
}
-bool OrganizerCore::testForSteam(bool *found, bool *access)
-{
- HANDLE hProcessSnap;
- HANDLE hProcess;
- PROCESSENTRY32 pe32;
- DWORD lastError;
-
- if (found == nullptr || access == nullptr) {
- return false;
- }
-
- // Take a snapshot of all processes in the system.
- hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
- if (hProcessSnap == INVALID_HANDLE_VALUE) {
- lastError = GetLastError();
- log::error("unable to get snapshot of processes (error {})", lastError);
- return false;
- }
-
- // Retrieve information about the first process,
- // and exit if unsuccessful
- pe32.dwSize = sizeof(PROCESSENTRY32);
- if (!Process32First(hProcessSnap, &pe32)) {
- lastError = GetLastError();
- log::error("unable to get first process (error {})", lastError);
- CloseHandle(hProcessSnap);
- return false;
- }
-
- *found = false;
- *access = true;
-
- // Now walk the snapshot of processes, and
- // display information about each process in turn
- do {
- if ((_tcsicmp(pe32.szExeFile, L"Steam.exe") == 0) ||
- (_tcsicmp(pe32.szExeFile, L"SteamService.exe") == 0)) {
-
- *found = true;
-
- // Try to open the process to determine if MO has the proper access
- hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
- FALSE, pe32.th32ProcessID);
- if (hProcess == NULL) {
- lastError = GetLastError();
- if (lastError == ERROR_ACCESS_DENIED) {
- *access = false;
- }
- } else {
- CloseHandle(hProcess);
- }
- break;
- }
-
-} while(Process32Next(hProcessSnap, &pe32));
-
-CloseHandle(hProcessSnap);
-return true;
-
-}
-
void OrganizerCore::updateExecutablesList()
{
if (m_PluginContainer == nullptr) {
@@ -1451,95 +1281,25 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
const QString &customOverwrite,
const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries)
{
- prepareStart();
+ spawn::SpawnParameters sp;
+ sp.binary = binary;
+ sp.arguments = arguments;
+ sp.currentDirectory = currentDirectory;
+ sp.hooked = true;
- if (!binary.exists()) {
- reportError(
- tr("Executable not found: %1").arg(qUtf8Printable(binary.absoluteFilePath())));
- return INVALID_HANDLE_VALUE;
- }
-
- if (!steamAppID.isEmpty()) {
- ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str());
- } else {
- ::SetEnvironmentVariableW(L"SteamAPPId",
- ToWString(m_Settings.steam().appID()).c_str());
- }
+ prepareStart();
QWidget *window = qApp->activeWindow();
if ((window != nullptr) && (!window->isVisible())) {
window = nullptr;
}
- // 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.game().loadMechanismType() == LoadMechanism::LOAD_MODORGANIZER)) {
-
- bool steamFound = true;
- bool steamAccess = true;
- if (!testForSteam(&steamFound, &steamAccess)) {
- log::error("unable to determine state of Steam");
- }
-
- if (!steamFound) {
- QDialogButtonBox::StandardButton result;
- result = QuestionBoxMemory::query(window, "steamQuery", binary.fileName(),
- 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::Cancel);
- if (result == QDialogButtonBox::Yes) {
- startSteam(window);
-
- // double-check that Steam is started and MO has access
- steamFound = true;
- steamAccess = true;
- if (!testForSteam(&steamFound, &steamAccess)) {
- log::error("unable to determine state of Steam");
- } else if (!steamFound) {
- log::error("could not find Steam");
- }
-
- } else if (result == QDialogButtonBox::Cancel) {
- return INVALID_HANDLE_VALUE;
- }
- }
+ if (!spawn::checkBinary(window, sp)) {
+ return INVALID_HANDLE_VALUE;
+ }
- if (!steamAccess) {
- QDialogButtonBox::StandardButton result;
- result = QuestionBoxMemory::query(window, "steamAdminQuery", binary.fileName(),
- tr("Steam: Access Denied"),
- tr("MO was denied access to the Steam process. This normally indicates that "
- "Steam is being run as administrator while MO is not. This can cause issues "
- "launching the game. It is recommended to not run Steam as administrator unless "
- "absolutely necessary.\n\n"
- "Restart MO as administrator?"),
- QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel);
- if (result == QDialogButtonBox::Yes) {
- WCHAR cwd[MAX_PATH];
- if (!GetCurrentDirectory(MAX_PATH, cwd)) {
- log::error("unable to get current directory (error {})", GetLastError());
- cwd[0] = L'\0';
- }
- if (!Helper::adminLaunch(
- qApp->applicationDirPath().toStdWString(),
- qApp->applicationFilePath().toStdWString(),
- std::wstring(cwd))) {
- log::error("unable to relaunch MO as admin");
- return INVALID_HANDLE_VALUE;
- }
- qApp->exit(0);
- return INVALID_HANDLE_VALUE;
- } else if (result == QDialogButtonBox::Cancel) {
- return INVALID_HANDLE_VALUE;
- }
- }
+ if (!spawn::checkSteam(window, sp, managedGame()->gameDirectory(), steamAppID, m_Settings)) {
+ return INVALID_HANDLE_VALUE;
}
while (m_DirectoryUpdate) {
@@ -1566,32 +1326,12 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
return INVALID_HANDLE_VALUE;
}
- // Check if the Windows Event Logging service is running. For some reason, this seems to be
- // critical to the successful running of usvfs.
- if (!checkService()) {
- if (QuestionBoxMemory::query(window, QString("eventLogService"), binary.fileName(),
- tr("Windows Event Log Error"),
- tr("The Windows Event Log service is disabled and/or not running. This prevents"
- " USVFS from running properly. Your mods may not be working in the executable"
- " that you are launching. Note that you may have to restart MO and/or your PC"
- " after the service is fixed.\n\nContinue launching %1?").arg(binary.fileName()),
- QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) {
- return INVALID_HANDLE_VALUE;
- }
+ if (!spawn::checkEnvironment(window, sp)) {
+ return INVALID_HANDLE_VALUE;
}
- for (auto exec : settings().executablesBlacklist().split(";")) {
- if (exec.compare(binary.fileName(), Qt::CaseInsensitive) == 0) {
- if (QuestionBoxMemory::query(window, QString("blacklistedExecutable"), binary.fileName(),
- tr("Blacklisted Executable"),
- tr("The executable you are attempted to launch is blacklisted in the virtual file"
- " system. This will likely prevent the executable, and any executables that are"
- " launched by this one, from seeing any mods. This could extend to INI files, save"
- " games and any other virtualized files.\n\nContinue launching %1?").arg(binary.fileName()),
- QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) {
- return INVALID_HANDLE_VALUE;
- }
- }
+ if (!spawn::checkBlacklist(window, sp, m_Settings)) {
+ return INVALID_HANDLE_VALUE;
}
QString modsPath = settings().paths().mods();
@@ -1626,13 +1366,14 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
.arg(QDir::toNativeSeparators(cwdPath),
QDir::toNativeSeparators(binPath), arguments);
- log::debug("Spawning proxyed process <{}>", cmdline);
+ sp.binary = QFileInfo(QCoreApplication::applicationFilePath());
+ sp.arguments = cmdline;
+ sp.currentDirectory.setPath(QCoreApplication::applicationDirPath());
- return startBinary(QFileInfo(QCoreApplication::applicationFilePath()),
- cmdline, QCoreApplication::applicationDirPath(), true);
+ return spawn::startBinary(window, sp);
} else {
log::debug("Spawning direct process <{}, {}, {}>", binPath, arguments, cwdPath);
- return startBinary(binary, arguments, currentDirectory, true);
+ return spawn::startBinary(window, sp);
}
} else {
log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath());
diff --git a/src/pch.h b/src/pch.h
index af1a4ade..01a97357 100644
--- a/src/pch.h
+++ b/src/pch.h
@@ -255,3 +255,4 @@
#include <QWhatsThisClickedEvent>
#include <QWidget>
#include <QWidgetAction>
+#include <QStorageInfo>
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp
index 33423225..c6c61da3 100644
--- a/src/pluginlist.cpp
+++ b/src/pluginlist.cpp
@@ -437,7 +437,10 @@ void PluginList::readLockedOrderFrom(const QString &fileName)
}
file.open(QIODevice::ReadOnly);
+
+ int lineNumber = 0;
while (!file.atEnd()) {
+ ++lineNumber;
QByteArray line = file.readLine();
if ((line.size() > 0) && (line.at(0) != '#')) {
QList<QByteArray> fields = line.split('|');
@@ -463,6 +466,7 @@ void PluginList::readLockedOrderFrom(const QString &fileName)
}
}
} else {
+ log::error("locked order file: invalid line #{} '{}'", lineNumber, QString::fromUtf8(line));
reportError(tr("The file containing locked plugin indices is broken"));
break;
}
diff --git a/src/settings.cpp b/src/settings.cpp
index 1f066100..7cea52fb 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -222,6 +222,17 @@ QString Settings::executablesBlacklist() const
return get<QString>(m_Settings, "Settings", "executable_blacklist", def);
}
+bool Settings::isExecutableBlacklisted(const QString& s) const
+{
+ for (auto exec : executablesBlacklist().split(";")) {
+ if (exec.compare(s, Qt::CaseInsensitive) == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
void Settings::setExecutablesBlacklist(const QString& s)
{
set(m_Settings, "Settings", "executable_blacklist", s);
@@ -935,7 +946,7 @@ QuestionBoxMemory::Button WidgetSettings::questionButton(
if (!filename.isEmpty()) {
const auto fileSetting = windowName + "/" + filename;
- if (auto v=getOptional<int>(m_Settings, sectionName, filename)) {
+ if (auto v=getOptional<int>(m_Settings, sectionName, fileSetting)) {
return static_cast<QuestionBoxMemory::Button>(*v);
}
}
@@ -1219,6 +1230,7 @@ void PluginSettings::setPersistent(
m_Settings.sync();
}
}
+
void PluginSettings::addBlacklist(const QString &fileName)
{
m_PluginBlacklist.insert(fileName);
diff --git a/src/settings.h b/src/settings.h
index 91b87e29..cd478a5b 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -32,13 +32,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
namespace MOBase {
class IPlugin;
class IPluginGame;
+ class ExpanderWidget;
}
class QSplitter;
class ServerList;
class Settings;
-class ExpanderWidget;
// helper class that calls restoreGeometry() in the constructor and
@@ -153,8 +153,8 @@ public:
void saveState(const QSplitter* splitter);
bool restoreState(QSplitter* splitter) const;
- void saveState(const ExpanderWidget* expander);
- bool restoreState(ExpanderWidget* expander) const;
+ void saveState(const MOBase::ExpanderWidget* expander);
+ bool restoreState(MOBase::ExpanderWidget* expander) const;
void saveVisibility(const QWidget* w);
bool restoreVisibility(QWidget* w, std::optional<bool> def={}) const;
@@ -678,6 +678,7 @@ public:
// by MO but given to usvfs when starting an executable
//
QString executablesBlacklist() const;
+ bool isExecutableBlacklisted(const QString& s) const;
void setExecutablesBlacklist(const QString& s);
// ? looks obsolete, only used by dead code
diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp
index 1d3d4a39..8fb25b1c 100644
--- a/src/settingsdialog.cpp
+++ b/src/settingsdialog.cpp
@@ -33,8 +33,8 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings& setti
: TutorableDialog("SettingsDialog", parent)
, ui(new Ui::SettingsDialog)
, m_settings(settings)
- , m_PluginContainer(pluginContainer)
- , m_keyChanged(false)
+ , m_pluginContainer(pluginContainer)
+ , m_restartNeeded(false)
{
ui->setupUi(this);
@@ -47,6 +47,25 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings& setti
m_tabs.push_back(std::unique_ptr<SettingsTab>(new WorkaroundsSettingsTab(settings, *this)));
}
+PluginContainer* SettingsDialog::pluginContainer()
+{
+ return m_pluginContainer;
+}
+
+QWidget* SettingsDialog::parentWidgetForDialogs()
+{
+ if (isVisible()) {
+ return this;
+ } else {
+ return parentWidget();
+ }
+}
+
+void SettingsDialog::setRestartNeeded()
+{
+ m_restartNeeded = true;
+}
+
int SettingsDialog::exec()
{
GeometrySaver gs(m_settings, this);
@@ -68,13 +87,8 @@ int SettingsDialog::exec()
}
}
- bool restartNeeded = false;
- if (getApiKeyChanged()) {
- restartNeeded = true;
- }
-
- if (restartNeeded) {
- if (QMessageBox::question(nullptr,
+ if (m_restartNeeded) {
+ if (QMessageBox::question(parentWidgetForDialogs(),
tr("Restart Mod Organizer?"),
tr("In order to finish configuration changes, MO must be restarted.\n"
"Restart it now?"),
@@ -111,7 +125,7 @@ void SettingsDialog::accept()
QDir::fromNativeSeparators(
Settings::instance().paths().mods(true))) &&
(QMessageBox::question(
- nullptr, tr("Confirm"),
+ parentWidgetForDialogs(), 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. "
@@ -124,11 +138,6 @@ void SettingsDialog::accept()
TutorableDialog::accept();
}
-bool SettingsDialog::getApiKeyChanged()
-{
- return m_keyChanged;
-}
-
SettingsTab::SettingsTab(Settings& s, SettingsDialog& d)
: ui(d.ui), m_settings(s), m_dialog(d)
@@ -146,3 +155,8 @@ SettingsDialog& SettingsTab::dialog()
{
return m_dialog;
}
+
+QWidget* SettingsTab::parentWidget()
+{
+ return m_dialog.parentWidgetForDialogs();
+}
diff --git a/src/settingsdialog.h b/src/settingsdialog.h
index 6a99cb8d..e89da665 100644
--- a/src/settingsdialog.h
+++ b/src/settingsdialog.h
@@ -42,6 +42,7 @@ protected:
Settings& settings();
SettingsDialog& dialog();
+ QWidget* parentWidget();
private:
Settings& m_settings;
@@ -56,11 +57,12 @@ private:
**/
class SettingsDialog : public MOBase::TutorableDialog
{
- Q_OBJECT
+ Q_OBJECT;
+ friend class SettingsTab;
public:
explicit SettingsDialog(
- PluginContainer *pluginContainer, Settings& settings, QWidget *parent = 0);
+ PluginContainer* pluginContainer, Settings& settings, QWidget* parent = 0);
~SettingsDialog();
@@ -70,23 +72,21 @@ public:
*/
QString getColoredButtonStyleSheet() const;
- // temp
- Ui::SettingsDialog *ui;
- bool m_keyChanged;
- PluginContainer *m_PluginContainer;
+ PluginContainer* pluginContainer();
+ QWidget* parentWidgetForDialogs();
+ void setRestartNeeded();
int exec() override;
public slots:
virtual void accept();
-public:
- bool getApiKeyChanged();
-
private:
Settings& m_settings;
std::vector<std::unique_ptr<SettingsTab>> m_tabs;
-
+ Ui::SettingsDialog* ui;
+ bool m_restartNeeded;
+ PluginContainer* m_pluginContainer;
};
#endif // SETTINGSDIALOG_H
diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui
index 1a3726fb..40079441 100644
--- a/src/settingsdialog.ui
+++ b/src/settingsdialog.ui
@@ -191,7 +191,7 @@ p, li { white-space: pre-wrap; }
<string>This will make all dialogs show up again where you checked the &quot;Remember selection&quot;-box.</string>
</property>
<property name="text">
- <string>Reset Dialogs</string>
+ <string>Reset Dialog Choices</string>
</property>
</widget>
</item>
diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp
index 3f7ece38..8ecdcbb9 100644
--- a/src/settingsdialoggeneral.cpp
+++ b/src/settingsdialoggeneral.cpp
@@ -238,8 +238,11 @@ void GeneralSettingsTab::on_resetColorsBtn_clicked()
void GeneralSettingsTab::on_resetDialogsButton_clicked()
{
- if (QMessageBox::question(&dialog(), QObject::tr("Confirm?"),
- QObject::tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"),
+ if (QMessageBox::question(
+ parentWidget(), QObject::tr("Confirm?"),
+ QObject::tr(
+ "This will reset all the choices you made to dialogs and make them all "
+ "visible again. Continue?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
resetDialogs();
}
diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp
index 0b08f13f..826075c0 100644
--- a/src/settingsdialognexus.cpp
+++ b/src/settingsdialognexus.cpp
@@ -226,7 +226,7 @@ void NexusSettingsTab::on_nexusDisconnect_clicked()
void NexusSettingsTab::on_clearCacheButton_clicked()
{
QDir(Settings::instance().paths().cache()).removeRecursively();
- NexusInterface::instance(dialog().m_PluginContainer)->clearCache();
+ NexusInterface::instance(dialog().pluginContainer())->clearCache();
}
void NexusSettingsTab::on_associateButton_clicked()
@@ -238,7 +238,7 @@ void NexusSettingsTab::validateKey(const QString& key)
{
if (!m_nexusValidator) {
m_nexusValidator.reset(new NexusKeyValidator(
- *NexusInterface::instance(dialog().m_PluginContainer)->getAccessManager()));
+ *NexusInterface::instance(dialog().pluginContainer())->getAccessManager()));
m_nexusValidator->stateChanged = [&](auto&& s, auto&& e){
onValidatorStateChanged(s, e);
@@ -294,7 +294,7 @@ void NexusSettingsTab::onValidatorStateChanged(
void NexusSettingsTab::onValidatorFinished(const APIUserAccount& user)
{
- NexusInterface::instance(dialog().m_PluginContainer)->setUserAccount(user);
+ NexusInterface::instance(dialog().pluginContainer())->setUserAccount(user);
if (!user.apiKey().isEmpty()) {
if (setKey(user.apiKey())) {
@@ -311,7 +311,7 @@ void NexusSettingsTab::addNexusLog(const QString& s)
bool NexusSettingsTab::setKey(const QString& key)
{
- dialog().m_keyChanged = true;
+ dialog().setRestartNeeded();
const bool ret = settings().nexus().setApiKey(key);
updateNexusState();
return ret;
@@ -319,10 +319,10 @@ bool NexusSettingsTab::setKey(const QString& key)
bool NexusSettingsTab::clearKey()
{
- dialog().m_keyChanged = true;
+ dialog().setRestartNeeded();
const auto ret = settings().nexus().clearApiKey();
- NexusInterface::instance(dialog().m_PluginContainer)->getAccessManager()->clearApiKey();
+ NexusInterface::instance(dialog().pluginContainer())->getAccessManager()->clearApiKey();
updateNexusState();
return ret;
@@ -371,7 +371,7 @@ void NexusSettingsTab::updateNexusButtons()
void NexusSettingsTab::updateNexusData()
{
- const auto user = NexusInterface::instance(dialog().m_PluginContainer)
+ const auto user = NexusInterface::instance(dialog().pluginContainer())
->getAPIUserAccount();
if (user.isValid()) {
diff --git a/src/settingsdialogpaths.cpp b/src/settingsdialogpaths.cpp
index aeb4dd5d..c6fd40a7 100644
--- a/src/settingsdialogpaths.cpp
+++ b/src/settingsdialogpaths.cpp
@@ -68,10 +68,12 @@ void PathsSettingsTab::update()
if (!QDir(realPath).exists()) {
if (!QDir().mkpath(realPath)) {
- QMessageBox::warning(qApp->activeWindow(), QObject::tr("Error"),
+ QMessageBox::warning(parentWidget(), QObject::tr("Error"),
QObject::tr("Failed to create \"%1\", you may not have the "
- "necessary permission. path remains unchanged.")
+ "necessary permissions. Path remains unchanged.")
.arg(realPath));
+
+ continue;
}
}
diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp
index 4d811e40..0e31fc4b 100644
--- a/src/settingsdialogworkarounds.cpp
+++ b/src/settingsdialogworkarounds.cpp
@@ -1,6 +1,7 @@
#include "settingsdialogworkarounds.h"
#include "ui_settingsdialog.h"
-#include "helper.h"
+#include "spawn.h"
+#include "settings.h"
#include <iplugingame.h>
WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d)
@@ -26,7 +27,7 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d)
ui->lockGUIBox->setChecked(settings().interface().lockGUI());
ui->enableArchiveParsingBox->setChecked(settings().archiveParsing());
- setExecutableBlacklist(settings().executablesBlacklist());
+ m_ExecutableBlacklist = settings().executablesBlacklist();
QObject::connect(ui->bsaDateBtn, &QPushButton::clicked, [&]{ on_bsaDateBtn_clicked(); });
QObject::connect(ui->execBlacklistBtn, &QPushButton::clicked, [&]{ on_execBlacklistBtn_clicked(); });
@@ -49,14 +50,29 @@ void WorkaroundsSettingsTab::update()
settings().interface().setDisplayForeign(ui->displayForeignBox->isChecked());
settings().interface().setLockGUI(ui->lockGUIBox->isChecked());
settings().setArchiveParsing(ui->enableArchiveParsingBox->isChecked());
- settings().setExecutablesBlacklist(getExecutableBlacklist());
+ settings().setExecutablesBlacklist(m_ExecutableBlacklist);
}
-void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked()
+bool WorkaroundsSettingsTab::changeBlacklistNow(
+ QWidget* parent, Settings& settings)
+{
+ const auto current = settings.executablesBlacklist();
+
+ if (auto s=changeBlacklistLater(parent, current)) {
+ settings.setExecutablesBlacklist(*s);
+ return true;
+ }
+
+ return false;
+}
+
+std::optional<QString> WorkaroundsSettingsTab::changeBlacklistLater(
+ QWidget* parent, const QString& current)
{
bool ok = false;
+
QString result = QInputDialog::getMultiLineText(
- &dialog(),
+ parent,
QObject::tr("Executables Blacklist"),
QObject::tr("Enter one executable per line to be blacklisted from the virtual file system.\n"
"Mods and other virtualized files will not be visible to these executables and\n"
@@ -64,17 +80,28 @@ void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked()
"Example:\n"
" Chrome.exe\n"
" Firefox.exe"),
- m_ExecutableBlacklist.split(";").join("\n"),
+ current.split(";").join("\n"),
&ok
);
- if (ok) {
- QStringList blacklist;
- for (auto exec : result.split("\n")) {
- if (exec.trimmed().endsWith(".exe", Qt::CaseInsensitive)) {
- blacklist << exec.trimmed();
- }
+
+ if (!ok) {
+ return {};
+ }
+
+ QStringList blacklist;
+ for (auto exec : result.split("\n")) {
+ if (exec.trimmed().endsWith(".exe", Qt::CaseInsensitive)) {
+ blacklist << exec.trimmed();
}
- m_ExecutableBlacklist = blacklist.join(";");
+ }
+
+ return blacklist.join(";");
+}
+
+void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked()
+{
+ if (auto s=changeBlacklistLater(parentWidget(), m_ExecutableBlacklist)) {
+ m_ExecutableBlacklist = *s;
}
}
@@ -83,7 +110,9 @@ void WorkaroundsSettingsTab::on_bsaDateBtn_clicked()
const auto* game = qApp->property("managed_game").value<MOBase::IPluginGame*>();
QDir dir = game->dataDirectory();
- Helper::backdateBSAs(qApp->applicationDirPath().toStdWString(),
+ helper::backdateBSAs(
+ parentWidget(),
+ qApp->applicationDirPath().toStdWString(),
dir.absolutePath().toStdWString());
}
@@ -95,7 +124,7 @@ void WorkaroundsSettingsTab::on_resetGeometryBtn_clicked()
"Restart now?");
const auto res = QMessageBox::question(
- nullptr, caption, text, QMessageBox::Yes | QMessageBox::Cancel);
+ parentWidget(), caption, text, QMessageBox::Yes | QMessageBox::Cancel);
if (res == QMessageBox::Yes) {
settings().geometry().requestReset();
diff --git a/src/settingsdialogworkarounds.h b/src/settingsdialogworkarounds.h
index d5d6815f..cffc54a0 100644
--- a/src/settingsdialogworkarounds.h
+++ b/src/settingsdialogworkarounds.h
@@ -8,6 +8,18 @@ class WorkaroundsSettingsTab : public SettingsTab
{
public:
WorkaroundsSettingsTab(Settings& settings, SettingsDialog& dialog);
+
+ // shows the blacklist dialog from the given settings, and changes the
+ // settings when the user accepts it
+ //
+ static bool changeBlacklistNow(QWidget* parent, Settings& settings);
+
+ // shows the blacklist dialog from the given string and returns the new
+ // blacklist if the user accepted it
+ //
+ static std::optional<QString> changeBlacklistLater(
+ QWidget* parent, const QString& current);
+
void update();
private:
@@ -16,9 +28,6 @@ private:
void on_bsaDateBtn_clicked();
void on_execBlacklistBtn_clicked();
void on_resetGeometryBtn_clicked();
-
- QString getExecutableBlacklist() { return m_ExecutableBlacklist; }
- void setExecutableBlacklist(QString blacklist) { m_ExecutableBlacklist = blacklist; }
};
#endif // SETTINGSDIALOGWORKAROUNDS_H
diff --git a/src/settingsutilities.cpp b/src/settingsutilities.cpp
index 7a9dcc35..db7c1818 100644
--- a/src/settingsutilities.cpp
+++ b/src/settingsutilities.cpp
@@ -213,55 +213,119 @@ void warnIfNotCheckable(const QAbstractButton* b)
}
-bool setWindowsCredential(const QString key, const QString data)
+QString credentialName(const QString& key)
{
- QString finalKey("ModOrganizer2_" + key);
- wchar_t* keyData = new wchar_t[finalKey.size()+1];
- finalKey.toWCharArray(keyData);
- keyData[finalKey.size()] = L'\0';
- bool result = false;
- if (data.isEmpty()) {
- result = CredDeleteW(keyData, CRED_TYPE_GENERIC, 0);
- if (!result)
- if (GetLastError() == ERROR_NOT_FOUND)
- result = true;
- } else {
- wchar_t* charData = new wchar_t[data.size()];
- data.toWCharArray(charData);
+ return "ModOrganizer2_" + key;
+}
+
+bool deleteWindowsCredential(const QString& key)
+{
+ const auto credName = credentialName(key);
+
+ if (!CredDeleteW(credName.toStdWString().c_str(), CRED_TYPE_GENERIC, 0)) {
+ const auto e = GetLastError();
- CREDENTIALW cred = {};
- cred.Flags = 0;
- cred.Type = CRED_TYPE_GENERIC;
- cred.TargetName = keyData;
- cred.CredentialBlob = (LPBYTE)charData;
- cred.CredentialBlobSize = sizeof(wchar_t) * data.size();
- cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
+ // not an error if the key already doesn't exist, and don't log it because
+ // it happens all the time when the settings dialog is closed since it
+ // doesn't check first
+ if (e == ERROR_NOT_FOUND) {
+ return true;
+ }
- result = CredWriteW(&cred, 0);
- delete[] charData;
+ log::error(
+ "failed to delete windows credential {}, {}",
+ credName, formatSystemMessage(e));
+ return false;
}
- delete[] keyData;
- return result;
+
+ log::debug("deleted windows credential {}", credName);
+
+ return true;
}
-QString getWindowsCredential(const QString key)
+bool addWindowsCredential(const QString& key, const QString& data)
{
- QString result;
- QString finalKey("ModOrganizer2_" + key);
- wchar_t* keyData = new wchar_t[finalKey.size()+1];
- finalKey.toWCharArray(keyData);
- keyData[finalKey.size()] = L'\0';
- PCREDENTIALW creds;
- if (CredReadW(keyData, 1, 0, &creds)) {
- wchar_t *charData = (wchar_t *)creds->CredentialBlob;
- result = QString::fromWCharArray(charData, creds->CredentialBlobSize / sizeof(wchar_t));
- CredFree(creds);
- } else {
+ const auto credName = credentialName(key);
+
+ const auto wname = credName.toStdWString();
+ const auto wdata = data.toStdWString();
+
+ const auto* blob = reinterpret_cast<const BYTE*>(wdata.data());
+ const auto blobSize = wdata.size() * sizeof(decltype(wdata)::value_type);
+
+ CREDENTIALW cred = {};
+ cred.Flags = 0;
+ cred.Type = CRED_TYPE_GENERIC;
+ cred.TargetName = const_cast<wchar_t*>(wname.c_str());
+ cred.CredentialBlob = const_cast<BYTE*>(blob);
+ cred.CredentialBlobSize = static_cast<DWORD>(blobSize);
+ cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
+
+ if (!CredWriteW(&cred, 0)) {
+ const auto e = GetLastError();
+
+ log::error(
+ "failed to delete windows credential {}, {}",
+ credName, formatSystemMessage(e));
+
+ return false;
+ }
+
+ log::debug("set windows credential {}", credName);
+
+ return true;
+}
+
+struct CredentialFreer
+{
+ void operator()(CREDENTIALW* c)
+ {
+ if (c) {
+ CredFree(c);
+ }
+ }
+};
+
+using CredentialPtr = std::unique_ptr<CREDENTIALW, CredentialFreer>;
+
+QString getWindowsCredential(const QString& key)
+{
+ const QString credName = credentialName(key);
+
+ CREDENTIALW* rawCreds = nullptr;
+
+ const auto ret = CredReadW(
+ credName.toStdWString().c_str(), CRED_TYPE_GENERIC, 0, &rawCreds);
+
+ CredentialPtr creds(rawCreds);
+
+ if (!ret) {
const auto e = GetLastError();
+
if (e != ERROR_NOT_FOUND) {
- log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e));
+ log::error(
+ "failed to retrieve windows credential {}: {}",
+ credName, formatSystemMessage(e));
}
+
+ return {};
+ }
+
+ QString value;
+ if (creds->CredentialBlob) {
+ value = QString::fromWCharArray(
+ reinterpret_cast<const wchar_t*>(creds->CredentialBlob),
+ creds->CredentialBlobSize / sizeof(wchar_t));
+ }
+
+ return value;
+}
+
+bool setWindowsCredential(const QString& key, const QString& data)
+{
+ if (data.isEmpty()) {
+ return deleteWindowsCredential(key);
+ } else {
+ return addWindowsCredential(key, data);
}
- delete[] keyData;
- return result;
}
diff --git a/src/settingsutilities.h b/src/settingsutilities.h
index d99abb06..a6737144 100644
--- a/src/settingsutilities.h
+++ b/src/settingsutilities.h
@@ -3,7 +3,9 @@
#include <log.h>
-class ExpanderWidget;
+namespace MOBase {
+ class ExpanderWidget;
+}
template <class T, class=void>
struct ValueConverter
@@ -241,7 +243,7 @@ private:
QString widgetNameWithTopLevel(const QWidget* widget);
QString widgetName(const QMainWindow* w);
QString widgetName(const QHeaderView* w);
-QString widgetName(const ExpanderWidget* w);
+QString widgetName(const MOBase::ExpanderWidget* w);
QString widgetName(const QWidget* w);
template <class Widget>
@@ -268,7 +270,7 @@ QString checkedSettingName(const QAbstractButton* b);
void warnIfNotCheckable(const QAbstractButton* b);
-bool setWindowsCredential(const QString key, const QString data);
-QString getWindowsCredential(const QString key);
+bool setWindowsCredential(const QString& key, const QString& data);
+QString getWindowsCredential(const QString& key);
#endif // SETTINGSUTILITIES_H
diff --git a/src/spawn.cpp b/src/spawn.cpp
index f77da35f..079677f4 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -21,164 +21,878 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "report.h"
#include "utility.h"
+#include "env.h"
+#include "envwindows.h"
+#include "envsecurity.h"
+#include "envmodule.h"
+#include "settings.h"
+#include "settingsdialogworkarounds.h"
+#include <errorcodes.h>
#include <report.h>
+#include <log.h>
#include <usvfs.h>
#include <Shellapi.h>
#include <appconfig.h>
#include <windows_error.h>
-#include "helper.h"
-
#include <QApplication>
#include <QMessageBox>
#include <QtDebug>
-
-
#include <Shellapi.h>
-
-#include <boost/scoped_array.hpp>
+#include <fmt/format.h>
using namespace MOBase;
using namespace MOShared;
+namespace spawn::dialogs
+{
+
+std::wstring makeRightsDetails(const env::FileSecurity& fs)
+{
+ if (fs.rights.normalRights) {
+ return L"(normal rights)";
+ }
+
+ if (fs.rights.list.isEmpty()) {
+ return L"(none)";
+ }
+
+ std::wstring s = fs.rights.list.join("|").toStdWString();
+ if (!fs.rights.hasExecute) {
+ s += L" (execute is missing)";
+ }
+
+ return s;
+}
+
+QString makeDetails(const SpawnParameters& sp, DWORD code, const QString& more={})
+{
+ std::wstring owner, rights;
+
+ if (sp.binary.isFile()) {
+ const auto fs = env::getFileSecurity(sp.binary.absoluteFilePath());
+
+ if (fs.error.isEmpty()) {
+ owner = fs.owner.toStdWString();
+ rights = makeRightsDetails(fs);
+ } else {
+ owner = fs.error.toStdWString();
+ rights = fs.error.toStdWString();
+ }
+ } else {
+ owner = L"(file not found)";
+ rights = L"(file not found)";
+ }
+
+ const bool cwdExists = (sp.currentDirectory.isEmpty() ?
+ true : sp.currentDirectory.exists());
+
+ const auto appDir = QCoreApplication::applicationDirPath();
+ const auto sep = QDir::separator();
+
+ const std::wstring usvfs_x86_dll =
+ QFileInfo(appDir + sep + "usvfs_x86.dll").isFile() ? L"ok" : L"not found";
+
+ const std::wstring usvfs_x64_dll =
+ QFileInfo(appDir + sep + "usvfs_x64.dll").isFile() ? L"ok" : L"not found";
+
+ const std::wstring usvfs_x86_proxy =
+ QFileInfo(appDir + sep + "usvfs_proxy_x86.exe").isFile() ? L"ok" : L"not found";
+
+ const std::wstring usvfs_x64_proxy =
+ QFileInfo(appDir + sep + "usvfs_proxy_x64.exe").isFile() ? L"ok" : L"not found";
+
+ std::wstring elevated;
+ if (auto b=env::Environment().windowsInfo().isElevated()) {
+ elevated = (*b ? L"yes" : L"no");
+ } else {
+ elevated = L"(not available)";
+ }
+
+ std::wstring f =
+ L"Error {code} {codename}{more}: {error}\n"
+ L" . binary: '{bin}'\n"
+ L" . owner: {owner}\n"
+ L" . rights: {rights}\n"
+ L" . arguments: '{args}'\n"
+ L" . cwd: '{cwd}'{cwdexists}\n"
+ L" . stdout: {stdout}, stderr: {stderr}, hooked: {hooked}\n"
+ L" . MO elevated: {elevated}";
+
+ if (sp.hooked) {
+ f += L"\n . usvfs x86:{x86_dll} x64:{x64_dll} proxy_x86:{x86_proxy} proxy_x64:{x64_proxy}";
+ }
+
+ const std::wstring wmore = (more.isEmpty() ? L"" : (", " + more).toStdWString());
+
+ const auto s = fmt::format(f,
+ fmt::arg(L"code", code),
+ fmt::arg(L"codename", errorCodeName(code)),
+ fmt::arg(L"more", wmore),
+ fmt::arg(L"bin", QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString()),
+ fmt::arg(L"owner", owner),
+ fmt::arg(L"rights", rights),
+ fmt::arg(L"error", formatSystemMessage(code)),
+ fmt::arg(L"args", sp.arguments.toStdWString()),
+ fmt::arg(L"cwd", QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString()),
+ fmt::arg(L"cwdexists", (cwdExists ? L"" : L" (not found)")),
+ fmt::arg(L"stdout", (sp.stdOut == INVALID_HANDLE_VALUE ? L"no" : L"yes")),
+ fmt::arg(L"stderr", (sp.stdErr == INVALID_HANDLE_VALUE ? L"no" : L"yes")),
+ fmt::arg(L"hooked", (sp.hooked ? L"yes" : L"no")),
+ fmt::arg(L"x86_dll", usvfs_x86_dll),
+ fmt::arg(L"x64_dll", usvfs_x64_dll),
+ fmt::arg(L"x86_proxy", usvfs_x86_proxy),
+ fmt::arg(L"x64_proxy", usvfs_x64_proxy),
+ fmt::arg(L"elevated", elevated));
+
+ return QString::fromStdWString(s);
+}
+
+QString makeContent(const SpawnParameters& sp, DWORD code)
+{
+ if (code == ERROR_INVALID_PARAMETER) {
+ return QObject::tr(
+ "This error typically happens because an antivirus has deleted critical "
+ "files from Mod Organizer's installation folder or has made them "
+ "generally inaccessible. Add an exclusion for Mod Organizer's "
+ "installation folder in your antivirus, reinstall Mod Organizer and try "
+ "again.");
+ } else if (code == ERROR_ACCESS_DENIED) {
+ return QObject::tr(
+ "This error typically happens because an antivirus is preventing Mod "
+ "Organizer from starting programs. Add an exclusion for Mod Organizer's "
+ "installation folder in your antivirus and try again.");
+ } else if (code == ERROR_FILE_NOT_FOUND) {
+ return QObject::tr("The file '%1' does not exist.")
+ .arg(QDir::toNativeSeparators(sp.binary.absoluteFilePath()));
+ } else {
+ return QString::fromStdWString(formatSystemMessage(code));
+ }
+}
+
+QMessageBox::StandardButton badSteamReg(
+ QWidget* parent, const QString& keyName, const QString& valueName)
+{
+ const auto details = QString(
+ "can't start steam, registry value at '%1' is empty or doesn't exist")
+ .arg(keyName + "\\" + valueName);
+
+ log::error("{}", details);
+
+ return MOBase::TaskDialog(parent, QObject::tr("Cannot start Steam"))
+ .main(QObject::tr("Cannot start Steam"))
+ .content(QObject::tr(
+ "The path to the Steam executable cannot be found. You might try "
+ "reinstalling Steam."))
+ .details(details)
+ .icon(QMessageBox::Critical)
+ .button({
+ QObject::tr("Continue without starting Steam"),
+ QObject::tr("The program may fail to launch."),
+ QMessageBox::Yes})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .exec();
+}
+
+QMessageBox::StandardButton startSteamFailed(
+ QWidget* parent,
+ const QString& keyName, const QString& valueName, const QString& exe,
+ const SpawnParameters& sp, DWORD e)
+{
+ auto details = QString(
+ "a steam install was found in the registry at '%1': '%2'\n\n")
+ .arg(keyName + "\\" + valueName)
+ .arg(exe);
+
+ details += makeDetails(sp, e);
+
+ log::error("{}", details);
+
+ return MOBase::TaskDialog(parent, QObject::tr("Cannot start Steam"))
+ .main(QObject::tr("Cannot start Steam"))
+ .content(makeContent(sp, e))
+ .details(details)
+ .icon(QMessageBox::Critical)
+ .button({
+ QObject::tr("Continue without starting Steam"),
+ QObject::tr("The program may fail to launch."),
+ QMessageBox::Yes})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .exec();
+}
+
+void spawnFailed(QWidget* parent, const SpawnParameters& sp, DWORD code)
+{
+ const auto details = makeDetails(sp, code);
+ log::error("{}", details);
+
+ const auto title = QObject::tr("Cannot launch program");
+
+ const auto mainText = QObject::tr("Cannot start %1")
+ .arg(sp.binary.fileName());
+
+ MOBase::TaskDialog(parent, title)
+ .main(mainText)
+ .content(makeContent(sp, code))
+ .details(details)
+ .icon(QMessageBox::Critical)
+ .exec();
+}
+
+void helperFailed(
+ QWidget* parent, DWORD code, const QString& why, const std::wstring& binary,
+ const std::wstring& cwd, const std::wstring& args)
+{
+ SpawnParameters sp;
+ sp.binary = QString::fromStdWString(binary);
+ sp.currentDirectory.setPath(QString::fromStdWString(cwd));
+ sp.arguments = QString::fromStdWString(args);
+
+ const auto details = makeDetails(sp, code, "in " + why);
+ log::error("{}", details);
+
+ const auto title = QObject::tr("Cannot launch helper");
+
+ const auto mainText = QObject::tr("Cannot start %1")
+ .arg(sp.binary.fileName());
+
+ MOBase::TaskDialog(parent, title)
+ .main(mainText)
+ .content(makeContent(sp, code))
+ .details(details)
+ .icon(QMessageBox::Critical)
+ .exec();
+}
+
+bool confirmRestartAsAdmin(QWidget* parent, const SpawnParameters& sp)
+{
+ const auto details = makeDetails(sp, ERROR_ELEVATION_REQUIRED);
+
+ log::error("{}", details);
+
+ const auto title = QObject::tr("Elevation required");
+
+ const auto mainText = QObject::tr("Cannot start %1")
+ .arg(sp.binary.fileName());
+
+ const auto content = QObject::tr(
+ "This program is requesting to run as administrator but Mod Organizer "
+ "itself is not running as administrator. Running programs as administrator "
+ "is typically unnecessary as long as the game and Mod Organizer have been "
+ "installed outside \"Program Files\".\r\n\r\n"
+ "You can restart Mod Organizer as administrator and try launching the "
+ "program again.");
+
+ log::debug("asking user to restart MO as administrator");
+
+ const auto r = MOBase::TaskDialog(parent, title)
+ .main(mainText)
+ .content(content)
+ .details(details)
+ .icon(QMessageBox::Question)
+ .button({
+ QObject::tr("Restart Mod Organizer as administrator"),
+ QObject::tr("You must allow \"helper.exe\" to make changes to the system."),
+ QMessageBox::Yes})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .exec();
+
+ return (r == QMessageBox::Yes);
+}
+
+QMessageBox::StandardButton confirmStartSteam(
+ QWidget* parent, const SpawnParameters& sp, const QString& details)
+{
+ const auto title = QObject::tr("Launch Steam");
+ const auto mainText = QObject::tr("This program requires Steam");
+ const auto content = QObject::tr(
+ "Mod Organizer has detected that this program likely requires Steam to be "
+ "running to function properly.");
+
+ return MOBase::TaskDialog(parent, title)
+ .main(mainText)
+ .content(content)
+ .details(details)
+ .icon(QMessageBox::Question)
+ .button({
+ QObject::tr("Start Steam"),
+ QMessageBox::Yes})
+ .button({
+ QObject::tr("Continue without starting Steam"),
+ QObject::tr("The program might fail to run."),
+ QMessageBox::No})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .remember("steamQuery", sp.binary.fileName())
+ .exec();
+}
+
+QMessageBox::StandardButton confirmRestartAsAdminForSteam(
+ QWidget* parent, const SpawnParameters& sp)
+{
+ const auto title = QObject::tr("Elevation required");
+ const auto mainText = QObject::tr("Steam is running as administrator");
+ const auto content = QObject::tr(
+ "Running Steam as administrator is typically unnecessary and can cause "
+ "problems when Mod Organizer itself is not running as administrator."
+ "\r\n\r\n"
+ "You can restart Mod Organizer as administrator and try launching the "
+ "program again.");
+
+ return MOBase::TaskDialog(parent, title)
+ .main(mainText)
+ .content(content)
+ .icon(QMessageBox::Question)
+ .button({
+ QObject::tr("Restart Mod Organizer as administrator"),
+ QObject::tr("You must allow \"helper.exe\" to make changes to the system."),
+ QMessageBox::Yes})
+ .button({
+ QObject::tr("Continue"),
+ QObject::tr("The program might fail to run."),
+ QMessageBox::No})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .remember("steamAdminQuery", sp.binary.fileName())
+ .exec();
+}
-static const int BUFSIZE = 4096;
+bool eventLogNotRunning(
+ QWidget* parent, const env::Service& s, const SpawnParameters& sp)
+{
+ const auto title = QObject::tr("Event Log not running");
+ const auto mainText = QObject::tr("The Event Log service is not running");
+ const auto content = QObject::tr(
+ "The Windows Event Log service is not running. This can prevent USVFS from "
+ "running properly and your mods may not be recognized by the program being "
+ "launched.");
+
+ const auto r = MOBase::TaskDialog(parent, title)
+ .main(mainText)
+ .content(content)
+ .details(s.toString())
+ .icon(QMessageBox::Question)
+ .remember("eventLogService", sp.binary.fileName())
+ .button({
+ QObject::tr("Continue"),
+ QObject::tr("Your mods might not work."),
+ QMessageBox::Yes})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .exec();
+
+ return (r == QMessageBox::Yes);
+}
+
+QMessageBox::StandardButton confirmBlacklisted(
+ QWidget* parent, const SpawnParameters& sp, Settings& settings)
+{
+ const auto title = QObject::tr("Blacklisted program");
+ const auto mainText = QObject::tr("The program %1 is blacklisted")
+ .arg(sp.binary.fileName());
+ const auto content = QObject::tr(
+ "The program you are attempting to launch is blacklisted in the virtual "
+ "filesystem. This will likely prevent it from seeing any mods, INI files "
+ "or any other virtualized files.");
+
+ const auto details =
+ "Executable: " + sp.binary.fileName() + "\n"
+ "Current blacklist: " + settings.executablesBlacklist();
+
+ auto r = MOBase::TaskDialog(parent, title)
+ .main(mainText)
+ .content(content)
+ .details(details)
+ .icon(QMessageBox::Question)
+ .remember("blacklistedExecutable", sp.binary.fileName())
+ .button({
+ QObject::tr("Continue"),
+ QObject::tr("Your mods might not work."),
+ QMessageBox::Yes})
+ .button({
+ QObject::tr("Change the blacklist"),
+ QMessageBox::Retry})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .exec();
+
+ if (r == QMessageBox::Retry) {
+ if (!WorkaroundsSettingsTab::changeBlacklistNow(parent, settings)) {
+ r = QMessageBox::Cancel;
+ }
+ }
+
+ return r;
+}
+
+} // namespace
+
+
+namespace spawn
+{
-static bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory,
- bool suspended, bool hooked,
- HANDLE stdOut, HANDLE stdErr,
- HANDLE& processHandle, HANDLE& threadHandle)
+DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHandle)
{
BOOL inheritHandles = FALSE;
- STARTUPINFO si;
- ::ZeroMemory(&si, sizeof(si));
- if (stdOut != INVALID_HANDLE_VALUE) {
- si.hStdOutput = stdOut;
+
+ STARTUPINFO si = {};
+ si.cb = sizeof(si);
+
+ // inherit handles if we plan to use stdout or stderr reroute
+ if (sp.stdOut != INVALID_HANDLE_VALUE) {
+ si.hStdOutput = sp.stdOut;
inheritHandles = TRUE;
si.dwFlags |= STARTF_USESTDHANDLES;
}
- if (stdErr != INVALID_HANDLE_VALUE) {
- si.hStdError = stdErr;
+
+ if (sp.stdErr != INVALID_HANDLE_VALUE) {
+ si.hStdError = sp.stdErr;
inheritHandles = TRUE;
si.dwFlags |= STARTF_USESTDHANDLES;
}
- si.cb = sizeof(si);
- size_t length = wcslen(binary) + wcslen(arguments) + 4;
- wchar_t *commandLine = nullptr;
- if (arguments[0] != L'\0') {
- commandLine = new wchar_t[length];
- _snwprintf(commandLine, length, L"\"%ls\" %ls", binary, arguments);
- } else {
- commandLine = new wchar_t[length];
- _snwprintf_s(commandLine, length, _TRUNCATE, L"\"%ls\"", binary);
+
+ const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString();
+ const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString();
+
+ std::wstring commandLine = L"\"" + bin + L"\"";
+ if (sp.arguments[0] != L'\0') {
+ commandLine += L" " + sp.arguments.toStdWString();
}
QString moPath = QCoreApplication::applicationDirPath();
+ const auto oldPath = env::addPath(QDir::toNativeSeparators(moPath));
+
+ PROCESS_INFORMATION pi;
+ BOOL success = FALSE;
+
+ if (sp.hooked) {
+ success = ::CreateProcessHooked(
+ nullptr, const_cast<wchar_t*>(commandLine.c_str()), nullptr, nullptr,
+ inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr,
+ cwd.c_str(), &si, &pi);
+ } else {
+ success = ::CreateProcess(
+ nullptr, const_cast<wchar_t*>(commandLine.c_str()), nullptr, nullptr,
+ inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr,
+ cwd.c_str(), &si, &pi);
+ }
+
+ const auto e = GetLastError();
+ env::setPath(oldPath);
+
+ if (!success) {
+ return e;
+ }
- 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);
+ processHandle = pi.hProcess;
+ threadHandle = pi.hThread;
+
+ return ERROR_SUCCESS;
+}
+
+bool restartAsAdmin(QWidget* parent)
+{
+ WCHAR cwd[MAX_PATH] = {};
+ if (!GetCurrentDirectory(MAX_PATH, cwd)) {
+ cwd[0] = L'\0';
}
+ if (!helper::adminLaunch(
+ parent,
+ qApp->applicationDirPath().toStdWString(),
+ qApp->applicationFilePath().toStdWString(),
+ std::wstring(cwd)))
{
- boost::scoped_array<TCHAR> newPath(new TCHAR[offset + moPath.length() + 2]);
- _tcsncpy(newPath.get(), oldPath.get(), offset);
- newPath.get()[offset] = '\0';
- _tcsncat(newPath.get(), TEXT(";"), 1);
- _tcsncat(newPath.get(), ToWString(QDir::toNativeSeparators(moPath)).c_str(), moPath.length());
+ log::error("admin launch failed");
+ return false;
+ }
+
+ log::debug("exiting MO");
+ qApp->exit(0);
- ::SetEnvironmentVariable(TEXT("PATH"), newPath.get());
+ return true;
+}
+
+void startBinaryAdmin(QWidget* parent, const SpawnParameters& sp)
+{
+ if (!dialogs::confirmRestartAsAdmin(parent, sp)) {
+ log::debug("user declined");
+ return;
}
- PROCESS_INFORMATION pi;
- 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
- );
+ log::info("restarting MO as administrator");
+ restartAsAdmin(parent);
+}
+
+bool checkBinary(QWidget* parent, const SpawnParameters& sp)
+{
+ if (!sp.binary.exists()) {
+ dialogs::spawnFailed(parent, sp, ERROR_FILE_NOT_FOUND);
+ return false;
+ }
+
+ return true;
+}
+
+struct SteamStatus
+{
+ bool running=false;
+ bool accessible=false;
+};
+
+SteamStatus getSteamStatus()
+{
+ SteamStatus ss;
+
+ const auto ps = env::Environment().runningProcesses();
+
+ for (const auto& p : ps) {
+ if ((p.name().compare("Steam.exe", Qt::CaseInsensitive) == 0) ||
+ (p.name().compare("SteamService.exe", Qt::CaseInsensitive) == 0))
+ {
+ ss.running = true;
+ ss.accessible = p.canAccess();
+
+ log::debug(
+ "'{}' is running, accessible={}",
+ p.name(), (ss.accessible ? "yes" : "no"));
+
+ break;
+ }
+ }
+
+ return ss;
+}
+
+QString makeSteamArguments(const QString& username, const QString& password)
+{
+ QString args;
+
+ if (username != "") {
+ args += "-login " + username;
+
+ if (password != "") {
+ args += " " + password;
+ }
+ }
+
+ return args;
+}
+
+bool startSteam(QWidget* parent)
+{
+ const QString keyName = "HKEY_CURRENT_USER\\Software\\Valve\\Steam";
+ const QString valueName = "SteamExe";
+
+ const QSettings steamSettings(keyName, QSettings::NativeFormat);
+ const QString exe = steamSettings.value(valueName, "").toString();
+
+ if (exe.isEmpty()) {
+ return (dialogs::badSteamReg(parent, keyName, valueName) == QMessageBox::Yes);
+ }
+
+ SpawnParameters sp;
+ sp.binary = exe;
+
+ // See if username and password supplied. If so, pass them into steam.
+ QString username, password;
+ if (Settings::instance().steam().login(username, password)) {
+ sp.arguments = makeSteamArguments(username, password);
+ }
+
+ log::debug(
+ "starting steam process:\n"
+ " . program: '{}'\n"
+ " . username={}, password={}",
+ sp.binary.filePath().toStdString(),
+ (username.isEmpty() ? "no" : "yes"),
+ (password.isEmpty() ? "no" : "yes"));
+
+ HANDLE ph = INVALID_HANDLE_VALUE;
+ HANDLE th = INVALID_HANDLE_VALUE;
+ const auto e = spawn(sp, ph, th);
+
+ if (e != ERROR_SUCCESS) {
+ // make sure username and passwords are not shown
+ sp.arguments = makeSteamArguments(
+ (username.isEmpty() ? "" : "USERNAME"),
+ (password.isEmpty() ? "" : "PASSWORD"));
+
+ const auto r = dialogs::startSteamFailed(
+ parent, keyName, valueName, exe, sp, e);
+
+ return (r == QMessageBox::Yes);
+ }
+
+ QMessageBox::information(
+ parent, QObject::tr("Waiting"),
+ QObject::tr("Please press OK once you're logged into steam."));
+
+ return true;
+}
+
+bool checkSteam(
+ QWidget* parent, const SpawnParameters& sp,
+ const QDir& gameDirectory, const QString &steamAppID, const Settings& settings)
+{
+ static const std::vector<QString> steamFiles = {
+ "steam_api.dll", "steam_api64.dll"
+ };
+
+ log::debug("checking steam");
+
+ if (!steamAppID.isEmpty()) {
+ env::set("SteamAPPId", steamAppID);
} 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
- );
+ env::set("SteamAPPId", settings.steam().appID());
}
- ::SetEnvironmentVariable(TEXT("PATH"), oldPath.get());
- delete [] commandLine;
+ bool steamRequired = false;
+ QString details;
- if (!success) {
- throw windows_error("failed to start process");
+ for (const auto& file : steamFiles) {
+ const QFileInfo fi(gameDirectory.absoluteFilePath(file));
+ if (fi.exists()) {
+ details = QString(
+ "managed game is located at '%1' and file '%2' exists")
+ .arg(gameDirectory.absolutePath())
+ .arg(fi.absoluteFilePath());
+
+ log::debug("{}", details);
+ steamRequired = true;
+
+ break;
+ }
+ }
+
+ if (!steamRequired) {
+ log::debug("program doesn't seem to require steam");
+ return true;
+ }
+
+
+ auto ss = getSteamStatus();
+
+ if (!ss.running) {
+ log::debug("steam isn't running, asking to start steam");
+
+ const auto c = dialogs::confirmStartSteam(parent, sp, details);
+
+ if (c == QDialogButtonBox::Yes) {
+ log::debug("user wants to start steam");
+
+ if (!startSteam(parent)) {
+ // cancel
+ return false;
+ }
+
+ // double-check that Steam is started
+ ss = getSteamStatus();
+ if (!ss.running) {
+ log::error("steam is still not running, hoping for the best");
+ return true;
+ }
+ } else if (c == QDialogButtonBox::No) {
+ log::debug("user declined to start steam");
+ return true;
+ } else {
+ log::debug("user cancelled");
+ return false;
+ }
+ }
+
+ if (ss.running && !ss.accessible) {
+ log::debug("steam is running but is not accessible, asking to restart MO");
+ const auto c = dialogs::confirmRestartAsAdminForSteam(parent, sp);
+
+ if (c == QDialogButtonBox::Yes) {
+ restartAsAdmin(parent);
+ return false;
+ } else if (c == QDialogButtonBox::No) {
+ log::debug("user declined to restart MO, continuing");
+ return true;
+ } else {
+ log::debug("user cancelled");
+ return false;
+ }
}
- processHandle = pi.hProcess;
- threadHandle = pi.hThread;
return true;
}
+bool checkEnvironment(QWidget* parent, const SpawnParameters& sp)
+{
+ // check if the Windows Event Logging service is running; for some reason,
+ // this seems to be critical to the successful running of usvfs.
+ const auto serviceName = "EventLog";
+
+ const auto s = env::getService(serviceName);
+
+ if (!s.isValid()) {
+ log::error(
+ "cannot determine the status of the {} service, continuing",
+ serviceName);
+
+ return true;
+ }
+
+ if (s.status() == env::Service::Status::Running) {
+ log::debug("{}", s.toString());
+ return true;
+ }
+
+ log::error("{}", s.toString());
+ return dialogs::eventLogNotRunning(parent, s, sp);
+}
+
+bool checkBlacklist(
+ QWidget* parent, const SpawnParameters& sp, Settings& settings)
+{
+ for (;;) {
+ if (!settings.isExecutableBlacklisted(sp.binary.fileName())) {
+ return true;
+ }
+
+ const auto r = dialogs::confirmBlacklisted(parent, sp, settings);
+
+ if (r != QMessageBox::Retry) {
+ return (r == QMessageBox::Yes);
+ }
+ }
+}
+
-HANDLE startBinary(const QFileInfo &binary,
- const QString &arguments,
- const QDir &currentDirectory,
- bool hooked,
- HANDLE stdOut,
- HANDLE stdErr)
+HANDLE startBinary(QWidget* parent, const SpawnParameters& sp)
{
HANDLE processHandle, threadHandle;
- std::wstring binaryName = ToWString(QDir::toNativeSeparators(binary.absoluteFilePath()));
- std::wstring currentDirectoryName = ToWString(QDir::toNativeSeparators(currentDirectory.absolutePath()));
+ const auto e = spawn(sp, processHandle, threadHandle);
- try {
- 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;
+ switch (e)
+ {
+ case ERROR_SUCCESS:
+ {
+ ::CloseHandle(threadHandle);
+ return processHandle;
}
- } catch (const windows_error &e) {
- if (e.getErrorCode() == ERROR_ELEVATION_REQUIRED) {
- if (QMessageBox::question(QApplication::activeModalWidget(), QObject::tr("Elevation required"),
- QObject::tr("This process requires elevation to run.\n"
- "This is a potential security risk so I highly advise you to investigate if\n"
- "\"%1\"\n"
- "can be installed to work without elevation.\n\n"
- "Restart Mod Organizer as an elevated process?\n"
- "You will be asked if you want to allow helper.exe to make changes to the system. "
- "You will need to relaunch the process above manually.").arg(
- QDir::toNativeSeparators(binary.absoluteFilePath())),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- WCHAR cwd[MAX_PATH];
- if (!GetCurrentDirectory(MAX_PATH, cwd)) {
- reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(::GetLastError()));
- cwd[0] = L'\0';
- }
- if (!Helper::adminLaunch(
- qApp->applicationDirPath().toStdWString(),
- qApp->applicationFilePath().toStdWString(),
- std::wstring(cwd))) {
- return INVALID_HANDLE_VALUE;
- }
- qApp->exit(0);
- }
+
+ case ERROR_ELEVATION_REQUIRED:
+ {
+ startBinaryAdmin(parent, sp);
return INVALID_HANDLE_VALUE;
+ }
- } else {
- reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(e.what()));
+ default:
+ {
+ dialogs::spawnFailed(parent, sp, e);
return INVALID_HANDLE_VALUE;
}
}
+}
+
+} // namespace
+
+
+
+namespace helper
+{
- ::CloseHandle(threadHandle);
- return processHandle;
+bool helperExec(
+ QWidget* parent,
+ const std::wstring& moDirectory, const std::wstring& commandLine, BOOL async)
+{
+ const std::wstring fileName = moDirectory + L"\\helper.exe";
+
+ env::HandlePtr process;
+
+ {
+ SHELLEXECUTEINFOW execInfo = {};
+
+ ULONG flags = SEE_MASK_FLAG_NO_UI ;
+ if (!async)
+ flags |= SEE_MASK_NOCLOSEPROCESS;
+
+ execInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
+ execInfo.fMask = flags;
+ execInfo.hwnd = 0;
+ execInfo.lpVerb = L"runas";
+ execInfo.lpFile = fileName.c_str();
+ execInfo.lpParameters = commandLine.c_str();
+ execInfo.lpDirectory = moDirectory.c_str();
+ execInfo.nShow = SW_SHOW;
+
+ if (!::ShellExecuteExW(&execInfo) && execInfo.hProcess == 0) {
+ const auto e = GetLastError();
+
+ spawn::dialogs::helperFailed(
+ parent, e, "ShellExecuteExW()", fileName, moDirectory, commandLine);
+
+ return false;
+ }
+
+ if (async) {
+ return true;
+ }
+
+ process.reset(execInfo.hProcess);
+ }
+
+ const auto r = ::WaitForSingleObject(process.get(), INFINITE);
+
+ if (r != WAIT_OBJECT_0) {
+ // for WAIT_ABANDONED, the documentation doesn't mention that GetLastError()
+ // returns something meaningful, but code ERROR_ABANDONED_WAIT_0 exists, so
+ // use that instead
+ const auto code = (r == WAIT_ABANDONED ?
+ ERROR_ABANDONED_WAIT_0 : GetLastError());
+
+ spawn::dialogs::helperFailed(
+ parent, code, "WaitForSingleObject()",
+ fileName, moDirectory, commandLine);
+
+ return false;
+ }
+
+ DWORD exitCode = 0;
+ if (!GetExitCodeProcess(process.get(), &exitCode)) {
+ const auto e = GetLastError();
+
+ spawn::dialogs::helperFailed(
+ parent, e, "GetExitCodeProcess()", fileName, moDirectory, commandLine);
+
+ return false;
+ }
+
+ return (exitCode == 0);
+}
+
+bool backdateBSAs(
+ QWidget* parent, const std::wstring &moPath, const std::wstring &dataPath)
+{
+ const std::wstring commandLine = fmt::format(
+ L"backdateBSA \"{}\"", dataPath);
+
+ return helperExec(parent, moPath, commandLine, FALSE);
+}
+
+bool adminLaunch(
+ QWidget* parent, const std::wstring &moPath,
+ const std::wstring &moFile, const std::wstring &workingDir)
+{
+ const std::wstring commandLine = fmt::format(
+ L"adminLaunch {} \"{}\" \"{}\"",
+ ::GetCurrentProcessId(), moFile, workingDir);
+
+ return helperExec(parent, moPath, commandLine, true);
}
+
+} // namespace
diff --git a/src/spawn.h b/src/spawn.h
index c2d99bdb..31b44739 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -26,28 +26,12 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QFileInfo>
#include <QDir>
+class Settings;
-/**
- * @brief a dirty little trick so we can issue a clean restart from startBinary
- * @note unused
- */
-/*class ExitProxy : public QObject {
- Q_OBJECT
-public:
- static ExitProxy *instance();
- void emitExit();
-signals:
- void exit();
-private:
- ExitProxy() {}
-private:
- static ExitProxy *s_Instance;
-};*/
-
+namespace spawn
+{
-/**
- * @brief spawn a binary with Mod Organizer injected
- *
+/*
* @param binary the binary to spawn
* @param arguments arguments to pass to the binary
* @param profileName name of the active profile
@@ -56,13 +40,64 @@ private:
* @param hooked if set, the binary is started with mo injected
* @param stdout if not equal to INVALID_HANDLE_VALUE, this is used as stdout for the process
* @param stderr if not equal to INVALID_HANDLE_VALUE, this is used as stderr for the process
+*/
+struct SpawnParameters
+{
+ QFileInfo binary;
+ QString arguments;
+ QDir currentDirectory;
+ bool hooked = false;
+ HANDLE stdOut = INVALID_HANDLE_VALUE;
+ HANDLE stdErr = INVALID_HANDLE_VALUE;
+};
+
+
+bool checkBinary(QWidget* parent, const SpawnParameters& sp);
+
+bool checkSteam(
+ QWidget* parent, const SpawnParameters& sp,
+ const QDir& gameDirectory, const QString &steamAppID, const Settings& settings);
+
+bool checkEnvironment(QWidget* parent, const SpawnParameters& sp);
+
+bool checkBlacklist(
+ QWidget* parent, const SpawnParameters& sp, Settings& settings);
+
+/**
+ * @brief spawn a binary with Mod Organizer injected
* @return the process handle
- * @todo is the profile name even used any more?
- * @todo is the hooked parameter used?
**/
-HANDLE startBinary(const QFileInfo &binary, const QString &arguments,
- const QDir &currentDirectory, bool hooked,
- HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE);
+HANDLE startBinary(QWidget* parent, const SpawnParameters& sp);
+
+} // namespace
+
+
+// convenience functions to work with the external helper program, which is used
+// to make changes on the system that require administrative rights, so that
+// ModOrganizer itself can run without special privileges
+//
+namespace helper
+{
+
+/**
+* @brief sets the last modified time for all .bsa-files in the target directory well into the past
+* @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(
+ QWidget* parent, const std::wstring &moPath, const std::wstring &dataPath);
+
+/**
+* @brief waits for the current process to exit and restarts it as an administrator
+* @param moPath absolute path to the modOrganizer base directory
+* @param moFile file name of modOrganizer
+* @param workingDir current working directory
+**/
+bool adminLaunch(
+ QWidget* parent, const std::wstring &moPath,
+ const std::wstring &moFile, const std::wstring &workingDir);
+
+} // namespace
#endif // SPAWN_H