diff options
| author | isanae <14251494+isanae@users.noreply.github.com> | 2019-09-22 15:14:53 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-09-22 15:14:53 -0400 |
| commit | 5044a6cd2c7d75e2505e5abc6645fc990f343dc9 (patch) | |
| tree | e06b037a1d44876a895fd5ecc47071477e52101b | |
| parent | f69559fe0bd40629e66ecde6e362b73595d9bd2e (diff) | |
| parent | c1a5f2ef73f4435c08876155d4551c045d5e7594 (diff) | |
Merge pull request #839 from isanae/even-more-logging
Even more logging
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(); } } } @@ -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()); @@ -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 "Remember selection"-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 ¤tDirectory,
- 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 ¤tDirectory, 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
|
