#include "env.h" #include "envmodule.h" #include "settings.h" #include #include #include using namespace MOBase; 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(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(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("'{}': file is blocked, zone id is {}", path, 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 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" }); 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( "'{}' seems to be missing, an antivirus may have deleted it", file.absoluteFilePath()); ++n; } } return n; } int checkIncompatibleModule(const env::Module& m) { // 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 // // 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 static const std::map names = { {"NahimicOSD.dll", "Nahimic"}, {"RTSSHooks64.dll", "RivaTuner Statistics Server"}, {"SSAudioOSD.dll", "SteelSeries Audio"} }; const QFileInfo file(m.path()); int n = 0; for (auto&& p : names) { if (file.fileName().compare(p.first, Qt::CaseInsensitive) == 0) { log::warn( "{} is loaded. This program is known to cause issues with " "Mod Organizer, such as freezing or blank windows. Consider " "uninstalling it. ({})", p.second, file.absoluteFilePath()); ++n; } } 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> getSystemDirectories() { // folder ids and display names for logging const std::vector> systemFolderIDs = { {FOLDERID_ProgramFiles, "Program Files"}, {FOLDERID_ProgramFilesX86, "Program Files"} }; std::vector> systemDirs; for (auto&& p : systemFolderIDs) { try { const auto dir = MOBase::getKnownFolder(p.first); auto path = QDir::toNativeSeparators(dir.absolutePath()).toLower(); if (!path.endsWith("\\")) { path += "\\"; } systemDirs.push_back({path, p.second}); } catch(std::exception&) { // ignore } } 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 in {}; this may cause issues because it's a protected " "system folder", what, sd.second); log::debug("path '{}' starts with '{}'", path, sd.first); return 1; } } return 0; } int checkPathsForSanity(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 sanityChecks(const env::Environment& e) { log::debug("running sanity checks..."); int n = 0; n += checkBlocked(); n += checkMissingFiles(); n += checkIncompatibilities(e); log::debug( "sanity checks done, {}", (n > 0 ? "problems were found" : "everything looks okay")); }