/* Copyright (C) 2015 Sebastian Herbord. All rights reserved. This file is part of Mod Organizer. Mod Organizer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Mod Organizer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ #include "usvfsconnector.h" #include "envmodule.h" #include "organizercore.h" #include "settings.h" #include "shared/util.h" #include #include #include #include #include #include #include #include #include static const char SHMID[] = "mod_organizer_instance"; using namespace MOBase; std::string to_hex(void* bufferIn, size_t bufferSize) { unsigned char* buffer = static_cast(bufferIn); std::ostringstream temp; temp << std::hex; for (size_t i = 0; i < bufferSize; ++i) { temp << std::setfill('0') << std::setw(2) << (unsigned int)buffer[i]; if ((i % 16) == 15) { temp << "\n"; } else { temp << " "; } } return temp.str(); } LogWorker::LogWorker() : m_Buffer(1024, '\0'), m_QuitRequested(false), m_LogFile( qApp->property("dataPath").toString() + QString("/logs/usvfs-%1.log") .arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd_hh-mm-ss"))) { m_LogFile.open(QIODevice::WriteOnly); log::debug("usvfs log messages are written to {}", m_LogFile.fileName()); } LogWorker::~LogWorker() {} void LogWorker::process() { MOShared::SetThisThreadName("LogWorker"); int noLogCycles = 0; while (!m_QuitRequested) { if (usvfsGetLogMessages(&m_Buffer[0], m_Buffer.size(), false)) { m_LogFile.write(m_Buffer.c_str()); m_LogFile.write("\n"); m_LogFile.flush(); noLogCycles = 0; } else { QThread::msleep(std::min(40, noLogCycles) * 5); ++noLogCycles; } } emit finished(); } void LogWorker::exit() { m_QuitRequested = true; } LogLevel toUsvfsLogLevel(log::Levels level) { switch (level) { case log::Info: return LogLevel::Info; case log::Warning: return LogLevel::Warning; case log::Error: return LogLevel::Error; case log::Debug: // fall-through default: return LogLevel::Debug; } } CrashDumpsType toUsvfsCrashDumpsType(env::CoreDumpTypes type) { switch (type) { case env::CoreDumpTypes::None: return CrashDumpsType::None; case env::CoreDumpTypes::Data: return CrashDumpsType::Data; case env::CoreDumpTypes::Full: return CrashDumpsType::Full; case env::CoreDumpTypes::Mini: default: return CrashDumpsType::Mini; } } UsvfsConnector::UsvfsConnector() { using namespace std::chrono; const auto& s = Settings::instance(); const LogLevel logLevel = toUsvfsLogLevel(s.diagnostics().logLevel()); const auto dumpType = toUsvfsCrashDumpsType(s.diagnostics().coreDumpType()); const auto delay = duration_cast(s.diagnostics().spawnDelay()); std::string dumpPath = MOShared::ToString(OrganizerCore::getGlobalCoreDumpPath(), true); usvfsParameters* params = usvfsCreateParameters(); usvfsSetInstanceName(params, SHMID); usvfsSetDebugMode(params, false); usvfsSetLogLevel(params, logLevel); usvfsSetCrashDumpType(params, dumpType); usvfsSetCrashDumpPath(params, dumpPath.c_str()); usvfsSetProcessDelay(params, delay.count()); usvfsInitLogging(false); log::debug("initializing usvfs:\n" " . instance: {}\n" " . log: {}\n" " . dump: {} ({})", SHMID, usvfsLogLevelToString(logLevel), dumpPath.c_str(), usvfsCrashDumpTypeToString(dumpType)); usvfsCreateVFS(params); usvfsFreeParameters(params); usvfsClearExecutableBlacklist(); for (auto exec : s.executablesBlacklist().split(";")) { std::wstring buf = exec.toStdWString(); usvfsBlacklistExecutable(buf.data()); } usvfsClearSkipFileSuffixes(); for (auto& suffix : s.skipFileSuffixes()) { if (suffix.isEmpty()) { continue; } std::wstring buf = suffix.toStdWString(); usvfsAddSkipFileSuffix(buf.data()); } usvfsClearSkipDirectories(); for (auto& dir : s.skipDirectories()) { std::wstring buf = dir.toStdWString(); usvfsAddSkipDirectory(buf.data()); } usvfsClearLibraryForceLoads(); m_LogWorker.moveToThread(&m_WorkerThread); connect(&m_WorkerThread, SIGNAL(started()), &m_LogWorker, SLOT(process())); connect(&m_LogWorker, SIGNAL(finished()), &m_WorkerThread, SLOT(quit())); m_WorkerThread.start(QThread::LowestPriority); } UsvfsConnector::~UsvfsConnector() { usvfsDisconnectVFS(); m_LogWorker.exit(); m_WorkerThread.quit(); m_WorkerThread.wait(); } void UsvfsConnector::updateMapping(const MappingType& mapping) { const auto start = std::chrono::high_resolution_clock::now(); QProgressDialog progress(qApp->activeWindow()); progress.setLabelText(tr("Preparing vfs")); progress.setMaximum(static_cast(mapping.size())); progress.show(); int value = 0; int files = 0; int dirs = 0; log::debug("Updating VFS mappings..."); usvfsClearVirtualMappings(); for (auto map : mapping) { if (progress.wasCanceled()) { usvfsClearVirtualMappings(); throw UsvfsConnectorException("VFS mapping canceled by user"); } progress.setValue(value++); if (value % 10 == 0) { QCoreApplication::processEvents(); } if (map.isDirectory) { usvfsVirtualLinkDirectoryStatic( map.source.toStdWString().c_str(), map.destination.toStdWString().c_str(), (map.createTarget ? LINKFLAG_CREATETARGET : 0) | LINKFLAG_RECURSIVE); ++dirs; } else { usvfsVirtualLinkFile(map.source.toStdWString().c_str(), map.destination.toStdWString().c_str(), 0); ++files; } } const auto end = std::chrono::high_resolution_clock::now(); const auto time = std::chrono::duration_cast(end - start); log::debug("VFS mappings updated, linked {} dirs and {} files in {}ms", dirs, files, time.count()); } void UsvfsConnector::updateParams(MOBase::log::Levels logLevel, env::CoreDumpTypes coreDumpType, const QString& crashDumpsPath, std::chrono::seconds spawnDelay, QString executableBlacklist, const QStringList& skipFileSuffixes, const QStringList& skipDirectories) { using namespace std::chrono; usvfsParameters* p = usvfsCreateParameters(); usvfsSetDebugMode(p, FALSE); usvfsSetLogLevel(p, toUsvfsLogLevel(logLevel)); usvfsSetCrashDumpType(p, toUsvfsCrashDumpsType(coreDumpType)); usvfsSetCrashDumpPath(p, crashDumpsPath.toStdString().c_str()); usvfsSetProcessDelay(p, duration_cast(spawnDelay).count()); usvfsUpdateParameters(p); usvfsFreeParameters(p); usvfsClearExecutableBlacklist(); for (auto exec : executableBlacklist.split(";")) { std::wstring buf = exec.toStdWString(); usvfsBlacklistExecutable(buf.data()); } usvfsClearSkipFileSuffixes(); for (auto& suffix : skipFileSuffixes) { if (suffix.isEmpty()) { continue; } std::wstring buf = suffix.toStdWString(); usvfsAddSkipFileSuffix(buf.data()); } usvfsClearSkipDirectories(); for (auto& dir : skipDirectories) { std::wstring buf = dir.toStdWString(); usvfsAddSkipDirectory(buf.data()); } } void UsvfsConnector::updateForcedLibraries( const QList& forcedLibraries) { usvfsClearLibraryForceLoads(); for (auto setting : forcedLibraries) { if (setting.enabled()) { usvfsForceLoadLibrary(setting.process().toStdWString().data(), setting.library().toStdWString().data()); } } } std::vector getRunningUSVFSProcesses() { std::vector pids; { size_t count = 0; DWORD* buffer = nullptr; if (!::usvfsGetVFSProcessList2(&count, &buffer)) { log::error("failed to get usvfs process list"); return {}; } if (buffer) { pids.assign(buffer, buffer + count); std::free(buffer); } } const auto thisPid = GetCurrentProcessId(); std::vector v; const auto rights = PROCESS_QUERY_LIMITED_INFORMATION | // exit code, image name, etc. SYNCHRONIZE | // wait functions PROCESS_SET_QUOTA | PROCESS_TERMINATE; // add to job for (auto&& pid : pids) { if (pid == thisPid) { continue; // obviously don't wait for MO process } HANDLE handle = ::OpenProcess(rights, FALSE, pid); if (handle == INVALID_HANDLE_VALUE) { const auto e = GetLastError(); log::warn("failed to open usvfs process {}: {}", pid, formatSystemMessage(e)); continue; } v.push_back(handle); } return v; }