aboutsummaryrefslogtreecommitdiff
path: root/libs/usvfs/src/usvfs_dll/hooks
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
commit7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch)
tree27fb39be241fdb5ac2734c574de678977d1856d0 /libs/usvfs/src/usvfs_dll/hooks
Fluorine Manager: full Linux port of Mod Organizer 2
Complete native Linux port with FUSE-based virtual filesystem, Proton/umu-run integration, and Flatpak packaging. Key features: - FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak) - Proton/GE-Proton/umu-run launcher with env var forwarding - Flatpak support (sandbox-aware VFS, NXM handler, umu-run) - Wine prefix management UI - Case-insensitive path resolution for Linux filesystems - QSettings-safe INI handling (avoids Bethesda INI corruption) - Portable instance support with auto-generated launcher scripts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/usvfs/src/usvfs_dll/hooks')
-rw-r--r--libs/usvfs/src/usvfs_dll/hooks/file_information_utils.h263
-rw-r--r--libs/usvfs/src/usvfs_dll/hooks/kernel32.cpp1860
-rw-r--r--libs/usvfs/src/usvfs_dll/hooks/kernel32.h116
-rw-r--r--libs/usvfs/src/usvfs_dll/hooks/ntdll.cpp1520
-rw-r--r--libs/usvfs/src/usvfs_dll/hooks/ntdll.h57
-rw-r--r--libs/usvfs/src/usvfs_dll/hooks/sharedids.h9
6 files changed, 3825 insertions, 0 deletions
diff --git a/libs/usvfs/src/usvfs_dll/hooks/file_information_utils.h b/libs/usvfs/src/usvfs_dll/hooks/file_information_utils.h
new file mode 100644
index 0000000..dbd3597
--- /dev/null
+++ b/libs/usvfs/src/usvfs_dll/hooks/file_information_utils.h
@@ -0,0 +1,263 @@
+#pragma once
+
+/**
+ * This header defines function to deal with the various FileInformationClass
+ * enumeration.
+ */
+
+#include <type_traits>
+
+#include <ntdll_declarations.h>
+
+#include <windows.h>
+
+namespace usvfs::details
+{
+
+#define DECLARE_HAS_FIELD(Field) \
+ template <typename T> \
+ constexpr auto HasFieldImpl##Field(int)->decltype(std::declval<T>().Field, void(), \
+ std::true_type()) \
+ { \
+ return {}; \
+ } \
+ template <typename T> \
+ constexpr auto HasFieldImpl##Field(...) \
+ { \
+ return std::false_type{}; \
+ } \
+ template <typename T> \
+ constexpr auto HasField##Field() \
+ { \
+ return HasFieldImpl##Field<T>(0); \
+ }
+
+DECLARE_HAS_FIELD(FileName)
+DECLARE_HAS_FIELD(NextEntryOffset)
+DECLARE_HAS_FIELD(ShortName)
+
+#undef DECLARE_HAS_FIELD
+
+template <FILE_INFORMATION_CLASS fileInformationClass, class FileInformationClass>
+struct FileInformationClassUtilsImpl
+{
+ static void get_data(LPCVOID address, ULONG& offset, std::wstring& fileName)
+ {
+ const FileInformationClass* info =
+ reinterpret_cast<const FileInformationClass*>(address);
+
+ // default value
+ offset = sizeof(FileInformationClass);
+
+ if constexpr (HasFieldFileName<FileInformationClass>()) {
+ if constexpr (HasFieldNextEntryOffset<FileInformationClass>()) {
+ offset = info->NextEntryOffset;
+ }
+ fileName = std::wstring(info->FileName, info->FileNameLength / sizeof(WCHAR));
+ }
+ }
+
+ static void set_offset(LPVOID address, ULONG offset)
+ {
+ FileInformationClass* info = reinterpret_cast<FileInformationClass*>(address);
+
+ if constexpr (HasFieldNextEntryOffset<FileInformationClass>()) {
+ info->NextEntryOffset = offset;
+ }
+ }
+
+ static void set_filename(LPVOID address, const std::wstring& fileName)
+ {
+ FileInformationClass* info = reinterpret_cast<FileInformationClass*>(address);
+
+ if constexpr (HasFieldFileName<FileInformationClass>()) {
+ memset(info->FileName, L'\0', info->FileNameLength);
+ info->FileNameLength = static_cast<ULONG>(fileName.length() * sizeof(WCHAR));
+
+ // doesn't need to be 0-terminated
+ memcpy(info->FileName, fileName.c_str(), info->FileNameLength);
+
+ if constexpr (HasFieldShortName<FileInformationClass>()) {
+ if (info->ShortNameLength > 0) {
+ info->ShortNameLength = static_cast<CCHAR>(
+ GetShortPathNameW(fileName.c_str(), info->ShortName, 8));
+ }
+ }
+ }
+ }
+};
+
+template <FILE_INFORMATION_CLASS fileInformationClass>
+struct FileInformationClassUtils;
+
+template <>
+struct FileInformationClassUtils<FileDirectoryInformation>
+ : FileInformationClassUtilsImpl<FileDirectoryInformation,
+ FILE_DIRECTORY_INFORMATION>
+{};
+template <>
+struct FileInformationClassUtils<FileFullDirectoryInformation>
+ : FileInformationClassUtilsImpl<FileFullDirectoryInformation,
+ FILE_FULL_DIR_INFORMATION>
+{};
+template <>
+struct FileInformationClassUtils<FileBothDirectoryInformation>
+ : FileInformationClassUtilsImpl<FileBothDirectoryInformation,
+ FILE_BOTH_DIR_INFORMATION>
+{};
+template <>
+struct FileInformationClassUtils<FileStandardInformation>
+ : FileInformationClassUtilsImpl<FileStandardInformation, FILE_STANDARD_INFORMATION>
+{};
+template <>
+struct FileInformationClassUtils<FileNameInformation>
+ : FileInformationClassUtilsImpl<FileNameInformation, FILE_NAME_INFORMATION>
+{};
+template <>
+struct FileInformationClassUtils<FileRenameInformation>
+ : FileInformationClassUtilsImpl<FileRenameInformation, FILE_RENAME_INFORMATION>
+{};
+template <>
+struct FileInformationClassUtils<FileNamesInformation>
+ : FileInformationClassUtilsImpl<FileNamesInformation, FILE_NAMES_INFORMATION>
+{};
+template <>
+struct FileInformationClassUtils<FileObjectIdInformation>
+ : FileInformationClassUtilsImpl<FileObjectIdInformation, FILE_OBJECTID_INFORMATION>
+{};
+template <>
+struct FileInformationClassUtils<FileReparsePointInformation>
+ : FileInformationClassUtilsImpl<FileReparsePointInformation,
+ FILE_REPARSE_POINT_INFORMATION>
+{};
+template <>
+struct FileInformationClassUtils<FileIdBothDirectoryInformation>
+ : FileInformationClassUtilsImpl<FileIdBothDirectoryInformation,
+ FILE_ID_BOTH_DIR_INFORMATION>
+{};
+template <>
+struct FileInformationClassUtils<FileIdFullDirectoryInformation>
+ : FileInformationClassUtilsImpl<FileIdFullDirectoryInformation,
+ FILE_ID_FULL_DIR_INFORMATION>
+{};
+template <>
+struct FileInformationClassUtils<FileNormalizedNameInformation>
+ : FileInformationClassUtilsImpl<FileNormalizedNameInformation,
+ FILE_NAME_INFORMATION>
+{};
+template <>
+struct FileInformationClassUtils<FileIdExtdDirectoryInformation>
+ : FileInformationClassUtilsImpl<FileIdExtdDirectoryInformation,
+ FILE_ID_EXTD_DIR_INFORMATION>
+{};
+template <>
+struct FileInformationClassUtils<FileIdExtdBothDirectoryInformation>
+ : FileInformationClassUtilsImpl<FileIdExtdBothDirectoryInformation,
+ FILE_ID_EXTD_BOTH_DIR_INFORMATION>
+{};
+template <>
+struct FileInformationClassUtils<FileId64ExtdDirectoryInformation>
+ : FileInformationClassUtilsImpl<FileId64ExtdDirectoryInformation,
+ FILE_ID_64_EXTD_DIR_INFORMATION>
+{};
+template <>
+struct FileInformationClassUtils<FileId64ExtdBothDirectoryInformation>
+ : FileInformationClassUtilsImpl<FileId64ExtdBothDirectoryInformation,
+ FILE_ID_64_EXTD_BOTH_DIR_INFORMATION>
+{};
+template <>
+struct FileInformationClassUtils<FileIdAllExtdDirectoryInformation>
+ : FileInformationClassUtilsImpl<FileIdAllExtdDirectoryInformation,
+ FILE_ID_ALL_EXTD_DIR_INFORMATION>
+{};
+template <>
+struct FileInformationClassUtils<FileIdAllExtdBothDirectoryInformation>
+ : FileInformationClassUtilsImpl<FileIdAllExtdBothDirectoryInformation,
+ FILE_ID_ALL_EXTD_BOTH_DIR_INFORMATION>
+{};
+
+// FILE_ALL_INFORMATION needs to be handled differently because it has a field that
+// is itself a structure
+template <>
+struct FileInformationClassUtils<FileAllInformation>
+{
+ static void get_data(LPCVOID address, ULONG& offset, std::wstring& fileName)
+ {
+ FileInformationClassUtils<FileNameInformation>::get_data(
+ &reinterpret_cast<const FILE_ALL_INFORMATION*>(address)->NameInformation,
+ offset, fileName);
+ }
+
+ static void set_offset(LPVOID address, ULONG offset)
+ {
+ // this is a no-op but it's consistent to do that everywhere
+ FileInformationClassUtils<FileNameInformation>::set_offset(
+ &reinterpret_cast<FILE_ALL_INFORMATION*>(address)->NameInformation, offset);
+ }
+
+ static void set_filename(LPVOID address, const std::wstring& fileName)
+ {
+ FileInformationClassUtils<FileNameInformation>::set_filename(
+ &reinterpret_cast<FILE_ALL_INFORMATION*>(address)->NameInformation, fileName);
+ }
+};
+
+} // namespace usvfs::details
+
+#define _APP_FINFO_CASE(clazz, fn, ...) \
+ case clazz: \
+ return usvfs::details::FileInformationClassUtils<clazz>::fn(__VA_ARGS__);
+
+#define _APPLY_FILEINFO_FN(fn, ...) \
+ _APP_FINFO_CASE(FileAllInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileDirectoryInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileFullDirectoryInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileBothDirectoryInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileStandardInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileNameInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileRenameInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileNamesInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileObjectIdInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileReparsePointInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileIdBothDirectoryInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileIdFullDirectoryInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileNormalizedNameInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileIdExtdDirectoryInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileIdExtdBothDirectoryInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileId64ExtdDirectoryInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileId64ExtdBothDirectoryInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileIdAllExtdDirectoryInformation, fn, __VA_ARGS__) \
+ _APP_FINFO_CASE(FileIdAllExtdBothDirectoryInformation, fn, __VA_ARGS__)
+
+void GetFileInformationData(FILE_INFORMATION_CLASS fileInformationClass,
+ LPCVOID address, ULONG& offset, std::wstring& fileName)
+{
+ switch (fileInformationClass) {
+ _APPLY_FILEINFO_FN(get_data, address, offset, fileName);
+ default:
+ offset = ULONG_MAX;
+ }
+}
+
+void SetFileInformationOffset(FILE_INFORMATION_CLASS fileInformationClass,
+ LPVOID address, ULONG offset)
+{
+ switch (fileInformationClass) {
+ _APPLY_FILEINFO_FN(set_offset, address, offset);
+ default:
+ break;
+ }
+}
+
+void SetFileInformationFileName(FILE_INFORMATION_CLASS fileInformationClass,
+ LPVOID address, const std::wstring& fileName)
+{
+ switch (fileInformationClass) {
+ _APPLY_FILEINFO_FN(set_filename, address, fileName);
+ default:
+ break;
+ }
+}
+
+#undef _APP_FINFO_CASE
+#undef _APPLY_FILEINFO_FN
diff --git a/libs/usvfs/src/usvfs_dll/hooks/kernel32.cpp b/libs/usvfs/src/usvfs_dll/hooks/kernel32.cpp
new file mode 100644
index 0000000..6599eb0
--- /dev/null
+++ b/libs/usvfs/src/usvfs_dll/hooks/kernel32.cpp
@@ -0,0 +1,1860 @@
+#include "kernel32.h"
+#include "sharedids.h"
+
+#include "../hookcallcontext.h"
+#include "../hookcontext.h"
+#include "../hookmanager.h"
+#include "../maptracker.h"
+#include <formatters.h>
+#include <inject.h>
+#include <loghelpers.h>
+#include <stringcast.h>
+#include <stringutils.h>
+#include <usvfs.h>
+#include <winapi.h>
+#include <winbase.h>
+
+namespace ush = usvfs::shared;
+using ush::CodePage;
+using ush::string_cast;
+
+namespace usvfs
+{
+MapTracker k32DeleteTracker;
+MapTracker k32FakeDirTracker;
+} // namespace usvfs
+
+class CurrentDirectoryTracker
+{
+public:
+ using wstring = std::wstring;
+
+ bool get(wstring& currentDir, const wchar_t* forRelativePath = nullptr)
+ {
+ int index = m_currentDrive;
+ if (forRelativePath && *forRelativePath && forRelativePath[1] == ':')
+ if (!getDriveIndex(forRelativePath, index))
+ spdlog::get("usvfs")->warn(
+ "CurrentDirectoryTracker::get() invalid drive letter: {}, will use current "
+ "drive {}",
+ string_cast<std::string>(forRelativePath),
+ static_cast<char>('A' + index)); // prints '@' for m_currentDrive == -1
+ if (index < 0)
+ return false;
+
+ std::shared_lock<std::shared_mutex> lock(m_mutex);
+ if (m_perDrive[index].empty())
+ return false;
+ else {
+ currentDir = m_perDrive[index];
+ return true;
+ }
+ }
+
+ bool set(const wstring& currentDir)
+ {
+ int index = -1;
+ bool good = !currentDir.empty() && getDriveIndex(currentDir.c_str(), index) &&
+ currentDir[1] == ':';
+ std::unique_lock<std::shared_mutex> lock(m_mutex);
+ m_currentDrive = good ? index : -1;
+ if (good)
+ m_perDrive[index] = currentDir;
+ return good;
+ }
+
+private:
+ static bool getDriveIndex(const wchar_t* path, int& index)
+ {
+ if (*path >= 'a' && *path <= 'z')
+ index = *path - 'a';
+ else if (*path >= 'A' && *path <= 'Z')
+ index = *path - 'A';
+ else
+ return false;
+ return true;
+ }
+
+ mutable std::shared_mutex m_mutex;
+ wstring m_perDrive['z' - 'a' + 1];
+ int m_currentDrive{-1};
+};
+
+CurrentDirectoryTracker k32CurrentDirectoryTracker;
+
+// attempts to copy source to destination and return the error code
+static inline DWORD copyFileDirect(LPCWSTR source, LPCWSTR destination, bool overwrite)
+{
+ usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::SHELL_FILEOP);
+ return CopyFileExW(source, destination, NULL, NULL, NULL,
+ overwrite ? 0 : COPY_FILE_FAIL_IF_EXISTS)
+ ? ERROR_SUCCESS
+ : GetLastError();
+}
+
+static inline WCHAR pathNameDriveLetter(LPCWSTR path)
+{
+ if (!path || !path[0])
+ return 0;
+ if (path[1] == ':')
+ return path[0];
+ // if path is not ?: or \* then we need to get absolute path:
+ std::wstring buf;
+ if (path[0] != '\\') {
+ buf = winapi::wide::getFullPathName(path).first;
+ path = buf.c_str();
+ if (!path[0] || path[1] == ':')
+ return path[0];
+ }
+ // check for \??\C:
+ if (path[1] && path[2] && path[3] && path[4] && path[0] == '\\' && path[3] == '\\' &&
+ path[5] == ':')
+ return path[4];
+ // give up
+ return 0;
+}
+
+// returns false also in case we fail to determine the drive letter of the path
+static inline bool pathsOnDifferentDrives(LPCWSTR path1, LPCWSTR path2)
+{
+ WCHAR drive1 = pathNameDriveLetter(path1);
+ WCHAR drive2 = pathNameDriveLetter(path2);
+ return drive1 && drive2 && towupper(drive1) != towupper(drive2);
+}
+
+HMODULE WINAPI usvfs::hook_LoadLibraryExA(LPCSTR lpFileName, HANDLE hFile,
+ DWORD dwFlags)
+{
+ HMODULE res = nullptr;
+
+ HOOK_START_GROUP(MutExHookGroup::LOAD_LIBRARY)
+ const std::wstring fileName = ush::string_cast<std::wstring>(lpFileName);
+
+ PRE_REALCALL
+ res = LoadLibraryExW(fileName.c_str(), hFile, dwFlags);
+ POST_REALCALL
+
+ HOOK_END
+ return res;
+}
+
+HMODULE WINAPI usvfs::hook_LoadLibraryExW(LPCWSTR lpFileName, HANDLE hFile,
+ DWORD dwFlags)
+{
+ HMODULE res = nullptr;
+
+ HOOK_START_GROUP(MutExHookGroup::LOAD_LIBRARY)
+ // Why is the usual if (!callContext.active()... check missing?
+
+ RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, lpFileName);
+ PRE_REALCALL
+ res = ::LoadLibraryExW(reroute.fileName(), hFile, dwFlags);
+ POST_REALCALL
+
+ if (reroute.wasRerouted()) {
+ LOG_CALL()
+ .PARAM(lpFileName)
+ .PARAM(reroute.fileName())
+ .PARAM(res)
+ .PARAM(callContext.lastError());
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+/// determine name of the binary to run based on parameters for createprocess
+std::wstring getBinaryName(LPCWSTR applicationName, LPCWSTR lpCommandLine)
+{
+ if (applicationName != nullptr) {
+ std::pair<std::wstring, std::wstring> fullPath =
+ winapi::wide::getFullPathName(applicationName);
+ return fullPath.second;
+ } else {
+ if (lpCommandLine[0] == '"') {
+ const wchar_t* endQuote = wcschr(lpCommandLine, '"');
+ if (endQuote != nullptr) {
+ return std::wstring(lpCommandLine + 1, endQuote - 1);
+ }
+ }
+
+ // according to the documentation, if the commandline is unquoted and has
+ // spaces, it will be interpreted in multiple ways, i.e.
+ // c:\program.exe files\sub dir\program name
+ // c:\program files\sub.exe dir\program name
+ // c:\program files\sub dir\program.exe name
+ // c:\program files\sub dir\program name.exe
+ LPCWSTR space = wcschr(lpCommandLine, L' ');
+ while (space != nullptr) {
+ std::wstring subString(lpCommandLine, space);
+ bool isDirectory = true;
+ if (winapi::ex::wide::fileExists(subString.c_str(), &isDirectory) &&
+ !isDirectory) {
+ return subString;
+ } else {
+ space = wcschr(space + 1, L' ');
+ }
+ }
+ return std::wstring(lpCommandLine);
+ }
+}
+
+BOOL(WINAPI* usvfs::CreateProcessInternalW)(
+ LPVOID token, LPCWSTR lpApplicationName, LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation, LPVOID newToken);
+
+BOOL WINAPI usvfs::hook_CreateProcessInternalW(
+ LPVOID token, LPCWSTR lpApplicationName, LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation, LPVOID newToken)
+{
+ BOOL res = FALSE;
+
+ HOOK_START_GROUP(MutExHookGroup::CREATE_PROCESS)
+ if (!callContext.active()) {
+ res = CreateProcessInternalW(
+ token, lpApplicationName, lpCommandLine, lpProcessAttributes,
+ lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment,
+ lpCurrentDirectory, lpStartupInfo, lpProcessInformation, newToken);
+ callContext.updateLastError();
+ return res;
+ }
+
+ // remember if the caller wanted the process to be suspended. If so, we
+ // don't resume when we're done
+ BOOL susp = dwCreationFlags & CREATE_SUSPENDED;
+ dwCreationFlags |= CREATE_SUSPENDED;
+
+ RerouteW applicationReroute;
+ RerouteW cmdReroute;
+ LPWSTR cend = nullptr;
+
+ std::wstring dllPath;
+ usvfsParameters callParameters;
+
+ { // scope for context lock
+ auto context = READ_CONTEXT();
+
+ if (RerouteW::interestingPath(lpCommandLine)) {
+ // First "argument" in the commandline is the command, we need to identify it and
+ // reroute it:
+ if (*lpCommandLine == '"') {
+ // If the first argument is quoted we trust its is quoted correctly
+ for (cend = lpCommandLine; *cend && *cend != ' '; ++cend)
+ if (*cend == '"') {
+ int escaped = 0;
+ for (++cend; *cend && (*cend != '"' || escaped % 2 != 0); ++cend)
+ escaped = *cend == '\\' ? escaped + 1 : 0;
+ }
+
+ if (*(cend - 1) == '"')
+ --cend;
+ auto old_cend = *cend;
+ *cend = 0;
+ cmdReroute = RerouteW::create(context, callContext, lpCommandLine + 1);
+ *cend = old_cend;
+ if (old_cend == '"')
+ ++cend;
+ } else {
+ // If the first argument we have no choice but to test all the options to quote
+ // the command as the real CreateProcess will do this:
+ cend = lpCommandLine;
+ while (true) {
+ while (*cend && *cend != ' ')
+ ++cend;
+
+ auto old_cend = *cend;
+ *cend = 0;
+ cmdReroute = RerouteW::create(context, callContext, lpCommandLine);
+ *cend = old_cend;
+ if (cmdReroute.wasRerouted() || pathIsFile(cmdReroute.fileName()))
+ break;
+
+ while (*cend == ' ')
+ ++cend;
+
+ if (!*cend) {
+ // if we reached the end of the string we'll just use the whole commandline
+ // as is:
+ cend = nullptr;
+ break;
+ }
+ }
+ }
+ }
+
+ applicationReroute = RerouteW::create(context, callContext, lpApplicationName);
+
+ dllPath = context->dllPath();
+ callParameters = context->callParameters();
+ }
+
+ std::wstring cmdline;
+ if (cend && cmdReroute.fileName()) {
+ auto fileName = cmdReroute.fileName();
+ cmdline.reserve(wcslen(fileName) + wcslen(cend) + 2);
+ if (*fileName != '"')
+ cmdline += L"\"";
+ cmdline += fileName;
+ if (*fileName != '"')
+ cmdline += L"\"";
+ cmdline += cend;
+ }
+
+ PRE_REALCALL
+ res = CreateProcessInternalW(token, applicationReroute.fileName(),
+ cmdline.empty() ? lpCommandLine : &cmdline[0],
+ lpProcessAttributes, lpThreadAttributes, bInheritHandles,
+ dwCreationFlags, lpEnvironment, lpCurrentDirectory,
+ lpStartupInfo, lpProcessInformation, newToken);
+ POST_REALCALL
+
+ BOOL blacklisted = FALSE;
+ { // limit scope of context
+ auto context = READ_CONTEXT();
+ blacklisted = context->executableBlacklisted(applicationReroute.fileName(),
+ cmdReroute.fileName());
+ }
+
+ if (res) {
+ if (!blacklisted) {
+ try {
+ injectProcess(dllPath, callParameters, *lpProcessInformation);
+ } catch (const std::exception& e) {
+ spdlog::get("hooks")->error("failed to inject into {0}: {1}",
+ lpApplicationName != nullptr
+ ? applicationReroute.fileName()
+ : static_cast<LPCWSTR>(lpCommandLine),
+ e.what());
+ }
+ }
+
+ // resume unless process is supposed to start suspended
+ if (!susp && (ResumeThread(lpProcessInformation->hThread) == (DWORD)-1)) {
+ spdlog::get("hooks")->error("failed to inject into spawned process");
+ res = FALSE;
+ }
+ }
+
+ LOG_CALL()
+ .PARAM(lpApplicationName)
+ .PARAM(applicationReroute.fileName())
+ .PARAM(cmdReroute.fileName())
+ .PARAM(res)
+ .PARAM(callContext.lastError())
+ .PARAM(cmdline);
+ HOOK_END
+
+ return res;
+}
+
+BOOL WINAPI usvfs::hook_GetFileAttributesExA(LPCSTR lpFileName,
+ GET_FILEEX_INFO_LEVELS fInfoLevelId,
+ LPVOID lpFileInformation)
+{
+ BOOL res = FALSE;
+
+ HOOK_START_GROUP(MutExHookGroup::FILE_ATTRIBUTES)
+ if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) {
+ res = GetFileAttributesExA(lpFileName, fInfoLevelId, lpFileInformation);
+ callContext.updateLastError();
+ return res;
+ }
+ HOOK_END
+
+ HOOK_START
+ const std::wstring fileName = ush::string_cast<std::wstring>(lpFileName);
+
+ PRE_REALCALL
+ res = GetFileAttributesExW(fileName.c_str(), fInfoLevelId, lpFileInformation);
+ POST_REALCALL
+
+ HOOK_END
+ return res;
+}
+
+BOOL WINAPI usvfs::hook_GetFileAttributesExW(LPCWSTR lpFileName,
+ GET_FILEEX_INFO_LEVELS fInfoLevelId,
+ LPVOID lpFileInformation)
+{
+ BOOL res = FALSE;
+
+ HOOK_START_GROUP(MutExHookGroup::FILE_ATTRIBUTES)
+ if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) {
+ res = GetFileAttributesExW(lpFileName, fInfoLevelId, lpFileInformation);
+ callContext.updateLastError();
+ return res;
+ }
+
+ fs::path canonicalFile = RerouteW::canonizePath(RerouteW::absolutePath(lpFileName));
+
+ RerouteW reroute =
+ RerouteW::create(READ_CONTEXT(), callContext, canonicalFile.c_str());
+
+ PRE_REALCALL
+ res = ::GetFileAttributesExW(reroute.fileName(), fInfoLevelId, lpFileInformation);
+ POST_REALCALL
+
+ DWORD originalError = callContext.lastError();
+ DWORD fixedError = originalError;
+ // In case the target does not exist the error value varies to differentiate if the
+ // parent folder exists (ERROR_FILE_NOT_FOUND) or not (ERROR_PATH_NOT_FOUND).
+ // If the original target's parent folder doesn't actually exist it may exist in the
+ // the virtualized sense, or if we rerouted the query the parent of the original path
+ // might exist while the parent of the rerouted path might not:
+ if (!res && fixedError == ERROR_PATH_NOT_FOUND) {
+ // first query original file parent (if we rerouted it):
+ fs::path originalParent = canonicalFile.parent_path();
+ WIN32_FILE_ATTRIBUTE_DATA parentAttr;
+ if (reroute.wasRerouted() &&
+ ::GetFileAttributesExW(originalParent.c_str(), GetFileExInfoStandard,
+ &parentAttr) &&
+ (parentAttr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ fixedError = ERROR_FILE_NOT_FOUND;
+ else {
+ // now query the rerouted path for parent (which can be different from the parent
+ // of the rerouted path)
+ RerouteW rerouteParent =
+ RerouteW::create(READ_CONTEXT(), callContext, originalParent.c_str());
+ if (rerouteParent.wasRerouted() &&
+ ::GetFileAttributesExW(rerouteParent.fileName(), GetFileExInfoStandard,
+ &parentAttr) &&
+ (parentAttr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ fixedError = ERROR_FILE_NOT_FOUND;
+ }
+ }
+ if (fixedError != originalError)
+ callContext.updateLastError(fixedError);
+
+ if (reroute.wasRerouted() || fixedError != originalError) {
+ DWORD resAttrib;
+ if (res && fInfoLevelId == GetFileExInfoStandard && lpFileInformation)
+ resAttrib = reinterpret_cast<WIN32_FILE_ATTRIBUTE_DATA*>(lpFileInformation)
+ ->dwFileAttributes;
+ else
+ resAttrib = (DWORD)-1;
+ LOG_CALL()
+ .PARAM(lpFileName)
+ .PARAM(reroute.fileName())
+ .PARAMHEX(fInfoLevelId)
+ .PARAMHEX(res)
+ .PARAMHEX(resAttrib)
+ .PARAM(originalError)
+ .PARAM(fixedError);
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+DWORD WINAPI usvfs::hook_GetFileAttributesA(LPCSTR lpFileName)
+{
+ BOOL res = FALSE;
+
+ HOOK_START_GROUP(MutExHookGroup::FILE_ATTRIBUTES)
+ if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) {
+ res = GetFileAttributesA(lpFileName);
+ callContext.updateLastError();
+ return res;
+ }
+ HOOK_END
+
+ HOOK_START
+ const std::wstring fileName = ush::string_cast<std::wstring>(lpFileName);
+
+ PRE_REALCALL
+ res = GetFileAttributesW(fileName.c_str());
+ POST_REALCALL
+
+ HOOK_END
+ return res;
+}
+
+DWORD WINAPI usvfs::hook_GetFileAttributesW(LPCWSTR lpFileName)
+{
+ DWORD res = 0UL;
+
+ HOOK_START_GROUP(MutExHookGroup::FILE_ATTRIBUTES)
+ if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) {
+ res = GetFileAttributesW(lpFileName);
+ callContext.updateLastError();
+ return res;
+ }
+
+ fs::path canonicalFile = RerouteW::canonizePath(RerouteW::absolutePath(lpFileName));
+
+ RerouteW reroute =
+ RerouteW::create(READ_CONTEXT(), callContext, canonicalFile.c_str());
+
+ if (reroute.wasRerouted())
+ PRE_REALCALL
+ res = ::GetFileAttributesW(reroute.fileName());
+ POST_REALCALL
+
+ DWORD originalError = callContext.lastError();
+ DWORD fixedError = originalError;
+ // In case the target does not exist the error value varies to differentiate if the
+ // parent folder exists (ERROR_FILE_NOT_FOUND) or not (ERROR_PATH_NOT_FOUND).
+ // If the original target's parent folder doesn't actually exist it may exist in the
+ // the virtualized sense, or if we rerouted the query the parent of the original path
+ // might exist while the parent of the rerouted path might not:
+ if (res == INVALID_FILE_ATTRIBUTES && fixedError == ERROR_PATH_NOT_FOUND) {
+ // first query original file parent (if we rerouted it):
+ fs::path originalParent = canonicalFile.parent_path();
+ DWORD attr;
+ if (reroute.wasRerouted() &&
+ (attr = ::GetFileAttributesW(originalParent.c_str())) !=
+ INVALID_FILE_ATTRIBUTES &&
+ (attr & FILE_ATTRIBUTE_DIRECTORY))
+ fixedError = ERROR_FILE_NOT_FOUND;
+ else {
+ // now query the rerouted path for parent (which can be different from the parent
+ // of the rerouted path)
+ RerouteW rerouteParent =
+ RerouteW::create(READ_CONTEXT(), callContext, originalParent.c_str());
+ if (rerouteParent.wasRerouted() &&
+ (attr = ::GetFileAttributesW(rerouteParent.fileName())) !=
+ INVALID_FILE_ATTRIBUTES &&
+ (attr & FILE_ATTRIBUTE_DIRECTORY))
+ fixedError = ERROR_FILE_NOT_FOUND;
+ }
+ }
+ if (fixedError != originalError)
+ callContext.updateLastError(fixedError);
+
+ if (reroute.wasRerouted() || fixedError != originalError) {
+ LOG_CALL()
+ .PARAM(lpFileName)
+ .PARAM(reroute.fileName())
+ .PARAMHEX(res)
+ .PARAM(originalError)
+ .PARAM(fixedError);
+ }
+
+ HOOK_ENDP(lpFileName);
+
+ return res;
+}
+
+DWORD WINAPI usvfs::hook_SetFileAttributesW(LPCWSTR lpFileName, DWORD dwFileAttributes)
+{
+ DWORD res = 0UL;
+
+ HOOK_START_GROUP(MutExHookGroup::FILE_ATTRIBUTES)
+ // Why is the usual if (!callContext.active()... check missing?
+
+ RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, lpFileName);
+ PRE_REALCALL
+ res = ::SetFileAttributesW(reroute.fileName(), dwFileAttributes);
+ POST_REALCALL
+
+ if (reroute.wasRerouted()) {
+ LOG_CALL().PARAM(reroute.fileName()).PARAM(res).PARAM(callContext.lastError());
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+BOOL WINAPI usvfs::hook_DeleteFileW(LPCWSTR lpFileName)
+{
+ BOOL res = FALSE;
+
+ HOOK_START_GROUP(MutExHookGroup::DELETE_FILE)
+ // Why is the usual if (!callContext.active()... check missing?
+
+ const std::wstring path =
+ RerouteW::canonizePath(RerouteW::absolutePath(lpFileName)).wstring();
+
+ RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, path.c_str());
+
+ PRE_REALCALL
+ if (reroute.wasRerouted()) {
+ res = ::DeleteFileW(reroute.fileName());
+ } else {
+ res = ::DeleteFileW(path.c_str());
+ }
+ POST_REALCALL
+
+ if (res) {
+ reroute.removeMapping(READ_CONTEXT());
+ }
+
+ if (reroute.wasRerouted())
+ LOG_CALL()
+ .PARAM(lpFileName)
+ .PARAM(reroute.fileName())
+ .PARAM(res)
+ .PARAM(callContext.lastError());
+
+ HOOK_END
+
+ return res;
+}
+
+BOOL rewriteChangedDrives(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName,
+ const usvfs::RerouteW& readReroute,
+ const usvfs::CreateRerouter& writeReroute)
+{
+ return ((readReroute.wasRerouted() || writeReroute.wasRerouted()) &&
+ pathsOnDifferentDrives(readReroute.fileName(), writeReroute.fileName()) &&
+ !pathsOnDifferentDrives(lpExistingFileName, lpNewFileName));
+}
+
+BOOL WINAPI usvfs::hook_MoveFileA(LPCSTR lpExistingFileName, LPCSTR lpNewFileName)
+{
+ BOOL res = FALSE;
+
+ HOOK_START_GROUP(MutExHookGroup::SHELL_FILEOP)
+
+ if (!callContext.active()) {
+ res = MoveFileA(lpExistingFileName, lpNewFileName);
+ callContext.updateLastError();
+ return res;
+ }
+
+ HOOK_END
+ HOOK_START
+
+ const auto& existingFileName = ush::string_cast<std::wstring>(lpExistingFileName);
+ const auto& newFileName = ush::string_cast<std::wstring>(lpNewFileName);
+
+ PRE_REALCALL
+ res = MoveFileW(existingFileName.c_str(), newFileName.c_str());
+ POST_REALCALL
+
+ HOOK_END
+
+ return res;
+}
+
+BOOL WINAPI usvfs::hook_MoveFileW(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName)
+{
+ BOOL res = FALSE;
+
+ HOOK_START_GROUP(MutExHookGroup::SHELL_FILEOP)
+ if (!callContext.active()) {
+ res = MoveFileW(lpExistingFileName, lpNewFileName);
+ callContext.updateLastError();
+ return res;
+ }
+
+ RerouteW readReroute;
+ CreateRerouter writeReroute;
+ bool callOriginal = true;
+ DWORD newFlags = 0;
+
+ {
+ auto context = READ_CONTEXT();
+ readReroute = RerouteW::create(context, callContext, lpExistingFileName);
+ callOriginal = writeReroute.rerouteNew(context, callContext, lpNewFileName, false,
+ "hook_MoveFileW");
+ }
+
+ if (callOriginal) {
+ bool movedDrives = rewriteChangedDrives(lpExistingFileName, lpNewFileName,
+ readReroute, writeReroute);
+ if (movedDrives)
+ newFlags |= MOVEFILE_COPY_ALLOWED;
+
+ bool isDirectory = pathIsDirectory(readReroute.fileName());
+
+ PRE_REALCALL
+ if (isDirectory && movedDrives) {
+ SHFILEOPSTRUCTW sf = {0};
+ sf.wFunc = FO_MOVE;
+ sf.hwnd = 0;
+ sf.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI;
+ sf.pFrom = readReroute.fileName();
+ sf.pTo = writeReroute.fileName();
+ int shRes = ::SHFileOperationW(&sf);
+ switch (shRes) {
+ case 0x78:
+ callContext.updateLastError(ERROR_ACCESS_DENIED);
+ break;
+ case 0x7C:
+ callContext.updateLastError(ERROR_FILE_NOT_FOUND);
+ break;
+ case 0x7E:
+ case 0x80:
+ callContext.updateLastError(ERROR_FILE_EXISTS);
+ break;
+ default:
+ callContext.updateLastError(shRes);
+ }
+ res = shRes == 0;
+ } else if (newFlags)
+ res = ::MoveFileExW(readReroute.fileName(), writeReroute.fileName(), newFlags);
+ else
+ res = ::MoveFileW(readReroute.fileName(), writeReroute.fileName());
+ POST_REALCALL
+
+ if (res)
+ SetLastError(ERROR_SUCCESS);
+
+ writeReroute.updateResult(callContext, res);
+
+ if (res) {
+ readReroute.removeMapping(
+ READ_CONTEXT(), isDirectory); // Updating the rerouteCreate to check deleted
+ // file entries should make this okay
+
+ if (writeReroute.newReroute()) {
+ if (isDirectory)
+ RerouteW::addDirectoryMapping(WRITE_CONTEXT(), fs::path(lpNewFileName),
+ fs::path(writeReroute.fileName()));
+ else
+ writeReroute.insertMapping(WRITE_CONTEXT());
+ }
+ }
+
+ if (readReroute.wasRerouted() || writeReroute.wasRerouted() ||
+ writeReroute.changedError())
+ LOG_CALL()
+ .PARAM(readReroute.fileName())
+ .PARAM(writeReroute.fileName())
+ .PARAMWRAP(newFlags)
+ .PARAM(res)
+ .PARAM(writeReroute.originalError())
+ .PARAM(callContext.lastError());
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+BOOL WINAPI usvfs::hook_MoveFileExA(LPCSTR lpExistingFileName, LPCSTR lpNewFileName,
+ DWORD dwFlags)
+{
+ BOOL res = FALSE;
+
+ HOOK_START_GROUP(MutExHookGroup::SHELL_FILEOP)
+
+ if (!callContext.active()) {
+ res = MoveFileExA(lpExistingFileName, lpNewFileName, dwFlags);
+ callContext.updateLastError();
+ return res;
+ }
+
+ HOOK_END
+ HOOK_START
+
+ const std::wstring existingFileName =
+ ush::string_cast<std::wstring>(lpExistingFileName);
+
+ // careful: lpNewFileName can be null if dwFlags is
+ // MOVEFILE_DELAY_UNTIL_REBOOT, so don't blindly cast the string and make sure
+ // the null pointer is forwarded correctly
+ std::wstring newFileNameWstring;
+ const wchar_t* newFileName = nullptr;
+
+ if (lpNewFileName) {
+ newFileNameWstring = ush::string_cast<std::wstring>(lpNewFileName);
+ newFileName = newFileNameWstring.c_str();
+ }
+
+ PRE_REALCALL
+ res = MoveFileExW(existingFileName.c_str(), newFileName, dwFlags);
+ POST_REALCALL
+
+ HOOK_END
+
+ return res;
+}
+
+BOOL WINAPI usvfs::hook_MoveFileExW(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName,
+ DWORD dwFlags)
+{
+ BOOL res = FALSE;
+
+ HOOK_START_GROUP(MutExHookGroup::SHELL_FILEOP)
+ if (!callContext.active()) {
+ res = MoveFileExW(lpExistingFileName, lpNewFileName, dwFlags);
+ callContext.updateLastError();
+ return res;
+ }
+
+ RerouteW readReroute;
+ CreateRerouter writeReroute;
+ bool callOriginal = true;
+ DWORD newFlags = dwFlags;
+
+ {
+ auto context = READ_CONTEXT();
+ readReroute = RerouteW::create(context, callContext, lpExistingFileName);
+ callOriginal = writeReroute.rerouteNew(context, callContext, lpNewFileName,
+ newFlags & MOVEFILE_REPLACE_EXISTING,
+ "hook_MoveFileExW");
+ }
+
+ if (callOriginal) {
+ bool movedDrives = rewriteChangedDrives(lpExistingFileName, lpNewFileName,
+ readReroute, writeReroute);
+
+ bool isDirectory = pathIsDirectory(readReroute.fileName());
+
+ PRE_REALCALL
+ if (isDirectory && movedDrives) {
+ SHFILEOPSTRUCTW sf = {0};
+ sf.wFunc = FO_MOVE;
+ sf.hwnd = 0;
+ sf.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI;
+ sf.pFrom = readReroute.fileName();
+ sf.pTo = writeReroute.fileName();
+ int shRes = ::SHFileOperationW(&sf);
+ switch (shRes) {
+ case 0x78:
+ callContext.updateLastError(ERROR_ACCESS_DENIED);
+ break;
+ case 0x7C:
+ callContext.updateLastError(ERROR_FILE_NOT_FOUND);
+ break;
+ case 0x7E:
+ case 0x80:
+ callContext.updateLastError(ERROR_FILE_EXISTS);
+ break;
+ default:
+ callContext.updateLastError(shRes);
+ }
+ res = shRes == 0;
+ } else
+ res = ::MoveFileExW(readReroute.fileName(), writeReroute.fileName(), newFlags);
+ POST_REALCALL
+
+ if (res)
+ SetLastError(ERROR_SUCCESS);
+
+ writeReroute.updateResult(callContext, res);
+
+ if (res) {
+ readReroute.removeMapping(
+ READ_CONTEXT(), isDirectory); // Updating the rerouteCreate to check deleted
+ // file entries should make this okay
+
+ if (writeReroute.newReroute()) {
+ if (isDirectory)
+ RerouteW::addDirectoryMapping(WRITE_CONTEXT(), fs::path(lpNewFileName),
+ fs::path(writeReroute.fileName()));
+ else
+ writeReroute.insertMapping(WRITE_CONTEXT());
+ }
+ }
+
+ if (readReroute.wasRerouted() || writeReroute.wasRerouted() ||
+ writeReroute.changedError())
+ LOG_CALL()
+ .PARAM(readReroute.fileName())
+ .PARAM(writeReroute.fileName())
+ .PARAMWRAP(dwFlags)
+ .PARAMWRAP(newFlags)
+ .PARAM(res)
+ .PARAM(writeReroute.originalError())
+ .PARAM(callContext.lastError());
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+BOOL WINAPI usvfs::hook_MoveFileWithProgressA(LPCSTR lpExistingFileName,
+ LPCSTR lpNewFileName,
+ LPPROGRESS_ROUTINE lpProgressRoutine,
+ LPVOID lpData, DWORD dwFlags)
+{
+ BOOL res = FALSE;
+
+ HOOK_START_GROUP(MutExHookGroup::SHELL_FILEOP)
+
+ if (!callContext.active()) {
+ res = MoveFileWithProgressA(lpExistingFileName, lpNewFileName, lpProgressRoutine,
+ lpData, dwFlags);
+ callContext.updateLastError();
+ return res;
+ }
+
+ HOOK_END
+ HOOK_START
+
+ const auto& existingFileName = ush::string_cast<std::wstring>(lpExistingFileName);
+
+ // careful: lpNewFileName can be null if dwFlags is
+ // MOVEFILE_DELAY_UNTIL_REBOOT, so don't blindly cast the string and make sure
+ // the null pointer is forwarded correctly
+ std::wstring newFileNameWstring;
+ const wchar_t* newFileName = nullptr;
+
+ if (lpNewFileName) {
+ newFileNameWstring = ush::string_cast<std::wstring>(lpNewFileName);
+ newFileName = newFileNameWstring.c_str();
+ }
+
+ PRE_REALCALL
+ res = MoveFileWithProgressW(existingFileName.c_str(), newFileName, lpProgressRoutine,
+ lpData, dwFlags);
+ POST_REALCALL
+
+ HOOK_END
+
+ return res;
+}
+
+BOOL WINAPI usvfs::hook_MoveFileWithProgressW(LPCWSTR lpExistingFileName,
+ LPCWSTR lpNewFileName,
+ LPPROGRESS_ROUTINE lpProgressRoutine,
+ LPVOID lpData, DWORD dwFlags)
+{
+
+ // TODO: Remove all redundant hooks to moveFile alternatives.
+ // it would appear that all other moveFile functions end up calling this one with no
+ // additional code.
+ BOOL res = FALSE;
+
+ HOOK_START_GROUP(MutExHookGroup::SHELL_FILEOP)
+ if (!callContext.active()) {
+ res = MoveFileWithProgressW(lpExistingFileName, lpNewFileName, lpProgressRoutine,
+ lpData, dwFlags);
+ callContext.updateLastError();
+ return res;
+ }
+
+ RerouteW readReroute;
+ usvfs::CreateRerouter writeReroute;
+ bool callOriginal = true;
+ DWORD newFlags = dwFlags;
+
+ {
+ auto context = READ_CONTEXT();
+ readReroute = RerouteW::create(context, callContext, lpExistingFileName);
+ callOriginal = writeReroute.rerouteNew(context, callContext, lpNewFileName,
+ newFlags & MOVEFILE_REPLACE_EXISTING,
+ "hook_MoveFileWithProgressW");
+ }
+
+ if (callOriginal) {
+ bool movedDrives = rewriteChangedDrives(lpExistingFileName, lpNewFileName,
+ readReroute, writeReroute);
+ if (movedDrives)
+ newFlags |= MOVEFILE_COPY_ALLOWED;
+
+ bool isDirectory = pathIsDirectory(readReroute.fileName());
+
+ PRE_REALCALL
+ if (isDirectory && movedDrives) {
+ SHFILEOPSTRUCTW sf = {0};
+ sf.wFunc = FO_MOVE;
+ sf.hwnd = 0;
+ sf.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI;
+ sf.pFrom = readReroute.fileName();
+ sf.pTo = writeReroute.fileName();
+ int shRes = ::SHFileOperationW(&sf);
+ switch (shRes) {
+ case 0x78:
+ callContext.updateLastError(ERROR_ACCESS_DENIED);
+ break;
+ case 0x7C:
+ callContext.updateLastError(ERROR_FILE_NOT_FOUND);
+ break;
+ case 0x7E:
+ case 0x80:
+ callContext.updateLastError(ERROR_FILE_EXISTS);
+ break;
+ default:
+ callContext.updateLastError(shRes);
+ }
+ res = shRes == 0;
+ } else
+ res = ::MoveFileWithProgressW(readReroute.fileName(), writeReroute.fileName(),
+ lpProgressRoutine, lpData, newFlags);
+ POST_REALCALL
+
+ if (res)
+ SetLastError(ERROR_SUCCESS);
+
+ writeReroute.updateResult(callContext, res);
+
+ if (res) {
+ // TODO: this call causes the node to be removed twice in case of
+ // MOVEFILE_COPY_ALLOWED as the deleteFile hook lower level already takes care of
+ // it, but deleteFile can't be disabled since we are relying on it in case of
+ // MOVEFILE_REPLACE_EXISTING for the destination file.
+ readReroute.removeMapping(
+ READ_CONTEXT(),
+ isDirectory); // Updating the rerouteCreate to check deleted file entries
+ // should make this okay (not related to comments above)
+
+ if (writeReroute.newReroute()) {
+ if (isDirectory)
+ RerouteW::addDirectoryMapping(WRITE_CONTEXT(), fs::path(lpNewFileName),
+ fs::path(writeReroute.fileName()));
+ else
+ writeReroute.insertMapping(WRITE_CONTEXT());
+ }
+ }
+
+ if (readReroute.wasRerouted() || writeReroute.wasRerouted() ||
+ writeReroute.changedError())
+ LOG_CALL()
+ .PARAM(readReroute.fileName())
+ .PARAM(writeReroute.fileName())
+ .PARAMWRAP(dwFlags)
+ .PARAMWRAP(newFlags)
+ .PARAM(res)
+ .PARAM(writeReroute.originalError())
+ .PARAM(callContext.lastError());
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+BOOL WINAPI usvfs::hook_CopyFileExW(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName,
+ LPPROGRESS_ROUTINE lpProgressRoutine, LPVOID lpData,
+ LPBOOL pbCancel, DWORD dwCopyFlags)
+{
+ BOOL res = FALSE;
+
+ HOOK_START_GROUP(MutExHookGroup::SHELL_FILEOP)
+ if (!callContext.active()) {
+ res = CopyFileExW(lpExistingFileName, lpNewFileName, lpProgressRoutine, lpData,
+ pbCancel, dwCopyFlags);
+ callContext.updateLastError();
+ return res;
+ }
+
+ RerouteW readReroute;
+ usvfs::CreateRerouter writeReroute;
+ bool callOriginal = true;
+
+ {
+ auto context = READ_CONTEXT();
+ readReroute = RerouteW::create(context, callContext, lpExistingFileName);
+ callOriginal = writeReroute.rerouteNew(
+ context, callContext, lpNewFileName,
+ (dwCopyFlags & COPY_FILE_FAIL_IF_EXISTS) == 0, "hook_CopyFileExW");
+ }
+
+ if (callOriginal) {
+ PRE_REALCALL
+ res = ::CopyFileExW(readReroute.fileName(), writeReroute.fileName(),
+ lpProgressRoutine, lpData, pbCancel, dwCopyFlags);
+ POST_REALCALL
+ writeReroute.updateResult(callContext, res);
+
+ if (res && writeReroute.newReroute())
+ writeReroute.insertMapping(WRITE_CONTEXT());
+
+ if (readReroute.wasRerouted() || writeReroute.wasRerouted() ||
+ writeReroute.changedError())
+ LOG_CALL()
+ .PARAM(readReroute.fileName())
+ .PARAM(writeReroute.fileName())
+ .PARAM(res)
+ .PARAM(writeReroute.originalError())
+ .PARAM(callContext.lastError());
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+DWORD WINAPI usvfs::hook_GetCurrentDirectoryA(DWORD nBufferLength, LPSTR lpBuffer)
+{
+ DWORD res = 0;
+
+ HOOK_START
+
+ std::wstring buffer;
+ buffer.resize(nBufferLength);
+
+ PRE_REALCALL
+ res = GetCurrentDirectoryW(nBufferLength, &buffer[0]);
+ POST_REALCALL
+
+ if (res > 0) {
+ res = WideCharToMultiByte(CP_ACP, 0, buffer.c_str(), res + 1, lpBuffer,
+ nBufferLength, nullptr, nullptr);
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+DWORD WINAPI usvfs::hook_GetCurrentDirectoryW(DWORD nBufferLength, LPWSTR lpBuffer)
+{
+ DWORD res = FALSE;
+
+ HOOK_START
+
+ std::wstring actualCWD;
+
+ if (!k32CurrentDirectoryTracker.get(actualCWD)) {
+ PRE_REALCALL
+ res = ::GetCurrentDirectoryW(nBufferLength, lpBuffer);
+ POST_REALCALL
+ } else {
+ ush::wcsncpy_sz(lpBuffer, &actualCWD[0],
+ std::min(static_cast<size_t>(nBufferLength), actualCWD.size() + 1));
+
+ // yupp, that's how GetCurrentDirectory actually works...
+ if (actualCWD.size() < nBufferLength) {
+ res = static_cast<DWORD>(actualCWD.size());
+ } else {
+ res = static_cast<DWORD>(actualCWD.size() + 1);
+ }
+ }
+
+ if (nBufferLength)
+ LOG_CALL()
+ .PARAM(std::wstring(lpBuffer, res))
+ .PARAM(nBufferLength)
+ .PARAM(actualCWD.size())
+ .PARAM(res)
+ .PARAM(callContext.lastError());
+
+ HOOK_END
+
+ return res;
+}
+
+BOOL WINAPI usvfs::hook_SetCurrentDirectoryA(LPCSTR lpPathName)
+{
+ return SetCurrentDirectoryW(ush::string_cast<std::wstring>(lpPathName).c_str());
+}
+
+BOOL WINAPI usvfs::hook_SetCurrentDirectoryW(LPCWSTR lpPathName)
+{
+ BOOL res = FALSE;
+
+ HOOK_START
+
+ const fs::path& realPath = RerouteW::canonizePath(RerouteW::absolutePath(lpPathName));
+ const std::wstring& realPathStr = realPath.wstring();
+ std::wstring finalRoute;
+ BOOL found = FALSE;
+
+ if (fs::exists(realPath))
+ finalRoute = realPathStr;
+ else {
+ WCHAR processDir[MAX_PATH];
+ if (::GetModuleFileNameW(NULL, processDir, MAX_PATH) != 0 &&
+ ::PathRemoveFileSpecW(processDir)) {
+ WCHAR processName[MAX_PATH];
+ ::GetModuleFileNameW(NULL, processName, MAX_PATH);
+ fs::path routedName = realPath / processName;
+ RerouteW rerouteTest =
+ RerouteW::create(READ_CONTEXT(), callContext, routedName.wstring().c_str());
+ if (rerouteTest.wasRerouted()) {
+ std::wstring reroutedPath = rerouteTest.fileName();
+ if (routedName.wstring().find(processDir) != std::string::npos) {
+ fs::path finalPath(reroutedPath);
+ finalRoute = finalPath.parent_path().wstring();
+ found = TRUE;
+ }
+ }
+ }
+
+ if (!found) {
+ RerouteW reroute =
+ RerouteW::create(READ_CONTEXT(), callContext, realPathStr.c_str());
+ finalRoute = reroute.fileName();
+ }
+ }
+
+ PRE_REALCALL
+ res = ::SetCurrentDirectoryW(finalRoute.c_str());
+ POST_REALCALL
+
+ if (res)
+ if (!k32CurrentDirectoryTracker.set(realPathStr))
+ spdlog::get("usvfs")->warn("Updating actual current directory failed: {} ?!",
+ string_cast<std::string>(realPathStr));
+
+ LOG_CALL()
+ .PARAM(lpPathName)
+ .PARAM(realPathStr)
+ .PARAM(finalRoute)
+ .PARAM(res)
+ .PARAM(callContext.lastError());
+
+ HOOK_END
+
+ return res;
+}
+
+DLLEXPORT BOOL WINAPI usvfs::hook_CreateDirectoryW(
+ LPCWSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes)
+{
+ BOOL res = FALSE;
+ HOOK_START
+
+ RerouteW reroute = RerouteW::createOrNew(READ_CONTEXT(), callContext, lpPathName);
+
+ PRE_REALCALL
+ res = ::CreateDirectoryW(reroute.fileName(), lpSecurityAttributes);
+ POST_REALCALL
+
+ if (res && reroute.newReroute())
+ reroute.insertMapping(WRITE_CONTEXT(), true);
+
+ if (reroute.wasRerouted())
+ LOG_CALL()
+ .PARAM(lpPathName)
+ .PARAM(reroute.fileName())
+ .PARAM(res)
+ .PARAM(callContext.lastError());
+
+ HOOK_END
+
+ return res;
+}
+
+DLLEXPORT BOOL WINAPI usvfs::hook_RemoveDirectoryW(LPCWSTR lpPathName)
+{
+
+ BOOL res = FALSE;
+
+ HOOK_START_GROUP(MutExHookGroup::DELETE_FILE)
+ // Why is the usual if (!callContext.active()... check missing?
+
+ RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, lpPathName);
+
+ PRE_REALCALL
+ if (reroute.wasRerouted()) {
+ res = ::RemoveDirectoryW(reroute.fileName());
+ } else {
+ res = ::RemoveDirectoryW(lpPathName);
+ }
+ POST_REALCALL
+
+ if (res) {
+ reroute.removeMapping(READ_CONTEXT(), true);
+ }
+
+ if (reroute.wasRerouted())
+ LOG_CALL()
+ .PARAM(lpPathName)
+ .PARAM(reroute.fileName())
+ .PARAM(res)
+ .PARAM(callContext.lastError());
+
+ HOOK_END
+
+ return res;
+}
+
+DWORD WINAPI usvfs::hook_GetFullPathNameA(LPCSTR lpFileName, DWORD nBufferLength,
+ LPSTR lpBuffer, LPSTR* lpFilePart)
+{
+ DWORD res = 0UL;
+
+ HOOK_START_GROUP(MutExHookGroup::FULL_PATHNAME)
+ if (!callContext.active()) {
+ res = GetFullPathNameA(lpFileName, nBufferLength, lpBuffer, lpFilePart);
+ callContext.updateLastError();
+ return res;
+ }
+
+ std::string resolvedWithCMD;
+
+ std::wstring actualCWD;
+ fs::path filePath = ush::string_cast<std::wstring>(lpFileName, CodePage::UTF8);
+ if (k32CurrentDirectoryTracker.get(actualCWD, filePath.wstring().c_str())) {
+ if (!filePath.is_absolute())
+ resolvedWithCMD = ush::string_cast<std::string>(
+ (actualCWD / filePath.relative_path()).wstring());
+ }
+
+ PRE_REALCALL
+ res =
+ ::GetFullPathNameA(resolvedWithCMD.empty() ? lpFileName : resolvedWithCMD.c_str(),
+ nBufferLength, lpBuffer, lpFilePart);
+ POST_REALCALL
+
+ if (false && nBufferLength)
+ LOG_CALL()
+ .PARAM(lpFileName)
+ .PARAM(resolvedWithCMD)
+ .PARAM(std::string(lpBuffer, res))
+ .PARAM(nBufferLength)
+ .PARAM(res)
+ .PARAM(callContext.lastError());
+
+ HOOK_END
+
+ return res;
+}
+
+DWORD WINAPI usvfs::hook_GetFullPathNameW(LPCWSTR lpFileName, DWORD nBufferLength,
+ LPWSTR lpBuffer, LPWSTR* lpFilePart)
+{
+ DWORD res = 0UL;
+
+ HOOK_START_GROUP(MutExHookGroup::FULL_PATHNAME)
+ if (!callContext.active()) {
+ res = GetFullPathNameW(lpFileName, nBufferLength, lpBuffer, lpFilePart);
+ callContext.updateLastError();
+ return res;
+ }
+
+ std::wstring resolvedWithCMD;
+
+ std::wstring actualCWD;
+ if (k32CurrentDirectoryTracker.get(actualCWD, lpFileName)) {
+ fs::path filePath = lpFileName;
+ if (!filePath.is_absolute())
+ resolvedWithCMD = (actualCWD / filePath.relative_path()).wstring();
+ }
+
+ PRE_REALCALL
+ res =
+ ::GetFullPathNameW(resolvedWithCMD.empty() ? lpFileName : resolvedWithCMD.c_str(),
+ nBufferLength, lpBuffer, lpFilePart);
+ POST_REALCALL
+
+ if (false && nBufferLength)
+ LOG_CALL()
+ .PARAM(lpFileName)
+ .PARAM(resolvedWithCMD)
+ .PARAM(std::wstring(lpBuffer, res))
+ .PARAM(nBufferLength)
+ .PARAM(res)
+ .PARAM(callContext.lastError());
+
+ HOOK_END
+
+ return res;
+}
+
+DWORD WINAPI usvfs::hook_GetModuleFileNameA(HMODULE hModule, LPSTR lpFilename,
+ DWORD nSize)
+{
+ DWORD res = 0UL;
+
+ HOOK_START_GROUP(MutExHookGroup::ALL_GROUPS)
+
+ PRE_REALCALL
+ res = ::GetModuleFileNameA(hModule, lpFilename, nSize);
+ POST_REALCALL
+ if ((res != 0) && callContext.active()) {
+ std::vector<char> buf;
+ // If GetModuleFileNameA failed because the buffer is not large enough this
+ // complicates matters because we are dealing with incomplete information
+ // (consider for example the case that we have a long real path which will
+ // be routed to a short virtual so the call should actually succeed in such
+ // a case). To solve this we simply use our own buffer to find the complete
+ // module path:
+ DWORD full_res = res;
+ size_t buf_size = nSize;
+ while (full_res == buf_size) {
+ buf_size = std::max(static_cast<size_t>(MAX_PATH), buf_size * 2);
+ buf.resize(buf_size);
+ full_res = ::GetModuleFileNameA(hModule, buf.data(), buf_size);
+ }
+
+ RerouteW reroute = RerouteW::create(
+ READ_CONTEXT(), callContext,
+ ush::string_cast<std::wstring>(buf.empty() ? lpFilename : buf.data()).c_str(),
+ true);
+ if (reroute.wasRerouted()) {
+ DWORD reroutedSize = static_cast<DWORD>(wcslen(reroute.fileName()));
+ if (reroutedSize >= nSize) {
+ reroutedSize = nSize - 1;
+ callContext.updateLastError(ERROR_INSUFFICIENT_BUFFER);
+ res = nSize;
+ } else
+ res = reroutedSize;
+ memcpy(lpFilename, ush::string_cast<std::string>(reroute.fileName()).c_str(),
+ reroutedSize * sizeof(lpFilename[0]));
+ lpFilename[reroutedSize] = 0;
+
+ LOG_CALL()
+ .PARAM(hModule)
+ .addParam("lpFilename", (res != 0UL) ? lpFilename : "<not set>")
+ .PARAM(nSize)
+ .PARAM(res)
+ .PARAM(callContext.lastError());
+ }
+ }
+ HOOK_END
+
+ return res;
+}
+
+DWORD WINAPI usvfs::hook_GetModuleFileNameW(HMODULE hModule, LPWSTR lpFilename,
+ DWORD nSize)
+{
+ DWORD res = 0UL;
+
+ HOOK_START_GROUP(MutExHookGroup::ALL_GROUPS)
+
+ PRE_REALCALL
+ res = ::GetModuleFileNameW(hModule, lpFilename, nSize);
+ POST_REALCALL
+ if ((res != 0) && callContext.active()) {
+ std::vector<WCHAR> buf;
+ // If GetModuleFileNameW failed because the buffer is not large enough this
+ // complicates matters because we are dealing with incomplete information (consider
+ // for example the case that we have a long real path which will be routed to a
+ // short virtual so the call should actually succeed in such a case). To solve this
+ // we simply use our own buffer to find the complete module path:
+ DWORD full_res = res;
+ size_t buf_size = nSize;
+ while (full_res == buf_size) {
+ buf_size = std::max(static_cast<size_t>(MAX_PATH), buf_size * 2);
+ buf.resize(buf_size);
+ full_res = ::GetModuleFileNameW(hModule, buf.data(), buf_size);
+ }
+
+ RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext,
+ buf.empty() ? lpFilename : buf.data(), true);
+ if (reroute.wasRerouted()) {
+ DWORD reroutedSize = static_cast<DWORD>(wcslen(reroute.fileName()));
+ if (reroutedSize >= nSize) {
+ reroutedSize = nSize - 1;
+ callContext.updateLastError(ERROR_INSUFFICIENT_BUFFER);
+ res = nSize;
+ } else
+ res = reroutedSize;
+ memcpy(lpFilename, reroute.fileName(), reroutedSize * sizeof(lpFilename[0]));
+ lpFilename[reroutedSize] = 0;
+
+ LOG_CALL()
+ .PARAM(hModule)
+ .addParam("lpFilename", (res != 0UL) ? lpFilename : L"<not set>")
+ .PARAM(nSize)
+ .PARAM(res)
+ .PARAM(callContext.lastError());
+ }
+ }
+ HOOK_END
+
+ return res;
+}
+
+HANDLE WINAPI usvfs::hook_FindFirstFileExW(
+ LPCWSTR lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, LPVOID lpFindFileData,
+ FINDEX_SEARCH_OPS fSearchOp, LPVOID lpSearchFilter, DWORD dwAdditionalFlags)
+{
+ HANDLE res = INVALID_HANDLE_VALUE;
+
+ HOOK_START_GROUP(MutExHookGroup::SEARCH_FILES)
+ if (!callContext.active()) {
+ res = FindFirstFileExW(lpFileName, fInfoLevelId, lpFindFileData, fSearchOp,
+ lpSearchFilter, dwAdditionalFlags);
+ callContext.updateLastError();
+ return res;
+ }
+
+ // FindFirstFileEx() must fail early if the path ends with a slash
+ if (lpFileName) {
+ const auto len = wcslen(lpFileName);
+ if (len > 0) {
+ if (lpFileName[len - 1] == L'\\' || lpFileName[len - 1] == L'/') {
+ spdlog::get("usvfs")->warn(
+ "hook_FindFirstFileExW(): path '{}' ends with slash, always fails",
+ fs::path(lpFileName).string());
+ return INVALID_HANDLE_VALUE;
+ }
+ }
+ }
+
+ fs::path finalPath;
+ RerouteW reroute;
+ fs::path originalPath;
+
+ bool usedRewrite = false;
+
+ // We need to do some trickery here, since we only want to use the hooked
+ // NtQueryDirectoryFile for rerouted locations we need to check if the Directory path
+ // has been routed instead of the full path.
+ originalPath = RerouteW::canonizePath(RerouteW::absolutePath(lpFileName));
+ PRE_REALCALL
+ res = ::FindFirstFileExW(originalPath.c_str(), fInfoLevelId, lpFindFileData,
+ fSearchOp, lpSearchFilter, dwAdditionalFlags);
+ POST_REALCALL
+
+ if (res == INVALID_HANDLE_VALUE) {
+ fs::path searchPath = originalPath.filename();
+ fs::path parentPath = originalPath.parent_path();
+ std::wstring findPath = parentPath.wstring();
+ while (findPath.find(L"*?<>\"", 0, 1) != std::wstring::npos) {
+ searchPath = parentPath.filename() / searchPath;
+ parentPath = parentPath.parent_path();
+ findPath = parentPath.wstring();
+ }
+ reroute = RerouteW::create(READ_CONTEXT(), callContext, parentPath.c_str());
+ if (reroute.wasRerouted()) {
+ finalPath = reroute.fileName();
+ finalPath /= searchPath.wstring();
+ }
+ if (!finalPath.empty()) {
+ PRE_REALCALL
+ usedRewrite = true;
+ res = ::FindFirstFileExW(finalPath.c_str(), fInfoLevelId, lpFindFileData,
+ fSearchOp, lpSearchFilter, dwAdditionalFlags);
+ POST_REALCALL
+ }
+ }
+
+ if (res != INVALID_HANDLE_VALUE) {
+ // store the original search path for use during iteration
+ WRITE_CONTEXT()->customData<SearchHandleMap>(SearchHandles)[res] = lpFileName;
+ }
+
+ LOG_CALL()
+ .PARAM(lpFileName)
+ .PARAM(originalPath.c_str())
+ .PARAM(res)
+ .PARAM(callContext.lastError());
+ if (usedRewrite)
+ LOG_CALL()
+ .PARAM(lpFileName)
+ .PARAM(finalPath.c_str())
+ .PARAM(res)
+ .PARAM(callContext.lastError());
+
+ HOOK_END
+
+ return res;
+}
+
+HRESULT(WINAPI* usvfs::CopyFile2)(PCWSTR pwszExistingFileName, PCWSTR pwszNewFileName,
+ COPYFILE2_EXTENDED_PARAMETERS* pExtendedParameters);
+
+HRESULT WINAPI usvfs::hook_CopyFile2(PCWSTR pwszExistingFileName,
+ PCWSTR pwszNewFileName,
+ COPYFILE2_EXTENDED_PARAMETERS* pExtendedParameters)
+{
+ HRESULT res = E_FAIL;
+
+ typedef HRESULT(WINAPI * CopyFile2_t)(PCWSTR, PCWSTR, COPYFILE2_EXTENDED_PARAMETERS*);
+
+ HOOK_START_GROUP(MutExHookGroup::SHELL_FILEOP)
+ if (!callContext.active()) {
+ res = CopyFile2(pwszExistingFileName, pwszNewFileName, pExtendedParameters);
+ callContext.updateLastError();
+ return res;
+ }
+
+ RerouteW readReroute;
+ CreateRerouter writeReroute;
+ bool callOriginal = true;
+
+ {
+ auto context = READ_CONTEXT();
+ readReroute = RerouteW::create(context, callContext, pwszExistingFileName);
+ callOriginal = writeReroute.rerouteNew(
+ context, callContext, pwszNewFileName,
+ pExtendedParameters &&
+ (pExtendedParameters->dwCopyFlags & COPY_FILE_FAIL_IF_EXISTS) == 0,
+ "hook_CopyFile2");
+ }
+
+ if (callOriginal) {
+ PRE_REALCALL
+ res =
+ CopyFile2(readReroute.fileName(), writeReroute.fileName(), pExtendedParameters);
+ POST_REALCALL
+ writeReroute.updateResult(callContext, SUCCEEDED(res));
+
+ if (SUCCEEDED(res) && writeReroute.newReroute())
+ writeReroute.insertMapping(WRITE_CONTEXT());
+
+ if (readReroute.wasRerouted() || writeReroute.wasRerouted() ||
+ writeReroute.changedError())
+ LOG_CALL()
+ .PARAM(readReroute.fileName())
+ .PARAM(writeReroute.fileName())
+ .PARAM(res)
+ .PARAM(writeReroute.originalError())
+ .PARAM(callContext.lastError());
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+DWORD WINAPI usvfs::hook_GetPrivateProfileStringA(LPCSTR lpAppName, LPCSTR lpKeyName,
+ LPCSTR lpDefault,
+ LPSTR lpReturnedString, DWORD nSize,
+ LPCSTR lpFileName)
+{
+ DWORD res = 0;
+
+ HOOK_START_GROUP(MutExHookGroup::OPEN_FILE)
+
+ if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) {
+ res = ::GetPrivateProfileStringA(lpAppName, lpKeyName, lpDefault, lpReturnedString,
+ nSize, lpFileName);
+ callContext.updateLastError();
+ return res;
+ }
+
+ RerouteW reroute = RerouteW::create(
+ READ_CONTEXT(), callContext, ush::string_cast<std::wstring>(lpFileName).c_str());
+
+ PRE_REALCALL
+ res = ::GetPrivateProfileStringA(
+ lpAppName, lpKeyName, lpDefault, lpReturnedString, nSize,
+ ush::string_cast<std::string>(reroute.fileName()).c_str());
+ POST_REALCALL
+
+ if (reroute.wasRerouted()) {
+ LOG_CALL()
+ .PARAM(lpAppName)
+ .PARAM(lpKeyName)
+ .PARAM(lpFileName)
+ .PARAM(reroute.fileName())
+ .PARAMHEX(res)
+ .PARAM(callContext.lastError());
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+DWORD WINAPI usvfs::hook_GetPrivateProfileStringW(LPCWSTR lpAppName, LPCWSTR lpKeyName,
+ LPCWSTR lpDefault,
+ LPWSTR lpReturnedString, DWORD nSize,
+ LPCWSTR lpFileName)
+{
+ DWORD res = 0;
+
+ HOOK_START_GROUP(MutExHookGroup::OPEN_FILE)
+
+ if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) {
+ res = ::GetPrivateProfileStringW(lpAppName, lpKeyName, lpDefault, lpReturnedString,
+ nSize, lpFileName);
+ callContext.updateLastError();
+ return res;
+ }
+
+ RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, lpFileName);
+
+ PRE_REALCALL
+ res = ::GetPrivateProfileStringW(lpAppName, lpKeyName, lpDefault, lpReturnedString,
+ nSize, reroute.fileName());
+ POST_REALCALL
+
+ if (reroute.wasRerouted()) {
+ LOG_CALL()
+ .PARAM(lpAppName)
+ .PARAM(lpKeyName)
+ .PARAM(lpFileName)
+ .PARAM(reroute.fileName())
+ .PARAMHEX(res)
+ .PARAM(callContext.lastError());
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+DWORD WINAPI usvfs::hook_GetPrivateProfileSectionA(LPCSTR lpAppName,
+ LPSTR lpReturnedString, DWORD nSize,
+ LPCSTR lpFileName)
+{
+ DWORD res = 0;
+
+ HOOK_START_GROUP(MutExHookGroup::OPEN_FILE)
+
+ if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) {
+ res = ::GetPrivateProfileSectionA(lpAppName, lpReturnedString, nSize, lpFileName);
+ callContext.updateLastError();
+ return res;
+ }
+
+ RerouteW reroute = RerouteW::create(
+ READ_CONTEXT(), callContext, ush::string_cast<std::wstring>(lpFileName).c_str());
+
+ PRE_REALCALL
+ res = ::GetPrivateProfileSectionA(
+ lpAppName, lpReturnedString, nSize,
+ ush::string_cast<std::string>(reroute.fileName()).c_str());
+ POST_REALCALL
+
+ if (reroute.wasRerouted()) {
+ LOG_CALL()
+ .PARAM(lpAppName)
+ .PARAM(lpFileName)
+ .PARAM(reroute.fileName())
+ .PARAMHEX(res)
+ .PARAM(callContext.lastError());
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+DWORD WINAPI usvfs::hook_GetPrivateProfileSectionW(LPCWSTR lpAppName,
+ LPWSTR lpReturnedString, DWORD nSize,
+ LPCWSTR lpFileName)
+{
+ DWORD res = 0;
+
+ HOOK_START_GROUP(MutExHookGroup::OPEN_FILE)
+
+ if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) {
+ res = ::GetPrivateProfileSectionW(lpAppName, lpReturnedString, nSize, lpFileName);
+ callContext.updateLastError();
+ return res;
+ }
+
+ RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, lpFileName);
+
+ PRE_REALCALL
+ res = ::GetPrivateProfileSectionW(lpAppName, lpReturnedString, nSize,
+ reroute.fileName());
+ POST_REALCALL
+
+ if (reroute.wasRerouted()) {
+ LOG_CALL()
+ .PARAM(lpAppName)
+ .PARAM(lpFileName)
+ .PARAM(reroute.fileName())
+ .PARAMHEX(res)
+ .PARAM(callContext.lastError());
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+BOOL WINAPI usvfs::hook_WritePrivateProfileStringA(LPCSTR lpAppName, LPCSTR lpKeyName,
+ LPCSTR lpString, LPCSTR lpFileName)
+{
+ BOOL res = false;
+
+ HOOK_START_GROUP(MutExHookGroup::OPEN_FILE)
+
+ if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) {
+ res = ::WritePrivateProfileStringA(lpAppName, lpKeyName, lpString, lpFileName);
+ callContext.updateLastError();
+ return res;
+ }
+
+ CreateRerouter reroute;
+ bool callOriginal = reroute.rerouteNew(
+ READ_CONTEXT(), callContext, ush::string_cast<std::wstring>(lpFileName).c_str(),
+ true, "hook_WritePrivateProfileStringA");
+
+ if (callOriginal) {
+ PRE_REALCALL
+ res = ::WritePrivateProfileStringA(
+ lpAppName, lpKeyName, lpString,
+ ush::string_cast<std::string>(reroute.fileName()).c_str());
+ POST_REALCALL
+ reroute.updateResult(callContext, res);
+
+ if (res && reroute.newReroute())
+ reroute.insertMapping(WRITE_CONTEXT());
+
+ if (reroute.wasRerouted() || reroute.changedError())
+ LOG_CALL()
+ .PARAM(lpAppName)
+ .PARAM(lpKeyName)
+ .PARAM(lpFileName)
+ .PARAM(reroute.fileName())
+ .PARAMHEX(res)
+ .PARAM(reroute.originalError())
+ .PARAM(callContext.lastError());
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+BOOL WINAPI usvfs::hook_WritePrivateProfileStringW(LPCWSTR lpAppName, LPCWSTR lpKeyName,
+ LPCWSTR lpString, LPCWSTR lpFileName)
+{
+ BOOL res = false;
+
+ HOOK_START_GROUP(MutExHookGroup::OPEN_FILE)
+
+ if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) {
+ res = ::WritePrivateProfileStringW(lpAppName, lpKeyName, lpString, lpFileName);
+ callContext.updateLastError();
+ return res;
+ }
+
+ CreateRerouter reroute;
+ bool callOriginal = reroute.rerouteNew(READ_CONTEXT(), callContext, lpFileName, true,
+ "hook_WritePrivateProfileStringW");
+
+ if (callOriginal) {
+ PRE_REALCALL
+ res = ::WritePrivateProfileStringW(lpAppName, lpKeyName, lpString,
+ reroute.fileName());
+ POST_REALCALL
+ reroute.updateResult(callContext, res);
+
+ if (res && reroute.newReroute())
+ reroute.insertMapping(WRITE_CONTEXT());
+
+ if (reroute.wasRerouted() || reroute.changedError())
+ LOG_CALL()
+ .PARAM(lpAppName)
+ .PARAM(lpKeyName)
+ .PARAM(lpFileName)
+ .PARAM(reroute.fileName())
+ .PARAMHEX(res)
+ .PARAM(reroute.originalError())
+ .PARAM(callContext.lastError());
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+VOID WINAPI usvfs::hook_ExitProcess(UINT exitCode)
+{
+ HOOK_START
+
+ {
+ HookContext::Ptr context = WRITE_CONTEXT();
+
+ std::vector<std::future<int>>& delayed = context->delayed();
+
+ if (!delayed.empty()) {
+ // ensure all delayed tasks are completed before we exit the process
+ for (std::future<int>& delayedOp : delayed) {
+ delayedOp.get();
+ }
+ delayed.clear();
+ }
+ }
+
+ // exitprocess doesn't return so logging the call after the real call doesn't
+ // make much sense.
+ // nor does any pre/post call macro
+ LOG_CALL().PARAM(exitCode);
+
+ usvfsDisconnectVFS();
+
+ // HookManager::instance().removeHook("ExitProcess");
+ // PRE_REALCALL
+ ::ExitProcess(exitCode);
+ // POST_REALCALL
+
+ HOOK_END
+}
diff --git a/libs/usvfs/src/usvfs_dll/hooks/kernel32.h b/libs/usvfs/src/usvfs_dll/hooks/kernel32.h
new file mode 100644
index 0000000..7087b2e
--- /dev/null
+++ b/libs/usvfs/src/usvfs_dll/hooks/kernel32.h
@@ -0,0 +1,116 @@
+#pragma once
+
+#include "ntdll_declarations.h"
+#include <dllimport.h>
+
+namespace usvfs
+{
+
+DLLEXPORT BOOL WINAPI hook_GetFileAttributesExA(LPCSTR lpFileName,
+ GET_FILEEX_INFO_LEVELS fInfoLevelId,
+ LPVOID lpFileInformation);
+DLLEXPORT BOOL WINAPI hook_GetFileAttributesExW(LPCWSTR lpFileName,
+ GET_FILEEX_INFO_LEVELS fInfoLevelId,
+ LPVOID lpFileInformation);
+DLLEXPORT DWORD WINAPI hook_GetFileAttributesA(LPCSTR lpFileName);
+DLLEXPORT DWORD WINAPI hook_GetFileAttributesW(LPCWSTR lpFileName);
+DLLEXPORT DWORD WINAPI hook_SetFileAttributesW(LPCWSTR lpFileName,
+ DWORD dwFileAttributes);
+
+DLLEXPORT DWORD WINAPI hook_GetCurrentDirectoryA(DWORD nBufferLength, LPSTR lpBuffer);
+DLLEXPORT DWORD WINAPI hook_GetCurrentDirectoryW(DWORD nBufferLength, LPWSTR lpBuffer);
+DLLEXPORT BOOL WINAPI hook_SetCurrentDirectoryA(LPCSTR lpPathName);
+DLLEXPORT BOOL WINAPI hook_SetCurrentDirectoryW(LPCWSTR lpPathName);
+
+DLLEXPORT DWORD WINAPI hook_GetFullPathNameA(LPCSTR lpFileName, DWORD nBufferLength,
+ LPSTR lpBuffer, LPSTR* lpFilePart);
+DLLEXPORT DWORD WINAPI hook_GetFullPathNameW(LPCWSTR lpFileName, DWORD nBufferLength,
+ LPWSTR lpBuffer, LPWSTR* lpFilePart);
+
+DLLEXPORT BOOL WINAPI hook_CreateDirectoryW(LPCWSTR lpPathName,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes);
+DLLEXPORT BOOL WINAPI hook_RemoveDirectoryW(LPCWSTR lpPathName);
+
+DLLEXPORT BOOL WINAPI hook_DeleteFileW(LPCWSTR lpFileName);
+
+DLLEXPORT BOOL WINAPI hook_MoveFileA(LPCSTR lpExistingFileName, LPCSTR lpNewFileName);
+DLLEXPORT BOOL WINAPI hook_MoveFileW(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName);
+DLLEXPORT BOOL WINAPI hook_MoveFileExA(LPCSTR lpExistingFileName, LPCSTR lpNewFileName,
+ DWORD dwFlags);
+DLLEXPORT BOOL WINAPI hook_MoveFileExW(LPCWSTR lpExistingFileName,
+ LPCWSTR lpNewFileName, DWORD dwFlags);
+DLLEXPORT BOOL WINAPI hook_MoveFileWithProgressA(LPCSTR lpExistingFileName,
+ LPCSTR lpNewFileName,
+ LPPROGRESS_ROUTINE lpProgressRoutine,
+ LPVOID lpData, DWORD dwFlags);
+DLLEXPORT BOOL WINAPI hook_MoveFileWithProgressW(LPCWSTR lpExistingFileName,
+ LPCWSTR lpNewFileName,
+ LPPROGRESS_ROUTINE lpProgressRoutine,
+ LPVOID lpData, DWORD dwFlags);
+;
+
+DLLEXPORT BOOL WINAPI hook_CopyFileExW(LPCWSTR lpExistingFileName,
+ LPCWSTR lpNewFileName,
+ LPPROGRESS_ROUTINE lpProgressRoutine,
+ LPVOID lpData, LPBOOL pbCancel,
+ DWORD dwCopyFlags);
+
+extern HRESULT(WINAPI* CopyFile2)(PCWSTR pwszExistingFileName, PCWSTR pwszNewFileName,
+ COPYFILE2_EXTENDED_PARAMETERS* pExtendedParameters);
+DLLEXPORT HRESULT WINAPI
+hook_CopyFile2(PCWSTR pwszExistingFileName, PCWSTR pwszNewFileName,
+ COPYFILE2_EXTENDED_PARAMETERS* pExtendedParameters);
+
+DLLEXPORT HMODULE WINAPI hook_LoadLibraryExA(LPCSTR lpFileName, HANDLE hFile,
+ DWORD dwFlags);
+DLLEXPORT HMODULE WINAPI hook_LoadLibraryExW(LPCWSTR lpFileName, HANDLE hFile,
+ DWORD dwFlags);
+
+extern BOOL(WINAPI* CreateProcessInternalW)(
+ LPVOID token, LPCWSTR lpApplicationName, LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation, LPVOID newToken);
+DLLEXPORT BOOL WINAPI hook_CreateProcessInternalW(
+ LPVOID token, LPCWSTR lpApplicationName, LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation, LPVOID newToken);
+
+DLLEXPORT DWORD WINAPI hook_GetModuleFileNameA(HMODULE hModule, LPSTR lpFilename,
+ DWORD nSize);
+DLLEXPORT DWORD WINAPI hook_GetModuleFileNameW(HMODULE hModule, LPWSTR lpFilename,
+ DWORD nSize);
+
+DLLEXPORT HANDLE WINAPI hook_FindFirstFileExW(
+ LPCWSTR lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, LPVOID lpFindFileData,
+ FINDEX_SEARCH_OPS fSearchOp, LPVOID lpSearchFilter, DWORD dwAdditionalFlags);
+
+DLLEXPORT DWORD WINAPI hook_GetPrivateProfileStringA(LPCSTR lpAppName, LPCSTR lpKeyName,
+ LPCSTR lpDefault,
+ LPSTR lpReturnedString,
+ DWORD nSize, LPCSTR lpFileName);
+DLLEXPORT DWORD WINAPI hook_GetPrivateProfileStringW(LPCWSTR lpAppName,
+ LPCWSTR lpKeyName,
+ LPCWSTR lpDefault,
+ LPWSTR lpReturnedString,
+ DWORD nSize, LPCWSTR lpFileName);
+DLLEXPORT DWORD WINAPI hook_GetPrivateProfileSectionA(LPCSTR lpAppName,
+ LPSTR lpReturnedString,
+ DWORD nSize, LPCSTR lpFileName);
+DLLEXPORT DWORD WINAPI hook_GetPrivateProfileSectionW(LPCWSTR lpAppName,
+ LPWSTR lpReturnedString,
+ DWORD nSize, LPCWSTR lpFileName);
+DLLEXPORT BOOL WINAPI hook_WritePrivateProfileStringA(LPCSTR lpAppName,
+ LPCSTR lpKeyName, LPCSTR lpString,
+ LPCSTR lpFileName);
+DLLEXPORT BOOL WINAPI hook_WritePrivateProfileStringW(LPCWSTR lpAppName,
+ LPCWSTR lpKeyName,
+ LPCWSTR lpString,
+ LPCWSTR lpFileName);
+
+DLLEXPORT VOID WINAPI hook_ExitProcess(UINT exitCode);
+
+} // namespace usvfs
diff --git a/libs/usvfs/src/usvfs_dll/hooks/ntdll.cpp b/libs/usvfs/src/usvfs_dll/hooks/ntdll.cpp
new file mode 100644
index 0000000..ad53418
--- /dev/null
+++ b/libs/usvfs/src/usvfs_dll/hooks/ntdll.cpp
@@ -0,0 +1,1520 @@
+#include "ntdll.h"
+
+#include <mutex>
+#include <queue>
+#include <set>
+
+#include <boost/filesystem.hpp>
+
+#include <addrtools.h>
+#include <loghelpers.h>
+#include <stringcast.h>
+#include <stringutils.h>
+#include <unicodestring.h>
+#include <usvfs.h>
+
+#include "../hookcallcontext.h"
+#include "../hookcontext.h"
+#include "../maptracker.h"
+#include "../stringcast_boost.h"
+
+#include "file_information_utils.h"
+#include "sharedids.h"
+
+namespace ulog = usvfs::log;
+namespace ush = usvfs::shared;
+namespace bfs = boost::filesystem;
+
+using usvfs::UnicodeString;
+
+// flag definitions below are copied from winternl.h
+#define FILE_SUPERSEDE 0x00000000
+#define FILE_OPEN 0x00000001
+#define FILE_CREATE 0x00000002
+#define FILE_OPEN_IF 0x00000003
+#define FILE_OVERWRITE 0x00000004
+#define FILE_OVERWRITE_IF 0x00000005
+#define FILE_MAXIMUM_DISPOSITION 0x00000005
+
+template <typename T>
+using unique_ptr_deleter = std::unique_ptr<T, void (*)(T*)>;
+
+class RedirectionInfo
+{
+public:
+ UnicodeString path;
+ bool redirected;
+
+ RedirectionInfo() {}
+ RedirectionInfo(UnicodeString path, bool redirected)
+ : path(path), redirected(redirected)
+ {}
+};
+
+class HandleTracker
+{
+public:
+ using handle_type = HANDLE;
+ using info_type = UnicodeString;
+
+ HandleTracker() { insert_current_directory(); }
+
+ info_type lookup(handle_type handle) const
+ {
+ if (valid_handle(handle)) {
+ std::shared_lock<std::shared_mutex> lock(m_mutex);
+ auto find = m_map.find(handle);
+ if (find != m_map.end())
+ return find->second;
+ }
+ return info_type();
+ }
+
+ void insert(handle_type handle, const info_type& info)
+ {
+ if (!valid_handle(handle))
+ return;
+ std::unique_lock<std::shared_mutex> lock(m_mutex);
+ m_map[handle] = info;
+ }
+
+ void erase(handle_type handle)
+ {
+ if (!valid_handle(handle))
+ return;
+ std::unique_lock<std::shared_mutex> lock(m_mutex);
+ m_map.erase(handle);
+ }
+
+private:
+ static bool valid_handle(handle_type handle)
+ {
+ return handle && handle != INVALID_HANDLE_VALUE;
+ }
+
+ void insert_current_directory()
+ {
+ ntdll_declarations_init();
+
+ UNICODE_STRING p{0};
+ RTL_RELATIVE_NAME r{0};
+ NTSTATUS status =
+ RtlDosPathNameToRelativeNtPathName_U_WithStatus(L"x", &p, nullptr, &r);
+ if (status >= 0) {
+ if (r.ContainingDirectory && p.Buffer) {
+ size_t len = p.Length / sizeof(WCHAR);
+ size_t trim = strlen("\\x") + (p.Buffer[len - 1] ? 0 : 1);
+ if (len > trim)
+ insert(r.ContainingDirectory, info_type(p.Buffer, len - trim));
+ }
+ RtlReleaseRelativeName(&r);
+ if (p.Buffer)
+ HeapFree(GetProcessHeap(), 0, p.Buffer);
+ }
+ }
+
+ mutable std::shared_mutex m_mutex;
+ std::unordered_map<handle_type, info_type> m_map;
+};
+
+HandleTracker ntdllHandleTracker;
+
+UnicodeString CreateUnicodeString(const OBJECT_ATTRIBUTES* objectAttributes)
+{
+ UnicodeString result = ntdllHandleTracker.lookup(objectAttributes->RootDirectory);
+ if (objectAttributes->ObjectName != nullptr) {
+ result.appendPath(objectAttributes->ObjectName);
+ }
+ return result;
+}
+
+std::ostream& operator<<(std::ostream& os, const _UNICODE_STRING& str)
+{
+ try {
+ // TODO this does not correctly support surrogate pairs
+ // since the size used here is the number of 16-bit characters in the buffer
+ // whereas
+ // toNarrow expects the actual number of characters.
+ // It will always underestimate though, so worst case scenario we truncate
+ // the string
+ os << ush::string_cast<std::string>(str.Buffer, ush::CodePage::UTF8,
+ str.Length / sizeof(WCHAR));
+ } catch (const std::exception& e) {
+ os << e.what();
+ }
+
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, POBJECT_ATTRIBUTES attr)
+{
+ return operator<<(os, *attr->ObjectName);
+}
+
+RedirectionInfo applyReroute(const usvfs::HookContext::ConstPtr& context,
+ const usvfs::HookCallContext& callContext,
+ const UnicodeString& inPath)
+{
+ RedirectionInfo result;
+ result.path = inPath;
+ result.redirected = false;
+
+ if (callContext.active()) {
+ // see if the file exists in the redirection tree
+ std::string lookupPath = ush::string_cast<std::string>(
+ static_cast<LPCWSTR>(result.path) + 4, ush::CodePage::UTF8);
+ auto node = context->redirectionTable()->findNode(lookupPath.c_str());
+ // if so, replace the file name with the path to the mapped file
+ if ((node.get() != nullptr) &&
+ (!node->data().linkTarget.empty() || node->isDirectory())) {
+ std::wstring reroutePath;
+
+ if (node->data().linkTarget.length() > 0) {
+ reroutePath = ush::string_cast<std::wstring>(node->data().linkTarget.c_str(),
+ ush::CodePage::UTF8);
+ } else {
+ reroutePath =
+ ush::string_cast<std::wstring>(node->path().c_str(), ush::CodePage::UTF8);
+ }
+ if ((*reroutePath.rbegin() == L'\\') && (*lookupPath.rbegin() != '\\')) {
+ reroutePath.resize(reroutePath.size() - 1);
+ }
+ std::replace(reroutePath.begin(), reroutePath.end(), L'/', L'\\');
+ if (reroutePath[1] == L'\\')
+ reroutePath[1] = L'?';
+ result.path = LR"(\??\)" + reroutePath;
+ result.redirected = true;
+ }
+ }
+ return result;
+}
+
+RedirectionInfo applyReroute(const usvfs::CreateRerouter& rerouter)
+{
+ RedirectionInfo result;
+ result.path = rerouter.fileName();
+ result.redirected = rerouter.wasRerouted();
+
+ std::wstring reroutePath(static_cast<LPCWSTR>(result.path));
+ std::replace(reroutePath.begin(), reroutePath.end(), L'/', L'\\');
+ if (reroutePath[1] == L'\\')
+ reroutePath[1] = L'?';
+ if (!((reroutePath[0] == L'\\') && (reroutePath[1] == L'?') &&
+ (reroutePath[2] == L'?') && (reroutePath[3] == L'\\'))) {
+ result.path = LR"(\??\)" + reroutePath;
+ }
+
+ return result;
+}
+
+struct FindCreateTarget
+{
+ usvfs::RedirectionTree::NodePtrT target;
+ void operator()(usvfs::RedirectionTree::NodePtrT node)
+ {
+ if (node->hasFlag(usvfs::shared::FLAG_CREATETARGET)) {
+ target = node;
+ }
+ }
+};
+
+std::pair<UnicodeString, UnicodeString>
+findCreateTarget(const usvfs::HookContext::ConstPtr& context,
+ const UnicodeString& inPath)
+{
+ std::pair<UnicodeString, UnicodeString> result;
+ result.first = inPath;
+ result.second = UnicodeString();
+
+ std::string lookupPath = ush::string_cast<std::string>(
+ static_cast<LPCWSTR>(result.first) + 4, ush::CodePage::UTF8);
+ FindCreateTarget visitor;
+ usvfs::RedirectionTree::VisitorFunction visitorWrapper =
+ [&](const usvfs::RedirectionTree::NodePtrT& node) {
+ visitor(node);
+ };
+ context->redirectionTable()->visitPath(lookupPath, visitorWrapper);
+ if (visitor.target.get() != nullptr) {
+ bfs::path relativePath =
+ ush::make_relative(visitor.target->path(), bfs::path(lookupPath));
+
+ bfs::path target(visitor.target->data().linkTarget.c_str());
+ target /= relativePath;
+
+ result.second = UnicodeString(target.wstring().c_str());
+ winapi::ex::wide::createPath(target.parent_path());
+ }
+ return result;
+}
+
+RedirectionInfo applyReroute(const usvfs::HookContext::ConstPtr& context,
+ const usvfs::HookCallContext& callContext,
+ POBJECT_ATTRIBUTES inAttributes)
+{
+ return applyReroute(context, callContext, CreateUnicodeString(inAttributes));
+}
+
+int NextDividableBy(int number, int divider)
+{
+ return static_cast<int>(
+ ceilf(static_cast<float>(number) / static_cast<float>(divider)) * divider);
+}
+
+// Something is trying to create a variety of files starting with "\Device\",
+// such as "\Device\DeviceApi\Dev\Query", "\Device\MMCSS\MmThread",
+// "\Device\DeviceApi\CMNotify", etc.
+//
+// This used to create a bunch of spurious "Device" and "MMCSS" folders when
+// using Explorer++. Some of these names seem to part of PnP, others from the
+// "Multimedia Class Scheduler Service".
+//
+// There's basically zero information on this stuff online.
+//
+// Regardless, these files ended up being rerouted, so they became something
+// like C:\game\Data\Somewhere\Device\MMCSS\MmThread, which ended up being
+// rerouted to overwrite and created as a fake path
+// "overwrite\Somewhere\Device\MMCSS"
+//
+// These paths are weird, they feel like pipes or something. It's not clear how
+// they should be reliably recognized, so this is a hardcoded check for any
+// path that starts with "\Device\". These paths will be forwarded directly
+// to NtCreateFile/NtOpenFile
+//
+bool isDeviceFile(std::wstring_view name)
+{
+ static const std::wstring_view DevicePrefix(L"\\Device\\");
+
+ // starts with
+ if (name.size() < DevicePrefix.size()) {
+ return false;
+ } else {
+ return (_wcsnicmp(name.data(), DevicePrefix.data(), DevicePrefix.size()) == 0);
+ }
+}
+
+NTSTATUS addNtSearchData(HANDLE hdl, PUNICODE_STRING FileName,
+ const std::wstring& fakeName,
+ FILE_INFORMATION_CLASS FileInformationClass, PVOID& buffer,
+ ULONG& bufferSize, std::set<std::wstring>& foundFiles,
+ HANDLE event, PIO_APC_ROUTINE apcRoutine, PVOID apcContext,
+ BOOLEAN returnSingleEntry)
+{
+ NTSTATUS res = STATUS_NO_SUCH_FILE;
+ if (hdl != INVALID_HANDLE_VALUE) {
+ PVOID lastValidRecord = nullptr;
+ PVOID bufferInit = buffer;
+ IO_STATUS_BLOCK status;
+ res = NtQueryDirectoryFile(hdl, event, apcRoutine, apcContext, &status, buffer,
+ bufferSize, FileInformationClass, returnSingleEntry,
+ FileName, FALSE);
+
+ if ((res != STATUS_SUCCESS) || (status.Information <= 0)) {
+ bufferSize = 0UL;
+ } else {
+ ULONG totalOffset = 0;
+ PVOID lastSkipPos = nullptr;
+
+ while (totalOffset < status.Information) {
+ ULONG offset;
+ std::wstring fileName;
+ GetFileInformationData(FileInformationClass, buffer, offset, fileName);
+ // in case this is a single-file search result and the specified
+ // filename differs from the file name found, replace it in the
+ // information structure
+ if ((totalOffset == 0) && (offset == 0) && (fakeName.length() > 0)) {
+ // if the fake name is larger than what is in the buffer and there is
+ // not enough room, that's a buffer overflow
+ if ((fakeName.length() > fileName.length()) &&
+ ((fakeName.length() - fileName.length()) >
+ (bufferSize - status.Information))) {
+ res = STATUS_BUFFER_OVERFLOW;
+ break;
+ }
+ // WARNING for the case where the fake name is longer this needs to
+ // move back all further results and update the offset first
+ SetFileInformationFileName(FileInformationClass, buffer, fakeName);
+ fileName = fakeName;
+ }
+ bool add = true;
+ if (fileName.length() > 0) {
+ auto insertRes = foundFiles.insert(ush::to_upper(fileName));
+ add = insertRes.second; // add only if we didn't find this file before
+ }
+ if (!add) {
+ if (lastSkipPos == nullptr) {
+ lastSkipPos = buffer;
+ }
+ } else {
+ if (lastSkipPos != nullptr) {
+ memmove(lastSkipPos, buffer, status.Information - totalOffset);
+ ULONG delta = static_cast<ULONG>(ush::AddrDiff(buffer, lastSkipPos));
+ totalOffset -= delta;
+
+ buffer = lastSkipPos;
+ lastSkipPos = nullptr;
+ }
+ lastValidRecord = buffer;
+ }
+
+ if (offset == 0) {
+ offset = static_cast<ULONG>(status.Information) - totalOffset;
+ }
+ buffer = ush::AddrAdd(buffer, offset);
+ totalOffset += offset;
+ }
+
+ if (lastSkipPos != nullptr) {
+ buffer = lastSkipPos;
+ bufferSize = static_cast<ULONG>(ush::AddrDiff(buffer, bufferInit));
+ // null out the unused rest if there is some
+ memset(lastSkipPos, 0, status.Information - bufferSize);
+ } else {
+ bufferSize = static_cast<ULONG>(ush::AddrDiff(buffer, bufferInit));
+ }
+ }
+ if (lastValidRecord != nullptr) {
+ SetFileInformationOffset(FileInformationClass, lastValidRecord, 0);
+ }
+ }
+ return res;
+}
+
+DATA_ID(SearchInfo);
+
+struct Searches
+{
+ struct Info
+ {
+ struct VirtualMatch
+ {
+ // full path to where the file/directory actually is
+ std::wstring realPath;
+ // virtual filename (only filename since it has to be within the searched
+ // directory)
+ // this is left empty when a folder with all its content is mapped to the
+ // search directory
+ std::wstring virtualName;
+ };
+
+ Info() : currentSearchHandle(INVALID_HANDLE_VALUE) {}
+ std::set<std::wstring> foundFiles;
+ HANDLE currentSearchHandle;
+ std::queue<VirtualMatch> virtualMatches;
+ UnicodeString searchPattern;
+ bool regularComplete{false};
+ };
+
+ Searches() = default;
+
+ // must provide a special copy constructor because boost::mutex is
+ // non-copyable
+ Searches(const Searches& reference) : info(reference.info) {}
+
+ Searches& operator=(const Searches&) = delete;
+
+ std::recursive_mutex queryMutex;
+
+ std::map<HANDLE, Info> info;
+};
+
+void gatherVirtualEntries(const UnicodeString& dirName,
+ const usvfs::RedirectionTreeContainer& redir,
+ PUNICODE_STRING FileName, Searches::Info& info)
+{
+ LPCWSTR dirNameW = static_cast<LPCWSTR>(dirName);
+ // fix directory name. I'd love to know why microsoft sometimes uses "\??\" vs
+ // "\\?\"
+ if ((wcsncmp(dirNameW, LR"(\\?\)", 4) == 0) ||
+ (wcsncmp(dirNameW, LR"(\??\)", 4) == 0)) {
+ dirNameW += 4;
+ }
+ auto node = redir->findNode(boost::filesystem::path(dirNameW));
+ if (node.get() != nullptr) {
+ std::string searchPattern =
+ FileName != nullptr
+ ? ush::string_cast<std::string>(FileName->Buffer, ush::CodePage::UTF8)
+ : "*.*";
+
+ boost::replace_all(searchPattern, "\"", ".");
+
+ for (const auto& subNode : node->find(searchPattern)) {
+ if (((subNode->data().linkTarget.length() > 0) || subNode->isDirectory()) &&
+ !subNode->hasFlag(usvfs::shared::FLAG_DUMMY)) {
+ std::wstring vName =
+ ush::string_cast<std::wstring>(subNode->name(), ush::CodePage::UTF8);
+
+ Searches::Info::VirtualMatch m;
+ if (subNode->data().linkTarget.length() > 0) {
+ m = {ush::string_cast<std::wstring>(subNode->data().linkTarget.c_str(),
+ ush::CodePage::UTF8),
+ vName};
+ } else {
+ m = {ush::string_cast<std::wstring>(subNode->path().c_str(),
+ ush::CodePage::UTF8),
+ vName};
+ }
+
+ info.virtualMatches.push(m);
+ info.foundFiles.insert(ush::to_upper(vName));
+ }
+ }
+ }
+}
+
+/**
+ * @brief insert a virtual entry into the search result
+ * @param FileInformation
+ * @param FileInformationClass
+ * @param info
+ * @param realPath path were the actual file resides
+ * @param virtualName virtual file name (without path). will often be the same
+ * as the name component of realpath
+ * @param ReturnSingleEntry
+ * @param dataRead
+ * @return true if a virtual result was added, false if the search handle in the
+ * info object yields no more results
+ */
+bool addVirtualSearchResult(PVOID& FileInformation,
+ FILE_INFORMATION_CLASS FileInformationClass,
+ Searches::Info& info, const std::wstring& realPath,
+ const std::wstring& virtualName, BOOLEAN ReturnSingleEntry,
+ ULONG& dataRead)
+{
+ // this opens a search in the real location, then copies the information about
+ // files we care about (the ones being mapped) to the result we intend to
+ // return
+ bfs::path fullPath(realPath);
+ if (fullPath.filename().wstring() == L".") {
+ fullPath = fullPath.parent_path();
+ }
+ if (info.currentSearchHandle == INVALID_HANDLE_VALUE) {
+ std::wstring dirName = fullPath.parent_path().wstring();
+ if (dirName.length() >= MAX_PATH && !ush::startswith(dirName.c_str(), LR"(\\?\)"))
+ dirName = LR"(\\?\)" + dirName;
+ info.currentSearchHandle =
+ CreateFileW(dirName.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
+ nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
+ }
+ std::wstring fileName =
+ ush::string_cast<std::wstring>(fullPath.filename().string(), ush::CodePage::UTF8);
+ NTSTATUS subRes = addNtSearchData(
+ info.currentSearchHandle,
+ (fileName != L".") ? static_cast<PUNICODE_STRING>(UnicodeString(fileName.c_str()))
+ : nullptr,
+ virtualName, FileInformationClass, FileInformation, dataRead, info.foundFiles,
+ nullptr, nullptr, nullptr, ReturnSingleEntry);
+ if (subRes == STATUS_SUCCESS) {
+ return true;
+ } else {
+ // STATUS_NO_MORE_FILES merely means the search ended, everything else is an
+ // error message. Either way, the search here is finished and we should
+ // resume in the next mapped directory
+ if (subRes != STATUS_NO_MORE_FILES) {
+ spdlog::get("hooks")->warn("error reported listing files in {0}: {1:x}",
+ fullPath.string(), static_cast<uint32_t>(subRes));
+ }
+ return false;
+ }
+}
+
+NTSTATUS WINAPI usvfs::hook_NtQueryDirectoryFile(
+ HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext,
+ PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, ULONG Length,
+ FILE_INFORMATION_CLASS FileInformationClass, BOOLEAN ReturnSingleEntry,
+ PUNICODE_STRING FileName, BOOLEAN RestartScan)
+{
+ PreserveGetLastError ntFunctionsDoNotChangeGetLastError;
+
+ // this is quite messy...
+ // first, this will gather the virtual locations mapping to the iterated one
+ // then we return results from the real location, skipping those that exist
+ // in the virtual locations, as those take precedence
+ // finally the virtual results are returned, adding each result to a skip
+ // list, so they don't get added twice
+ //
+ // if we don't add the regular files first, "." and ".." wouldn't be in the
+ // first search result of wildcard searches which may confuse the caller
+ NTSTATUS res = STATUS_NO_MORE_FILES;
+ HOOK_START_GROUP(MutExHookGroup::FIND_FILES)
+ if (!callContext.active()) {
+ return ::NtQueryDirectoryFile(
+ FileHandle, Event, ApcRoutine, ApcContext, IoStatusBlock, FileInformation,
+ Length, FileInformationClass, ReturnSingleEntry, FileName, RestartScan);
+ }
+
+ // std::unique_lock<std::recursive_mutex> queryLock;
+ std::map<HANDLE, Searches::Info>::iterator infoIter;
+ bool firstSearch = false;
+
+ { // scope to limit context lifetime
+ HookContext::ConstPtr context = READ_CONTEXT();
+ Searches& activeSearches = context->customData<Searches>(SearchInfo);
+ // queryLock = std::unique_lock<std::recursive_mutex>(activeSearches.queryMutex);
+
+ if (RestartScan) {
+ auto iter = activeSearches.info.find(FileHandle);
+ if (iter != activeSearches.info.end()) {
+ activeSearches.info.erase(iter);
+ }
+ }
+
+ // see if we already have a running search
+ infoIter = activeSearches.info.find(FileHandle);
+ firstSearch = (infoIter == activeSearches.info.end());
+ }
+
+ if (firstSearch) {
+ HookContext::Ptr context = WRITE_CONTEXT();
+ Searches& activeSearches = context->customData<Searches>(SearchInfo);
+ // tradeoff time: we store this search status even if no virtual results
+ // were found. This causes a little extra cost here and in NtClose every
+ // time a non-virtual dir is being searched. However if we don't,
+ // whenever NtQueryDirectoryFile is called another time on the same handle,
+ // this (expensive) block would be run again.
+ infoIter =
+ activeSearches.info.insert(std::make_pair(FileHandle, Searches::Info())).first;
+ infoIter->second.searchPattern.appendPath(FileName);
+
+ SearchHandleMap& searchMap = context->customData<SearchHandleMap>(SearchHandles);
+ SearchHandleMap::iterator iter = searchMap.find(FileHandle);
+
+ UnicodeString searchPath;
+ if (iter != searchMap.end()) {
+ searchPath = UnicodeString(iter->second.c_str());
+ infoIter->second.currentSearchHandle = CreateFileW(
+ iter->second.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
+ nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
+ } else {
+ searchPath = ntdllHandleTracker.lookup(FileHandle);
+ }
+ gatherVirtualEntries(searchPath, context->redirectionTable(), FileName,
+ infoIter->second);
+ }
+
+ ULONG dataRead = Length;
+ PVOID FileInformationCurrent = FileInformation;
+
+ // add regular search results, skipping those files we have in a virtual
+ // location
+ bool moreRegular = !infoIter->second.regularComplete;
+ bool dataReturned = false;
+ while (moreRegular && !dataReturned) {
+ dataRead = Length;
+
+ HANDLE handle = infoIter->second.currentSearchHandle;
+ if (handle == INVALID_HANDLE_VALUE) {
+ handle = FileHandle;
+ }
+ NTSTATUS subRes = addNtSearchData(
+ handle, FileName, L"", FileInformationClass, FileInformationCurrent, dataRead,
+ infoIter->second.foundFiles, Event, ApcRoutine, ApcContext, ReturnSingleEntry);
+ moreRegular = subRes == STATUS_SUCCESS;
+ if (moreRegular) {
+ dataReturned = dataRead != 0;
+ } else {
+ infoIter->second.regularComplete = true;
+ infoIter->second.foundFiles.clear();
+ if (infoIter->second.currentSearchHandle != INVALID_HANDLE_VALUE) {
+ ::CloseHandle(infoIter->second.currentSearchHandle);
+ infoIter->second.currentSearchHandle = INVALID_HANDLE_VALUE;
+ }
+ }
+ }
+ if (!moreRegular) {
+ // add virtual results
+ while (!dataReturned && infoIter->second.virtualMatches.size() > 0) {
+ auto match = infoIter->second.virtualMatches.front();
+ if (match.realPath.size() != 0) {
+ dataRead = Length;
+ if (addVirtualSearchResult(FileInformationCurrent, FileInformationClass,
+ infoIter->second, match.realPath, match.virtualName,
+ ReturnSingleEntry, dataRead)) {
+ // a positive result here means the call returned data and there may
+ // be further objects to be retrieved by repeating the call
+ dataReturned = true;
+ } else {
+ // proceed to next search handle
+
+ // TODO: doesn't append search results from more than one redirection
+ // per call. This is bad for performance but otherwise we'd need to
+ // re-write the offsets between information objects
+ infoIter->second.virtualMatches.pop();
+ CloseHandle(infoIter->second.currentSearchHandle);
+ infoIter->second.currentSearchHandle = INVALID_HANDLE_VALUE;
+ }
+ }
+ }
+ }
+
+ if (!dataReturned) {
+ if (firstSearch) {
+ res = STATUS_NO_SUCH_FILE;
+ } else {
+ res = STATUS_NO_MORE_FILES;
+ }
+ } else {
+ res = STATUS_SUCCESS;
+ }
+ IoStatusBlock->Status = res;
+ IoStatusBlock->Information = dataRead;
+
+ size_t numVirtualFiles = infoIter->second.virtualMatches.size();
+ if ((numVirtualFiles > 0)) {
+ LOG_CALL()
+ .addParam("path", ntdllHandleTracker.lookup(FileHandle))
+ .PARAM(FileInformationClass)
+ .PARAM(FileName)
+ .PARAM(numVirtualFiles)
+ .PARAMWRAP(res);
+ }
+
+ HOOK_END
+ return res;
+}
+
+NTSTATUS WINAPI usvfs::hook_NtQueryDirectoryFileEx(
+ HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext,
+ PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, ULONG Length,
+ FILE_INFORMATION_CLASS FileInformationClass, ULONG QueryFlags,
+ PUNICODE_STRING FileName)
+{
+ PreserveGetLastError ntFunctionsDoNotChangeGetLastError;
+ NTSTATUS res = STATUS_NO_MORE_FILES;
+
+ // this is quite messy...
+ // first, this will gather the virtual locations mapping to the iterated one
+ // then we return results from the real location, skipping those that exist
+ // in the virtual locations, as those take precedence
+ // finally the virtual results are returned, adding each result to a skip
+ // list, so they don't get added twice
+ //
+ // if we don't add the regular files first, "." and ".." wouldn't be in the
+ // first search result of wildcard searches which may confuse the caller
+ HOOK_START_GROUP(MutExHookGroup::FIND_FILES)
+ if (!callContext.active()) {
+ return ::NtQueryDirectoryFileEx(FileHandle, Event, ApcRoutine, ApcContext,
+ IoStatusBlock, FileInformation, Length,
+ FileInformationClass, QueryFlags, FileName);
+ }
+
+ // std::unique_lock<std::recursive_mutex> queryLock;
+ std::map<HANDLE, Searches::Info>::iterator infoIter;
+ bool firstSearch = false;
+
+ { // scope to limit context lifetime
+ HookContext::ConstPtr context = READ_CONTEXT();
+ Searches& activeSearches = context->customData<Searches>(SearchInfo);
+ // queryLock = std::unique_lock<std::recursive_mutex>(activeSearches.queryMutex);
+
+ if (QueryFlags & SL_RESTART_SCAN) {
+ auto iter = activeSearches.info.find(FileHandle);
+ if (iter != activeSearches.info.end()) {
+ activeSearches.info.erase(iter);
+ }
+ }
+
+ // see if we already have a running search
+ infoIter = activeSearches.info.find(FileHandle);
+ firstSearch = (infoIter == activeSearches.info.end());
+ }
+
+ if (firstSearch) {
+ HookContext::Ptr context = WRITE_CONTEXT();
+ Searches& activeSearches = context->customData<Searches>(SearchInfo);
+ // tradeoff time: we store this search status even if no virtual results
+ // were found. This causes a little extra cost here and in NtClose every
+ // time a non-virtual dir is being searched. However if we don't,
+ // whenever NtQueryDirectoryFile is called another time on the same handle,
+ // this (expensive) block would be run again.
+ infoIter =
+ activeSearches.info.insert(std::make_pair(FileHandle, Searches::Info())).first;
+ infoIter->second.searchPattern.appendPath(FileName);
+
+ SearchHandleMap& searchMap = context->customData<SearchHandleMap>(SearchHandles);
+ SearchHandleMap::iterator iter = searchMap.find(FileHandle);
+
+ UnicodeString searchPath;
+ if (iter != searchMap.end()) {
+ searchPath = UnicodeString(iter->second.c_str());
+ infoIter->second.currentSearchHandle = CreateFileW(
+ iter->second.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
+ nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
+ } else {
+ searchPath = ntdllHandleTracker.lookup(FileHandle);
+ }
+ gatherVirtualEntries(searchPath, context->redirectionTable(), FileName,
+ infoIter->second);
+ }
+
+ ULONG dataRead = Length;
+ PVOID FileInformationCurrent = FileInformation;
+
+ // add regular search results, skipping those files we have in a virtual
+ // location
+ bool moreRegular = !infoIter->second.regularComplete;
+ bool dataReturned = false;
+ while (moreRegular && !dataReturned) {
+ dataRead = Length;
+
+ HANDLE handle = infoIter->second.currentSearchHandle;
+ if (handle == INVALID_HANDLE_VALUE) {
+ handle = FileHandle;
+ }
+ NTSTATUS subRes = addNtSearchData(handle, FileName, L"", FileInformationClass,
+ FileInformationCurrent, dataRead,
+ infoIter->second.foundFiles, Event, ApcRoutine,
+ ApcContext, QueryFlags & SL_RETURN_SINGLE_ENTRY);
+ moreRegular = subRes == STATUS_SUCCESS;
+ if (moreRegular) {
+ dataReturned = dataRead != 0;
+ } else {
+ infoIter->second.regularComplete = true;
+ infoIter->second.foundFiles.clear();
+ if (infoIter->second.currentSearchHandle != INVALID_HANDLE_VALUE) {
+ ::CloseHandle(infoIter->second.currentSearchHandle);
+ infoIter->second.currentSearchHandle = INVALID_HANDLE_VALUE;
+ }
+ }
+ }
+ if (!moreRegular) {
+ // add virtual results
+ while (!dataReturned && infoIter->second.virtualMatches.size() > 0) {
+ auto match = infoIter->second.virtualMatches.front();
+ if (match.realPath.size() != 0) {
+ dataRead = Length;
+ if (addVirtualSearchResult(FileInformationCurrent, FileInformationClass,
+ infoIter->second, match.realPath, match.virtualName,
+ QueryFlags & SL_RETURN_SINGLE_ENTRY, dataRead)) {
+ // a positive result here means the call returned data and there may
+ // be further objects to be retrieved by repeating the call
+ dataReturned = true;
+ } else {
+ // proceed to next search handle
+
+ // TODO: doesn't append search results from more than one redirection
+ // per call. This is bad for performance but otherwise we'd need to
+ // re-write the offsets between information objects
+ infoIter->second.virtualMatches.pop();
+ CloseHandle(infoIter->second.currentSearchHandle);
+ infoIter->second.currentSearchHandle = INVALID_HANDLE_VALUE;
+ }
+ }
+ }
+ }
+
+ if (!dataReturned) {
+ if (firstSearch) {
+ res = STATUS_NO_SUCH_FILE;
+ } else {
+ res = STATUS_NO_MORE_FILES;
+ }
+ } else {
+ res = STATUS_SUCCESS;
+ }
+ IoStatusBlock->Status = res;
+ IoStatusBlock->Information = dataRead;
+
+ size_t numVirtualFiles = infoIter->second.virtualMatches.size();
+ if ((numVirtualFiles > 0)) {
+ LOG_CALL()
+ .addParam("path", ntdllHandleTracker.lookup(FileHandle))
+ .PARAM(FileInformationClass)
+ .PARAM(FileName)
+ .PARAM(QueryFlags)
+ .PARAM(numVirtualFiles)
+ .PARAMWRAP(res);
+ }
+
+ HOOK_END
+ return res;
+}
+
+DLLEXPORT NTSTATUS WINAPI usvfs::hook_NtQueryObject(
+ HANDLE Handle, OBJECT_INFORMATION_CLASS ObjectInformationClass,
+ PVOID ObjectInformation, ULONG ObjectInformationLength, PULONG ReturnLength)
+{
+ NTSTATUS res = STATUS_SUCCESS;
+
+ HOOK_START_GROUP(MutExHookGroup::FILE_ATTRIBUTES)
+ if (!callContext.active()) {
+ return ::NtQueryObject(Handle, ObjectInformationClass, ObjectInformation,
+ ObjectInformationLength, ReturnLength);
+ }
+
+ PRE_REALCALL
+ res = ::NtQueryObject(Handle, ObjectInformationClass, ObjectInformation,
+ ObjectInformationLength, ReturnLength);
+ POST_REALCALL
+
+ // we handle both SUCCESS and BUFFER_OVERFLOW since the fixed name might be
+ // smaller than the original one
+ //
+ // we only handle STATUS_INFO_LENGTH_MISMATCH if ReturnLength is not NULL since
+ // this is only returned if the length is too small to hold the structure itself
+ // (regardless of the name), in which case, we need to compute our own ReturnLength
+ //
+ if ((res == STATUS_SUCCESS || res == STATUS_BUFFER_OVERFLOW ||
+ (res == STATUS_INFO_LENGTH_MISMATCH && ReturnLength)) &&
+ (ObjectInformationClass == ObjectNameInformation)) {
+ const auto trackerInfo = ntdllHandleTracker.lookup(Handle);
+ const auto redir = applyReroute(READ_CONTEXT(), callContext, trackerInfo);
+
+ OBJECT_NAME_INFORMATION* info =
+ reinterpret_cast<OBJECT_NAME_INFORMATION*>(ObjectInformation);
+
+ if (redir.redirected) {
+ // https://learn.microsoft.com/en-us/windows/win32/fileio/displaying-volume-paths
+ //
+
+ // TODO: is that always true?
+ // path should start with \??\X: - we need to replace this by device name
+ //
+ WCHAR deviceName[MAX_PATH];
+ std::wstring buffer(static_cast<LPCWSTR>(trackerInfo));
+ buffer[6] = L'\0';
+
+ QueryDosDeviceW(buffer.data() + 4, deviceName, ARRAYSIZE(deviceName));
+
+ buffer = std::wstring(deviceName) + L'\\' +
+ std::wstring(buffer.data() + 7, buffer.size() - 7);
+
+ // the name is put in the buffer AFTER the struct, so the required size if
+ // sizeof(OBJECT_NAME_INFORMATION) + the number of bytes for the name + 2 bytes
+ // for a wide null character
+ const auto requiredLength =
+ sizeof(OBJECT_NAME_INFORMATION) + buffer.size() * 2 + 2;
+ if (ObjectInformationLength < requiredLength) {
+ // if the status was info length mismatch, we keep it, we are just going to
+ // update *ReturnLength
+ if (res != STATUS_INFO_LENGTH_MISMATCH) {
+ res = STATUS_BUFFER_OVERFLOW;
+ }
+
+ if (ReturnLength) {
+ *ReturnLength = static_cast<ULONG>(requiredLength);
+ }
+ } else {
+ // put the unicode buffer at the end of the object
+ const USHORT unicodeBufferLength = static_cast<USHORT>(std::min(
+ static_cast<unsigned long long>(std::numeric_limits<USHORT>::max()),
+ static_cast<unsigned long long>(ObjectInformationLength -
+ sizeof(OBJECT_NAME_INFORMATION))));
+ LPWSTR unicodeBuffer = reinterpret_cast<LPWSTR>(
+ static_cast<LPSTR>(ObjectInformation) + sizeof(OBJECT_NAME_INFORMATION));
+
+ // copy the path into the buffer
+ wmemcpy_s(unicodeBuffer, unicodeBufferLength, buffer.data(), buffer.size());
+
+ // set the null character
+ unicodeBuffer[buffer.size()] = L'\0';
+
+ // update the actual unicode string
+ info->Name.Buffer = unicodeBuffer;
+ info->Name.Length = static_cast<USHORT>(buffer.size() * 2);
+ info->Name.MaximumLength = unicodeBufferLength;
+
+ res = STATUS_SUCCESS;
+ }
+ }
+
+ auto logger = LOG_CALL()
+ .PARAMWRAP(res)
+ .PARAM(ObjectInformationLength)
+ .addParam("return_length", ReturnLength ? *ReturnLength : -1)
+ .addParam("tracker_path", trackerInfo)
+ .PARAM(ObjectInformationClass)
+ .PARAM(redir.redirected)
+ .PARAM(redir.path);
+
+ if (res == STATUS_SUCCESS) {
+ logger.addParam("name_info", info->Name);
+ } else {
+ logger.addParam("name_info", "");
+ }
+ }
+
+ HOOK_END
+ return res;
+}
+
+DLLEXPORT NTSTATUS WINAPI usvfs::hook_NtQueryInformationFile(
+ HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation,
+ ULONG Length, FILE_INFORMATION_CLASS FileInformationClass)
+{
+ NTSTATUS res = STATUS_SUCCESS;
+
+ HOOK_START_GROUP(MutExHookGroup::FILE_ATTRIBUTES)
+ if (!callContext.active()) {
+ return ::NtQueryInformationFile(FileHandle, IoStatusBlock, FileInformation, Length,
+ FileInformationClass);
+ }
+
+ PRE_REALCALL
+ res = ::NtQueryInformationFile(FileHandle, IoStatusBlock, FileInformation, Length,
+ FileInformationClass);
+ POST_REALCALL
+
+ // we handle both SUCCESS and BUFFER_OVERFLOW since the fixed name might be
+ // smaller than the original one
+ //
+ // we do not handle STATUS_INFO_LENGTH_MISMATCH because this is only returned if
+ // the length is too small to old the structure itself (regardless of the name)
+ //
+ // TODO: currently, this does not handle STATUS_BUFFER_OVERLOW for ALL information
+ // because most of the structures would need to be manually filled, which is very
+ // complicated - this should not pose huge issue
+ //
+ if (((res == STATUS_SUCCESS || res == STATUS_BUFFER_OVERFLOW) &&
+ (FileInformationClass == FileNameInformation ||
+ FileInformationClass == FileNormalizedNameInformation)) ||
+ (res == STATUS_SUCCESS && FileInformationClass == FileAllInformation)) {
+
+ const auto trackerInfo = ntdllHandleTracker.lookup(FileHandle);
+ const auto redir = applyReroute(READ_CONTEXT(), callContext, trackerInfo);
+
+ // TODO: difference between FileNameInformation and FileNormalizedNameInformation
+
+ // maximum length in bytes - the required length is
+ // - for ALL, 100 + the number of bytes in the name (not account for null character)
+ // - for NAME, 4 + the number of bytes in the name (not accounting for null
+ // character)
+ //
+ // it is close to the sizeof the structure + the number of bytes in the name, except
+ // for the alignment that gives us a bit more space
+ //
+ ULONG prefixStructLength;
+ FILE_NAME_INFORMATION* info;
+ if (FileInformationClass == FileAllInformation) {
+ info = &reinterpret_cast<FILE_ALL_INFORMATION*>(FileInformation)->NameInformation;
+ prefixStructLength = sizeof(FILE_ALL_INFORMATION) - 4;
+ } else {
+ info = reinterpret_cast<FILE_NAME_INFORMATION*>(FileInformation);
+ prefixStructLength = sizeof(FILE_NAME_INFORMATION) - 4;
+ }
+
+ if (redir.redirected) {
+ auto requiredLength = prefixStructLength + 2 * (trackerInfo.size() - 6);
+ if (Length < requiredLength) {
+ res = STATUS_BUFFER_OVERFLOW;
+ } else {
+ // strip the \??\X: prefix (X being the drive name)
+ LPCWSTR filenameFixed = static_cast<LPCWSTR>(trackerInfo) + 6;
+
+ // not using SetInfoFilename because the length is not set and we do not need to
+ // 0-out the memory here
+ info->FileNameLength = static_cast<ULONG>((trackerInfo.size() - 6) * 2);
+ wmemcpy(info->FileName, filenameFixed, trackerInfo.size() - 6);
+ res = STATUS_SUCCESS;
+
+ // update status block, Information is the number of bytes written, basically
+ // the required length
+ IoStatusBlock->Status = STATUS_SUCCESS;
+ IoStatusBlock->Information = static_cast<ULONG_PTR>(requiredLength);
+ }
+ }
+
+ LOG_CALL()
+ .PARAMWRAP(res)
+ .addParam("tracker_path", trackerInfo)
+ .PARAM(FileInformationClass)
+ .PARAM(redir.redirected)
+ .PARAM(redir.path)
+ .addParam("name_info", res == STATUS_SUCCESS
+ ? std::wstring{info->FileName,
+ info->FileNameLength / sizeof(WCHAR)}
+ : std::wstring{});
+ }
+
+ HOOK_END
+ return res;
+}
+
+unique_ptr_deleter<OBJECT_ATTRIBUTES>
+makeObjectAttributes(RedirectionInfo& redirInfo, POBJECT_ATTRIBUTES attributeTemplate)
+{
+ if (redirInfo.redirected) {
+ unique_ptr_deleter<OBJECT_ATTRIBUTES> result(new OBJECT_ATTRIBUTES,
+ [](OBJECT_ATTRIBUTES* ptr) {
+ delete ptr;
+ });
+ memcpy(result.get(), attributeTemplate, sizeof(OBJECT_ATTRIBUTES));
+ result->RootDirectory = nullptr;
+ result->ObjectName = static_cast<PUNICODE_STRING>(redirInfo.path);
+ return result;
+ } else {
+ // just reuse the template with a dummy deleter
+ return unique_ptr_deleter<OBJECT_ATTRIBUTES>(attributeTemplate,
+ [](OBJECT_ATTRIBUTES*) {});
+ }
+}
+
+DLLEXPORT NTSTATUS WINAPI usvfs::hook_NtQueryInformationByName(
+ POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock,
+ PVOID FileInformation, ULONG Length, FILE_INFORMATION_CLASS FileInformationClass)
+{
+ NTSTATUS res = STATUS_SUCCESS;
+
+ HOOK_START_GROUP(MutExHookGroup::FILE_ATTRIBUTES)
+
+ if (!callContext.active()) {
+ res = ::NtQueryInformationByName(ObjectAttributes, IoStatusBlock, FileInformation,
+ Length, FileInformationClass);
+ callContext.updateLastError();
+ return res;
+ }
+
+ RedirectionInfo redir =
+ applyReroute(READ_CONTEXT(), callContext, CreateUnicodeString(ObjectAttributes));
+
+ if (redir.redirected) {
+ auto newObjectAttributes = makeObjectAttributes(redir, ObjectAttributes);
+
+ PRE_REALCALL
+ res = ::NtQueryInformationByName(newObjectAttributes.get(), IoStatusBlock,
+ FileInformation, Length, FileInformationClass);
+ POST_REALCALL
+
+ LOG_CALL()
+ .PARAMWRAP(res)
+ .addParam("input_path", *ObjectAttributes->ObjectName)
+ .addParam("reroute_path", redir.path);
+ } else {
+ PRE_REALCALL
+ res = ::NtQueryInformationByName(ObjectAttributes, IoStatusBlock, FileInformation,
+ Length, FileInformationClass);
+ POST_REALCALL
+ }
+
+ HOOK_END
+ return res;
+}
+
+NTSTATUS ntdll_mess_NtOpenFile(PHANDLE FileHandle, ACCESS_MASK DesiredAccess,
+ POBJECT_ATTRIBUTES ObjectAttributes,
+ PIO_STATUS_BLOCK IoStatusBlock, ULONG ShareAccess,
+ ULONG OpenOptions)
+{
+ using namespace usvfs;
+
+ PreserveGetLastError ntFunctionsDoNotChangeGetLastError;
+
+ NTSTATUS res = STATUS_NO_SUCH_FILE;
+
+ HOOK_START_GROUP(MutExHookGroup::OPEN_FILE)
+ // Why is the usual if (!callContext.active()... check missing?
+
+ bool storePath = false;
+ if (((OpenOptions & FILE_DIRECTORY_FILE) != 0UL) &&
+ ((OpenOptions & FILE_OPEN_FOR_BACKUP_INTENT) != 0UL)) {
+ // this may be an attempt to open a directory handle for iterating.
+ // If so we need to treat it a little bit differently
+ /* usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::FILE_ATTRIBUTES);
+ FILE_BASIC_INFORMATION dummy;
+ storePath = FAILED(NtQueryAttributesFile(ObjectAttributes, &dummy));*/
+ storePath = true;
+ }
+
+ UnicodeString fullName = CreateUnicodeString(ObjectAttributes);
+
+ if (isDeviceFile(static_cast<LPCWSTR>(fullName))) {
+ return ::NtOpenFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock,
+ ShareAccess, OpenOptions);
+ }
+
+ UnicodeString Path = ntdllHandleTracker.lookup(ObjectAttributes->RootDirectory);
+
+ std::wstring checkpath =
+ ush::string_cast<std::wstring>(static_cast<LPCWSTR>(Path), ush::CodePage::UTF8);
+
+ if ((fullName.size() == 0) ||
+ (GetFileSize(ObjectAttributes->RootDirectory, nullptr) != INVALID_FILE_SIZE)) {
+ // //relative paths that we don't have permission over will fail here due that we
+ // can't get the filesize of the root directory
+ // //We should try again to see if it is a directory using another method
+ if ((fullName.size() == 0) ||
+ (GetFileAttributesW((LPCWSTR)checkpath.c_str()) == INVALID_FILE_ATTRIBUTES)) {
+ return ::NtOpenFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock,
+ ShareAccess, OpenOptions);
+ }
+ }
+
+ try {
+ RedirectionInfo redir = applyReroute(READ_CONTEXT(), callContext, fullName);
+ unique_ptr_deleter<OBJECT_ATTRIBUTES> adjustedAttributes =
+ makeObjectAttributes(redir, ObjectAttributes);
+
+ PRE_REALCALL
+ res = ::NtOpenFile(FileHandle, DesiredAccess, adjustedAttributes.get(),
+ IoStatusBlock, ShareAccess, OpenOptions);
+ POST_REALCALL
+ if (SUCCEEDED(res) && storePath) {
+ // store the original search path for use during iteration
+ READ_CONTEXT()->customData<SearchHandleMap>(SearchHandles)[*FileHandle] =
+ static_cast<LPCWSTR>(fullName);
+#pragma message("need to clean up this handle in CloseHandle call")
+ }
+
+ if (redir.redirected) {
+ LOG_CALL()
+ .addParam("source", ObjectAttributes)
+ .addParam("rerouted", adjustedAttributes.get())
+ .PARAM(*FileHandle)
+ .PARAM(OpenOptions)
+ .PARAMWRAP(res);
+ }
+ } catch (const std::exception&) {
+ PRE_REALCALL
+ res = ::NtOpenFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock,
+ ShareAccess, OpenOptions);
+ POST_REALCALL
+ }
+ HOOK_END
+
+ return res;
+}
+
+NTSTATUS WINAPI usvfs::hook_NtOpenFile(PHANDLE FileHandle, ACCESS_MASK DesiredAccess,
+ POBJECT_ATTRIBUTES ObjectAttributes,
+ PIO_STATUS_BLOCK IoStatusBlock,
+ ULONG ShareAccess, ULONG OpenOptions)
+{
+ NTSTATUS res = ntdll_mess_NtOpenFile(FileHandle, DesiredAccess, ObjectAttributes,
+ IoStatusBlock, ShareAccess, OpenOptions);
+ if (res >= 0 && ObjectAttributes && FileHandle &&
+ GetFileType(*FileHandle) == FILE_TYPE_DISK)
+ ntdllHandleTracker.insert(*FileHandle, CreateUnicodeString(ObjectAttributes));
+ return res;
+}
+
+NTSTATUS ntdll_mess_NtCreateFile(PHANDLE FileHandle, ACCESS_MASK DesiredAccess,
+ POBJECT_ATTRIBUTES ObjectAttributes,
+ PIO_STATUS_BLOCK IoStatusBlock,
+ PLARGE_INTEGER AllocationSize, ULONG FileAttributes,
+ ULONG ShareAccess, ULONG CreateDisposition,
+ ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength)
+{
+ using namespace usvfs;
+
+ NTSTATUS res = STATUS_NO_SUCH_FILE;
+
+ PreserveGetLastError ntFunctionsDoNotChangeGetLastError;
+
+ HOOK_START_GROUP(MutExHookGroup::OPEN_FILE)
+ if (!callContext.active()) {
+ return ::NtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock,
+ AllocationSize, FileAttributes, ShareAccess,
+ CreateDisposition, CreateOptions, EaBuffer, EaLength);
+ }
+
+ UnicodeString inPath = CreateUnicodeString(ObjectAttributes);
+ LPCWSTR inPathW = static_cast<LPCWSTR>(inPath);
+
+ if (inPath.size() == 0) {
+ spdlog::get("hooks")->info(
+ "failed to set from handle: {0}",
+ ush::string_cast<std::string>(ObjectAttributes->ObjectName->Buffer));
+ return ::NtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock,
+ AllocationSize, FileAttributes, ShareAccess,
+ CreateDisposition, CreateOptions, EaBuffer, EaLength);
+ }
+
+ if (isDeviceFile(inPathW)) {
+ return ::NtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock,
+ AllocationSize, FileAttributes, ShareAccess,
+ CreateDisposition, CreateOptions, EaBuffer, EaLength);
+ }
+
+ DWORD convertedDisposition = OPEN_EXISTING;
+ switch (CreateDisposition) {
+ case FILE_SUPERSEDE:
+ convertedDisposition = CREATE_ALWAYS;
+ break;
+ case FILE_OPEN:
+ convertedDisposition = OPEN_EXISTING;
+ break;
+ case FILE_CREATE:
+ convertedDisposition = CREATE_NEW;
+ break;
+ case FILE_OPEN_IF:
+ convertedDisposition = OPEN_ALWAYS;
+ break;
+ case FILE_OVERWRITE:
+ convertedDisposition = TRUNCATE_EXISTING;
+ break;
+ case FILE_OVERWRITE_IF:
+ convertedDisposition = CREATE_ALWAYS;
+ break;
+ default:
+ spdlog::get("hooks")->error("invalid disposition: {0}", CreateDisposition);
+ break;
+ }
+
+ DWORD convertedAccess = 0;
+ if ((DesiredAccess & FILE_GENERIC_READ) == FILE_GENERIC_READ)
+ convertedAccess |= GENERIC_READ;
+ if ((DesiredAccess & FILE_GENERIC_WRITE) == FILE_GENERIC_WRITE)
+ convertedAccess |= GENERIC_WRITE;
+ if ((DesiredAccess & FILE_GENERIC_EXECUTE) == FILE_GENERIC_EXECUTE)
+ convertedAccess |= GENERIC_EXECUTE;
+ if ((DesiredAccess & FILE_ALL_ACCESS) == FILE_ALL_ACCESS)
+ convertedAccess |= GENERIC_ALL;
+
+ ULONG originalDisposition = CreateDisposition;
+ CreateRerouter rerouter;
+ if (rerouter.rerouteCreate(
+ READ_CONTEXT(), callContext, inPathW, convertedDisposition, convertedAccess,
+ (LPSECURITY_ATTRIBUTES)ObjectAttributes->SecurityDescriptor)) {
+ switch (convertedDisposition) {
+ case CREATE_NEW:
+ CreateDisposition = FILE_CREATE;
+ break;
+ case CREATE_ALWAYS:
+ if (CreateDisposition != FILE_SUPERSEDE)
+ CreateDisposition = FILE_OVERWRITE_IF;
+ break;
+ case OPEN_EXISTING:
+ CreateDisposition = FILE_OPEN;
+ break;
+ case OPEN_ALWAYS:
+ CreateDisposition = FILE_OPEN_IF;
+ break;
+ case TRUNCATE_EXISTING:
+ CreateDisposition = FILE_OVERWRITE;
+ break;
+ }
+
+ RedirectionInfo redir = applyReroute(rerouter);
+
+ unique_ptr_deleter<OBJECT_ATTRIBUTES> adjustedAttributes =
+ makeObjectAttributes(redir, ObjectAttributes);
+
+ PRE_REALCALL
+ res = ::NtCreateFile(FileHandle, DesiredAccess, adjustedAttributes.get(),
+ IoStatusBlock, AllocationSize, FileAttributes, ShareAccess,
+ CreateDisposition, CreateOptions, EaBuffer, EaLength);
+ POST_REALCALL
+ rerouter.updateResult(callContext, res == STATUS_SUCCESS);
+
+ if (res == STATUS_SUCCESS) {
+ if (rerouter.newReroute())
+ rerouter.insertMapping(WRITE_CONTEXT());
+
+ if (rerouter.isDir() && rerouter.wasRerouted() &&
+ ((FileAttributes & FILE_OPEN_FOR_BACKUP_INTENT) ==
+ FILE_OPEN_FOR_BACKUP_INTENT)) {
+ // store the original search path for use during iteration
+ WRITE_CONTEXT()->customData<SearchHandleMap>(SearchHandles)[*FileHandle] =
+ inPathW;
+ }
+ }
+
+ if (rerouter.wasRerouted() || rerouter.changedError() ||
+ originalDisposition != CreateDisposition) {
+ LOG_CALL()
+ .PARAM(inPathW)
+ .PARAM(rerouter.fileName())
+ .PARAMHEX(DesiredAccess)
+ .PARAMHEX(originalDisposition)
+ .PARAMHEX(CreateDisposition)
+ .PARAMHEX(FileAttributes)
+ .PARAMHEX(res)
+ .PARAMHEX(*FileHandle)
+ .PARAM(rerouter.originalError())
+ .PARAM(rerouter.error());
+ }
+ } else {
+ // make the original call to set up the proper errors and return statuses
+ PRE_REALCALL
+ res = ::NtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock,
+ AllocationSize, FileAttributes, ShareAccess, CreateDisposition,
+ CreateOptions, EaBuffer, EaLength);
+ POST_REALCALL
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+NTSTATUS WINAPI usvfs::hook_NtCreateFile(PHANDLE FileHandle, ACCESS_MASK DesiredAccess,
+ POBJECT_ATTRIBUTES ObjectAttributes,
+ PIO_STATUS_BLOCK IoStatusBlock,
+ PLARGE_INTEGER AllocationSize,
+ ULONG FileAttributes, ULONG ShareAccess,
+ ULONG CreateDisposition, ULONG CreateOptions,
+ PVOID EaBuffer, ULONG EaLength)
+{
+ NTSTATUS res = ntdll_mess_NtCreateFile(FileHandle, DesiredAccess, ObjectAttributes,
+ IoStatusBlock, AllocationSize, FileAttributes,
+ ShareAccess, CreateDisposition, CreateOptions,
+ EaBuffer, EaLength);
+ if (res >= 0 && ObjectAttributes && FileHandle &&
+ GetFileType(*FileHandle) == FILE_TYPE_DISK)
+ ntdllHandleTracker.insert(*FileHandle, CreateUnicodeString(ObjectAttributes));
+ return res;
+}
+
+NTSTATUS WINAPI usvfs::hook_NtClose(HANDLE Handle)
+{
+ PreserveGetLastError ntFunctionsDoNotChangeGetLastError;
+
+ NTSTATUS res = STATUS_NO_SUCH_FILE;
+
+ HOOK_START_GROUP(MutExHookGroup::ALL_GROUPS)
+ bool log = false;
+
+ if ((::GetFileType(Handle) == FILE_TYPE_DISK)) {
+ HookContext::Ptr context = WRITE_CONTEXT();
+
+ { // clean up search data associated with this handle part 1
+ Searches& activeSearches = context->customData<Searches>(SearchInfo);
+ // std::lock_guard<std::recursive_mutex> lock(activeSearches.queryMutex);
+ auto iter = activeSearches.info.find(Handle);
+ if (iter != activeSearches.info.end()) {
+ if (iter->second.currentSearchHandle != INVALID_HANDLE_VALUE) {
+ ::CloseHandle(iter->second.currentSearchHandle);
+ }
+
+ activeSearches.info.erase(iter);
+ log = true;
+ }
+ }
+
+ {
+ SearchHandleMap& searchHandles =
+ context->customData<SearchHandleMap>(SearchHandles);
+ auto iter = searchHandles.find(Handle);
+ if (iter != searchHandles.end()) {
+ searchHandles.erase(iter);
+ log = true;
+ }
+ }
+ }
+
+ if (GetFileType(Handle) == FILE_TYPE_DISK)
+ ntdllHandleTracker.erase(Handle);
+
+ PRE_REALCALL
+ res = ::NtClose(Handle);
+ POST_REALCALL
+
+ if (log) {
+ LOG_CALL().PARAM(Handle).PARAMWRAP(res);
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+NTSTATUS WINAPI usvfs::hook_NtQueryAttributesFile(
+ POBJECT_ATTRIBUTES ObjectAttributes, PFILE_BASIC_INFORMATION FileInformation)
+{
+ PreserveGetLastError ntFunctionsDoNotChangeGetLastError;
+
+ NTSTATUS res = STATUS_SUCCESS;
+
+ HOOK_START_GROUP(MutExHookGroup::FILE_ATTRIBUTES)
+ // Why is the usual if (!callContext.active()... check missing?
+
+ UnicodeString inPath = CreateUnicodeString(ObjectAttributes);
+
+ RedirectionInfo redir = applyReroute(READ_CONTEXT(), callContext, inPath);
+ unique_ptr_deleter<OBJECT_ATTRIBUTES> adjustedAttributes =
+ makeObjectAttributes(redir, ObjectAttributes);
+
+ PRE_REALCALL
+ res = ::NtQueryAttributesFile(adjustedAttributes.get(), FileInformation);
+ POST_REALCALL
+
+ if (redir.redirected) {
+ LOG_CALL()
+ .addParam("source", ObjectAttributes)
+ .addParam("rerouted", adjustedAttributes.get())
+ .PARAMWRAP(res);
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+NTSTATUS WINAPI usvfs::hook_NtQueryFullAttributesFile(
+ POBJECT_ATTRIBUTES ObjectAttributes, PFILE_NETWORK_OPEN_INFORMATION FileInformation)
+{
+ PreserveGetLastError ntFunctionsDoNotChangeGetLastError;
+
+ NTSTATUS res = STATUS_SUCCESS;
+
+ HOOK_START_GROUP(MutExHookGroup::FILE_ATTRIBUTES)
+
+ if (!callContext.active()) {
+ return ::NtQueryFullAttributesFile(ObjectAttributes, FileInformation);
+ }
+
+ UnicodeString inPath;
+ try {
+ inPath = CreateUnicodeString(ObjectAttributes);
+ } catch (const std::exception&) {
+ return ::NtQueryFullAttributesFile(ObjectAttributes, FileInformation);
+ }
+
+ RedirectionInfo redir = applyReroute(READ_CONTEXT(), callContext, inPath);
+ unique_ptr_deleter<OBJECT_ATTRIBUTES> adjustedAttributes =
+ makeObjectAttributes(redir, ObjectAttributes);
+
+ PRE_REALCALL
+ res = ::NtQueryFullAttributesFile(adjustedAttributes.get(), FileInformation);
+ POST_REALCALL
+
+ if (redir.redirected) {
+ LOG_CALL()
+ .addParam("source", ObjectAttributes)
+ .addParam("rerouted", adjustedAttributes.get())
+ .PARAMWRAP(res);
+ }
+
+ HOOK_END
+
+ return res;
+}
+
+NTSTATUS WINAPI usvfs::hook_NtTerminateProcess(HANDLE ProcessHandle,
+ NTSTATUS ExitStatus)
+{
+ // this hook is normally called when terminating another process, in which
+ // case there's nothing to do
+ //
+ // if the current process exits normally, the ExitProcess() hook is called
+ // and disconnects from the vfs; if the current process crashes, this hook
+ // is not called, the process just dies
+ //
+ // but a process can also terminate itself, bypassing ExitProcess(), and
+ // ending up here, in which case the vfs should be disconnected
+ //
+ // NtTerminateProcess() can be called two different ways to terminate the
+ // current process:
+ // - with a valid handle for the current process, or
+ // - with -1, because that's what GetCurrentProcess() returns
+ //
+ // it's unclear what a NULL handle represents, the behaviour is not
+ // documented anywhere, but looking at ReactOS, it's also interpreted as the
+ // current process
+
+ NTSTATUS res = STATUS_SUCCESS;
+
+ HOOK_START
+
+ const bool isCurrentProcess = ProcessHandle == (HANDLE)-1 || ProcessHandle == 0 ||
+ GetProcessId(ProcessHandle) == GetCurrentProcessId();
+
+ if (isCurrentProcess) {
+ usvfsDisconnectVFS();
+ }
+
+ res = ::NtTerminateProcess(ProcessHandle, ExitStatus);
+
+ HOOK_END
+
+ return res;
+}
diff --git a/libs/usvfs/src/usvfs_dll/hooks/ntdll.h b/libs/usvfs/src/usvfs_dll/hooks/ntdll.h
new file mode 100644
index 0000000..f3db4a6
--- /dev/null
+++ b/libs/usvfs/src/usvfs_dll/hooks/ntdll.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#include <dllimport.h>
+#include <loghelpers.h>
+#include <ntdll_declarations.h>
+
+namespace usvfs
+{
+
+DLLEXPORT NTSTATUS WINAPI
+hook_NtQueryFullAttributesFile(POBJECT_ATTRIBUTES ObjectAttributes,
+ PFILE_NETWORK_OPEN_INFORMATION FileInformation);
+
+DLLEXPORT NTSTATUS WINAPI hook_NtQueryAttributesFile(
+ POBJECT_ATTRIBUTES ObjectAttributes, PFILE_BASIC_INFORMATION FileInformation);
+
+DLLEXPORT NTSTATUS WINAPI hook_NtQueryDirectoryFile(
+ HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext,
+ PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, ULONG Length,
+ FILE_INFORMATION_CLASS FileInformationClass, BOOLEAN ReturnSingleEntry,
+ PUNICODE_STRING FileName, BOOLEAN RestartScan);
+
+DLLEXPORT NTSTATUS WINAPI hook_NtQueryDirectoryFileEx(
+ HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext,
+ PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, ULONG Length,
+ FILE_INFORMATION_CLASS FileInformationClass, ULONG QueryFlags,
+ PUNICODE_STRING FileName);
+
+DLLEXPORT NTSTATUS WINAPI hook_NtQueryObject(
+ HANDLE Handle, OBJECT_INFORMATION_CLASS ObjectInformationClass,
+ PVOID ObjectInformation, ULONG ObjectInformationLength, PULONG ReturnLength);
+
+DLLEXPORT NTSTATUS WINAPI hook_NtQueryInformationFile(
+ HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation,
+ ULONG Length, FILE_INFORMATION_CLASS FileInformationClass);
+
+DLLEXPORT NTSTATUS WINAPI hook_NtQueryInformationByName(
+ POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock,
+ PVOID FileInformation, ULONG Length, FILE_INFORMATION_CLASS FileInformationClass);
+
+DLLEXPORT NTSTATUS WINAPI hook_NtOpenFile(PHANDLE FileHandle, ACCESS_MASK DesiredAccess,
+ POBJECT_ATTRIBUTES ObjectAttributes,
+ PIO_STATUS_BLOCK IoStatusBlock,
+ ULONG ShareAccess, ULONG OpenOptions);
+
+DLLEXPORT NTSTATUS WINAPI hook_NtCreateFile(
+ PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes,
+ PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes,
+ ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer,
+ ULONG EaLength);
+
+DLLEXPORT NTSTATUS WINAPI hook_NtClose(HANDLE Handle);
+
+DLLEXPORT NTSTATUS WINAPI hook_NtTerminateProcess(HANDLE ProcessHandle,
+ NTSTATUS ExitStatus);
+
+} // namespace usvfs
diff --git a/libs/usvfs/src/usvfs_dll/hooks/sharedids.h b/libs/usvfs/src/usvfs_dll/hooks/sharedids.h
new file mode 100644
index 0000000..9a7531c
--- /dev/null
+++ b/libs/usvfs/src/usvfs_dll/hooks/sharedids.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "../hookcontext.h"
+
+typedef std::map<HANDLE, std::wstring> SearchHandleMap;
+
+// maps handles opened for searching to the original search path, which is
+// necessary if the handle creation was rerouted
+DATA_ID(SearchHandles);