/* Userspace Virtual Filesystem Copyright (C) 2015 Sebastian Herbord. All rights reserved. This file is part of usvfs. usvfs 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. usvfs 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 usvfs. If not, see . */ #include "usvfs.h" #include "hookmanager.h" #include "loghelpers.h" #include "redirectiontree.h" #include "usvfs_version.h" #include "usvfsparametersprivate.h" #include #include #include #include #include #include #include // note that there's a mix of boost and std filesystem stuff in this file and // that they're not completely compatible #include namespace bfs = boost::filesystem; namespace ush = usvfs::shared; namespace bip = boost::interprocess; namespace ba = boost::algorithm; using usvfs::log::ConvertLogLevel; usvfs::HookManager* manager = nullptr; usvfs::HookContext* context = nullptr; HMODULE dllModule = nullptr; PVOID exceptionHandler = nullptr; CrashDumpsType usvfs_dump_type = CrashDumpsType::None; std::wstring usvfs_dump_path; // this is called for every single file, so it's a bit long winded, but it's // as fast as it gets, probably // template bool extensionMatchesCI( std::string_view name, const std::array& extensionsLC, const std::array& extensionsUC) { constexpr std::size_t longestExtensionWithDot = LongestExtension + 1; // quick check if (name.size() < longestExtensionWithDot) { return false; } // for each extension for (std::size_t i = 0; i < ExtensionsCount; ++i) { const std::size_t extensionLength = extensionsLC[i].size(); const std::size_t extensionLengthWithDot = extensionLength + 1; // check size if (name.size() < extensionLengthWithDot) { continue; } // check dot if (name[name.size() - extensionLengthWithDot] != '.') { continue; } // starts at one past the dot const auto* p = name.data() + name.size() - extensionLength; // set to false as soon as a character doesn't match bool found = true; // for each character in extension for (std::size_t c = 0; c < extensionLength; ++c) { // checking both lowercase and uppercase if (*p != extensionsLC[i][c] && *p != extensionsUC[i][c]) { // neither found = false; break; } // matches, check next ++p; } if (found) { return true; } } return false; } bool shouldAddToInverseTree(std::string_view name) { static std::array extensionsLC{"exe", "dll"}; static std::array extensionsUC{"EXE", "DLL"}; // must be changed if any extension longer than 3 letters is added constexpr std::size_t longestExtension = 3; return extensionMatchesCI(name, extensionsLC, extensionsUC); } // // Logging // void InitLoggingInternal(bool toConsole, bool connectExistingSHM) { try { if (!toConsole && !SHMLogger::isInstantiated()) { if (connectExistingSHM) { SHMLogger::open("usvfs"); } else { SHMLogger::create("usvfs"); } } // a temporary logger was created in DllMain spdlog::drop("usvfs"); #pragma message("need a customized name for the shm") auto logger = spdlog::get("usvfs"); if (logger.get() == nullptr) { logger = toConsole ? spdlog::create("usvfs") : spdlog::create("usvfs", "usvfs"); logger->set_pattern("%H:%M:%S.%e [%L] %v"); } logger->set_level(spdlog::level::debug); spdlog::drop("hooks"); logger = spdlog::get("hooks"); if (logger.get() == nullptr) { logger = toConsole ? spdlog::create("hooks") : spdlog::create("hooks", "usvfs"); logger->set_pattern("%H:%M:%S.%e <%P:%t> [%L] %v"); } logger->set_level(spdlog::level::debug); } catch (const std::exception&) { // TODO should really report this // OutputDebugStringA((boost::format("init exception: %1%\n") % // e.what()).str().c_str()); if (spdlog::get("usvfs").get() == nullptr) { spdlog::create("usvfs"); } if (spdlog::get("hooks").get() == nullptr) { spdlog::create("hooks"); } } spdlog::get("usvfs")->info("usvfs dll {} initialized in process {}", USVFS_VERSION_STRING, GetCurrentProcessId()); } void WINAPI usvfsInitLogging(bool toConsole) { InitLoggingInternal(toConsole, false); } extern "C" DLLEXPORT bool WINAPI usvfsGetLogMessages(LPSTR buffer, size_t size, bool blocking) { buffer[0] = '\0'; try { if (blocking) { SHMLogger::instance().get(buffer, size); return true; } else { return SHMLogger::instance().tryGet(buffer, size); } } catch (const std::exception& e) { _snprintf_s(buffer, size, _TRUNCATE, "Failed to retrieve log messages: %s", e.what()); return false; } } void SetLogLevel(LogLevel level) { spdlog::get("usvfs")->set_level(ConvertLogLevel(level)); spdlog::get("hooks")->set_level(ConvertLogLevel(level)); } void WINAPI usvfsUpdateParameters(usvfsParameters* p) { spdlog::get("usvfs")->info("updating parameters:\n" " . debugMode: {}\n" " . log level: {}\n" " . dump type: {}\n" " . dump path: {}\n" " . delay process: {}ms", p->debugMode, usvfsLogLevelToString(p->logLevel), usvfsCrashDumpTypeToString(p->crashDumpsType), p->crashDumpsPath, p->delayProcessMs); // update actual values used: usvfs_dump_type = p->crashDumpsType; usvfs_dump_path = ush::string_cast(p->crashDumpsPath, ush::CodePage::UTF8); SetLogLevel(p->logLevel); // update parameters in context so spawned process will inherit changes: context->setDebugParameters(p->logLevel, p->crashDumpsType, p->crashDumpsPath, std::chrono::milliseconds(p->delayProcessMs)); } // // Structured Exception handling // std::wstring generate_minidump_name(const wchar_t* dumpPath) { DWORD pid = GetCurrentProcessId(); wchar_t pname[100]; if (GetModuleBaseName(GetCurrentProcess(), NULL, pname, _countof(pname)) == 0) return std::wstring(); // find an available name: wchar_t dmpFile[MAX_PATH]; int count = 0; _snwprintf_s(dmpFile, _TRUNCATE, L"%s\\%s-%lu.dmp", dumpPath, pname, pid); while (winapi::ex::wide::fileExists(dmpFile)) { if (++count > 99) return std::wstring(); _snwprintf_s(dmpFile, _TRUNCATE, L"%s\\%s-%lu_%02d.dmp", dumpPath, pname, pid, count); } return dmpFile; } int createMiniDumpImpl(PEXCEPTION_POINTERS exceptionPtrs, CrashDumpsType type, const wchar_t* dumpPath, HMODULE dbgDLL) { typedef BOOL(WINAPI * FuncMiniDumpWriteDump)( HANDLE process, DWORD pid, HANDLE file, MINIDUMP_TYPE dumpType, const PMINIDUMP_EXCEPTION_INFORMATION exceptionParam, const PMINIDUMP_USER_STREAM_INFORMATION userStreamParam, const PMINIDUMP_CALLBACK_INFORMATION callbackParam); // notice we avoid logging here on purpose because this is called from the VEHandler // and the logger can crash it in extreme cases. // additionally it is also called for MO crashes which use it's own logging. winapi::ex::wide::createPath(dumpPath); auto dmpName = generate_minidump_name(dumpPath); if (dmpName.empty()) return 4; FuncMiniDumpWriteDump funcDump = reinterpret_cast( GetProcAddress(dbgDLL, "MiniDumpWriteDump")); if (!funcDump) return 5; HANDLE dumpFile = winapi::wide::createFile(dmpName) .createAlways() .access(GENERIC_WRITE) .share(FILE_SHARE_WRITE)(); if (dumpFile != INVALID_HANDLE_VALUE) { DWORD dumpType = MiniDumpNormal | MiniDumpWithHandleData | MiniDumpWithUnloadedModules | MiniDumpWithProcessThreadData; if (type == CrashDumpsType::Data) dumpType |= MiniDumpWithDataSegs; if (type == CrashDumpsType::Full) dumpType |= MiniDumpWithFullMemory; _MINIDUMP_EXCEPTION_INFORMATION exceptionInfo; exceptionInfo.ThreadId = GetCurrentThreadId(); exceptionInfo.ExceptionPointers = exceptionPtrs; exceptionInfo.ClientPointers = FALSE; BOOL success = funcDump(GetCurrentProcess(), GetCurrentProcessId(), dumpFile, static_cast(dumpType), &exceptionInfo, nullptr, nullptr); CloseHandle(dumpFile); return success ? 0 : 7; } else return 6; } int WINAPI usvfsCreateMiniDump(PEXCEPTION_POINTERS exceptionPtrs, CrashDumpsType type, const wchar_t* dumpPath) { if (type == CrashDumpsType::None) return 0; int res = 1; if (HMODULE dbgDLL = LoadLibraryW(L"dbghelp.dll")) { try { res = createMiniDumpImpl(exceptionPtrs, type, dumpPath, dbgDLL); } catch (...) { res = 2; } FreeLibrary(dbgDLL); } return res; } static bool exceptionInUSVFS(PEXCEPTION_POINTERS exceptionPtrs) { if (!dllModule) // shouldn't happen, check just in case return true; // create dump to better understand how this could happen std::pair range = winapi::ex::getSectionRange(dllModule); uintptr_t exceptionAddress = reinterpret_cast(exceptionPtrs->ExceptionRecord->ExceptionAddress); return range.first <= exceptionAddress && exceptionAddress < range.second; } LONG WINAPI VEHandler(PEXCEPTION_POINTERS exceptionPtrs) { // NOTICE: don't use logger in VEHandler as it can cause another fault causing // VEHandler to be called again and so on. if ((exceptionPtrs->ExceptionRecord->ExceptionCode < 0x80000000) // non-critical || (exceptionPtrs->ExceptionRecord->ExceptionCode == 0xe06d7363)) { // cpp exception // don't report non-critical exceptions return EXCEPTION_CONTINUE_SEARCH; } /* if (((exceptionPtrs->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) != 0) || (exceptionPtrs->ExceptionRecord->ExceptionCode == 0xe06d7363)) { // don't want to break on non-critical exceptions. 0xe06d7363 indicates a C++ exception. why are those marked non-continuable? return EXCEPTION_CONTINUE_SEARCH; } */ // VEHandler is called on "first-chance" exceptions which might be caught and handled. // Ideally we would like to use an UnhandledExceptionFilter but that fails to catch // crashes inside our hooks at least on x64, which is the main reason why want a crash // collection from usvfs. As a workaround/compromise we catch vectored exception but // only ones that originate directly within the usvfs code: if (!exceptionInUSVFS(exceptionPtrs)) return EXCEPTION_CONTINUE_SEARCH; // disable our hooking mechanism to increase chances the dump writing won't crash HookLib::TrampolinePool& trampPool = HookLib::TrampolinePool::instance(); if (&trampPool) { // need to test this in case of crash before TrampolinePool // initialized trampPool.forceUnlockBarrier(); trampPool.setBlock(true); } usvfsCreateMiniDump(exceptionPtrs, usvfs_dump_type, usvfs_dump_path.c_str()); return EXCEPTION_CONTINUE_SEARCH; } // // Exported functions // void __cdecl InitHooks(LPVOID parameters, size_t) { InitLoggingInternal(false, true); const usvfsParameters* params = reinterpret_cast(parameters); // there is already a wait in the constructor of HookManager, but this one is useful // to debug code here (from experience... ), should not wait twice since the second // will return true immediately if (params->debugMode) { while (!::IsDebuggerPresent()) { // wait for debugger to attach ::Sleep(100); } } usvfs_dump_type = params->crashDumpsType; usvfs_dump_path = ush::string_cast(params->crashDumpsPath, ush::CodePage::UTF8); if (params->delayProcessMs > 0) { ::Sleep(static_cast(params->delayProcessMs)); } SetLogLevel(params->logLevel); if (exceptionHandler == nullptr) { if (usvfs_dump_type != CrashDumpsType::None) exceptionHandler = ::AddVectoredExceptionHandler(0, VEHandler); } else { spdlog::get("usvfs")->info("vectored exception handler already active"); // how did this happen?? } spdlog::get("usvfs")->info( "inithooks called {0} in process {1}:{2} (log level {3}, dump type {4}, dump " "path {5})", params->instanceName, winapi::ansi::getModuleFileName(nullptr), ::GetCurrentProcessId(), static_cast(params->logLevel), static_cast(params->crashDumpsType), params->crashDumpsPath); try { manager = new usvfs::HookManager(*params, dllModule); auto context = manager->context(); auto exePath = boost::dll::program_location(); auto libraries = context->librariesToForceLoad(exePath.filename().c_str()); for (auto library : libraries) { if (std::filesystem::exists(library)) { const auto ret = LoadLibraryExW(library.c_str(), NULL, 0); if (ret) { spdlog::get("usvfs")->info("inithooks succeeded to force load {0}", ush::string_cast(library).c_str()); } else { spdlog::get("usvfs")->critical( "inithooks failed to force load {0}", ush::string_cast(library).c_str()); } } } spdlog::get("usvfs")->info("inithooks in process {0} successful", ::GetCurrentProcessId()); } catch (const std::exception& e) { spdlog::get("usvfs")->debug("failed to initialise hooks: {0}", e.what()); } } void WINAPI usvfsGetCurrentVFSName(char* buffer, size_t size) { ush::strncpy_sz(buffer, context->callParameters().currentSHMName, size); } BOOL WINAPI usvfsCreateVFS(const usvfsParameters* p) { usvfs::HookContext::remove(p->instanceName); return usvfsConnectVFS(p); } BOOL WINAPI usvfsConnectVFS(const usvfsParameters* params) { if (spdlog::get("usvfs").get() == nullptr) { // create temporary logger so we don't get null-pointer exceptions spdlog::create("usvfs"); } try { usvfsDisconnectVFS(); context = new usvfs::HookContext(*params, dllModule); return TRUE; } catch (const std::exception& e) { spdlog::get("usvfs")->debug("failed to connect to vfs: {}", e.what()); return FALSE; } } void WINAPI usvfsDisconnectVFS() { if (spdlog::get("usvfs").get() == nullptr) { // create temporary logger so we don't get null-pointer exceptions spdlog::create("usvfs"); } spdlog::get("usvfs")->debug("remove from process {}", GetCurrentProcessId()); if (manager != nullptr) { delete manager; manager = nullptr; } if (context != nullptr) { delete context; context = nullptr; spdlog::get("usvfs")->debug("vfs unloaded"); } } bool processStillActive(DWORD pid) { HANDLE proc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); if (proc == nullptr) { return false; } ON_BLOCK_EXIT([&]() { if (proc != INVALID_HANDLE_VALUE) ::CloseHandle(proc); }); DWORD exitCode; if (!GetExitCodeProcess(proc, &exitCode)) { spdlog::get("usvfs")->warn("failed to query exit code on process {}: {}", pid, ::GetLastError()); return false; } else { return exitCode == STILL_ACTIVE; } } BOOL WINAPI usvfsGetVFSProcessList(size_t* count, LPDWORD processIDs) { if (count == nullptr) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } if (context == nullptr) { *count = 0; } else { std::vector pids = context->registeredProcesses(); size_t realCount = 0; for (DWORD pid : pids) { if (processStillActive(pid)) { if ((realCount < *count) && (processIDs != nullptr)) { processIDs[realCount] = pid; } ++realCount; } // else the process has already ended } *count = realCount; } return TRUE; } BOOL WINAPI usvfsGetVFSProcessList2(size_t* count, DWORD** buffer) { if (!count || !buffer) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } *count = 0; *buffer = nullptr; std::vector pids = context->registeredProcesses(); auto last = std::remove_if(pids.begin(), pids.end(), [](DWORD id) { return !processStillActive(id); }); pids.erase(last, pids.end()); if (pids.empty()) { return TRUE; } *count = pids.size(); *buffer = static_cast(std::calloc(pids.size(), sizeof(DWORD))); std::copy(pids.begin(), pids.end(), *buffer); return TRUE; } void WINAPI usvfsClearVirtualMappings() { context->redirectionTable()->clear(); context->inverseTable()->clear(); } /// ensure the specified path exists. If a physical path of the same name /// exists, it is inserted into the virtual directory as an empty reference. If /// the path doesn't exist virtually and can't be cloned from a physical /// directory, this returns false /// \todo if this fails (i.e. not all intermediate directories exists) any /// intermediate directories already created aren't removed bool assertPathExists(usvfs::RedirectionTreeContainer& table, LPCWSTR path) { bfs::path p(path); p = p.parent_path(); usvfs::RedirectionTree::NodeT* current = table.get(); for (auto iter = p.begin(); iter != p.end(); iter = ush::nextIter(iter, p.end())) { if (current->exists(iter->string().c_str())) { // subdirectory exists virtually, all good usvfs::RedirectionTree::NodePtrT found = current->node(iter->string().c_str()); current = found.get().get(); } else { // targetPath is relative to the last rerouted "real" path. This means // that if virtual c:/foo maps to real c:/windows then creating virtual // c:/foo/bar will map to real c:/windows/bar bfs::path targetPath = current->data().linkTarget.size() > 0 ? bfs::path(current->data().linkTarget.c_str()) / *iter : *iter / "\\"; // is_directory returns false for symlinks and reparse points, // which causes this function to fail if the target path contains // either of those. paths containing reparse points is a common // scenario when running under Wine, so check for those explicitly. // this check could have a false positive if the path contains a // symlink to a file, but such a scenario is extremely unlikely. if (is_directory(targetPath) || is_symlink(targetPath) || status(targetPath).type() == bfs::file_type::reparse_file) { usvfs::RedirectionTree::NodePtrT newNode = table.addDirectory(current->path() / *iter, targetPath.string().c_str(), ush::FLAG_DUMMY, false); current = newNode.get().get(); } else { spdlog::get("usvfs")->info("{} doesn't exist", targetPath.c_str()); return false; } } } return true; } static bool fileNameInSkipSuffixes(const std::string& fileNameUtf8, const std::vector& skipFileSuffixes) { for (const auto& skipFileSuffix : skipFileSuffixes) { if (boost::algorithm::iends_with(fileNameUtf8, skipFileSuffix)) { spdlog::get("usvfs")->debug( "file '{}' should be skipped, matches file suffix '{}'", fileNameUtf8, skipFileSuffix); return true; } } return false; } static bool fileNameInSkipDirectories(const std::string& directoryNameUtf8, const std::vector& skipDirectories) { for (const auto& skipDir : skipDirectories) { if (boost::algorithm::iequals(directoryNameUtf8, skipDir)) { spdlog::get("usvfs")->debug("directory '{}' should be skipped", directoryNameUtf8); return true; } } return false; } BOOL WINAPI usvfsVirtualLinkFile(LPCWSTR source, LPCWSTR destination, unsigned int flags) { // TODO difference between winapi and ntdll api regarding system32 vs syswow64 // (and other windows links?) try { if (!assertPathExists(context->redirectionTable(), destination)) { SetLastError(ERROR_PATH_NOT_FOUND); return FALSE; } const auto skipFileSuffixes = context->skipFileSuffixes(); std::string sourceU8 = ush::string_cast(source, ush::CodePage::UTF8); // Check if the file should be skipped if (fileNameInSkipSuffixes(sourceU8, skipFileSuffixes)) { // return false when we want to fail when the file is skipped return (flags & LINKFLAG_FAILIFSKIPPED) ? FALSE : TRUE; } auto res = context->redirectionTable().addFile( bfs::path(destination), usvfs::RedirectionDataLocal(sourceU8), !(flags & LINKFLAG_FAILIFEXISTS)); if (shouldAddToInverseTree(sourceU8)) { std::string destinationU8 = ush::string_cast(destination, ush::CodePage::UTF8); context->inverseTable().addFile(bfs::path(source), usvfs::RedirectionDataLocal(destinationU8), true); } context->updateParameters(); if (res.get() == nullptr) { // the tree structure currently doesn't provide useful error codes but // this is currently the only reason // we would return a nullptr. SetLastError(ERROR_FILE_EXISTS); return FALSE; } else { return TRUE; } } catch (const std::exception& e) { spdlog::get("usvfs")->error("failed to copy file {}", e.what()); // TODO: no clue what's wrong SetLastError(ERROR_INVALID_DATA); return FALSE; } } /** * @brief extract the flags relevant to redirection */ static usvfs::shared::TreeFlags convertRedirectionFlags(unsigned int flags) { usvfs::shared::TreeFlags result = 0; if (flags & LINKFLAG_CREATETARGET) { result |= usvfs::shared::FLAG_CREATETARGET; } return result; } BOOL WINAPI usvfsVirtualLinkDirectoryStatic(LPCWSTR source, LPCWSTR destination, unsigned int flags) { // TODO change notification not yet implemented try { if ((flags & LINKFLAG_FAILIFEXISTS) && winapi::ex::wide::fileExists(destination)) { SetLastError(ERROR_FILE_EXISTS); return FALSE; } if (!assertPathExists(context->redirectionTable(), destination)) { SetLastError(ERROR_PATH_NOT_FOUND); return FALSE; } std::string sourceU8 = ush::string_cast(source, ush::CodePage::UTF8) + "\\"; context->redirectionTable().addDirectory( destination, usvfs::RedirectionDataLocal(sourceU8), usvfs::shared::FLAG_DIRECTORY | convertRedirectionFlags(flags), (flags & LINKFLAG_CREATETARGET) != 0); const auto skipDirectories = context->skipDirectories(); const auto skipFileSuffixes = context->skipFileSuffixes(); if ((flags & LINKFLAG_RECURSIVE) != 0) { std::wstring sourceP(source); std::wstring sourceW = sourceP + L"\\"; std::wstring destinationW = std::wstring(destination) + L"\\"; if (sourceP.length() >= MAX_PATH && !ush::startswith(sourceP.c_str(), LR"(\\?\)")) sourceP = LR"(\\?\)" + sourceP; for (winapi::ex::wide::FileResult file : winapi::ex::wide::quickFindFiles(sourceP.c_str(), L"*")) { if (file.attributes & FILE_ATTRIBUTE_DIRECTORY) { if ((file.fileName != L".") && (file.fileName != L"..")) { const auto nameU8 = ush::string_cast(file.fileName.c_str(), ush::CodePage::UTF8); // Check if the directory should be skipped if (fileNameInSkipDirectories(nameU8, skipDirectories)) { // Fail if we desire to fail when a dir/file is skipped if (flags & LINKFLAG_FAILIFSKIPPED) { spdlog::get("usvfs")->debug( "directory '{}' skipped, failing as defined by link flags", nameU8); return FALSE; } continue; } usvfsVirtualLinkDirectoryStatic((sourceW + file.fileName).c_str(), (destinationW + file.fileName).c_str(), flags); } } else { const auto nameU8 = ush::string_cast(file.fileName.c_str(), ush::CodePage::UTF8); // Check if the file should be skipped if (fileNameInSkipSuffixes(nameU8, skipFileSuffixes)) { // Fail if we desire to fail when a dir/file is skipped if (flags & LINKFLAG_FAILIFSKIPPED) { spdlog::get("usvfs")->debug( "file '{}' skipped, failing as defined by link flags", nameU8); return FALSE; } continue; } // TODO could save memory here by storing only the file name for the // source and constructing the full name using the parent directory context->redirectionTable().addFile( bfs::path(destination) / nameU8, usvfs::RedirectionDataLocal(sourceU8 + nameU8), true); if (shouldAddToInverseTree(nameU8)) { std::string destinationU8 = ush::string_cast(destination, ush::CodePage::UTF8) + "\\"; context->inverseTable().addFile( bfs::path(source) / nameU8, usvfs::RedirectionDataLocal(destinationU8 + nameU8), true); } } } } context->updateParameters(); return TRUE; } catch (const std::exception& e) { spdlog::get("usvfs")->error("failed to copy file {}", e.what()); // TODO: no clue what's wrong SetLastError(ERROR_INVALID_DATA); return FALSE; } } BOOL WINAPI usvfsCreateProcessHooked(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation) { BOOL susp = dwCreationFlags & CREATE_SUSPENDED; DWORD flags = dwCreationFlags | CREATE_SUSPENDED; BOOL blacklisted = context->executableBlacklisted(lpApplicationName, lpCommandLine); BOOL res = CreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, flags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); if (!res) { spdlog::get("usvfs")->error("failed to spawn {}", ush::string_cast(lpCommandLine)); return FALSE; } if (!blacklisted) { std::wstring applicationDirPath = winapi::wide::getModuleFileName(dllModule); boost::filesystem::path p(applicationDirPath); try { usvfs::injectProcess(p.parent_path().wstring(), context->callParameters(), *lpProcessInformation); } catch (const std::exception& e) { spdlog::get("usvfs")->error("failed to inject: {}", e.what()); logExtInfo(e, LogLevel::Error); ::TerminateProcess(lpProcessInformation->hProcess, 1); ::SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } } if (!susp) { ResumeThread(lpProcessInformation->hThread); } return TRUE; } BOOL WINAPI usvfsCreateVFSDump(LPSTR buffer, size_t* size) { assert(size != nullptr); std::ostringstream output; usvfs::shared::dumpTree(output, *context->redirectionTable().get()); std::string str = output.str(); if ((buffer != NULL) && (*size > 0)) { strncpy_s(buffer, *size, str.c_str(), _TRUNCATE); } bool success = *size >= str.length(); *size = str.length(); return success ? TRUE : FALSE; } VOID WINAPI usvfsBlacklistExecutable(LPCWSTR executableName) { context->blacklistExecutable(executableName); } VOID WINAPI usvfsClearExecutableBlacklist() { context->clearExecutableBlacklist(); } VOID WINAPI usvfsAddSkipFileSuffix(LPCWSTR fileSuffix) { context->addSkipFileSuffix(fileSuffix); } VOID WINAPI usvfsClearSkipFileSuffixes() { context->clearSkipFileSuffixes(); } VOID WINAPI usvfsAddSkipDirectory(LPCWSTR directory) { context->addSkipDirectory(directory); } VOID WINAPI usvfsClearSkipDirectories() { context->clearSkipDirectories(); } VOID WINAPI usvfsForceLoadLibrary(LPCWSTR processName, LPCWSTR libraryPath) { context->forceLoadLibrary(processName, libraryPath); } VOID WINAPI usvfsClearLibraryForceLoads() { context->clearLibraryForceLoads(); } VOID WINAPI usvfsPrintDebugInfo() { spdlog::get("usvfs")->warn("===== debug {} =====", context->redirectionTable().shmName()); void* buffer = nullptr; size_t bufferSize = 0; context->redirectionTable().getBuffer(buffer, bufferSize); std::ostringstream temp; for (size_t i = 0; i < bufferSize; ++i) { temp << std::hex << std::setfill('0') << std::setw(2) << (unsigned)reinterpret_cast(buffer)[i] << " "; if ((i % 16) == 15) { spdlog::get("usvfs")->info("{}", temp.str()); temp.str(""); temp.clear(); } } if (!temp.str().empty()) { spdlog::get("usvfs")->info("{}", temp.str()); } spdlog::get("usvfs")->warn("===== / debug {} =====", context->redirectionTable().shmName()); } const char* WINAPI usvfsVersionString() { return USVFS_VERSION_STRING; } // // DllMain // BOOL APIENTRY DllMain(HMODULE module, DWORD reasonForCall, LPVOID) { switch (reasonForCall) { case DLL_PROCESS_ATTACH: { dllModule = module; } break; case DLL_PROCESS_DETACH: { if (exceptionHandler) ::RemoveVectoredExceptionHandler(exceptionHandler); } break; case DLL_THREAD_ATTACH: { } break; case DLL_THREAD_DETACH: { } break; } return TRUE; }