From b2a1e1391fdd6bdee1c5e8d337b273447c70a506 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 18 Jul 2019 23:13:57 -0400 Subject: split env --- src/env.cpp | 479 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 479 insertions(+) create mode 100644 src/env.cpp (limited to 'src/env.cpp') diff --git a/src/env.cpp b/src/env.cpp new file mode 100644 index 00000000..641eb4a7 --- /dev/null +++ b/src/env.cpp @@ -0,0 +1,479 @@ +#include "env.h" +#include "envmetrics.h" +#include "envmodule.h" +#include "envsecurity.h" +#include "envshortcut.h" +#include "envwindows.h" +#include +#include + +namespace env +{ + +using namespace MOBase; + +Console::Console() + : m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr) +{ + // open a console + if (!AllocConsole()) { + // failed, ignore + } + + m_hasConsole = true; + + // redirect stdin, stdout and stderr to it + freopen_s(&m_in, "CONIN$", "r", stdin); + freopen_s(&m_out, "CONOUT$", "w", stdout); + freopen_s(&m_err, "CONOUT$", "w", stderr); +} + +Console::~Console() +{ + // close redirected handles and redirect standard stream to NUL in case + // they're used after this + + if (m_err) { + std::fclose(m_err); + freopen_s(&m_err, "NUL", "w", stderr); + } + + if (m_out) { + std::fclose(m_out); + freopen_s(&m_out, "NUL", "w", stdout); + } + + if (m_in) { + std::fclose(m_in); + freopen_s(&m_in, "NUL", "r", stdin); + } + + // close console + if (m_hasConsole) { + FreeConsole(); + } +} + + +Environment::Environment() + : m_windows(new WindowsInfo), m_metrics(new Metrics) +{ + m_modules = getLoadedModules(); + m_security = getSecurityProducts(); +} + +// anchor +Environment::~Environment() = default; + +const std::vector& Environment::loadedModules() const +{ + return m_modules; +} + +const WindowsInfo& Environment::windowsInfo() const +{ + return *m_windows; +} + +const std::vector& Environment::securityProducts() const +{ + return m_security; +} + +const Metrics& Environment::metrics() const +{ + return *m_metrics; +} + +void Environment::dump() const +{ + log::debug("windows: {}", m_windows->toString()); + + if (m_windows->compatibilityMode()) { + log::warn("MO seems to be running in compatibility mode"); + } + + log::debug("security products:"); + for (const auto& sp : m_security) { + log::debug(" . {}", sp.toString()); + } + + log::debug("modules loaded in process:"); + for (const auto& m : m_modules) { + log::debug(" . {}", m.toString()); + } + + log::debug("displays:"); + for (const auto& d : m_metrics->displays()) { + log::debug(" . {}", d.toString()); + } +} + + +struct Process +{ + std::wstring filename; + DWORD pid; + + Process(std::wstring f, DWORD id) + : filename(std::move(f)), pid(id) + { + } +}; + + +// returns the filename of the given process or the current one +// +std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) +{ + // double the buffer size 10 times + const int MaxTries = 10; + + DWORD bufferSize = MAX_PATH; + + for (int tries=0; tries(bufferSize + 1); + std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); + + DWORD writtenSize = 0; + + if (process == INVALID_HANDLE_VALUE) { + // query this process + writtenSize = GetModuleFileNameW(0, buffer.get(), bufferSize); + } else { + // query another process + writtenSize = GetModuleBaseNameW(process, 0, buffer.get(), bufferSize); + } + + if (writtenSize == 0) { + // hard failure + const auto e = GetLastError(); + std::wcerr << formatSystemMessage(e) << L"\n"; + break; + } else if (writtenSize >= bufferSize) { + // buffer is too small, try again + bufferSize *= 2; + } else { + // if GetModuleFileName() works, `writtenSize` does not include the null + // terminator + const std::wstring s(buffer.get(), writtenSize); + const std::filesystem::path path(s); + + return path.filename().native(); + } + } + + // something failed or the path is way too long to make sense + + std::wstring what; + if (process == INVALID_HANDLE_VALUE) { + what = L"the current process"; + } else { + what = L"pid " + std::to_wstring(reinterpret_cast(process)); + } + + std::wcerr << L"failed to get filename for " << what << L"\n"; + return {}; +} + +std::vector 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(size); + std::fill(ids.get(), ids.get() + size, 0); + + DWORD bytesGiven = static_cast(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(ids.get(), ids.get() + count); + } + + std::cerr << L"too many processes to enumerate"; + return {}; +} + +std::vector runningProcesses() +{ + const auto pids = runningProcessesIds(); + std::vector 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"; + + std::wclog << L"looking for the other process...\n"; + + // used to skip the current process below + const auto thisPid = GetCurrentProcessId(); + std::wclog << L"this process id is " << thisPid << L"\n"; + + // getting the filename for this process, assumes the other process has the + // smae one + auto filename = processFilename(); + if (filename.empty()) { + std::wcerr + << L"can't get current process filename, defaulting to " + << defaultName << L"\n"; + + filename = defaultName; + } else { + std::wclog << L"this process filename is " << filename << L"\n"; + } + + // getting all running processes + const auto processes = runningProcesses(); + 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; + } + } + } + + std::wclog + << L"no process with this filename\n" + << L"MO may not be running, or it may be running as administrator\n" + << L"you can try running this again as administrator\n"; + + return 0; +} + +std::wstring tempDir() +{ + const DWORD bufferSize = MAX_PATH + 1; + wchar_t buffer[bufferSize + 1] = {}; + + const auto written = GetTempPathW(bufferSize, buffer); + if (written == 0) { + const auto e = GetLastError(); + + std::wcerr + << L"failed to get temp path, " << formatSystemMessage(e) << L"\n"; + + return {}; + } + + // `written` does not include the null terminator + return std::wstring(buffer, buffer + written); +} + +HandlePtr tempFile(const std::wstring dir) +{ + // maximum tries of incrementing the counter + const int MaxTries = 100; + + // UTC time and date will be in the filename + const auto now = std::time(0); + const auto tm = std::gmtime(&now); + + // "ModOrganizer-YYYYMMDDThhmmss.dmp", with a possible "-i" appended, where + // i can go until MaxTries + std::wostringstream oss; + oss + << L"ModOrganizer-" + << std::setw(4) << (1900 + tm->tm_year) + << std::setw(2) << std::setfill(L'0') << (tm->tm_mon + 1) + << std::setw(2) << std::setfill(L'0') << tm->tm_mday << "T" + << std::setw(2) << std::setfill(L'0') << tm->tm_hour + << std::setw(2) << std::setfill(L'0') << tm->tm_min + << std::setw(2) << std::setfill(L'0') << tm->tm_sec; + + const std::wstring prefix = oss.str(); + const std::wstring ext = L".dmp"; + + // first path to try, without counter in it + std::wstring path = dir + L"\\" + prefix + ext; + + for (int i=0; i Date: Sun, 8 Sep 2019 03:49:33 -0400 Subject: added missing include guards log free space on drives involved in paths --- src/env.cpp | 40 +++++++++++++++++++++++++++++++++++++++- src/env.h | 13 ++++++++++++- src/envmetrics.h | 5 +++++ src/envmodule.h | 5 +++++ src/envsecurity.h | 5 +++++ src/envshortcut.h | 5 +++++ src/envwindows.h | 5 +++++ src/main.cpp | 3 +-- src/pch.h | 1 + 9 files changed, 78 insertions(+), 4 deletions(-) (limited to 'src/env.cpp') diff --git a/src/env.cpp b/src/env.cpp index 641eb4a7..4628e3f4 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 #include @@ -85,7 +86,7 @@ const Metrics& Environment::metrics() const return *m_metrics; } -void Environment::dump() const +void Environment::dump(const Settings& s) const { log::debug("windows: {}", m_windows->toString()); @@ -107,6 +108,43 @@ void Environment::dump() const for (const auto& d : m_metrics->displays()) { log::debug(" . {}", d.toString()); } + + dumpDisks(s); +} + +void Environment::dumpDisks(const Settings& s) const +{ + std::set 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()); } diff --git a/src/env.h b/src/env.h index 0e88263b..1f146a08 100644 --- a/src/env.h +++ b/src/env.h @@ -1,3 +1,8 @@ +#ifndef ENV_ENV_H +#define ENV_ENV_H + +class Settings; + namespace env { @@ -125,13 +130,17 @@ public: // logs the environment // - void dump() const; + void dump(const Settings& s) const; private: std::vector m_modules; std::unique_ptr m_windows; std::vector m_security; std::unique_ptr m_metrics; + + // dumps all the disks involved in the settings + // + void dumpDisks(const Settings& s) const; }; @@ -152,3 +161,5 @@ bool coredump(CoreDumpTypes type); bool coredumpOther(CoreDumpTypes type); } // namespace env + +#endif // ENV_ENV_H diff --git a/src/envmetrics.h b/src/envmetrics.h index bede36fc..c5d2765a 100644 --- a/src/envmetrics.h +++ b/src/envmetrics.h @@ -1,3 +1,6 @@ +#ifndef ENV_METRICS_H +#define ENV_METRICS_H + #include #include @@ -70,3 +73,5 @@ private: }; } // namespace + +#endif // ENV_METRICS_H diff --git a/src/envmodule.h b/src/envmodule.h index ea1156bd..3f0f99ab 100644 --- a/src/envmodule.h +++ b/src/envmodule.h @@ -1,3 +1,6 @@ +#ifndef ENV_MODULE_H +#define ENV_MODULE_H + #include #include @@ -96,3 +99,5 @@ private: std::vector getLoadedModules(); } // namespace env + +#endif // ENV_MODULE_H diff --git a/src/envsecurity.h b/src/envsecurity.h index 200cb531..bc63c4a2 100644 --- a/src/envsecurity.h +++ b/src/envsecurity.h @@ -1,3 +1,6 @@ +#ifndef ENV_SECURITY_H +#define ENV_SECURITY_H + #include #include @@ -47,3 +50,5 @@ private: std::vector getSecurityProducts(); } // 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 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 #include @@ -104,3 +107,5 @@ private: }; } // namespace + +#endif // ENV_WINDOWS_H diff --git a/src/main.cpp b/src/main.cpp index b5568fec..04bc423b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -577,8 +577,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType()); env::Environment env; - - env.dump(); + env.dump(settings); settings.dump(); sanityChecks(env); diff --git a/src/pch.h b/src/pch.h index af1a4ade..01a97357 100644 --- a/src/pch.h +++ b/src/pch.h @@ -255,3 +255,4 @@ #include #include #include +#include -- cgit v1.3.1 From 12e1a91e4fe8de291fbe72c23031f3e79613c0dd Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 8 Sep 2019 04:58:17 -0400 Subject: log desktop geometry log more info on game plugin --- src/env.cpp | 5 +++++ src/envmetrics.cpp | 12 ++++++++++++ src/envmetrics.h | 4 ++++ src/main.cpp | 5 ++++- src/settings.cpp | 1 + 5 files changed, 26 insertions(+), 1 deletion(-) (limited to 'src/env.cpp') diff --git a/src/env.cpp b/src/env.cpp index 4628e3f4..411443c5 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -109,6 +109,11 @@ void Environment::dump(const Settings& s) const log::debug(" . {}", d.toString()); } + const auto r = m_metrics->desktopGeometry(); + log::debug( + "desktop geometry: ({},{})-({},{})", + r.left(), r.top(), r.right(), r.bottom()); + dumpDisks(s); } 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 #include #include +#include namespace env { @@ -225,6 +226,17 @@ const std::vector& 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 c5d2765a..8dfdb087 100644 --- a/src/envmetrics.h +++ b/src/envmetrics.h @@ -66,6 +66,10 @@ public: // const std::vector& displays() const; + // full resolution + // + QRect desktopGeometry() const; + private: std::vector m_displays; diff --git a/src/main.cpp b/src/main.cpp index 04bc423b..9cb8c08d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -648,7 +648,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(); diff --git a/src/settings.cpp b/src/settings.cpp index 1f066100..7fdda2bf 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1219,6 +1219,7 @@ void PluginSettings::setPersistent( m_Settings.sync(); } } + void PluginSettings::addBlacklist(const QString &fileName) { m_PluginBlacklist.insert(fileName); -- cgit v1.3.1 From 09f98576e882a5fef68cdefdff939834a8782eaa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 11 Sep 2019 23:33:23 -0400 Subject: added env::getFileSecurity() Environment gets stuff on demand --- src/env.cpp | 31 +++-- src/env.h | 21 +++- src/envsecurity.cpp | 332 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/envsecurity.h | 17 +++ 4 files changed, 388 insertions(+), 13 deletions(-) (limited to 'src/env.cpp') diff --git a/src/env.cpp b/src/env.cpp index 411443c5..e2b85560 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -57,10 +57,7 @@ Console::~Console() Environment::Environment() - : m_windows(new WindowsInfo), m_metrics(new Metrics) { - m_modules = getLoadedModules(); - m_security = getSecurityProducts(); } // anchor @@ -68,48 +65,64 @@ Environment::~Environment() = default; const std::vector& Environment::loadedModules() const { + if (m_modules.empty()){ + m_modules = getLoadedModules(); + } + return m_modules; } const WindowsInfo& Environment::windowsInfo() const { + if (!m_windows) { + m_windows.reset(new WindowsInfo); + } + return *m_windows; } const std::vector& 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 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 = m_metrics->desktopGeometry(); + const auto r = metrics().desktopGeometry(); log::debug( "desktop geometry: ({},{})-({},{})", r.left(), r.top(), r.right(), r.bottom()); diff --git a/src/env.h b/src/env.h index 1f146a08..46095ca3 100644 --- a/src/env.h +++ b/src/env.h @@ -79,6 +79,19 @@ template using COMPtr = std::unique_ptr; +// used by MallocPtr, calls std::free() as the deleter +// +struct MallocFreer +{ + void operator()(void* p) + { + std::free(p); + } +}; + +template +using MallocPtr = std::unique_ptr; + // creates a console in the constructor and destroys it in the destructor, // also redirects standard streams // @@ -133,10 +146,10 @@ public: void dump(const Settings& s) const; private: - std::vector m_modules; - std::unique_ptr m_windows; - std::vector m_security; - std::unique_ptr m_metrics; + mutable std::vector m_modules; + mutable std::unique_ptr m_windows; + mutable std::vector m_security; + mutable std::unique_ptr m_metrics; // dumps all the disks involved in the settings // diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 376be4df..6e3fadbe 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -9,6 +9,11 @@ #include #pragma comment(lib, "Wbemuuid.lib") +#include +#include +#include +#pragma comment(lib, "advapi32.lib") + namespace env { @@ -397,4 +402,331 @@ std::vector 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 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 sd( + static_cast(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 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 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 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(nameSize); + auto wsDomain = std::make_unique(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 bc63c4a2..436103b7 100644 --- a/src/envsecurity.h +++ b/src/envsecurity.h @@ -49,6 +49,23 @@ private: std::vector 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 -- cgit v1.3.1 From ac6bc5fd01e115d523de65a02e46b2cde1188d37 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 12 Sep 2019 02:45:03 -0400 Subject: testForSteam() now uses env to get processes moved processes from env.cpp to envmodule.cpp, merged what crash dumps did with what was in testForSteam() --- src/env.cpp | 105 ++++++------------------------------------------------ src/env.h | 5 +++ src/envmodule.cpp | 81 +++++++++++++++++++++++++++++++++++++++++ src/envmodule.h | 24 ++++++++++++- src/spawn.cpp | 62 +++++++------------------------- 5 files changed, 131 insertions(+), 146 deletions(-) (limited to 'src/env.cpp') diff --git a/src/env.cpp b/src/env.cpp index e2b85560..34f53294 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -72,6 +72,11 @@ const std::vector& Environment::loadedModules() const return m_modules; } +std::vector Environment::runningProcesses() const +{ + return getRunningProcesses(); +} + const WindowsInfo& Environment::windowsInfo() const { if (!m_windows) { @@ -166,18 +171,6 @@ void Environment::dumpDisks(const Settings& s) const } -struct Process -{ - std::wstring filename; - DWORD pid; - - Process(std::wstring f, DWORD id) - : filename(std::move(f)), pid(id) - { - } -}; - - // returns the filename of the given process or the current one // std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) @@ -233,84 +226,6 @@ std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) return {}; } -std::vector 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(size); - std::fill(ids.get(), ids.get() + size, 0); - - DWORD bytesGiven = static_cast(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(ids.get(), ids.get() + count); - } - - std::cerr << L"too many processes to enumerate"; - return {}; -} - -std::vector runningProcesses() -{ - const auto pids = runningProcessesIds(); - std::vector 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"; @@ -322,7 +237,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 @@ -335,15 +250,15 @@ DWORD findOtherPid() } // getting all running processes - const auto processes = runningProcesses(); + const auto processes = getRunningProcesses(); std::wclog << L"there are " << processes.size() << L" processes running\n"; // going through processes, trying to find one with the same name and a // different pid than this process has for (const auto& p : processes) { - if (p.filename == filename) { - if (p.pid != thisPid) { - return p.pid; + if (p.name() == filename) { + if (p.pid() != thisPid) { + return p.pid(); } } } diff --git a/src/env.h b/src/env.h index 46095ca3..6222c86b 100644 --- a/src/env.h +++ b/src/env.h @@ -7,6 +7,7 @@ namespace env { class Module; +class Process; class SecurityProduct; class WindowsInfo; class Metrics; @@ -129,6 +130,10 @@ public: // const std::vector& loadedModules() const; + // list of running processes; not cached + // + std::vector runningProcesses() const; + // information about the operating system // const WindowsInfo& windowsInfo() const; 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 getLoadedModules() { HandlePtr snapshot(CreateToolhelp32Snapshot( @@ -373,4 +407,51 @@ std::vector getLoadedModules() return v; } + +std::vector 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 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 3f0f99ab..deb7520f 100644 --- a/src/envmodule.h +++ b/src/envmodule.h @@ -12,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 // @@ -96,6 +96,28 @@ 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 getRunningProcesses(); std::vector getLoadedModules(); } // namespace env diff --git a/src/spawn.cpp b/src/spawn.cpp index a56df23a..604f9ffa 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see . #include "env.h" #include "envwindows.h" #include "envsecurity.h" +#include "envmodule.h" #include "settings.h" #include #include @@ -49,7 +50,6 @@ namespace spawn namespace { - std::wstring pathEnv() { std::wstring s(4000, L' '); @@ -249,6 +249,9 @@ void spawnFailed(const SpawnParameters& sp, DWORD code) "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) { + content = QObject::tr("The file '%1' does not exist.") + .arg(QDir::toNativeSeparators(sp.binary.absoluteFilePath())); } else { content = QString::fromStdWString(formatSystemMessage(code)); } @@ -337,10 +340,7 @@ void startBinaryAdmin(const SpawnParameters& sp) bool checkBinary(QWidget* parent, const SpawnParameters& sp) { if (!sp.binary.exists()) { - reportError( - QObject::tr("Executable not found: %1") - .arg(sp.binary.absoluteFilePath())); - + spawnFailed(sp, ERROR_FILE_NOT_FOUND); return false; } @@ -349,61 +349,23 @@ bool checkBinary(QWidget* parent, const SpawnParameters& sp) bool 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; - } - + const auto ps = env::Environment().runningProcesses(); *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)) { + for (const auto& p : ps) { + if ((p.name().compare("Steam.exe", Qt::CaseInsensitive) == 0) || + (p.name().compare("SteamService.exe", Qt::CaseInsensitive) == 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); - } + *access = p.canAccess(); break; } + } - } while(Process32Next(hProcessSnap, &pe32)); - - CloseHandle(hProcessSnap); return true; } -- cgit v1.3.1 From 09b95e39434b9efc49a606957c94cd42309b7fb6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 13 Sep 2019 15:15:18 -0400 Subject: moved all spawn dialogs into a namespace starting steam with spawn() instead of QProcess dialogs for bad steam registry key and failure refactored credentials code, added logging add environment variables to env --- src/env.cpp | 42 +++++ src/env.h | 12 +- src/organizercore.cpp | 4 - src/settingsutilities.cpp | 148 ++++++++++++----- src/settingsutilities.h | 4 +- src/spawn.cpp | 410 +++++++++++++++++++++++++--------------------- 6 files changed, 383 insertions(+), 237 deletions(-) (limited to 'src/env.cpp') diff --git a/src/env.cpp b/src/env.cpp index 34f53294..1aaaa8ef 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -171,6 +171,48 @@ void Environment::dumpDisks(const Settings& s) const } +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(s.size())); + + if (realSize > s.size()) { + s.resize(realSize); + + ::GetEnvironmentVariableW( + name.toStdWString().c_str(), s.data(), static_cast(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; +} + + // returns the filename of the given process or the current one // std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) diff --git a/src/env.h b/src/env.h index 6222c86b..7bdc9b85 100644 --- a/src/env.h +++ b/src/env.h @@ -162,6 +162,16 @@ private: }; +// 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); + + enum class CoreDumpTypes { Mini = 1, @@ -169,7 +179,7 @@ enum class CoreDumpTypes Full }; -// creates a minidump file for the given process +// creates a minidump file for this process // bool coredump(CoreDumpTypes type); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index dd4edbce..fbc9083b 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1288,7 +1288,6 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, sp.currentDirectory = currentDirectory; sp.hooked = true; - prepareStart(); QWidget *window = qApp->activeWindow(); @@ -1296,7 +1295,6 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, window = nullptr; } - if (!spawn::checkBinary(window, sp)) { return INVALID_HANDLE_VALUE; } @@ -1369,8 +1367,6 @@ 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()); diff --git a/src/settingsutilities.cpp b/src/settingsutilities.cpp index 7a9dcc35..6c99a602 100644 --- a/src/settingsutilities.cpp +++ b/src/settingsutilities.cpp @@ -213,55 +213,117 @@ 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); - - 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; - - result = CredWriteW(&cred, 0); - delete[] 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(); + if (e == ERROR_NOT_FOUND) { + // not an error if the key already doesn't exist + log::debug("can't delete windows credential {}, doesn't exist", credName); + return true; + } else { + 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) -{ - 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 { +bool addWindowsCredential(const QString& key, const QString& data) +{ + const auto credName = credentialName(key); + + const auto wname = credName.toStdWString(); + const auto wdata = data.toStdWString(); + + const auto* blob = reinterpret_cast(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(wname.c_str()); + cred.CredentialBlob = const_cast(blob); + cred.CredentialBlobSize = static_cast(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("added windows credential {}", credName); + + return true; +} + +struct CredentialFreer +{ + void operator()(CREDENTIALW* c) + { + if (c) { + CredFree(c); + } + } +}; + +using CredentialPtr = std::unique_ptr; + +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(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 c3eef12f..a6737144 100644 --- a/src/settingsutilities.h +++ b/src/settingsutilities.h @@ -270,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 80f82a28..94737871 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -43,96 +43,8 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; -namespace spawn -{ - -// details -namespace -{ - -std::wstring pathEnv() +namespace spawn::dialogs { - std::wstring s(4000, L' '); - - DWORD realSize = ::GetEnvironmentVariableW( - L"PATH", s.data(), static_cast(s.size())); - - if (realSize > s.size()) { - s.resize(realSize); - - ::GetEnvironmentVariableW( - TEXT("PATH"), s.data(), static_cast(s.size())); - } - - return s; -} - -void setPathEnv(const std::wstring& s) -{ - ::SetEnvironmentVariableW(L"PATH", s.c_str()); -} - -DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHandle) -{ - BOOL inheritHandles = FALSE; - - 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 (sp.stdErr != INVALID_HANDLE_VALUE) { - si.hStdError = sp.stdErr; - inheritHandles = TRUE; - si.dwFlags |= STARTF_USESTDHANDLES; - } - - const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()).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 = pathEnv(); - setPathEnv(oldPath + L";" + QDir::toNativeSeparators(moPath).toStdWString()); - - PROCESS_INFORMATION pi; - BOOL success = FALSE; - - const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString(); - - if (sp.hooked) { - success = ::CreateProcessHooked( - nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, - inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - cwd.c_str(), &si, &pi); - } else { - success = ::CreateProcess( - nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, - inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - cwd.c_str(), &si, &pi); - } - - const auto e = GetLastError(); - setPathEnv(oldPath); - - if (!success) { - return e; - } - - processHandle = pi.hProcess; - threadHandle = pi.hThread; - - return ERROR_SUCCESS; -} std::wstring makeRightsDetails(const env::FileSecurity& fs) { @@ -196,7 +108,7 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) elevated = L"(not available)"; } - return fmt::format( + std::wstring f = L"Error {code} {codename}: {error}\n" L" . binary: '{bin}'\n" L" . owner: {owner}\n" @@ -204,8 +116,13 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) L" . arguments: '{args}'\n" L" . cwd: '{cwd}'{cwdexists}\n" L" . stdout: {stdout}, stderr: {stderr}, hooked: {hooked}\n" - L" . usvfs x86:{x86_dll} x64:{x64_dll} proxy_x86:{x86_proxy} proxy_x64:{x64_proxy}\n" - L" . MO elevated: {elevated}", + 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}"; + } + + return fmt::format(f, fmt::arg(L"code", code), fmt::arg(L"codename", errorCodeName(code)), fmt::arg(L"bin", QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString()), @@ -225,36 +142,82 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) fmt::arg(L"elevated", elevated)); } -void spawnFailed(const SpawnParameters& sp, DWORD code) +QString makeContent(const SpawnParameters& sp, DWORD code) { - const auto details = QString::fromStdWString(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()); - - QString content; - if (code == ERROR_INVALID_PARAMETER) { - content = QObject::tr( + 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) { - content = QObject::tr( + 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) { - content = QObject::tr("The file '%1' does not exist.") + return QObject::tr("The file '%1' does not exist.") .arg(QDir::toNativeSeparators(sp.binary.absoluteFilePath())); } else { - content = QString::fromStdWString(formatSystemMessage(code)); + 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) + .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 SpawnParameters& sp, DWORD e) +{ + const auto 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(QString::fromStdWString(details)) + .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(const SpawnParameters& sp, DWORD code) +{ + const auto details = QString::fromStdWString(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()); QWidget *window = qApp->activeWindow(); if ((window != nullptr) && (!window->isVisible())) { @@ -263,7 +226,7 @@ void spawnFailed(const SpawnParameters& sp, DWORD code) MOBase::TaskDialog(window, title) .main(mainText) - .content(content) + .content(makeContent(sp, code)) .details(details) .exec(); } @@ -301,48 +264,143 @@ bool confirmRestartAsAdmin(const SpawnParameters& sp) .content(content) .details(details) .button({ - QObject::tr("Restart Mod Organizer as administrator"), - QObject::tr("You must allow \"helper.exe\" to make changes to the system."), - QMessageBox::Yes}) + 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}) + QObject::tr("Cancel"), + QMessageBox::Cancel}) .exec(); return (r == QMessageBox::Yes); } -void startBinaryAdmin(const SpawnParameters& sp) +QuestionBoxMemory::Button confirmStartSteam(QWidget* parent, const SpawnParameters& sp) { - if (!confirmRestartAsAdmin(sp)) { - log::debug("user declined"); - return; + return QuestionBoxMemory::query( + parent, "steamQuery", sp.binary.fileName(), + QObject::tr("Start Steam?"), + QObject::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); +} + +QuestionBoxMemory::Button confirmRestartAsAdminForSteam(QWidget* parent, const SpawnParameters& sp) +{ + return QuestionBoxMemory::query( + parent, "steamAdminQuery", sp.binary.fileName(), + QObject::tr("Steam: Access Denied"), + QObject::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); +} + +} // namepsace + + +namespace spawn +{ + +DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHandle) +{ + BOOL inheritHandles = FALSE; + + 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; } - log::info("restarting MO as administrator"); + if (sp.stdErr != INVALID_HANDLE_VALUE) { + si.hStdError = sp.stdErr; + inheritHandles = TRUE; + si.dwFlags |= STARTF_USESTDHANDLES; + } + + 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(commandLine.c_str()), nullptr, nullptr, + inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, + cwd.c_str(), &si, &pi); + } else { + success = ::CreateProcess( + nullptr, const_cast(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; + } + processHandle = pi.hProcess; + threadHandle = pi.hThread; + + return ERROR_SUCCESS; +} + +bool restartAsAdmin() +{ WCHAR cwd[MAX_PATH] = {}; if (!GetCurrentDirectory(MAX_PATH, cwd)) { cwd[0] = L'\0'; } - if (Helper::adminLaunch( + if (!Helper::adminLaunch( qApp->applicationDirPath().toStdWString(), qApp->applicationFilePath().toStdWString(), std::wstring(cwd))) { - log::debug("exiting MO"); - qApp->exit(0); + // todo + log::error("admin launch failed"); + return false; } + + log::debug("exiting MO"); + qApp->exit(0); + return true; } -} // namespace +void startBinaryAdmin(const SpawnParameters& sp) +{ + if (!dialogs::confirmRestartAsAdmin(sp)) { + log::debug("user declined"); + return; + } + + log::info("restarting MO as administrator"); + restartAsAdmin(); +} bool checkBinary(QWidget* parent, const SpawnParameters& sp) { if (!sp.binary.exists()) { - spawnFailed(sp, ERROR_FILE_NOT_FOUND); + dialogs::spawnFailed(sp, ERROR_FILE_NOT_FOUND); return false; } @@ -379,10 +437,23 @@ SteamStatus getSteamStatus() return ss; } -bool startSteam(QWidget *widget) +QString makeSteamArguments(const QString& username, const QString& password) { - log::debug("starting steam"); + 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"; @@ -390,42 +461,41 @@ bool startSteam(QWidget *widget) const QString exe = steamSettings.value(valueName, "").toString(); if (exe.isEmpty()) { - log::error( - "can't start steam, registry value at '{}' is empty", - keyName + "\\" + valueName); - - return false; + return (dialogs::badSteamReg(parent, keyName, valueName) == QMessageBox::Yes); } - const QString program = QString("\"%1\"").arg(exe); + SpawnParameters sp; + sp.binary = exe; // See if username and password supplied. If so, pass them into steam. - QStringList args; QString username, password; if (Settings::instance().steam().login(username, password)) { - args.push_back("-login"); - args.push_back(username); - - if (password != "") { - args.push_back(password); - } + sp.arguments = makeSteamArguments(username, password); } log::debug( "starting steam process:\n" " . program: '{}'\n" " . username={}, password={}", - program, + sp.binary.filePath().toStdString(), (username.isEmpty() ? "no" : "yes"), (password.isEmpty() ? "no" : "yes")); - if (!QProcess::startDetached(program, args)) { - reportError(QObject::tr("Failed to start \"%1\"").arg(program)); - return false; + 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")); + + return (dialogs::startSteamFailed(parent, sp, e) == QMessageBox::Yes); } QMessageBox::information( - widget, QObject::tr("Waiting"), + parent, QObject::tr("Waiting"), QObject::tr("Please press OK once you're logged into steam.")); return true; @@ -448,29 +518,6 @@ bool gameRequiresSteam(const QDir& gameDirectory, const Settings& settings) return false; } -QuestionBoxMemory::Button confirmStartSteam(QWidget* parent, const SpawnParameters& sp) -{ - return QuestionBoxMemory::query( - parent, "steamQuery", sp.binary.fileName(), - QObject::tr("Start Steam?"), - QObject::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); -} - -QuestionBoxMemory::Button confirmRestartAsAdminForSteam(QWidget* parent, const SpawnParameters& sp) -{ - return QuestionBoxMemory::query( - parent, "steamAdminQuery", sp.binary.fileName(), - QObject::tr("Steam: Access Denied"), - QObject::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); -} - bool checkSteam( QWidget* parent, const SpawnParameters& sp, const QDir& gameDirectory, const QString &steamAppID, const Settings& settings) @@ -492,16 +539,20 @@ bool checkSteam( if (!ss.running) { log::debug("steam isn't running, asking to start steam"); - const auto c = confirmStartSteam(parent, sp); + const auto c = dialogs::confirmStartSteam(parent, sp); if (c == QDialogButtonBox::Yes) { log::debug("user wants to start steam"); - startSteam(parent); + + if (!startSteam(parent)) { + // cancel + return false; + } // double-check that Steam is started ss = getSteamStatus(); if (!ss.running) { - log::error("could not start steam, continuing and hoping for the best"); + log::error("steam is still not running, continuing and hoping for the best"); return true; } } else if (c == QDialogButtonBox::No) { @@ -515,25 +566,10 @@ bool checkSteam( if (ss.running && !ss.accessible) { log::debug("steam is running but is not accessible, asking to restart MO"); - const auto c = confirmRestartAsAdminForSteam(parent, sp); + const auto c = dialogs::confirmRestartAsAdminForSteam(parent, sp); if (c == QDialogButtonBox::Yes) { - WCHAR cwd[MAX_PATH]; - if (!GetCurrentDirectory(MAX_PATH, cwd)) { - cwd[0] = L'\0'; - } - - if (Helper::adminLaunch( - qApp->applicationDirPath().toStdWString(), - qApp->applicationFilePath().toStdWString(), - std::wstring(cwd))) - { - log::debug("exiting MO"); - qApp->exit(0); - return false; - } - - log::error("unable to relaunch MO as admin"); + restartAsAdmin(); return false; } else if (c == QDialogButtonBox::No) { log::debug("user declined to restart MO, continuing"); @@ -686,7 +722,7 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) default: { - spawnFailed(sp, e); + dialogs::spawnFailed(sp, e); return INVALID_HANDLE_VALUE; } } -- cgit v1.3.1 From c1ab18b614aa6212f942d8d91afa0b191802f599 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Sep 2019 11:14:14 -0400 Subject: moved the content of checkService() to env::getService(), refactored it --- src/env.cpp | 228 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/env.h | 58 +++++++++++++++ src/spawn.cpp | 92 ++++-------------------- 3 files changed, 301 insertions(+), 77 deletions(-) (limited to 'src/env.cpp') diff --git a/src/env.cpp b/src/env.cpp index 1aaaa8ef..78b5dc96 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -213,6 +213,234 @@ QString set(const QString& n, const QString& v) } +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(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(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 config( + static_cast(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) +{ + DWORD needed = 0; + + if (!QueryServiceStatusEx(s, SC_STATUS_PROCESS_INFO, NULL, 0, &needed)) { + const auto e = GetLastError(); + + if (e != ERROR_INSUFFICIENT_BUFFER) { + log::error( + "QueryServiceStatusEx() for size for '{}' failed, {}", + name, GetLastError()); + + return Service::Status::None; + } + } + + const auto size = needed; + MallocPtr status( + static_cast(std::malloc(size))); + + const auto r = QueryServiceStatusEx( + s, SC_STATUS_PROCESS_INFO, reinterpret_cast(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 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 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 // std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) diff --git a/src/env.h b/src/env.h index 7bdc9b85..1760c7fe 100644 --- a/src/env.h +++ b/src/env.h @@ -93,6 +93,24 @@ struct MallocFreer template using MallocPtr = std::unique_ptr; + +// used by LocalPtr, calls LocalFree() as the deleter +// +template +struct LocalFreer +{ + using pointer = T; + + void operator()(T p) + { + ::LocalFree(p); + } +}; + +template +using LocalPtr = std::unique_ptr>; + + // creates a console in the constructor and destroys it in the destructor, // also redirects standard streams // @@ -172,6 +190,46 @@ 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, diff --git a/src/spawn.cpp b/src/spawn.cpp index 64766adf..551a5adb 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -428,7 +428,6 @@ void startBinaryAdmin(const SpawnParameters& sp) restartAsAdmin(); } - bool checkBinary(QWidget* parent, const SpawnParameters& sp) { if (!sp.binary.exists()) { @@ -557,9 +556,9 @@ bool checkSteam( log::debug("checking steam"); if (!steamAppID.isEmpty()) { - ::SetEnvironmentVariableW(L"SteamAPPId", steamAppID.toStdWString().c_str()); + env::set("SteamAPPId", steamAppID); } else { - ::SetEnvironmentVariableW(L"SteamAPPId", settings.steam().appID().toStdWString().c_str()); + env::set("SteamAPPId", settings.steam().appID()); } if (!gameRequiresSteam(gameDirectory, settings)) { @@ -584,7 +583,7 @@ bool checkSteam( // double-check that Steam is started ss = getSteamStatus(); if (!ss.running) { - log::error("steam is still not running, continuing and hoping for the best"); + log::error("steam is still not running, hoping for the best"); return true; } } else if (c == QDialogButtonBox::No) { @@ -615,90 +614,29 @@ bool checkSteam( return true; } -bool checkService() +bool checkEventLogService() { - 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; - } + const auto s = env::getService("EventLog"); - 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) { - serviceRunning = false; + if (!s.isValid()) { + log::error("cannot determine the status of the EventLog, continuing"); + return true; } - if (serviceStatus) { - LocalFree(serviceStatus); - } - if (serviceConfig) { - LocalFree(serviceConfig); - } - if (serviceHandle) { - CloseServiceHandle(serviceHandle); - } - if (serviceManagerHandle) { - CloseServiceHandle(serviceManagerHandle); + if (s.status() == env::Service::Status::Running) { + log::debug("{}", s.toString()); + return true; + } else { + log::error("{}", s.toString()); + return false; } - - return serviceRunning; } 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. - if (!checkService()) { + if (!checkEventLogService()) { if (QuestionBoxMemory::query(parent, QString("eventLogService"), sp.binary.fileName(), QObject::tr("Windows Event Log Error"), QObject::tr("The Windows Event Log service is disabled and/or not running. This prevents" -- cgit v1.3.1 From 4e8dcc5157706e1478396179f5dc11305532b159 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 24 Oct 2019 04:27:22 -0400 Subject: moved findJavaInstallation() and getFileExecutionContext() to spawn fixed env::get() returning garbage after value --- src/editexecutablesdialog.cpp | 3 +- src/env.cpp | 31 +++-- src/mainwindow.cpp | 68 +++++------ src/organizercore.cpp | 264 +++++++++++++++--------------------------- src/organizercore.h | 12 -- src/spawn.cpp | 183 ++++++++++++++++++++++++++++- src/spawn.h | 19 +++ 7 files changed, 351 insertions(+), 229 deletions(-) (limited to 'src/env.cpp') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 8535b7a7..32b31357 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see . #include "modlist.h" #include "forcedloaddialog.h" #include "organizercore.h" +#include "spawn.h" #include #include @@ -800,7 +801,7 @@ QFileInfo EditExecutablesDialog::browseBinary(const QString& initial) void EditExecutablesDialog::setJarBinary(const QFileInfo& binary) { - auto java = OrganizerCore::findJavaInstallation(binary.absoluteFilePath()); + auto java = spawn::findJavaInstallation(binary.absoluteFilePath()); if (java.isEmpty()) { QMessageBox::information( diff --git a/src/env.cpp b/src/env.cpp index 78b5dc96..507607d1 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -190,19 +190,36 @@ QString setPath(const QString& s) QString get(const QString& name) { - std::wstring s(4000, L' '); + std::size_t bufferSize = 4000; + auto buffer = std::make_unique(bufferSize); DWORD realSize = ::GetEnvironmentVariableW( - name.toStdWString().c_str(), s.data(), static_cast(s.size())); + name.toStdWString().c_str(), + buffer.get(), static_cast(bufferSize)); - if (realSize > s.size()) { - s.resize(realSize); + if (realSize > bufferSize) { + bufferSize = realSize; + buffer = std::make_unique(bufferSize); - ::GetEnvironmentVariableW( - name.toStdWString().c_str(), s.data(), static_cast(s.size())); + realSize = ::GetEnvironmentVariableW( + name.toStdWString().c_str(), + buffer.get(), static_cast(bufferSize)); } - return QString::fromStdWString(s); + if (realSize == 0) { + const auto e = ::GetLastError(); + + // don't log if not found + if (e != ERROR_ENVVAR_NOT_FOUND) { + log::error( + "failed to get environment variable '{}', {}", + name, formatSystemMessage(e)); + } + + return {}; + } + + return QString::fromWCharArray(buffer.get(), realSize); } QString set(const QString& n, const QString& v) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2474fdc0..0181a335 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5295,43 +5295,42 @@ void MainWindow::addAsExecutable() return; } - using FileExecutionTypes = OrganizerCore::FileExecutionTypes; + const QFileInfo target(m_ContextItem->data(0, Qt::UserRole).toString()); + const auto fec = spawn::getFileExecutionContext(this, target); - QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); - QFileInfo binaryInfo; - QString arguments; - FileExecutionTypes type; - - if (!OrganizerCore::getFileExecutionContext(this, targetInfo, binaryInfo, arguments, type)) { - return; - } - - switch (type) + switch (fec.type) { - case FileExecutionTypes::Executable: { - QString name = QInputDialog::getText(this, tr("Enter Name"), - tr("Please enter a name for the executable"), QLineEdit::Normal, - targetInfo.completeBaseName()); - - if (!name.isEmpty()) { - //Note: If this already exists, you'll lose custom settings - m_OrganizerCore.executablesList()->setExecutable(Executable() - .title(name) - .binaryInfo(binaryInfo) - .arguments(arguments) - .workingDirectory(targetInfo.absolutePath())); - - refreshExecutablesList(); - } - - break; + case spawn::FileExecutionTypes::Executable: + { + const QString name = QInputDialog::getText( + this, tr("Enter Name"), + tr("Enter a name for the executable"), + QLineEdit::Normal, + target.completeBaseName()); + + if (!name.isEmpty()) { + //Note: If this already exists, you'll lose custom settings + m_OrganizerCore.executablesList()->setExecutable(Executable() + .title(name) + .binaryInfo(fec.binary) + .arguments(fec.arguments) + .workingDirectory(target.absolutePath())); + + refreshExecutablesList(); } - case FileExecutionTypes::Other: // fall-through - default: { - QMessageBox::information(this, tr("Not an executable"), tr("This is not a recognized executable.")); - break; - } + break; + } + + case spawn::FileExecutionTypes::Other: // fall-through + default: + { + QMessageBox::information( + this, tr("Not an executable"), + tr("This is not a recognized executable.")); + + break; + } } } @@ -5458,7 +5457,8 @@ void MainWindow::openDataFile() return; } - QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); + const QString path = m_ContextItem->data(0, Qt::UserRole).toString(); + const QFileInfo targetInfo(path); m_OrganizerCore.runFile(this, targetInfo); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 6bf2c290..78f9517c 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -976,76 +976,6 @@ QStringList OrganizerCore::modsSortedByProfilePriority() const return res; } -QString OrganizerCore::findJavaInstallation(const QString& jarFile) -{ - if (!jarFile.isEmpty()) { - // try to find java automatically based on the given jar file - std::wstring jarFileW = jarFile.toStdWString(); - - WCHAR buffer[MAX_PATH]; - if (::FindExecutableW(jarFileW.c_str(), nullptr, buffer) > (HINSTANCE)32) { - DWORD binaryType = 0UL; - if (!::GetBinaryTypeW(buffer, &binaryType)) { - log::debug( - "failed to determine binary type of \"{}\": {}", - QString::fromWCharArray(buffer), ::GetLastError()); - } else if (binaryType == SCS_32BIT_BINARY || binaryType == SCS_64BIT_BINARY) { - return QString::fromWCharArray(buffer); - } - } - } - - // second attempt: look to the registry - QSettings reg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", QSettings::NativeFormat); - if (reg.contains("CurrentVersion")) { - QString currentVersion = reg.value("CurrentVersion").toString(); - return reg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe"); - } - - // not found - return {}; -} - -bool OrganizerCore::getFileExecutionContext( - QWidget* parent, const QFileInfo &targetInfo, - QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type) -{ - QString extension = targetInfo.suffix(); - if ((extension.compare("cmd", Qt::CaseInsensitive) == 0) || - (extension.compare("com", Qt::CaseInsensitive) == 0) || - (extension.compare("bat", Qt::CaseInsensitive) == 0)) { - binaryInfo = QFileInfo("C:\\Windows\\System32\\cmd.exe"); - arguments = QString("/C \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - type = FileExecutionTypes::Executable; - return true; - } else if (extension.compare("exe", Qt::CaseInsensitive) == 0) { - binaryInfo = targetInfo; - type = FileExecutionTypes::Executable; - return true; - } else if (extension.compare("jar", Qt::CaseInsensitive) == 0) { - auto java = findJavaInstallation(targetInfo.absoluteFilePath()); - - if (java.isEmpty()) { - java = QFileDialog::getOpenFileName( - parent, QObject::tr("Select binary"), - QString(), QObject::tr("Binary") + " (*.exe)"); - } - - if (java.isEmpty()) { - return false; - } - - binaryInfo = QFileInfo(java); - arguments = QString("-jar \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - type = FileExecutionTypes::Executable; - - return true; - } else { - type = FileExecutionTypes::Other; - return true; - } -} - bool OrganizerCore::previewFileWithAlternatives( QWidget* parent, QString fileName, int selectedOrigin) { @@ -1177,35 +1107,23 @@ bool OrganizerCore::previewFile( bool OrganizerCore::runFile( QWidget* parent, const QFileInfo& targetInfo) { - QFileInfo binaryInfo; - QString arguments; - FileExecutionTypes type; + const auto fec = spawn::getFileExecutionContext(parent, targetInfo); - if (!getFileExecutionContext(parent, targetInfo, binaryInfo, arguments, type)) { - return false; - } - - switch (type) + switch (fec.type) { - case FileExecutionTypes::Executable: { - runExecutableFile( - binaryInfo, arguments, currentProfile()->name(), - targetInfo.absolutePath()); - + case spawn::FileExecutionTypes::Executable: + { + runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir()); return true; } - case FileExecutionTypes::Other: { - ::ShellExecuteW(nullptr, L"open", - ToWString(targetInfo.absoluteFilePath()).c_str(), - nullptr, nullptr, SW_SHOWNORMAL); - - return true; + case spawn::FileExecutionTypes::Other: // fall-through + default: + { + const auto r = shell::Open(targetInfo.absoluteFilePath()); + return r.success(); } } - - // nop - return false; } bool OrganizerCore::runExecutableFile( @@ -1281,6 +1199,87 @@ bool OrganizerCore::runShortcut(const MOShortcut& shortcut) return runExecutable(exe, false); } +HANDLE OrganizerCore::runExecutableOrExecutableFile( + const QString &executable, const QStringList &args, const QString &cwd, + const QString &profile, const QString &forcedCustomOverwrite, + bool ignoreCustomOverwrite) +{ + QString profileName = profile; + if (profile == "") { + if (m_CurrentProfile != nullptr) { + profileName = m_CurrentProfile->name(); + } else { + throw MyException(tr("No profile set")); + } + } + + QFileInfo binary; + QString arguments = args.join(" "); + QString currentDirectory = cwd; + QString steamAppID; + QString customOverwrite; + QList forcedLibraries; + + if (executable.contains('\\') || executable.contains('/')) { + // file path + + binary = QFileInfo(executable); + if (binary.isRelative()) { + // relative path, should be relative to game directory + binary = managedGame()->gameDirectory().absoluteFilePath(executable); + } + + if (currentDirectory == "") { + currentDirectory = binary.absolutePath(); + } + + try { + const Executable& exe = m_ExecutablesList.getByBinary(binary); + steamAppID = exe.steamAppID(); + customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); + if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); + } + } catch (const std::runtime_error &) { + // nop + } + } else { + // only a file name, search executables list + try { + const Executable &exe = m_ExecutablesList.get(executable); + steamAppID = exe.steamAppID(); + customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); + if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); + } + if (arguments == "") { + arguments = exe.arguments(); + } + binary = exe.binaryInfo(); + if (currentDirectory == "") { + currentDirectory = exe.workingDirectory(); + } + } catch (const std::runtime_error &) { + log::warn("\"{}\" not set up as executable", executable); + binary = QFileInfo(executable); + } + } + + if (!forcedCustomOverwrite.isEmpty()) + customOverwrite = forcedCustomOverwrite; + + if (ignoreCustomOverwrite) + customOverwrite.clear(); + + return spawnAndWait(binary, + arguments, + profileName, + currentDirectory, + steamAppID, + customOverwrite, + forcedLibraries); +} + HANDLE OrganizerCore::spawnAndWait( const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID, @@ -1362,87 +1361,6 @@ HANDLE OrganizerCore::spawnAndWait( return handle; } -HANDLE OrganizerCore::runExecutableOrExecutableFile( - const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite, - bool ignoreCustomOverwrite) -{ - QString profileName = profile; - if (profile == "") { - if (m_CurrentProfile != nullptr) { - profileName = m_CurrentProfile->name(); - } else { - throw MyException(tr("No profile set")); - } - } - - QFileInfo binary; - QString arguments = args.join(" "); - QString currentDirectory = cwd; - QString steamAppID; - QString customOverwrite; - QList forcedLibraries; - - if (executable.contains('\\') || executable.contains('/')) { - // file path - - binary = QFileInfo(executable); - if (binary.isRelative()) { - // relative path, should be relative to game directory - binary = managedGame()->gameDirectory().absoluteFilePath(executable); - } - - if (currentDirectory == "") { - currentDirectory = binary.absolutePath(); - } - - try { - const Executable& exe = m_ExecutablesList.getByBinary(binary); - steamAppID = exe.steamAppID(); - customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - } catch (const std::runtime_error &) { - // nop - } - } else { - // only a file name, search executables list - try { - const Executable &exe = m_ExecutablesList.get(executable); - steamAppID = exe.steamAppID(); - customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - if (arguments == "") { - arguments = exe.arguments(); - } - binary = exe.binaryInfo(); - if (currentDirectory == "") { - currentDirectory = exe.workingDirectory(); - } - } catch (const std::runtime_error &) { - log::warn("\"{}\" not set up as executable", executable); - binary = QFileInfo(executable); - } - } - - if (!forcedCustomOverwrite.isEmpty()) - customOverwrite = forcedCustomOverwrite; - - if (ignoreCustomOverwrite) - customOverwrite.clear(); - - return spawnAndWait(binary, - arguments, - profileName, - currentDirectory, - steamAppID, - customOverwrite, - forcedLibraries); -} - bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) { if (!Settings::instance().interface().lockGUI()) diff --git a/src/organizercore.h b/src/organizercore.h index fa05a20f..f802c8cb 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -91,12 +91,6 @@ private: typedef boost::signals2::signal SignalModInstalled; public: - enum class FileExecutionTypes - { - Executable = 1, - Other = 2 - }; - static bool isNxmLink(const QString &link) { return link.startsWith("nxm://", Qt::CaseInsensitive); } OrganizerCore(Settings &settings); @@ -152,12 +146,6 @@ public: void doAfterLogin(const std::function &function) { m_PostLoginTasks.append(function); } void loggedInAction(QWidget* parent, std::function f); - static QString findJavaInstallation(const QString& jarFile={}); - - static bool getFileExecutionContext( - QWidget* parent, const QFileInfo &targetInfo, - QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type); - bool previewFileWithAlternatives(QWidget* parent, QString filename, int selectedOrigin=-1); bool previewFile(QWidget* parent, const QString& originName, const QString& path); diff --git a/src/spawn.cpp b/src/spawn.cpp index 3c7d64ce..dd93bfaa 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -901,11 +901,11 @@ SpawnedProcess Spawner::spawn( return {INVALID_HANDLE_VALUE, sp}; } - if (!spawn::checkEnvironment(parent, sp)) { + if (!checkEnvironment(parent, sp)) { return {INVALID_HANDLE_VALUE, sp}; } - if (!spawn::checkBlacklist(parent, sp, settings)) { + if (!checkBlacklist(parent, sp, settings)) { return {INVALID_HANDLE_VALUE, sp}; } @@ -914,6 +914,185 @@ SpawnedProcess Spawner::spawn( return {startBinary(parent, sp), sp}; } + +QString getExecutableForJarFile(const QString& jarFile) +{ + const std::wstring jarFileW = jarFile.toStdWString(); + + WCHAR buffer[MAX_PATH]; + + const auto hinst = ::FindExecutableW(jarFileW.c_str(), nullptr, buffer); + const auto r = static_cast(reinterpret_cast(hinst)); + + // anything <= 32 signals failure + if (r <= 32) { + log::warn( + "failed to find executable associated with file '{}', {}", + jarFile, shell::formatError(r)); + + return {}; + } + + DWORD binaryType = 0; + + if (!::GetBinaryTypeW(buffer, &binaryType)) { + const auto e = ::GetLastError(); + + log::warn( + "failed to determine binary type of '{}', {}", + QString::fromWCharArray(buffer), formatSystemMessage(e)); + + return {}; + } + + if (binaryType != SCS_32BIT_BINARY && binaryType != SCS_64BIT_BINARY) { + log::warn( + "unexpected binary type {} for file '{}'", + binaryType, QString::fromWCharArray(buffer)); + + return {}; + } + + return QString::fromWCharArray(buffer); +} + +QString getJavaHome() +{ + const QString key = "HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment"; + const QString value = "CurrentVersion"; + + QSettings reg(key, QSettings::NativeFormat); + + if (!reg.contains(value)) { + log::warn("key '{}\\{}' doesn't exist", key, value); + return {}; + } + + const QString currentVersion = reg.value("CurrentVersion").toString(); + const QString javaHome = QString("%1/JavaHome").arg(currentVersion); + + if (!reg.contains(javaHome)) { + log::warn( + "java version '{}' was found at '{}\\{}', but '{}\\{}' doesn't exist", + currentVersion, key, value, key, javaHome); + + return {}; + } + + const auto path = reg.value(javaHome).toString(); + return path + "\\bin\\javaw.exe"; +} + +QString findJavaInstallation(const QString& jarFile) +{ + // try to find java automatically based on the given jar file + if (!jarFile.isEmpty()) { + const auto s = getExecutableForJarFile(jarFile); + if (!s.isEmpty()) { + return s; + } + } + + // second attempt: look to the registry + const auto s = getJavaHome(); + if (!s.isEmpty()) { + return s; + } + + // not found + return {}; +} + +bool isBatchFile(const QFileInfo& target) +{ + const auto batchExtensions = {"cmd", "bat"}; + + const QString extension = target.suffix(); + for (auto&& e : batchExtensions) { + if (extension.compare(e, Qt::CaseInsensitive) == 0) { + return true; + } + } + + return false; +} + +bool isExeFile(const QFileInfo& target) +{ + return (target.suffix().compare("exe", Qt::CaseInsensitive) == 0); +} + +bool isJavaFile(const QFileInfo& target) +{ + return (target.suffix().compare("jar", Qt::CaseInsensitive) == 0); +} + +QFileInfo getCmdPath() +{ + const auto p = env::get("COMSPEC2"); + if (!p.isEmpty()) { + return p; + } + + QString systemDirectory; + + const std::size_t buffer_size = 1000; + wchar_t buffer[buffer_size + 1] = {}; + + const auto length = ::GetSystemDirectoryW(buffer, buffer_size); + if (length != 0) { + systemDirectory = QString::fromWCharArray(buffer, length); + + if (!systemDirectory.endsWith("\\")) { + systemDirectory += "\\"; + } + } else { + systemDirectory = "C:\\Windows\\System32\\"; + } + + return systemDirectory + "cmd.exe"; +} + +FileExecutionContext getFileExecutionContext( + QWidget* parent, const QFileInfo& target) +{ + if (isExeFile(target)) { + return { + target, + "", + FileExecutionTypes::Executable + }; + } + + if (isBatchFile(target)) { + return { + getCmdPath(), + QString("/C \"%1\"").arg(QDir::toNativeSeparators(target.absoluteFilePath())), + FileExecutionTypes::Executable + }; + } + + if (isJavaFile(target)) { + auto java = findJavaInstallation(target.absoluteFilePath()); + + if (java.isEmpty()) { + java = QFileDialog::getOpenFileName( + parent, QObject::tr("Select binary"), + QString(), QObject::tr("Binary") + " (*.exe)"); + } + + if (!java.isEmpty()) { + return { + QFileInfo(java), + QString("-jar \"%1\"").arg(QDir::toNativeSeparators(target.absoluteFilePath())), + FileExecutionTypes::Executable + }; + } + } + + return {{}, {}, FileExecutionTypes::Other}; +} + } // namespace diff --git a/src/spawn.h b/src/spawn.h index d2853cd5..866e1795 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -103,6 +103,25 @@ public: private: }; + +enum class FileExecutionTypes +{ + Executable = 1, + Other +}; + +struct FileExecutionContext +{ + QFileInfo binary; + QString arguments; + FileExecutionTypes type; +}; + +QString findJavaInstallation(const QString& jarFile); + +FileExecutionContext getFileExecutionContext( + QWidget* parent, const QFileInfo& target); + } // namespace -- cgit v1.3.1 From e67381b4b8731d80a4f11fb441d46424b571a659 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 06:14:13 -0500 Subject: log timezone --- src/env.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/env.h | 4 ++++ 2 files changed, 48 insertions(+) (limited to 'src/env.cpp') diff --git a/src/env.cpp b/src/env.cpp index 507607d1..a23e65a4 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -104,10 +104,54 @@ const Metrics& Environment::metrics() const return *m_metrics; } +QString Environment::timezone() const +{ + TIME_ZONE_INFORMATION tz = {}; + + const auto r = GetTimeZoneInformation(&tz); + if (r == TIME_ZONE_ID_INVALID) { + const auto e = GetLastError(); + log::error("failed to get timezone, {}", formatSystemMessage(e)); + return "unknown"; + } + + auto offsetString = [](int o) { + return + QString("%1%2:%3") + .arg(o < 0 ? "" : "+") + .arg(QString::number(o / 60), 2, QChar::fromLatin1('0')) + .arg(QString::number(o % 60), 2, QChar::fromLatin1('0')); + }; + + const auto stdName = QString::fromWCharArray(tz.StandardName); + const auto stdOffset = -(tz.Bias + tz.StandardBias); + const auto std = QString("%1, %2") + .arg(stdName) + .arg(offsetString(stdOffset)); + + const auto dstName = QString::fromWCharArray(tz.DaylightName); + const auto dstOffset = -(tz.Bias + tz.DaylightBias); + const auto dst = QString("%1, %2") + .arg(dstName) + .arg(offsetString(dstOffset)); + + QString s; + + if (r == TIME_ZONE_ID_DAYLIGHT) { + s = dst + " (dst is active, std is " + std + ")"; + } else { + s = std + " (std is active, dst is " + dst + ")"; + } + + return s; +} + void Environment::dump(const Settings& s) const { log::debug("windows: {}", windowsInfo().toString()); + log::debug("time zone: {}", timezone()); + if (windowsInfo().compatibilityMode()) { log::warn("MO seems to be running in compatibility mode"); } diff --git a/src/env.h b/src/env.h index f95d1013..f8b1eb70 100644 --- a/src/env.h +++ b/src/env.h @@ -147,6 +147,10 @@ public: // const Metrics& metrics() const; + // timezone + // + QString timezone() const; + // logs the environment // void dump(const Settings& s) const; -- cgit v1.3.1 From 7e1403dd28ec5141e7f4ca3aa6b0bb6e8b0375b0 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 06:18:13 -0500 Subject: simplified security products: don't log guids, remove duplicate entries --- src/env.cpp | 14 ++++++++++++-- src/envsecurity.cpp | 7 ------- 2 files changed, 12 insertions(+), 9 deletions(-) (limited to 'src/env.cpp') diff --git a/src/env.cpp b/src/env.cpp index a23e65a4..b3f9525f 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -157,8 +157,18 @@ void Environment::dump(const Settings& s) const } log::debug("security products:"); - for (const auto& sp : securityProducts()) { - log::debug(" . {}", sp.toString()); + + { + // ignore products with identical names, some AVs register themselves with + // the same names and provider, but different guids + std::set productNames; + for (const auto& sp : securityProducts()) { + productNames.insert(sp.toString()); + } + + for (auto&& name : productNames) { + log::debug(" . {}", name); + } } log::debug("modules loaded in process:"); diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 6d62728b..87db98ce 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -207,13 +207,6 @@ QString SecurityProduct::toString() const s += ", definitions outdated"; } - // all products have a guid, but the windows firewall is not actually a real - // one from wmi, it's queried independently in getWindowsFirewall() and has a - // null guid, so just don't log it - if (!m_guid.isNull()) { - s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); - } - return s; } -- cgit v1.3.1 From 2c2c47c21502db6471fe7d727f942c2400b150c1 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 07:06:20 -0500 Subject: log new modules being loaded after startup --- src/env.cpp | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/env.h | 24 ++++++++++ src/main.cpp | 4 ++ 3 files changed, 175 insertions(+) (limited to 'src/env.cpp') diff --git a/src/env.cpp b/src/env.cpp index b3f9525f..51631607 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -56,6 +56,55 @@ Console::~Console() } +ModuleNotification::ModuleNotification(std::function f) + : m_cookie(nullptr), m_f(std::move(f)) +{ +} + +ModuleNotification::~ModuleNotification() +{ + if (!m_cookie) { + return; + } + + typedef NTSTATUS NTAPI LdrUnregisterDllNotificationType( + PVOID Cookie + ); + + LibraryPtr ntdll(LoadLibraryW(L"ntdll.dll")); + + if (!ntdll) { + log::error("failed to load ntdll.dll while unregistering for module notifications"); + return; + } + + auto* LdrUnregisterDllNotification = reinterpret_cast( + GetProcAddress(ntdll.get(), "LdrUnregisterDllNotification")); + + if (!LdrUnregisterDllNotification) { + log::error("LdrUnregisterDllNotification not found in ntdll.dll"); + return; + } + + const auto r = LdrUnregisterDllNotification(m_cookie); + if (r != 0) { + log::error("failed to unregister for module notifications, error {}", r); + } +} + +void ModuleNotification::setCookie(void* c) +{ + m_cookie = c; +} + +void ModuleNotification::fire(const Module& m) +{ + if (m_f) { + m_f(m); + } +} + + Environment::Environment() { } @@ -146,6 +195,104 @@ QString Environment::timezone() const return s; } +std::unique_ptr Environment::onModuleLoaded( + std::function f) +{ + typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; + } UNICODE_STRING, *PUNICODE_STRING; + + typedef const PUNICODE_STRING PCUNICODE_STRING; + + typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA { + ULONG Flags; //Reserved. + PCUNICODE_STRING FullDllName; //The full path name of the DLL module. + PCUNICODE_STRING BaseDllName; //The base file name of the DLL module. + PVOID DllBase; //A pointer to the base address for the DLL in memory. + ULONG SizeOfImage; //The size of the DLL image, in bytes. + } LDR_DLL_LOADED_NOTIFICATION_DATA, *PLDR_DLL_LOADED_NOTIFICATION_DATA; + + typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA { + ULONG Flags; //Reserved. + PCUNICODE_STRING FullDllName; //The full path name of the DLL module. + PCUNICODE_STRING BaseDllName; //The base file name of the DLL module. + PVOID DllBase; //A pointer to the base address for the DLL in memory. + ULONG SizeOfImage; //The size of the DLL image, in bytes. + } LDR_DLL_UNLOADED_NOTIFICATION_DATA, *PLDR_DLL_UNLOADED_NOTIFICATION_DATA; + + typedef union _LDR_DLL_NOTIFICATION_DATA { + LDR_DLL_LOADED_NOTIFICATION_DATA Loaded; + LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded; + } LDR_DLL_NOTIFICATION_DATA, *PLDR_DLL_NOTIFICATION_DATA; + + typedef VOID CALLBACK LDR_DLL_NOTIFICATION_FUNCTION( + ULONG NotificationReason, + const PLDR_DLL_NOTIFICATION_DATA NotificationData, + PVOID Context + ); + + typedef LDR_DLL_NOTIFICATION_FUNCTION* PLDR_DLL_NOTIFICATION_FUNCTION; + + typedef NTSTATUS NTAPI LdrRegisterDllNotificationType( + ULONG Flags, + PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, + PVOID Context, + PVOID *Cookie + ); + + const ULONG LDR_DLL_NOTIFICATION_REASON_LOADED = 1; + const ULONG LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2; + + + // loading ntdll.dll, the function will be found with GetProcAddress() + LibraryPtr ntdll(LoadLibraryW(L"ntdll.dll")); + + if (!ntdll) { + log::error("failed to load ntdll.dll while registering for module notifications"); + return {}; + } + + auto* LdrRegisterDllNotification = reinterpret_cast( + GetProcAddress(ntdll.get(), "LdrRegisterDllNotification")); + + if (!LdrRegisterDllNotification) { + log::error("LdrRegisterDllNotification not found in ntdll.dll"); + return {}; + } + + + auto context = std::make_unique(f); + void* cookie = nullptr; + + auto OnDllLoaded = [](ULONG reason, const PLDR_DLL_NOTIFICATION_DATA data, void* context) { + if (reason == LDR_DLL_NOTIFICATION_REASON_LOADED) { + const Module m( + QString::fromWCharArray( + data->Loaded.FullDllName->Buffer, + data->Loaded.FullDllName->Length / sizeof(wchar_t)), + data->Loaded.SizeOfImage); + + if (context) { + static_cast(context)->fire(m); + } + } + }; + + const auto r = LdrRegisterDllNotification( + 0, OnDllLoaded, context.get(), &cookie); + + if (r != 0) { + log::error("failed to register for module notifications, error {}", r); + return {}; + } + + context->setCookie(cookie); + + return context; +} + void Environment::dump(const Settings& s) const { log::debug("windows: {}", windowsInfo().toString()); diff --git a/src/env.h b/src/env.h index f8b1eb70..946cb13b 100644 --- a/src/env.h +++ b/src/env.h @@ -119,6 +119,27 @@ private: }; +class ModuleNotification +{ +public: + ModuleNotification(std::function f); + ~ModuleNotification(); + + ModuleNotification(const ModuleNotification&) = delete; + ModuleNotification& operator=(const ModuleNotification&) = delete; + + ModuleNotification(ModuleNotification&&) = default; + ModuleNotification& operator=(ModuleNotification&&) = default; + + void setCookie(void* c); + void fire(const Module& m); + +private: + void* m_cookie; + std::function m_f; +}; + + // represents the process's environment // class Environment @@ -151,6 +172,9 @@ public: // QString timezone() const; + std::unique_ptr onModuleLoaded( + std::function f); + // logs the environment // void dump(const Settings& s) const; diff --git a/src/main.cpp b/src/main.cpp index 41ce0eb5..6e112479 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -547,6 +547,10 @@ int runApplication(MOApplication &application, SingleInstance &instance, settings.dump(); sanityChecks(env); + const auto moduleNotification = env.onModuleLoaded([](auto&& m) { + log::debug("loaded module {}", m.toString()); + }); + log::debug("initializing core"); OrganizerCore organizer(settings); if (!organizer.bootstrap()) { -- cgit v1.3.1 From c3068ce50ab6e25e21452c2ea2f83086ddf555d5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 08:39:22 -0500 Subject: added rivatuner to sanity checks now also checks modules loaded after startup fixed crash on w7 when checking some modules --- src/env.cpp | 44 ++++++++++++++++++++++++++++++-------------- src/env.h | 11 ++++++++--- src/main.cpp | 4 +++- src/sanitychecks.cpp | 37 ++++++++++++++++++++++--------------- 4 files changed, 63 insertions(+), 33 deletions(-) (limited to 'src/env.cpp') diff --git a/src/env.cpp b/src/env.cpp index 51631607..f9507dc1 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -56,8 +56,8 @@ Console::~Console() } -ModuleNotification::ModuleNotification(std::function f) - : m_cookie(nullptr), m_f(std::move(f)) +ModuleNotification::ModuleNotification(QObject* o, std::function f) + : m_cookie(nullptr), m_object(o), m_f(std::move(f)) { } @@ -97,10 +97,26 @@ void ModuleNotification::setCookie(void* c) m_cookie = c; } -void ModuleNotification::fire(const Module& m) +void ModuleNotification::fire(QString path, std::size_t fileSize) { + if (m_loaded.contains(path)) { + // don't notify if it's been loaded before + } + + m_loaded.insert(path); + + // constructing a Module will query the version info of the file, which seems + // to generate an access violation for at least plugin_python.dll on Windows 7 + // + // it's not clear what the problem is, but making sure this is deferred until + // _after_ the dll is loaded seems to fix it + // + // so this queues the callback in the main thread + if (m_f) { - m_f(m); + QMetaObject::invokeMethod(m_object, [path, fileSize, f=m_f] { + f(Module(path, fileSize)); + }, Qt::QueuedConnection); } } @@ -196,7 +212,7 @@ QString Environment::timezone() const } std::unique_ptr Environment::onModuleLoaded( - std::function f) + QObject* o, std::function f) { typedef struct _UNICODE_STRING { USHORT Length; @@ -263,19 +279,19 @@ std::unique_ptr Environment::onModuleLoaded( } - auto context = std::make_unique(f); + auto context = std::make_unique(o, f); void* cookie = nullptr; auto OnDllLoaded = [](ULONG reason, const PLDR_DLL_NOTIFICATION_DATA data, void* context) { if (reason == LDR_DLL_NOTIFICATION_REASON_LOADED) { - const Module m( - QString::fromWCharArray( - data->Loaded.FullDllName->Buffer, - data->Loaded.FullDllName->Length / sizeof(wchar_t)), - data->Loaded.SizeOfImage); - - if (context) { - static_cast(context)->fire(m); + if (data && data->Loaded.FullDllName) { + if (context) { + static_cast(context)->fire( + QString::fromWCharArray( + data->Loaded.FullDllName->Buffer, + data->Loaded.FullDllName->Length / sizeof(wchar_t)), + data->Loaded.SizeOfImage); + } } } }; diff --git a/src/env.h b/src/env.h index 946cb13b..dc0fd864 100644 --- a/src/env.h +++ b/src/env.h @@ -122,7 +122,7 @@ private: class ModuleNotification { public: - ModuleNotification(std::function f); + ModuleNotification(QObject* o, std::function f); ~ModuleNotification(); ModuleNotification(const ModuleNotification&) = delete; @@ -132,10 +132,12 @@ public: ModuleNotification& operator=(ModuleNotification&&) = default; void setCookie(void* c); - void fire(const Module& m); + void fire(QString path, std::size_t fileSize); private: void* m_cookie; + QObject* m_object; + std::set m_loaded; std::function m_f; }; @@ -172,8 +174,11 @@ public: // QString timezone() const; + // will call `f` on the same thread `o` is running on every time a module + // is loaded in the process + // std::unique_ptr onModuleLoaded( - std::function f); + QObject* o, std::function f); // logs the environment // diff --git a/src/main.cpp b/src/main.cpp index 6e112479..3cccf365 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -93,6 +93,7 @@ using namespace MOShared; void sanityChecks(const env::Environment& env); +int checkIncompatibleModule(const env::Module& m); bool createAndMakeWritable(const std::wstring &subPath) { QString const dataPath = qApp->property("dataPath").toString(); @@ -547,8 +548,9 @@ int runApplication(MOApplication &application, SingleInstance &instance, settings.dump(); sanityChecks(env); - const auto moduleNotification = env.onModuleLoaded([](auto&& m) { + const auto moduleNotification = env.onModuleLoaded(qApp, [](auto&& m) { log::debug("loaded module {}", m.toString()); + checkIncompatibleModule(m); }); log::debug("initializing core"); diff --git a/src/sanitychecks.cpp b/src/sanitychecks.cpp index 3b4185a7..495795f2 100644 --- a/src/sanitychecks.cpp +++ b/src/sanitychecks.cpp @@ -202,29 +202,36 @@ int checkMissingFiles() return n; } -bool checkNahimic(const env::Environment& e) +int checkIncompatibleModule(const env::Module& m) { - // Nahimic seems to interfere mostly with dialogs, like the mod info dialog: - // it renders dialogs fully white and makes it impossible to interact with - // them + // these dlls seems to interfere mostly with dialogs, like the mod info + // dialog: it renders dialogs fully white and makes it impossible to interact + // with them // - // NahimicOSD.dll is usually loaded on startup, but there has been some - // reports where it got loaded later, so this check is not entirely accurate + // the dlls is usually loaded on startup, but there has been some reports + // where it got loaded later, so this is also called every time a new module + // is loaded into this process - for (auto&& m : e.loadedModules()) { - const QFileInfo file(m.path()); + static const std::map names = { + {"NahimicOSD.dll", "Nahimic"}, + {"RTSSHooks64.dll", "RivaTuner Statistics Server"} + }; + + const QFileInfo file(m.path()); + int n = 0; - if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive) == 0) { + for (auto&& p : names) { + if (file.fileName().compare(p.first, Qt::CaseInsensitive) == 0) { log::warn( - "NahimicOSD.dll is loaded. Nahimic is known to cause issues with " + "{} is loaded. This program is known to cause issues with " "Mod Organizer, such as freezing or blank windows. Consider " - "uninstalling it."); + "uninstalling it. ({})", p.second, file.absoluteFilePath()); - return true; + ++n; } } - return false; + return n; } int checkIncompatibilities(const env::Environment& e) @@ -233,8 +240,8 @@ int checkIncompatibilities(const env::Environment& e) int n = 0; - if (checkNahimic(e)) { - ++n; + for (auto&& m : e.loadedModules()) { + n += checkIncompatibleModule(m); } return n; -- cgit v1.3.1 From d4172dc5f8c642dbbe235a86a28992af310e703a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Dec 2019 13:51:46 -0500 Subject: added "open with vfs" option to conflicts tab --- src/env.cpp | 157 +++++++++++++++++++++++++++++++++++++++++ src/env.h | 20 ++++++ src/modinfodialog.cpp | 23 +++++- src/modinfodialogconflicts.cpp | 39 +++++++++- src/modinfodialogconflicts.h | 2 + src/modinfodialogfwd.h | 1 + src/processrunner.cpp | 20 +++++- src/processrunner.h | 12 ++-- 8 files changed, 263 insertions(+), 11 deletions(-) (limited to 'src/env.cpp') diff --git a/src/env.cpp b/src/env.cpp index f9507dc1..0098456e 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -675,6 +675,163 @@ Service getService(const QString& name) } +std::optional getAssocString(const QFileInfo& file, ASSOCSTR astr) +{ + const auto ext = L"." + file.suffix().toStdWString(); + + // getting buffer size + DWORD bufferSize = 0; + auto r = AssocQueryStringW( + ASSOCF_INIT_IGNOREUNKNOWN, astr, ext.c_str(), L"open", nullptr, &bufferSize); + + // returns S_FALSE when giving back the buffer size, so that's actually the + // expected return value + + if (r != S_FALSE || bufferSize == 0) { + if (r == HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION)) { + log::error("file '{}' has no associated executable", file.absoluteFilePath()); + } else { + log::error( + "can't get buffer size for AssocQueryStringW(), {}", + formatSystemMessage(r)); + } + return {}; + } + + // getting string + auto buffer = std::make_unique(bufferSize + 1); + std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); + + r = AssocQueryStringW( + ASSOCF_INIT_IGNOREUNKNOWN, astr, ext.c_str(), L"open", buffer.get(), &bufferSize); + + if (FAILED(r)) { + log::error( + "failed to get exe associated with '{}', {}", + file.suffix(), formatSystemMessage(r)); + + return {}; + } + + // buffer size includes the null terminator + return QString::fromWCharArray(buffer.get(), bufferSize - 1); +} + +QString formatCommandLine(const QFileInfo& targetInfo, const QString& cmd) +{ + // yeah, FormatMessage() expects at least as many arguments as there are + // placeholders and while the command for associations should typically only + // have %1, the user can actually enter anything in the registry + // + // since the maximum number of arguments is 99, this creates an array of 99 + // wchar_* where the first one (%1) points to the filename and the remaining + // 98 to "" + // + // FormatMessage() actually takes a va_list* for the arguments, but by passing + // FORMAT_MESSAGE_ARGUMENT_ARRAY, an array of DWORD_PTR can be given instead + + // 99 arguments + std::array args; + + // first one is the filename + const auto wpath = targetInfo.absoluteFilePath().toStdWString(); + args[0] = reinterpret_cast(wpath.c_str()); + + // remaining are "" + std::fill(args.begin() + 1, args.end(), reinterpret_cast(L"")); + + // must be freed with LocalFree() + wchar_t* buffer = nullptr; + + const auto wcmd = cmd.toStdWString(); + + const auto n = ::FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_FROM_STRING, + wcmd.c_str(), 0, 0, + reinterpret_cast(&buffer), + 0, reinterpret_cast(&args[0])); + + if (n == 0 || !buffer){ + const auto e = GetLastError(); + + log::error( + "failed to format command line '{}' with path '{}', {}", + cmd, targetInfo.absoluteFilePath(), formatSystemMessage(e)); + + return {}; + } + + auto s = QString::fromWCharArray(buffer, n); + ::LocalFree(buffer); + + return s.trimmed(); +} + +std::pair splitExeAndArguments(const QString& cmd) +{ + int exeBegin = 0; + int exeEnd = -1; + + if (cmd[0] == '"'){ + // surrounded by double-quotes, so find the next one + exeBegin = 1; + exeEnd = cmd.indexOf('"', exeBegin); + + if (exeEnd == -1) { + log::error("missing terminating double-quote in command line '{}'", cmd); + return {}; + } + } else { + // no double-quotes, find the first whitespace + exeEnd = cmd.indexOf(QRegExp("\\s")); + if (exeEnd == -1) { + exeEnd = cmd.size(); + } + } + + QString exe = cmd.mid(exeBegin, exeEnd - exeBegin).trimmed(); + QString args = cmd.mid(exeEnd + 1).trimmed(); + + return {std::move(exe), std::move(args)}; +} + +Association getAssociation(const QFileInfo& targetInfo) +{ + log::debug( + "getting association for '{}', extension is '.{}'", + targetInfo.absoluteFilePath(), targetInfo.suffix()); + + const auto cmd = getAssocString(targetInfo, ASSOCSTR_COMMAND); + if (!cmd) { + return {}; + } + + log::debug("raw cmd is '{}'", *cmd); + + QString formattedCmd = formatCommandLine(targetInfo, *cmd); + if (formattedCmd.isEmpty()) { + log::error( + "command line associated with '{}' is empty", + targetInfo.absoluteFilePath()); + + return {}; + } + + log::debug("formatted cmd is '{}'", formattedCmd); + + const auto p = splitExeAndArguments(formattedCmd); + if (p.first.isEmpty()) { + return {}; + } + + log::debug("split into exe='{}' and cmd='{}'", p.first, p.second); + + return {p.first, *cmd, p.second}; +} + + // returns the filename of the given process or the current one // std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) diff --git a/src/env.h b/src/env.h index dc0fd864..16f8039e 100644 --- a/src/env.h +++ b/src/env.h @@ -246,6 +246,26 @@ Service getService(const QString& name); QString toString(Service::StartType st); QString toString(Service::Status st); + +struct Association +{ + // path to the executable associated with the file + QFileInfo executable; + + // full command line associated with the file, no replacements + QString commandLine; + + // command line _without_ the executable and with placeholders such as %1 + // replaced by the given file + QString formattedCommandLine; +}; + +// returns the associated executable and command line, executable is empty on +// error +// +Association getAssociation(const QFileInfo& file); + + enum class CoreDumpTypes { Mini = 1, diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index c7e071ad..f5ca1de7 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -61,10 +61,27 @@ bool canPreviewFile( return pluginContainer.previewGenerator().previewSupported(ext); } -bool canOpenFile(bool isArchive, const QString&) +bool isExecutableFilename(const QString& filename) { - // can open anything as long as it's not in an archive - return !isArchive; + static const std::set exeExtensions = { + "exe", "cmd", "bat" + }; + + const auto ext = QFileInfo(filename).suffix().toLower(); + + return exeExtensions.contains(ext); +} + +bool canRunFile(bool isArchive, const QString& filename) +{ + // can run executables that are not archives + return !isArchive && isExecutableFilename(filename); +} + +bool canOpenFile(bool isArchive, const QString& filename) +{ + // can open non-executables that are not archives + return !isArchive && !isExecutableFilename(filename); } bool canExploreFile(bool isArchive, const QString&) diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index d37f068c..58e935f2 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -81,6 +81,11 @@ public: return canUnhideFile(isArchive(), fileName()); } + bool canRun() const + { + return canRunFile(isArchive(), fileName()); + } + bool canOpen() const { return canOpenFile(isArchive(), fileName()); @@ -536,6 +541,20 @@ void ConflictsTab::openItems(QTreeView* tree) }); } +void ConflictsTab::runItemsHooked(QTreeView* tree) +{ + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for_each_in_selection(tree, [&](const ConflictItem* item) { + core().processRunner() + .setFromFile(parentWidget(), item->fileName(), true) + .setWaitForCompletion() + .run(); + + return true; + }); +} + void ConflictsTab::previewItems(QTreeView* tree) { // the menu item is only shown for a single selection, but handle all of them @@ -571,6 +590,15 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addAction(actions.open); } + // run hooked + if (actions.runHooked) { + connect(actions.runHooked, &QAction::triggered, [&]{ + runItemsHooked(tree); + }); + + menu.addAction(actions.runHooked); + } + // preview if (actions.preview) { connect(actions.preview, &QAction::triggered, [&]{ @@ -633,6 +661,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) bool enableHide = true; bool enableUnhide = true; + bool enableRun = true; bool enableOpen = true; bool enablePreview = true; bool enableExplore = true; @@ -657,6 +686,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) enableHide = item->canHide(); enableUnhide = item->canUnhide(); + enableRun = item->canRun(); enableOpen = item->canOpen(); enablePreview = item->canPreview(plugin()); enableExplore = item->canExplore(); @@ -665,6 +695,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) else { // this is a multiple selection, don't show open/preview so users don't open // a thousand files + enableRun = false; enableOpen = false; enablePreview = false; @@ -709,8 +740,12 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) actions.unhide = new QAction(tr("&Unhide"), parentWidget()); actions.unhide->setEnabled(enableUnhide); - actions.open = new QAction(tr("&Open/Execute"), parentWidget()); - actions.open->setEnabled(enableOpen); + if (enableRun) { + actions.open = new QAction(tr("&Execute"), parentWidget()); + } else if (enableOpen) { + actions.open = new QAction(tr("&Open"), parentWidget()); + actions.runHooked = new QAction(tr("Open with &VFS"), parentWidget()); + } actions.preview = new QAction(tr("&Preview"), parentWidget()); actions.preview->setEnabled(enablePreview); diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index ad305dfc..ebf82033 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -112,6 +112,7 @@ public: bool canHandleUnmanaged() const override; void openItems(QTreeView* tree); + void runItemsHooked(QTreeView* tree); void previewItems(QTreeView* tree); void exploreItems(QTreeView* tree); @@ -125,6 +126,7 @@ private: QAction* hide = nullptr; QAction* unhide = nullptr; QAction* open = nullptr; + QAction* runHooked = nullptr; QAction* preview = nullptr; QAction* explore = nullptr; QMenu* gotoMenu = nullptr; diff --git a/src/modinfodialogfwd.h b/src/modinfodialogfwd.h index 9ede766f..2147fc04 100644 --- a/src/modinfodialogfwd.h +++ b/src/modinfodialogfwd.h @@ -23,6 +23,7 @@ enum class ModInfoTabIDs class PluginContainer; bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); +bool canRunFile(bool isArchive, const QString& filename); bool canOpenFile(bool isArchive, const QString& filename); bool canExploreFile(bool isArchive, const QString& filename); bool canHideFile(bool isArchive, const QString& filename); diff --git a/src/processrunner.cpp b/src/processrunner.cpp index b6167706..46065d69 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -3,6 +3,8 @@ #include "instancemanager.h" #include "iuserinterface.h" #include "envmodule.h" +#include "env.h" +#include #include using namespace MOBase; @@ -473,13 +475,14 @@ ProcessRunner& ProcessRunner::setWaitForCompletion( return *this; } -ProcessRunner& ProcessRunner::setFromFile(QWidget* parent, const QFileInfo& targetInfo) +ProcessRunner& ProcessRunner::setFromFile( + QWidget* parent, const QFileInfo& targetInfo, bool forceHook) { if (!parent && m_ui) { parent = m_ui->qtWidget(); } - // if the file is a .exe, start it directory; if it's anything else, ask the + // if the file is a .exe, start it directly; if it's anything else, ask the // shell to start it const auto fec = spawn::getFileExecutionContext(parent, targetInfo); @@ -497,6 +500,19 @@ ProcessRunner& ProcessRunner::setFromFile(QWidget* parent, const QFileInfo& targ case spawn::FileExecutionTypes::Other: // fall-through default: { + if (forceHook) { + auto assoc = env::getAssociation(targetInfo); + if (!assoc.executable.filePath().isEmpty()) { + setBinary(assoc.executable); + setArguments(assoc.formattedCommandLine); + setCurrentDirectory(assoc.executable.absoluteDir()); + + return *this; + } + + // if it fails, just use the regular shell open + } + m_shellOpen = targetInfo.absoluteFilePath(); // picked up by postRun() diff --git a/src/processrunner.h b/src/processrunner.h index a5099136..d576216a 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -68,10 +68,14 @@ public: ProcessRunner& setWaitForCompletion( WaitFlags flags=NoFlags, UILocker::Reasons reason=UILocker::LockUI); - // if the target is an executable file, runs that; for anything else, calls - // ShellExecute() on it - // - ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo); + // - if the target is an executable file, runs it hooked + // - if the target is a file: + // - if forceHook is false, calls ShellExecute() on it + // - if forceHook is true, gets the executable associated with the file + // and runs that hooked by passing the file as an argument + // + ProcessRunner& setFromFile( + QWidget* parent, const QFileInfo& targetInfo, bool forceHook = false); ProcessRunner& setFromExecutable(const Executable& exe); ProcessRunner& setFromShortcut(const MOShortcut& shortcut); -- cgit v1.3.1 From 52cc3a41c73851d88983223361c03b800db8c2a2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 29 Dec 2019 07:16:09 -0500 Subject: refresh after manually unlocking the ui fixed nexus connect button staying as "cancel" in case of errors fixed logging of duplicate dll loading bumped to rc6 --- src/env.cpp | 1 + src/nxmaccessmanager.cpp | 2 +- src/organizer_en.ts | 6 +++--- src/processrunner.cpp | 56 +++++++++++++++++++++++++++++++++++++----------- src/processrunner.h | 1 + src/version.rc | 2 +- 6 files changed, 50 insertions(+), 18 deletions(-) (limited to 'src/env.cpp') diff --git a/src/env.cpp b/src/env.cpp index 0098456e..4c0aeb86 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -101,6 +101,7 @@ void ModuleNotification::fire(QString path, std::size_t fileSize) { if (m_loaded.contains(path)) { // don't notify if it's been loaded before + return; } m_loaded.insert(path); diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index e5f1fffe..20540593 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -333,8 +333,8 @@ void NexusSSOLogin::onDisconnected() void NexusSSOLogin::onError(QAbstractSocket::SocketError e) { if (m_active) { - setState(Error, m_socket.errorString()); close(); + setState(Error, m_socket.errorString()); } } diff --git a/src/organizer_en.ts b/src/organizer_en.ts index d05ada70..fe4dfd32 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -6317,9 +6317,9 @@ If the folder was still in use, restart MO and try again. - - - + + + No profile set diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 3f1e3a3b..91443750 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -795,6 +795,48 @@ std::optional ProcessRunner::runBinary() return {}; } +bool ProcessRunner::shouldRefresh(Results r) const +{ + // afterRun() is only called with the Refresh flag; it refreshes the + // directory structure and notifies plugins + // + // refreshing is not always required and can actually cause problems: + // + // 1) running shortcuts doesn't need refreshing because MO closes right + // after + // + // 2) the mod info dialog is not set up to deal with refreshes, so that + // it will crash because the old DirectoryEntry's are still being used + // in the list + if (!m_waitFlags.testFlag(Refresh)) { + log::debug("not refreshing because the flag isn't set"); + return false; + } + + switch (r) + { + case Completed: + { + log::debug("refreshing because the process completed"); + return true; + } + + case ForceUnlocked: + { + log::debug("refreshing because the ui was force unlocked"); + return true; + } + + case Error: // fall-through + case Cancelled: + case Running: + default: + { + return false; + } + } +} + ProcessRunner::Results ProcessRunner::postRun() { const bool mustWait = (m_waitFlags & ForceWait); @@ -841,19 +883,7 @@ ProcessRunner::Results ProcessRunner::postRun() r = waitForProcess(m_handle.get(), &m_exitCode, ls); }); - if (r == Completed && (m_waitFlags & Refresh)) { - // afterRun() is only called with the Refresh flag; it refreshes the - // directory structure and notifies plugins - // - // refreshing is not always required and can actually cause problems: - // - // 1) running shortcuts doesn't need refreshing because MO closes right - // after - // - // 2) the mod info dialog is not set up to deal with refreshes, so that - // it will crash because the old DirectoryEntry's are still being used - // in the list - // + if (shouldRefresh(r)) { m_core.afterRun(m_sp.binary, m_exitCode); } diff --git a/src/processrunner.h b/src/processrunner.h index 1bfdc465..d8ff0227 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -156,6 +156,7 @@ private: bool shouldRunShell() const; + bool shouldRefresh(Results r) const; // runs the command in m_shellOpen; returns empty if it can be waited for // diff --git a/src/version.rc b/src/version.rc index 0cf44243..f7b5f0d5 100644 --- a/src/version.rc +++ b/src/version.rc @@ -4,7 +4,7 @@ // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha #define VER_FILEVERSION 2,2,2 -#define VER_FILEVERSION_STR "2.2.2rc5\0" +#define VER_FILEVERSION_STR "2.2.2rc6\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1