aboutsummaryrefslogtreecommitdiff
path: root/src/src/sanitychecks.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/src/sanitychecks.cpp')
-rw-r--r--src/src/sanitychecks.cpp588
1 files changed, 101 insertions, 487 deletions
diff --git a/src/src/sanitychecks.cpp b/src/src/sanitychecks.cpp
index 77ab759..b18ea43 100644
--- a/src/src/sanitychecks.cpp
+++ b/src/src/sanitychecks.cpp
@@ -1,487 +1,101 @@
-#include "sanitychecks.h"
-#include "env.h"
-#include "envmodule.h"
-#include "settings.h"
-#include <iplugingame.h>
-#include <log.h>
-#include <utility.h>
-
-namespace sanity
-{
-
-using namespace MOBase;
-
-#ifdef _WIN32
-
-enum class SecurityZone
-{
- NoZone = -1,
- MyComputer = 0,
- Intranet = 1,
- Trusted = 2,
- Internet = 3,
- Untrusted = 4,
-};
-
-QString toCodeName(SecurityZone z)
-{
- switch (z) {
- case SecurityZone::NoZone:
- return "NoZone";
- case SecurityZone::MyComputer:
- return "MyComputer";
- case SecurityZone::Intranet:
- return "Intranet";
- case SecurityZone::Trusted:
- return "Trusted";
- case SecurityZone::Internet:
- return "Internet";
- case SecurityZone::Untrusted:
- return "Untrusted";
- default:
- return "Unknown zone";
- }
-}
-
-QString toString(SecurityZone z)
-{
- return QString("%1 (%2)").arg(toCodeName(z)).arg(static_cast<int>(z));
-}
-
-// whether the given zone is considered blocked
-//
-bool isZoneBlocked(SecurityZone z)
-{
- switch (z) {
- case SecurityZone::Internet:
- case SecurityZone::Untrusted:
- return true;
-
- case SecurityZone::NoZone:
- case SecurityZone::MyComputer:
- case SecurityZone::Intranet:
- case SecurityZone::Trusted:
- default:
- return false;
- }
-}
-
-// whether the given file is blocked
-//
-bool isFileBlocked(const QFileInfo& fi)
-{
- // name of the alternate data stream containing the zone identifier ini
- const QString ads = "Zone.Identifier";
-
- // key in the ini
- const auto key = "ZoneTransfer/ZoneId";
-
- // the path to the ADS is always `filename:Zone.Identifier`
- const auto path = fi.absoluteFilePath();
- const auto adsPath = path + ":" + ads;
-
- QFile f(adsPath);
- if (!f.exists()) {
- // no ADS for this file
- return false;
- }
-
- log::debug("'{}' has an ADS for {}", path, adsPath);
-
- const QSettings qs(adsPath, QSettings::IniFormat);
-
- // looking for key
- if (!qs.contains(key)) {
- log::debug("'{}': key '{}' not found", adsPath, key);
- return false;
- }
-
- // getting value
- const auto v = qs.value(key);
- if (v.isNull()) {
- log::debug("'{}': key '{}' is null", adsPath, key);
- return false;
- }
-
- // should be an int
- bool ok = false;
- const auto z = static_cast<SecurityZone>(v.toInt(&ok));
-
- if (!ok) {
- log::debug("'{}': key '{}' is not an int (value is '{}')", adsPath, key, v);
- return false;
- }
-
- if (!isZoneBlocked(z)) {
- // that zone is not a blocked zone
- log::debug("'{}': zone id is {}, which is fine", adsPath, toString(z));
- return false;
- }
-
- // file is blocked
- log::warn("{}", QObject::tr("'%1': file is blocked (%2)").arg(path).arg(toString(z)));
-
- return true;
-}
-
-int checkBlockedFiles(const QDir& dir)
-{
- // executables file types
- const QStringList FileTypes = {"*.dll", "*.exe"};
-
- if (!dir.exists()) {
- // shouldn't happen
- log::error("while checking for blocked files, directory '{}' not found",
- dir.absolutePath());
-
- return 1;
- }
-
- const auto files = dir.entryInfoList(FileTypes, QDir::Files);
- if (files.empty()) {
- // shouldn't happen
- log::error("while checking for blocked files, directory '{}' is empty",
- dir.absolutePath());
-
- return 1;
- }
-
- int n = 0;
-
- // checking each file in this directory
- for (auto&& fi : files) {
- if (isFileBlocked(fi)) {
- ++n;
- }
- }
-
- return n;
-}
-
-int checkBlocked()
-{
- // directories that contain executables; these need to be explicit because
- // portable instances might add billions of files in MO's directory
- const QString dirs[] = {".", "/dlls", "/loot", "/NCC", "/platforms", "/plugins"};
-
- log::debug(" . blocked files");
- const QString appDir = QCoreApplication::applicationDirPath();
-
- int n = 0;
-
- for (const auto& d : dirs) {
- const auto path = QDir(appDir + "/" + d).canonicalPath();
- n += checkBlockedFiles(path);
- }
-
- return n;
-}
-
-int checkMissingFiles()
-{
- // files that are likely to be eaten
-#ifdef _WIN32
- static const QStringList files(
- {"helper.exe", "nxmhandler.exe", "usvfs_proxy_x64.exe", "usvfs_proxy_x86.exe",
- "usvfs_x64.dll", "usvfs_x86.dll", "loot/loot.dll", "loot/lootcli.exe"});
-#else
- static const QStringList files({});
-#endif
-
- log::debug(" . missing files");
- const auto dir = QCoreApplication::applicationDirPath();
-
- int n = 0;
-
- for (const auto& name : files) {
- const QFileInfo file(dir + "/" + name);
-
- if (!file.exists()) {
- log::warn("{}", QObject::tr(
- "'%1' seems to be missing, an antivirus may have deleted it")
- .arg(file.absoluteFilePath()));
-
- ++n;
- }
- }
-
- return n;
-}
-
-int checkBadOSDs(const env::Module& m)
-{
- // these dlls seem to interfere mostly with dialogs, like the mod info
- // dialog: they render some dialogs fully white and make it impossible to
- // interact with them
- //
- // the dlls is usually loaded on startup, but there has been some reports
- // where they got loaded later, so this is also called every time a new module
- // is loaded into this process
-
- const std::string nahimic = "Nahimic (also known as SonicSuite, SonicRadar, "
- "SteelSeries, A-Volute, etc.)";
-
- auto p = [](std::string re, std::string s) {
- return std::make_pair(std::regex(re, std::regex::icase), s);
- };
-
- static const std::vector<std::pair<std::regex, std::string>> list = {
- p("nahimic(.*)osd\\.dll", nahimic),
- p("cassini(.*)osd\\.dll", nahimic),
- p(".+devprops.*.dll", nahimic),
- p("ss2osd\\.dll", nahimic),
- p("RTSSHooks64\\.dll", "RivaTuner Statistics Server"),
- p("SSAudioOSD\\.dll", "SteelSeries Audio"),
- p("specialk64\\.dll", "SpecialK"),
- p("corsairosdhook\\.x64\\.dll", "Corsair Utility Engine"),
- p("gtii-osd64-vk\\.dll", "ASUS GPU Tweak 2"),
- p("easyhook64\\.dll", "Razer Cortex"),
- p("k_fps64\\.dll", "Razer Cortex"),
- p("fw1fontwrapper\\.dll", "Gigabyte 3D OSD"),
- p("gfxhook64\\.dll", "Gigabyte 3D OSD")};
-
- const QFileInfo file(m.path());
- int n = 0;
-
- for (auto&& p : list) {
- std::smatch m;
- const auto filename = file.fileName().toStdString();
-
- if (std::regex_match(filename, m, p.first)) {
- log::warn("{}", QObject::tr(
- "%1 is loaded.\nThis program is known to cause issues with "
- "Mod Organizer, such as freezing or blank windows. Consider "
- "uninstalling it.")
- .arg(QString::fromStdString(p.second)));
-
- log::warn("{}", file.absoluteFilePath());
- ++n;
- }
- }
-
- return n;
-}
-
-int checkUsvfsIncompatibilites(const env::Module& m)
-{
- // these dlls seems to interfere with usvfs
-
- static const std::map<QString, QString> names = {
- {"mactype64.dll", "Mactype"}, {"epclient64.dll", "Citrix ICA Client"}};
-
- const QFileInfo file(m.path());
- int n = 0;
-
- for (auto&& p : names) {
- if (file.fileName().compare(p.first, Qt::CaseInsensitive) == 0) {
- log::warn(
- "{}",
- QObject::tr("%1 is loaded. This program is known to cause issues with "
- "Mod Organizer and its virtual filesystem, such script extenders "
- "or others programs refusing to run. Consider uninstalling it.")
- .arg(p.second));
-
- log::warn("{}", file.absoluteFilePath());
-
- ++n;
- }
- }
-
- return n;
-}
-
-int checkIncompatibleModule(const env::Module& m)
-{
- int n = 0;
-
- n += checkBadOSDs(m);
- n += checkUsvfsIncompatibilites(m);
-
- return n;
-}
-
-int checkIncompatibilities(const env::Environment& e)
-{
- log::debug(" . incompatibilities");
-
- int n = 0;
-
- for (auto&& m : e.loadedModules()) {
- n += checkIncompatibleModule(m);
- }
-
- return n;
-}
-
-std::vector<std::pair<QString, QString>> getSystemDirectories()
-{
- // folder ids and display names for logging
- const std::vector<std::pair<GUID, QString>> systemFolderIDs = {
- {FOLDERID_ProgramFiles, "in Program Files"},
- {FOLDERID_ProgramFilesX86, "in Program Files"},
- {FOLDERID_Desktop, "on the desktop"},
- {FOLDERID_OneDrive, "in OneDrive"},
- {FOLDERID_Documents, "in Documents"},
- {FOLDERID_Downloads, "in Downloads"}};
-
- std::vector<std::pair<QString, QString>> systemDirs;
-
- for (auto&& p : systemFolderIDs) {
- const auto dir = MOBase::getOptionalKnownFolder(p.first);
-
- if (!dir.isEmpty()) {
- auto path = QDir::toNativeSeparators(dir).toLower();
- if (!path.endsWith("\\")) {
- path += "\\";
- }
-
- systemDirs.push_back({path, p.second});
- }
- }
-
- return systemDirs;
-}
-
-int checkProtected(const QDir& d, const QString& what)
-{
- static const auto systemDirs = getSystemDirectories();
-
- const auto path = QDir::toNativeSeparators(d.absolutePath()).toLower();
-
- log::debug(" . {}: {}", what, path);
-
- for (auto&& sd : systemDirs) {
- if (path.startsWith(sd.first)) {
- log::warn("{} is {}; this may cause issues because it's a special "
- "system folder",
- what, sd.second);
-
- log::debug("path '{}' starts with '{}'", path, sd.first);
-
- return 1;
- }
- }
-
- return 0;
-}
-
-int checkMicrosoftStore(const QDir& gameDir)
-{
- const QStringList pathsToCheck = {
- "/ModifiableWindowsApps/",
- "/WindowsApps/",
- };
- for (auto badPath : pathsToCheck) {
- if (gameDir.path().contains(badPath)) {
- log::error("This game is not supported by Mod Organizer.");
- log::error("Games installed through the Microsoft Store will not work properly.");
- return 1;
- }
- }
-
- return 0;
-}
-
-#else // Linux
-
-int checkMissingFiles()
-{
- log::debug(" . checking Linux dependencies");
- int n = 0;
-
- // Check for FUSE (check both unversioned .so and versioned .so.3, since
- // on Debian/Ubuntu the unversioned symlink is only in libfuse3-dev)
- {
- static const char* fusePaths[] = {
- "/usr/lib/libfuse3.so",
- "/usr/lib/libfuse3.so.3",
- "/usr/lib64/libfuse3.so",
- "/usr/lib64/libfuse3.so.3",
- "/usr/lib/x86_64-linux-gnu/libfuse3.so",
- "/usr/lib/x86_64-linux-gnu/libfuse3.so.3",
- nullptr
- };
- bool fuseFound = false;
- for (int i = 0; fusePaths[i]; ++i) {
- if (QFileInfo::exists(fusePaths[i])) {
- fuseFound = true;
- break;
- }
- }
- if (!fuseFound) {
- log::warn("libfuse3 not found - FUSE VFS will not work");
- ++n;
- }
- }
-
- return n;
-}
-
-int checkIncompatibleModule(const env::Module& /*m*/)
-{
- // No known incompatible modules on Linux
- return 0;
-}
-
-int checkProtected(const QDir& d, const QString& what)
-{
- const auto path = d.absolutePath();
- log::debug(" . {}: {}", what, path);
-
- // Check if running from system directories
- if (path.startsWith("/root") || path.startsWith("/usr") || path.startsWith("/bin")) {
- log::warn("{} is in a system directory; this may cause permission issues", what);
- return 1;
- }
-
- return 0;
-}
-
-#endif // _WIN32
-
-int checkPaths(IPluginGame& game, const Settings& s)
-{
- log::debug("checking paths");
-
- int n = 0;
-
- n += checkProtected(game.gameDirectory(), "the game");
-#ifdef _WIN32
- n += checkMicrosoftStore(game.gameDirectory());
-#endif
- n += checkProtected(QApplication::applicationDirPath(), "Mod Organizer");
-
- if (checkProtected(s.paths().base(), "the instance base directory")) {
- ++n;
- } else {
- n += checkProtected(s.paths().downloads(), "the downloads directory");
- n += checkProtected(s.paths().mods(), "the mods directory");
- n += checkProtected(s.paths().cache(), "the cache directory");
- n += checkProtected(s.paths().profiles(), "the profiles directory");
- n += checkProtected(s.paths().overwrite(), "the overwrite directory");
- }
-
- return n;
-}
-
-void checkEnvironment(const env::Environment& e)
-{
- log::debug("running sanity checks...");
-
- int n = 0;
-
-#ifdef _WIN32
- n += checkBlocked();
-#endif
- n += checkMissingFiles();
-#ifdef _WIN32
- n += checkIncompatibilities(e);
-#else
- Q_UNUSED(e);
-#endif
-
- log::debug("sanity checks done, {}",
- (n > 0 ? "problems were found" : "everything looks okay"));
-}
-
-} // namespace sanity
+#include "sanitychecks.h"
+#include "env.h"
+#include "envmodule.h"
+#include "settings.h"
+
+#include <iplugingame.h>
+#include <log.h>
+#include <utility.h>
+
+namespace sanity
+{
+
+using namespace MOBase;
+
+int checkMissingFiles()
+{
+ log::debug(" . checking Linux dependencies");
+ int n = 0;
+
+ // Check for FUSE (check both unversioned .so and versioned .so.3 — on
+ // Debian/Ubuntu the unversioned symlink is only in libfuse3-dev).
+ static const char* fusePaths[] = {
+ "/usr/lib/libfuse3.so",
+ "/usr/lib/libfuse3.so.3",
+ "/usr/lib64/libfuse3.so",
+ "/usr/lib64/libfuse3.so.3",
+ "/usr/lib/x86_64-linux-gnu/libfuse3.so",
+ "/usr/lib/x86_64-linux-gnu/libfuse3.so.3",
+ nullptr};
+
+ bool fuseFound = false;
+ for (int i = 0; fusePaths[i]; ++i) {
+ if (QFileInfo::exists(fusePaths[i])) {
+ fuseFound = true;
+ break;
+ }
+ }
+ if (!fuseFound) {
+ log::warn("libfuse3 not found - FUSE VFS will not work");
+ ++n;
+ }
+
+ return n;
+}
+
+int checkIncompatibleModule(const env::Module& /*m*/)
+{
+ // No Wine/Linux equivalent of the Win32 OSD/usvfs incompatibility list.
+ return 0;
+}
+
+int checkProtected(const QDir& d, const QString& what)
+{
+ const auto path = d.absolutePath();
+ log::debug(" . {}: {}", what, path);
+
+ // Warn if running from a system-owned directory.
+ if (path.startsWith("/root") || path.startsWith("/usr") || path.startsWith("/bin")) {
+ log::warn("{} is in a system directory; this may cause permission issues", what);
+ return 1;
+ }
+
+ return 0;
+}
+
+int checkPaths(IPluginGame& game, const Settings& s)
+{
+ log::debug("checking paths");
+
+ int n = 0;
+
+ n += checkProtected(game.gameDirectory(), "the game");
+ n += checkProtected(QApplication::applicationDirPath(), "Mod Organizer");
+
+ if (checkProtected(s.paths().base(), "the instance base directory")) {
+ ++n;
+ } else {
+ n += checkProtected(s.paths().downloads(), "the downloads directory");
+ n += checkProtected(s.paths().mods(), "the mods directory");
+ n += checkProtected(s.paths().cache(), "the cache directory");
+ n += checkProtected(s.paths().profiles(), "the profiles directory");
+ n += checkProtected(s.paths().overwrite(), "the overwrite directory");
+ }
+
+ return n;
+}
+
+void checkEnvironment(const env::Environment& e)
+{
+ log::debug("running sanity checks...");
+
+ int n = 0;
+
+ n += checkMissingFiles();
+ Q_UNUSED(e);
+
+ log::debug("sanity checks done, {}",
+ (n > 0 ? "problems were found" : "everything looks okay"));
+}
+
+} // namespace sanity