From e08e605c85a1f62f4b6b83f5404457f5dc55654a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> 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 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) (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()); } -- 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