aboutsummaryrefslogtreecommitdiff
path: root/libs/usvfs/src/usvfs_dll/maptracker.h
diff options
context:
space:
mode:
Diffstat (limited to 'libs/usvfs/src/usvfs_dll/maptracker.h')
-rw-r--r--libs/usvfs/src/usvfs_dll/maptracker.h691
1 files changed, 691 insertions, 0 deletions
diff --git a/libs/usvfs/src/usvfs_dll/maptracker.h b/libs/usvfs/src/usvfs_dll/maptracker.h
new file mode 100644
index 0000000..6ddf471
--- /dev/null
+++ b/libs/usvfs/src/usvfs_dll/maptracker.h
@@ -0,0 +1,691 @@
+#pragma once
+
+#include "hookcallcontext.h"
+#include "hookcontext.h"
+#include "stringcast.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<std::shared_mutex> 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<std::shared_mutex> 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<std::shared_mutex> lock(m_mutex);
+ m_map[fromPath] = toPath;
+ }
+
+ bool erase(const std::wstring& fromPath)
+ {
+ if (fromPath.empty())
+ return false;
+ std::unique_lock<std::shared_mutex> lock(m_mutex);
+ return m_map.erase(fromPath);
+ }
+
+private:
+ mutable std::shared_mutex m_mutex;
+ std::unordered_map<std::wstring, std::wstring> 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<std::string>(m_RealPath, shared::CodePage::UTF8),
+ shared::string_cast<std::string>(m_FileName, shared::CodePage::UTF8));
+ m_FileNode = context->redirectionTable().addFile(
+ m_RealPath, RedirectionDataLocal(shared::string_cast<std::string>(
+ 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<std::string>(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<std::string>(parent));
+ } else if (GetLastError() != ERROR_DIR_NOT_EMPTY) {
+ auto error = GetLastError();
+ spdlog::get("usvfs")->warn("removing fake directory failed: {}, error={}",
+ shared::string_cast<std::string>(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<std::string>(
+ originalPath.wstring(), shared::CodePage::UTF8)
+ .c_str(),
+ shared::string_cast<std::string>(
+ 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<std::string>(
+ originalPath.wstring(), shared::CodePage::UTF8)
+ .c_str(),
+ shared::string_cast<std::string>(
+ reroutedPath.wstring(), shared::CodePage::UTF8)
+ .c_str());
+ return false;
+ }
+ }
+
+ std::string reroutedU8 = shared::string_cast<std::string>(reroutedPath.wstring(),
+ shared::CodePage::UTF8);
+ if (reroutedU8.empty() || reroutedU8[reroutedU8.size() - 1] != '\\')
+ reroutedU8 += "\\";
+
+ spdlog::get("hooks")->info("mapping directory in vfs: {}, {}",
+ shared::string_cast<std::string>(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<std::string>(
+ itr->path().wstring(), shared::CodePage::UTF8);
+ spdlog::get("hooks")->info(
+ "mapping file in vfs: {}, {}",
+ shared::string_cast<std::string>(
+ (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 <class char_t>
+ 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<std::string>(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<std::wstring>(
+ 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<std::string>(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<std::string>(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<std::string>(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