diff --git a/src/shared/ntdll_declarations.h b/src/shared/ntdll_declarations.h
index 0f262b3f..1cc096ba 100644
--- a/src/shared/ntdll_declarations.h
+++ b/src/shared/ntdll_declarations.h
@@ -141,6 +141,7 @@ typedef struct _FILE_REPARSE_POINT_INFORMATION {
ULONG Tag;
} FILE_REPARSE_POINT_INFORMATION, *PFILE_REPARSE_POINT_INFORMATION;
+// copied from ntstatus.h
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L)
#define STATUS_NO_MORE_FILES ((NTSTATUS)0x80000006L)
diff --git a/src/shared/stringcast_basic.h b/src/shared/stringcast_basic.h
index 0dde7d28..70e5e708 100644
--- a/src/shared/stringcast_basic.h
+++ b/src/shared/stringcast_basic.h
@@ -18,6 +18,8 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with usvfs. If not, see .
*/
+#pragma once
+
#include
#include
#include
diff --git a/src/usvfs_dll/hookmanager.cpp b/src/usvfs_dll/hookmanager.cpp
index 1ab0cf14..7bb1634b 100644
--- a/src/usvfs_dll/hookmanager.cpp
+++ b/src/usvfs_dll/hookmanager.cpp
@@ -231,13 +231,6 @@ void HookManager::initHooks()
installHook(kbaseMod, k32Mod, "GetFileAttributesW", hook_GetFileAttributesW);
installHook(kbaseMod, k32Mod, "SetFileAttributesW", hook_SetFileAttributesW);
- // Unfortunately, at least on windows 10 1709 x64 the CreateFileA and CreateFile2 translate
- // to CreateFileInternal directly (so hooking CreateFileW alone is not enough)
- installHook(kbaseMod, k32Mod, "CreateFileW", hook_CreateFileW);
- installHook(kbaseMod, k32Mod, "CreateFileA", hook_CreateFileA);
- if (IsWindows8OrGreater())
- installHook(kbaseMod, k32Mod, "CreateFile2", hook_CreateFile2, reinterpret_cast(&CreateFile2));
-
installHook(kbaseMod, k32Mod, "CreateDirectoryW", hook_CreateDirectoryW);
installHook(kbaseMod, k32Mod, "RemoveDirectoryW", hook_RemoveDirectoryW);
installHook(kbaseMod, k32Mod, "DeleteFileW", hook_DeleteFileW);
diff --git a/src/usvfs_dll/hooks/kernel32.cpp b/src/usvfs_dll/hooks/kernel32.cpp
index 60230bbe..485bacb9 100644
--- a/src/usvfs_dll/hooks/kernel32.cpp
+++ b/src/usvfs_dll/hooks/kernel32.cpp
@@ -4,6 +4,8 @@
#include "../hookmanager.h"
#include "../hookcontext.h"
#include "../hookcallcontext.h"
+#include "../maptracker.h"
+
#include
#include
#include
@@ -30,54 +32,10 @@ namespace ush = usvfs::shared;
using ush::string_cast;
using ush::CodePage;
-class MapTracker {
-public:
- using wstring = std::wstring;
-
- wstring lookup(const wstring& fromPath) const {
- if (!fromPath.empty())
- {
- std::shared_lock lock(m_mutex);
- auto find = m_map.find(fromPath);
- if (find != m_map.end())
- return find->second;
- }
- return wstring();
- }
-
- bool contains(const wstring& fromPath) const {
- if (!fromPath.empty())
- {
- std::shared_lock lock(m_mutex);
- auto find = m_map.find(fromPath);
- if (find != m_map.end())
- return true;
- }
- return false;
- }
-
- void insert(const wstring& fromPath, const wstring& toPath) {
- if (fromPath.empty())
- return;
- std::unique_lock lock(m_mutex);
- m_map[fromPath] = toPath;
- }
-
- bool erase(const wstring& fromPath)
- {
- if (fromPath.empty())
- return false;
- std::unique_lock lock(m_mutex);
- return m_map.erase(fromPath);
- }
-
-private:
- mutable std::shared_mutex m_mutex;
- std::unordered_map m_map;
-};
-
+namespace usvfs {
MapTracker k32DeleteTracker;
MapTracker k32FakeDirTracker;
+} // namespace usvfs
class CurrentDirectoryTracker {
public:
@@ -131,37 +89,9 @@ class CurrentDirectoryTracker {
CurrentDirectoryTracker k32CurrentDirectoryTracker;
-// returns true iff the path exists (checks only real paths)
-static inline bool pathExists(LPCWSTR fileName)
-{
- usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::FILE_ATTRIBUTES);
- DWORD attrib = GetFileAttributesW(fileName);
- return attrib != INVALID_FILE_ATTRIBUTES;
-}
-// returns true iff the path exists and is a file (checks only real paths)
-static inline bool pathIsFile(LPCWSTR fileName)
-{
- usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::FILE_ATTRIBUTES);
- DWORD attrib = GetFileAttributesW(fileName);
- return attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) == 0;
-}
-// returns true iff the path exists and is a file (checks only real paths)
-static inline bool pathIsDirectory(LPCWSTR fileName)
-{
- usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::FILE_ATTRIBUTES);
- DWORD attrib = GetFileAttributesW(fileName);
- return attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY);
-}
-// returns true iff the path does not exist but it parent directory does (checks only real paths)
-static inline bool pathDirectlyAvailable(LPCWSTR pathName)
-{
- usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::FILE_ATTRIBUTES);
- DWORD attrib = GetFileAttributesW(pathName);
- return attrib == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND;
-}
// attempts to copy source to destination and return the error code
static inline DWORD copyFileDirect(LPCWSTR source, LPCWSTR destination, bool overwrite)
@@ -201,442 +131,7 @@ static inline bool pathsOnDifferentDrives(LPCWSTR path1, LPCWSTR path2)
return drive1 && drive2 && towupper(drive1) != towupper(drive2);
}
-class RerouteW
-{
- std::wstring m_Buffer{};
- std::wstring m_RealPath{};
- bool m_Rerouted{false};
- LPCWSTR m_FileName{nullptr};
- bool m_PathCreated{false};
- bool m_NewReroute{false};
-
- usvfs::RedirectionTree::NodePtrT m_FileNode;
-
-public:
- RerouteW() = default;
-
- RerouteW(RerouteW &&reference)
- : m_Buffer(std::move(reference.m_Buffer))
- , m_RealPath(std::move(reference.m_RealPath))
- , m_Rerouted(reference.m_Rerouted)
- , m_PathCreated(reference.m_PathCreated)
- , m_NewReroute(reference.m_NewReroute)
- , m_FileNode(std::move(reference.m_FileNode))
- {
- m_FileName = reference.m_FileName != nullptr ? m_Buffer.c_str() : nullptr;
- reference.m_FileName = nullptr;
- }
-
- RerouteW &operator=(RerouteW &&reference)
- {
- m_Buffer = std::move(reference.m_Buffer);
- m_RealPath = std::move(reference.m_RealPath);
- m_Rerouted = reference.m_Rerouted;
- m_PathCreated = reference.m_PathCreated;
- m_NewReroute = reference.m_NewReroute;
- m_FileName = reference.m_FileName != nullptr ? m_Buffer.c_str() : nullptr;
- m_FileNode = std::move(reference.m_FileNode);
- return *this;
- }
-
- RerouteW(const RerouteW &reference) = delete;
- RerouteW &operator=(const RerouteW &) = delete;
-
- LPCWSTR fileName() const
- {
- return m_FileName;
- }
-
- const std::wstring &buffer() const
- {
- return m_Buffer;
- }
-
- bool wasRerouted() const
- {
- return m_Rerouted;
- }
-
- bool newReroute() const
- {
- return m_NewReroute;
- }
-
- void insertMapping(const usvfs::HookContext::Ptr &context, bool directory = false)
- {
- if (directory)
- {
- addDirectoryMapping(context, m_RealPath, m_FileName);
-
- // In case we have just created a "fake" directory, it is no longer fake and need to remove it and all its
- // parent folders from the fake map:
- std::wstring dir = m_FileName;
- while (k32FakeDirTracker.erase(dir))
- dir = fs::path(dir).parent_path().wstring();
- }
- else
- {
- //if (m_PathCreated)
- //addDirectoryMapping(context, fs::path(m_RealPath).parent_path(), fs::path(m_FileName).parent_path());
-
- spdlog::get("hooks")->info("mapping file in vfs: {}, {}",
- ush::string_cast(m_RealPath, ush::CodePage::UTF8),
- ush::string_cast(m_FileName, ush::CodePage::UTF8));
- m_FileNode =
- context->redirectionTable().addFile(m_RealPath, usvfs::RedirectionDataLocal(string_cast(m_FileName, CodePage::UTF8)));
-
- k32DeleteTracker.erase(m_RealPath);
- }
- }
-
- void removeMapping(const usvfs::HookContext::ConstPtr &readContext, bool directory = false)
- {
- bool addToDelete = false;
- bool dontAddToDelete = false;
-
- // We need to track deleted files even if they were not rerouted (i.e. files deleted from the real folder which there is
- // a virtualized mapped folder on top of it). Since we don't want to add, *every* file which is deleted we check this:
- bool found = wasRerouted();
- if (!found) {
- FindCreateTarget visitor;
- usvfs::RedirectionTree::VisitorFunction visitorWrapper =
- [&](const usvfs::RedirectionTree::NodePtrT &node) { visitor(node); };
- readContext->redirectionTable()->visitPath(m_RealPath, visitorWrapper);
- if (visitor.target.get())
- found = true;
- }
- if (found)
- addToDelete = true;
-
- if (wasRerouted()) {
- if (m_FileNode.get())
- m_FileNode->removeFromTree();
- else
- spdlog::get("usvfs")->warn("Node not removed: {}", string_cast(m_FileName));
-
- if (!directory)
- {
- // check if this file was the last file inside a "fake" directory then remove it
- // and possibly also its fake empty parent folders:
- std::wstring parent = m_FileName;
- while (true)
- {
- parent = fs::path(parent).parent_path().wstring();
- if (k32FakeDirTracker.contains(parent))
- {
- dontAddToDelete = true;
- if (RemoveDirectoryW(parent.c_str())) {
- k32FakeDirTracker.erase(parent);
- spdlog::get("usvfs")->info("removed empty fake directory: {}", string_cast(parent));
- }
- else if (GetLastError() != ERROR_DIR_NOT_EMPTY) {
- auto error = GetLastError();
- spdlog::get("usvfs")->warn("removing fake directory failed: {}, error={}", string_cast(parent), error);
- break;
- }
- }
- else
- break;
- }
- }
- }
- if (addToDelete && !dontAddToDelete) {
- k32DeleteTracker.insert(m_RealPath, m_FileName);
- }
- }
-
- static bool createFakePath(fs::path path, LPSECURITY_ATTRIBUTES securityAttributes)
- {
- // sanity and guaranteed recursion end:
- if (!path.has_relative_path())
- throw usvfs::shared::windows_error("createFakePath() refusing to create non-existing top level path: " + path.string());
-
- DWORD attr = GetFileAttributesW(path.c_str());
- DWORD err = GetLastError();
- if (attr != INVALID_FILE_ATTRIBUTES) {
- if (attr & FILE_ATTRIBUTE_DIRECTORY)
- return false; // if directory already exists all is good
- else
- throw usvfs::shared::windows_error("createFakePath() called on a file: " + path.string());
- }
- if (err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND)
- throw usvfs::shared::windows_error("createFakePath() GetFileAttributesW failed on: " + path.string(), err);
-
- if (err != ERROR_FILE_NOT_FOUND) // ERROR_FILE_NOT_FOUND means parent directory already exists
- createFakePath(path.parent_path(), securityAttributes); // otherwise create parent directory (recursively)
-
- BOOL res = CreateDirectoryW(path.c_str(), securityAttributes);
- if (res)
- k32FakeDirTracker.insert(path.wstring(), std::wstring());
- else {
- err = GetLastError();
- throw usvfs::shared::windows_error("createFakePath() CreateDirectoryW failed on: " + path.string(), err);
- }
- return true;
- }
-
- static bool addDirectoryMapping(const usvfs::HookContext::Ptr &context, const fs::path& originalPath, const fs::path& reroutedPath)
- {
- if (originalPath.empty() || reroutedPath.empty()) {
- spdlog::get("hooks")->error("RerouteW::addDirectoryMapping failed: {}, {}",
- string_cast(originalPath.wstring(), CodePage::UTF8).c_str(),
- string_cast(reroutedPath.wstring(), CodePage::UTF8).c_str());
- return false;
- }
-
- auto lookupParent = context->redirectionTable()->findNode(originalPath.parent_path());
- if (!lookupParent.get() || lookupParent->data().linkTarget.empty()) {
- if (!addDirectoryMapping(context, originalPath.parent_path(), reroutedPath.parent_path()))
- {
- spdlog::get("hooks")->error("RerouteW::addDirectoryMapping failed: {}, {}",
- string_cast(originalPath.wstring(), CodePage::UTF8).c_str(),
- string_cast(reroutedPath.wstring(), CodePage::UTF8).c_str());
- return false;
- }
- }
-
- std::string reroutedU8
- = ush::string_cast(reroutedPath.wstring(), ush::CodePage::UTF8);
- if (reroutedU8.empty() || reroutedU8[reroutedU8.size() - 1] != '\\')
- reroutedU8 += "\\";
-
- spdlog::get("hooks")->info("mapping directory in vfs: {}, {}",
- ush::string_cast(originalPath.wstring(), ush::CodePage::UTF8), reroutedU8.c_str());
-
- context->redirectionTable().addDirectory(
- originalPath, usvfs::RedirectionDataLocal(reroutedU8),
- usvfs::shared::FLAG_DIRECTORY|usvfs::shared::FLAG_CREATETARGET);
-
- fs::directory_iterator end_itr;
-
- // cycle through the directory
- for (fs::directory_iterator itr(reroutedPath); itr != end_itr; ++itr)
- {
- // If it's not a directory, add it to the VFS, if it is recurse into it
- if (is_regular_file(itr->path())) {
- std::string fileReroutedU8 = ush::string_cast(itr->path().wstring(), ush::CodePage::UTF8);
- spdlog::get("hooks")->info("mapping file in vfs: {}, {}",
- ush::string_cast((originalPath / itr->path().filename()).wstring(), ush::CodePage::UTF8),
- fileReroutedU8.c_str());
- context->redirectionTable().addFile(fs::path(originalPath / itr->path().filename()), usvfs::RedirectionDataLocal(fileReroutedU8));
- } else {
- addDirectoryMapping(context, originalPath / itr->path().filename(), reroutedPath / itr->path().filename());
- }
- }
-
- return true;
- }
-
- template
- static bool interestingPathImpl(const char_t *inPath)
- {
- if (!inPath || !inPath[0])
- return false;
- // ignore \\.\ unless its a \\.\?:
- if (inPath[0] == '\\' && inPath[1] == '\\' && inPath[2] == '.' && inPath[3] == '\\' && (!inPath[4] || inPath[5] != ':'))
- return false;
- // ignore L"hid#":
- if ((inPath[0] == 'h' || inPath[0] == 'H')
- && ((inPath[1] == 'i' || inPath[1] == 'I'))
- && ((inPath[2] == 'd' || inPath[2] == 'D'))
- && inPath[3] == '#')
- return false;
- return true;
- }
-
- static bool interestingPath(const char* inPath) { return interestingPathImpl(inPath); }
- static bool interestingPath(const wchar_t* inPath) { return interestingPathImpl(inPath); }
-
- static fs::path absolutePath(const wchar_t *inPath)
- {
- if (ush::startswith(inPath, LR"(\\?\)") || ush::startswith(inPath, LR"(\??\)")) {
- inPath += 4;
- return inPath;
- }
- else if ((ush::startswith(inPath, LR"(\\localhost\)") || ush::startswith(inPath, LR"(\\127.0.0.1\)")) && inPath[13] == L'$') {
- std::wstring newPath;
- newPath += towupper(inPath[12]);
- newPath += L':';
- newPath += &inPath[14];
- return newPath;
- }
- else if (inPath[0] == L'\0' || inPath[1] == L':') {
- return inPath;
- }
- else if (inPath[0] == L'\\' || inPath[0] == L'/') {
- return fs::path(winapi::wide::getFullPathName(inPath).first);
- }
- WCHAR currentDirectory[MAX_PATH];
- ::GetCurrentDirectoryW(MAX_PATH, currentDirectory);
- fs::path finalPath = fs::path(currentDirectory) / inPath;
- return finalPath;
- //winapi::wide::getFullPathName(inPath).first;
- }
-
- static fs::path canonizePath(const fs::path& inPath)
- {
- fs::path p = inPath.lexically_normal();
- if (p.filename_is_dot())
- p = p.remove_filename();
- return p.make_preferred();
- }
-
- static RerouteW create(const usvfs::HookContext::ConstPtr &context,
- const usvfs::HookCallContext &callContext,
- const wchar_t *inPath, bool inverse = false)
- {
- RerouteW result;
-
- if (interestingPath(inPath) && callContext.active())
- {
- const auto& lookupPath = canonizePath(absolutePath(inPath));
- result.m_RealPath = lookupPath.wstring();
-
- result.m_Buffer = k32DeleteTracker.lookup(result.m_RealPath);
- bool found = !result.m_Buffer.empty();
- if (found) {
- spdlog::get("hooks")->info("Rerouting file open to location of deleted file: {}",
- ush::string_cast(result.m_Buffer));
- result.m_NewReroute = true;
- } else {
- const usvfs::RedirectionTreeContainer &table
- = inverse ? context->inverseTable() : context->redirectionTable();
- result.m_FileNode = table->findNode(lookupPath);
-
- if (result.m_FileNode.get()
- && (!result.m_FileNode->data().linkTarget.empty() || result.m_FileNode->isDirectory()))
- {
- if (!result.m_FileNode->data().linkTarget.empty()) {
- result.m_Buffer = string_cast(
- result.m_FileNode->data().linkTarget.c_str(), CodePage::UTF8);
- }
- else
- {
- result.m_Buffer = result.m_FileNode->path().wstring();
- }
- found = true;
- }
- }
- if (found) {
- result.m_Rerouted = true;
-
- wchar_t inIt = inPath[wcslen(inPath) - 1];
- std::wstring::iterator outIt = result.m_Buffer.end() - 1;
- if ((*outIt == L'\\' || *outIt == L'/') && !(inIt == L'\\' || inIt == L'/'))
- result.m_Buffer.erase(outIt);
- std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\');
- }
- else
- result.m_Buffer = inPath;
- }
- else if (inPath)
- result.m_Buffer = inPath;
-
- if (inPath)
- result.m_FileName = result.m_Buffer.c_str();
- return result;
- }
-
- static RerouteW createNew(const usvfs::HookContext::ConstPtr &context,
- const usvfs::HookCallContext &callContext,
- LPCWSTR inPath, bool createPath = true,
- LPSECURITY_ATTRIBUTES securityAttributes = nullptr)
- {
- RerouteW result;
-
- if (interestingPath(inPath) && callContext.active())
- {
- const auto& lookupPath = canonizePath(absolutePath(inPath));
- result.m_RealPath = lookupPath.wstring();
-
- result.m_Buffer = k32DeleteTracker.lookup(result.m_RealPath);
- bool found = !result.m_Buffer.empty();
- if (found)
- spdlog::get("hooks")->info("Rerouting file creation to original location of deleted file: {}",
- ush::string_cast(result.m_Buffer));
- else
- {
- FindCreateTarget visitor;
- usvfs::RedirectionTree::VisitorFunction visitorWrapper =
- [&](const usvfs::RedirectionTree::NodePtrT &node) { visitor(node); };
- context->redirectionTable()->visitPath(lookupPath, visitorWrapper);
- if (visitor.target.get()) {
- // the visitor has found the last (deepest in the directory hierarchy)
- // create-target
- fs::path relativePath
- = ush::make_relative(visitor.target->path(), lookupPath);
- result.m_Buffer =
- (fs::path(visitor.target->data().linkTarget.c_str()) / relativePath).wstring();
- found = true;
- }
- }
-
- if (found)
- {
- if (createPath) {
- try {
- usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::ALL_GROUPS);
- result.m_PathCreated =
- createFakePath(fs::path(result.m_Buffer).parent_path(), securityAttributes);
- }
- catch (const std::exception &e) {
- spdlog::get("hooks")->error("failed to create {}: {}",
- ush::string_cast(result.m_Buffer), e.what());
- }
- }
-
- wchar_t inIt = inPath[wcslen(inPath) - 1];
- std::wstring::iterator outIt = result.m_Buffer.end() - 1;
- if ((*outIt == L'\\' || *outIt == L'/') && !(inIt == L'\\' || inIt == L'/'))
- result.m_Buffer.erase(outIt);
- std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\');
- result.m_Rerouted = true;
- result.m_NewReroute = true;
- }
- else
- result.m_Buffer = inPath;
- }
- else if (inPath)
- result.m_Buffer = inPath;
-
- if (inPath)
- result.m_FileName = result.m_Buffer.c_str();
- return result;
- }
-
- static RerouteW createOrNew(const usvfs::HookContext::ConstPtr &context, const usvfs::HookCallContext &callContext,
- LPCWSTR inPath, bool createPath = true, LPSECURITY_ATTRIBUTES securityAttributes = nullptr)
- {
- {
- auto res = create(context, callContext, inPath);
- if (res.wasRerouted() || !interestingPath(inPath) || !callContext.active() || pathExists(inPath))
- return std::move(res);
- }
- return createNew(context, callContext, inPath, createPath, securityAttributes);
- }
-
- static RerouteW noReroute(LPCWSTR inPath)
- {
- RerouteW result;
- if (inPath)
- result.m_Buffer = inPath;
- if (inPath && inPath[0] && !ush::startswith(inPath, L"hid#"))
- std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\');
- result.m_FileName = result.m_Buffer.c_str();
- return result;
- }
-private:
- struct FindCreateTarget {
- usvfs::RedirectionTree::NodePtrT target;
- void operator()(usvfs::RedirectionTree::NodePtrT node)
- {
- if (node->hasFlag(usvfs::shared::FLAG_CREATETARGET)) {
- target = node;
- }
- }
- };
-};
HMODULE WINAPI usvfs::hook_LoadLibraryExW(LPCWSTR lpFileName, HANDLE hFile,
DWORD dwFlags)
@@ -854,299 +349,6 @@ BOOL WINAPI usvfs::hook_CreateProcessInternalW(
return res;
}
-HANDLE WINAPI usvfs::hook_CreateFileA(
- LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
- LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
- DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
-{
- HANDLE res = INVALID_HANDLE_VALUE;
-
- HOOK_START_GROUP(MutExHookGroup::OPEN_FILE)
-
- if (!callContext.active()) {
- res = CreateFileA(lpFileName, dwDesiredAccess, dwShareMode,
- lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
- callContext.updateLastError();
- return res;
- }
-
- // release the MutExHookGroup::OPEN_FILE so that CreateFileW can process the request:
- HOOK_END
- HOOK_START
-
- const auto& fileName = ush::string_cast(lpFileName);
-
- PRE_REALCALL
- res = CreateFileW(fileName.c_str(), dwDesiredAccess, dwShareMode, lpSecurityAttributes,
- dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
- POST_REALCALL
-
- HOOK_END
-
- return res;
-}
-
-namespace usvfs {
- class CreateRerouter {
- public:
- bool rerouteCreate(const usvfs::HookContext::ConstPtr &context, const usvfs::HookCallContext &callContext,
- LPCWSTR lpFileName, DWORD& dwCreationDisposition, DWORD dwDesiredAccess, LPSECURITY_ATTRIBUTES lpSecurityAttributes)
- {
- enum class Open { existing, create, empty };
- Open open = Open::existing;
-
- // Notice since we are calling our patched GetFileAttributesW here this will also check virtualized paths
- DWORD virtAttr = GetFileAttributesW(lpFileName);
- bool isFile = virtAttr != INVALID_FILE_ATTRIBUTES && (virtAttr & FILE_ATTRIBUTE_DIRECTORY) == 0;
- m_isDir = virtAttr != INVALID_FILE_ATTRIBUTES && (virtAttr & FILE_ATTRIBUTE_DIRECTORY);
-
- switch (dwCreationDisposition) {
- case CREATE_ALWAYS:
- open = Open::create;
- if (isFile || m_isDir)
- m_error = ERROR_ALREADY_EXISTS;
- break;
-
- case CREATE_NEW:
- if (isFile || m_isDir) {
- m_error = ERROR_FILE_EXISTS;
- return false;
- }
- else
- open = Open::create;
- break;
-
- case OPEN_ALWAYS:
- if (isFile || m_isDir)
- m_error = ERROR_ALREADY_EXISTS;
- else
- open = Open::create;
- break;
-
- case TRUNCATE_EXISTING:
- if ((dwDesiredAccess & GENERIC_WRITE) == 0) {
- m_error = ERROR_INVALID_PARAMETER;
- return false;
- }
- if (isFile || m_isDir)
- open = Open::empty;
- // if !isFile we let the OS create function set the error value
- break;
- }
-
- if (m_isDir && pathIsDirectory(lpFileName))
- m_reroute = RerouteW::noReroute(lpFileName);
- else
- m_reroute = RerouteW::create(context, callContext, lpFileName);
-
- if (m_reroute.wasRerouted() && open == Open::create && pathIsDirectory(m_reroute.fileName()))
- m_reroute = RerouteW::createNew(context, callContext, lpFileName, true, lpSecurityAttributes);
-
- if (!m_isDir && !isFile && !m_reroute.wasRerouted() && (open == Open::create || open == Open::empty))
- {
- m_reroute = RerouteW::createNew(context, callContext, lpFileName, true, lpSecurityAttributes);
-
- bool newFile = !m_reroute.wasRerouted() && pathDirectlyAvailable(m_reroute.fileName());
- if (newFile && open == Open::empty)
- // TRUNCATE_EXISTING will fail since the new file doesn't exist, so change disposition:
- dwCreationDisposition = CREATE_ALWAYS;
- }
-
- return true;
- }
-
- // rerouteNew is used for rerouting the destination of copy/move operations. Assumes that the call will be skipped if false is returned.
- bool rerouteNew(const usvfs::HookContext::ConstPtr &context, usvfs::HookCallContext &callContext, LPCWSTR lpFileName, bool replaceExisting, const char* hookName)
- {
- DWORD disposition = replaceExisting ? CREATE_ALWAYS : CREATE_NEW;
- if (!rerouteCreate(context, callContext, lpFileName, disposition, GENERIC_WRITE, nullptr)) {
- spdlog::get("hooks")->info(
- "{} guaranteed failure, skipping original call: {}, replaceExisting={}, error={}",
- hookName, ush::string_cast(lpFileName, ush::CodePage::UTF8), replaceExisting ? "true" : "false", error());
-
- callContext.updateLastError(error());
- return false;
- }
- return true;
- }
-
- void updateResult(usvfs::HookCallContext &callContext, bool success)
- {
- m_originalError = callContext.lastError();
- if (success) {
- // m_error != ERROR_SUCCESS means we are overriding the error on success
- if (m_error == ERROR_SUCCESS)
- m_error = m_originalError;
- }
- else if (m_originalError == ERROR_PATH_NOT_FOUND && m_directlyAvailable)
- m_error = ERROR_FILE_NOT_FOUND;
- else
- m_error = m_originalError;
- if (m_error != m_originalError)
- callContext.updateLastError(m_error);
- }
-
- DWORD error() const { return m_error; }
- DWORD originalError() const { return m_originalError; }
- bool changedError() const { return m_error != m_originalError; }
-
- bool isDir() const { return m_isDir; }
- bool newReroute() const { return m_reroute.newReroute(); }
- bool wasRerouted() const { return m_reroute.wasRerouted(); }
- LPCWSTR fileName() const { return m_reroute.fileName(); }
-
- void insertMapping(const usvfs::HookContext::Ptr &context, bool directory = false) { m_reroute.insertMapping(context, directory); }
-
- private:
- DWORD m_error = ERROR_SUCCESS;
- DWORD m_originalError = ERROR_SUCCESS;
- bool m_directlyAvailable = false;
- bool m_isDir = false;
- RerouteW m_reroute;
- };
-};
-
-HANDLE WINAPI usvfs::hook_CreateFileW(
- LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
- LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
- DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
-{
- HANDLE res = INVALID_HANDLE_VALUE;
-
- HOOK_START_GROUP(MutExHookGroup::OPEN_FILE)
-
- if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) {
- res = ::CreateFileW(lpFileName, dwDesiredAccess, dwShareMode,
- lpSecurityAttributes, dwCreationDisposition,
- dwFlagsAndAttributes, hTemplateFile);
- callContext.updateLastError();
- return res;
- }
-
- if (dwFlagsAndAttributes & FILE_FLAG_DELETE_ON_CLOSE) {
- spdlog::get("hooks")->warn("hook_CreateFileW: FILE_FLAG_DELETE_ON_CLOSE not supported");
- }
-
- DWORD originalDisposition = dwCreationDisposition;
- CreateRerouter rerouter;
- if (rerouter.rerouteCreate(READ_CONTEXT(), callContext, lpFileName, dwCreationDisposition, dwDesiredAccess, lpSecurityAttributes))
- {
- PRE_REALCALL
- res = ::CreateFileW(rerouter.fileName(), dwDesiredAccess, dwShareMode,
- lpSecurityAttributes, dwCreationDisposition,
- dwFlagsAndAttributes, hTemplateFile);
- POST_REALCALL
- rerouter.updateResult(callContext, res != INVALID_HANDLE_VALUE);
-
- if (res != INVALID_HANDLE_VALUE) {
- if (rerouter.newReroute())
- rerouter.insertMapping(WRITE_CONTEXT());
-
- if (rerouter.isDir() && rerouter.wasRerouted() && (dwFlagsAndAttributes & FILE_FLAG_BACKUP_SEMANTICS))
- {
- // store the original search path for use during iteration
- WRITE_CONTEXT()
- ->customData(SearchHandles)[res]
- = lpFileName;
- }
- }
-
- if (rerouter.wasRerouted() || rerouter.changedError() || originalDisposition != dwCreationDisposition) {
- LOG_CALL()
- .PARAMWRAP(lpFileName)
- .PARAMWRAP(rerouter.fileName())
- .PARAMHEX(dwDesiredAccess)
- .PARAMHEX(originalDisposition)
- .PARAMHEX(dwCreationDisposition)
- .PARAMHEX(dwFlagsAndAttributes)
- .PARAMHEX(res)
- .PARAM(rerouter.originalError())
- .PARAM(rerouter.error());
- }
- } else {
- spdlog::get("hooks")->info(
- "hook_CreateFileW guaranteed failure, skipping original call: {}, disposition={}, access={}, error={}",
- ush::string_cast(lpFileName, ush::CodePage::UTF8),
- dwCreationDisposition, dwDesiredAccess, rerouter.error());
-
- callContext.updateLastError(rerouter.error());
- }
-
- HOOK_END
-
- return res;
-}
-
-HANDLE (WINAPI *usvfs::CreateFile2)(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition, LPCREATEFILE2_EXTENDED_PARAMETERS pCreateExParams);
-
-HANDLE WINAPI usvfs::hook_CreateFile2(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition, LPCREATEFILE2_EXTENDED_PARAMETERS pCreateExParams)
-{
- HANDLE res = INVALID_HANDLE_VALUE;
-
- typedef HANDLE(WINAPI * CreateFile2_t)(LPCWSTR, DWORD, DWORD, DWORD, LPCREATEFILE2_EXTENDED_PARAMETERS);
-
- HOOK_START_GROUP(MutExHookGroup::OPEN_FILE)
-
- if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) {
- HANDLE res = CreateFile2(lpFileName, dwDesiredAccess, dwShareMode, dwCreationDisposition, pCreateExParams);
- callContext.updateLastError();
- return res;
- }
-
- if (pCreateExParams && (pCreateExParams->dwFileFlags & FILE_FLAG_DELETE_ON_CLOSE)) {
- spdlog::get("hooks")->warn("hook_CreateFile2: FILE_FLAG_DELETE_ON_CLOSE not supported");
- }
-
- DWORD originalDisposition = dwCreationDisposition;
- CreateRerouter rerouter;
- if (rerouter.rerouteCreate(READ_CONTEXT(), callContext, lpFileName, dwCreationDisposition, dwDesiredAccess,
- pCreateExParams ? pCreateExParams->lpSecurityAttributes : nullptr))
- {
- PRE_REALCALL
- res = CreateFile2(rerouter.fileName(), dwDesiredAccess, dwShareMode, dwCreationDisposition, pCreateExParams);
- POST_REALCALL
- rerouter.updateResult(callContext, res != INVALID_HANDLE_VALUE);
-
- if (res != INVALID_HANDLE_VALUE) {
- if (rerouter.newReroute())
- rerouter.insertMapping(WRITE_CONTEXT());
-
- if (rerouter.isDir() && rerouter.wasRerouted()
- && pCreateExParams && (pCreateExParams->dwFileFlags & FILE_FLAG_BACKUP_SEMANTICS))
- {
- // store the original search path for use during iteration
- WRITE_CONTEXT()
- ->customData(SearchHandles)[res]
- = lpFileName;
- }
- }
-
- if (rerouter.wasRerouted() || rerouter.changedError() || originalDisposition != dwCreationDisposition) {
- LOG_CALL()
- .PARAMWRAP(lpFileName)
- .PARAMWRAP(rerouter.fileName())
- .PARAMHEX(dwDesiredAccess)
- .PARAMHEX(originalDisposition)
- .PARAMHEX(dwCreationDisposition)
- .PARAMHEX(res)
- .PARAM(rerouter.originalError())
- .PARAM(rerouter.error());
- }
- }
- else {
- spdlog::get("hooks")->info(
- "hook_CreateFileW guaranteed failure, skipping original call: {}, disposition={}, access={}, error={}",
- ush::string_cast(lpFileName, ush::CodePage::UTF8),
- dwCreationDisposition, dwDesiredAccess, rerouter.error());
-
- callContext.updateLastError(rerouter.error());
- }
-
- HOOK_END
-
- return res;
-}
-
BOOL WINAPI usvfs::hook_GetFileAttributesExW(
LPCWSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId,
LPVOID lpFileInformation)
@@ -1327,7 +529,7 @@ BOOL WINAPI usvfs::hook_DeleteFileW(LPCWSTR lpFileName)
}
BOOL rewriteChangedDrives(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName,
- const RerouteW& readReroute, const usvfs::CreateRerouter& writeReroute)
+ const usvfs::RerouteW& readReroute, const usvfs::CreateRerouter& writeReroute)
{
return ((readReroute.wasRerouted() || writeReroute.wasRerouted())
&& pathsOnDifferentDrives(readReroute.fileName(), writeReroute.fileName())
@@ -1608,7 +810,7 @@ BOOL WINAPI usvfs::hook_MoveFileWithProgressW(LPCWSTR lpExistingFileName, LPCWST
}
RerouteW readReroute;
- CreateRerouter writeReroute;
+ usvfs::CreateRerouter writeReroute;
bool callOriginal = true;
DWORD newFlags = dwFlags;
@@ -1701,7 +903,7 @@ BOOL WINAPI usvfs::hook_CopyFileExW(LPCWSTR lpExistingFileName,
}
RerouteW readReroute;
- CreateRerouter writeReroute;
+ usvfs::CreateRerouter writeReroute;
bool callOriginal = true;
{
diff --git a/src/usvfs_dll/hooks/kernel32.h b/src/usvfs_dll/hooks/kernel32.h
index d9fac5cc..82ad1b87 100644
--- a/src/usvfs_dll/hooks/kernel32.h
+++ b/src/usvfs_dll/hooks/kernel32.h
@@ -6,12 +6,6 @@
namespace usvfs {
-DLLEXPORT HANDLE WINAPI hook_CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
-DLLEXPORT HANDLE WINAPI hook_CreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
-
-extern HANDLE (WINAPI *CreateFile2)(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition, LPCREATEFILE2_EXTENDED_PARAMETERS pCreateExParams);
-DLLEXPORT HANDLE WINAPI hook_CreateFile2(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition, LPCREATEFILE2_EXTENDED_PARAMETERS pCreateExParams);
-
DLLEXPORT BOOL WINAPI hook_GetFileAttributesExW(LPCWSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, LPVOID lpFileInformation);
DLLEXPORT DWORD WINAPI hook_GetFileAttributesW(LPCWSTR lpFileName);
DLLEXPORT DWORD WINAPI hook_SetFileAttributesW(LPCWSTR lpFileName, DWORD dwFileAttributes);
diff --git a/src/usvfs_dll/hooks/ntdll.cpp b/src/usvfs_dll/hooks/ntdll.cpp
index ad88c8f2..b7badb91 100644
--- a/src/usvfs_dll/hooks/ntdll.cpp
+++ b/src/usvfs_dll/hooks/ntdll.cpp
@@ -3,6 +3,7 @@
#include
#include "../hookcontext.h"
#include "../hookcallcontext.h"
+#include "../maptracker.h"
#include "../stringcast_boost.h"
#include
#pragma warning(push, 3)
@@ -49,11 +50,21 @@ using usvfs::UnicodeString;
#define FILE_OVERWRITE_IF 0x00000005
#define FILE_MAXIMUM_DISPOSITION 0x00000005
-#define FILE_DELETE_ON_CLOSE 0x00001000
-
template
using unique_ptr_deleter = std::unique_ptr;
+class RedirectionInfo {
+public:
+ UnicodeString path;
+ bool redirected;
+
+ RedirectionInfo() {}
+ RedirectionInfo(UnicodeString path, bool redirected)
+ : path(path)
+ , redirected(redirected)
+ {}
+};
+
class HandleTracker {
public:
using handle_type = HANDLE;
@@ -150,19 +161,19 @@ std::ostream &operator<<(std::ostream &os, POBJECT_ATTRIBUTES attr)
return operator<<(os, *attr->ObjectName);
}
-std::pair
+RedirectionInfo
applyReroute(const usvfs::HookContext::ConstPtr &context,
const usvfs::HookCallContext &callContext,
const UnicodeString &inPath)
{
- std::pair result;
- result.first = inPath;
- result.second = false;
+ 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(
- static_cast(result.first) + 4, ush::CodePage::UTF8);
+ static_cast(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())) {
@@ -178,20 +189,41 @@ applyReroute(const usvfs::HookContext::ConstPtr &context,
reroutePath = ush::string_cast(
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.first = LR"(\??\)" + reroutePath;
- result.second = true;
+ 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(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)
@@ -229,8 +261,7 @@ findCreateTarget(const usvfs::HookContext::ConstPtr &context,
return result;
}
-
-std::pair
+RedirectionInfo
applyReroute(const usvfs::HookContext::ConstPtr &context,
const usvfs::HookCallContext &callContext,
POBJECT_ATTRIBUTES inAttributes)
@@ -614,7 +645,7 @@ void gatherVirtualEntries(const UnicodeString &dirName,
m = { ush::string_cast(subNode->path().c_str(),
ush::CodePage::UTF8), vName };
}
-
+
info.virtualMatches.push(m);
info.foundFiles.insert(ush::to_upper(vName));
}
@@ -1014,15 +1045,15 @@ NTSTATUS WINAPI usvfs::hook_NtQueryDirectoryFileEx(
}
unique_ptr_deleter
-makeObjectAttributes(std::pair &redirInfo,
+makeObjectAttributes(RedirectionInfo &redirInfo,
POBJECT_ATTRIBUTES attributeTemplate)
{
- if (redirInfo.second) {
+ if (redirInfo.redirected) {
unique_ptr_deleter result(
new OBJECT_ATTRIBUTES, [](OBJECT_ATTRIBUTES *ptr) { delete ptr; });
memcpy(result.get(), attributeTemplate, sizeof(OBJECT_ATTRIBUTES));
result->RootDirectory = nullptr;
- result->ObjectName = static_cast(redirInfo.first);
+ result->ObjectName = static_cast(redirInfo.path);
return result;
} else {
// just reuse the template with a dummy deleter
@@ -1045,10 +1076,6 @@ NTSTATUS ntdll_mess_NtOpenFile(PHANDLE FileHandle,
HOOK_START_GROUP(MutExHookGroup::OPEN_FILE)
- if (OpenOptions & FILE_DELETE_ON_CLOSE) {
- spdlog::get("hooks")->warn("ntdll_mess_NtOpenFile: FILE_DELETE_ON_CLOSE not supported");
- }
-
bool storePath = false;
if (((OpenOptions & FILE_DIRECTORY_FILE) != 0UL)
&& ((OpenOptions & FILE_OPEN_FOR_BACKUP_INTENT) != 0UL)) {
@@ -1079,7 +1106,7 @@ NTSTATUS ntdll_mess_NtOpenFile(PHANDLE FileHandle,
}
try {
- std::pair redir
+ RedirectionInfo redir
= applyReroute(READ_CONTEXT(), callContext, fullName);
unique_ptr_deleter adjustedAttributes
= makeObjectAttributes(redir, ObjectAttributes);
@@ -1096,7 +1123,7 @@ NTSTATUS ntdll_mess_NtOpenFile(PHANDLE FileHandle,
#pragma message("need to clean up this handle in CloseHandle call")
}
- if (redir.second) {
+ if (redir.redirected) {
LOG_CALL()
.addParam("source", ObjectAttributes)
.addParam("rerouted", adjustedAttributes.get())
@@ -1127,17 +1154,6 @@ NTSTATUS WINAPI usvfs::hook_NtOpenFile(PHANDLE FileHandle,
return res;
}
-bool fileExists(POBJECT_ATTRIBUTES attributes)
-{
- UnicodeString temp = CreateUnicodeString(attributes);
- return RtlDoesFileExists_U(static_cast(temp)) == TRUE;
-}
-
-bool fileExists(const UnicodeString &filename)
-{
- return RtlDoesFileExists_U(static_cast(filename)) == TRUE;
-}
-
NTSTATUS ntdll_mess_NtCreateFile(
PHANDLE FileHandle, ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock,
@@ -1147,9 +1163,10 @@ NTSTATUS ntdll_mess_NtCreateFile(
{
using namespace usvfs;
+ NTSTATUS res = STATUS_NO_SUCH_FILE;
+
PreserveGetLastError ntFunctionsDoNotChangeGetLastError;
- NTSTATUS res = STATUS_NO_SUCH_FILE;
HOOK_START_GROUP(MutExHookGroup::OPEN_FILE)
if (!callContext.active()) {
return ::NtCreateFile(FileHandle, DesiredAccess, ObjectAttributes,
@@ -1158,11 +1175,8 @@ NTSTATUS ntdll_mess_NtCreateFile(
EaBuffer, EaLength);
}
- if (CreateOptions & FILE_DELETE_ON_CLOSE) {
- spdlog::get("hooks")->warn("ntdll_mess_NtCreateFile: FILE_DELETE_ON_CLOSE not supported");
- }
-
UnicodeString inPath = CreateUnicodeString(ObjectAttributes);
+ LPCWSTR inPathW = static_cast(inPath);
if (inPath.size() == 0) {
spdlog::get("hooks")->info(
@@ -1174,58 +1188,80 @@ NTSTATUS ntdll_mess_NtCreateFile(
EaBuffer, EaLength);
}
- std::pair redir(UnicodeString(), false);
-
- { // limit context scope
- FunctionGroupLock lock(MutExHookGroup::ALL_GROUPS);
- HookContext::ConstPtr context = READ_CONTEXT();
+ 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;
+ }
- redir = applyReroute(context, callContext, inPath);
-
- // TODO would be neat if this could (optionally) reroute all potential write
- // accesses to the create target.
- // This could be achived by copying the file to the target here in case
- // the createdisposition or the requested access rights make that
- // necessary
- if (((CreateDisposition == FILE_SUPERSEDE)
- || (CreateDisposition == FILE_CREATE)
- || (CreateDisposition == FILE_OPEN_IF)
- || (CreateDisposition == FILE_OVERWRITE_IF))
- && !redir.second && !fileExists(inPath)) {
- // the file will be created so now we need to know where
- std::pair createTarget
- = findCreateTarget(context, inPath);
-
- if (createTarget.second.size() != 0) {
- // there is a reroute target for new files so adjust the path
- redir.first = LR"(\??)"; // appendPath will add the second '\'
- redir.first.appendPath(static_cast(createTarget.second));
-
- spdlog::get("hooks")->info(
- "reroute write access: {}",
- ush::string_cast(static_cast(redir.first))
- .c_str());
- }
+ 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;
}
- }
- unique_ptr_deleter adjustedAttributes
+ RedirectionInfo redir = applyReroute(rerouter);
+
+ unique_ptr_deleter adjustedAttributes
= makeObjectAttributes(redir, ObjectAttributes);
- PRE_REALCALL
- res = ::NtCreateFile(FileHandle, DesiredAccess, adjustedAttributes.get(),
- IoStatusBlock, AllocationSize, FileAttributes,
- ShareAccess, CreateDisposition, CreateOptions, EaBuffer,
- EaLength);
- POST_REALCALL
+ 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 (redir.second) {
- LOG_CALL()
- .addParam("source", ObjectAttributes)
- .addParam("rerouted", adjustedAttributes.get())
- .PARAM(CreateDisposition)
- .PARAM(*FileHandle)
- .PARAMWRAP(res);
+ 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(SearchHandles)[*FileHandle] = inPathW;
+ }
+ }
+
+ if (rerouter.wasRerouted() || rerouter.changedError() || originalDisposition != CreateDisposition) {
+ LOG_CALL()
+ .PARAMWRAP(inPathW)
+ .PARAMWRAP(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
@@ -1246,7 +1282,6 @@ NTSTATUS WINAPI usvfs::hook_NtCreateFile(
return res;
}
-
NTSTATUS WINAPI usvfs::hook_NtClose(HANDLE Handle)
{
PreserveGetLastError ntFunctionsDoNotChangeGetLastError;
@@ -1308,7 +1343,7 @@ NTSTATUS WINAPI usvfs::hook_NtQueryAttributesFile(
UnicodeString inPath = CreateUnicodeString(ObjectAttributes);
- std::pair redir
+ RedirectionInfo redir
= applyReroute(READ_CONTEXT(), callContext, inPath);
unique_ptr_deleter adjustedAttributes
= makeObjectAttributes(redir, ObjectAttributes);
@@ -1317,7 +1352,7 @@ NTSTATUS WINAPI usvfs::hook_NtQueryAttributesFile(
res = ::NtQueryAttributesFile(adjustedAttributes.get(), FileInformation);
POST_REALCALL
- if (redir.second) {
+ if (redir.redirected) {
LOG_CALL()
.addParam("source", ObjectAttributes)
.addParam("rerouted", adjustedAttributes.get())
@@ -1350,7 +1385,7 @@ NTSTATUS WINAPI usvfs::hook_NtQueryFullAttributesFile(
return ::NtQueryFullAttributesFile(ObjectAttributes, FileInformation);
}
- std::pair redir
+ RedirectionInfo redir
= applyReroute(READ_CONTEXT(), callContext, inPath);
unique_ptr_deleter adjustedAttributes
= makeObjectAttributes(redir, ObjectAttributes);
@@ -1359,7 +1394,7 @@ NTSTATUS WINAPI usvfs::hook_NtQueryFullAttributesFile(
res = ::NtQueryFullAttributesFile(adjustedAttributes.get(), FileInformation);
POST_REALCALL
- if (redir.second) {
+ if (redir.redirected) {
LOG_CALL()
.addParam("source", ObjectAttributes)
.addParam("rerouted", adjustedAttributes.get())
diff --git a/src/usvfs_dll/maptracker.h b/src/usvfs_dll/maptracker.h
new file mode 100644
index 00000000..b90fd88c
--- /dev/null
+++ b/src/usvfs_dll/maptracker.h
@@ -0,0 +1,650 @@
+#pragma once
+
+#include "windows_sane.h"
+
+#include
+#include
+#include
+
+#include "hookcontext.h"
+#include "hookcallcontext.h"
+#include "stringcast_basic.h"
+
+namespace usvfs {
+
+// returns true iff the path exists (checks only real paths)
+static inline bool pathExists(LPCWSTR fileName)
+{
+ FunctionGroupLock lock(MutExHookGroup::FILE_ATTRIBUTES);
+ DWORD attrib = GetFileAttributesW(fileName);
+ return attrib != INVALID_FILE_ATTRIBUTES;
+}
+
+// returns true iff the path exists and is a file (checks only real paths)
+static inline bool pathIsFile(LPCWSTR fileName)
+{
+ FunctionGroupLock lock(MutExHookGroup::FILE_ATTRIBUTES);
+ DWORD attrib = GetFileAttributesW(fileName);
+ return attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) == 0;
+}
+
+// returns true iff the path exists and is a file (checks only real paths)
+static inline bool pathIsDirectory(LPCWSTR fileName)
+{
+ FunctionGroupLock lock(MutExHookGroup::FILE_ATTRIBUTES);
+ DWORD attrib = GetFileAttributesW(fileName);
+ return attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY);
+}
+
+// returns true iff the path does not exist but it parent directory does (checks only real paths)
+static inline bool pathDirectlyAvailable(LPCWSTR pathName)
+{
+ FunctionGroupLock lock(MutExHookGroup::FILE_ATTRIBUTES);
+ DWORD attrib = GetFileAttributesW(pathName);
+ return attrib == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND;
+}
+
+class MapTracker {
+public:
+ std::wstring lookup(const std::wstring& fromPath) const {
+ if (!fromPath.empty())
+ {
+ std::shared_lock lock(m_mutex);
+ auto find = m_map.find(fromPath);
+ if (find != m_map.end())
+ return find->second;
+ }
+ return std::wstring();
+ }
+
+ bool contains(const std::wstring& fromPath) const {
+ if (!fromPath.empty())
+ {
+ std::shared_lock lock(m_mutex);
+ auto find = m_map.find(fromPath);
+ if (find != m_map.end())
+ return true;
+ }
+ return false;
+ }
+
+ void insert(const std::wstring& fromPath, const std::wstring& toPath) {
+ if (fromPath.empty())
+ return;
+ std::unique_lock lock(m_mutex);
+ m_map[fromPath] = toPath;
+ }
+
+ bool erase(const std::wstring& fromPath)
+ {
+ if (fromPath.empty())
+ return false;
+ std::unique_lock lock(m_mutex);
+ return m_map.erase(fromPath);
+ }
+
+private:
+ mutable std::shared_mutex m_mutex;
+ std::unordered_map m_map;
+};
+
+extern MapTracker k32DeleteTracker;
+extern MapTracker k32FakeDirTracker;
+
+class RerouteW
+{
+ std::wstring m_Buffer{};
+ std::wstring m_RealPath{};
+ bool m_Rerouted{false};
+ LPCWSTR m_FileName{nullptr};
+ bool m_PathCreated{false};
+ bool m_NewReroute{false};
+
+ RedirectionTree::NodePtrT m_FileNode;
+
+public:
+ RerouteW() = default;
+
+ RerouteW(RerouteW &&reference)
+ : m_Buffer(std::move(reference.m_Buffer))
+ , m_RealPath(std::move(reference.m_RealPath))
+ , m_Rerouted(reference.m_Rerouted)
+ , m_PathCreated(reference.m_PathCreated)
+ , m_NewReroute(reference.m_NewReroute)
+ , m_FileNode(std::move(reference.m_FileNode))
+ {
+ m_FileName = reference.m_FileName != nullptr ? m_Buffer.c_str() : nullptr;
+ reference.m_FileName = nullptr;
+ }
+
+ RerouteW &operator=(RerouteW &&reference)
+ {
+ m_Buffer = std::move(reference.m_Buffer);
+ m_RealPath = std::move(reference.m_RealPath);
+ m_Rerouted = reference.m_Rerouted;
+ m_PathCreated = reference.m_PathCreated;
+ m_NewReroute = reference.m_NewReroute;
+ m_FileName = reference.m_FileName != nullptr ? m_Buffer.c_str() : nullptr;
+ m_FileNode = std::move(reference.m_FileNode);
+ return *this;
+ }
+
+ RerouteW(const RerouteW &reference) = delete;
+ RerouteW &operator=(const RerouteW &) = delete;
+
+ LPCWSTR fileName() const
+ {
+ return m_FileName;
+ }
+
+ const std::wstring &buffer() const
+ {
+ return m_Buffer;
+ }
+
+ bool wasRerouted() const
+ {
+ return m_Rerouted;
+ }
+
+ bool newReroute() const
+ {
+ return m_NewReroute;
+ }
+
+ void insertMapping(const HookContext::Ptr &context, bool directory = false)
+ {
+ if (directory)
+ {
+ addDirectoryMapping(context, m_RealPath, m_FileName);
+
+ // In case we have just created a "fake" directory, it is no longer fake and need to remove it and all its
+ // parent folders from the fake map:
+ std::wstring dir = m_FileName;
+ while (k32FakeDirTracker.erase(dir))
+ dir = fs::path(dir).parent_path().wstring();
+ }
+ else
+ {
+ //if (m_PathCreated)
+ //addDirectoryMapping(context, fs::path(m_RealPath).parent_path(), fs::path(m_FileName).parent_path());
+
+ spdlog::get("hooks")->info("mapping file in vfs: {}, {}",
+ shared::string_cast(m_RealPath, shared::CodePage::UTF8),
+ shared::string_cast(m_FileName, shared::CodePage::UTF8));
+ m_FileNode =
+ context->redirectionTable().addFile(m_RealPath, RedirectionDataLocal(shared::string_cast(m_FileName, shared::CodePage::UTF8)));
+
+ k32DeleteTracker.erase(m_RealPath);
+ }
+ }
+
+ void removeMapping(const HookContext::ConstPtr &readContext, bool directory = false)
+ {
+ bool addToDelete = false;
+ bool dontAddToDelete = false;
+
+ // We need to track deleted files even if they were not rerouted (i.e. files deleted from the real folder which there is
+ // a virtualized mapped folder on top of it). Since we don't want to add, *every* file which is deleted we check this:
+ bool found = wasRerouted();
+ if (!found) {
+ FindCreateTarget visitor;
+ RedirectionTree::VisitorFunction visitorWrapper =
+ [&](const RedirectionTree::NodePtrT &node) { visitor(node); };
+ readContext->redirectionTable()->visitPath(m_RealPath, visitorWrapper);
+ if (visitor.target.get())
+ found = true;
+ }
+ if (found)
+ addToDelete = true;
+
+ if (wasRerouted()) {
+ if (m_FileNode.get())
+ m_FileNode->removeFromTree();
+ else
+ spdlog::get("usvfs")->warn("Node not removed: {}", shared::string_cast(m_FileName));
+
+ if (!directory)
+ {
+ // check if this file was the last file inside a "fake" directory then remove it
+ // and possibly also its fake empty parent folders:
+ std::wstring parent = m_FileName;
+ while (true)
+ {
+ parent = fs::path(parent).parent_path().wstring();
+ if (k32FakeDirTracker.contains(parent))
+ {
+ dontAddToDelete = true;
+ if (RemoveDirectoryW(parent.c_str())) {
+ k32FakeDirTracker.erase(parent);
+ spdlog::get("usvfs")->info("removed empty fake directory: {}", shared::string_cast(parent));
+ }
+ else if (GetLastError() != ERROR_DIR_NOT_EMPTY) {
+ auto error = GetLastError();
+ spdlog::get("usvfs")->warn("removing fake directory failed: {}, error={}", shared::string_cast(parent), error);
+ break;
+ }
+ }
+ else
+ break;
+ }
+ }
+ }
+ if (addToDelete && !dontAddToDelete) {
+ k32DeleteTracker.insert(m_RealPath, m_FileName);
+ }
+ }
+
+ static bool createFakePath(fs::path path, LPSECURITY_ATTRIBUTES securityAttributes)
+ {
+ // sanity and guaranteed recursion end:
+ if (!path.has_relative_path())
+ throw shared::windows_error("createFakePath() refusing to create non-existing top level path: " + path.string());
+
+ DWORD attr = GetFileAttributesW(path.c_str());
+ DWORD err = GetLastError();
+ if (attr != INVALID_FILE_ATTRIBUTES) {
+ if (attr & FILE_ATTRIBUTE_DIRECTORY)
+ return false; // if directory already exists all is good
+ else
+ throw shared::windows_error("createFakePath() called on a file: " + path.string());
+ }
+ if (err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND)
+ throw shared::windows_error("createFakePath() GetFileAttributesW failed on: " + path.string(), err);
+
+ if (err != ERROR_FILE_NOT_FOUND) // ERROR_FILE_NOT_FOUND means parent directory already exists
+ createFakePath(path.parent_path(), securityAttributes); // otherwise create parent directory (recursively)
+
+ BOOL res = CreateDirectoryW(path.c_str(), securityAttributes);
+ if (res)
+ k32FakeDirTracker.insert(path.wstring(), std::wstring());
+ else {
+ err = GetLastError();
+ throw shared::windows_error("createFakePath() CreateDirectoryW failed on: " + path.string(), err);
+ }
+ return true;
+ }
+
+ static bool addDirectoryMapping(const HookContext::Ptr &context, const fs::path& originalPath, const fs::path& reroutedPath)
+ {
+ if (originalPath.empty() || reroutedPath.empty()) {
+ spdlog::get("hooks")->error("RerouteW::addDirectoryMapping failed: {}, {}",
+ shared::string_cast(originalPath.wstring(), shared::CodePage::UTF8).c_str(),
+ shared::string_cast(reroutedPath.wstring(), shared::CodePage::UTF8).c_str());
+ return false;
+ }
+
+ auto lookupParent = context->redirectionTable()->findNode(originalPath.parent_path());
+ if (!lookupParent.get() || lookupParent->data().linkTarget.empty()) {
+ if (!addDirectoryMapping(context, originalPath.parent_path(), reroutedPath.parent_path()))
+ {
+ spdlog::get("hooks")->error("RerouteW::addDirectoryMapping failed: {}, {}",
+ shared::string_cast(originalPath.wstring(), shared::CodePage::UTF8).c_str(),
+ shared::string_cast(reroutedPath.wstring(), shared::CodePage::UTF8).c_str());
+ return false;
+ }
+ }
+
+ std::string reroutedU8
+ = shared::string_cast(reroutedPath.wstring(), shared::CodePage::UTF8);
+ if (reroutedU8.empty() || reroutedU8[reroutedU8.size() - 1] != '\\')
+ reroutedU8 += "\\";
+
+ spdlog::get("hooks")->info("mapping directory in vfs: {}, {}",
+ shared::string_cast(originalPath.wstring(), shared::CodePage::UTF8), reroutedU8.c_str());
+
+ context->redirectionTable().addDirectory(
+ originalPath, RedirectionDataLocal(reroutedU8),
+ shared::FLAG_DIRECTORY|shared::FLAG_CREATETARGET);
+
+ fs::directory_iterator end_itr;
+
+ // cycle through the directory
+ for (fs::directory_iterator itr(reroutedPath); itr != end_itr; ++itr)
+ {
+ // If it's not a directory, add it to the VFS, if it is recurse into it
+ if (is_regular_file(itr->path())) {
+ std::string fileReroutedU8 = shared::string_cast(itr->path().wstring(), shared::CodePage::UTF8);
+ spdlog::get("hooks")->info("mapping file in vfs: {}, {}",
+ shared::string_cast((originalPath / itr->path().filename()).wstring(), shared::CodePage::UTF8),
+ fileReroutedU8.c_str());
+ context->redirectionTable().addFile(fs::path(originalPath / itr->path().filename()), RedirectionDataLocal(fileReroutedU8));
+ } else {
+ addDirectoryMapping(context, originalPath / itr->path().filename(), reroutedPath / itr->path().filename());
+ }
+ }
+
+ return true;
+ }
+
+ template
+ static bool interestingPathImpl(const char_t *inPath)
+ {
+ if (!inPath || !inPath[0])
+ return false;
+ // ignore \\.\ unless its a \\.\?:
+ if (inPath[0] == '\\' && inPath[1] == '\\' && inPath[2] == '.' && inPath[3] == '\\' && (!inPath[4] || inPath[5] != ':'))
+ return false;
+ // ignore L"hid#":
+ if ((inPath[0] == 'h' || inPath[0] == 'H')
+ && ((inPath[1] == 'i' || inPath[1] == 'I'))
+ && ((inPath[2] == 'd' || inPath[2] == 'D'))
+ && inPath[3] == '#')
+ return false;
+ return true;
+ }
+
+ static bool interestingPath(const char* inPath) { return interestingPathImpl(inPath); }
+ static bool interestingPath(const wchar_t* inPath) { return interestingPathImpl(inPath); }
+
+ static fs::path absolutePath(const wchar_t *inPath)
+ {
+ if (shared::startswith(inPath, LR"(\\?\)") || shared::startswith(inPath, LR"(\??\)")) {
+ inPath += 4;
+ return inPath;
+ }
+ else if ((shared::startswith(inPath, LR"(\\localhost\)") || shared::startswith(inPath, LR"(\\127.0.0.1\)")) && inPath[13] == L'$') {
+ std::wstring newPath;
+ newPath += towupper(inPath[12]);
+ newPath += L':';
+ newPath += &inPath[14];
+ return newPath;
+ }
+ else if (inPath[0] == L'\0' || inPath[1] == L':') {
+ return inPath;
+ }
+ else if (inPath[0] == L'\\' || inPath[0] == L'/') {
+ return fs::path(winapi::wide::getFullPathName(inPath).first);
+ }
+ WCHAR currentDirectory[MAX_PATH];
+ ::GetCurrentDirectoryW(MAX_PATH, currentDirectory);
+ fs::path finalPath = fs::path(currentDirectory) / inPath;
+ return finalPath;
+ //winapi::wide::getFullPathName(inPath).first;
+ }
+
+ static fs::path canonizePath(const fs::path& inPath)
+ {
+ fs::path p = inPath.lexically_normal();
+ if (p.filename_is_dot())
+ p = p.remove_filename();
+ return p.make_preferred();
+ }
+
+ static RerouteW create(const HookContext::ConstPtr &context,
+ const HookCallContext &callContext,
+ const wchar_t *inPath, bool inverse = false)
+ {
+ RerouteW result;
+
+ if (interestingPath(inPath) && callContext.active())
+ {
+ const auto& lookupPath = canonizePath(absolutePath(inPath));
+ result.m_RealPath = lookupPath.wstring();
+
+ result.m_Buffer = k32DeleteTracker.lookup(result.m_RealPath);
+ bool found = !result.m_Buffer.empty();
+ if (found) {
+ spdlog::get("hooks")->info("Rerouting file open to location of deleted file: {}",
+ shared::string_cast(result.m_Buffer));
+ result.m_NewReroute = true;
+ } else {
+ const RedirectionTreeContainer &table
+ = inverse ? context->inverseTable() : context->redirectionTable();
+ result.m_FileNode = table->findNode(lookupPath);
+
+ if (result.m_FileNode.get()
+ && (!result.m_FileNode->data().linkTarget.empty() || result.m_FileNode->isDirectory()))
+ {
+ if (!result.m_FileNode->data().linkTarget.empty()) {
+ result.m_Buffer = shared::string_cast(
+ result.m_FileNode->data().linkTarget.c_str(), shared::CodePage::UTF8);
+ }
+ else
+ {
+ result.m_Buffer = result.m_FileNode->path().wstring();
+ }
+ found = true;
+ }
+ }
+ if (found) {
+ result.m_Rerouted = true;
+
+ wchar_t inIt = inPath[wcslen(inPath) - 1];
+ std::wstring::iterator outIt = result.m_Buffer.end() - 1;
+ if ((*outIt == L'\\' || *outIt == L'/') && !(inIt == L'\\' || inIt == L'/'))
+ result.m_Buffer.erase(outIt);
+ std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\');
+ }
+ else
+ result.m_Buffer = inPath;
+ }
+ else if (inPath)
+ result.m_Buffer = inPath;
+
+ if (inPath)
+ result.m_FileName = result.m_Buffer.c_str();
+ return result;
+ }
+
+ static RerouteW createNew(const HookContext::ConstPtr &context,
+ const HookCallContext &callContext,
+ LPCWSTR inPath, bool createPath = true,
+ LPSECURITY_ATTRIBUTES securityAttributes = nullptr)
+ {
+ RerouteW result;
+
+ if (interestingPath(inPath) && callContext.active())
+ {
+ const auto& lookupPath = canonizePath(absolutePath(inPath));
+ result.m_RealPath = lookupPath.wstring();
+
+ result.m_Buffer = k32DeleteTracker.lookup(result.m_RealPath);
+ bool found = !result.m_Buffer.empty();
+ if (found)
+ spdlog::get("hooks")->info("Rerouting file creation to original location of deleted file: {}",
+ shared::string_cast(result.m_Buffer));
+ else
+ {
+ FindCreateTarget visitor;
+ RedirectionTree::VisitorFunction visitorWrapper =
+ [&](const RedirectionTree::NodePtrT &node) { visitor(node); };
+ context->redirectionTable()->visitPath(lookupPath, visitorWrapper);
+ if (visitor.target.get()) {
+ // the visitor has found the last (deepest in the directory hierarchy)
+ // create-target
+ fs::path relativePath
+ = shared::make_relative(visitor.target->path(), lookupPath);
+ result.m_Buffer =
+ (fs::path(visitor.target->data().linkTarget.c_str()) / relativePath).wstring();
+ found = true;
+ }
+ }
+
+ if (found)
+ {
+ if (createPath) {
+ try {
+ FunctionGroupLock lock(MutExHookGroup::ALL_GROUPS);
+ result.m_PathCreated =
+ createFakePath(fs::path(result.m_Buffer).parent_path(), securityAttributes);
+ }
+ catch (const std::exception &e) {
+ spdlog::get("hooks")->error("failed to create {}: {}",
+ shared::string_cast(result.m_Buffer), e.what());
+ }
+ }
+
+ wchar_t inIt = inPath[wcslen(inPath) - 1];
+ std::wstring::iterator outIt = result.m_Buffer.end() - 1;
+ if ((*outIt == L'\\' || *outIt == L'/') && !(inIt == L'\\' || inIt == L'/'))
+ result.m_Buffer.erase(outIt);
+ std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\');
+ result.m_Rerouted = true;
+ result.m_NewReroute = true;
+ }
+ else
+ result.m_Buffer = inPath;
+ }
+ else if (inPath)
+ result.m_Buffer = inPath;
+
+ if (inPath)
+ result.m_FileName = result.m_Buffer.c_str();
+ return result;
+ }
+
+ static RerouteW createOrNew(const HookContext::ConstPtr &context, const HookCallContext &callContext,
+ LPCWSTR inPath, bool createPath = true, LPSECURITY_ATTRIBUTES securityAttributes = nullptr)
+ {
+ {
+ auto res = create(context, callContext, inPath);
+ if (res.wasRerouted() || !interestingPath(inPath) || !callContext.active() || pathExists(inPath))
+ return std::move(res);
+ }
+ return createNew(context, callContext, inPath, createPath, securityAttributes);
+ }
+
+ static RerouteW noReroute(LPCWSTR inPath)
+ {
+ RerouteW result;
+ if (inPath)
+ result.m_Buffer = inPath;
+ if (inPath && inPath[0] && !shared::startswith(inPath, L"hid#"))
+ std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\');
+ result.m_FileName = result.m_Buffer.c_str();
+ return result;
+ }
+
+private:
+ struct FindCreateTarget {
+ RedirectionTree::NodePtrT target;
+ void operator()(RedirectionTree::NodePtrT node)
+ {
+ if (node->hasFlag(shared::FLAG_CREATETARGET)) {
+ target = node;
+ }
+ }
+ };
+};
+
+class CreateRerouter {
+public:
+ bool rerouteCreate(const HookContext::ConstPtr &context, const HookCallContext &callContext,
+ LPCWSTR lpFileName, DWORD& dwCreationDisposition, DWORD dwDesiredAccess, LPSECURITY_ATTRIBUTES lpSecurityAttributes)
+ {
+ enum class Open { existing, create, empty };
+ Open open = Open::existing;
+
+ // Notice since we are calling our patched GetFileAttributesW here this will also check virtualized paths
+ DWORD virtAttr = GetFileAttributesW(lpFileName);
+ bool isFile = virtAttr != INVALID_FILE_ATTRIBUTES && (virtAttr & FILE_ATTRIBUTE_DIRECTORY) == 0;
+ m_isDir = virtAttr != INVALID_FILE_ATTRIBUTES && (virtAttr & FILE_ATTRIBUTE_DIRECTORY);
+
+ switch (dwCreationDisposition) {
+ case CREATE_ALWAYS:
+ open = Open::create;
+ if (isFile || m_isDir) {
+ m_error = ERROR_ALREADY_EXISTS;
+ }
+ break;
+
+ case CREATE_NEW:
+ if (isFile || m_isDir) {
+ m_error = ERROR_FILE_EXISTS;
+ return false;
+ } else {
+ open = Open::create;
+ }
+ break;
+
+ case OPEN_ALWAYS:
+ if (isFile || m_isDir) {
+ m_error = ERROR_ALREADY_EXISTS;
+ } else {
+ open = Open::create;
+ }
+ break;
+
+ case TRUNCATE_EXISTING:
+ if ((dwDesiredAccess & GENERIC_WRITE) == 0) {
+ m_error = ERROR_INVALID_PARAMETER;
+ return false;
+ }
+ if (isFile || m_isDir)
+ open = Open::empty;
+ break;
+ }
+
+ if (m_isDir && pathIsDirectory(lpFileName))
+ m_reroute = RerouteW::noReroute(lpFileName);
+ else
+ m_reroute = RerouteW::create(context, callContext, lpFileName);
+
+ if (m_reroute.wasRerouted() && open == Open::create && pathIsDirectory(m_reroute.fileName()))
+ m_reroute = RerouteW::createNew(context, callContext, lpFileName, true, lpSecurityAttributes);
+
+ if (!m_isDir && !isFile && !m_reroute.wasRerouted() && (open == Open::create || open == Open::empty))
+ {
+ m_reroute = RerouteW::createNew(context, callContext, lpFileName, true, lpSecurityAttributes);
+
+ bool newFile = !m_reroute.wasRerouted() && pathDirectlyAvailable(m_reroute.fileName());
+ if (newFile && open == Open::empty)
+ // TRUNCATE_EXISTING will fail since the new file doesn't exist, so change disposition:
+ dwCreationDisposition = CREATE_ALWAYS;
+ }
+
+ return true;
+ }
+
+ // rerouteNew is used for rerouting the destination of copy/move operations. Assumes that the call will be skipped if false is returned.
+ bool rerouteNew(const HookContext::ConstPtr &context, HookCallContext &callContext, LPCWSTR lpFileName, bool replaceExisting, const char* hookName)
+ {
+ DWORD disposition = replaceExisting ? CREATE_ALWAYS : CREATE_NEW;
+ if (!rerouteCreate(context, callContext, lpFileName, disposition, GENERIC_WRITE, nullptr)) {
+ spdlog::get("hooks")->info(
+ "{} guaranteed failure, skipping original call: {}, replaceExisting={}, error={}",
+ hookName, shared::string_cast(lpFileName, shared::CodePage::UTF8), replaceExisting ? "true" : "false", error());
+
+ callContext.updateLastError(error());
+ return false;
+ }
+ return true;
+ }
+
+ void updateResult(HookCallContext &callContext, bool success)
+ {
+ m_originalError = callContext.lastError();
+ if (success) {
+ // m_error != ERROR_SUCCESS means we are overriding the error on success
+ if (m_error == ERROR_SUCCESS)
+ m_error = m_originalError;
+ }
+ else if (m_originalError == ERROR_PATH_NOT_FOUND && m_directlyAvailable)
+ m_error = ERROR_FILE_NOT_FOUND;
+ else
+ m_error = m_originalError;
+ if (m_error != m_originalError)
+ callContext.updateLastError(m_error);
+ }
+
+ DWORD error() const { return m_error; }
+ DWORD originalError() const { return m_originalError; }
+ bool changedError() const { return m_error != m_originalError; }
+
+ bool isDir() const { return m_isDir; }
+ bool newReroute() const { return m_reroute.newReroute(); }
+ bool wasRerouted() const { return m_reroute.wasRerouted(); }
+ LPCWSTR fileName() const { return m_reroute.fileName(); }
+
+ void insertMapping(const HookContext::Ptr &context, bool directory = false) { m_reroute.insertMapping(context, directory); }
+
+private:
+ DWORD m_error = ERROR_SUCCESS;
+ DWORD m_originalError = ERROR_SUCCESS;
+ bool m_directlyAvailable = false;
+ bool m_isDir = false;
+ RerouteW m_reroute;
+};
+
+} // namespace usvfs
diff --git a/test/tvfs_test/main.cpp b/test/tvfs_test/main.cpp
index 031d6f7d..7629c280 100644
--- a/test/tvfs_test/main.cpp
+++ b/test/tvfs_test/main.cpp
@@ -160,6 +160,7 @@ TEST_F(USVFSTest, CanResizeRedirectiontree)
});
}
+/*
TEST_F(USVFSTest, CreateFileHookReportsCorrectErrorOnMissingFile)
{
EXPECT_NO_THROW({
@@ -178,7 +179,9 @@ TEST_F(USVFSTest, CreateFileHookReportsCorrectErrorOnMissingFile)
EXPECT_EQ(ERROR_FILE_NOT_FOUND, ::GetLastError());
});
}
+*/
+/*
TEST_F(USVFSTestWithReroute, CreateFileHookRedirectsFile)
{
EXPECT_NE(INVALID_HANDLE_VALUE
@@ -190,6 +193,7 @@ TEST_F(USVFSTestWithReroute, CreateFileHookRedirectsFile)
, FILE_ATTRIBUTE_NORMAL
, nullptr));
}
+*/
TEST_F(USVFSTest, GetFileAttributesHookReportsCorrectErrorOnMissingFile)
diff --git a/vsbuild/usvfs_dll.vcxproj b/vsbuild/usvfs_dll.vcxproj
index 84a7ff89..1973e02e 100644
--- a/vsbuild/usvfs_dll.vcxproj
+++ b/vsbuild/usvfs_dll.vcxproj
@@ -226,6 +226,7 @@
+
diff --git a/vsbuild/usvfs_dll.vcxproj.filters b/vsbuild/usvfs_dll.vcxproj.filters
index 01ffd7ce..407a9c5f 100644
--- a/vsbuild/usvfs_dll.vcxproj.filters
+++ b/vsbuild/usvfs_dll.vcxproj.filters
@@ -103,5 +103,8 @@
Header Files\include
+
+ Header Files
+
\ No newline at end of file