aboutsummaryrefslogtreecommitdiff
path: root/libs/usvfs/src/usvfs_dll/usvfs.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/usvfs/src/usvfs_dll/usvfs.cpp')
-rw-r--r--libs/usvfs/src/usvfs_dll/usvfs.cpp965
1 files changed, 965 insertions, 0 deletions
diff --git a/libs/usvfs/src/usvfs_dll/usvfs.cpp b/libs/usvfs/src/usvfs_dll/usvfs.cpp
new file mode 100644
index 0000000..56d9f71
--- /dev/null
+++ b/libs/usvfs/src/usvfs_dll/usvfs.cpp
@@ -0,0 +1,965 @@
+/*
+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 <http://www.gnu.org/licenses/>.
+*/
+#include "usvfs.h"
+#include "hookmanager.h"
+#include "loghelpers.h"
+#include "redirectiontree.h"
+#include "usvfs_version.h"
+#include "usvfsparametersprivate.h"
+#include <inject.h>
+#include <shmlogger.h>
+#include <spdlog/sinks/null_sink.h>
+#include <spdlog/sinks/stdout_sinks.h>
+#include <stringcast.h>
+#include <ttrampolinepool.h>
+#include <winapi.h>
+
+// note that there's a mix of boost and std filesystem stuff in this file and
+// that they're not completely compatible
+#include <filesystem>
+
+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 <std::size_t LongestExtension, std::size_t ExtensionsCount>
+bool extensionMatchesCI(
+ std::string_view name,
+ const std::array<std::string_view, ExtensionsCount>& extensionsLC,
+ const std::array<std::string_view, ExtensionsCount>& 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<std::string_view, 3> extensionsLC{"exe", "dll"};
+ static std::array<std::string_view, 3> extensionsUC{"EXE", "DLL"};
+
+ // must be changed if any extension longer than 3 letters is added
+ constexpr std::size_t longestExtension = 3;
+
+ return extensionMatchesCI<longestExtension>(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<spdlog::sinks::stdout_sink_mt>("usvfs")
+ : spdlog::create<usvfs::sinks::shm_sink>("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<spdlog::sinks::stdout_sink_mt>("hooks")
+ : spdlog::create<usvfs::sinks::shm_sink>("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<spdlog::sinks::null_sink_mt>("usvfs");
+ }
+ if (spdlog::get("hooks").get() == nullptr) {
+ spdlog::create<spdlog::sinks::null_sink_mt>("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<std::wstring>(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<FuncMiniDumpWriteDump>(
+ 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<MINIDUMP_TYPE>(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<uintptr_t, uintptr_t> range = winapi::ex::getSectionRange(dllModule);
+
+ uintptr_t exceptionAddress =
+ reinterpret_cast<uintptr_t>(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<usvfsParameters*>(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<std::wstring>(params->crashDumpsPath, ush::CodePage::UTF8);
+
+ if (params->delayProcessMs > 0) {
+ ::Sleep(static_cast<unsigned long>(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<int>(params->logLevel),
+ static_cast<int>(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<std::string>(library).c_str());
+ } else {
+ spdlog::get("usvfs")->critical(
+ "inithooks failed to force load {0}",
+ ush::string_cast<std::string>(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<spdlog::sinks::null_sink_mt>("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<spdlog::sinks::null_sink_mt>("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<DWORD> 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<DWORD> 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<DWORD*>(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<std::string>& 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<std::string>& 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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<char*>(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;
+}