diff options
Diffstat (limited to 'libs/usvfs/src')
73 files changed, 0 insertions, 15674 deletions
diff --git a/libs/usvfs/src/shared/CMakeLists.txt b/libs/usvfs/src/shared/CMakeLists.txt deleted file mode 100644 index b1a7c05..0000000 --- a/libs/usvfs/src/shared/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -cmake_minimum_required(VERSION 3.16) - -find_package(Boost CONFIG REQUIRED COMPONENTS algorithm interprocess filesystem) -find_package(spdlog CONFIG REQUIRED) - -file(GLOB sources "*.cpp" "*.h") -source_group("" FILES ${sources}) - -add_library(shared STATIC ${sources}) -target_link_libraries(shared - PUBLIC - Boost::algorithm Boost::interprocess comsuppw - Boost::filesystem spdlog::spdlog_header_only) -target_include_directories(shared - PUBLIC ${PROJECT_SOURCE_DIR}/include/usvfs ${CMAKE_CURRENT_SOURCE_DIR}) -target_precompile_headers(shared PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/pch.h) -target_compile_options(shared PUBLIC /FI"pch.h") -target_compile_definitions(shared PUBLIC - SPDLOG_USE_STD_FORMAT SPDLOG_NO_NAME SPDLOG_NO_REGISTRY_MUTEX - BOOST_INTERPROCESS_SHARED_DIR_FUNC BOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE - NOMINMAX _WINDOWS _UNICODE UNICODE - _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR -) diff --git a/libs/usvfs/src/shared/addrtools.h b/libs/usvfs/src/shared/addrtools.h deleted file mode 100644 index b888ebf..0000000 --- a/libs/usvfs/src/shared/addrtools.h +++ /dev/null @@ -1,64 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include "windows_sane.h" - -namespace usvfs::shared -{ - -#ifdef _M_AMD64 -typedef DWORD64 REGWORD; -#elif _M_IX86 -typedef DWORD REGWORD; -#endif - -inline LPVOID AddrAdd(LPVOID address, size_t offset) -{ - return reinterpret_cast<LPVOID>(reinterpret_cast<LPBYTE>(address) + offset); -} - -inline std::ptrdiff_t AddrDiff(LPVOID lhs, LPVOID rhs) -{ - return reinterpret_cast<LPBYTE>(lhs) - reinterpret_cast<LPBYTE>(rhs); -} - -// implicitly cast pointer to void*, from there cast to target type. -// This is supposed to be safer than directly reinterpret-casting -template <typename T> -inline T void_ptr_cast(void* ptr) -{ - return reinterpret_cast<T>(ptr); -} - -template <> -inline int64_t void_ptr_cast(void* ptr) -{ - return reinterpret_cast<int64_t>(ptr); -} - -template <> -inline uint64_t void_ptr_cast(void* ptr) -{ - return reinterpret_cast<uint64_t>(ptr); -} - -} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/directory_tree.cpp b/libs/usvfs/src/shared/directory_tree.cpp deleted file mode 100644 index 33d7ae1..0000000 --- a/libs/usvfs/src/shared/directory_tree.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include "directory_tree.h" -#include "tree_container.h" - -namespace usvfs::shared -{ - -fs::path::iterator nextIter(const fs::path::iterator& iter, - const fs::path::iterator& end) -{ - fs::path::iterator next = iter; - advanceIter(next, end); - return next; -} - -void advanceIter(fs::path::iterator& iter, const fs::path::iterator& end) -{ - if (iter == end) { - return; - } - - ++iter; - while (iter != end && (iter->wstring() == L"/" || iter->wstring() == L"\\" || - iter->wstring() == L".")) - ++iter; -} - -} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/directory_tree.h b/libs/usvfs/src/shared/directory_tree.h deleted file mode 100644 index 1712071..0000000 --- a/libs/usvfs/src/shared/directory_tree.h +++ /dev/null @@ -1,650 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include "exceptionex.h" -#include "logging.h" -#include "shared_memory.h" -#include "stringutils.h" -#include "wildcard.h" - -// simplify unit tests by allowing access to private members -#ifndef PRIVATE -#define PRIVATE private -#endif // PRIVATE - -namespace usvfs::shared -{ - -template <typename T, typename U> -struct SHMDataCreator -{ - static T create(const U& source, const VoidAllocatorT& allocator) - { - return T(source, allocator); - } -}; - -template <typename T, typename U> -T createData(const U& source, const VoidAllocatorT& allocator) -{ - return SHMDataCreator<T, U>::create(source, allocator); -} - -template <typename T> -T createDataEmpty(const VoidAllocatorT& allocator); - -template <typename T> -void dataAssign(T& destination, const T& source); - -// crappy little workaround for fs::path iterating over path separators -fs::path::iterator nextIter(const fs::path::iterator& iter, - const fs::path::iterator& end); - -void advanceIter(fs::path::iterator& iter, const fs::path::iterator& end); - -// decomposes a path into its components -// -class DecomposablePath -{ -public: - explicit DecomposablePath(std::string s) : m_s(std::move(s)), m_begin(0), m_end(0) - { - m_end = nextSeparator(m_begin); - } - - // move to the next component, returns false when there are no more components - // - bool next() - { - for (;;) { - if (m_end >= m_s.size()) { - // done - m_begin = m_end; - return false; - } - - // move begin to one past the last separator found - m_begin = m_end + 1; - - // find the next separator - m_end = nextSeparator(m_begin); - - // check for components that should be ignored: - // - empty, happens when the path ends with a separator - // - slashes, happens with consecutive separators - // - dot, unnecessary - const auto c = current(); - if (!c.empty() && c != "\\" && c != "/" && c != ".") { - return true; - } - } - } - - // checks if next() would return false - // - bool peekNext() const - { - auto copy = *this; - return copy.next(); - } - - // the current component, empty when next() returned false - // - std::string_view current() const { return {m_s.data() + m_begin, m_end - m_begin}; } - -private: - const std::string m_s; - std::size_t m_begin, m_end; - - // finds the next path separator - // - std::size_t nextSeparator(std::size_t from) const - { - while (from < m_s.size()) { - if (m_s[from] == '/' || m_s[from] == '\\') { - break; - } - - ++from; - } - - return from; - } -}; - -namespace bi = boost::interprocess; -namespace bmi = boost::multi_index; - -typedef uint8_t TreeFlags; - -static const TreeFlags FLAG_DIRECTORY = 0x01; -static const TreeFlags FLAG_DUMMY = 0x02; -static const TreeFlags FLAG_FIRSTUSERFLAG = 0x10; - -struct MissingThrowT -{}; -static const MissingThrowT MissingThrow = MissingThrowT(); - -template <typename NodeDataT> -class TreeContainer; - -template <typename T1, typename T2, typename Alloc> -struct mutable_pair -{ - typedef T1 first_type; - typedef T2 second_type; - - mutable_pair(Alloc alloc) : first(T1(alloc)), second(T2(alloc)) {} - - mutable_pair(const T1& f, const T2& s) : first(f), second(s) {} - - mutable_pair(const std::pair<T1, T2>& p) : first(p.first), second(p.second) {} - - T1 first; - mutable T2 second; -}; - -template <typename Key, typename T, typename Compare, typename Allocator, - typename Element = mutable_pair<Key, T, Allocator>> -using mimap = bmi::multi_index_container< - Element, - bmi::indexed_by< - bmi::ordered_unique<bmi::member<Element, Key, &Element::first>, Compare>>, - typename Allocator::template rebind<Element>::other>; - -/** - * a representation of a directory tree in memory. - * This class is designed to be stored in shared memory. - */ -template <typename NodeDataT> -class DirectoryTree -{ - template <typename T> - friend class TreeContainer; - -public: - struct CILess - { - template <typename U, typename V> - bool operator()(const U& lhs, const V& rhs) const - { - const size_t lhsLength = getLength(lhs); - const size_t rhsLength = getLength(rhs); - - const auto r = - _strnicmp(getCharPtr(lhs), getCharPtr(rhs), std::min(lhsLength, rhsLength)); - - if (r == 0) { - return lhsLength < rhsLength; - } - - return (r < 0); - } - - private: - const char* getCharPtr(const StringT& s) const { return s.c_str(); } - - const char* getCharPtr(const std::string& s) const { return s.c_str(); } - - const char* getCharPtr(const char* s) const { return s; } - - const char* getCharPtr(std::string_view s) const { return s.data(); } - - size_t getLength(const StringT& s) const { return s.size(); } - - size_t getLength(const std::string& s) const { return s.size(); } - - size_t getLength(const char* s) const { return strlen(s); } - - size_t getLength(std::string_view s) const { return s.size(); } - }; - - typedef DirectoryTree<NodeDataT> NodeT; - typedef bi::deleter<NodeT, SegmentManagerT> DeleterT; - typedef NodeDataT DataT; - - typedef bi::shared_ptr<NodeT, VoidAllocatorT, DeleterT> NodePtrT; - typedef bi::weak_ptr<NodeT, VoidAllocatorT, DeleterT> WeakPtrT; - - typedef bi::allocator<std::pair<const StringT, NodePtrT>, SegmentManagerT> - NodeEntryAllocatorT; - - typedef mimap<StringT, NodePtrT, CILess, NodeEntryAllocatorT> NodeMapT; - typedef typename NodeMapT::iterator file_iterator; - typedef typename NodeMapT::const_iterator const_file_iterator; - - typedef std::function<void(const NodePtrT&)> VisitorFunction; - - DirectoryTree() = delete; - DirectoryTree(const NodeT& reference) = delete; - DirectoryTree(NodeT&& reference) = delete; - NodeT& operator=(NodeT reference) = delete; - - /** - * @brief construct a new node to be inserted in an existing tree - **/ - DirectoryTree(std::string_view name, TreeFlags flags, const NodePtrT& parent, - const NodeDataT& data, const VoidAllocatorT& allocator) - : m_Parent(parent), m_Name(name.begin(), name.end(), allocator), m_Data(data), - m_Nodes(allocator), m_Flags(flags) - {} - - ~DirectoryTree() { m_Nodes.clear(); } - - /** - * @return parent node - */ - NodePtrT parent() const { return m_Parent.lock(); } - - /** - * @return the full path to the node - */ - fs::path path() const - { - if (m_Parent.lock().get() == nullptr) { - if (m_Name.size() == 0) { - return fs::path(); - } else { - return fs::path(m_Name.c_str()) / "\\"; - } - } else { - return m_Parent.lock()->path() / m_Name.c_str(); - } - } - - /** - * @return data connected to this node - **/ - const NodeDataT& data() const { return m_Data; } - - /** - * @return name of this node - */ - std::string name() const { return m_Name.c_str(); } - - /** - * @brief setFlag change a flag for this node - * @param enabled new state for the specified flag - */ - void setFlag(TreeFlags flag, bool enabled = true) - { - m_Flags = enabled ? m_Flags | flag : m_Flags & ~flag; - } - - /** - * @return true if the specified flag is set, false otherwise - */ - bool hasFlag(TreeFlags flag) const { return (m_Flags & flag) != 0; } - - /** - * @return true if this node is a directory, false if it's a regular file - */ - bool isDirectory() const { return hasFlag(FLAG_DIRECTORY); } - - /** - * @return the number of subnodes (directly) below this one - */ - size_t numNodes() const { return m_Nodes.size(); } - - /** - * @return number of nodes in this (sub-)tree including this one - */ - size_t numNodesRecursive() const - { - size_t result = numNodes() + 1; - - for (const auto& node : m_Nodes) { - result += node.second->numNodesRecursive(); - } - - return result; - } - - /** - * @brief find a node by its path - * @param path the path to look up - * @return a pointer to the node or a null ptr - */ - NodePtrT findNode(const fs::path& path) - { - fs::path::iterator iter = path.begin(); - if (iter == path.end()) { - return NodePtrT(); - } - return findNode(path, iter); - } - - /** - * @brief find a node by its path - * @param path the path to look up - * @return a pointer to the node or a null ptr - */ - const NodePtrT findNode(const fs::path& path) const - { - fs::path::iterator iter = path.begin(); - if (iter == path.end()) { - return NodePtrT(); - } - return findNode(path, iter); - } - - /** - * @brief visit the nodes along the specified path (in order) calling the visitor for - * each - * @param path the path to visit - * @param visitor a function called for each node - */ - void visitPath(const fs::path& path, const VisitorFunction& visitor) const - { - fs::path::iterator iter = path.begin(); - if (iter == path.end()) { - return; - } - visitPath(path, iter, visitor); - } - - /** - * @brief retrieve a node by the specified name - * @param name name of the node - * @return the node found or an empty pointer if no such node was found - */ - NodePtrT node(std::string_view name, MissingThrowT) const - { - auto iter = m_Nodes.find(name); - - if (iter != m_Nodes.end()) { - return iter->second; - } else { - USVFS_THROW_EXCEPTION(node_missing_error()); - } - } - - /** - * @brief retrieve a node by the specified name - * @param name name of the node - * @return the node found or an empty pointer if no such node was found - */ - NodePtrT node(std::string_view name) - { - auto iter = m_Nodes.find(name); - - if (iter != m_Nodes.end()) { - return iter->second; - } else { - return NodePtrT(); - } - } - - /** - * @brief retrieve a node by the specified name - * @param name name of the node - * @return the node found or an empty pointer if no such node was found - */ - const NodePtrT node(std::string_view name, MissingThrowT) - { - auto iter = m_Nodes.find(name); - - if (iter != m_Nodes.end()) { - return iter->second; - } else { - USVFS_THROW_EXCEPTION(node_missing_error()); - } - } - - /** - * @brief retrieve a node by the specified name - * @param name name of the node - * @return the node found or an empty pointer if no such node was found - */ - const NodePtrT node(std::string_view name) const - { - auto iter = m_Nodes.find(name); - - if (iter != m_Nodes.end()) { - return iter->second; - } else { - return NodePtrT(); - } - } - - /** - * @brief test if a node by the specified name exists - * @param name name of the node - * @return true if the node exists, false otherwise - */ - bool exists(std::string_view name) const - { - return m_Nodes.find(name) != m_Nodes.end(); - } - - /** - * @brief find all matches for a pattern - * @param pattern the pattern to look for - * @return a vector of the found nodes - */ - std::vector<NodePtrT> find(const std::string& pattern) const - { - // determine if there is a prefix in the pattern that indicates a specific - // directory. - size_t fixedPart = pattern.find_first_of("*?"); - - if (fixedPart == 0) - fixedPart = std::string::npos; - if (fixedPart != std::string::npos) - fixedPart = pattern.find_last_of(R"(\/)", fixedPart); - - std::vector<NodePtrT> result; - - if (fixedPart != std::string::npos) { - // if there is a prefix, search for the node representing that path and - // search only on that - NodePtrT node = findNode(fs::path(pattern.substr(0, fixedPart))); - if (node.get() != nullptr) { - node->findLocal(result, pattern.substr(fixedPart + 1)); - } - } else { - findLocal(result, pattern); - } - - return result; - } - - /** - * @return an iterator to the first leaf - **/ - file_iterator filesBegin() { return m_Nodes.begin(); } - - /** - * @return a const iterator to the first leaf - **/ - const_file_iterator filesBegin() const { return m_Nodes.begin(); } - - /** - * @return an iterator one past the last leaf - **/ - file_iterator filesEnd() { return m_Nodes.end(); } - - /** - * @return a const iterator one past the last leaf - **/ - const_file_iterator filesEnd() const { return m_Nodes.end(); } - - /** - * @brief erase the leaf at the specified iterator - * @return an iterator to the following file - **/ - file_iterator erase(file_iterator iter) { return m_Nodes.erase(iter); } - - /** - * @brief clear all nodes - */ - void clear() { m_Nodes.clear(); } - - void removeFromTree() - { - if (auto par = parent()) { - spdlog::get("usvfs")->info("remove from tree {}", m_Name.c_str()); - auto self = par->m_Nodes.find(m_Name.c_str()); - if (self != par->m_Nodes.end()) { - par->erase(self); - } else { - // trying to remove a node that does not exist, most likely because it was - // already removed in a lower level call. this is known to happen when MoveFile - // has the MOVEFILE_COPY_ALLOWED flag and moving a mapped file. - spdlog::get("usvfs")->warn("Failed to remove inexisting node from tree: {}", - m_Name.c_str()); - } - } - } - - PRIVATE : void set(StringT key, const NodePtrT& value) - { - auto res = m_Nodes.emplace(std::move(key), value); - if (!res.second) { - res.first->second = value; - } - } - - WeakPtrT findRoot() const - { - if (m_Parent.lock().get() == nullptr) { - return m_Self; - } else { - return m_Parent.lock()->findRoot(); - } - } - - NodePtrT findNode(const fs::path& name, fs::path::iterator& iter) - { - if (iter == name.end()) { - return NodePtrT(); - } - - std::string l = iter->string(); - auto subNode = m_Nodes.find(iter->string()); - advanceIter(iter, name.end()); - - if (iter == name.end()) { - // last name component, should be a local node - if (subNode != m_Nodes.end()) { - return subNode->second; - } else { - return NodePtrT(); - } - } else { - if (subNode != m_Nodes.end()) { - return subNode->second->findNode(name, iter); - } else { - return NodePtrT(); - } - } - } - - const NodePtrT findNode(const fs::path& name, fs::path::iterator& iter) const - { - if (iter == name.end()) { - return NodePtrT(); - } - - auto subNode = m_Nodes.find(iter->string()); - advanceIter(iter, name.end()); - - if (iter == name.end()) { - // last name component, should be a local node - if (subNode != m_Nodes.end()) { - return subNode->second; - } else { - return NodePtrT(); - } - } else { - if (subNode != m_Nodes.end()) { - return subNode->second->findNode(name, iter); - } else { - return NodePtrT(); - } - } - } - - void visitPath(const fs::path& path, fs::path::iterator& iter, - const VisitorFunction& visitor) const - { - if (iter == path.end()) { - return; - } - - auto subNode = m_Nodes.find(iter->string()); - - if (subNode != m_Nodes.end()) { - visitor(subNode->second); - advanceIter(iter, path.end()); - if (iter != path.end()) { - subNode->second->visitPath(path, iter, visitor); - } - } - } - - void findLocal(std::vector<NodePtrT>& output, const std::string& pattern) const - { - for (auto iter = m_Nodes.begin(); iter != m_Nodes.end(); ++iter) { - LPCSTR remainder = nullptr; - - if (pattern.size() > 1 && (pattern[0] == '*') && - ((pattern[1] == '/') || (pattern[1] == '\\')) && - iter->second->isDirectory()) { - // the star may represent a directory (one directory level, not - // multiple!), search in subdirectory - iter->second->findLocal(output, pattern.substr(1)); - } else if ((remainder = wildcard::PartialMatch(iter->second->name().c_str(), - pattern.c_str())) != nullptr) { - if ((*remainder == '\0') || (strcmp(remainder, "*") == 0)) { - NodePtrT node = iter->second; - output.push_back(node); - } - - if (iter->second->isDirectory()) { - iter->second->findLocal(output, remainder); - } - } - } - } - - PRIVATE : TreeFlags m_Flags; - - WeakPtrT m_Parent; - WeakPtrT m_Self; - - StringT m_Name; - NodeDataT m_Data; - - NodeMapT m_Nodes; -}; - -template <typename NodeDataT> -void dumpTree(std::ostream& stream, const DirectoryTree<NodeDataT>& tree, int level = 0) -{ - stream << std::string(level, ' ') << tree.name() << " -> " << tree.data() << "\n"; - for (auto iter = tree.filesBegin(); iter != tree.filesEnd(); ++iter) { - dumpTree<NodeDataT>(stream, *iter->second, level + 1); - } -} - -} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/exceptionex.cpp b/libs/usvfs/src/shared/exceptionex.cpp deleted file mode 100644 index 4a814b5..0000000 --- a/libs/usvfs/src/shared/exceptionex.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include "exceptionex.h" -#include "winapi.h" -#include <spdlog/spdlog.h> - -namespace usvfs::shared -{ - -std::string windows_error::constructMessage(const std::string& input, int inErrorCode) -{ - std::ostringstream finalMessage; - finalMessage << input; - - LPSTR buffer = nullptr; - - DWORD errorCode = inErrorCode != -1 ? inErrorCode : GetLastError(); - - // TODO: the message is not english? - if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPSTR)&buffer, 0, nullptr) == 0) { - finalMessage << " (errorcode " << errorCode << ")"; - } else { - LPSTR lastChar = buffer + strlen(buffer) - 2; - *lastChar = '\0'; - finalMessage << " (" << buffer << " [" << errorCode << "])"; - LocalFree(buffer); // allocated by FormatMessage - } - - SetLastError( - errorCode); // restore error code because FormatMessage might have modified it - return finalMessage.str(); -} - -} // namespace usvfs::shared - -void logExtInfo(const std::exception& e, LogLevel logLevel) -{ - std::string content; - - if (const std::string* msg = boost::get_error_info<ex_msg>(e)) { - content = *msg; - } - - if (const DWORD* errorCode = boost::get_error_info<ex_win_errcode>(e)) { - content = std::string("error: ") + winapi::ex::ansi::errorString(*errorCode); - } - - switch (logLevel) { - case LogLevel::Debug: - spdlog::get("usvfs")->debug(content); - break; - case LogLevel::Info: - spdlog::get("usvfs")->info(content); - break; - case LogLevel::Warning: - spdlog::get("usvfs")->warn(content); - break; - case LogLevel::Error: - spdlog::get("usvfs")->error(content); - break; - } -} diff --git a/libs/usvfs/src/shared/exceptionex.h b/libs/usvfs/src/shared/exceptionex.h deleted file mode 100644 index 4c298a5..0000000 --- a/libs/usvfs/src/shared/exceptionex.h +++ /dev/null @@ -1,99 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include "logging.h" -#include <boost/exception/all.hpp> -#include <stdexcept> - -typedef boost::error_info<struct tag_message, DWORD> ex_win_errcode; -typedef boost::error_info<struct tag_message, std::string> ex_msg; - -struct std_boost_exception : virtual boost::exception, virtual std::exception -{ - const char* what() const noexcept override - { - return boost::diagnostic_information_what(*this); - } -}; - -struct incompatibility_error : std_boost_exception -{}; -struct usage_error : std_boost_exception -{}; -struct data_error : std_boost_exception -{}; -struct file_not_found_error : std_boost_exception -{}; -struct timeout_error : std_boost_exception -{}; -struct unknown_error : std_boost_exception -{}; -struct node_missing_error : std_boost_exception -{}; - -#define USVFS_THROW_EXCEPTION(x) BOOST_THROW_EXCEPTION(x) - -void logExtInfo(const std::exception& e, LogLevel logLevel = LogLevel::Warning); - -namespace usvfs::shared -{ - -class windows_error : public std::runtime_error -{ -public: - windows_error(const std::string& message, int errorcode = GetLastError()) - : runtime_error(constructMessage(message, errorcode)), m_ErrorCode(errorcode) - {} - - int getErrorCode() const { return m_ErrorCode; } - -private: - int m_ErrorCode; - - std::string constructMessage(const std::string& input, int errorcode); -}; - -class guard -{ -public: - explicit guard(std::function<void()> f) : m_f(std::move(f)) {} - - ~guard() - { - if (m_f) { - m_f(); - } - } - - guard(const guard&); - guard& operator=(const guard&); - -private: - std::function<void()> m_f; -}; - -} // namespace usvfs::shared - -#define CONCATENATE_DIRECT(s1, s2) s1##s2 -#define CONCATENATE(s1, s2) CONCATENATE_DIRECT(s1, s2) -#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __LINE__) -#define ON_BLOCK_EXIT(f) usvfs::shared::guard ANONYMOUS_VARIABLE(guard)(f) diff --git a/libs/usvfs/src/shared/formatters.h b/libs/usvfs/src/shared/formatters.h deleted file mode 100644 index 3d0e47e..0000000 --- a/libs/usvfs/src/shared/formatters.h +++ /dev/null @@ -1,158 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2024. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include <format> -#include <type_traits> - -#include "ntdll_declarations.h" - -// formatters for standard types - -namespace usvfs::log -{ -std::string to_string(LPCWSTR value); -std::string to_string(PCUNICODE_STRING value); -} // namespace usvfs::log - -template <class Enum, class CharT> - requires std::is_enum_v<Enum> -struct std::formatter<Enum, CharT> : std::formatter<std::underlying_type_t<Enum>, CharT> -{ - template <class FmtContext> - FmtContext::iterator format(Enum v, FmtContext& ctx) const - { - return std::formatter<std::underlying_type_t<Enum>, CharT>::format( - static_cast<std::underlying_type_t<Enum>>(v), ctx); - } -}; - -template <> -struct std::formatter<LPCWSTR, char> : std::formatter<std::string, char> -{ - template <class FmtContext> - FmtContext::iterator format(LPCWSTR v, FmtContext& ctx) const - { - return std::formatter<std::string, char>::format(usvfs::log::to_string(v), ctx); - } -}; - -template <> -struct std::formatter<std::wstring, char> : std::formatter<LPCWSTR, char> -{ - template <class FmtContext> - FmtContext::iterator format(const std::wstring& v, FmtContext& ctx) const - { - return std::formatter<LPCWSTR, char>::format(v.c_str(), ctx); - } -}; - -template <> -struct std::formatter<PCUNICODE_STRING, char> : std::formatter<std::string, char> -{ - template <class FmtContext> - FmtContext::iterator format(PCUNICODE_STRING v, FmtContext& ctx) const - { - return std::formatter<std::string, char>::format(usvfs::log::to_string(v), ctx); - } -}; - -template <> -struct std::formatter<UNICODE_STRING, char> : std::formatter<std::string, char> -{ - template <class FmtContext> - FmtContext::iterator format(UNICODE_STRING v, FmtContext& ctx) const - { - return std::formatter<std::string, char>::format(usvfs::log::to_string(&v), ctx); - } -}; - -template <class Pointer> - requires(std::is_pointer_v<Pointer> && !std::is_same_v<Pointer, const char*> && - !std::is_same_v<Pointer, char*> && !std::is_same_v<Pointer, const void*> && - !std::is_same_v<Pointer, void*>) -struct std::formatter<Pointer, char> : std::formatter<const void*, char> -{ - template <class FmtContext> - FmtContext::iterator format(Pointer v, FmtContext& ctx) const - { - return std::formatter<const void*, char>::format(v, ctx); - } -}; - -namespace usvfs::log -{ - -/** - * a small helper class to wrap any object. The whole point is to give us a way - * to ensure our own operator<< is used in addParam calls - */ -template <typename T> -class Wrap -{ -public: - explicit Wrap(const T& data) : m_Data(data) {} - Wrap(Wrap<T>&& reference) : m_Data(std::move(reference.m_Data)) {} - - Wrap(const Wrap<T>& reference) = delete; - Wrap<T>& operator=(const Wrap<T>& reference) = delete; - -private: - friend struct ::std::formatter<Wrap<T>, char>; - const T& m_Data; -}; - -template <typename T> -Wrap<T> wrap(const T& data) -{ - return Wrap<T>(data); -} - -} // namespace usvfs::log - -template <> -struct std::formatter<usvfs::log::Wrap<DWORD>, char> : std::formatter<DWORD, char> -{ - template <class FmtContext> - FmtContext::iterator format(const usvfs::log::Wrap<DWORD>& v, FmtContext& ctx) const - { - return std::format_to(ctx.out(), "{:x}", v.m_Data); - } -}; - -template <> -struct std::formatter<usvfs::log::Wrap<NTSTATUS>, char> : std::formatter<NTSTATUS, char> -{ - template <class FmtContext> - FmtContext::iterator format(const usvfs::log::Wrap<NTSTATUS>& v, - FmtContext& ctx) const - { - switch (v.m_Data) { - case 0x00000000: - return std::format_to(ctx.out(), "ok"); - case 0xC0000022: - return std::format_to(ctx.out(), "access denied"); - case 0xC0000035: - return std::format_to(ctx.out(), "exists already"); - } - return std::format_to(ctx.out(), "err {:x}", v.m_Data); - } -}; diff --git a/libs/usvfs/src/shared/loghelpers.cpp b/libs/usvfs/src/shared/loghelpers.cpp deleted file mode 100644 index bfcabf1..0000000 --- a/libs/usvfs/src/shared/loghelpers.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include <format> - -#include "formatters.h" -#include "loghelpers.h" -#include "stringcast.h" -#include "stringutils.h" - -namespace ush = usvfs::shared; - -namespace usvfs::log -{ - -// this is declared in formatters but is defined here to avoid a whole .cpp -// file for two small functions -// -// comments from old code, taken as is: -// -// 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 -// -std::string to_string(LPCWSTR value) -{ - if (value == nullptr) { - return "<null>"; - } - try { - return ush::string_cast<std::string>(value, ush::CodePage::UTF8); - } catch (const std::exception& e) { - return std::format("<err: {}>", e.what()); - } -} -std::string to_string(PCUNICODE_STRING value) -{ - try { - return ush::string_cast<std::string>(value->Buffer, ush::CodePage::UTF8, - value->Length / sizeof(WCHAR)); - } catch (const std::exception& e) { - return std::format("<err: {}>", e.what()); - } -} - -spdlog::level::level_enum ConvertLogLevel(LogLevel level) -{ - switch (level) { - case LogLevel::Debug: - return spdlog::level::debug; - case LogLevel::Info: - return spdlog::level::info; - case LogLevel::Warning: - return spdlog::level::warn; - case LogLevel::Error: - return spdlog::level::err; - default: - return spdlog::level::debug; - } -} - -LogLevel ConvertLogLevel(spdlog::level::level_enum level) -{ - switch (level) { - case spdlog::level::debug: - return LogLevel::Debug; - case spdlog::level::info: - return LogLevel::Info; - case spdlog::level::warn: - return LogLevel::Warning; - case spdlog::level::err: - return LogLevel::Error; - default: - return LogLevel::Debug; - } -} - -} // namespace usvfs::log diff --git a/libs/usvfs/src/shared/loghelpers.h b/libs/usvfs/src/shared/loghelpers.h deleted file mode 100644 index 071ad9d..0000000 --- a/libs/usvfs/src/shared/loghelpers.h +++ /dev/null @@ -1,123 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include "dllimport.h" -#include "formatters.h" -#include "ntdll_declarations.h" -#include "shmlogger.h" -#include "stringutils.h" - -namespace usvfs::log -{ - -enum class DisplayStyle : uint8_t -{ - Hex = 0x01 -}; - -class CallLoggerDummy -{ -public: - template <typename T> - CallLoggerDummy& addParam(const char*, const T&, uint8_t style = 0) - { - return *this; - } -}; - -class CallLogger -{ -public: - explicit CallLogger(const char* function) - { - const char* namespaceend = strrchr(function, ':'); - - if (namespaceend != nullptr) { - function = namespaceend + 1; - } - - m_Message = function; - } - - ~CallLogger() - { - try { - static std::shared_ptr<spdlog::logger> log = spdlog::get("hooks"); - log->debug("{}", m_Message); - } catch (...) { - // suppress all exceptions in destructor - } - } - - template <typename T> - CallLogger& addParam(const char* name, const T& value, uint8_t style = 0); - -private: - std::string m_Message; -}; - -template <typename T> -CallLogger& CallLogger::addParam(const char* name, const T& value, uint8_t style) -{ - static bool enabled = spdlog::get("hooks")->should_log(spdlog::level::debug); - typedef std::underlying_type<DisplayStyle>::type DSType; - - if (enabled) { - if constexpr (std::is_pointer_v<T>) { - if (value == nullptr) { - std::format_to(std::back_inserter(m_Message), "[{}=<null>]", name); - } else { - std::format_to(std::back_inserter(m_Message), "[{}={}]", name, value); - } - } else if constexpr (std::is_integral_v<T>) { - if (style & static_cast<DSType>(DisplayStyle::Hex)) { - std::format_to(std::back_inserter(m_Message), "[{}={:x}]", name, value); - } else { - std::format_to(std::back_inserter(m_Message), "[{}={}]", name, value); - } - } else { - std::format_to(std::back_inserter(m_Message), "[{}={}]", name, value); - } - } - - return *this; -} - -spdlog::level::level_enum ConvertLogLevel(LogLevel level); -LogLevel ConvertLogLevel(spdlog::level::level_enum level); - -} // namespace usvfs::log - -// prefer the short variant of the function name, without signature. -// Fall back to the portable boost macro -#ifdef __FUNCTION__ -#define __MYFUNC__ __FUNCTION__ -#else -#define __MYFUNC__ BOOST_CURRENT_FUNCTION -#endif - -#define LOG_CALL() usvfs::log::CallLogger(__MYFUNC__) - -#define PARAM(val) addParam(#val, val) -#define PARAMHEX(val) \ - addParam(#val, val, static_cast<uint8_t>(usvfs::log::DisplayStyle::Hex)) -#define PARAMWRAP(val) addParam(#val, usvfs::log::wrap(val)) diff --git a/libs/usvfs/src/shared/ntdll_declarations.cpp b/libs/usvfs/src/shared/ntdll_declarations.cpp deleted file mode 100644 index b4bf0f5..0000000 --- a/libs/usvfs/src/shared/ntdll_declarations.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include "ntdll_declarations.h" -#include <cassert> - -#define LOAD_EXT(mod, name) \ - name = reinterpret_cast<name##_type>(::GetProcAddress(mod, #name)); \ - assert(name != nullptr) - -NtQueryDirectoryFile_type NtQueryDirectoryFile; -NtQueryDirectoryFileEx_type NtQueryDirectoryFileEx; -NtQueryFullAttributesFile_type NtQueryFullAttributesFile; -NtQueryAttributesFile_type NtQueryAttributesFile; -NtQueryObject_type NtQueryObject; -NtQueryInformationFile_type NtQueryInformationFile; -NtQueryInformationByName_type NtQueryInformationByName; -NtOpenFile_type NtOpenFile; -NtCreateFile_type NtCreateFile; -NtClose_type NtClose; -RtlDoesFileExists_U_type RtlDoesFileExists_U; -RtlDosPathNameToRelativeNtPathName_U_WithStatus_type - RtlDosPathNameToRelativeNtPathName_U_WithStatus; -RtlReleaseRelativeName_type RtlReleaseRelativeName; -RtlGetVersion_type RtlGetVersion; -NtTerminateProcess_type NtTerminateProcess; - -static bool ntdll_initialized; - -void ntdll_declarations_init() -{ - if (!ntdll_initialized) { - HMODULE ntDLLMod = GetModuleHandleW(L"ntdll.dll"); - - LOAD_EXT(ntDLLMod, NtQueryDirectoryFile); - LOAD_EXT(ntDLLMod, NtQueryDirectoryFileEx); - LOAD_EXT(ntDLLMod, NtQueryFullAttributesFile); - LOAD_EXT(ntDLLMod, NtQueryAttributesFile); - LOAD_EXT(ntDLLMod, NtQueryObject); - LOAD_EXT(ntDLLMod, NtQueryInformationFile); - LOAD_EXT(ntDLLMod, NtQueryInformationByName); - LOAD_EXT(ntDLLMod, NtCreateFile); - LOAD_EXT(ntDLLMod, NtOpenFile); - LOAD_EXT(ntDLLMod, NtClose); - LOAD_EXT(ntDLLMod, RtlDoesFileExists_U); - LOAD_EXT(ntDLLMod, RtlDosPathNameToRelativeNtPathName_U_WithStatus); - LOAD_EXT(ntDLLMod, RtlReleaseRelativeName); - LOAD_EXT(ntDLLMod, RtlGetVersion); - LOAD_EXT(ntDLLMod, NtTerminateProcess); - - ntdll_initialized = true; - } -} - -static struct __Initializer -{ - __Initializer() { ntdll_declarations_init(); } -} __initializer; diff --git a/libs/usvfs/src/shared/ntdll_declarations.h b/libs/usvfs/src/shared/ntdll_declarations.h deleted file mode 100644 index 813ad8c..0000000 --- a/libs/usvfs/src/shared/ntdll_declarations.h +++ /dev/null @@ -1,612 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include "windows_sane.h" - -#pragma warning(push) -#pragma warning(disable : 4201) - -typedef LONG NTSTATUS; - -typedef struct _IO_STATUS_BLOCK -{ - union - { - NTSTATUS Status; - PVOID Pointer; - } DUMMYUNIONNAME; - - ULONG_PTR Information; -} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; - -typedef struct _FILE_DIRECTORY_INFORMATION -{ - ULONG NextEntryOffset; - ULONG FileIndex; - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER EndOfFile; - LARGE_INTEGER AllocationSize; - ULONG FileAttributes; - ULONG FileNameLength; - WCHAR FileName[1]; -} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION; - -typedef struct _FILE_FULL_DIR_INFORMATION -{ - ULONG NextEntryOffset; - ULONG FileIndex; - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER EndOfFile; - LARGE_INTEGER AllocationSize; - ULONG FileAttributes; - ULONG FileNameLength; - ULONG EaSize; - WCHAR FileName[1]; -} FILE_FULL_DIR_INFORMATION, *PFILE_FULL_DIR_INFORMATION; - -typedef struct _FILE_ID_FULL_DIR_INFORMATION -{ - ULONG NextEntryOffset; - ULONG FileIndex; - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER EndOfFile; - LARGE_INTEGER AllocationSize; - ULONG FileAttributes; - ULONG FileNameLength; - ULONG EaSize; - LARGE_INTEGER FileId; - WCHAR FileName[1]; -} FILE_ID_FULL_DIR_INFORMATION, *PFILE_ID_FULL_DIR_INFORMATION; - -typedef struct _FILE_BOTH_DIR_INFORMATION -{ - ULONG NextEntryOffset; - ULONG FileIndex; - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER EndOfFile; - LARGE_INTEGER AllocationSize; - ULONG FileAttributes; - ULONG FileNameLength; - ULONG EaSize; - CCHAR ShortNameLength; - WCHAR ShortName[12]; - WCHAR FileName[1]; -} FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION; - -typedef struct _FILE_ID_BOTH_DIR_INFORMATION -{ - ULONG NextEntryOffset; - ULONG FileIndex; - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER EndOfFile; - LARGE_INTEGER AllocationSize; - ULONG FileAttributes; - ULONG FileNameLength; - ULONG EaSize; - CCHAR ShortNameLength; - WCHAR ShortName[12]; - LARGE_INTEGER FileId; - WCHAR FileName[1]; -} FILE_ID_BOTH_DIR_INFORMATION, *PFILE_ID_BOTH_DIR_INFORMATION; - -typedef struct _FILE_BASIC_INFORMATION -{ - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - ULONG FileAttributes; -} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION; - -typedef struct _FILE_RENAME_INFORMATION -{ -#if (_WIN32_WINNT >= _WIN32_WINNT_WIN10_RS1) - union - { - BOOLEAN ReplaceIfExists; // FileRenameInformation - ULONG Flags; // FileRenameInformationEx - } DUMMYUNIONNAME; -#else - BOOLEAN ReplaceIfExists; -#endif - HANDLE RootDirectory; - ULONG FileNameLength; - WCHAR FileName[1]; -} FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION; - -typedef struct _FILE_STANDARD_INFORMATION -{ - LARGE_INTEGER AllocationSize; - LARGE_INTEGER EndOfFile; - ULONG NumberOfLinks; - BOOLEAN DeletePending; - BOOLEAN Directory; -} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION; - -typedef struct _FILE_NAMES_INFORMATION -{ - ULONG NextEntryOffset; - ULONG FileIndex; - ULONG FileNameLength; - WCHAR FileName[1]; -} FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION; - -typedef struct _FILE_INTERNAL_INFORMATION -{ - LARGE_INTEGER IndexNumber; -} FILE_INTERNAL_INFORMATION, *PFILE_INTERNAL_INFORMATION; - -typedef struct _FILE_EA_INFORMATION -{ - ULONG EaSize; -} FILE_EA_INFORMATION, *PFILE_EA_INFORMATION; - -typedef struct _FILE_ACCESS_INFORMATION -{ - ACCESS_MASK AccessFlags; -} FILE_ACCESS_INFORMATION, *PFILE_ACCESS_INFORMATION; - -typedef struct _FILE_POSITION_INFORMATION -{ - LARGE_INTEGER CurrentByteOffset; -} FILE_POSITION_INFORMATION, *PFILE_POSITION_INFORMATION; - -typedef struct _FILE_MODE_INFORMATION -{ - ULONG Mode; -} FILE_MODE_INFORMATION, *PFILE_MODE_INFORMATION; - -typedef struct _FILE_ALIGNMENT_INFORMATION -{ - ULONG AlignmentRequirement; -} FILE_ALIGNMENT_INFORMATION, *PFILE_ALIGNMENT_INFORMATION; - -typedef struct _FILE_NAME_INFORMATION -{ - ULONG FileNameLength; - WCHAR FileName[1]; -} FILE_NAME_INFORMATION, *PFILE_NAME_INFORMATION; - -typedef struct _FILE_ID_EXTD_DIR_INFORMATION -{ - ULONG NextEntryOffset; - ULONG FileIndex; - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER EndOfFile; - LARGE_INTEGER AllocationSize; - ULONG FileAttributes; - ULONG FileNameLength; - ULONG EaSize; - ULONG ReparsePointTag; - FILE_ID_128 FileId; - WCHAR FileName[1]; -} FILE_ID_EXTD_DIR_INFORMATION, *PFILE_ID_EXTD_DIR_INFORMATION; - -typedef struct _FILE_ID_EXTD_BOTH_DIR_INFORMATION -{ - ULONG NextEntryOffset; - ULONG FileIndex; - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER EndOfFile; - LARGE_INTEGER AllocationSize; - ULONG FileAttributes; - ULONG FileNameLength; - ULONG EaSize; - ULONG ReparsePointTag; - FILE_ID_128 FileId; - CCHAR ShortNameLength; - WCHAR ShortName[12]; - WCHAR FileName[1]; -} FILE_ID_EXTD_BOTH_DIR_INFORMATION, *PFILE_ID_EXTD_BOTH_DIR_INFORMATION; - -typedef struct _FILE_ID_64_EXTD_DIR_INFORMATION -{ - ULONG NextEntryOffset; - ULONG FileIndex; - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER EndOfFile; - LARGE_INTEGER AllocationSize; - ULONG FileAttributes; - ULONG FileNameLength; - ULONG EaSize; - ULONG ReparsePointTag; - LARGE_INTEGER FileId; - WCHAR FileName[1]; -} FILE_ID_64_EXTD_DIR_INFORMATION, *PFILE_ID_64_EXTD_DIR_INFORMATION; - -typedef struct _FILE_ID_64_EXTD_BOTH_DIR_INFORMATION -{ - ULONG NextEntryOffset; - ULONG FileIndex; - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER EndOfFile; - LARGE_INTEGER AllocationSize; - ULONG FileAttributes; - ULONG FileNameLength; - ULONG EaSize; - ULONG ReparsePointTag; - LARGE_INTEGER FileId; - CCHAR ShortNameLength; - WCHAR ShortName[12]; - WCHAR FileName[1]; -} FILE_ID_64_EXTD_BOTH_DIR_INFORMATION, *PFILE_ID_64_EXTD_BOTH_DIR_INFORMATION; - -typedef struct _FILE_ID_ALL_EXTD_DIR_INFORMATION -{ - ULONG NextEntryOffset; - ULONG FileIndex; - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER EndOfFile; - LARGE_INTEGER AllocationSize; - ULONG FileAttributes; - ULONG FileNameLength; - ULONG EaSize; - ULONG ReparsePointTag; - LARGE_INTEGER FileId; - FILE_ID_128 FileId128; - WCHAR FileName[1]; -} FILE_ID_ALL_EXTD_DIR_INFORMATION, *PFILE_ID_ALL_EXTD_DIR_INFORMATION; - -typedef struct _FILE_ID_ALL_EXTD_BOTH_DIR_INFORMATION -{ - ULONG NextEntryOffset; - ULONG FileIndex; - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER EndOfFile; - LARGE_INTEGER AllocationSize; - ULONG FileAttributes; - ULONG FileNameLength; - ULONG EaSize; - ULONG ReparsePointTag; - LARGE_INTEGER FileId; - FILE_ID_128 FileId128; - CCHAR ShortNameLength; - WCHAR ShortName[12]; - WCHAR FileName[1]; -} FILE_ID_ALL_EXTD_BOTH_DIR_INFORMATION, *PFILE_ID_ALL_EXTD_BOTH_DIR_INFORMATION; - -typedef struct _FILE_ALL_INFORMATION -{ - FILE_BASIC_INFORMATION BasicInformation; - FILE_STANDARD_INFORMATION StandardInformation; - FILE_INTERNAL_INFORMATION InternalInformation; - FILE_EA_INFORMATION EaInformation; - FILE_ACCESS_INFORMATION AccessInformation; - FILE_POSITION_INFORMATION PositionInformation; - FILE_MODE_INFORMATION ModeInformation; - FILE_ALIGNMENT_INFORMATION AlignmentInformation; - FILE_NAME_INFORMATION NameInformation; -} FILE_ALL_INFORMATION, *PFILE_ALL_INFORMATION; - -typedef struct _FILE_OBJECTID_INFORMATION -{ - LONGLONG FileReference; - UCHAR ObjectId[16]; - union - { - struct - { - UCHAR BirthVolumeId[16]; - UCHAR BirthObjectId[16]; - UCHAR DomainId[16]; - }; - UCHAR ExtendedInfo[48]; - }; -} FILE_OBJECTID_INFORMATION, *PFILE_OBJECTID_INFORMATION; - -typedef struct _FILE_REPARSE_POINT_INFORMATION -{ - LONGLONG FileReference; - 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) -#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) -#define STATUS_NO_SUCH_FILE ((NTSTATUS)0xC000000FL) - -#define SL_RESTART_SCAN 0x01 -#define SL_RETURN_SINGLE_ENTRY 0x02 -#define SL_INDEX_SPECIFIED 0x04 -#define SL_RETURN_ON_DISK_ENTRIES_ONLY 0x08 - -#define SL_QUERY_DIRECTORY_MASK 0x0b - -typedef enum _FILE_INFORMATION_CLASS -{ - FileDirectoryInformation = 1, - FileFullDirectoryInformation = 2, - FileBothDirectoryInformation = 3, - FileStandardInformation = 5, - FileNameInformation = 9, - FileRenameInformation = 10, - FileNamesInformation = 12, - FileAllInformation = 18, - FileObjectIdInformation = 29, - FileReparsePointInformation = 33, - FileIdBothDirectoryInformation = 37, - FileIdFullDirectoryInformation = 38, - FileNormalizedNameInformation = 48, - FileIdExtdDirectoryInformation = 60, - FileIdExtdBothDirectoryInformation = 63, - FileId64ExtdDirectoryInformation = 78, - FileId64ExtdBothDirectoryInformation = 79, - FileIdAllExtdDirectoryInformation = 80, - FileIdAllExtdBothDirectoryInformation = 81 -} FILE_INFORMATION_CLASS, - *PFILE_INFORMATION_CLASS; - -typedef enum _MODE -{ - KernelMode, - UserMode, - MaximumMode -} MODE; - -typedef struct _IO_STATUS_BLOCK IO_STATUS_BLOCK; - -typedef struct _IO_STATUS_BLOCK* PIO_STATUS_BLOCK; -// typedef VOID (NTAPI *PIO_APC_ROUTINE )(__in PVOID ApcContext, __in -// PIO_STATUS_BLOCK IoStatusBlock, __in ULONG Reserved); -typedef VOID(NTAPI* PIO_APC_ROUTINE)(PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, - ULONG Reserved); -typedef enum _FILE_INFORMATION_CLASS FILE_INFORMATION_CLASS; - -typedef struct _UNICODE_STRING -{ - USHORT Length; - USHORT MaximumLength; - PWSTR Buffer; -} UNICODE_STRING, *PUNICODE_STRING; -typedef const UNICODE_STRING* PCUNICODE_STRING; - -typedef struct _OBJECT_ATTRIBUTES -{ - ULONG Length; - HANDLE RootDirectory; - PUNICODE_STRING ObjectName; - ULONG Attributes; - PVOID SecurityDescriptor; - PVOID SecurityQualityOfService; -} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES; - -typedef enum _POOL_TYPE -{ - NonPagedPool, - NonPagedPoolExecute = NonPagedPool, - PagedPool, - NonPagedPoolMustSucceed = NonPagedPool + 2, - DontUseThisType, - NonPagedPoolCacheAligned = NonPagedPool + 4, - PagedPoolCacheAligned, - NonPagedPoolCacheAlignedMustS = NonPagedPool + 6, - MaxPoolType, - NonPagedPoolBase = 0, - NonPagedPoolBaseMustSucceed = NonPagedPoolBase + 2, - NonPagedPoolBaseCacheAligned = NonPagedPoolBase + 4, - NonPagedPoolBaseCacheAlignedMustS = NonPagedPoolBase + 6, - NonPagedPoolSession = 32, - PagedPoolSession = NonPagedPoolSession + 1, - NonPagedPoolMustSucceedSession = PagedPoolSession + 1, - DontUseThisTypeSession = NonPagedPoolMustSucceedSession + 1, - NonPagedPoolCacheAlignedSession = DontUseThisTypeSession + 1, - PagedPoolCacheAlignedSession = NonPagedPoolCacheAlignedSession + 1, - NonPagedPoolCacheAlignedMustSSession = PagedPoolCacheAlignedSession + 1, - NonPagedPoolNx = 512, - NonPagedPoolNxCacheAligned = NonPagedPoolNx + 4, - NonPagedPoolSessionNx = NonPagedPoolNx + 32 -} POOL_TYPE; - -typedef struct _OBJECT_TYPE_INITIALIZER -{ - WORD Length; - UCHAR ObjectTypeFlags; - ULONG CaseInsensitive : 1; - ULONG UnnamedObjectsOnly : 1; - ULONG UseDefaultObject : 1; - ULONG SecurityRequired : 1; - ULONG MaintainHandleCount : 1; - ULONG MaintainTypeList : 1; - ULONG ObjectTypeCode; - ULONG InvalidAttributes; - GENERIC_MAPPING GenericMapping; - ULONG ValidAccessMask; - POOL_TYPE PoolType; - ULONG DefaultPagedPoolCharge; - ULONG DefaultNonPagedPoolCharge; - PVOID DumpProcedure; - LONG* OpenProcedure; - PVOID CloseProcedure; - PVOID DeleteProcedure; - LONG* ParseProcedure; - LONG* SecurityProcedure; - LONG* QueryNameProcedure; - UCHAR* OkayToCloseProcedure; -} OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER; - -typedef CCHAR KPROCESSOR_MODE; - -typedef struct _OBJECT_HANDLE_INFORMATION -{ - ULONG HandleAttributes; - ACCESS_MASK GrantedAccess; -} OBJECT_HANDLE_INFORMATION, *POBJECT_HANDLE_INFORMATION; - -typedef struct _OBJECT_NAME_INFORMATION -{ - UNICODE_STRING Name; -} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION; - -typedef enum _OBJECT_INFORMATION_CLASS -{ - ObjectBasicInformation = 0, - ObjectNameInformation = 1, - ObjectTypeInformation = 2 -} OBJECT_INFORMATION_CLASS; - -typedef struct _RTL_RELATIVE_NAME -{ - UNICODE_STRING RelativeName; - HANDLE ContainingDirectory; - void* CurDirRef; -} RTL_RELATIVE_NAME, *PRTL_RELATIVE_NAME; - -typedef struct _FILE_NETWORK_OPEN_INFORMATION -{ - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER AllocationSize; - LARGE_INTEGER EndOfFile; - ULONG FileAttributes; -} FILE_NETWORK_OPEN_INFORMATION, *PFILE_NETWORK_OPEN_INFORMATION; - -#define FILE_DIRECTORY_FILE 0x00000001 -#define FILE_WRITE_THROUGH 0x00000002 -#define FILE_SEQUENTIAL_ONLY 0x00000004 -#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008 -#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010 -#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 -#define FILE_NON_DIRECTORY_FILE 0x00000040 -#define FILE_CREATE_TREE_CONNECTION 0x00000080 -#define FILE_COMPLETE_IF_OPLOCKED 0x00000100 -#define FILE_NO_EA_KNOWLEDGE 0x00000200 -#define FILE_OPEN_REMOTE_INSTANCE 0x00000400 -#define FILE_RANDOM_ACCESS 0x00000800 -#define FILE_DELETE_ON_CLOSE 0x00001000 -#define FILE_OPEN_BY_FILE_ID 0x00002000 -#define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000 -#define FILE_NO_COMPRESSION 0x00008000 -#define FILE_OPEN_REQUIRING_OPLOCK 0x00010000 -#define FILE_DISALLOW_EXCLUSIVE 0x00020000 -#define FILE_SESSION_AWARE 0x00040000 -#define FILE_RESERVE_OPFILTER 0x00100000 -#define FILE_OPEN_REPARSE_POINT 0x00200000 -#define FILE_OPEN_NO_RECALL 0x00400000 -#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 -#define FILE_CONTAINS_EXTENDED_CREATE_INFORMATION 0x10000000 - -// Nt - -using NtQueryDirectoryFile_type = NTSTATUS(WINAPI*)(HANDLE, HANDLE, PIO_APC_ROUTINE, - PVOID, PIO_STATUS_BLOCK, PVOID, - ULONG, FILE_INFORMATION_CLASS, - BOOLEAN, PUNICODE_STRING, BOOLEAN); -using NtQueryDirectoryFileEx_type = NTSTATUS(WINAPI*)(HANDLE, HANDLE, PIO_APC_ROUTINE, - PVOID, PIO_STATUS_BLOCK, PVOID, - ULONG, FILE_INFORMATION_CLASS, - ULONG, PUNICODE_STRING); - -using NtQueryFullAttributesFile_type = - NTSTATUS(WINAPI*)(POBJECT_ATTRIBUTES, PFILE_NETWORK_OPEN_INFORMATION); -using NtQueryAttributesFile_type = NTSTATUS(WINAPI*)(POBJECT_ATTRIBUTES, - PFILE_BASIC_INFORMATION); - -using NtQueryObject_type = NTSTATUS(WINAPI*)( - HANDLE Handle, OBJECT_INFORMATION_CLASS ObjectInformationClass, - PVOID ObjectInformation, ULONG ObjectInformationLength, PULONG ReturnLength); -using NtQueryInformationFile_type = NTSTATUS(WINAPI*)( - HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, - ULONG Length, FILE_INFORMATION_CLASS FileInformationClass); - -using NtQueryInformationByName_type = NTSTATUS(WINAPI*)( - HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, - ULONG Length, FILE_INFORMATION_CLASS FileInformationClass); - -using NtOpenFile_type = NTSTATUS(WINAPI*)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, - PIO_STATUS_BLOCK, ULONG, ULONG); -using NtCreateFile_type = NTSTATUS(WINAPI*)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, - PIO_STATUS_BLOCK, PLARGE_INTEGER, ULONG, - ULONG, ULONG, ULONG, PVOID, ULONG); - -using NtClose_type = NTSTATUS(WINAPI*)(HANDLE); - -using NtTerminateProcess_type = NTSTATUS(WINAPI*)(HANDLE ProcessHandle, - NTSTATUS ExitStatus); - -// Rtl - -using RtlDoesFileExists_U_type = NTSYSAPI BOOLEAN(NTAPI*)(PCWSTR); -using RtlDosPathNameToRelativeNtPathName_U_WithStatus_type = - NTSTATUS(NTAPI*)(PCWSTR DosFileName, PUNICODE_STRING NtFileName, PWSTR* FilePath, - PRTL_RELATIVE_NAME RelativeName); -using RtlReleaseRelativeName_type = void(NTAPI*)(PRTL_RELATIVE_NAME RelativeName); -using RtlGetVersion_type = NTSTATUS(NTAPI*)(PRTL_OSVERSIONINFOW); - -extern NtQueryDirectoryFile_type NtQueryDirectoryFile; -extern NtQueryDirectoryFileEx_type NtQueryDirectoryFileEx; -extern NtQueryFullAttributesFile_type NtQueryFullAttributesFile; -extern NtQueryAttributesFile_type NtQueryAttributesFile; -extern NtQueryObject_type NtQueryObject; -extern NtQueryInformationFile_type NtQueryInformationFile; -extern NtQueryInformationByName_type NtQueryInformationByName; -extern NtOpenFile_type NtOpenFile; -extern NtCreateFile_type NtCreateFile; -extern NtClose_type NtClose; -extern NtTerminateProcess_type NtTerminateProcess; -extern RtlDoesFileExists_U_type RtlDoesFileExists_U; -extern RtlDosPathNameToRelativeNtPathName_U_WithStatus_type - RtlDosPathNameToRelativeNtPathName_U_WithStatus; -extern RtlReleaseRelativeName_type RtlReleaseRelativeName; -extern RtlGetVersion_type RtlGetVersion; - -// ensures ntdll functions have been initialized (only needed during static objects -// initialization) -void ntdll_declarations_init(); - -#pragma warning(pop) diff --git a/libs/usvfs/src/shared/pch.cpp b/libs/usvfs/src/shared/pch.cpp deleted file mode 100644 index 1d9f38c..0000000 --- a/libs/usvfs/src/shared/pch.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "pch.h" diff --git a/libs/usvfs/src/shared/pch.h b/libs/usvfs/src/shared/pch.h deleted file mode 100644 index e787f68..0000000 --- a/libs/usvfs/src/shared/pch.h +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#include <algorithm> -#include <atomic> -#include <bitset> -#include <cassert> -#include <codecvt> -#include <cstddef> -#include <cstdint> -#include <cstring> -#include <ctime> -#include <exception> -#include <format> -#include <functional> -#include <future> -#include <iomanip> -#include <ios> -#include <limits> -#include <map> -#include <memory> -#include <mutex> -#include <regex> -#include <shared_mutex> -#include <sstream> -#include <stdexcept> -#include <string> -#include <thread> -#include <type_traits> -#include <utility> -#include <vector> - -#ifndef NOMINMAX -#define NOMINMAX -#endif - -// clang-format off -#define WIN32_LEAN_AND_MEAN -#include <Windows.h> -#include <ShlObj.h> -#include <comutil.h> -#include <Psapi.h> -#include <shellapi.h> -#include <shlwapi.h> -#include <fileapi.h> -#include <DbgHelp.h> -// clang-format on - -#define BOOST_INTERPROCESS_SEGMENT_MANAGER_ABI 1 -#include <boost/algorithm/string.hpp> -#include <boost/algorithm/string/case_conv.hpp> -#include <boost/any.hpp> -#include <boost/container/flat_set.hpp> -#include <boost/container/map.hpp> -#include <boost/container/scoped_allocator.hpp> -#include <boost/container/slist.hpp> -#include <boost/container/string.hpp> -#include <boost/container/vector.hpp> -#include <boost/current_function.hpp> -#include <boost/dll/runtime_symbol_info.hpp> -#include <boost/filesystem.hpp> -#include <boost/filesystem/detail/utf8_codecvt_facet.hpp> -#include <boost/filesystem/operations.hpp> -#include <boost/filesystem/path.hpp> -#include <boost/format.hpp> -#include <boost/interprocess/ipc/message_queue.hpp> -#include <boost/interprocess/managed_windows_shared_memory.hpp> -#include <boost/interprocess/offset_ptr.hpp> -#include <boost/interprocess/smart_ptr/deleter.hpp> -#include <boost/interprocess/smart_ptr/shared_ptr.hpp> -#include <boost/interprocess/smart_ptr/weak_ptr.hpp> -#include <boost/interprocess/sync/named_mutex.hpp> -#include <boost/lexical_cast.hpp> -#include <boost/locale.hpp> -#include <boost/multi_index/member.hpp> -#include <boost/multi_index/ordered_index.hpp> -#include <boost/multi_index_container.hpp> -#include <boost/predef.h> -#include <boost/static_assert.hpp> -#include <boost/thread.hpp> -#include <boost/thread/shared_lock_guard.hpp> -#include <boost/thread/shared_mutex.hpp> -#include <boost/tokenizer.hpp> -#include <boost/type_traits.hpp> - -#include <spdlog/spdlog.h> - -namespace fs = boost::filesystem; diff --git a/libs/usvfs/src/shared/shared_memory.h b/libs/usvfs/src/shared/shared_memory.h deleted file mode 100644 index 28da34b..0000000 --- a/libs/usvfs/src/shared/shared_memory.h +++ /dev/null @@ -1,53 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ - -#pragma once - -namespace bi = boost::interprocess; -namespace bc = boost::container; - -namespace usvfs::shared -{ - -template <typename T> -using OffsetPtrT = bi::offset_ptr<T, std::int32_t, std::uint64_t>; - -using VoidPointerT = OffsetPtrT<void>; - -// important: the windows shared memory mechanism, unlike other implementations, -// automatically removes the SHM object when there are no more "subscribers". -// MO currently depends on that feature! - -// managed_windows_shared_memory apparently doesn't support sharing between -// 64bit and 32bit processes -using managed_windows_shared_memory = bi::basic_managed_windows_shared_memory< - char, bi::rbtree_best_fit<bi::mutex_family, VoidPointerT, 8>, bi::iset_index>; - -using SharedMemoryT = managed_windows_shared_memory; -using SegmentManagerT = SharedMemoryT::segment_manager; - -using VoidAllocatorT = boost::container::scoped_allocator_adaptor< - boost::interprocess::allocator<void, SegmentManagerT>>; -using CharAllocatorT = VoidAllocatorT::rebind<char>::other; - -using StringT = bc::basic_string<char, std::char_traits<char>, CharAllocatorT>; - -} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/shmlogger.cpp b/libs/usvfs/src/shared/shmlogger.cpp deleted file mode 100644 index 66512de..0000000 --- a/libs/usvfs/src/shared/shmlogger.cpp +++ /dev/null @@ -1,205 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma warning(disable : 4714) -#pragma warning(disable : 4503) -#pragma warning(push, 3) -#include "shmlogger.h" -#pragma warning(pop) - -#pragma warning(disable : 4996) - -using namespace boost::interprocess; -using namespace boost::posix_time; - -SHMLogger* SHMLogger::s_Instance = nullptr; - -SHMLogger::owner_t SHMLogger::owner; -SHMLogger::client_t SHMLogger::client; - -SHMLogger::SHMLogger(owner_t, const std::string& queueName) - : m_QueueName(queueName), - m_LogQueue(create_only, queueName.c_str(), MESSAGE_COUNT, MESSAGE_SIZE), - m_DroppedMessages(0) -{ - if (s_Instance != nullptr) { - throw std::runtime_error("duplicate shm logger instantiation"); - } else { - s_Instance = this; - } -} - -SHMLogger::SHMLogger(client_t, const std::string& queueName) - : m_QueueName(queueName), m_LogQueue(open_only, queueName.c_str()), - m_DroppedMessages(0) -{ - if (s_Instance != nullptr) { - throw std::runtime_error("duplicate shm logger instantiation"); - } else { - s_Instance = this; - } -} - -SHMLogger::~SHMLogger() -{ - s_Instance = nullptr; - message_queue_interop::remove(m_QueueName.c_str()); -} - -SHMLogger& SHMLogger::create(const char* instanceName) -{ - if (s_Instance != nullptr) { - throw std::runtime_error("duplicate shm logger instantiation"); - } else { - std::string queueName = std::string("__shm_sink_") + instanceName; - message_queue::remove(queueName.c_str()); - new SHMLogger(owner, queueName); - atexit([]() { - delete s_Instance; - }); - } - return *s_Instance; -} - -SHMLogger& SHMLogger::open(const char* instanceName) -{ - if (s_Instance != nullptr) { - throw std::runtime_error("duplicate shm logger instantiation"); - } else { - new SHMLogger(client, std::string("__shm_sink_") + instanceName); - } - return *s_Instance; -} - -void SHMLogger::free() -{ - if (s_Instance != nullptr) { - SHMLogger* temp = s_Instance; - s_Instance = nullptr; - delete temp; - } -} - -bool SHMLogger::tryGet(char* buffer, size_t bufferSize) -{ - message_queue_interop::size_type receivedSize; - unsigned int prio; - bool res = m_LogQueue.try_receive(buffer, static_cast<unsigned int>(bufferSize), - receivedSize, prio); - if (res) { - buffer[std::min(bufferSize - 1, static_cast<size_t>(receivedSize))] = '\0'; - } - return res; -} - -void SHMLogger::get(char* buffer, size_t bufferSize) -{ - message_queue_interop::size_type receivedSize; - unsigned int prio; - m_LogQueue.receive(buffer, static_cast<unsigned int>(bufferSize), receivedSize, prio); - buffer[std::min(bufferSize - 1, static_cast<size_t>(receivedSize))] = '\0'; -} - -usvfs::sinks::shm_sink::shm_sink(const char* queueName) - : m_LogQueue(open_only, (std::string("__shm_sink_") + queueName).c_str()), - m_DroppedMessages(0) -{} - -void usvfs::sinks::shm_sink::flush_() {} - -void usvfs::sinks::shm_sink::sink_it_(const spdlog::details::log_msg& msg) -{ - int droppedMessages = m_DroppedMessages.load(std::memory_order_relaxed); - if (droppedMessages > 0) { - auto dropMessage = std::format("{} debug messages dropped", droppedMessages); - if (m_LogQueue.try_send(dropMessage.c_str(), - static_cast<unsigned int>(dropMessage.size()), 0)) { - m_DroppedMessages.store(0, std::memory_order_relaxed); - } - } - - std::string message{msg.payload}; - - // blacklist %USERNAME% because PII - static const std::string username = std::string(getenv("USERNAME")); - boost::algorithm::ireplace_all(message, std::string("\\") + username, "\\USERNAME"); - boost::algorithm::ireplace_all(message, std::string("/") + username, "/USERNAME"); - - if (message.length() > SHMLogger::MESSAGE_SIZE) { - std::vector<std::string> splitVec; - boost::split(splitVec, message, boost::is_any_of("\r\n"), boost::token_compress_on); - for (const std::string& line : splitVec) { - output(msg.level, line); - } - } else { - output(msg.level, message); - } -} - -void usvfs::sinks::shm_sink::output(spdlog::level::level_enum lev, - const std::string& message) -{ - bool sent = true; - - // spdlog auto-append line breaks which we don't need. Probably would be - // better to not write the breaks to begin with? - size_t count = std::min(message.find_last_not_of("\r\n") + 1, - static_cast<size_t>(m_LogQueue.get_max_msg_size())); - - // depending on the log level, drop less important messages if the receiver - // can't keep up - switch (lev) { - case spdlog::level::trace: - case spdlog::level::debug: - case spdlog::level::info: { - // m_LogQueue.send(message.c_str(), count, 0); - sent = m_LogQueue.try_send(message.c_str(), static_cast<unsigned int>(count), 0); - } break; - case spdlog::level::err: - case spdlog::level::critical: { - m_LogQueue.send(message.c_str(), static_cast<unsigned int>(count), 0); - } break; - default: { - boost::posix_time::ptime time = - microsec_clock::universal_time() + boost::posix_time::milliseconds(200); - sent = m_LogQueue.timed_send(message.c_str(), static_cast<unsigned int>(count), 0, - time); - } break; - } - - if (!sent) { - m_DroppedMessages.fetch_add(1, std::memory_order_relaxed); - } -} - -void __cdecl boost::interprocess::ipcdetail::get_shared_dir(std::string& shared_dir) -{ - PWSTR path; - if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_ProgramData, 0, NULL, &path))) { - _bstr_t bPath(path); - shared_dir = (char*)bPath; - shared_dir += "\\USVFS"; - } else { - shared_dir = "C:\\ProgramData\\USVFS"; - } - boost::filesystem::path boostPath(shared_dir); - if (!boost::filesystem::exists(boostPath)) - boost::filesystem::create_directories(boostPath); -} diff --git a/libs/usvfs/src/shared/shmlogger.h b/libs/usvfs/src/shared/shmlogger.h deleted file mode 100644 index 158f941..0000000 --- a/libs/usvfs/src/shared/shmlogger.h +++ /dev/null @@ -1,105 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include <spdlog/details/null_mutex.h> -#include <spdlog/sinks/base_sink.h> - -#include "logging.h" -#include "shared_memory.h" -#include "windows_sane.h" - -typedef boost::interprocess::message_queue_t<usvfs::shared::VoidPointerT> - message_queue_interop; - -namespace usvfs::sinks -{ -class shm_sink : public spdlog::sinks::base_sink<spdlog::details::null_mutex> -{ -public: - shm_sink(const char* queueName); - -protected: - void sink_it_(const spdlog::details::log_msg& msg) override; - void output(spdlog::level::level_enum lev, const std::string& message); - void flush_() override; - -private: - message_queue_interop m_LogQueue; - std::atomic<int> m_DroppedMessages; -}; - -} // namespace usvfs::sinks - -class SHMLogger -{ -public: - static const size_t MESSAGE_COUNT = 1024; - static const size_t MESSAGE_SIZE = 512; - - static SHMLogger& create(const char* instanceName); - static SHMLogger& open(const char* instanceName); - static void free(); - - static bool isInstantiated() { return s_Instance != nullptr; } - - static inline SHMLogger& instance() - { - if (s_Instance == nullptr) { - throw std::runtime_error("shm logger not instantiated"); - } - - return *s_Instance; - } - - void log(LogLevel logLevel, const std::string& message); - bool tryGet(char* buffer, size_t bufferSize); - void get(char* buffer, size_t bufferSize); - -private: - struct owner_t - {}; - static owner_t owner; - - struct client_t - {}; - static client_t client; - -private: - SHMLogger(owner_t, const std::string& instanceName); - SHMLogger(client_t, const std::string& instanceName); - - SHMLogger(const SHMLogger&) = delete; - SHMLogger& operator=(const SHMLogger&) = delete; - - ~SHMLogger(); - -private: - static SHMLogger* s_Instance; - - message_queue_interop m_LogQueue; - - std::string m_SHMName; - std::string m_LockName; - std::string m_QueueName; - - std::atomic<int> m_DroppedMessages; -}; diff --git a/libs/usvfs/src/shared/stringcast.cpp b/libs/usvfs/src/shared/stringcast.cpp deleted file mode 100644 index 47822d5..0000000 --- a/libs/usvfs/src/shared/stringcast.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include "stringcast.h" - -namespace usvfs::shared -{ - -UINT windowsCP(CodePage codePage) -{ - switch (codePage) { - case CodePage::LOCAL: - return CP_ACP; - case CodePage::UTF8: - return CP_UTF8; - case CodePage::LATIN1: - return 850; - } - // this should not be possible in practice - throw std::runtime_error("unsupported codePage"); -} - -} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/stringcast.h b/libs/usvfs/src/shared/stringcast.h deleted file mode 100644 index 0ce56d1..0000000 --- a/libs/usvfs/src/shared/stringcast.h +++ /dev/null @@ -1,170 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include "exceptionex.h" - -namespace usvfs::shared -{ - -enum class CodePage -{ - LOCAL, - LATIN1, - UTF8 -}; - -template <typename ToT, typename FromT> -class string_cast_impl -{ -public: - static ToT cast(const FromT& source, CodePage codePage, size_t sourceLength); -}; - -template <typename ToT, typename FromT> -ToT string_cast(FromT source, CodePage codePage = CodePage::LOCAL, - size_t sourceLength = std::numeric_limits<size_t>::max()) -{ - return string_cast_impl<ToT, FromT>::cast(source, codePage, sourceLength); -} - -template <typename ToT, typename CharT> -class string_cast_impl<ToT, std::basic_string<CharT>> -{ -public: - static ToT cast(const std::basic_string<CharT>& source, CodePage codePage, - size_t sourceLength) - { - return string_cast_impl<ToT, const CharT*>::cast(source.c_str(), codePage, - sourceLength); - } -}; - -template <typename ToT, typename CharT> -class string_cast_impl<ToT, CharT*> -{ - BOOST_STATIC_ASSERT(!boost::is_base_and_derived<ToT, CharT>::value); - -public: - static ToT cast(CharT* source, CodePage codePage, size_t sourceLength) - { - return string_cast_impl<ToT, const CharT*>::cast(source, codePage, sourceLength); - } -}; - -template <typename ToT, typename CharT, int N> -class string_cast_impl<ToT, CharT[N]> -{ -public: - static ToT cast(CharT (&source)[N], CodePage codePage, size_t sourceLength) - { - return string_cast_impl<ToT, const CharT*>::cast(source, codePage, sourceLength); - } -}; - -UINT windowsCP(CodePage codePage); - -template <> -class string_cast_impl<std::string, const wchar_t*> -{ -public: - static std::string cast(const wchar_t* const& source, CodePage codePage, - size_t sourceLength) - { - std::string result; - - if (sourceLength == std::numeric_limits<size_t>::max()) { - sourceLength = wcslen(source); - } - - if (sourceLength > 0) { - // use utf8 or local 8-bit encoding depending on user choice - UINT cp = windowsCP(codePage); - // preflight to find out the required buffer size - int outLength = WideCharToMultiByte(cp, 0, source, static_cast<int>(sourceLength), - nullptr, 0, nullptr, nullptr); - if (outLength == 0) { - throw windows_error("string conversion failed"); - } - result.resize(outLength); - outLength = WideCharToMultiByte(cp, 0, source, static_cast<int>(sourceLength), - &result[0], outLength, nullptr, nullptr); - if (outLength == 0) { - throw windows_error("string conversion failed"); - } - // fix output string length (i.e. in case of unconvertible characters - while (result[outLength - 1] == L'\0') { - result.resize(--outLength); - } - } - - return result; - } -}; - -template <> -class string_cast_impl<std::wstring, const char*> -{ -public: - static std::wstring cast(const char* const& source, CodePage codePage, - size_t sourceLength) - { - std::wstring result; - - if (sourceLength == std::numeric_limits<size_t>::max()) { - sourceLength = strlen(source); - } - - if (sourceLength > 0) { - // use utf8 or local 8-bit encoding depending on user choice - UINT cp = windowsCP(codePage); - // preflight to find out the required source size - int outLength = MultiByteToWideChar(cp, 0, source, static_cast<int>(sourceLength), - &result[0], 0); - if (outLength == 0) { - throw windows_error("string conversion failed"); - } - result.resize(outLength); - outLength = MultiByteToWideChar(cp, 0, source, static_cast<int>(sourceLength), - &result[0], outLength); - if (outLength == 0) { - throw windows_error("string conversion failed"); - } - while (result[outLength - 1] == L'\0') { - result.resize(--outLength); - } - } - - return result; - } -}; - -template <> -class string_cast_impl<std::wstring, const wchar_t*> -{ -public: - static std::wstring cast(const wchar_t* const& source, CodePage, size_t) - { - return std::wstring(source); - } -}; - -} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/stringutils.cpp b/libs/usvfs/src/shared/stringutils.cpp deleted file mode 100644 index 2ddbf64..0000000 --- a/libs/usvfs/src/shared/stringutils.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include "stringutils.h" -#include "windows_sane.h" - -namespace usvfs::shared -{ - -void strncpy_sz(char* dest, const char* src, size_t destSize) -{ - if (destSize > 0) { - strncpy(dest, src, destSize - 1); - dest[destSize - 1] = '\0'; - } -} - -void wcsncpy_sz(wchar_t* dest, const wchar_t* src, size_t destSize) -{ - if ((destSize > 0) && (dest != nullptr)) { - wcsncpy(dest, src, destSize - 1); - dest[destSize - 1] = L'\0'; - } -} - -bool startswith(const wchar_t* string, const wchar_t* subString) -{ - while ((*string != '\0') && (*subString != '\0')) { - if (towlower(*string) != towlower(*subString)) { - return false; - } - ++string; - ++subString; - } - - return *subString == '\0'; -} - -static fs::path normalize(const fs::path& path) -{ - fs::path result; - - boost::locale::generator gen; - auto loc = gen("en_US.UTF-8"); - for (fs::path::iterator iter = path.begin(); iter != path.end(); ++iter) { - if (*iter == "..") { - result = result.parent_path(); - } else if (*iter != ".") { - result /= boost::to_lower_copy(iter->string(), loc); - } // single dot is ignored - } - return result; -} - -fs::path make_relative(const fs::path& fromIn, const fs::path& toIn) -{ - // converting path to lower case to make iterator comparison work correctly - // on case-insenstive filesystems - fs::path from(fs::absolute(fromIn)); - fs::path to(fs::absolute(toIn)); - - // find common base - fs::path::const_iterator fromIter(from.begin()); - fs::path::const_iterator toIter(to.begin()); - - // TODO the following equivalent test is probably quite expensive as new - // paths are created for each iteration but the case sensitivity depends on - // the fs - while ((fromIter != from.end()) && (toIter != to.end()) && - (boost::iequals(fromIter->string(), toIter->string()))) { - ++fromIter; - ++toIter; - } - - // Navigate backwards in directory to reach previously found base - fs::path result; - for (; fromIter != from.end(); ++fromIter) { - if (*fromIter != ".") { - result /= ".."; - } - } - - // Now navigate down the directory branch - for (; toIter != to.end(); ++toIter) { - result /= *toIter; - } - return result; -} - -std::string to_hex(void* bufferIn, size_t bufferSize) -{ - unsigned char* buffer = static_cast<unsigned char*>(bufferIn); - std::ostringstream temp; - temp << std::hex; - for (size_t i = 0; i < bufferSize; ++i) { - temp << std::setfill('0') << std::setw(2) << (unsigned int)buffer[i]; - if ((i % 16) == 15) { - temp << "\n"; - } else { - temp << " "; - } - } - return temp.str(); -} - -std::wstring to_upper(const std::wstring& input) -{ - std::wstring result; - result.resize(input.size()); - ::LCMapStringW(LOCALE_INVARIANT, LCMAP_UPPERCASE, input.c_str(), - static_cast<int>(input.size()), &result[0], - static_cast<int>(result.size())); - return result; -} - -std::string byte_string(std::size_t n) -{ - auto s = std::to_string(n); - auto p = s.size(); - - while (p > 3) { - p -= 3; - s.insert(p, 1, ','); - } - - return s + " B"; -} - -} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/stringutils.h b/libs/usvfs/src/shared/stringutils.h deleted file mode 100644 index 64d0af7..0000000 --- a/libs/usvfs/src/shared/stringutils.h +++ /dev/null @@ -1,57 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -namespace usvfs::shared -{ - -void strncpy_sz(char* dest, const char* src, size_t destSize); -void wcsncpy_sz(wchar_t* dest, const wchar_t* src, size_t destSize); - -bool startswith(const wchar_t* string, const wchar_t* subString); - -// Return path when appended to a_From will resolve to same as a_To -fs::path make_relative(const fs::path& from, const fs::path& to); - -std::string to_hex(void* bufferIn, size_t bufferSize); - -// convert unicode string to upper-case (locale invariant) -std::wstring to_upper(const std::wstring& input); - -// formats a number with thousand separators and B at the end -// -std::string byte_string(std::size_t n); - -class FormatGuard -{ - std::ostream& m_Stream; - std::ios::fmtflags m_Flags; - -public: - FormatGuard(std::ostream& stream) : m_Stream(stream), m_Flags(stream.flags()) {} - - ~FormatGuard() { m_Stream.flags(m_Flags); } - - FormatGuard(const FormatGuard&) = delete; - FormatGuard& operator=(FormatGuard&) = delete; -}; - -} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/tree_container.h b/libs/usvfs/src/shared/tree_container.h deleted file mode 100644 index a2f61fa..0000000 --- a/libs/usvfs/src/shared/tree_container.h +++ /dev/null @@ -1,630 +0,0 @@ -#pragma once - -#include "directory_tree.h" -#include "shared_memory.h" - -namespace usvfs::shared -{ - -// smart pointer to DirectoryTrees (only intended for top-level nodes). This -// will transparently switch to new shared memory regions in case they get -// reallocated -// -// -// reassign() is called from a variety of places when the current chunk of -// shared memory is full or is marked as being outdated; its job is to either -// find another chunk that may have been created by another process or to create -// a brand new one -// -// -// if there is only a single process hooked, it will slowly fill up the shared -// memory when adding files, eventually throw a bi::bad_alloc and end up in -// reassign(); a new block will be allocated, the data copied over, and the old -// block will be deallocated -// -// -// when multiple processes are involved, things are more complicated -// -// two processes A and B will start by using the same shared memory, but they -// have their own pointer to it that's local to the process (the `m_TreeMeta` -// member variable) -// -// so when process A fills up the shared memory and reallocates it, process B -// is still pointing to the old shared memory; only when process B does some -// operation that accesses the file tree will the pointer be checked and -// adjusted to point to the new shared memory -// -// in this example, when process A ran out of memory, it set `outdated` to -// `true` in the shared memory block, allocated a new one and copied the data -// over, but it did not deallocate the block because process B is still -// pointing to it -// -// when process B tries to access the block, it checks `outdated` (see get()); -// if it's true, it means that it's pointing to an outdated shared memory block -// and must find the new one that process A created -// -// the new block is not necessarily the very next name, it is possible for -// process A to burn through a series of blocks quickly when adding a bunch of -// new files, and these blocks will all have been deallocated by the time -// process B tries to find the newest one -// -// so when a process sees its block as outdated, it will try to open a bunch of -// names until one exists that is not outdated; if it can't find the block -// (shouldn't happen), it will just create a new one -// -template <typename TreeT> -class TreeContainer -{ -public: - /** - * @brief Constructor - * @param SHMName name of the shared memory holding the tree. This should contain the - * running number - * @param size initial size in bytes of the container. since the tree is resized by - * doubling this should be a power of two. 64k is supposed to be the page size on - * windows so smaller allocations make little sense - * @note size can't be too small. If initial allocations fail automatic growing won't - * work - */ - TreeContainer(const std::string& SHMName, size_t size = 64 * 1024) - : m_TreeMeta(nullptr), m_SHMName(SHMName) - { - std::locale global_loc = std::locale(); - std::locale loc(global_loc, new fs::detail::utf8_codecvt_facet); - fs::path::imbue(loc); - - // append _1 to the name if it doesn't end with _N already - std::regex pattern(R"exp((.*_)(\d+))exp"); - std::smatch match; - std::string shmName(m_SHMName.c_str()); - regex_match(shmName, match, pattern); - - if (match.size() != 3) { - m_SHMName += "_1"; - } - - // creates a new memory block if this is the first process to run or attach - // to an already existing one - createOrOpen(m_SHMName, size); - - spdlog::get("usvfs")->info("attached to {0} with {1} nodes, size {2}", m_SHMName, - m_TreeMeta->tree->numNodesRecursive(), - byte_string(m_SHM->get_size())); - } - - TreeContainer(const TreeContainer&) = delete; - TreeContainer& operator=(const TreeContainer&) = delete; - - ~TreeContainer() - { - if (unassign(m_SHM, m_TreeMeta)) { - bi::shared_memory_object::remove(m_SHMName.c_str()); - } - } - - /** - * @return retrieve an allocator that can be used to create objects in this tree - */ - VoidAllocatorT allocator() { return VoidAllocatorT(m_SHM->get_segment_manager()); } - - template <typename... Arguments> - typename TreeT::DataT create(Arguments&&... args) - { - return TreeT::DataT(std::forward<Arguments>(args)..., allocator()); - } - - TreeT* operator->() { return get(); } - - /** - * @return raw pointer to the managed tree - */ - TreeT* get() - { - if (m_TreeMeta->outdated) { - reassign(); - } - - return m_TreeMeta->tree.get(); - } - - /** - * @return raw const pointer to the managed tree - */ - const TreeT* get() const - { - if (m_TreeMeta->outdated) { - // safe const_cast, TreeContainer are never created const - const_cast<TreeContainer<TreeT>*>(this)->reassign(); - } - - return m_TreeMeta->tree.get(); - } - - const TreeT* operator->() const { return get(); } - - /** - * @return current name of the managed shared memory - */ - std::string shmName() const { return m_SHMName; } - - void clear() { m_TreeMeta->tree->clear(); } - - /** - * @brief add a new file to the tree - * - * @param name name of the file, expected to be relative to this directory - * @param data the file data to attach - * @param flags flags for this files - * @param overwrite if true, the new leaf will overwrite an existing one that compares - *as "equal" - * @return pointer to the new node or a null ptr - **/ - template <typename T> - typename TreeT::NodePtrT addFile(const fs::path& name, const T& data, - TreeFlags flags = 0, bool overwrite = true) - { - for (;;) { - DecomposablePath dp(name.string()); - - try { - return addNode(m_TreeMeta->tree.get(), dp, data, overwrite, flags, allocator()); - } catch (const bi::bad_alloc&) { - } - - reassign(); - } - } - - /** - * @brief add a new directory to the tree - * - * @param name name of the file, expected to be relative to this directory - * @param data the file data to attach - * @param flags flags for this files - * @param overwrite if true, the new leaf will overwrite an existing one that compares - *as "equal" - * @return pointer to the new node or a null ptr - **/ - template <typename T> - typename TreeT::NodePtrT addDirectory(const fs::path& name, const T& data, - TreeFlags flags = 0, bool overwrite = true) - { - for (;;) { - DecomposablePath dp(name.string()); - - try { - return addNode(m_TreeMeta->tree.get(), dp, data, overwrite, - flags | FLAG_DIRECTORY, allocator()); - } catch (const bi::bad_alloc&) { - } - - reassign(); - } - } - - void getBuffer(void*& buffer, size_t& bufferSize) const - { - buffer = m_SHM->get_address(); - bufferSize = m_SHM->get_size(); - } - -private: - struct TreeMeta - { - TreeMeta(const typename TreeT::DataT& data, SegmentManagerT* segmentManager) - : tree(segmentManager->construct<TreeT>(bi::anonymous_instance)( - "", true, TreeT::NodePtrT(), data, VoidAllocatorT(segmentManager))), - referenceCount(0), // reference count only set on top level node - outdated(false) - {} - - OffsetPtrT<TreeT> tree; - long referenceCount; - bool outdated; - bi::interprocess_mutex mutex; - }; - - std::string m_SHMName; - std::shared_ptr<SharedMemoryT> m_SHM; - TreeMeta* m_TreeMeta; - - typename TreeT::DataT createEmpty() - { - return createDataEmpty<typename TreeT::DataT>(allocator()); - } - - template <typename T> - TreeT* createSubNode(const VoidAllocatorT& allocator, std::string_view name, - unsigned long flags, const T& data) - { - auto* manager = allocator.get_segment_manager(); - - return manager->construct<TreeT>(bi::anonymous_instance)( - name, flags, TreeT::NodePtrT(), - createData<typename TreeT::DataT, T>(data, allocator), manager); - } - - typename TreeT::NodePtrT createSubPtr(TreeT* subNode) - { - SharedMemoryT::segment_manager* manager = m_SHM->get_segment_manager(); - return TreeT::NodePtrT(subNode, allocator(), TreeT::DeleterT(manager)); - } - - template <typename T> - typename TreeT::NodePtrT addNode(TreeT* base, DecomposablePath& path, const T& data, - bool overwrite, unsigned int flags, - const VoidAllocatorT& allocator) - { - if (!path.peekNext()) { - typename TreeT::NodePtrT newNode = base->node(path.current()); - - if (!newNode) { - // last name component, should be the filename - TreeT* node = createSubNode(allocator, path.current(), flags, data); - newNode = createSubPtr(node); - newNode->m_Self = TreeT::WeakPtrT(newNode); - newNode->m_Parent = base->m_Self; - base->set(StringT(path.current(), allocator), newNode); - return newNode; - } else if (overwrite) { - newNode->m_Data = createData<typename TreeT::DataT, T>(data, allocator); - newNode->m_Flags = static_cast<usvfs::shared::TreeFlags>(flags); - return newNode; - } else { - // the node is already in the tree, overwrite is false, nothing to do - return {}; - } - } else { - // not last component, continue search in child node - auto subNode = base->m_Nodes.find(path.current()); - - if (subNode == base->m_Nodes.end()) { - typename TreeT::NodePtrT newNode = createSubPtr(createSubNode( - allocator, path.current(), FLAG_DIRECTORY | FLAG_DUMMY, createEmpty())); - - subNode = - base->m_Nodes.emplace(StringT(path.current(), allocator), newNode).first; - subNode->second->m_Self = TreeT::WeakPtrT(subNode->second); - subNode->second->m_Parent = base->m_Self; - } - - path.next(); - - return addNode(subNode->second.get().get(), path, data, overwrite, flags, - allocator); - } - } - - /** - * @brief copy content of one tree to a different tree (in a different shared memory - * segment - * @param destination - * @param reference - * @note at the time this is called, destination needs to refer to the shm of - * "destination" so that objects can be allocated in the new tree - */ - void copyTree(TreeT* destination, const TreeT* reference) - { - VoidAllocatorT allocator = VoidAllocatorT(m_SHM->get_segment_manager()); - destination->m_Flags = reference->m_Flags; - dataAssign(destination->m_Data, reference->m_Data); - destination->m_Name.assign(reference->m_Name.c_str()); - - for (const auto& kv : reference->m_Nodes) { - TreeT* newNode = createSubNode(allocator, "", true, createEmpty()); - typename TreeT::NodePtrT newNodePtr = createSubPtr(newNode); - - // need to set self BEFORE recursively copying the subtree, otherwise - // how would we assign parent pointers? - newNode->m_Self = newNodePtr; - - TreeT* source = reinterpret_cast<TreeT*>(kv.second.get().get()); - copyTree(newNode, source); - destination->set(newNode->m_Name, newNodePtr); - newNode->m_Parent = destination->m_Self; - } - } - - int increaseRefCount(TreeMeta* treeMeta) - { - bi::scoped_lock<bi::interprocess_mutex> lock(treeMeta->mutex); - return ++treeMeta->referenceCount; - } - - int decreaseRefCount(TreeMeta* treeMeta) - { - bi::scoped_lock<bi::interprocess_mutex> lock(treeMeta->mutex); - return --treeMeta->referenceCount; - } - - // see activateSHM() for return value - // - std::optional<std::string> createOrOpen(const std::string& SHMName, size_t size) - { - SharedMemoryT* newSHM = openSHM(SHMName); - - if (newSHM) { - spdlog::get("usvfs")->info("{} opened in process {}", SHMName, - ::GetCurrentProcessId()); - } else { - newSHM = createSHM(SHMName, size); - - if (newSHM) { - spdlog::get("usvfs")->info("{} created in process {}", SHMName, - ::GetCurrentProcessId()); - } - } - - if (!newSHM) { - spdlog::get("usvfs")->error("failed to create or open {} in process {}", SHMName, - ::GetCurrentProcessId()); - - throw std::exception("no shm instance"); - } - - return activateSHM(newSHM, SHMName); - } - - // see activateSHM() for return value - // - SharedMemoryT* createSHM(const std::string& SHMName, size_t size) - { - try { - return new SharedMemoryT(bi::create_only, SHMName.c_str(), - static_cast<unsigned int>(size)); - } catch (const bi::interprocess_exception&) { - } - - return nullptr; - } - - // see activateSHM() for return value - // - SharedMemoryT* openSHM(const std::string& SHMName) - { - try { - return new SharedMemoryT(bi::open_only, SHMName.c_str()); - } catch (const bi::interprocess_exception&) { - } - - return nullptr; - } - - // makes the given shm current, returns the name of the previous shm block - // if it is now unused and must be destroyed; if the block is still used by - // another process, returns empty - // - // see reassign() - // - std::optional<std::string> activateSHM(SharedMemoryT* shm, const std::string& SHMName) - { - std::shared_ptr<SharedMemoryT> oldSHM = m_SHM; - - m_SHM.reset(shm); - std::pair<TreeMeta*, SharedMemoryT::size_type> res = m_SHM->find<TreeMeta>("Meta"); - - if (res.first == nullptr) { - res.first = m_SHM->construct<TreeMeta>("Meta")(createEmpty(), - m_SHM->get_segment_manager()); - if (res.first == nullptr) { - USVFS_THROW_EXCEPTION(bi::bad_alloc()); - } - if (m_TreeMeta != nullptr) { - copyTree(res.first->tree.get(), m_TreeMeta->tree.get()); - } - } - - increaseRefCount(res.first); - - std::optional<std::string> deadSHMName; - - if (oldSHM.get() != nullptr) { - const bool lastUser = unassign(oldSHM, m_TreeMeta); - if (lastUser) { - deadSHMName = m_SHMName; - } - } - - m_TreeMeta = res.first; - m_SHMName = SHMName; - - return deadSHMName; - } - - static std::string followupName(const std::string& currentName) - { - std::regex pattern(R"exp((.*_)(\d+))exp"); - std::smatch match; - regex_match(currentName, match, pattern); - - if (match.size() != 3) { - USVFS_THROW_EXCEPTION(usage_error() << ex_msg("shared memory name invalid")); - } - - const int count = boost::lexical_cast<int>(match[2]); - return match[1].str() + std::to_string(count + 1); - } - - bool unassign(const std::shared_ptr<SharedMemoryT>& shm, TreeMeta* tree) - { - if (tree == nullptr) { - return true; - } - - if (decreaseRefCount(tree) == 0) { - shm->get_segment_manager()->destroy_ptr(tree); - return true; - } else { - return false; - } - } - - // re-entrancy: every time a process switches to a new shared memory block, it - // will know whether it was the last process to have a handle to it; when that - // happens, the block will be destroyed to avoid leaking it - // - // destroying these blocks is somewhat dangerous: it ends up in boost, which - // will try to access the filesystem to see if the name of the shared memory - // corresponds to a file on the drive, which can call hooked functions and end - // up right back here - // - // (note that in usvfs, only the shared memory for the log file uses a real - // file on the filesystem, see shmlogger.cpp; all the tree stuff uses - // anonymous, memory mapped files that live in the Windows pagefile) - // - // so the old blocks can be destroyed, but only after all the shenanigans with - // finding the correct shared memory block are over and `m_TreeMeta` points to - // a valid block, so all the names of the dead shared memory blocks are kept - // in a vector and deallocated at the very end - // - void reassign() - { - // list of all the shared memory blocks that are now unused and can be - // destroyed - std::vector<std::string> deadSHMNames; - - if (m_TreeMeta->outdated) { - // this block was marked as outdated, which should only happen when - // another process has ran out of memory and started allocating blocks - // with higher numbers - - spdlog::get("usvfs")->info("tree {0} is outdated, looking for another one", - m_SHMName); - - if (findNewerBlock(deadSHMNames)) { - // the new block was found and activated - return; - } - } else { - // this block isn't outdated, so reassign() was called because a bad_alloc - // exception was thrown - spdlog::get("usvfs")->info( - "ran out of memory in tree {0}, will create another one", m_SHMName); - } - - // either the block is full or it's outdated, but no higher block was found; - // just create a new one - - createNewBlock(deadSHMNames); - - // remove the old shared memory blocks; this can be recursive and call - // reassign() again, but it's safe at this point - for (const std::string& name : deadSHMNames) { - spdlog::get("usvfs")->info("destroying {0}", name); - bi::shared_memory_object::remove(name.c_str()); - } - } - - // tries a series of shm names above the current one in the hope of finding - // the most recent one - // - // if the new block was found and activated correctly, returns true - // - // when findNewerBlock() returns false, this container will either still be - // pointing to the same, outdated block as it started with, or it might have - // moved up to another outdated block, but it will always be pointing to - // a valid block in memory - // - bool findNewerBlock(std::vector<std::string>& deadSHMNames) - { - // how many blocks are checked above the current one; it's unlikely there - // will ever be more than 15 to 20 blocks allocated, but this is high - // because it's really the only way to keep processes connected to the same - // shm block, so processes must not fail to find the next one - constexpr int Tries = 100; - - std::string nextName = m_SHMName; - - for (int i = 0; i < Tries; ++i) { - // the shm name is something like "mod_organizer_3", which becomes - // "mod_organizer_4" - nextName = followupName(nextName); - spdlog::get("usvfs")->info("opening {0}", nextName); - - // open the shm, see if it exists - SharedMemoryT* shm = openSHM(nextName); - - if (!shm) { - // this is not necessarily an error, another process might have created - // a bunch of blocks and destroyed them before this process had a chance - // to see them, so just keep going - spdlog::get("usvfs")->info("{0} doesn't exist", nextName); - continue; - } - - spdlog::get("usvfs")->info("{0} exists, activating", nextName); - const auto deadSHMName = activateSHM(shm, nextName); - - // if this process was the last user of the previous block, it must be - // deallocated, but only after this whole thing is finished, because it - // can end up calling reassign() again - if (deadSHMName) { - spdlog::get("usvfs")->info("will destroy {0}", *deadSHMName); - deadSHMNames.push_back(*deadSHMName); - } - - // another process might have already created this block, run out of - // memory and created more, so make sure to only stop when finding a block - // that's not outdated - if (m_TreeMeta->outdated) { - spdlog::get("usvfs")->info("{0} is also outdated", nextName); - } else { - // shm opened correctly, activated and not outdated, done - spdlog::get("usvfs")->info("{0} not outdated, taking it, size now {1}", - nextName, byte_string(m_SHM->get_size())); - - return true; - } - } - - // this block is outdated, but no other valid block was found above; this - // shouldn't happen, but just create a new block to make sure programs can - // still run - - spdlog::get("usvfs")->error( - "found no existing tree above {0}, will create a new one", m_SHMName); - - return false; - } - - // creates a new block and activates it, throws on failure - // - void createNewBlock(std::vector<std::string>& deadSHMNames) - { - // the current block is now considered stale, so make sure other processes - // are aware of it and try to find the new block - // - // todo: there really should be some synchronization here, another process - // can pick up this flag before the next block has been created - m_TreeMeta->outdated = true; - - // the shm name is something like "mod_organizer_3", which becomes - // "mod_organizer_4" - const std::string nextName = followupName(m_SHMName); - spdlog::get("usvfs")->info("creating {0}", nextName); - - SharedMemoryT* shm = createSHM(nextName, m_SHM->get_size() * 2); - - if (!shm) { - // this shouldn't happen - spdlog::get("usvfs")->error("failed to create {0}", nextName); - throw std::exception("cannot create block"); - } - - spdlog::get("usvfs")->info("{0} created, activating", nextName); - const auto deadSHMName = activateSHM(shm, nextName); - - // if this process was the last user of the previous block, it must be - // deallocated, but only after this whole thing is finished, because it - // can end up calling reassign() again - if (deadSHMName) { - spdlog::get("usvfs")->info("will destroy {0}", *deadSHMName); - deadSHMNames.push_back(*deadSHMName); - } - - spdlog::get("usvfs")->info("tree {0} size now {1}", m_SHMName, - byte_string(m_SHM->get_size())); - } -}; - -} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/unicodestring.cpp b/libs/usvfs/src/shared/unicodestring.cpp deleted file mode 100644 index f997e7f..0000000 --- a/libs/usvfs/src/shared/unicodestring.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include "unicodestring.h" -#include "logging.h" -#include "stringcast.h" -#include "stringutils.h" - -namespace usvfs -{ - -UnicodeString::UnicodeString(LPCWSTR string, size_t length) -{ - if (length == std::string::npos) { - length = wcslen(string); - } - m_Buffer.resize(length + 1); - memcpy(m_Buffer.data(), string, length * sizeof(wchar_t)); - update(); -} - -UnicodeString::UnicodeString(const std::wstring& string) -{ - m_Buffer.resize(string.length() + 1); - memcpy(m_Buffer.data(), string.data(), string.length() * sizeof(wchar_t)); - update(); -} - -UnicodeString& UnicodeString::operator=(const std::wstring& string) -{ - m_Buffer.resize(string.length() + 1); - memcpy(m_Buffer.data(), string.data(), string.length() * sizeof(wchar_t)); - update(); - return *this; -} - -UnicodeString& UnicodeString::appendPath(PUNICODE_STRING path) -{ - if (path != nullptr && path->Buffer && path->Length) { - auto appendAt = size(); - if (appendAt) { - m_Buffer.resize(m_Buffer.size() + path->Length / sizeof(WCHAR) + 1); - m_Buffer[appendAt++] = L'\\'; - } else - m_Buffer.resize(path->Length / sizeof(WCHAR) + 1); - memcpy(&m_Buffer[appendAt], path->Buffer, path->Length); - update(); - } - - return *this; -} - -void UnicodeString::update() -{ - m_Data.Length = static_cast<USHORT>(size() * sizeof(WCHAR)); - m_Data.MaximumLength = static_cast<USHORT>((m_Buffer.capacity() - 1) * sizeof(WCHAR)); - m_Data.Buffer = m_Buffer.data(); -} - -} // namespace usvfs diff --git a/libs/usvfs/src/shared/unicodestring.h b/libs/usvfs/src/shared/unicodestring.h deleted file mode 100644 index 77071ad..0000000 --- a/libs/usvfs/src/shared/unicodestring.h +++ /dev/null @@ -1,110 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include "ntdll_declarations.h" -#include "windows_sane.h" - -#include "formatters.h" - -namespace usvfs -{ - -/** - * @brief C++ wrapper for the windows UNICODE_STRING structure - */ -class UnicodeString -{ -public: - UnicodeString() : m_Buffer(1) { update(); } - - UnicodeString(const UnicodeString& other) : m_Buffer(other.m_Buffer) { update(); } - - UnicodeString(UnicodeString&& other) : m_Buffer(std::move(other.m_Buffer)) - { - update(); - } - - UnicodeString(const std::wstring& string); - UnicodeString(LPCWSTR string, size_t length = std::string::npos); - - UnicodeString& operator=(const std::wstring& string); - - UnicodeString& operator=(const UnicodeString& other) - { - m_Buffer = other.m_Buffer; - update(); - return *this; - } - - UnicodeString& operator=(UnicodeString&& other) - { - m_Buffer = std::move(other.m_Buffer); - update(); - return *this; - } - - /** - * @brief convert to a WinNt Api-style unicode string. This is only valid as long - * as the string isn't modified - */ - explicit operator PUNICODE_STRING() { return &m_Data; } - - /** - * @brief convert to a Win32 Api-style unicode string. This is only valid as long - * as the string isn't modified - */ - explicit operator LPCWSTR() const { return m_Data.Buffer; } - - /** - * @return length of the string in 16-bit words (not including zero termination) - */ - size_t size() const { return m_Buffer.size() - 1; } - - wchar_t operator[](size_t pos) const { return m_Buffer[pos]; } - - UnicodeString& appendPath(PUNICODE_STRING path); - -private: - friend struct ::std::formatter<UnicodeString, char>; - - void update(); - - UNICODE_STRING m_Data; - std::vector<wchar_t> m_Buffer; -}; - -} // namespace usvfs - -template <> -struct std::formatter<usvfs::UnicodeString, char> - : std::formatter<PCUNICODE_STRING, char> -{ - template <class FmtContext> - FmtContext::iterator format(const usvfs::UnicodeString& v, FmtContext& ctx) const - { - if (v.size() == 0) { - return std::format_to(ctx.out(), "<empty string>"); - } else { - return std::formatter<PCUNICODE_STRING, char>::format(&v.m_Data, ctx); - } - } -}; diff --git a/libs/usvfs/src/shared/wildcard.cpp b/libs/usvfs/src/shared/wildcard.cpp deleted file mode 100644 index a2efe6d..0000000 --- a/libs/usvfs/src/shared/wildcard.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include "wildcard.h" -#include "logging.h" -#include "windows_sane.h" - -static bool IsInnerMatch(LPCWSTR pszString, LPCWSTR pszMatch) -{ - while (*pszMatch != L'\0') { - if ((*pszMatch == L'?') || (*pszMatch == L'>')) { - if (!*pszString) { - // ? must match exactly one character - return false; - } - - ++pszString; - ++pszMatch; - } else if ((*pszMatch == L'*') || (*pszMatch == L'<')) { - if (IsInnerMatch(pszString, pszMatch + 1)) { - // * may match empty string or we may have matched something already - return true; - } - - return *pszString && IsInnerMatch(pszString + 1, pszMatch); - - // the rest of the string can't be matched - } else { - if (CharUpperW(MAKEINTRESOURCEW(MAKELONG(*pszString++, 0))) != - CharUpperW(MAKEINTRESOURCEW(MAKELONG(*pszMatch++, 0)))) { - // regular chars compare - return false; - } - } - } - - return !*pszString && !*pszMatch; -} - -static LPCSTR InnerMatch(LPCSTR pszString, LPCSTR pszMatch) -{ - // We have a special case where string is empty ("") and the mask is "*". - // We need to handle this too. So we can't test on !*pszString here. - // The loop breaks when the match string is exhausted. - while (*pszString != '\0') { - // Single wildcard character - if ((*pszMatch == '?') || (*pszMatch == '>')) { - // can't match directory separator - if ((*pszString == '\\') || (*pszString == '/')) - return nullptr; - - // Matches any character except empty string - if (*pszString == '\0') { - // string consumed, part of the pattern is left - return pszMatch; - } - - // OK next - ++pszString; - ++pszMatch; - } else if ((*pszMatch == '*') || (*pszMatch == '<')) { - // * doesn't match directory separators - if ((*pszString == '\\') || (*pszString == '/')) { - ++pszMatch; - continue; - } - - // Need to do some tricks. - - // 1. The wildcard * is ignored. - // So just an empty string matches. This is done by recursion. - // Because we eat one character from the match string, the - // recursion will stop. - { - LPCSTR remainder = InnerMatch(pszString, pszMatch + 1); - if (remainder != nullptr) { - // we have a match and the * replaces no other character - return remainder; - } - } - - // 2. Chance we eat the next character and try it again, with a - // wildcard * match. This is done by recursion. Because we eat - // one character from the string, the recursion will stop. - if (*pszString != '\0') { - LPCSTR remainder = InnerMatch(pszString + 1, pszMatch); - if (remainder != nullptr) { - return remainder; - } - } - - // Nothing worked with this wildcard. - return nullptr; - } else { - // Standard compare of 2 chars. Note that *pszSring might be 0 - // here, but then we never get a match on *pszMask that has always - // a value while inside this loop. - if (CharUpperA(MAKEINTRESOURCEA(MAKELONG(*pszString++, 0))) != - CharUpperA(MAKEINTRESOURCEA(MAKELONG(*pszMatch++, 0)))) - return nullptr; - } - } - - // successful match if the input string was completely consumed - if (*pszString == '\0') { - while ((*pszMatch == '*') || (*pszMatch == '<')) { - ++pszMatch; - } - return pszMatch; - } else { - return nullptr; - } -} - -namespace usvfs::shared::wildcard -{ - -bool Match(LPCWSTR pszString, LPCWSTR pszMatch) -{ - if (*pszString == L'.') { - // cmd.exe seems to ignore - return Match(pszString + 1, pszMatch); - } else { - size_t len = wcslen(pszMatch); - if ((len > 2) && (wcscmp(pszMatch + len - 2, L".*") == 0)) { - // cmd.exe seems to completely ignore .* at the end. - std::wstring temp(pszMatch, pszMatch + len - 2); - return IsInnerMatch(pszString, temp.c_str()); - } - return IsInnerMatch(pszString, pszMatch); - } -} - -bool Match(LPCSTR pszString, LPCSTR pszMatch) -{ - if (*pszString == '.') { - // cmd.exe seems to ignore - return Match(pszString + 1, pszMatch); - } else { - size_t len = strlen(pszMatch); - LPCSTR res = nullptr; - if ((len > 2) && (strcmp(pszMatch + len - 2, ".*") == 0)) { - // cmd.exe seems to completely ignore .* at the end. - std::string temp(pszMatch, pszMatch + len - 2); - res = InnerMatch(pszString, temp.c_str()); - } else { - res = InnerMatch(pszString, pszMatch); - } - return ((res != nullptr) && (*res == '\0')); - } -} - -LPCSTR PartialMatch(LPCSTR pszString, LPCSTR pszMatch) -{ - if (*pszString == '.') { - // cmd.exe seems to ignore dots at the start - return PartialMatch(pszString + 1, pszMatch); - } else { - size_t len = strlen(pszMatch); - if ((len > 2) && (strcmp(pszMatch + len - 2, ".*") == 0)) { - // in cmd.exe there seems to be no difference between <something>* and - // <something>*.* - std::string temp(pszMatch, pszMatch + len - 2); - LPCSTR pos = InnerMatch(pszString, temp.c_str()); - if (pos != nullptr) { - if (*pos == '\0') { - return pszMatch + strlen(pszMatch); - } else { - return pszMatch + (pos - temp.c_str()); - } - } - } - return InnerMatch(pszString, pszMatch); - } -} - -} // namespace usvfs::shared::wildcard diff --git a/libs/usvfs/src/shared/wildcard.h b/libs/usvfs/src/shared/wildcard.h deleted file mode 100644 index bf5b4a3..0000000 --- a/libs/usvfs/src/shared/wildcard.h +++ /dev/null @@ -1,67 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -/// Wildcard matching code -/// by Martin Richter -/// licensed under The Code Project Open License (CPOL) - -#pragma once - -#include "windows_sane.h" - -namespace usvfs::shared::wildcard -{ - -/** - * @brief match string to wildcard windows-style - * @param pszString Input string to match - * @param pszMatch Match mask that may contain wildcards like ? and * - * @note A ? sign matches any character, except an empty string. - * @note A * sign matches any string inclusive an empty string. - * @note Characters are compared caseless. - * @return true if the string matches the pattern - */ -bool Match(LPCWSTR pszString, LPCWSTR pszMatch); - -/** - * @brief match string to wildcard windows-style - * @param pszString Input string to match - * @param pszMatch Match mask that may contain wildcards like ? and * - * @note A ? sign matches any character, except an empty string. - * @note A * sign matches any string inclusive an empty string. - * @note Characters are compared caseless. - * @return true if the string matches the pattern - */ -bool Match(LPCSTR pszString, LPCSTR pszMatch); - -/** - * @brief match string to wildcard windows-style - * @param pszString Input string to match - * @param pszMatch Match mask that may contain wildcards like ? and * - * @note A ? sign matches any character, except an empty string. - * @note A * sign matches any string inclusive an empty string. - * @note Characters are compared caseless. - * @return the "not-consumed" remainder of the pattern. If this points to a - * zero terminator, this was a full match. - * Returns nullptr if no match is possible - */ -LPCSTR PartialMatch(LPCSTR pszString, LPCSTR pszMatch); - -} // namespace usvfs::shared::wildcard diff --git a/libs/usvfs/src/shared/winapi.cpp b/libs/usvfs/src/shared/winapi.cpp deleted file mode 100644 index e28a13c..0000000 --- a/libs/usvfs/src/shared/winapi.cpp +++ /dev/null @@ -1,490 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include "winapi.h" -#include "exceptionex.h" -#include "logging.h" -#include "ntdll_declarations.h" -#include "stringcast.h" -#include "stringutils.h" -#include "unicodestring.h" - -namespace winapi::ansi -{ - -std::string getModuleFileName(HMODULE module, HANDLE process) -{ - std::wstring result = wide::getModuleFileName(module, process); - return usvfs::shared::string_cast<std::string>(result); -} - -std::string getCurrentDirectory() -{ - std::string result; - DWORD required = GetCurrentDirectoryA(0, nullptr); - if (required == 0UL) { - throw usvfs::shared::windows_error("failed to determine current working directory"); - } - result.resize(required); - GetCurrentDirectoryA(required, &result[0]); - result.resize(required - 1); - return result; -} - -std::pair<std::string, std::string> getFullPathName(LPCSTR fileName) -{ - static const int INIT_SIZE = 128; - std::string result; - result.resize(INIT_SIZE); - LPSTR filePart = nullptr; - DWORD requiredSize = GetFullPathNameA(fileName, INIT_SIZE, &result[0], &filePart); - if (requiredSize >= INIT_SIZE) { - result.resize(requiredSize); - GetFullPathNameA(fileName, requiredSize, &result[0], &filePart); - } - if (requiredSize != 0UL) { - return std::make_pair(result, std::string(filePart != nullptr ? filePart : "")); - } else { - return make_pair(result, std::string()); - } -} - -} // namespace winapi::ansi - -namespace winapi::wide -{ - -std::wstring getModuleFileName(HMODULE module, HANDLE process) -{ - std::wstring result; - result.resize(64); - DWORD rc = 0UL; - - while ((rc = (process == INVALID_HANDLE_VALUE) - ? ::GetModuleFileNameW(module, &result[0], - static_cast<DWORD>(result.size())) - : ::GetModuleFileNameExW(process, module, &result[0], - static_cast<DWORD>(result.size()))) == - result.size()) { - result.resize(result.size() * 2); - } - - if (rc == 0UL) { - if (::GetLastError() == ERROR_PARTIAL_COPY) { -#if BOOST_ARCH_X86_64 - return L"unknown (32-bit process)"; -#else - return L"unknown (64-bit process)"; -#endif - } else { - throw usvfs::shared::windows_error("failed to retrieve module file name"); - } - } - - result.resize(rc); - - return result; -} - -std::pair<std::wstring, std::wstring> getFullPathName(LPCWSTR fileName) -{ - wchar_t buf1[MAX_PATH]; - std::vector<wchar_t> buf2; - wchar_t* result = buf1; - LPWSTR filePart = nullptr; - DWORD requiredSize = GetFullPathNameW(fileName, MAX_PATH, result, &filePart); - if (requiredSize >= MAX_PATH) { - buf2.resize(requiredSize); - result = &buf2[0]; - requiredSize = GetFullPathNameW(fileName, requiredSize, result, &filePart); - } - return make_pair(std::wstring(result, requiredSize), - std::wstring((requiredSize && filePart) ? filePart : L"")); -} - -std::wstring getCurrentDirectory() -{ - // really great api this (::GetCurrentDirectoryW) - // - if it succeeds, returns size in characters WITHOUT zero termination - // - if it fails due to buffer too small, returns size in characters WITH zero - // termination - // - if it fails for other reasons, returns 0 - std::wstring result; - DWORD required = GetCurrentDirectoryW(0, nullptr); - if (required == 0UL) { - throw usvfs::shared::windows_error("failed to determine current working directory"); - } - result.resize(required); - GetCurrentDirectoryW(required, &result[0]); - result.resize(required - 1); - return result; -} - -std::wstring getKnownFolderPath(REFKNOWNFOLDERID folderID) -{ - PWSTR writablePath; - - ::SHGetKnownFolderPath(folderID, 0, nullptr, &writablePath); - - ON_BLOCK_EXIT([writablePath]() { - ::CoTaskMemFree(writablePath); - }); - - return std::wstring(writablePath); -} - -} // namespace winapi::wide - -namespace winapi::ex -{ - -std::pair<uintptr_t, uintptr_t> getSectionRange(HANDLE moduleHandle) -{ - std::pair<uintptr_t, uintptr_t> result; - bool found = false; - uintptr_t exeModule = reinterpret_cast<uintptr_t>(moduleHandle); - if (exeModule == 0) { - throw std::runtime_error("failed to determine address range of executable"); - } - - std::pair<uintptr_t, uintptr_t> totalRange{UINT_MAX, 0}; - - PIMAGE_DOS_HEADER dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(exeModule); - PIMAGE_NT_HEADERS ntHeader = - reinterpret_cast<PIMAGE_NT_HEADERS>(exeModule + dosHeader->e_lfanew); - PIMAGE_SECTION_HEADER sectionHeader = - reinterpret_cast<PIMAGE_SECTION_HEADER>(ntHeader + 1); - for (int i = 0; i < ntHeader->FileHeader.NumberOfSections && !found; ++i) { - if (memcmp(sectionHeader->Name, ".text", 5) == 0) { - result.first = exeModule + sectionHeader->VirtualAddress; - result.second = result.first + sectionHeader->Misc.VirtualSize; - found = true; - } else { - uintptr_t start = exeModule + sectionHeader->VirtualAddress; - totalRange.first = std::min(totalRange.first, start); - totalRange.second = std::max<uintptr_t>(totalRange.second, - start + sectionHeader->Misc.VirtualSize); - } - ++sectionHeader; - } - - if (!found) { - return totalRange; - } - - return result; -} - -OSVersion getOSVersion() -{ - RTL_OSVERSIONINFOEXW versionInfo; - ZeroMemory(&versionInfo, sizeof(RTL_OSVERSIONINFOEXW)); - versionInfo.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW); - RtlGetVersion((PRTL_OSVERSIONINFOW)&versionInfo); - - OSVersion result; - result.major = versionInfo.dwMajorVersion; - result.minor = versionInfo.dwMinorVersion; - result.build = versionInfo.dwBuildNumber; - result.platformid = versionInfo.dwPlatformId; - result.servicpack = - versionInfo.wServicePackMajor << 16 | versionInfo.wServicePackMinor; - return result; -} - -} // namespace winapi::ex - -namespace winapi::ex::ansi -{ - -std::string errorString(DWORD errorCode) -{ - std::ostringstream finalMessage; - - LPSTR buffer = nullptr; - - DWORD currentErrorCode = GetLastError(); - - errorCode = - errorCode != std::numeric_limits<DWORD>::max() ? errorCode : currentErrorCode; - - // TODO: the message is not english? - if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - nullptr, errorCode, - 0 //, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) - , - (LPSTR)&buffer, 0, nullptr) == 0) { - finalMessage << "(unknown error [" << errorCode << "])"; - } else { - if (buffer != nullptr) { - size_t end = strlen(buffer) - 1; - while ((buffer[end] == '\n') || (buffer[end] == '\r')) { - buffer[end--] = '\0'; - } - finalMessage << "(" << buffer << " [" << errorCode << "])"; - LocalFree(buffer); // allocated by FormatMessage - } - } - - SetLastError(currentErrorCode); // restore error code because FormatMessage might - // have modified it - return finalMessage.str(); -} - -std::string toString(const FILETIME& time) -{ - SYSTEMTIME temp; - FileTimeToSystemTime(&time, &temp); - std::ostringstream stream; - stream << temp.wYear << "-" << temp.wMonth << "-" << temp.wDay << temp.wHour << ":" - << temp.wMinute << ":" << temp.wSecond; - return stream.str(); -} - -LPCSTR GetBaseName(LPCSTR string) -{ - LPCSTR result = string + strlen(string) - 1; - while (result > string) { - if ((*result == '\\') || (*result == '/')) { - ++result; - break; - } else { - --result; - } - } - return result; -} - -} // namespace winapi::ex::ansi - -namespace winapi::ex::wide -{ - -bool fileExists(LPCWSTR fileName, bool* isDirectory) -{ - DWORD attrib = GetFileAttributesW(fileName); - - if (attrib == INVALID_FILE_ATTRIBUTES) { - return false; - } else { - if (isDirectory != nullptr) { - *isDirectory = (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0; - } - return true; - } -} - -std::wstring errorString(DWORD errorCode) -{ - std::wostringstream finalMessage; - - LPWSTR buffer = nullptr; - - DWORD currentErrorCode = GetLastError(); - - errorCode = - errorCode != std::numeric_limits<DWORD>::max() ? errorCode : currentErrorCode; - - // TODO: the message is not english? - if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - nullptr, errorCode, - 0 //, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) - , - (LPWSTR)&buffer, 0, nullptr) == 0) { - finalMessage << L"(unknown error [" << errorCode << "])"; - } else { - if (buffer != nullptr) { - size_t end = wcslen(buffer) - 1; - while ((buffer[end] == L'\n') || (buffer[end] == L'\r')) { - buffer[end--] = L'\0'; - } - finalMessage << L"(" << buffer << L" [" << errorCode << L"])"; - LocalFree(buffer); // allocated by FormatMessage - } - } - - SetLastError(currentErrorCode); // restore error code because FormatMessage might - // have modified it - return finalMessage.str(); -} - -std::wstring toString(const FILETIME& time) -{ - SYSTEMTIME temp; - FileTimeToSystemTime(&time, &temp); - std::wostringstream stream; - stream << temp.wYear << "-" << temp.wMonth << "-" << temp.wDay << temp.wHour << ":" - << temp.wMinute << ":" << temp.wSecond; - return stream.str(); -} - -LPCWSTR GetBaseName(LPCWSTR string) -{ - LPCWSTR result; - if ((string == nullptr) || (string[0] == L'\0')) { - result = string; - } else { - result = string + wcslen(string) - 1; - } - - while (result > string) { - if ((*result == L'\\') || (*result == L'/')) { - ++result; - break; - } else { - --result; - } - } - return result; -} - -LPWSTR GetBaseName(LPWSTR path) -{ - LPCWSTR result = GetBaseName(static_cast<LPCWSTR>(path)); - return const_cast<LPWSTR>(result); -} - -std::wstring getSectionName(PVOID addressIn, HANDLE process) -{ - if (process == nullptr) { - process = GetCurrentProcess(); - } - HMODULE modules[1024]; - intptr_t address = reinterpret_cast<intptr_t>(addressIn); - DWORD required; - if (::EnumProcessModules(process, modules, sizeof(modules), &required)) { - for (DWORD i = 0; i < (std::min<DWORD>(1024UL, required) / sizeof(HMODULE)); ++i) { - std::pair<intptr_t, intptr_t> range = getSectionRange(modules[i]); - if ((address > range.first) && (address < range.second)) { - try { - return winapi::wide::getModuleFileName(modules[i], process); - } catch (const std::exception&) { - return std::wstring(L"unknown"); - } - } - } - } - return std::wstring(L"unknown"); -} - -std::vector<FileResult> quickFindFiles(LPCWSTR directoryName, LPCWSTR pattern) -{ - std::vector<FileResult> result; - - static const unsigned int BUFFER_SIZE = 1024; - - HANDLE hdl = CreateFileW(directoryName, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); - - ON_BLOCK_EXIT([hdl]() { - CloseHandle(hdl); - }); - - uint8_t buffer[BUFFER_SIZE]; - - NTSTATUS res = STATUS_SUCCESS; // status success - while (res == STATUS_SUCCESS) { - IO_STATUS_BLOCK status; - - res = NtQueryDirectoryFile( - hdl, nullptr, nullptr, nullptr, &status, buffer, BUFFER_SIZE, - FileFullDirectoryInformation, FALSE, - static_cast<PUNICODE_STRING>(usvfs::UnicodeString(pattern)), FALSE); - if (res == STATUS_SUCCESS) { - FILE_FULL_DIR_INFORMATION* info = - reinterpret_cast<FILE_FULL_DIR_INFORMATION*>(buffer); - void* endPos = buffer + status.Information; - while (info < endPos) { - FileResult file; - file.fileName = - std::wstring(info->FileName, info->FileNameLength / sizeof(wchar_t)); - file.attributes = info->FileAttributes; - - result.push_back(file); - if (info->NextEntryOffset == 0) { - break; - } else { - info = reinterpret_cast<FILE_FULL_DIR_INFORMATION*>( - reinterpret_cast<uint8_t*>(info) + info->NextEntryOffset); - } - } - } - } - - return result; -} - -bool createPath(boost::filesystem::path path, LPSECURITY_ATTRIBUTES securityAttributes) -{ - // sanity and guaranteed recursion end: - if (!path.has_relative_path()) - throw usvfs::shared::windows_error( - "createPath() 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("createPath() called on a file: " + - path.string()); - } - if (err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND) - throw usvfs::shared::windows_error( - "createPath() GetFileAttributesW failed on: " + path.string(), err); - - if (err != ERROR_FILE_NOT_FOUND) // ERROR_FILE_NOT_FOUND means parent directory - // already exists - createPath(path.parent_path(), - securityAttributes); // otherwise create parent directory (recursively) - - BOOL res = CreateDirectoryW(path.c_str(), securityAttributes); - if (!res) { - err = GetLastError(); - throw usvfs::shared::windows_error( - "createPath() CreateDirectoryW failed on: " + path.string(), err); - } - return true; -} - -std::wstring getWindowsBuildLab(bool ex) -{ - HKEY hKey = nullptr; - auto res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, - LR"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", 0, - KEY_READ, &hKey); - if (res != ERROR_SUCCESS || !hKey) - return L"Opening HKLM Windows NT\\CurrentVersion failed?!"; - WCHAR buf[200]; - DWORD size = static_cast<DWORD>(sizeof(buf)); - res = RegQueryValueExW(hKey, ex ? L"BuildLabEx" : L"BuildLab", NULL, NULL, - reinterpret_cast<LPBYTE>(buf), &size); - if (res != ERROR_SUCCESS || size > sizeof(buf)) - return ex ? L"BuildLabEx reg value not found?!" : L"BuildLab reg value not found?!"; - size /= sizeof(buf[0]); - if (size && !buf[size - 1]) - --size; - return std::wstring(buf, size); -} - -} // namespace winapi::ex::wide diff --git a/libs/usvfs/src/shared/winapi.h b/libs/usvfs/src/shared/winapi.h deleted file mode 100644 index f94eff6..0000000 --- a/libs/usvfs/src/shared/winapi.h +++ /dev/null @@ -1,610 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include "logging.h" -#include "stringcast.h" -#include "windows_sane.h" -#include <ShlObj.h> - -#define ALIAS(alias, original) \ - template <typename... Args> \ - auto alias(Args&&... args) -> decltype(original(std::forward<Args>(args)...)) \ - { \ - return original(std::forward<Args>(args)...); \ - } - -#define ALIAST(alias, original) \ - template <typename T, typename... Args> \ - auto alias<T>(Args && ... args) \ - -> decltype(original<T>(std::forward<Args>(args)...)) \ - { \ - return original<T>(std::forward<Args>(args)...); \ - } - -namespace winapi -{ - -struct parameter_error : public std::runtime_error -{ - parameter_error(const std::string& msg) : runtime_error(msg) {} -}; - -} // namespace winapi - -namespace winapi::process -{ - -/** - * @brief result of process creation - */ -struct Result -{ - Result() - { - ::ZeroMemory(&processInfo, sizeof(PROCESS_INFORMATION)); - ::ZeroMemory(&startupInfo, sizeof(STARTUPINFO)); - startupInfo.cb = sizeof(STARTUPINFO); - } - - Result(Result&& reference) - : valid(reference.valid), startupInfo(reference.startupInfo), - processInfo(reference.processInfo), errorCode(reference.errorCode) - { - reference.valid = false; - } - - ~Result() - { - if (valid) { - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - } - - if (stdoutPipe != INVALID_HANDLE_VALUE) { - CloseHandle(stdoutPipe); - } - } - - Result(const Result&) = delete; - - size_t readStdout(std::vector<uint8_t>& buffer, bool& eof) - { - if (stdoutPipe != INVALID_HANDLE_VALUE) { - DWORD read; - BOOL res = ReadFile(stdoutPipe, &buffer[0], static_cast<DWORD>(buffer.size()), - &read, nullptr); - eof = (res == TRUE) && (read == 0); - return static_cast<size_t>(read); - } else { - eof = true; - return 0; - } - } - - bool valid{false}; - STARTUPINFO startupInfo; - PROCESS_INFORMATION processInfo; - DWORD errorCode{0UL}; - - HANDLE stdoutPipe{INVALID_HANDLE_VALUE}; -}; - -/** - * @brief internal class to handle process creation with named parameters. - */ -template <typename CharT> -class _Create -{ -public: - _Create(const std::basic_string<CharT>& binaryName); - _Create(const _Create<CharT>& reference) = delete; - _Create<CharT>& operator=(const _Create<CharT>& reference) = delete; - - _Create(_Create<CharT>&& reference) - : m_CurrentDirectory(std::move(reference.m_CurrentDirectory)), - m_ProcessAttributes(reference.m_ProcessAttributes), - m_ThreadAttributes(reference.m_ThreadAttributes), - m_InheritHandles(reference.m_InheritHandles), - m_CreationFlags(std::move(reference.m_CreationFlags)), - m_Executed(reference.m_Executed) - { - // stringstream should be moveable but it seems it isn't on mingw - m_CommandLine << reference.m_CommandLine.rdbuf(); - } - - /// named parameter "argument". May be called repeatedly. This is - /// directly appended to the command line with a separating space. No - /// quoting happens - template <typename ArgT> - _Create& argument(const ArgT& argin) - { - m_CommandLine << " " << argin; - return *this; - } - - template <typename ArgT> - _Create& arg(const ArgT& argin) - { - return this->argument(argin); - } - - template <typename IterT> - _Create& arguments(IterT begin, IterT end) - { - for (; begin != end; ++begin) { - m_CommandLine << " " << *begin; - } - - return *this; - } - - /// @brief set the working directory for the process - _Create& workingDirectory(const std::basic_string<CharT>& path); - - /// @brief set process attributes - _Create& processAttributes(SECURITY_ATTRIBUTES* attributes); - - /// @brief set thread attributes - _Create& threadAttributes(SECURITY_ATTRIBUTES* attributes); - - /// @brief activate inheriting handles - _Create& inheritHandles(); - - /// @brief have the process start suspended - _Create& suspended(); - - /// @brief set the process up to output stout to a pipe which can be - /// retrieved through the result object - _Create& stdoutPipe(); - - /// @brief end the named parameter cascade and create the process - Result operator()() - { - m_CommandLine.seekp(0, std::ios::end); - unsigned int length = static_cast<unsigned int>(m_CommandLine.tellp()); - std::unique_ptr<CharT[]> clBuffer(new CharT[length + 1]); - memset(clBuffer.get(), 0, (length + 1) * sizeof(CharT)); - memcpy(clBuffer.get(), m_CommandLine.str().c_str(), length * sizeof(CharT)); - Result result; - - if (m_StdoutPipe) { - result.stdoutPipe = setupPipe(result.startupInfo.hStdOutput); - result.startupInfo.dwFlags |= STARTF_USESTDHANDLES; - } - - result.valid = - createProcessInt(nullptr, clBuffer.get(), m_ProcessAttributes, - m_ThreadAttributes, m_InheritHandles, m_CreationFlags, nullptr, - m_CurrentDirectory.length() > 0 ? m_CurrentDirectory.c_str() - : nullptr, - &result.startupInfo, &result.processInfo) == TRUE; - - if (m_Stdout != INVALID_HANDLE_VALUE) { - // got to close the write end of pipes - CloseHandle(result.startupInfo.hStdOutput); - } - - if (result.valid) { - result.errorCode = NOERROR; - } else { - result.errorCode = GetLastError(); - } - return result; - } - -private: - static BOOL createProcessInt(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, - SECURITY_ATTRIBUTES* lpProcessAttributes, - SECURITY_ATTRIBUTES* lpThreadAttributes, - BOOL bInheritHandles, DWORD dwCreationFlags, - LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, - LPSTARTUPINFOW lpStartupInfo, - LPPROCESS_INFORMATION lpProcessInformation) - { - return ::CreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes, - lpThreadAttributes, bInheritHandles, dwCreationFlags, - lpEnvironment, lpCurrentDirectory, lpStartupInfo, - lpProcessInformation); - } - - static BOOL createProcessInt(LPCSTR lpApplicationName, LPSTR lpCommandLine, - SECURITY_ATTRIBUTES* lpProcessAttributes, - SECURITY_ATTRIBUTES* lpThreadAttributes, - BOOL bInheritHandles, DWORD dwCreationFlags, - LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, - LPSTARTUPINFOW lpStartupInfo, - LPPROCESS_INFORMATION lpProcessInformation) - { - std::wstring executable; - if (lpApplicationName != nullptr) { - executable = usvfs::shared::string_cast<std::wstring>(lpApplicationName); - } - - std::wstring cmdline; - if (lpCommandLine != nullptr) { - cmdline = usvfs::shared::string_cast<std::wstring>(lpCommandLine); - } - - std::wstring cwd; - if (lpCurrentDirectory != nullptr) { - cwd = usvfs::shared::string_cast<std::wstring>(lpCurrentDirectory); - } - - return ::CreateProcessW(lpApplicationName != nullptr ? executable.c_str() : nullptr, - lpCommandLine != nullptr ? &cmdline[0] : nullptr, - lpProcessAttributes, lpThreadAttributes, bInheritHandles, - dwCreationFlags, lpEnvironment, - lpCurrentDirectory != nullptr ? cwd.c_str() : nullptr, - lpStartupInfo, lpProcessInformation); - } - - HANDLE setupPipe(HANDLE& childHandle) - { - SECURITY_ATTRIBUTES attr; - attr.nLength = sizeof(SECURITY_ATTRIBUTES); - attr.bInheritHandle = TRUE; - attr.lpSecurityDescriptor = nullptr; - - HANDLE pipe[2]; - - CreatePipe(&pipe[0], &pipe[1], &attr, 0); - SetHandleInformation(pipe[0], HANDLE_FLAG_INHERIT, 0); - - childHandle = pipe[1]; - - return pipe[0]; - } - -private: - std::basic_stringstream<CharT> m_CommandLine; - std::basic_string<CharT> m_CurrentDirectory{}; - SECURITY_ATTRIBUTES* m_ProcessAttributes{nullptr}; - SECURITY_ATTRIBUTES* m_ThreadAttributes{nullptr}; - BOOL m_InheritHandles{false}; - DWORD m_CreationFlags{0UL}; - bool m_Executed{false}; - bool m_StdoutPipe{false}; - - HANDLE m_Stdout{INVALID_HANDLE_VALUE}; -}; - -} // namespace winapi::process - -namespace winapi::file -{ -/** - * @brief internal class to handle file creation (opening) with named - * parameters. - */ -template <typename CharT, DWORD DefaultDisposition> -class _Create -{ -public: - _Create(const std::basic_string<CharT>& fileName) : m_FileName(fileName) {} - - _Create& access(DWORD desiredAccess) - { - m_DesiredAccess = desiredAccess; - return *this; - } - - _Create& share(DWORD shareMode) - { - m_ShareMode = shareMode; - return *this; - } - - _Create& createAlways() - { - m_CreationDisposition = CREATE_ALWAYS; - return *this; - } - - _Create& openAlways() - { - m_CreationDisposition = OPEN_ALWAYS; - return *this; - } - - _Create& security(SECURITY_ATTRIBUTES* attributes) - { - m_SecurityAttributes = attributes; - return *this; - } - - _Create& templateFile(HANDLE templateFile) - { - m_Template = templateFile; - return *this; - } - - /// @brief end the named parameter cascade and open the file - HANDLE operator()() - { - return callDelegate( - std::integral_constant<bool, sizeof(CharT) == sizeof(wchar_t)>()); - } - -private: - HANDLE callDelegate(std::true_type) - { - return ::CreateFileW(m_FileName.c_str(), m_DesiredAccess, m_ShareMode, - m_SecurityAttributes, m_CreationDisposition, m_Flags, - m_Template); - } - - HANDLE callDelegate(std::false_type) - { - return ::CreateFileA(m_FileName.c_str(), m_DesiredAccess, m_ShareMode, - m_SecurityAttributes, m_CreationDisposition, m_Flags, - m_Template); - } - -private: - std::basic_string<CharT> m_FileName; - DWORD m_DesiredAccess{GENERIC_ALL}; - DWORD m_ShareMode{0UL}; - DWORD m_CreationDisposition{DefaultDisposition}; - DWORD m_Flags{FILE_ATTRIBUTE_NORMAL}; - HANDLE m_Template{nullptr}; - SECURITY_ATTRIBUTES* m_SecurityAttributes{nullptr}; -}; - -} // namespace winapi::file - -namespace winapi::ansi -{ - -std::string getModuleFileName(HMODULE module, HANDLE process = INVALID_HANDLE_VALUE); -std::pair<std::string, std::string> getFullPathName(LPCSTR fileName); -std::string getCurrentDirectory(); -typedef process::_Create<char> createProcess; -typedef file::_Create<char, CREATE_NEW> createFile; -typedef file::_Create<char, OPEN_EXISTING> openFile; - -} // namespace winapi::ansi - -namespace winapi::wide -{ - -std::wstring getModuleFileName(HMODULE module, HANDLE process = INVALID_HANDLE_VALUE); -std::pair<std::wstring, std::wstring> getFullPathName(LPCWSTR fileName); -std::wstring getCurrentDirectory(); -std::wstring getKnownFolderPath(REFKNOWNFOLDERID folderID); - -typedef process::_Create<wchar_t> createProcess; -typedef file::_Create<wchar_t, CREATE_NEW> createFile; -typedef file::_Create<wchar_t, OPEN_EXISTING> openFile; - -} // namespace winapi::wide - -/** - * useful convenience functions close to the api - */ -namespace winapi::ex -{ - -/** - * @brief retrieve the address range covering the code section of a module - * @param moduleHandle handle to the module - * @return start and end address of the code section - * @note the code section can only be identified if it has the standardized section name - * ".text" Otherwise the whole address range of all sections in the module is returned. - * This happens for compressed exectuables for example - */ -std::pair<uintptr_t, uintptr_t> getSectionRange(HANDLE moduleHandle); - -struct OSVersion -{ - DWORD major; - DWORD minor; - DWORD build; - DWORD platformid; - DWORD servicpack; -}; - -OSVersion getOSVersion(); - -} // namespace winapi::ex - -namespace winapi::ex::ansi -{ - -/** - * @brief retrieve an error string for a windows error message - * @param errorCode the error code to look up. If this is left at the default, - * ::GetLastError is used - * @return string representation of the error. Currently this is localized - */ -std::string errorString(DWORD errorCode = std::numeric_limits<DWORD>::max()); - -/** - * @brief convert filetime to string - * @param time time to convert - * @return a string representation (currently only supports utc and iso format with - * second precision) - */ -std::string toString(const FILETIME& time); - -/** - * @brief find file name in a windows file path - * @param path the path to search in - * @return the file name of the path or an empty string if the path ends on - * a slash - * @note this function doesn't access the file system so it doesn't depend - * on whether the file actually exists. This also means it can't - * determine if a path that doesn't end on a slash refers to a file or - * directory - * @note the return value is a pointer into the same buffer, no copy is - * created - */ -LPCSTR GetBaseName(LPCSTR string); - -} // namespace winapi::ex::ansi - -namespace winapi::ex::wide -{ -/** - * retrieve the name of the binary section containing the specified address - * @param address the address to test - * @param process the process for which to retrieve the section. If this is - * nullptr, the current process is analized. - * @return name of the section or "unknown" if no matching section was found - */ -std::wstring getSectionName(PVOID address, HANDLE process = nullptr); - -/** - * @brief test if a file exists - * @param path path to check - * @param isDirectory (optional) if this isn't null, it will be set to true if the path - * specifies a directory, false otherwise - * @return true if the file (or directory) exists. - */ -bool fileExists(LPCWSTR fileName, bool* isDirectory = nullptr); - -/** - * @brief retrieve an error string for a windows error message - * @param errorCode the error code to look up. If this is left at the default, - * ::GetLastError is used - * @return string representation of the error. Currently this is localized - */ -std::wstring errorString(DWORD errorCode = std::numeric_limits<DWORD>::max()); - -/** - * @brief convert filetime to string - * @param time time to convert - * @return a string representation (currently only supports utc and iso format with - * second precision) - */ -std::wstring toString(const FILETIME& time); - -/** - * @brief find file name in a windows file path - * @param path the path to search in - * @return the file name of the path or an empty string if the path ends on - * a slash - * @note this function doesn't access the file system so it doesn't depend - * on whether the file actually exists. This also means it can't - * determine if a path that doesn't end on a slash refers to a file or - * directory - * @note the return value is a pointer into the same buffer, no copy is - * created - */ -LPCWSTR GetBaseName(LPCWSTR path); - -/** - * @see const-variant of this function - */ -LPWSTR GetBaseName(LPWSTR path); - -struct FileResult -{ - std::wstring fileName; - ULONG attributes; -}; - -/** - * @brief a quick function to find all files in a directory or files following a - * pattern. This uses NtQueryDirectoryFile api internally so it should be faster than - * the usual FindFirstFile/FindNextFile pattern - * @param directoryName name of the directory to search in - * @param pattern name pattern that needs to match - * @return the list of files found - */ -std::vector<FileResult> quickFindFiles(LPCWSTR directoryName, LPCWSTR pattern); - -/** - * @brief create the specified directory including all intermediate - * directories - * @param path the path to create - * @param securityAttributes the security attributes to use for all created - * directories. if this is null (default), the standard attributes - * are used - * @return true if the directory (and possibly parent directories) were actually created - * and false if the directory already existed. Throws exceptions on failure. - */ -bool createPath(boost::filesystem::path path, - LPSECURITY_ATTRIBUTES securityAttributes = nullptr); -inline bool createPath(LPCWSTR path, LPSECURITY_ATTRIBUTES securityAttributes = nullptr) -{ - return createPath(boost::filesystem::path(path), securityAttributes); -} - -std::wstring getWindowsBuildLab(bool ex = false); - -} // namespace winapi::ex::wide - -namespace winapi::process -{ - -template <typename CharT> -_Create<CharT>::_Create(const std::basic_string<CharT>& binaryName) -{ - if (binaryName.length() > MAX_PATH) { - throw parameter_error("executable filename can't be longer than 260 characters"); - } - m_CommandLine << "\"" << binaryName << "\""; -} - -template <typename CharT> -_Create<CharT>& _Create<CharT>::workingDirectory(const std::basic_string<CharT>& path) -{ - m_CurrentDirectory = path; - return *this; -} - -template <typename CharT> -_Create<CharT>& _Create<CharT>::processAttributes(SECURITY_ATTRIBUTES* attributes) -{ - m_ProcessAttributes = attributes; - return *this; -} - -template <typename CharT> -_Create<CharT>& _Create<CharT>::threadAttributes(SECURITY_ATTRIBUTES* attributes) -{ - m_ThreadAttributes = attributes; - return *this; -} - -template <typename CharT> -_Create<CharT>& _Create<CharT>::inheritHandles() -{ - m_InheritHandles = true; - return *this; -} - -template <typename CharT> -_Create<CharT>& _Create<CharT>::suspended() -{ - m_CreationFlags |= CREATE_SUSPENDED; - return *this; -} - -template <typename CharT> -_Create<CharT>& _Create<CharT>::stdoutPipe() -{ - m_StdoutPipe = true; - return *this; -} - -} // namespace winapi::process diff --git a/libs/usvfs/src/shared/windows_sane.h b/libs/usvfs/src/shared/windows_sane.h deleted file mode 100644 index 7bb3726..0000000 --- a/libs/usvfs/src/shared/windows_sane.h +++ /dev/null @@ -1,28 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#define WIN32_LEAN_AND_MEAN -#include <Windows.h> diff --git a/libs/usvfs/src/thooklib/CMakeLists.txt b/libs/usvfs/src/thooklib/CMakeLists.txt deleted file mode 100644 index 48426db..0000000 --- a/libs/usvfs/src/thooklib/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -cmake_minimum_required(VERSION 3.16) - -find_package(asmjit CONFIG REQUIRED) -find_library(LIBUDIS86_LIBRARY libudis86) -find_package(spdlog CONFIG REQUIRED) - -file(GLOB sources CONFIGURE_DEPENDS "*.cpp" "*.h") -source_group("" FILES ${sources}) - -add_library(thooklib STATIC ${sources}) -target_link_libraries(thooklib PRIVATE shared asmjit::asmjit ${LIBUDIS86_LIBRARY} spdlog::spdlog_header_only) -target_include_directories(thooklib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -set_target_properties(thooklib PROPERTIES FOLDER injection) -target_precompile_headers(shared PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../shared/pch.h) diff --git a/libs/usvfs/src/thooklib/asmjit_sane.h b/libs/usvfs/src/thooklib/asmjit_sane.h deleted file mode 100644 index 365622a..0000000 --- a/libs/usvfs/src/thooklib/asmjit_sane.h +++ /dev/null @@ -1,29 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#pragma warning(push) -#pragma warning(disable : 4201) -#pragma warning(disable : 4244) -#pragma warning(disable : 4245) -#include <asmjit/asmjit.h> -#include <asmjit/x86.h> -#pragma warning(pop) diff --git a/libs/usvfs/src/thooklib/hooklib.cpp b/libs/usvfs/src/thooklib/hooklib.cpp deleted file mode 100644 index 9dbbafa..0000000 --- a/libs/usvfs/src/thooklib/hooklib.cpp +++ /dev/null @@ -1,740 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include "udis86wrapper.h" -#include <boost/format.hpp> -#include <boost/predef.h> -#include <map> -#pragma warning(push, 3) -#include <asmjit/asmjit.h> -#pragma warning(pop) -#include "hooklib.h" -#include "ttrampolinepool.h" -#include "utility.h" -#include <addrtools.h> -#include <formatters.h> -#include <shmlogger.h> -#include <winapi.h> - -#if BOOST_ARCH_X86_64 -#pragma message("64bit build") -#define JUMP_SIZE 13 -#elif BOOST_ARCH_X86_32 -#define JUMP_SIZE 5 -#else -#error "unsupported architecture" -#endif - -using namespace asmjit; -// from here on out I'll only test for 64 or "other" - -using namespace HookLib; -using namespace asmjit; - -using namespace usvfs; - -struct THookInfo -{ - LPVOID originalFunction; - LPVOID replacementFunction; - LPVOID detour; // detour to call the original function after hook was installed. - LPVOID trampoline; // code fragment that decides whether the replacement function or - // detour is executed (preventing endless loops) - std::vector<uint8_t> - preamble; // part of the detour that needs to be re-inserted into the original - // function to return it to vanilla state - bool stub; // if this is true, the trampoline calls the "replacement"-function that - // before the original function, not instead of it - enum - { - TYPE_HOTPATCH, // official hot-patch variant as used on 32-bit windows - TYPE_WIN64PATCH, // custom patch variant used on 64-bit windows - TYPE_CHAINPATCH, // the hook is part of the hook chain (and not the first) - TYPE_OVERWRITE, // full jump overwrite used if none of the above work - TYPE_RIPINDIRECT // the function already started on a rip-relative jump so we only - // modified that variable - } type; -}; - -UDis86Wrapper& disasm() -{ - static UDis86Wrapper instance; - return instance; -} - -void PauseOtherThreads() -{ - // TODO: implement me! -} - -void ResumePausedThreads() -{ - // TODO: implement me! should resume only the threads paused by PauseOtherThreads -} - -// not using the disassembler because this is simpler -LPBYTE ShortJumpTarget(LPBYTE address) -{ - int8_t off = *(address + 1); - return address + 2 + off; -} - -size_t GetJumpSize(LPBYTE, LPVOID) -{ - // TODO: it would be neater to use asmjit to generate this jump and ask asmjit for - // the size of this jump but with asmjit I can only generate absolute jumps, which - // take too much space. - - // Since trampoline buffers is always allocated within 32-bit - // address range of jump, we can say with confidence that a 5-byte jump is possible - return 5; -} - -void WriteLongJump(LPBYTE jumpAddr, LPVOID destination) -{ - // TODO: not using asmjit here because I couldn't figure out how to generate - // a working, space-optimized, relative jump to outside the generated code and - // we do want to optimize this jump -#if BOOST_ARCH_X86_64 - intptr_t dist = reinterpret_cast<intptr_t>(destination) - - (reinterpret_cast<intptr_t>(jumpAddr) + 5); - int32_t distShort = static_cast<int32_t>(dist); -#else - int32_t distShort = reinterpret_cast<intptr_t>(destination) - - (reinterpret_cast<intptr_t>(jumpAddr) + 5); -#endif - *jumpAddr = 0xE9; - *reinterpret_cast<int32_t*>(jumpAddr + 1) = distShort; -} - -void WriteSingleJump(THookInfo& hookInfo, HookError* error) -{ - DWORD oldprotect, ignore; - // Set the target function to copy on write, so we don't modify code for other - // processes - if (!VirtualProtect(hookInfo.originalFunction, JUMP_SIZE, PAGE_EXECUTE_WRITECOPY, - &oldprotect)) { - throw std::runtime_error("failed to change virtual protection"); - } - - WriteLongJump(reinterpret_cast<LPBYTE>(hookInfo.originalFunction), - hookInfo.trampoline); - - // restore old memory protection - if (!VirtualProtect(hookInfo.originalFunction, JUMP_SIZE, oldprotect, &ignore)) { - throw std::runtime_error("failed to change virtual protection"); - } - - if (error != nullptr) { - *error = ERR_NONE; - } -} - -void WriteShortJump(LPBYTE jumpAddr, const signed char offset) -{ - *jumpAddr = 0xEB; - *(jumpAddr + 1) = offset; -} - -void WriteIndirectJump(THookInfo& hookInfo, size_t jumpSize, HookError* error) -{ - DWORD oldProtect = 0; - LPBYTE jumpAddr = reinterpret_cast<LPBYTE>(hookInfo.originalFunction) - jumpSize; - // allow write to jump address + the short jump inside the function - if (!VirtualProtect(jumpAddr, jumpSize + 2, PAGE_EXECUTE_WRITECOPY, &oldProtect)) { - throw std::runtime_error("failed to change virtual protection"); - } - - // insert the long jump first, then the short jump to the long jump, thus - // activating the reroute - WriteLongJump(jumpAddr, hookInfo.trampoline); - - PauseOtherThreads(); - WriteShortJump(jumpAddr + jumpSize, -(static_cast<int8_t>(jumpSize) + 2)); - ResumePausedThreads(); - - // restore access protection - if (!VirtualProtect(jumpAddr, jumpSize + 2, oldProtect, &oldProtect)) { - throw std::runtime_error("failed to change virtual protection"); - } - if (error != nullptr) { - *error = ERR_NONE; - } -} - -/// implements function hooking using the mechanism intended for hot patching -/// Explanation: the visual studio compiler offers an option to prepare functions -/// for hot patching. In this case the compiler leaves room for one far jump before -/// the actual function and a 2-byte nop inside the function. -/// to hook such a function we write a jump to our replacement function to the -/// space before the function and short jump to that jump to where the 2-byte nop -/// was. -/// On 32-bit windows, MS seems to have compiled all relevant functions in the -/// windows API for hot patching starting with Windows XP SP3 -/// On 64-bit windows a lot of functions have the space for a jump before the function -/// but they don't have the 2-byte nop so this function doesn't work -/// \param hookInfo info about the hook to be installed -/// \return true on success, false on error -BOOL HookHotPatch(THookInfo& hookInfo, HookError* error) -{ - LPVOID original = reinterpret_cast<LPVOID>(hookInfo.originalFunction); - - if (hookInfo.stub) { - hookInfo.trampoline = TrampolinePool::instance().storeStub( - hookInfo.replacementFunction, - reinterpret_cast<LPVOID>(hookInfo.originalFunction), - shared::AddrAdd(original, 2)); - } else { - hookInfo.trampoline = TrampolinePool::instance().storeTrampoline( - hookInfo.replacementFunction, - reinterpret_cast<LPVOID>(hookInfo.originalFunction), - shared::AddrAdd(original, 2)); - } - - WriteIndirectJump(hookInfo, JUMP_SIZE, error); - hookInfo.type = THookInfo::TYPE_HOTPATCH; - // in this case we don't need a separate detour, we simply jump past the 2-byte nop - hookInfo.detour = reinterpret_cast<LPBYTE>(hookInfo.originalFunction) + 2; - - return TRUE; -} - -uintptr_t followJumps(THookInfo& hookInfo) -{ - LPBYTE original = reinterpret_cast<LPBYTE>(hookInfo.originalFunction); - LPBYTE shortTarget = ShortJumpTarget(original); - - // disassemble the long jump - disasm().setInputBuffer(shortTarget, JUMP_SIZE); - - ud_disassemble(disasm()); - if (ud_insn_mnemonic(disasm()) != UD_Ijmp) { - // this shouldn't happen, we only call this if the jump was discovered before - throw std::runtime_error("failed to find jump in patch"); - } - - uint64_t res = ud_insn_off(disasm()) + ud_insn_len(disasm()); - res += disasm().jumpOffset(); - - return static_cast<uintptr_t>(res); -} - -/// -/// \brief hook a call that is implemented as a short-jump to a long jump using a -/// rip-relative address variable -/// \param hookInfo info about the hook to be installed -/// \param error if the return code is false and this is not null, the referred-to -/// variable is set to an error code -/// \return true on success, false on error -/// -BOOL HookRIPIndirection(THookInfo& hookInfo, HookError* error) -{ - uintptr_t res = followJumps(hookInfo); - - const ud_operand_t* op = ud_insn_opr(disasm(), 0); - - if (op->base != UD_R_RIP) { - throw std::runtime_error("expected rip-relative addressing"); - } - - auto chainNext = disasm().jumpTarget(); - if (hookInfo.stub) { - hookInfo.trampoline = TrampolinePool::instance().storeStub( - hookInfo.replacementFunction, - reinterpret_cast<LPVOID>(hookInfo.originalFunction), - reinterpret_cast<LPVOID>(chainNext)); - } else { - hookInfo.trampoline = TrampolinePool::instance().storeTrampoline( - hookInfo.replacementFunction, - reinterpret_cast<LPVOID>(hookInfo.originalFunction), - reinterpret_cast<LPVOID>(chainNext)); - } - - DWORD oldProtect = 0; - if (!VirtualProtect(reinterpret_cast<LPVOID>(res), 2, PAGE_EXECUTE_WRITECOPY, - &oldProtect)) { - throw std::runtime_error("failed to change virtual protection"); - } - - *reinterpret_cast<uintptr_t*>(res) = reinterpret_cast<uintptr_t>(hookInfo.trampoline); - - if (!VirtualProtect(reinterpret_cast<LPVOID>(res), 2, oldProtect, &oldProtect)) { - throw std::runtime_error("failed to change virtual protection"); - } - - hookInfo.type = THookInfo::TYPE_RIPINDIRECT; - hookInfo.detour = reinterpret_cast<LPVOID>(chainNext); - - if (error != nullptr) { - *error = ERR_NONE; - } - - return TRUE; -} - -BOOL HookChainHook(THookInfo& hookInfo, LPBYTE jumpPos, HookError* error) -{ - // disassemble the long jump - disasm().setInputBuffer(jumpPos, JUMP_SIZE); - - ud_disassemble(disasm()); - if (ud_insn_mnemonic(disasm()) != UD_Ijmp) { - // this shouldn't happen, we only call this if the jump was discovered before - throw std::runtime_error("failed to find jump in patch"); - } - - auto chainTarget = disasm().jumpTarget(); - - size_t size = ud_insn_len(disasm()); - - // save the original code for the preamble so we can restore it later - hookInfo.preamble.resize(size); - memcpy(&hookInfo.preamble[0], jumpPos, size); - - spdlog::get("usvfs")->info("existing hook to {0:x} in {1}", chainTarget, - shared::string_cast<std::string>( - winapi::ex::wide::getSectionName((void*)chainTarget))); - - if (hookInfo.stub) { - hookInfo.trampoline = TrampolinePool::instance().storeStub( - hookInfo.replacementFunction, - reinterpret_cast<LPVOID>(hookInfo.originalFunction), - reinterpret_cast<LPVOID>(chainTarget)); - } else { - hookInfo.trampoline = TrampolinePool::instance().storeTrampoline( - hookInfo.replacementFunction, - reinterpret_cast<LPVOID>(hookInfo.originalFunction), - reinterpret_cast<LPVOID>(chainTarget)); - } - - DWORD oldProtect = 0; - if (!VirtualProtect(jumpPos, size, PAGE_EXECUTE_WRITECOPY, &oldProtect)) { - throw std::runtime_error("failed to change virtual protection"); - } - - WriteLongJump(jumpPos, hookInfo.trampoline); - - if (!VirtualProtect(jumpPos, size, oldProtect, &oldProtect)) { - throw std::runtime_error("failed to change virtual protection"); - } - - hookInfo.type = THookInfo::TYPE_CHAINPATCH; - hookInfo.detour = reinterpret_cast<LPVOID>(chainTarget); - - if (error != nullptr) { - *error = ERR_NONE; - } - - return TRUE; -} - -/// -/// implements hooking by chaining to an existing hook -/// \param hookInfo info about the hook to be installed -/// \param error if the return code is false and this is not null, -/// the referred-to variable is set to an error code -/// \return true on success, false on error -/// -BOOL HookChainHook(THookInfo& hookInfo, HookError* error) -{ - return HookChainHook(hookInfo, reinterpret_cast<LPBYTE>(hookInfo.originalFunction), - error); -} - -/// -/// implements hooking by chaining to an existing hot patch -/// \param hookInfo info about the hook to be installed -/// \param error if the return code is false and this is not null, -/// the referred-to variable is set to an error code -/// \return true on success, false on error -/// -BOOL HookChainPatch(THookInfo& hookInfo, HookError* error) -{ - LPBYTE original = reinterpret_cast<LPBYTE>(hookInfo.originalFunction); - LPBYTE shortTarget = ShortJumpTarget(original); - - return HookChainHook(hookInfo, shortTarget, error); -} - -/// implements function hooking by overwriting the first n bytes of the function -/// with a jump to the replacement function. Since this is destructive to the original -/// function code the first n bytes of the function need to be copied somewhere else -/// and that code needs to be called via a detour. This is a lot more complex than -/// the hotpatch mechanism. -/// \param hookInfo info about the hook to be installed -/// \param error if the return code is false and this is not null, the referred-to -/// variable is set to an error code -/// \return true on success, false on error -BOOL HookDisasm(THookInfo& hookInfo, HookError* error) -{ - LPBYTE address = reinterpret_cast<LPBYTE>(hookInfo.originalFunction); - ud_set_input_buffer(disasm(), address, 40); - - size_t jumpSize = GetJumpSize( - static_cast<LPBYTE>(hookInfo.originalFunction), - TrampolinePool::instance().currentBufferAddress(hookInfo.originalFunction)); - - // test if we have room for a jump before the function - bool jumpspace = true; - for (size_t i = 0; i < jumpSize; ++i) { - if (*(address - i - 1) != 0x90) { - jumpspace = false; - break; - } - } - - size_t minSize = jumpspace ? 2 : jumpSize; - - // iterate over all instructions that overlap with the jump instructions - // we want to write. - // TODO right now, this does not test if the function is smaller than the jump. - size_t size = 0; - while (size < minSize) { - if (ud_disassemble(disasm()) == 0) { - throw std::runtime_error("premature end of file in disassembly"); - } - - if ((size == 0) && (ud_insn_mnemonic(disasm()) == UD_Ijmp)) { - if (error != nullptr) { - *error = ERR_JUMP; - } - return FALSE; - } - - size += ud_insn_len(disasm()); - - // ret instruction or int3 smells like function end - if ((ud_insn_mnemonic(disasm()) == UD_Iret) || - (ud_insn_mnemonic(disasm()) == UD_Iint3)) { - if (error != nullptr) { - *error = ERR_FUNCEND; - } - return FALSE; - } - - // check the operands for relative addressing (not supported) - for (int i = 0; i < 3; ++i) { - const ud_operand* op = ud_insn_opr(disasm(), i); - if (op) { - if ( - // rip-relative are not handled, usually relative jumps - op->base == UD_R_RIP - - // rsp-relative call are not handled (there are valid rsp-relative - // instructions, e.g. sub) - || (ud_insn_mnemonic(disasm()) == UD_Icall && op->base == UD_R_RSP)) { - if (error != nullptr) { - *error = ERR_RIP; - } - return FALSE; - } - } - } - } - - // save the original code for the preamble so we can restore it later - hookInfo.preamble.resize(size); - memcpy(&hookInfo.preamble[0], hookInfo.originalFunction, size); - - size_t rerouteOffset = 0; - if (hookInfo.stub) { - hookInfo.trampoline = TrampolinePool::instance().storeStub( - hookInfo.replacementFunction, hookInfo.originalFunction, size, &rerouteOffset); - } else { - hookInfo.trampoline = TrampolinePool::instance().storeTrampoline( - hookInfo.replacementFunction, hookInfo.originalFunction, size, &rerouteOffset); - } - - if (jumpspace) { - WriteIndirectJump(hookInfo, jumpSize, error); - hookInfo.type = THookInfo::TYPE_WIN64PATCH; - } else { - WriteSingleJump(hookInfo, error); - hookInfo.type = THookInfo::TYPE_OVERWRITE; - } - hookInfo.detour = reinterpret_cast<LPBYTE>(hookInfo.trampoline) + rerouteOffset; - - return TRUE; -} - -enum EPreamble -{ - PRE_PATCHFREE, - PRE_PATCHUSED, - PRE_RIPINDIRECT, - PRE_FOREIGNHOOK, - PRE_UNKNOWN -}; - -EPreamble DeterminePreamble(LPBYTE address) -{ - ud_set_input_buffer(disasm(), address, JUMP_SIZE); - ud_disassemble(disasm()); - - if ((ud_insn_mnemonic(disasm()) == UD_Imov) && - (ud_insn_opr(disasm(), 0) == ud_insn_opr(disasm(), 1)) && - (ud_insn_opr(disasm(), 0)->type == UD_OP_REG)) { - // mov edi, edi - return PRE_PATCHFREE; - } else if ((ud_insn_mnemonic(disasm()) == UD_Ijmp) && (ud_insn_len(disasm()) == 2)) { - // determine target of the short jump - LPBYTE shortTarget = ShortJumpTarget(address); - - // test if that short jump leads to a long jump - ud_set_input_buffer(disasm(), shortTarget, JUMP_SIZE); - ud_disassemble(disasm()); - if (ud_insn_mnemonic(disasm()) == UD_Ijmp) { - const ud_operand* op = ud_insn_opr(disasm(), 0); - if (op->base == UD_R_RIP) { - return PRE_RIPINDIRECT; - } else { - return PRE_PATCHUSED; - } - } else { - return PRE_UNKNOWN; - } - } else if (ud_insn_mnemonic(disasm()) == UD_Ijmp) { - return PRE_FOREIGNHOOK; - } else { - return PRE_UNKNOWN; - } -} - -static std::map<HOOKHANDLE, THookInfo> s_Hooks; - -static HOOKHANDLE GenerateHandle() -{ - static ULONG NextHandle = 1; - return NextHandle++; -} - -HOOKHANDLE applyHook(THookInfo info, HookError* error) -{ - // apply the correct hook function depending on how the function start looks - EPreamble preamble = DeterminePreamble((LPBYTE)info.originalFunction); - - BOOL success = FALSE; - switch (preamble) { - case PRE_PATCHUSED: { - success = HookChainPatch(info, error); - } break; - case PRE_PATCHFREE: { - success = HookHotPatch(info, error); - } break; - case PRE_RIPINDIRECT: { - success = HookRIPIndirection(info, error); - } break; - case PRE_FOREIGNHOOK: { - success = HookChainHook(info, error); - } break; - default: { - success = HookDisasm(info, error); - } break; - } - - if (success == TRUE) { - HOOKHANDLE handle = GenerateHandle(); - s_Hooks[handle] = info; - return handle; - } else { - return INVALID_HOOK; - } -} - -HOOKHANDLE HookLib::InstallStub(LPVOID functionAddress, LPVOID stubAddress, - HookError* error) -{ - if (functionAddress == nullptr) { - if (error != nullptr) - *error = ERR_INVALIDPARAMETERS; - return INVALID_HOOK; - } - - THookInfo info; - info.originalFunction = functionAddress; - info.replacementFunction = stubAddress; - info.stub = true; - info.detour = nullptr; - info.trampoline = nullptr; - info.type = THookInfo::TYPE_OVERWRITE; - - return applyHook(info, error); -} - -HOOKHANDLE HookLib::InstallStub(HMODULE module, LPCSTR functionName, LPVOID stubAddress, - HookError* error) -{ - LPVOID funcAddr = MyGetProcAddress(module, functionName); - return InstallStub(funcAddr, stubAddress, error); -} - -HOOKHANDLE HookLib::InstallHook(LPVOID functionAddress, LPVOID hookAddress, - HookError* error) -{ - if (functionAddress == nullptr) { - if (error != nullptr) - *error = ERR_INVALIDPARAMETERS; - return INVALID_HOOK; - } - THookInfo info; - info.originalFunction = functionAddress; - info.replacementFunction = hookAddress; - info.stub = false; - info.detour = nullptr; - info.trampoline = nullptr; - info.type = THookInfo::TYPE_OVERWRITE; - - return applyHook(info, error); -} - -HOOKHANDLE HookLib::InstallHook(HMODULE module, LPCSTR functionName, LPVOID hookAddress, - HookError* error) -{ - LPVOID funcAddr = MyGetProcAddress(module, functionName); - return InstallHook(funcAddr, hookAddress, error); -} - -void HookLib::RemoveHook(HOOKHANDLE handle) -{ - auto iter = s_Hooks.find(handle); - if (iter != s_Hooks.end()) { - THookInfo info = iter->second; - PauseOtherThreads(); - LPBYTE address = reinterpret_cast<LPBYTE>(info.originalFunction); - if (info.type == THookInfo::TYPE_HOTPATCH) { - // return the short jump to 2-byte nop - // TODO: This doesn't take into account if we chain-loaded another hook - - DWORD oldProtect = 0; - if (!VirtualProtect(address, 2, PAGE_EXECUTE_WRITECOPY, &oldProtect)) { - throw shared::windows_error("failed to gain write access to remove hook"); - } - *address = 0x8b; - *(address + 1) = 0xff; - VirtualProtect(address, 2, oldProtect, &oldProtect); - } else if ((info.type == THookInfo::TYPE_OVERWRITE) || - (info.type == THookInfo::TYPE_WIN64PATCH)) { - DWORD oldProtect = 0; - if (!VirtualProtect(address, info.preamble.size(), PAGE_EXECUTE_WRITECOPY, - &oldProtect)) { - throw shared::windows_error("failed to gain write access to remove hook"); - } - // TODO: remove hook by restoring the original function. This only works if we - // have the exact code available somewhere - memcpy(address, &info.preamble[0], info.preamble.size()); - VirtualProtect(address, info.preamble.size(), oldProtect, &oldProtect); - } else if (info.type == THookInfo::TYPE_CHAINPATCH) { - // we could attempt to restore the original function preamble but I'm not - // sure we can reliably write the jump with same (or lower) size. - // Instead overwrite our own trampoline - disasm().setInputBuffer(static_cast<uint8_t*>(info.originalFunction), JUMP_SIZE); - ud_disassemble(disasm()); - if (ud_insn_mnemonic(disasm()) != UD_Ijmp) { - // this shouldn't happen, we only call this if the jump was discovered before - throw std::runtime_error("failed to find jump in patch"); - } - - LPBYTE jumpPos = static_cast<LPBYTE>(info.originalFunction); - if (ud_insn_len(disasm()) == 2) { - jumpPos = reinterpret_cast<LPBYTE>(disasm().jumpTarget()); - } - - DWORD oldProtect = 0; - if (!VirtualProtect(jumpPos, info.preamble.size(), PAGE_EXECUTE_WRITECOPY, - &oldProtect)) { - throw shared::windows_error("failed to gain write access to remove hook"); - } - memcpy(jumpPos, info.preamble.data(), info.preamble.size()); - VirtualProtect(jumpPos, info.preamble.size(), oldProtect, &oldProtect); - } else if (info.type == THookInfo::TYPE_RIPINDIRECT) { - uintptr_t res = followJumps(info); - DWORD oldProtect = 0; - if (!VirtualProtect(reinterpret_cast<LPVOID>(res), JUMP_SIZE, - PAGE_EXECUTE_WRITECOPY, &oldProtect)) { - throw shared::windows_error("failed to gain write access to remove hook"); - } - *reinterpret_cast<uintptr_t*>(res) = - reinterpret_cast<uintptr_t>(info.originalFunction); - VirtualProtect(reinterpret_cast<LPVOID>(res), JUMP_SIZE, oldProtect, &oldProtect); - } else { - spdlog::get("usvfs")->critical("can't remove hook, unknown hook type!"); - } - - s_Hooks.erase(iter); - ResumePausedThreads(); - } else { - spdlog::get("usvfs")->info("handle unknown: {0:x}", handle); - } -} - -const char* HookLib::GetErrorString(HookError err) -{ - switch (err) { - case ERR_NONE: - return "No Error"; - case ERR_INVALIDPARAMETERS: - return "Invalid parameters"; - case ERR_FUNCEND: - return "Function too short"; - case ERR_JUMP: - return "Function starts on a jump"; - case ERR_RIP: - return "RIP-relative addressing can't be relocated."; - case ERR_RELJUMP: - return "Relative Jump can't be relocated."; - default: - return "Unkown error code"; - } -} - -const char* HookLib::GetHookType(HOOKHANDLE handle) -{ - auto iter = s_Hooks.find(handle); - if (iter != s_Hooks.end()) { - THookInfo info = iter->second; - switch (info.type) { - case THookInfo::TYPE_HOTPATCH: - return "hot patch"; - case THookInfo::TYPE_WIN64PATCH: - return "64-bit hot patch"; - case THookInfo::TYPE_CHAINPATCH: - return "chained patch"; - case THookInfo::TYPE_OVERWRITE: - return "overwrite"; - case THookInfo::TYPE_RIPINDIRECT: - return "rip indirection modified"; - default: { - spdlog::get("usvfs")->error("invalid hook type {0}", info.type); - return "invalid hook type"; - } - } - } - return "invalid handle"; -} - -LPVOID HookLib::GetDetour(HOOKHANDLE handle) -{ - auto iter = s_Hooks.find(handle); - if (iter != s_Hooks.end()) { - THookInfo info = iter->second; - return info.detour; - } - return nullptr; -} diff --git a/libs/usvfs/src/thooklib/hooklib.h b/libs/usvfs/src/thooklib/hooklib.h deleted file mode 100644 index a549c03..0000000 --- a/libs/usvfs/src/thooklib/hooklib.h +++ /dev/null @@ -1,125 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include <map> -#include <windows_sane.h> - -namespace HookLib -{ - -enum HookError -{ - ERR_NONE, - ERR_INVALIDPARAMETERS, // parameters are invalid - ERR_FUNCEND, // function is too short to be hooked - ERR_JUMP, // function consists only of an unconditional jump. Maybe it has already - // been hooked? - ERR_RIP, // segment of the function to be overwritten contains a instruction-relative - // operation - ERR_RELJUMP // segment of the function to be overwritten contains a relative jump we - // can't relocated -}; - -typedef ULONG HOOKHANDLE; -static const HOOKHANDLE INVALID_HOOK = (HOOKHANDLE)-1; - -/// -/// \brief install a stub (function to be called before the target function) -/// \param functionAddress address of the function to stub -/// \param stubAddress address of the stub function. This function has to have the -/// signature of void foobar(LPVOID address). -/// address receives the address of the function. -/// \param error (optional) if set, the referenced variable will receive an error code -/// describing the problem (if any) -/// \return a handle to reference the hook in later operations or INVALID_HOOK on error -/// -HOOKHANDLE InstallStub(LPVOID functionAddress, LPVOID stubAddress, - HookError* error = nullptr); - -/// -/// \brief install a stub (function to be called before the target function) -/// \param module the module containing the function to hook -/// \param functionName name of the function to stub (as exported by the library) -/// \param stubAddress address of the stub function. This function has to have the -/// signature of void foobar(LPVOID address). -/// address receives the address of the function. -/// \param error (optional) if set, the referenced variable will receive an error code -/// describing the problem (if any) -/// \return a handle to reference the hook in later operations or INVALID_HOOK on error -/// -HOOKHANDLE InstallStub(HMODULE module, LPCSTR functionName, LPVOID stubAddress, - HookError* error = nullptr); - -/// -/// \brief install a hook (function replacing the existing functionality of the -/// function) -/// \param functionAddress address of the function to hook -/// \param hookAddress address of the replacement function. This function has to have -/// the exact same signature as the replaced function -/// \param error (optional) if set, the referenced variable will receive an error code -/// describing the problem (if any) -/// \return a handle to reference the hook in later operations or INVALID_HOOK on error -/// -HOOKHANDLE InstallHook(LPVOID functionAddress, LPVOID hookAddress, - HookError* error = nullptr); - -/// -/// \brief install a hook (function replacing the existing functionality of the -/// function) -/// \param functionName name of the function to hook (as exported by the library) -/// \param hookAddress address of the replacement function. This function has to have -/// the exact same signature as the replaced function -/// \param error (optional) if set, the referenced variable will receive an error code -/// describing the problem (if any) -/// \return a handle to reference the hook in later operations or INVALID_HOOK on error -/// -HOOKHANDLE InstallHook(HMODULE module, LPCSTR functionName, LPVOID hookAddress, - HookError* error = nullptr); - -/// -/// \brief remove a hook -/// \param handle handle returned in InstallStub or InstallHook -/// -void RemoveHook(HOOKHANDLE handle); - -/// -/// \brief determine the type of a hook -/// \param handle the handle to look up -/// \return a string describing the used hooking mechanism -/// -const char* GetHookType(HOOKHANDLE handle); - -/// -/// \brief retrieve the address that can be used to directly call a detour -/// \param handle handle for the hook -/// \return function address -/// -LPVOID GetDetour(HOOKHANDLE handle); - -/// -/// \brief resolve an error code to a descriptive string -/// \param err the error code to resolve -/// \return the error string -/// -const char* GetErrorString(HookError err); - -} // namespace HookLib diff --git a/libs/usvfs/src/thooklib/pch.cpp b/libs/usvfs/src/thooklib/pch.cpp deleted file mode 100644 index 1d9f38c..0000000 --- a/libs/usvfs/src/thooklib/pch.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "pch.h" diff --git a/libs/usvfs/src/thooklib/ttrampolinepool.cpp b/libs/usvfs/src/thooklib/ttrampolinepool.cpp deleted file mode 100644 index 3b6a51f..0000000 --- a/libs/usvfs/src/thooklib/ttrampolinepool.cpp +++ /dev/null @@ -1,601 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include "ttrampolinepool.h" -#include <addrtools.h> -#include <shmlogger.h> -// #include <boost/thread/lock_guard.hpp> -#include "udis86wrapper.h" - -using namespace asmjit; -#if BOOST_ARCH_X86_64 -using namespace x86; -#elif BOOST_ARCH_X86_32 -using namespace asmjit::x86; -#endif - -using namespace usvfs::shared; - -namespace HookLib -{ - -TrampolinePool* TrampolinePool::s_Instance = nullptr; - -TrampolinePool::TrampolinePool() : m_MaxTrampolineSize(sizeof(LPVOID)) -{ - m_BarrierAddr = &TrampolinePool::barrier; - m_ReleaseAddr = &TrampolinePool::release; - - SYSTEM_INFO sysInfo; - ::ZeroMemory(&sysInfo, sizeof(SYSTEM_INFO)); - GetSystemInfo(&sysInfo); - m_BufferSize = sysInfo.dwPageSize; - - // if search range = ffffff then addressmask = ffffffffff000000 - // => all jumps between xxxxxxxxxx000000 and xxxxxxxxxxffffff will use the same buffer - // for trampolines which is guaranteed to be in that range - // TODO it should be valid to use 2 ^ 32 as the search range to increase our chances - // of finding a memory block we can reserve but then there is a problem with - // converting negative jump distances to 32 bit I didn't understand. Everything - // up to 2 ^ 31 seems to be fine though - m_SearchRange = static_cast<size_t>(pow(2, 30)) - 1; - m_AddressMask = std::numeric_limits<uint64_t>::max() - m_SearchRange; -} - -// static -void TrampolinePool::initialize() -{ - if (!s_Instance) - s_Instance = new TrampolinePool(); -} - -void TrampolinePool::setBlock(bool block) -{ - m_FullBlock = block; - if (m_ThreadGuards.get() == nullptr) { - m_ThreadGuards.reset(new TThreadMap()); - } -} - -#if BOOST_ARCH_X86_64 -// push all registers (except rax) and flags to the stack -static void pushAll(X86Assembler& assembler) -{ - assembler.pushf(); - assembler.push(rcx); - assembler.push(rdx); - assembler.push(rbx); - assembler.push(rbp); - assembler.push(rsi); - assembler.push(rdi); - assembler.push(r8); - assembler.push(r9); - assembler.push(r10); - assembler.push(r11); - assembler.push(r12); - assembler.push(r13); - assembler.push(r14); - assembler.push(r15); -} - -// pop all registers (except rax) and flags from stack -static void popAll(X86Assembler& assembler) -{ - assembler.pop(r15); - assembler.pop(r14); - assembler.pop(r13); - assembler.pop(r12); - assembler.pop(r11); - assembler.pop(r10); - assembler.pop(r9); - assembler.pop(r8); - assembler.pop(rdi); - assembler.pop(rsi); - assembler.pop(rbp); - assembler.pop(rbx); - assembler.pop(rdx); - assembler.pop(rcx); - assembler.popf(); -} -#endif // BOOST_ARCH_X86_64 - -void TrampolinePool::addBarrier(LPVOID rerouteAddr, LPVOID original, - X86Assembler& assembler) -{ - Label skipLabel = assembler.newLabel(); - -#if BOOST_ARCH_X86_64 - pushAll(assembler); - assembler.mov(rcx, - imm(reinterpret_cast<int64_t>( - original))); // set call parameter for call to barrier function - assembler.mov(rax, imm((intptr_t)(void*)barrier)); - assembler.sub(rsp, 32); - assembler.call(rax); - assembler.add(rsp, 32); - popAll(assembler); - // test barrier - assembler.cmp(rax, 0); // test if the barrier is locked - assembler.jz(skipLabel); // skip if barrier was locked - - // call replacement function - // for this call no registers are saved. the called function is a compiled function so - // it should correctly save non-volatile registers, and the caller can't expect the - // volatile ones to remain valid - assembler.pop(r10); - assembler.mov(dword_ptr(rax), r10); // store that return address to the variable - // supplied by the barrier function - assembler.mov(rax, imm((intptr_t)(LPVOID)rerouteAddr)); - assembler.call(rax); - assembler.push(rax); // save away result - - // open the barrier again - pushAll(assembler); - assembler.mov(rcx, imm(reinterpret_cast<int64_t>(original))); - assembler.mov(rax, imm((intptr_t)(void*)release)); - assembler.sub(rsp, 32); - assembler.call(rax); - assembler.add(rsp, 32); - popAll(assembler); - assembler.pop(r10); // get the result from the replacement function to a register - assembler.push(rax); // push the original return address back on the stack - assembler.mov(rax, r10); // move result of actual call to rax - assembler.ret(); // return, using the original return address -#else // BOOST_ARCH_X86_64 - assembler.push(imm(void_ptr_cast<int32_t>( - original))); // push original function, as parameter to barrier - assembler.mov(ecx, (Ptr) static_cast<void*>(TrampolinePool::barrier)); - assembler.call(ecx); // call barrier function - assembler.cmp(eax, 0); - assembler.jz(skipLabel); // if barrier is locked, jump to end of function - - // case a: we got through the barrier - assembler.pop(ecx); // pop the return address into ecx - assembler.mov(dword_ptr(eax), ecx); // store that return address in the variable - // supplied by the barrier function - - assembler.mov(eax, (Ptr) static_cast<void*>(rerouteAddr)); - assembler.call(eax); // call replacement function (pointer was stored in front of the - // trampoline) (this function gets the parameters that were on - // the stack already and cleans - // them up itself (stdcall convention)) - assembler.push(eax); // save away result - assembler.push(imm(void_ptr_cast<int32_t>(original))); // open the barrier again - assembler.mov(eax, (Ptr) static_cast<void*>(TrampolinePool::release)); - assembler.call(eax); - assembler.pop(ecx); // pop the result from the actual call to ecx - assembler.push(eax); // push the original return address (returned by - // TTrampolinePool::release) back on the stack - assembler.mov(eax, ecx); // move result of actual call to eax - assembler.ret(); // return, using the original return address -#endif // BOOST_ARCH_X86_64 - - assembler.bind(skipLabel); -} - -LPVOID TrampolinePool::roundAddress(LPVOID address) const -{ - return reinterpret_cast<LPVOID>(reinterpret_cast<intptr_t>(address) & m_AddressMask); -} - -TrampolinePool::BufferList& TrampolinePool::getBufferList(LPVOID address) -{ - LPVOID rounded = roundAddress(address); - auto iter = m_Buffers.find(rounded); - if (iter == m_Buffers.end()) { - BufferList newBufList = {0, std::vector<LPVOID>()}; - m_Buffers[rounded] = newBufList; - iter = allocateBuffer(address); - } - return iter->second; -} - -LPVOID TrampolinePool::storeStub(LPVOID reroute, LPVOID original, LPVOID returnAddress) -{ - BufferList& bufferList = getBufferList(original); - // first test to increase likelyhood we don't have to reallocate later - if (bufferList.offset + m_MaxTrampolineSize > m_BufferSize) { - allocateBuffer(original); - } - - LPVOID spot = AddrAdd(*bufferList.buffers.rbegin(), bufferList.offset); - - // ??? write address of reroute to trampoline and move past the address - *reinterpret_cast<LPVOID*>(spot) = reroute; - // coverity[suspicious_sizeof] - spot = AddrAdd(spot, sizeof(LPVOID)); - bufferList.offset += sizeof(LPVOID); - - JitRuntime runtime; -#if BOOST_ARCH_X86_64 - X86Assembler assembler(&runtime); -#else - X86Assembler assembler(&runtime); -#endif - addCallToStub(assembler, original, reroute); - addAbsoluteJump(assembler, reinterpret_cast<uint64_t>(returnAddress)); - - size_t codeSize = assembler.getCodeSize(); - - m_MaxTrampolineSize = - std::max(m_MaxTrampolineSize, static_cast<int>(codeSize + sizeof(LPVOID))); - - // final test to see if we can store the trampoline in the buffer - if ((bufferList.offset + codeSize) > m_BufferSize) { - // can't place function in buffer, allocate another and try again - allocateBuffer(original); - // we could relocate the code and the data but this is simpler - return storeStub(reroute, original, returnAddress); - } - - // adjust relative jumps for move to buffer - codeSize = assembler.relocCode(spot); - - uint8_t* code = assembler.getBuffer(); - memcpy(spot, code, codeSize); - - bufferList.offset += codeSize; - - return spot; -} - -LPVOID TrampolinePool::storeTrampoline(LPVOID reroute, LPVOID original, - LPVOID returnAddress) -{ - BufferList& bufferList = getBufferList(original); - // first test to increase likelyhood we don't have to reallocate later - if (bufferList.offset + m_MaxTrampolineSize > m_BufferSize) { - allocateBuffer(original); - } - - LPVOID spot = AddrAdd(*bufferList.buffers.rbegin(), bufferList.offset); - - *reinterpret_cast<LPVOID*>(spot) = reroute; - // coverity[suspicious_sizeof] - spot = AddrAdd(spot, sizeof(LPVOID)); - bufferList.offset += sizeof(LPVOID); - - JitRuntime runtime; - X86Assembler assembler(&runtime); - addBarrier(reroute, original, assembler); -#if BOOST_ARCH_X86_64 - assembler.mov(rax, imm((intptr_t)(void*)(returnAddress))); - assembler.jmp(rax); -#else - assembler.mov(eax, imm((intptr_t)(void*)(returnAddress))); - assembler.jmp(eax); -#endif - size_t codeSize = assembler.getCodeSize(); - - m_MaxTrampolineSize = - std::max(m_MaxTrampolineSize, static_cast<int>(codeSize + sizeof(LPVOID))); - - // final test to see if we can store the trampoline in the buffer - if ((bufferList.offset + codeSize) > m_BufferSize) { - // can't place function in buffer, allocate another and try again - allocateBuffer(original); - // we could relocate the code and the data but this is simpler - return storeTrampoline(reroute, original, returnAddress); - } - - // adjust relative jumps for move to buffer - codeSize = assembler.relocCode(spot); - - // copy code to buffer - uint8_t* code = assembler.getBuffer(); - memcpy(spot, code, codeSize); - - bufferList.offset += codeSize; - return spot; -} - -#if BOOST_ARCH_X86_64 -void TrampolinePool::copyCode(X86Assembler& assembler, LPVOID source, size_t numBytes) -{ - static UDis86Wrapper disasm; - - disasm.setInputBuffer(static_cast<const uint8_t*>(source), numBytes); - - size_t offset = 0; - - while (ud_disassemble(disasm) != 0) { - // rewrite relative jumps, blind copy everything else - - offset += ud_insn_len(disasm); - - // WARNING: doesn't support conditional jumps - if ((ud_insn_mnemonic(disasm) == UD_Ijmp) && - (ud_insn_opr(disasm, 0)->type == UD_OP_JIMM)) { - uintptr_t dest = disasm.jumpTarget(); - assembler.mov(rax, imm(static_cast<uint64_t>(dest))); - assembler.jmp(rax); - } else { - assembler.embed(ud_insn_ptr(&disasm.obj()), ud_insn_len(&disasm.obj())); - // assembler.data(); - } - } -} -#endif - -void TrampolinePool::addCallToStub(X86Assembler& assembler, LPVOID original, - LPVOID reroute) -{ -#if BOOST_ARCH_X86_64 - pushAll(assembler); - assembler.mov(rcx, imm(reinterpret_cast<int64_t>(original))); - assembler.mov(rax, imm((intptr_t)(LPVOID)reroute)); - assembler.sub(rsp, 32); - assembler.call(rax); - assembler.add(rsp, 32); - popAll(assembler); -#else // BOOST_ARCH_X86_64 - assembler.push(reinterpret_cast<int64_t>(original)); - assembler.mov(ecx, imm((intptr_t)(LPVOID)reroute)); - assembler.call(ecx); - assembler.pop(ecx); // remove argument from stack -#endif // BOOST_ARCH_X86_64 -} - -void TrampolinePool::addAbsoluteJump(X86Assembler& assembler, uint64_t destination) -{ -#if BOOST_ARCH_X86_64 - assembler.push(rax); - assembler.push(rax); - assembler.mov(rax, imm(destination)); - assembler.mov(ptr(rsp, 8), rax); - assembler.pop(rax); - assembler.ret(); -#else // BOOST_ARCH_X86_64 - assembler.push(imm(destination)); - assembler.ret(); -#endif // BOOST_ARCH_X86_64 -} - -LPVOID TrampolinePool::storeStub(LPVOID reroute, LPVOID original, size_t preambleSize, - size_t* rerouteOffset) -{ - BufferList& bufferList = getBufferList(original); - // first test to increase likelyhood we don't have to reallocate later - if (bufferList.offset + m_MaxTrampolineSize > m_BufferSize) { - allocateBuffer(original); - } - - LPVOID spot = AddrAdd(*bufferList.buffers.rbegin(), bufferList.offset); - - *reinterpret_cast<LPVOID*>(spot) = reroute; - // coverity[suspicious_sizeof] - spot = AddrAdd(spot, sizeof(LPVOID)); - bufferList.offset += sizeof(LPVOID); - - JitRuntime runtime; - X86Assembler assembler(&runtime); - addCallToStub(assembler, original, reroute); -#if BOOST_ARCH_X86_64 - // insert backup code - *rerouteOffset = assembler.getCodeSize(); - copyCode(assembler, original, preambleSize); -#else // BOOST_ARCH_X86_64 - assembler.embed(original, preambleSize); -#endif // BOOST_ARCH_X86_64 - addAbsoluteJump(assembler, reinterpret_cast<uint64_t>(original) + preambleSize); - - // adjust relative jumps for move to buffer - size_t codeSize = assembler.getCodeSize(); - - m_MaxTrampolineSize = - std::max(m_MaxTrampolineSize, static_cast<int>(codeSize + sizeof(LPVOID))); - - // final test to see if we can store the trampoline in the buffer - if ((bufferList.offset + codeSize) > m_BufferSize) { - // can't place function in buffer, allocate another and try again - allocateBuffer(original); - // we could relocate the code and the data but this is simpler - return storeStub(reroute, original, preambleSize, rerouteOffset); - } - - // copy code to buffer - codeSize = assembler.relocCode(spot); - - bufferList.offset += preambleSize + codeSize; - return spot; -} - -LPVOID TrampolinePool::storeTrampoline(LPVOID reroute, LPVOID original, - size_t preambleSize, size_t* rerouteOffset) -{ - BufferList& bufferList = getBufferList(original); - // first test to increase likelyhood we don't have to reallocate later - if (bufferList.offset + m_MaxTrampolineSize > m_BufferSize) { - allocateBuffer(original); - } - - LPVOID spot = AddrAdd(*bufferList.buffers.rbegin(), bufferList.offset); - - *reinterpret_cast<LPVOID*>(spot) = reroute; - // coverity[suspicious_sizeof] - spot = AddrAdd(spot, sizeof(LPVOID)); - bufferList.offset += sizeof(LPVOID); - - JitRuntime runtime; - X86Assembler assembler(&runtime); - addBarrier(reroute, original, assembler); - // insert backup code - *rerouteOffset = assembler.getCodeSize(); - assembler.embed(original, static_cast<uint32_t>(preambleSize)); - addAbsoluteJump(assembler, reinterpret_cast<uint64_t>(original) + preambleSize); - - // adjust relative jumps for move to buffer - size_t codeSize = assembler.getCodeSize(); - - m_MaxTrampolineSize = - std::max(m_MaxTrampolineSize, static_cast<int>(codeSize + sizeof(LPVOID))); - - // TODO this does not take into account that the code size may technically change - // after relocation in which case the following test may determine the code fits into - // the buffer when it really doesnt't. asmjit doesn't seem to provide a way to adjust - // jumps without actually moving the code though - - // final test to see if we can store the trampoline in the buffer - if ((bufferList.offset + codeSize) > m_BufferSize) { - // can't place function in buffer, allocate another and try again - allocateBuffer(original); - // we could relocate the code and the data but this is simpler - return storeTrampoline(reroute, original, preambleSize, rerouteOffset); - } - - // copy code to buffer - codeSize = static_cast<size_t>(assembler.relocCode(spot)); - - bufferList.offset += preambleSize + codeSize; - - return spot; -} - -LPVOID TrampolinePool::currentBufferAddress(LPVOID addressNear) -{ - LPVOID rounded = roundAddress(addressNear); - auto lookupAddress = m_Buffers.find(rounded); - - if (lookupAddress == m_Buffers.end()) { - lookupAddress = m_Buffers.insert(std::make_pair(rounded, BufferList())).first; - } - if (lookupAddress->second.buffers.size() == 0) { - allocateBuffer(addressNear); - } - - LPVOID res = *(lookupAddress->second.buffers.rbegin()); - return res; -} - -void TrampolinePool::forceUnlockBarrier() -{ - if (m_ThreadGuards.get() != nullptr) { - for (auto funcId : *m_ThreadGuards) { - (*m_ThreadGuards)[funcId.first] = nullptr; - } - } // else no barriers to unlock -} - -TrampolinePool::BufferMap::iterator TrampolinePool::allocateBuffer(LPVOID addressNear) -{ - // allocate a buffer that we can write to and that is executable - SYSTEM_INFO sysInfo; - ::ZeroMemory(&sysInfo, sizeof(SYSTEM_INFO)); - GetSystemInfo(&sysInfo); - - LPVOID rounded = roundAddress(addressNear); - auto iter = m_Buffers.find(rounded); - uintptr_t lowerEnd = reinterpret_cast<uintptr_t>(rounded); - if (iter->second.buffers.size() > 0) { - // start searching were we last found a buffer - lowerEnd = reinterpret_cast<uintptr_t>(*iter->second.buffers.rbegin()) + - sysInfo.dwPageSize; - } - - uintptr_t start = - std::max(std::max(lowerEnd, MIN_ALLOC_ADDR), - reinterpret_cast<uintptr_t>(sysInfo.lpMinimumApplicationAddress)); - uintptr_t upperEnd = reinterpret_cast<uintptr_t>(rounded) + m_SearchRange; - uintptr_t end = std::min( - upperEnd, reinterpret_cast<uintptr_t>(sysInfo.lpMaximumApplicationAddress)); - - LPVOID buffer = nullptr; - for (uintptr_t cur = start; cur < end; cur += sysInfo.dwPageSize) { - buffer = VirtualAlloc(reinterpret_cast<LPVOID>(cur), m_BufferSize, - MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); - if (buffer != nullptr) { - break; - } - } - if (buffer == nullptr) { - throw std::runtime_error("failed to allocate buffer in range"); - } - - // the caller must have looked up the bufferlist in order to determine that a - // buffer has to be allocated - assert(iter != m_Buffers.end()); - - iter->second.offset = 0; - iter->second.buffers.push_back(buffer); - spdlog::get("usvfs")->debug( - "allocated trampoline buffer for jumps between {0:p} and {1:x} at {2:p}" - "(size {3})", - rounded, (reinterpret_cast<uintptr_t>(rounded) + m_SearchRange), buffer, - m_BufferSize); - return iter; -} - -LPVOID TrampolinePool::barrier(LPVOID function) -{ - DWORD err = GetLastError(); - LPVOID res = instance().barrierInt(function); - SetLastError(err); - return res; -} - -LPVOID TrampolinePool::release(LPVOID function) -{ - DWORD err = GetLastError(); - LPVOID res = instance().releaseInt(function); - SetLastError(err); - return res; -} - -LPVOID TrampolinePool::barrierInt(LPVOID func) -{ - if (m_FullBlock) { - return nullptr; - } - - if (m_ThreadGuards.get() == nullptr) { - m_ThreadGuards.reset(new TThreadMap()); - } - - auto iter = m_ThreadGuards->find(func); - if ((iter == m_ThreadGuards->end()) || (iter->second == nullptr)) { - (*m_ThreadGuards)[func] = reinterpret_cast<LPVOID>(1); - return &(*m_ThreadGuards)[func]; - } else { - return nullptr; - } -} - -LPVOID TrampolinePool::releaseInt(LPVOID func) -{ - DWORD lastError = GetLastError(); - if (m_ThreadGuards.get() == nullptr) { - m_ThreadGuards.reset(new TThreadMap()); - } - - auto iter = m_ThreadGuards->find(func); - if (iter == m_ThreadGuards->end()) { - spdlog::get("hooks")->error("failed to release barrier for func {}", func); - ::SetLastError(lastError); - return nullptr; - } - - LPVOID res = (*m_ThreadGuards)[func]; - (*m_ThreadGuards)[func] = nullptr; - - ::SetLastError(lastError); - return res; -} - -} // namespace HookLib diff --git a/libs/usvfs/src/thooklib/ttrampolinepool.h b/libs/usvfs/src/thooklib/ttrampolinepool.h deleted file mode 100644 index 5b44c51..0000000 --- a/libs/usvfs/src/thooklib/ttrampolinepool.h +++ /dev/null @@ -1,222 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include "asmjit_sane.h" -#include <logging.h> -#include <map> -#include <vector> -#include <windows_sane.h> - -#ifdef _MSC_VER -#pragma warning(disable : 4714) -#include <boost/config/compiler/visualc.hpp> -#else -#include <boost/config/compiler/gcc.hpp> -#endif -#include <boost/predef.h> -#include <boost/thread.hpp> -#include <mutex> - -// #include <boost/thread/mutex.hpp> - -namespace HookLib -{ - -/// -/// trampolines are runtime-generated mini-functions that are used to call the -/// original code of a function -/// -class TrampolinePool -{ -public: - /// Call initialize before you use the TrampolinePool - static void initialize(); - - static TrampolinePool& instance() - { - // This is a very very sensitive place so we want to keep this function as simple as - // possible and having it inlined probably cann't hurt - return *s_Instance; - } - - void setBlock(bool block); - - /// - /// store a stub without moving code from the original function. This is used in cases - /// where the hook can be placed without overwriting logic (i.e. hot-patchable - /// functions and when chaining hooks) - /// \param reroute the stub function to call before the regular function (on x86 this - /// needs to be cdecl calling convention!) - /// \param original the original function - /// \param returnAddress address under which the original functionality can be - /// reached. - /// for the first hook this should be (original + 2), otherwise - /// the address of the next hook in the chain - /// \return address of the created trampoline function - /// - LPVOID storeStub(LPVOID reroute, LPVOID original, LPVOID returnAddress); - - /// - /// store a stub, moving part of the original function to the trampoline - /// \param reroute the stub function to call before the regular function (on x86 this - /// needs to be cdecl calling convention!) - /// \param original the original function - /// \param preambleSize number of bytes from the original function to backup. Needs to - /// correspond to complete instructions - /// \param rerouteOffset offset in bytes from the created trampoline to the preamble - /// that leads back to the original code - /// \return address of the created trampoline function - /// - LPVOID storeStub(LPVOID reroute, LPVOID original, size_t preambleSize, - size_t* rerouteOffset); - - /// - /// store a trampoline for hot-patchable functions, where the original function - /// is unharmed. - /// \param reroute the reroute function - /// \param original original function - /// \param returnAddress address under which the original functionality can be - /// reached. - /// \return address of the trampoline function - /// - LPVOID storeTrampoline(LPVOID reroute, LPVOID original, LPVOID returnAddress); - - /// - /// store a trampoline, copying a part of the original function to the trampoline. - /// This is used for the case where the hooking mechanism needs to overwrite part of - /// the function - /// \param reroute the reroute function - /// \param original original function - /// \param preambleSize number of bytes from the original function to backup. Needs to - /// correspond to complete instructions - /// \param rerouteOffset offset in bytes from the created trampoline to the preamble - /// that leads us back to the original code - /// \return address of the trampoline function - /// - LPVOID storeTrampoline(LPVOID reroute, LPVOID original, size_t preambleSize, - size_t* rerouteOffset); - - /// - /// \param addressNear used to find a trampoline buffer near the jump instruction - /// \return retrieve address of current trampoline buffer - /// - LPVOID currentBufferAddress(LPVOID addressNear); - - /// - /// \brief forces the barrier(s) for the current thread to be released - /// - void forceUnlockBarrier(); - -private: - struct BufferList - { - size_t offset; - std::vector<LPVOID> buffers; - }; - - typedef std::map<LPVOID, BufferList> BufferMap; - static const intptr_t ADDRESS_MASK = - 0xFFFFFFFFFF000000LL; // mask to "round" addresses to consolidate near - // trampolines - -private: - TrampolinePool(); - - TrampolinePool& operator=(const TrampolinePool& reference); // not implemented - - /** - * @brief allocates a buffer with read, write and execute rights near the - * specified adress. The purpose is that we want to be able to jump from - * adressNear to generated code with a 5-byte jump, even on x64 systems. - * @param addressNear the reference adress - * @note the resulting buffer is stored in the m_Buffers map - */ - BufferMap::iterator allocateBuffer(LPVOID addressNear); - - void addBarrier(LPVOID rerouteAddr, LPVOID original, asmjit::X86Assembler& assembler); - -#if BOOST_ARCH_X86_64 - void copyCode(asmjit::X86Assembler& assembler, LPVOID source, size_t numBytes); -#endif // BOOST_ARCH_X86_64 - - BufferList& getBufferList(LPVOID address); - - LPVOID roundAddress(LPVOID address) const; - -public: - static LPVOID __stdcall barrier(LPVOID function); - static LPVOID __stdcall release(LPVOID function); - - LPVOID barrierInt(LPVOID function); - LPVOID releaseInt(LPVOID function); - - void addCallToStub(asmjit::X86Assembler& assembler, LPVOID original, LPVOID reroute); - -private: - /// - /// \brief add a jump to an address outside the custom generated asm code (without - /// modifying registers) - /// \param assembler the assembler generator to write to - /// \param destination destination address - /// \note this currently generates a lot code on x64, may be overly complicated - /// - void addAbsoluteJump(asmjit::X86Assembler& assembler, uint64_t destination); - - DWORD determinePageSize(); - -private: -#if BOOST_ARCH_X86_64 - static const int SIZE_OF_JUMP = 13; -#elif BOOST_ARCH_X86_32 - static const int SIZE_OF_JUMP = 5; -#endif - - // the hook lib tries to allocate buffer close to the hooked functions, - // for 32-bits application, this often falls on 0x40000000, which is the - // address at which DLLs can be loaded on Windows - // - // some old-style DRM (e.g. Dragon Age 2) check that address and prevent - // the game from running if it's not 0x40000000, so we give them a bit of - // spaces before allocating our buffers - // - static constexpr uintptr_t MIN_ALLOC_ADDR = 0x40000000 + 0x200000; - - static TrampolinePool* s_Instance; - - bool m_FullBlock{false}; - - BufferMap m_Buffers; - - typedef std::map<void*, void*> TThreadMap; - boost::thread_specific_ptr<TThreadMap> m_ThreadGuards; - - LPVOID m_BarrierAddr; - LPVOID m_ReleaseAddr; - - DWORD m_BufferSize = {1024}; - size_t m_SearchRange; - uint64_t m_AddressMask; - - int m_MaxTrampolineSize; -}; - -} // namespace HookLib diff --git a/libs/usvfs/src/thooklib/udis86wrapper.cpp b/libs/usvfs/src/thooklib/udis86wrapper.cpp deleted file mode 100644 index 1ac7332..0000000 --- a/libs/usvfs/src/thooklib/udis86wrapper.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include "udis86wrapper.h" -#include "pch.h" -#include <boost/predef.h> -#include <shmlogger.h> -#include <stdexcept> - -namespace HookLib -{ - -UDis86Wrapper::UDis86Wrapper() -{ - ud_init(&m_Obj); - ud_set_syntax(&m_Obj, UD_SYN_INTEL); -#if BOOST_ARCH_X86_64 - ud_set_mode(&m_Obj, 64); -#else - ud_set_mode(&m_Obj, 32); -#endif -} - -void UDis86Wrapper::setInputBuffer(const uint8_t* buffer, size_t size) -{ - m_Buffer = buffer; - ud_set_input_buffer(&m_Obj, buffer, size); - ud_set_pc(&m_Obj, reinterpret_cast<uint64_t>(m_Buffer)); -} - -ud_t& UDis86Wrapper::obj() -{ - return m_Obj; -} - -bool UDis86Wrapper::isRelativeJump() -{ - ud_mnemonic_code code = ud_insn_mnemonic(&m_Obj); - // all conditional jumps and loops are relative, as are unconditional jumps with an - // offset operand - return (code == UD_Ijo) || (code == UD_Ijno) || (code == UD_Ijb) || - (code == UD_Ijae) || (code == UD_Ijz) || (code == UD_Ijnz) || - (code == UD_Ijbe) || (code == UD_Ija) || (code == UD_Ijs) || - (code == UD_Ijns) || (code == UD_Ijp) || (code == UD_Ijnp) || - (code == UD_Ijl) || (code == UD_Ijge) || (code == UD_Ijle) || - (code == UD_Ijg) || (code == UD_Ijcxz) || (code == UD_Ijecxz) || - (code == UD_Ijrcxz) || (code == UD_Iloop) || (code == UD_Iloope) || - (code == UD_Iloopne) || - ((code == UD_Icall) && (ud_insn_opr(&m_Obj, 0)->type == UD_OP_JIMM)) || - ((code == UD_Ijmp) && (ud_insn_opr(&m_Obj, 0)->type == UD_OP_JIMM)); -} - -intptr_t UDis86Wrapper::jumpOffset() -{ - const ud_operand_t* op = ud_insn_opr(&m_Obj, 0); - switch (op->size) { - case 8: - return static_cast<intptr_t>(op->lval.sbyte); - case 16: - return static_cast<intptr_t>(op->lval.sword); - case 32: - return static_cast<intptr_t>(op->lval.sdword); - case 64: - return static_cast<intptr_t>(op->lval.sqword); - default: - throw std::runtime_error("unsupported jump size"); - } -} - -uint64_t UDis86Wrapper::jumpTarget() -{ - // TODO: assert we're actually on a jump - - uint64_t res = ud_insn_off(&m_Obj) + ud_insn_len(&m_Obj); - - res += jumpOffset(); - - if (ud_insn_opr(&m_Obj, 0)->base == UD_R_RIP) { - res = *reinterpret_cast<uintptr_t*>(res); - } - - return res; -} - -} // namespace HookLib diff --git a/libs/usvfs/src/thooklib/udis86wrapper.h b/libs/usvfs/src/thooklib/udis86wrapper.h deleted file mode 100644 index 48d8a42..0000000 --- a/libs/usvfs/src/thooklib/udis86wrapper.h +++ /dev/null @@ -1,62 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include <udis86.h> -#undef inline // libudis86/types.h defines inline to __inline which is no longer legal - // since vs2012 - -namespace HookLib -{ - -class UDis86Wrapper -{ - -public: - UDis86Wrapper(); - - void setInputBuffer(const uint8_t* buffer, size_t size); - - ud_t& obj(); - - operator ud_t*() { return &m_Obj; } - - bool isRelativeJump(); - - intptr_t jumpOffset(); - - /// - /// determines the absolute jump target at the current instruction, taking into - /// account relative instructions of all sizes and RIP-relative addressing. - /// \return absolute address of the jump at the current disassembler instruction - /// \note this works correctly ONLY if the input buffer has been set with - /// setInputBuffer or - /// if ud_set_pc has been called - /// - uint64_t jumpTarget(); - -private: -private: - ud_t m_Obj; - const uint8_t* m_Buffer{nullptr}; -}; - -} // namespace HookLib diff --git a/libs/usvfs/src/thooklib/utility.cpp b/libs/usvfs/src/thooklib/utility.cpp deleted file mode 100644 index 124141f..0000000 --- a/libs/usvfs/src/thooklib/utility.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include "utility.h" -#include "exceptionex.h" -#include <cstdlib> - -namespace HookLib -{ - -FARPROC MyGetProcAddress(HMODULE module, LPCSTR functionName) -{ - // determine position of the exports of the module - PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)module; - if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) { - return nullptr; - } - - PIMAGE_NT_HEADERS ntHeaders = - (PIMAGE_NT_HEADERS)(((LPBYTE)dosHeader) + dosHeader->e_lfanew); - if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) { - return nullptr; - } - - PIMAGE_OPTIONAL_HEADER optionalHeader = &ntHeaders->OptionalHeader; - if (optionalHeader->NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_EXPORT) { - return nullptr; - } - PIMAGE_DATA_DIRECTORY dataDirectory = - &optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; - PIMAGE_EXPORT_DIRECTORY exportDirectory = - (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)dosHeader + dataDirectory->VirtualAddress); - - ULONG* addressOfNames = (ULONG*)((LPBYTE)module + exportDirectory->AddressOfNames); - ULONG* funcAddr = (ULONG*)((LPBYTE)module + exportDirectory->AddressOfFunctions); - - // search exports for the specified name - for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) { - char* curFunctionName = (char*)((LPBYTE)module + addressOfNames[i]); - USHORT* nameOrdinals = - (USHORT*)((LPBYTE)module + exportDirectory->AddressOfNameOrdinals); - if (strcmp(functionName, curFunctionName) == 0) { - if (funcAddr[nameOrdinals[i]] >= dataDirectory->VirtualAddress && - funcAddr[nameOrdinals[i]] < - dataDirectory->VirtualAddress + dataDirectory->Size) { - char* forwardLibName = _strdup((LPSTR)module + funcAddr[nameOrdinals[i]]); - ON_BLOCK_EXIT([forwardLibName]() { - free(forwardLibName); - }); - char* forwardFunctionName = strchr(forwardLibName, '.'); - *forwardFunctionName = 0; - ++forwardFunctionName; - - HMODULE forwardLib = LoadLibraryA(forwardLibName); - FARPROC forward = nullptr; - if (forwardLib != nullptr) { - forward = MyGetProcAddress(forwardLib, forwardFunctionName); - FreeLibrary(forwardLib); - } - - return forward; - } - return (FARPROC)((LPBYTE)module + funcAddr[nameOrdinals[i]]); - } - } - return nullptr; -} - -} // namespace HookLib diff --git a/libs/usvfs/src/thooklib/utility.h b/libs/usvfs/src/thooklib/utility.h deleted file mode 100644 index e0be9f7..0000000 --- a/libs/usvfs/src/thooklib/utility.h +++ /dev/null @@ -1,35 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include <windows_sane.h> - -namespace HookLib -{ - -/// \brief reimplementation of GetProcAddress to circumvent foreign hooks of -/// GetProcAddress like AcLayer -/// \param module handle to the module that contains the function or variable -/// \param functionName function to retrieve the address of -/// \return address of the exported function -FARPROC MyGetProcAddress(HMODULE module, LPCSTR functionName); - -} // namespace HookLib diff --git a/libs/usvfs/src/tinjectlib/CMakeLists.txt b/libs/usvfs/src/tinjectlib/CMakeLists.txt deleted file mode 100644 index 096608e..0000000 --- a/libs/usvfs/src/tinjectlib/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -cmake_minimum_required(VERSION 3.16) - -find_package(asmjit CONFIG REQUIRED) - -add_library(tinjectlib STATIC asmjit_sane.h injectlib.cpp injectlib.h) -target_link_libraries(tinjectlib PRIVATE shared asmjit::asmjit) -target_include_directories(tinjectlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -set_target_properties(tinjectlib PROPERTIES FOLDER injection) diff --git a/libs/usvfs/src/tinjectlib/asmjit_sane.h b/libs/usvfs/src/tinjectlib/asmjit_sane.h deleted file mode 100644 index 70ee117..0000000 --- a/libs/usvfs/src/tinjectlib/asmjit_sane.h +++ /dev/null @@ -1,32 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#pragma warning(push, 3) -#pragma warning(disable : 4201) -#pragma warning(disable : 4244) -#pragma warning(disable : 4245) -// #ifndef ASMJIT_API -// #define ASMJIT_API -// #endif // ASMJIT_API -#include <asmjit/asmjit.h> -#include <asmjit/x86.h> -#pragma warning(pop) diff --git a/libs/usvfs/src/tinjectlib/injectlib.cpp b/libs/usvfs/src/tinjectlib/injectlib.cpp deleted file mode 100644 index 2c7362a..0000000 --- a/libs/usvfs/src/tinjectlib/injectlib.cpp +++ /dev/null @@ -1,482 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include <cstdio> -#include <stdexcept> - -#include <boost/filesystem.hpp> -#include <boost/predef.h> -#include <boost/static_assert.hpp> -#include <boost/type_traits.hpp> -namespace fs = boost::filesystem; - -#include "injectlib.h" -#include <addrtools.h> -#include <exceptionex.h> -#include <stringcast.h> -#include <stringutils.h> -// local version of asmjit with warning suppression -#include "asmjit_sane.h" -#include <TlHelp32.h> -#include <spdlog/spdlog.h> - -using namespace asmjit; -using namespace usvfs::shared; - -#if BOOST_ARCH_X86_64 -#pragma message("64bit build") -using namespace x86; -#elif BOOST_ARCH_X86_32 -#pragma message("32bit build") -using namespace asmjit::x86; -#else -#error "unsupported architecture" -#endif - -typedef HMODULE(WINAPI* TLoadLibraryType)(LPCWSTR); -typedef FARPROC(WINAPI* TGetProcAddressType)(HMODULE, LPCSTR); -typedef DWORD(WINAPI* TGetLastErrorType)(); - -typedef BOOL(WINAPI* TSetXStateFeaturesMaskType)(PCONTEXT, DWORD64); - -static const size_t MAX_FUNCTIONAME = 20; - -struct TDataRemote -{ - TLoadLibraryType loadLibrary; - TGetProcAddressType getProcAddress; - TGetLastErrorType getLastError; - REGWORD returnAddress; - - char initFunction[MAX_FUNCTIONAME + 1]; - WCHAR dllName[MAX_PATH]; -}; - -#if BOOST_ARCH_X86_64 -void pushAll(X86Assembler& assembler) -{ - assembler.pushf(); - assembler.push(rax); - assembler.push(rcx); - assembler.push(rdx); - assembler.push(rbx); - assembler.push(rbp); - assembler.push(rsi); - assembler.push(rdi); - assembler.push(r8); - assembler.push(r9); - assembler.push(r10); - assembler.push(r11); - assembler.push(r12); - assembler.push(r13); - assembler.push(r14); - assembler.push(r15); -} - -void popAll(X86Assembler& assembler) -{ - assembler.pop(r15); - assembler.pop(r14); - assembler.pop(r13); - assembler.pop(r12); - assembler.pop(r11); - assembler.pop(r10); - assembler.pop(r9); - assembler.pop(r8); - assembler.pop(rdi); - assembler.pop(rsi); - assembler.pop(rbp); - assembler.pop(rbx); - assembler.pop(rdx); - assembler.pop(rcx); - assembler.pop(rax); - assembler.popf(); -} -#endif // BOOST_ARCH_X86_64 - -void addStub(size_t userDataSize, X86Assembler& assembler, bool skipInit, - TDataRemote* localData, TDataRemote* remoteData, LPCSTR initFunction) -{ - Label Label_DLLLoaded = assembler.newLabel(); - -#if BOOST_ARCH_X86_64 - pushAll(assembler); - // call load library for the actual injection - assembler.mov(rcx, imm(reinterpret_cast<int64_t>(&remoteData->dllName))); - assembler.mov(rax, imm((intptr_t)(void*)localData->loadLibrary)); - assembler.sub(rsp, 32); - assembler.call(rax); - assembler.add(rsp, 32); - - // cancel out of here if we failed to load the dll - // TODO: would be great to report this error. But how? - assembler.test(rax, rax); - assembler.jnz(Label_DLLLoaded); - /* this commented out code may seem pointless but it is a simple way to get at the - error code when debugging. assembler.mov(rax, - imm((intptr_t)(void*)localData->getLastError)); assembler.sub(rsp, 32); - assembler.call(rax); - assembler.add(rsp, 32); - assembler.int3();*/ - popAll(assembler); - assembler.ret(); - assembler.bind(Label_DLLLoaded); - - // determine address of the init function - if (initFunction != nullptr) { - Label Label_SkipInit = assembler.newLabel(); - assembler.mov(rcx, rax); // handle of the dll - assembler.mov(rdx, imm(reinterpret_cast<int64_t>( - remoteData->initFunction))); // name of init function - assembler.mov(rax, imm((intptr_t)(void*)localData->getProcAddress)); - assembler.sub(rsp, 32); - assembler.call(rax); - assembler.add(rsp, 32); - - if (skipInit) { - assembler.test(rax, rax); - assembler.jz(Label_SkipInit); - } - - // call the init function with user data - assembler.mov(rcx, - imm(reinterpret_cast<int64_t>(remoteData) + sizeof(TDataRemote))); - assembler.mov(rdx, imm(static_cast<int64_t>(userDataSize))); - assembler.sub(rsp, 32); - assembler.call(rax); - assembler.add(rsp, 32); - assembler.bind(Label_SkipInit); - } - - // restore registers - popAll(assembler); -#else - - // save registers - assembler.push(eax); - assembler.pushf(); - - // call load library for the actual injection - assembler.push(imm(void_ptr_cast<int64_t>(remoteData->dllName))); - // assembler.call(ptr_abs(static_cast<void*>(remoteData->loadLibrary))); - assembler.mov(eax, imm(void_ptr_cast<int64_t>(localData->loadLibrary))); - assembler.call(eax); - - assembler.test(eax, eax); - assembler.jnz(Label_DLLLoaded); - /* this commented out code may seem pointless but it is a simple way to get at the - error code when debugging. assembler.mov(eax, - imm((intptr_t)(void*)localData->getLastError)); assembler.call(eax); - assembler.int3();*/ - assembler.popf(); - assembler.pop(eax); - assembler.ret(); - assembler.bind(Label_DLLLoaded); - - // determine address of the init function - if (initFunction != nullptr) { - Label Label_SkipInit = assembler.newLabel(); - assembler.push(imm( - void_ptr_cast<int64_t>(remoteData->initFunction))); // name of init function - assembler.push(eax); // handle of the dll - assembler.mov(eax, imm(void_ptr_cast<int64_t>(localData->getProcAddress))); - assembler.call(eax); - if (skipInit) { - assembler.cmp(eax, 0); - assembler.jz(Label_SkipInit); - } else { - assembler.cmp(eax, 0); - assembler.jnz(Label_SkipInit); - // heading for a crash! give an attached debugger a chance to analyse the error - assembler.mov(eax, imm(void_ptr_cast<int64_t>(localData->getLastError))); - assembler.call(eax); - assembler.int3(); - assembler.bind(Label_SkipInit); - } - - // call the init function with user data - assembler.push(userDataSize); - assembler.push(imm(void_ptr_cast<int64_t>(remoteData) + sizeof(TDataRemote))); - assembler.call(eax); - // init function is declared __cdecl so we have to remove parameters from the stack - assembler.pop(eax); - assembler.pop(eax); - if (skipInit) { - assembler.bind(Label_SkipInit); - } - } - - // restore registers - assembler.popf(); - assembler.pop(eax); - -#endif -} - -REGWORD WriteInjectionStub(HANDLE processHandle, LPCWSTR dllName, LPCSTR initFunction, - LPCVOID userData, size_t userDataSize, bool skipInit, - REGWORD returnAddress) -{ - HMODULE k32mod = ::LoadLibrary(__TEXT("kernel32.dll")); - TDataRemote data = {0}; - - if (k32mod != nullptr) { - data.loadLibrary = - reinterpret_cast<TLoadLibraryType>(GetProcAddress(k32mod, "LoadLibraryW")); - data.getProcAddress = - reinterpret_cast<TGetProcAddressType>(GetProcAddress(k32mod, "GetProcAddress")); - data.getLastError = - reinterpret_cast<TGetLastErrorType>(GetProcAddress(k32mod, "GetLastError")); - if ((data.loadLibrary == nullptr) || (data.getProcAddress == nullptr) || - (data.getLastError == nullptr)) { - throw windows_error("failed to determine address for required functions"); - } - } else { - throw windows_error("kernel32.dll not loaded?"); - } - - data.returnAddress = returnAddress; - - if (initFunction != nullptr) { - strncpy_s(data.initFunction, MAX_FUNCTIONAME, initFunction, MAX_FUNCTIONAME); - data.initFunction[MAX_FUNCTIONAME] = '\0'; - } else { - data.initFunction[0] = '\0'; - } - - wcsncpy_s(data.dllName, MAX_PATH, dllName, MAX_PATH - 1); - data.dllName[MAX_PATH - 1] = L'\0'; - - size_t totalSize = sizeof(TDataRemote) + userDataSize; - - // allocate memory in the target process and write the data-block there - LPVOID remoteMem = VirtualAllocEx(processHandle, nullptr, totalSize, - MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - - if (remoteMem == nullptr) { - throw windows_error("failed to allocate memory in target process"); - } - - SIZE_T written; - if (!WriteProcessMemory(processHandle, remoteMem, &data, sizeof(TDataRemote), - &written)) { - throw windows_error("failed to write control data to target process"); - } - if (written != sizeof(TDataRemote)) { - throw windows_error("failed to write whole control data to target process"); - } - - // write user data to remote memory if necessary - if (userData != nullptr) { - if (!WriteProcessMemory(processHandle, AddrAdd(remoteMem, sizeof(TDataRemote)), - userData, userDataSize, &written)) { - throw windows_error("failed to write user data to target process"); - } - if (written != userDataSize) { - throw windows_error("failed to write whole user data to target process"); - } - } - - TDataRemote* remoteData = reinterpret_cast<TDataRemote*>(remoteMem); - - // now for the interesting part: write a stub into the target process that is run - // before any code of the original binary. - - JitRuntime runtime; -#if BOOST_ARCH_X86_64 - X86Assembler assembler(&runtime); - if (returnAddress != 0) { - // put return address on the stack - // (this damages rax which hopefully doesn't matter) - assembler.mov(rax, imm((intptr_t)(void*)data.returnAddress)); - assembler.push(rax); - } // otherwise no return address was specified here. It better be on the stack - // already -#else - X86Assembler assembler(&runtime); - if (returnAddress != 0) { - assembler.push(imm((intptr_t)(void*)data.returnAddress)); - } -#endif - - addStub(userDataSize, assembler, skipInit, &data, remoteData, initFunction); - assembler.ret(0); - - size_t stubSize = assembler.getCodeSize(); - - // reserve memory for the stub - PBYTE stubRemote = reinterpret_cast<PBYTE>( - VirtualAllocEx(processHandle, nullptr, stubSize, MEM_COMMIT | MEM_RESERVE, - PAGE_EXECUTE_READWRITE)); - if (stubRemote == nullptr) { - throw windows_error("failed to allocate memory for stub"); - } - - // almost there. copy stub to target process - if (!WriteProcessMemory(processHandle, stubRemote, assembler.getBuffer(), stubSize, - &written) || - (written != stubSize)) { - throw windows_error("failed to write stub to target process"); - } - - return reinterpret_cast<REGWORD>(stubRemote); -} - -void InjectDLLEIP(HANDLE processHandle, HANDLE threadHandle, LPCWSTR dllName, - LPCSTR initFunction, LPCVOID userData, size_t userDataSize, - bool skipInit) -{ - threadHandle = - OpenThread((THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME), - FALSE, GetThreadId(threadHandle)); - - if (threadHandle == nullptr) { - throw windows_error("failed to open thread"); - } - - CONTEXT threadContext; - threadContext.ContextFlags = CONTEXT_CONTROL; - - // documentation says starting with Win7 SP1 you HAVE to call SetXStateFeaturesMask - HMODULE k32mod = ::LoadLibrary(__TEXT("kernel32.dll")); - if (k32mod == nullptr) { - throw windows_error("failed to load kernel32.dll"); - } - TSetXStateFeaturesMaskType sxsfm = reinterpret_cast<TSetXStateFeaturesMaskType>( - GetProcAddress(k32mod, "SetXStateFeaturesMask")); - if (sxsfm != nullptr) { - sxsfm(&threadContext, 0); - } - ::FreeLibrary(k32mod); - - if (GetThreadContext(threadHandle, &threadContext) == 0) { - throw windows_error("failed to access thread context."); - } - -#if BOOST_ARCH_X86_64 - REGWORD returnAddress = threadContext.Rip; -#else - REGWORD returnAddress = threadContext.Eip; -#endif - - REGWORD stubAddress = - WriteInjectionStub(processHandle, dllName, initFunction, userData, userDataSize, - skipInit, returnAddress); - - // make the stub the new next thing for the thread to execute -#if BOOST_ARCH_X86_64 - threadContext.Rip = stubAddress; -#else - threadContext.Eip = stubAddress; -#endif - - if (SetThreadContext(threadHandle, &threadContext) == 0) { - throw windows_error("failed to overwrite thread context"); - } -} - -void InjectDLLRemoteThread(HANDLE processHandle, LPCWSTR dllName, LPCSTR initFunction, - LPCVOID userData, size_t userDataSize, bool skipInit) -{ - REGWORD stubAddress = WriteInjectionStub(processHandle, dllName, initFunction, - userData, userDataSize, skipInit, 0); - - DWORD threadId = 0UL; - HANDLE threadHandle = CreateRemoteThread( - processHandle, nullptr, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(stubAddress), - nullptr, 0, &threadId); - if (threadHandle == nullptr) { - throw windows_error("failed to start remote thread"); - } - ResumeThread(threadHandle); - spdlog::get("usvfs")->info("waiting for {0:x} to complete", - GetThreadId(threadHandle)); - ::WaitForSingleObject(threadHandle, 100); - ::CloseHandle(threadHandle); -} - -void InjectLib::InjectDLL(HANDLE processHandle, HANDLE threadHandle, LPCWSTR dllName, - LPCSTR initFunction, LPCVOID userData, size_t userDataSize, - bool skipInit) -{ - namespace bfs = boost::filesystem; - if (!exists(bfs::path(dllName))) { - USVFS_THROW_EXCEPTION(file_not_found_error() - << ex_msg(string_cast<std::string>(dllName))); - } - if (threadHandle == INVALID_HANDLE_VALUE) { -#pragma message( \ - "doesn't seem to work as usvfs causes an exception in the first static initialization or pretty much on any function call. Because process is in different session? CRT related?") - /* - InjectDLLRemoteThread(processHandle, dllName, - initFunction, userData, userDataSize, skipInit); - */ - - DWORD pid = GetProcessId(processHandle); - HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); - THREADENTRY32 threadInfo; - threadInfo.dwSize = sizeof(THREADENTRY32); - BOOL moreThreads = Thread32First(snapshot, &threadInfo); - std::vector<HANDLE> threadHandles; - HANDLE injectThread = INVALID_HANDLE_VALUE; - FILETIME injectThreadTime; - spdlog::get("usvfs")->info("inject dll to process {0}", pid); - while (moreThreads) { - if (threadInfo.th32OwnerProcessID == pid) { - HANDLE thread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, threadInfo.th32ThreadID); - - if (thread != nullptr) { - DWORD suspCount = SuspendThread(thread); - if (suspCount == 0) { - FILETIME creationTime, exitTime, kernelTime, userTime; - ::GetThreadTimes(thread, &creationTime, &exitTime, &kernelTime, &userTime); - - if ((injectThread == INVALID_HANDLE_VALUE) || - (CompareFileTime(&creationTime, &injectThreadTime) < 0)) { - spdlog::get("usvfs")->info("candidate for oldest thread: {0}", - threadInfo.th32ThreadID); - injectThread = thread; - injectThreadTime = creationTime; - } - } - threadHandles.push_back(thread); - } - } - moreThreads = Thread32Next(snapshot, &threadInfo); - } - if (injectThread != INVALID_HANDLE_VALUE) { - spdlog::get("usvfs")->debug("going to inject dll"); - InjectDLLEIP(processHandle, injectThread, dllName, initFunction, userData, - userDataSize, skipInit); - } else { - spdlog::get("usvfs")->critical("found no thread to use for injecting"); - } - - for (HANDLE hdl : threadHandles) { - spdlog::get("usvfs")->info("resuming thread {0}", ::GetThreadId(hdl)); - ResumeThread(hdl); - CloseHandle(hdl); - } - CloseHandle(snapshot); - } else { - InjectDLLEIP(processHandle, threadHandle, dllName, initFunction, userData, - userDataSize, skipInit); - } -} diff --git a/libs/usvfs/src/tinjectlib/injectlib.h b/libs/usvfs/src/tinjectlib/injectlib.h deleted file mode 100644 index f165667..0000000 --- a/libs/usvfs/src/tinjectlib/injectlib.h +++ /dev/null @@ -1,50 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include <windows_sane.h> - -namespace InjectLib -{ - -/** - * @brief inject a dll into the target process - * @param processHandle handle of the process to inject to - * @param threadHandle handle of the main thread in the process - * @param dllname name/path of the dll to inject. The path can't be longer than MAX_PATH - * characters - * @param initFunction name of the initialization function. Can't be longer than 20 - * characters. If this is null, no function is called. Important: the init function has - * to exist, be exported, take the two userdata parameters and must be __cdecl calling - * convention on 32bit windows! Failing any of these the target process will crash or - * the dll isn't loaded Why __cdecl? Because otherwise we would need .def files to - * export the init function with a GetProcAddress-able function name. - * @param userData data passed to the init function - * @param userDataSize size of the data to be passed - * @param skipInit skip the call to the init function if the named function wasn't found - * in the dll. If false, the target process will crash if the function isn't exported in - * the dll - */ -void InjectDLL(HANDLE processHandle, HANDLE threadHandle, LPCWSTR dllName, - LPCSTR initFunction = nullptr, LPCVOID userData = nullptr, - size_t userDataSize = 0, bool skipInit = false); - -} // namespace InjectLib diff --git a/libs/usvfs/src/usvfs_dll/CMakeLists.txt b/libs/usvfs/src/usvfs_dll/CMakeLists.txt deleted file mode 100644 index 2520b50..0000000 --- a/libs/usvfs/src/usvfs_dll/CMakeLists.txt +++ /dev/null @@ -1,57 +0,0 @@ -cmake_minimum_required(VERSION 3.16) - -include(GNUInstallDirs) - -find_package(Boost CONFIG REQUIRED COMPONENTS thread) -find_package(spdlog CONFIG REQUIRED) - -file(GLOB sources "*.cpp" "*.h") -source_group("" FILES ${sources}) - -file(GLOB hooks "hooks/*.cpp" "hooks/*.h") -source_group("dlls" FILES ${hooks}) - -add_library(usvfs_dll SHARED) -target_sources(usvfs_dll - PRIVATE - ${PROJECT_SOURCE_DIR}/include/usvfs/usvfsparametersprivate.h - ${sources} - ${hooks} - version.rc - PUBLIC - FILE_SET HEADERS - BASE_DIRS ${PROJECT_SOURCE_DIR}/include - FILES - ${PROJECT_SOURCE_DIR}/include/usvfs/dllimport.h - ${PROJECT_SOURCE_DIR}/include/usvfs/logging.h - ${PROJECT_SOURCE_DIR}/include/usvfs/sharedparameters.h - ${PROJECT_SOURCE_DIR}/include/usvfs/usvfs_version.h - ${PROJECT_SOURCE_DIR}/include/usvfs/usvfs.h - ${PROJECT_SOURCE_DIR}/include/usvfs/usvfsparameters.h -) -target_link_libraries(usvfs_dll - PRIVATE shared thooklib usvfs_helper - Boost::thread spdlog::spdlog_header_only - Shlwapi) -target_compile_definitions(usvfs_dll PRIVATE BUILDING_USVFS_DLL) -set_target_properties(usvfs_dll - PROPERTIES - ARCHIVE_OUTPUT_NAME usvfs${ARCH_POSTFIX} - ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${USVFS_LIBDIR} - ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${USVFS_LIBDIR} - LIBRARY_OUTPUT_NAME usvfs${ARCH_POSTFIX} - LIBRARY_OUTPUT_DIRECTORY_DEBUG ${USVFS_LIBDIR} - LIBRARY_OUTPUT_DIRECTORY_RELEASE ${USVFS_LIBDIR} - PDB_OUTPUT_DIRECTORY_DEBUG ${USVFS_LIBDIR} - PDB_OUTPUT_DIRECTORY_RELEASE ${USVFS_LIBDIR} - RUNTIME_OUTPUT_NAME usvfs${ARCH_POSTFIX} - RUNTIME_OUTPUT_DIRECTORY_DEBUG ${USVFS_LIBDIR} - RUNTIME_OUTPUT_DIRECTORY_RELEASE ${USVFS_LIBDIR}) - -install(TARGETS usvfs_dll EXPORT usvfs${ARCH_POSTFIX}Targets FILE_SET HEADERS) -install(FILES $<TARGET_PDB_FILE:usvfs_dll> DESTINATION pdb OPTIONAL) -install(EXPORT usvfs${ARCH_POSTFIX}Targets - FILE usvfs${ARCH_POSTFIX}Targets.cmake - NAMESPACE usvfs${ARCH_POSTFIX}:: - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/usvfs -) diff --git a/libs/usvfs/src/usvfs_dll/hookcallcontext.cpp b/libs/usvfs/src/usvfs_dll/hookcallcontext.cpp deleted file mode 100644 index 32510c9..0000000 --- a/libs/usvfs/src/usvfs_dll/hookcallcontext.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include "hookcallcontext.h" -#include "hookcontext.h" -#include <logging.h> - -namespace usvfs -{ - -class HookStack -{ -public: - static HookStack& instance() - { - PreserveGetLastError boostChangesGetLastError; - if (s_Instance.get() == nullptr) { - s_Instance.reset(new HookStack()); - } - return *s_Instance.get(); - } - - bool setGroup(MutExHookGroup group) - { - if (m_ActiveGroups.test(static_cast<size_t>(group)) || - m_ActiveGroups.test(static_cast<size_t>(MutExHookGroup::ALL_GROUPS))) { - return false; - } else { - m_ActiveGroups.set(static_cast<size_t>(group), true); - return true; - } - } - - void unsetGroup(MutExHookGroup group) - { - m_ActiveGroups.set(static_cast<size_t>(group), false); - } - -private: - HookStack() {} - -private: - static boost::thread_specific_ptr<HookStack> s_Instance; - std::bitset<static_cast<size_t>(MutExHookGroup::LAST)> m_ActiveGroups; -}; - -boost::thread_specific_ptr<HookStack> HookStack::s_Instance; - -HookCallContext::HookCallContext() : m_Active(true), m_Group(MutExHookGroup::NO_GROUP) -{ - updateLastError(); -} - -HookCallContext::HookCallContext(MutExHookGroup group) - : m_Active(HookStack::instance().setGroup(group)), m_Group(group) -{ - updateLastError(); -} - -HookCallContext::~HookCallContext() -{ - if (m_Active && (m_Group != MutExHookGroup::NO_GROUP)) { - HookStack::instance().unsetGroup(m_Group); - } - SetLastError(m_LastError); -} - -void HookCallContext::restoreLastError() -{ - SetLastError(m_LastError); -} - -void HookCallContext::updateLastError(DWORD lastError) -{ - m_LastError = lastError; -} - -bool HookCallContext::active() const -{ - return m_Active; -} - -FunctionGroupLock::FunctionGroupLock(MutExHookGroup group) : m_Group(group) -{ - m_Active = HookStack::instance().setGroup(m_Group); -} - -FunctionGroupLock::~FunctionGroupLock() -{ - if (m_Active) { - HookStack::instance().unsetGroup(m_Group); - } -} - -} // namespace usvfs diff --git a/libs/usvfs/src/usvfs_dll/hookcallcontext.h b/libs/usvfs/src/usvfs_dll/hookcallcontext.h deleted file mode 100644 index ebb3bf9..0000000 --- a/libs/usvfs/src/usvfs_dll/hookcallcontext.h +++ /dev/null @@ -1,87 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -namespace usvfs -{ - -/** - * @brief groups of hooks which may be used to implement each other, so only the first - * call should be to one should be manipulated - */ -enum class MutExHookGroup : int -{ - ALL_GROUPS = 0, // An ALL_GROUPS-hook prevents all other hooks from becoming active - // BUT hooks from other groups don't prevent the ALL_GROUPS-hook from - // becoming activated - OPEN_FILE = 1, - CREATE_PROCESS = 2, - FILE_ATTRIBUTES = 3, - FIND_FILES = 4, - LOAD_LIBRARY = 5, - FULL_PATHNAME = 6, - SHELL_FILEOP = 7, - DELETE_FILE = 8, - GET_FILE_VERSION = 9, - GET_MODULE_HANDLE = 10, - SEARCH_FILES = 11, - - NO_GROUP = 12, - LAST = NO_GROUP, -}; - -class HookCallContext -{ - -public: - HookCallContext(); - HookCallContext(MutExHookGroup group); - ~HookCallContext(); - - HookCallContext(const HookCallContext& reference) = delete; - HookCallContext& operator=(const HookCallContext& reference) = delete; - - void restoreLastError(); - - void updateLastError(DWORD lastError = GetLastError()); - - DWORD lastError() const { return m_LastError; } - - bool active() const; - -private: - DWORD m_LastError; - bool m_Active; - MutExHookGroup m_Group; -}; - -class FunctionGroupLock -{ -public: - FunctionGroupLock(MutExHookGroup group); - ~FunctionGroupLock(); - -private: - MutExHookGroup m_Group; - bool m_Active; -}; - -} // namespace usvfs diff --git a/libs/usvfs/src/usvfs_dll/hookcontext.cpp b/libs/usvfs/src/usvfs_dll/hookcontext.cpp deleted file mode 100644 index f47e42d..0000000 --- a/libs/usvfs/src/usvfs_dll/hookcontext.cpp +++ /dev/null @@ -1,330 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include "hookcontext.h" -#include "exceptionex.h" -#include "hookcallcontext.h" -#include "loghelpers.h" -#include "usvfs.h" -#include <shared_memory.h> -#include <sharedparameters.h> -#include <usvfsparameters.h> -#include <winapi.h> - -namespace bi = boost::interprocess; -using usvfs::shared::SharedMemoryT; -using usvfs::shared::VoidAllocatorT; - -using namespace usvfs; -namespace ush = usvfs::shared; - -HookContext* HookContext::s_Instance = nullptr; - -void printBuffer(const char* buffer, size_t size) -{ - static const int bufferSize = 16 * 3; - char temp[bufferSize + 1]; - temp[bufferSize] = '\0'; - - for (size_t i = 0; i < size; ++i) { - size_t offset = i % 16; - _snprintf(&temp[offset * 3], 3, "%02x ", (unsigned char)buffer[i]); - if (offset == 15) { - spdlog::get("hooks")->info("{0:x} - {1}", i - offset, temp); - } - } - - spdlog::get("hooks")->info(temp); -} - -HookContext::HookContext(const usvfsParameters& params, HMODULE module) - : m_ConfigurationSHM(bi::open_or_create, params.instanceName, 64 * 1024), - m_Parameters(retrieveParameters(params)), - m_Tree(m_Parameters->currentSHMName(), - 4 * 1024 * 1024) // 4 MiB empirically covers most small setups without - // need to resize - , - m_InverseTree( - m_Parameters->currentInverseSHMName(), - 128 * 1024) // 128 KiB should cover reverse tree for even larger setups - , - m_DLLModule(module) -{ - if (s_Instance != nullptr) { - throw std::runtime_error("singleton duplicate instantiation (HookContext)"); - } - - const auto userCount = m_Parameters->userConnected(); - - spdlog::get("usvfs")->debug("context current shm: {0} (now {1} connections)", - m_Parameters->currentSHMName(), userCount); - - s_Instance = this; - - if (m_Tree.get() == nullptr) { - USVFS_THROW_EXCEPTION(usage_error() - << ex_msg("shm not found") << ex_msg(params.instanceName)); - } -} - -void HookContext::remove(const char* instanceName) -{ - bi::shared_memory_object::remove(instanceName); -} - -HookContext::~HookContext() -{ - spdlog::get("usvfs")->info("releasing hook context"); - - s_Instance = nullptr; - const auto userCount = m_Parameters->userDisconnected(); - - if (userCount == 0) { - spdlog::get("usvfs")->info("removing tree {}", m_Parameters->instanceName()); - bi::shared_memory_object::remove(m_Parameters->instanceName().c_str()); - } else { - spdlog::get("usvfs")->info("{} users left", userCount); - } -} - -SharedParameters* HookContext::retrieveParameters(const usvfsParameters& params) -{ - std::pair<SharedParameters*, SharedMemoryT::size_type> res = - m_ConfigurationSHM.find<SharedParameters>("parameters"); - - if (res.first == nullptr) { - // not configured yet - spdlog::get("usvfs")->info("create config in {}", ::GetCurrentProcessId()); - - res.first = m_ConfigurationSHM.construct<SharedParameters>("parameters")( - params, VoidAllocatorT(m_ConfigurationSHM.get_segment_manager())); - - if (res.first == nullptr) { - USVFS_THROW_EXCEPTION(bi::bad_alloc()); - } - } else { - spdlog::get("usvfs")->info("access existing config in {}", ::GetCurrentProcessId()); - } - - spdlog::get("usvfs")->info("{} processes", res.first->registeredProcessCount()); - - return res.first; -} - -HookContext::ConstPtr HookContext::readAccess(const char*) -{ - BOOST_ASSERT(s_Instance != nullptr); - - // TODO: this should be a shared mutex! - s_Instance->m_Mutex.wait(200); - return ConstPtr(s_Instance, unlockShared); -} - -HookContext::Ptr HookContext::writeAccess(const char*) -{ - BOOST_ASSERT(s_Instance != nullptr); - - s_Instance->m_Mutex.wait(200); - return Ptr(s_Instance, unlock); -} - -void HookContext::setDebugParameters(LogLevel level, CrashDumpsType dumpType, - const std::string& dumpPath, - std::chrono::milliseconds delayProcess) -{ - m_Parameters->setDebugParameters(level, dumpType, dumpPath, delayProcess); -} - -void HookContext::updateParameters() const -{ - m_Parameters->setSHMNames(m_Tree.shmName(), m_InverseTree.shmName()); -} - -usvfsParameters HookContext::callParameters() const -{ - updateParameters(); - return m_Parameters->makeLocal(); -} - -std::wstring HookContext::dllPath() const -{ - std::wstring path = winapi::wide::getModuleFileName(m_DLLModule); - return boost::filesystem::path(path).parent_path().make_preferred().wstring(); -} - -void HookContext::registerProcess(DWORD pid) -{ - m_Parameters->registerProcess(pid); -} - -void HookContext::unregisterCurrentProcess() -{ - m_Parameters->unregisterProcess(::GetCurrentProcessId()); -} - -std::vector<DWORD> HookContext::registeredProcesses() const -{ - return m_Parameters->registeredProcesses(); -} - -void HookContext::blacklistExecutable(const std::wstring& wexe) -{ - const auto exe = shared::string_cast<std::string>(wexe, shared::CodePage::UTF8); - - spdlog::get("usvfs")->debug("blacklisting '{}'", exe); - m_Parameters->blacklistExecutable(exe); -} - -void HookContext::clearExecutableBlacklist() -{ - spdlog::get("usvfs")->debug("clearing blacklist"); - m_Parameters->clearExecutableBlacklist(); -} - -BOOL HookContext::executableBlacklisted(LPCWSTR wapp, LPCWSTR wcmd) const -{ - std::string app; - if (wapp) { - app = ush::string_cast<std::string>(wapp, ush::CodePage::UTF8); - } - - std::string cmd; - if (wcmd) { - cmd = ush::string_cast<std::string>(wcmd, ush::CodePage::UTF8); - } - - return m_Parameters->executableBlacklisted(app, cmd); -} - -void usvfs::HookContext::addSkipFileSuffix(const std::wstring& fileSuffix) -{ - const auto fsuffix = - shared::string_cast<std::string>(fileSuffix, shared::CodePage::UTF8); - - if (fsuffix.empty()) { - return; - } - - spdlog::get("usvfs")->debug("added skip file suffix '{}'", fsuffix); - m_Parameters->addSkipFileSuffix(fsuffix); -} - -void usvfs::HookContext::clearSkipFileSuffixes() -{ - spdlog::get("usvfs")->debug("clearing skip file suffixes"); - m_Parameters->clearSkipFileSuffixes(); -} - -std::vector<std::string> usvfs::HookContext::skipFileSuffixes() const -{ - return m_Parameters->skipFileSuffixes(); -} - -void usvfs::HookContext::addSkipDirectory(const std::wstring& directory) -{ - const auto dir = shared::string_cast<std::string>(directory, shared::CodePage::UTF8); - - if (dir.empty()) { - return; - } - - spdlog::get("usvfs")->debug("added skip directory '{}'", dir); - m_Parameters->addSkipDirectory(dir); -} - -void usvfs::HookContext::clearSkipDirectories() -{ - spdlog::get("usvfs")->debug("clearing skip directories"); - m_Parameters->clearSkipDirectories(); -} - -std::vector<std::string> usvfs::HookContext::skipDirectories() const -{ - return m_Parameters->skipDirectories(); -} - -void HookContext::forceLoadLibrary(const std::wstring& wprocess, - const std::wstring& wpath) -{ - const auto process = - shared::string_cast<std::string>(wprocess, shared::CodePage::UTF8); - - const auto path = shared::string_cast<std::string>(wpath, shared::CodePage::UTF8); - - spdlog::get("usvfs")->debug("adding forced library '{}' for process '{}'", path, - process); - - m_Parameters->addForcedLibrary(process, path); -} - -void HookContext::clearLibraryForceLoads() -{ - spdlog::get("usvfs")->debug("clearing forced libraries"); - m_Parameters->clearForcedLibraries(); -} - -std::vector<std::wstring> -HookContext::librariesToForceLoad(const std::wstring& processName) -{ - const auto v = m_Parameters->forcedLibraries( - shared::string_cast<std::string>(processName, shared::CodePage::UTF8)); - - std::vector<std::wstring> wv; - for (const auto& s : v) { - wv.push_back(shared::string_cast<std::wstring>(s, shared::CodePage::UTF8)); - } - - return wv; -} - -void HookContext::registerDelayed(std::future<int> delayed) -{ - m_Futures.push_back(std::move(delayed)); -} - -std::vector<std::future<int>>& HookContext::delayed() -{ - return m_Futures; -} - -void HookContext::unlock(HookContext* instance) -{ - instance->m_Mutex.signal(); -} - -void HookContext::unlockShared(const HookContext* instance) -{ - instance->m_Mutex.signal(); -} - -// deprecated -// -extern "C" DLLEXPORT HookContext* __cdecl CreateHookContext( - const USVFSParameters& oldParams, HMODULE module) -{ - const usvfsParameters p(oldParams); - return usvfsCreateHookContext(p, module); -} - -extern "C" DLLEXPORT usvfs::HookContext* WINAPI -usvfsCreateHookContext(const usvfsParameters& params, HMODULE module) -{ - return new HookContext(params, module); -} diff --git a/libs/usvfs/src/usvfs_dll/hookcontext.h b/libs/usvfs/src/usvfs_dll/hookcontext.h deleted file mode 100644 index c9f6eb1..0000000 --- a/libs/usvfs/src/usvfs_dll/hookcontext.h +++ /dev/null @@ -1,224 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include "dllimport.h" -#include "redirectiontree.h" -#include "semaphore.h" -#include "tree_container.h" -#include <directory_tree.h> -#include <exceptionex.h> -#include <usvfsparameters.h> -#include <usvfsparametersprivate.h> -#include <winapi.h> - -namespace usvfs -{ - -class DLLEXPORT SharedParameters; - -/** - * @brief context available to hooks. This is protected by a many-reader - * single-writer mutex - */ -class HookContext -{ - -public: - typedef std::unique_ptr<const HookContext, void (*)(const HookContext*)> ConstPtr; - typedef std::unique_ptr<HookContext, void (*)(HookContext*)> Ptr; - typedef unsigned int DataIDT; - -public: - HookContext(const usvfsParameters& params, HMODULE module); - - HookContext(const HookContext& reference) = delete; - - DLLEXPORT ~HookContext(); - - HookContext& operator=(const HookContext& reference) = delete; - - static void remove(const char* instance); - - /** - * @brief get read access to the context. - * @return smart ptr to the context. mutex will automatically be released when - * this leaves scope - */ - static ConstPtr readAccess(const char* source); - - /** - * @brief get write access to the context. - * @return smart ptr to the context. mutex will automatically be released when - * this leaves scope - */ - static Ptr writeAccess(const char* source); - - /** - * @return table containing file redirection information - */ - RedirectionTreeContainer& redirectionTable() { return m_Tree; } - - /** - * @return table containing file redirection information - */ - const RedirectionTreeContainer& redirectionTable() const { return m_Tree; } - - RedirectionTreeContainer& inverseTable() { return m_InverseTree; } - - const RedirectionTreeContainer& inverseTable() const { return m_InverseTree; } - - /** - * @return the parameters passed in on dll initialisation - */ - usvfsParameters callParameters() const; - - /** - * @return path to the calling library itself - */ - std::wstring dllPath() const; - - /** - * @brief get access to custom data - * @note the caller gains write access to the data, independent on the lock on - * the context - * as a whole. The caller himself has to ensure thread safety - */ - template <typename T> - T& customData(DataIDT id) const - { - auto iter = m_CustomData.find(id); - if (iter == m_CustomData.end()) { - iter = m_CustomData.insert(std::make_pair(id, T())).first; - } - // std::map is supposed to not invalidate any iterators when elements are - // added - // so it should be safe to return a pointer here - T* res = boost::any_cast<T>(&iter->second); - return *res; - } - - void registerProcess(DWORD pid); - void unregisterCurrentProcess(); - std::vector<DWORD> registeredProcesses() const; - - void blacklistExecutable(const std::wstring& executableName); - void clearExecutableBlacklist(); - BOOL executableBlacklisted(LPCWSTR lpApplicationName, LPCWSTR lpCommandLine) const; - - void addSkipFileSuffix(const std::wstring& fileSuffix); - void clearSkipFileSuffixes(); - std::vector<std::string> skipFileSuffixes() const; - - void addSkipDirectory(const std::wstring& directory); - void clearSkipDirectories(); - std::vector<std::string> skipDirectories() const; - - void forceLoadLibrary(const std::wstring& processName, - const std::wstring& libraryPath); - void clearLibraryForceLoads(); - std::vector<std::wstring> librariesToForceLoad(const std::wstring& processName); - - void setDebugParameters(LogLevel level, CrashDumpsType dumpType, - const std::string& dumpPath, - std::chrono::milliseconds delayProcess); - - void updateParameters() const; - - void registerDelayed(std::future<int> delayed); - - std::vector<std::future<int>>& delayed(); - -private: - static void unlock(HookContext* instance); - static void unlockShared(const HookContext* instance); - - SharedParameters* retrieveParameters(const usvfsParameters& params); - -private: - static HookContext* s_Instance; - - shared::SharedMemoryT m_ConfigurationSHM; - SharedParameters* m_Parameters{nullptr}; - RedirectionTreeContainer m_Tree; - RedirectionTreeContainer m_InverseTree; - - std::vector<std::future<int>> m_Futures; - - mutable std::map<DataIDT, boost::any> m_CustomData; - - HMODULE m_DLLModule; - - // mutable std::recursive_mutex m_Mutex; - mutable RecursiveBenaphore m_Mutex; -}; - -} // namespace usvfs - -extern "C" DLLEXPORT usvfs::HookContext* WINAPI -usvfsCreateHookContext(const usvfsParameters& params, HMODULE module); - -class PreserveGetLastError -{ -public: - PreserveGetLastError() : m_err(GetLastError()) {} - ~PreserveGetLastError() { SetLastError(m_err); } - -private: - DWORD m_err; -}; - -// declare an identifier that is guaranteed to be unique across the application -#define DATA_ID(name) static const usvfs::HookContext::DataIDT name = __COUNTER__ - -// set of macros. These ensure a call context is created but most of all these -// ensure exceptions are caught. - -#define READ_CONTEXT() HookContext::readAccess(__MYFUNC__) -#define WRITE_CONTEXT() HookContext::writeAccess(__MYFUNC__) - -#define HOOK_START_GROUP(group) \ - try { \ - HookCallContext callContext(group); - -#define HOOK_START \ - try { \ - HookCallContext callContext; - -#define HOOK_END \ - } \ - catch (const std::exception& e) \ - { \ - spdlog::get("usvfs")->error("exception in {0}: {1}", __MYFUNC__, e.what()); \ - logExtInfo(e); \ - } - -#define HOOK_ENDP(param) \ - } \ - catch (const std::exception& e) \ - { \ - spdlog::get("usvfs")->error("exception in {0} ({1}): {2}", __MYFUNC__, param, \ - e.what()); \ - logExtInfo(e); \ - } - -#define PRE_REALCALL callContext.restoreLastError(); -#define POST_REALCALL callContext.updateLastError(); diff --git a/libs/usvfs/src/usvfs_dll/hookmanager.cpp b/libs/usvfs/src/usvfs_dll/hookmanager.cpp deleted file mode 100644 index 731b856..0000000 --- a/libs/usvfs/src/usvfs_dll/hookmanager.cpp +++ /dev/null @@ -1,333 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include "hookmanager.h" -#include "../thooklib/ttrampolinepool.h" -#include "../thooklib/utility.h" -#include "exceptionex.h" -#include "hooks/kernel32.h" -#include "hooks/ntdll.h" -#include "usvfs.h" -#include <VersionHelpers.h> -#include <directory_tree.h> -#include <logging.h> -#include <shmlogger.h> -#include <usvfsparameters.h> -#include <winapi.h> - -using namespace HookLib; -namespace bf = boost::filesystem; - -namespace usvfs -{ - -HookManager* HookManager::s_Instance = nullptr; - -HookManager::HookManager(const usvfsParameters& params, HMODULE module) - : m_Context(params, module) -{ - if (s_Instance != nullptr) { - throw std::runtime_error("singleton duplicate instantiation (HookManager)"); - } - - s_Instance = this; - - m_Context.registerProcess(::GetCurrentProcessId()); - spdlog::get("usvfs")->info("Process registered in shared process list : {}", - ::GetCurrentProcessId()); - - winapi::ex::OSVersion version = winapi::ex::getOSVersion(); - spdlog::get("usvfs")->info( - "Windows version {}.{}.{} sp {} platform {} ({})", version.major, version.minor, - version.build, version.servicpack, version.platformid, - shared::string_cast<std::string>(winapi::ex::wide::getWindowsBuildLab(true)) - .c_str()); - - initHooks(); - - if (params.debugMode) { - while (!::IsDebuggerPresent()) { - // wait for debugger to attach - ::Sleep(100); - } - } -} - -HookManager::~HookManager() -{ - spdlog::get("hooks")->debug("end hook of process {}", GetCurrentProcessId()); - removeHooks(); - m_Context.unregisterCurrentProcess(); -} - -HookManager& HookManager::instance() -{ - if (s_Instance == nullptr) { - throw std::runtime_error("singleton not instantiated"); - } - - return *s_Instance; -} - -LPVOID HookManager::detour(const char* functionName) -{ - auto iter = m_Hooks.find(functionName); - if (iter != m_Hooks.end()) { - return GetDetour(iter->second); - } else { - return nullptr; - } -} - -void HookManager::removeHook(const std::string& functionName) -{ - auto iter = m_Hooks.find(functionName); - if (iter != m_Hooks.end()) { - try { - RemoveHook(iter->second); - m_Hooks.erase(iter); - spdlog::get("usvfs")->info("removed hook for {}", functionName); - } catch (const std::exception& e) { - spdlog::get("usvfs")->critical("failed to remove hook of {}: {}", functionName, - e.what()); - } - } else { - spdlog::get("usvfs")->info("{} wasn't hooked", functionName); - } -} - -void HookManager::logStubInt(LPVOID address) -{ - if (m_Stubs.find(address) != m_Stubs.end()) { - spdlog::get("hooks")->warn("{0} called", m_Stubs[address]); - } else { - spdlog::get("hooks")->warn("unknown function at {0} called", address); - } -} - -void HookManager::logStub(LPVOID address) -{ - try { - instance().logStubInt(address); - } catch (const std::exception& e) { - spdlog::get("hooks")->debug("function at {0} called after shutdown: {1}", address, - e.what()); - } -} - -void HookManager::installHook(HMODULE module1, HMODULE module2, - const std::string& functionName, LPVOID hook, - LPVOID* fillFuncAddr = nullptr) -{ - BOOST_ASSERT(hook != nullptr); - HOOKHANDLE handle = INVALID_HOOK; - HookError err = ERR_NONE; - LPVOID funcAddr = nullptr; - HMODULE usedModule = nullptr; - // both module1 and module2 are allowed to be null - if (module1 != nullptr) { - funcAddr = MyGetProcAddress(module1, functionName.c_str()); - if (funcAddr != nullptr) { - handle = InstallHook(funcAddr, hook, &err); - } - if (handle != INVALID_HOOK) - usedModule = module1; - } - - if ((handle == INVALID_HOOK) && (module2 != nullptr)) { - funcAddr = MyGetProcAddress(module2, functionName.c_str()); - if (funcAddr != nullptr) { - handle = InstallHook(funcAddr, hook, &err); - } - if (handle != INVALID_HOOK) - usedModule = module2; - } - - if (fillFuncAddr) - *fillFuncAddr = funcAddr; - - if (handle == INVALID_HOOK) { - spdlog::get("usvfs")->error("failed to hook {0}: {1}", functionName, - GetErrorString(err)); - } else { - m_Stubs.insert(make_pair(funcAddr, functionName)); - m_Hooks.insert(make_pair(std::string(functionName), handle)); - spdlog::get("usvfs")->info("hooked {0} ({1}) in {2} type {3}", functionName, - funcAddr, winapi::ansi::getModuleFileName(usedModule), - GetHookType(handle)); - } -} - -void HookManager::installStub(HMODULE module1, HMODULE module2, - const std::string& functionName) -{ - HOOKHANDLE handle = INVALID_HOOK; - HookError err = ERR_NONE; - LPVOID funcAddr = nullptr; - HMODULE usedModule = nullptr; - // both module1 and module2 are allowed to be null - if (module1 != nullptr) { - funcAddr = MyGetProcAddress(module1, functionName.c_str()); - if (funcAddr != nullptr) { - handle = InstallStub(funcAddr, logStub, &err); - } else { - spdlog::get("usvfs")->debug("{} doesn't contain {}", - winapi::ansi::getModuleFileName(module1), - functionName); - } - if (handle != INVALID_HOOK) - usedModule = module1; - } - - if ((handle == INVALID_HOOK) && (module2 != nullptr)) { - funcAddr = MyGetProcAddress(module2, functionName.c_str()); - if (funcAddr != nullptr) { - handle = InstallStub(funcAddr, logStub, &err); - } else { - spdlog::get("usvfs")->debug("{} doesn't contain {}", - winapi::ansi::getModuleFileName(module2), - functionName); - } - if (handle != INVALID_HOOK) - usedModule = module2; - } - - if (handle == INVALID_HOOK) { - spdlog::get("usvfs")->error("failed to stub {0}: {1}", functionName, - GetErrorString(err)); - } else { - m_Stubs.insert(make_pair(funcAddr, functionName)); - m_Hooks.insert(make_pair(std::string(functionName), handle)); - spdlog::get("usvfs")->info("stubbed {0} ({1}) in {2} type {3}", functionName, - funcAddr, winapi::ansi::getModuleFileName(usedModule), - GetHookType(handle)); - } -} - -void HookManager::initHooks() -{ - TrampolinePool::initialize(); - - HookLib::TrampolinePool::instance().setBlock(true); - - HMODULE k32Mod = GetModuleHandleA("kernel32.dll"); - spdlog::get("usvfs")->debug("kernel32.dll at {0:x}", - reinterpret_cast<uintptr_t>(k32Mod)); - // kernelbase.dll contains the actual implementation for functions formerly in - // kernel32.dll and advapi32.dll, starting with Windows 7 - // http://msdn.microsoft.com/en-us/library/windows/desktop/dd371752(v=vs.85).aspx - HMODULE kbaseMod = GetModuleHandleA("kernelbase.dll"); - spdlog::get("usvfs")->debug("kernelbase.dll at {0:x}", - reinterpret_cast<uintptr_t>(kbaseMod)); - - installHook(kbaseMod, k32Mod, "GetFileAttributesExA", hook_GetFileAttributesExA); - installHook(kbaseMod, k32Mod, "GetFileAttributesA", hook_GetFileAttributesA); - installHook(kbaseMod, k32Mod, "GetFileAttributesExW", hook_GetFileAttributesExW); - installHook(kbaseMod, k32Mod, "GetFileAttributesW", hook_GetFileAttributesW); - installHook(kbaseMod, k32Mod, "SetFileAttributesW", hook_SetFileAttributesW); - - installHook(kbaseMod, k32Mod, "CreateDirectoryW", hook_CreateDirectoryW); - installHook(kbaseMod, k32Mod, "RemoveDirectoryW", hook_RemoveDirectoryW); - installHook(kbaseMod, k32Mod, "DeleteFileW", hook_DeleteFileW); - installHook(kbaseMod, k32Mod, "GetCurrentDirectoryA", hook_GetCurrentDirectoryA); - installHook(kbaseMod, k32Mod, "GetCurrentDirectoryW", hook_GetCurrentDirectoryW); - installHook(kbaseMod, k32Mod, "SetCurrentDirectoryA", hook_SetCurrentDirectoryA); - installHook(kbaseMod, k32Mod, "SetCurrentDirectoryW", hook_SetCurrentDirectoryW); - - installHook(kbaseMod, k32Mod, "ExitProcess", hook_ExitProcess); - - installHook(kbaseMod, k32Mod, "CreateProcessInternalW", hook_CreateProcessInternalW, - reinterpret_cast<LPVOID*>(&CreateProcessInternalW)); - - installHook(kbaseMod, k32Mod, "MoveFileA", hook_MoveFileA); - installHook(kbaseMod, k32Mod, "MoveFileW", hook_MoveFileW); - installHook(kbaseMod, k32Mod, "MoveFileExA", hook_MoveFileExA); - installHook(kbaseMod, k32Mod, "MoveFileExW", hook_MoveFileExW); - installHook(kbaseMod, k32Mod, "MoveFileWithProgressA", hook_MoveFileWithProgressA); - installHook(kbaseMod, k32Mod, "MoveFileWithProgressW", hook_MoveFileWithProgressW); - - installHook(kbaseMod, k32Mod, "CopyFileExW", hook_CopyFileExW); - if (IsWindows8OrGreater()) - installHook(kbaseMod, k32Mod, "CopyFile2", hook_CopyFile2, - reinterpret_cast<LPVOID*>(&CopyFile2)); - - installHook(kbaseMod, k32Mod, "GetPrivateProfileStringA", - hook_GetPrivateProfileStringA); - installHook(kbaseMod, k32Mod, "GetPrivateProfileStringW", - hook_GetPrivateProfileStringW); - installHook(kbaseMod, k32Mod, "GetPrivateProfileSectionA", - hook_GetPrivateProfileSectionA); - installHook(kbaseMod, k32Mod, "GetPrivateProfileSectionW", - hook_GetPrivateProfileSectionW); - installHook(kbaseMod, k32Mod, "WritePrivateProfileStringA", - hook_WritePrivateProfileStringA); - installHook(kbaseMod, k32Mod, "WritePrivateProfileStringW", - hook_WritePrivateProfileStringW); - - installHook(kbaseMod, k32Mod, "GetFullPathNameA", hook_GetFullPathNameA); - installHook(kbaseMod, k32Mod, "GetFullPathNameW", hook_GetFullPathNameW); - - installHook(kbaseMod, k32Mod, "FindFirstFileExW", hook_FindFirstFileExW); - - HMODULE ntdllMod = GetModuleHandleA("ntdll.dll"); - spdlog::get("usvfs")->debug("ntdll.dll at {0:x}", - reinterpret_cast<uintptr_t>(ntdllMod)); - installHook(ntdllMod, nullptr, "NtQueryFullAttributesFile", - hook_NtQueryFullAttributesFile); - installHook(ntdllMod, nullptr, "NtQueryAttributesFile", hook_NtQueryAttributesFile); - installHook(ntdllMod, nullptr, "NtQueryDirectoryFile", hook_NtQueryDirectoryFile); - installHook(ntdllMod, nullptr, "NtQueryDirectoryFileEx", hook_NtQueryDirectoryFileEx); - installHook(ntdllMod, nullptr, "NtQueryObject", hook_NtQueryObject); - installHook(ntdllMod, nullptr, "NtQueryInformationFile", hook_NtQueryInformationFile); - installHook(ntdllMod, nullptr, "NtQueryInformationByName", - hook_NtQueryInformationByName); - installHook(ntdllMod, nullptr, "NtOpenFile", hook_NtOpenFile); - installHook(ntdllMod, nullptr, "NtCreateFile", hook_NtCreateFile); - installHook(ntdllMod, nullptr, "NtClose", hook_NtClose); - installHook(ntdllMod, nullptr, "NtTerminateProcess", hook_NtTerminateProcess); - - installHook(kbaseMod, k32Mod, "LoadLibraryExA", hook_LoadLibraryExA); - installHook(kbaseMod, k32Mod, "LoadLibraryExW", hook_LoadLibraryExW); - - // install this hook late as usvfs is calling it itself for debugging purposes - installHook(kbaseMod, k32Mod, "GetModuleFileNameA", hook_GetModuleFileNameA); - installHook(kbaseMod, k32Mod, "GetModuleFileNameW", hook_GetModuleFileNameW); - - spdlog::get("usvfs")->debug("hooks installed"); - HookLib::TrampolinePool::instance().setBlock(false); -} - -void HookManager::removeHooks() -{ - while (m_Hooks.size() > 0) { - auto iter = m_Hooks.begin(); - try { - RemoveHook(iter->second); - spdlog::get("usvfs")->debug("removed hook {}", iter->first); - } catch (const std::exception& e) { - spdlog::get("usvfs")->critical("failed to remove hook: {}", e.what()); - } - - // remove either way, otherwise this is an endless loop - m_Hooks.erase(iter); - } -} - -} // namespace usvfs diff --git a/libs/usvfs/src/usvfs_dll/hookmanager.h b/libs/usvfs/src/usvfs_dll/hookmanager.h deleted file mode 100644 index 636a29c..0000000 --- a/libs/usvfs/src/usvfs_dll/hookmanager.h +++ /dev/null @@ -1,81 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include "hookcontext.h" -#include <hooklib.h> -#include <usvfsparameters.h> - -namespace usvfs -{ - -class HookManager -{ -public: - HookManager(const usvfsParameters& params, HMODULE module); - ~HookManager(); - - HookManager(const HookManager& reference) = delete; - - HookManager& operator=(const HookManager& reference) = delete; - - static HookManager& instance(); - - HookContext* context() { return &m_Context; } - - /// - /// \brief retrieve address of the detour of a function - /// \param functionName name of the function to look up - /// \return function address that can be used to directly execute the original code - /// - LPVOID detour(const char* functionName); - - /// - /// \brief remove the hook on the specified function. - /// \param functionName name of the function to unhook - /// \note This function is only exposed to allow a workaround for ExitProcess and may - /// be - /// removed if a better solution is found there. If you have another legit use - /// case, please let me know! - /// - void removeHook(const std::string& functionName); - -private: - void logStubInt(LPVOID address); - static void logStub(LPVOID address); - - void installHook(HMODULE module1, HMODULE module2, const std::string& functionName, - LPVOID hook, LPVOID* fillFuncAddr); - void installStub(HMODULE module1, HMODULE module2, const std::string& functionName); - void initHooks(); - void removeHooks(); - -private: - static HookManager* s_Instance; - - std::map<std::string, HookLib::HOOKHANDLE> m_Hooks; - - std::map<LPVOID, std::string> m_Stubs; - - HookContext m_Context; -}; - -} // namespace usvfs diff --git a/libs/usvfs/src/usvfs_dll/hooks/file_information_utils.h b/libs/usvfs/src/usvfs_dll/hooks/file_information_utils.h deleted file mode 100644 index dbd3597..0000000 --- a/libs/usvfs/src/usvfs_dll/hooks/file_information_utils.h +++ /dev/null @@ -1,263 +0,0 @@ -#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 deleted file mode 100644 index 6599eb0..0000000 --- a/libs/usvfs/src/usvfs_dll/hooks/kernel32.cpp +++ /dev/null @@ -1,1860 +0,0 @@ -#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 deleted file mode 100644 index 7087b2e..0000000 --- a/libs/usvfs/src/usvfs_dll/hooks/kernel32.h +++ /dev/null @@ -1,116 +0,0 @@ -#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 deleted file mode 100644 index ad53418..0000000 --- a/libs/usvfs/src/usvfs_dll/hooks/ntdll.cpp +++ /dev/null @@ -1,1520 +0,0 @@ -#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 deleted file mode 100644 index f3db4a6..0000000 --- a/libs/usvfs/src/usvfs_dll/hooks/ntdll.h +++ /dev/null @@ -1,57 +0,0 @@ -#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 deleted file mode 100644 index 9a7531c..0000000 --- a/libs/usvfs/src/usvfs_dll/hooks/sharedids.h +++ /dev/null @@ -1,9 +0,0 @@ -#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); diff --git a/libs/usvfs/src/usvfs_dll/maptracker.h b/libs/usvfs/src/usvfs_dll/maptracker.h deleted file mode 100644 index 6ddf471..0000000 --- a/libs/usvfs/src/usvfs_dll/maptracker.h +++ /dev/null @@ -1,691 +0,0 @@ -#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 diff --git a/libs/usvfs/src/usvfs_dll/pch.cpp b/libs/usvfs/src/usvfs_dll/pch.cpp deleted file mode 100644 index 1d9f38c..0000000 --- a/libs/usvfs/src/usvfs_dll/pch.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "pch.h" diff --git a/libs/usvfs/src/usvfs_dll/redirectiontree.cpp b/libs/usvfs/src/usvfs_dll/redirectiontree.cpp deleted file mode 100644 index e521365..0000000 --- a/libs/usvfs/src/usvfs_dll/redirectiontree.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include "redirectiontree.h" - -std::ostream& usvfs::operator<<(std::ostream& stream, const RedirectionData& data) -{ - stream << data.linkTarget; - return stream; -} diff --git a/libs/usvfs/src/usvfs_dll/redirectiontree.h b/libs/usvfs/src/usvfs_dll/redirectiontree.h deleted file mode 100644 index 6a2238a..0000000 --- a/libs/usvfs/src/usvfs_dll/redirectiontree.h +++ /dev/null @@ -1,104 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include <directory_tree.h> - -namespace usvfs -{ - -// typedef boost::interprocess::basic_string<char, std::char_traits<char>, -// shared::CharAllocatorT> StringT; - -namespace shared -{ - static const TreeFlags FLAG_CREATETARGET = FLAG_FIRSTUSERFLAG + 0x00; -} - -struct RedirectionDataLocal -{ - - RedirectionDataLocal(const char* target) : linkTarget(target) {} - - RedirectionDataLocal(const std::string& target) : linkTarget(target) {} - - std::string linkTarget; -}; - -struct RedirectionData -{ - - RedirectionData(const RedirectionData& reference, - const shared::VoidAllocatorT& allocator) - : linkTarget(reference.linkTarget.c_str(), allocator) - {} - - RedirectionData(const RedirectionDataLocal& reference, - const shared::VoidAllocatorT& allocator) - : linkTarget(reference.linkTarget.c_str(), allocator) - {} - - RedirectionData(const char* target, const shared::VoidAllocatorT& allocator) - : linkTarget(target, allocator) - {} - - shared::StringT linkTarget; -}; - -std::ostream& operator<<(std::ostream& stream, const RedirectionData& data); - -template <> -inline void shared::dataAssign<RedirectionData>(RedirectionData& destination, - const RedirectionData& source) -{ - destination.linkTarget.assign(source.linkTarget.c_str()); -} - -template <> -inline RedirectionData -shared::createDataEmpty<RedirectionData>(const VoidAllocatorT& allocator) -{ - return RedirectionData("", allocator); -} - -template <typename T> -struct shared::SHMDataCreator<RedirectionData, T> -{ - static RedirectionData create(T source, const VoidAllocatorT& allocator) - { - return RedirectionData(source, allocator); - } -}; - -template <> -struct shared::SHMDataCreator<RedirectionData, RedirectionData> -{ - static RedirectionData create(const RedirectionData& source, - const VoidAllocatorT& allocator) - { - return RedirectionData(source, allocator); - } -}; - -using RedirectionTree = shared::DirectoryTree<RedirectionData>; -using RedirectionTreeContainer = shared::TreeContainer<RedirectionTree>; - -} // namespace usvfs diff --git a/libs/usvfs/src/usvfs_dll/semaphore.cpp b/libs/usvfs/src/usvfs_dll/semaphore.cpp deleted file mode 100644 index 609b2f5..0000000 --- a/libs/usvfs/src/usvfs_dll/semaphore.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "semaphore.h" -#include "exceptionex.h" - -RecursiveBenaphore::RecursiveBenaphore() : m_Counter(0), m_OwnerId(0UL), m_Recursion(0) -{ - m_Semaphore = ::CreateSemaphore(nullptr, 1, 1, nullptr); -} - -RecursiveBenaphore::~RecursiveBenaphore() -{ - ::CloseHandle(m_Semaphore); -} - -void RecursiveBenaphore::wait(DWORD timeout) -{ - DWORD tid = ::GetCurrentThreadId(); - - if (::_InterlockedIncrement(&m_Counter) > 1) { - if (tid != m_OwnerId) { - int tries = 3; - while (::WaitForSingleObject(m_Semaphore, timeout) != WAIT_OBJECT_0) { - HANDLE owner = ::OpenThread(SYNCHRONIZE, FALSE, m_OwnerId); - ON_BLOCK_EXIT([owner]() { - ::CloseHandle(owner); - }); - if ((tries <= 0) || (::WaitForSingleObject(owner, 0) == WAIT_OBJECT_0)) { - // owner has quit without releasing the semaphore! - m_Recursion = 0; - spdlog::get("usvfs")->error("thread {} never released the mutex", m_OwnerId); - break; - } else { - --tries; - } - } - } - } - m_OwnerId = tid; - ++m_Recursion; -} - -void RecursiveBenaphore::signal() -{ - if (m_Recursion == 0) { - return; - } - // no validation the signaling thread is the one owning the lock - DWORD recursion = --m_Recursion; - if (recursion == 0) { - m_OwnerId = 0; - } - DWORD result = ::_InterlockedDecrement(&m_Counter); - if (result > 0) { - if (recursion == 0) { - ::ReleaseSemaphore(m_Semaphore, 1, nullptr); - } - } -} diff --git a/libs/usvfs/src/usvfs_dll/semaphore.h b/libs/usvfs/src/usvfs_dll/semaphore.h deleted file mode 100644 index 1048c47..0000000 --- a/libs/usvfs/src/usvfs_dll/semaphore.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -// based on code by Jeff Preshing - -// this is a synchronization class that prefers -// undefined behaviour over deadlock. It's utterly broken -// and needs to be replaced in time. - -class RecursiveBenaphore -{ - -public: - RecursiveBenaphore(); - ~RecursiveBenaphore(); - - // wait on the semaphore. after timeout this will check if the current owner - // thread is still alive and steal the semaphore if it isn't. Otherwise this - // will continue to wait. - void wait(DWORD timeout = INFINITE); - void signal(); - -private: - LONG m_Counter; - DWORD m_OwnerId; - int m_Recursion; - HANDLE m_Semaphore; -}; diff --git a/libs/usvfs/src/usvfs_dll/sharedparameters.cpp b/libs/usvfs/src/usvfs_dll/sharedparameters.cpp deleted file mode 100644 index 95cd0b2..0000000 --- a/libs/usvfs/src/usvfs_dll/sharedparameters.cpp +++ /dev/null @@ -1,264 +0,0 @@ -#include <logging.h> -#include <sharedparameters.h> -#include <usvfsparametersprivate.h> - -namespace usvfs -{ - -ForcedLibrary::ForcedLibrary(const std::string& process, const std::string& path, - const shared::VoidAllocatorT& alloc) - : m_processName(process.begin(), process.end(), alloc), - m_libraryPath(path.begin(), path.end(), alloc) -{} - -std::string ForcedLibrary::processName() const -{ - return {m_processName.begin(), m_processName.end()}; -} - -std::string ForcedLibrary::libraryPath() const -{ - return {m_libraryPath.begin(), m_libraryPath.end()}; -} - -SharedParameters::SharedParameters(const usvfsParameters& reference, - const shared::VoidAllocatorT& allocator) - : m_instanceName(reference.instanceName, allocator), - m_currentSHMName(reference.currentSHMName, allocator), - m_currentInverseSHMName(reference.currentInverseSHMName, allocator), - m_debugMode(reference.debugMode), m_logLevel(reference.logLevel), - m_crashDumpsType(reference.crashDumpsType), - m_crashDumpsPath(reference.crashDumpsPath, allocator), - m_delayProcess(reference.delayProcessMs), m_userCount(1), - m_processBlacklist(allocator), m_processList(allocator), - m_fileSuffixSkipList(allocator), m_directorySkipList(allocator), - m_forcedLibraries(allocator) -{} - -usvfsParameters SharedParameters::makeLocal() const -{ - bi::scoped_lock lock(m_mutex); - - return usvfsParameters(m_instanceName.c_str(), m_currentSHMName.c_str(), - m_currentInverseSHMName.c_str(), m_debugMode, m_logLevel, - m_crashDumpsType, m_crashDumpsPath.c_str(), - m_delayProcess.count()); -} - -std::string SharedParameters::instanceName() const -{ - bi::scoped_lock lock(m_mutex); - return {m_instanceName.begin(), m_instanceName.end()}; -} - -std::string SharedParameters::currentSHMName() const -{ - bi::scoped_lock lock(m_mutex); - return {m_currentSHMName.begin(), m_currentSHMName.end()}; -} - -std::string SharedParameters::currentInverseSHMName() const -{ - bi::scoped_lock lock(m_mutex); - return {m_currentInverseSHMName.begin(), m_currentInverseSHMName.end()}; -} - -void SharedParameters::setSHMNames(const std::string& current, - const std::string& inverse) -{ - bi::scoped_lock lock(m_mutex); - - m_currentSHMName.assign(current.begin(), current.end()); - m_currentInverseSHMName.assign(inverse.begin(), inverse.end()); -} - -void SharedParameters::setDebugParameters(LogLevel level, CrashDumpsType dumpType, - const std::string& dumpPath, - std::chrono::milliseconds delayProcess) -{ - bi::scoped_lock lock(m_mutex); - - m_logLevel = level; - m_crashDumpsType = dumpType; - m_crashDumpsPath.assign(dumpPath.begin(), dumpPath.end()); - m_delayProcess = delayProcess; -} - -std::size_t SharedParameters::userConnected() -{ - bi::scoped_lock lock(m_mutex); - return ++m_userCount; -} - -std::size_t SharedParameters::userDisconnected() -{ - bi::scoped_lock lock(m_mutex); - return --m_userCount; -} - -std::size_t SharedParameters::userCount() -{ - bi::scoped_lock lock(m_mutex); - return m_userCount; -} - -std::size_t SharedParameters::registeredProcessCount() const -{ - bi::scoped_lock lock(m_mutex); - return m_processList.size(); -} - -std::vector<DWORD> SharedParameters::registeredProcesses() const -{ - bi::scoped_lock lock(m_mutex); - return {m_processList.begin(), m_processList.end()}; -} - -void SharedParameters::registerProcess(DWORD pid) -{ - bi::scoped_lock lock(m_mutex); - m_processList.insert(pid); -} - -void SharedParameters::unregisterProcess(DWORD pid) -{ - { - bi::scoped_lock lock(m_mutex); - - auto itor = m_processList.find(pid); - - if (itor != m_processList.end()) { - m_processList.erase(itor); - return; - } - } - - spdlog::get("usvfs")->error("cannot unregister process {}, not in list", pid); -} - -void SharedParameters::blacklistExecutable(const std::string& name) -{ - bi::scoped_lock lock(m_mutex); - - m_processBlacklist.insert( - shared::StringT(name.begin(), name.end(), m_processBlacklist.get_allocator())); -} - -void SharedParameters::clearExecutableBlacklist() -{ - bi::scoped_lock lock(m_mutex); - m_processBlacklist.clear(); -} - -bool SharedParameters::executableBlacklisted(const std::string& appName, - const std::string& cmdLine) const -{ - bool blacklisted = false; - std::string log; - - { - bi::scoped_lock lock(m_mutex); - - for (const shared::StringT& sitem : m_processBlacklist) { - const auto item = "\\" + std::string(sitem.begin(), sitem.end()); - - if (!appName.empty()) { - if (boost::algorithm::iends_with(appName, item)) { - blacklisted = true; - log = std::format("application {} is blacklisted", appName); - break; - } - } - - if (!cmdLine.empty()) { - if (boost::algorithm::icontains(cmdLine, item)) { - blacklisted = true; - log = std::format("command line {} is blacklisted", cmdLine); - break; - } - } - } - } - - if (blacklisted) { - spdlog::get("usvfs")->info(log); - return true; - } - - return false; -} - -void SharedParameters::addSkipFileSuffix(const std::string& fileSuffix) -{ - bi::scoped_lock lock(m_mutex); - - m_fileSuffixSkipList.insert(shared::StringT(fileSuffix.begin(), fileSuffix.end(), - m_fileSuffixSkipList.get_allocator())); -} - -void SharedParameters::clearSkipFileSuffixes() -{ - bi::scoped_lock lock(m_mutex); - m_fileSuffixSkipList.clear(); -} - -std::vector<std::string> SharedParameters::skipFileSuffixes() const -{ - bi::scoped_lock lock(m_mutex); - return {m_fileSuffixSkipList.begin(), m_fileSuffixSkipList.end()}; -} - -void SharedParameters::addSkipDirectory(const std::string& directory) -{ - bi::scoped_lock lock(m_mutex); - - m_directorySkipList.insert(shared::StringT(directory.begin(), directory.end(), - m_directorySkipList.get_allocator())); -} - -void SharedParameters::clearSkipDirectories() -{ - bi::scoped_lock lock(m_mutex); - m_directorySkipList.clear(); -} - -std::vector<std::string> SharedParameters::skipDirectories() const -{ - bi::scoped_lock lock(m_mutex); - return {m_directorySkipList.begin(), m_directorySkipList.end()}; -} - -void SharedParameters::addForcedLibrary(const std::string& processName, - const std::string& libraryPath) -{ - bi::scoped_lock lock(m_mutex); - - m_forcedLibraries.push_front( - ForcedLibrary(processName, libraryPath, m_forcedLibraries.get_allocator())); -} - -std::vector<std::string> -SharedParameters::forcedLibraries(const std::string& processName) -{ - std::vector<std::string> v; - - { - bi::scoped_lock lock(m_mutex); - - for (const auto& lib : m_forcedLibraries) { - if (boost::algorithm::iequals(processName, lib.processName())) { - v.push_back(lib.libraryPath()); - } - } - } - - return v; -} - -void SharedParameters::clearForcedLibraries() -{ - bi::scoped_lock lock(m_mutex); - m_forcedLibraries.clear(); -} - -} // namespace usvfs diff --git a/libs/usvfs/src/usvfs_dll/stringcast_boost.h b/libs/usvfs/src/usvfs_dll/stringcast_boost.h deleted file mode 100644 index d2544ec..0000000 --- a/libs/usvfs/src/usvfs_dll/stringcast_boost.h +++ /dev/null @@ -1,42 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include <stringcast.h> - -namespace usvfs -{ -namespace shared -{ - - template <typename ToT, typename CharT, typename Traits, typename Allocator> - class string_cast_impl<ToT, boost::container::basic_string<CharT, Traits, Allocator>> - { - public: - static ToT - cast(const boost::container::basic_string<CharT, Traits, Allocator>& source, - CodePage codePage, size_t sourceLength) - { - return string_cast_impl<ToT, const CharT*>::cast(source.c_str(), codePage, - sourceLength); - } - }; - -} // namespace shared -} // namespace usvfs diff --git a/libs/usvfs/src/usvfs_dll/usvfs.cpp b/libs/usvfs/src/usvfs_dll/usvfs.cpp deleted file mode 100644 index 56d9f71..0000000 --- a/libs/usvfs/src/usvfs_dll/usvfs.cpp +++ /dev/null @@ -1,965 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include "usvfs.h" -#include "hookmanager.h" -#include "loghelpers.h" -#include "redirectiontree.h" -#include "usvfs_version.h" -#include "usvfsparametersprivate.h" -#include <inject.h> -#include <shmlogger.h> -#include <spdlog/sinks/null_sink.h> -#include <spdlog/sinks/stdout_sinks.h> -#include <stringcast.h> -#include <ttrampolinepool.h> -#include <winapi.h> - -// note that there's a mix of boost and std filesystem stuff in this file and -// that they're not completely compatible -#include <filesystem> - -namespace bfs = boost::filesystem; -namespace ush = usvfs::shared; -namespace bip = boost::interprocess; -namespace ba = boost::algorithm; - -using usvfs::log::ConvertLogLevel; - -usvfs::HookManager* manager = nullptr; -usvfs::HookContext* context = nullptr; -HMODULE dllModule = nullptr; -PVOID exceptionHandler = nullptr; -CrashDumpsType usvfs_dump_type = CrashDumpsType::None; -std::wstring usvfs_dump_path; - -// this is called for every single file, so it's a bit long winded, but it's -// as fast as it gets, probably -// -template <std::size_t LongestExtension, std::size_t ExtensionsCount> -bool extensionMatchesCI( - std::string_view name, - const std::array<std::string_view, ExtensionsCount>& extensionsLC, - const std::array<std::string_view, ExtensionsCount>& extensionsUC) -{ - constexpr std::size_t longestExtensionWithDot = LongestExtension + 1; - - // quick check - if (name.size() < longestExtensionWithDot) { - return false; - } - - // for each extension - for (std::size_t i = 0; i < ExtensionsCount; ++i) { - const std::size_t extensionLength = extensionsLC[i].size(); - const std::size_t extensionLengthWithDot = extensionLength + 1; - - // check size - if (name.size() < extensionLengthWithDot) { - continue; - } - - // check dot - if (name[name.size() - extensionLengthWithDot] != '.') { - continue; - } - - // starts at one past the dot - const auto* p = name.data() + name.size() - extensionLength; - - // set to false as soon as a character doesn't match - bool found = true; - - // for each character in extension - for (std::size_t c = 0; c < extensionLength; ++c) { - // checking both lowercase and uppercase - if (*p != extensionsLC[i][c] && *p != extensionsUC[i][c]) { - // neither - found = false; - break; - } - - // matches, check next - ++p; - } - - if (found) { - return true; - } - } - - return false; -} - -bool shouldAddToInverseTree(std::string_view name) -{ - static std::array<std::string_view, 3> extensionsLC{"exe", "dll"}; - static std::array<std::string_view, 3> extensionsUC{"EXE", "DLL"}; - - // must be changed if any extension longer than 3 letters is added - constexpr std::size_t longestExtension = 3; - - return extensionMatchesCI<longestExtension>(name, extensionsLC, extensionsUC); -} - -// -// Logging -// - -void InitLoggingInternal(bool toConsole, bool connectExistingSHM) -{ - try { - if (!toConsole && !SHMLogger::isInstantiated()) { - if (connectExistingSHM) { - SHMLogger::open("usvfs"); - } else { - SHMLogger::create("usvfs"); - } - } - - // a temporary logger was created in DllMain - spdlog::drop("usvfs"); -#pragma message("need a customized name for the shm") - auto logger = spdlog::get("usvfs"); - if (logger.get() == nullptr) { - logger = toConsole ? spdlog::create<spdlog::sinks::stdout_sink_mt>("usvfs") - : spdlog::create<usvfs::sinks::shm_sink>("usvfs", "usvfs"); - logger->set_pattern("%H:%M:%S.%e [%L] %v"); - } - logger->set_level(spdlog::level::debug); - - spdlog::drop("hooks"); - logger = spdlog::get("hooks"); - if (logger.get() == nullptr) { - logger = toConsole ? spdlog::create<spdlog::sinks::stdout_sink_mt>("hooks") - : spdlog::create<usvfs::sinks::shm_sink>("hooks", "usvfs"); - logger->set_pattern("%H:%M:%S.%e <%P:%t> [%L] %v"); - } - logger->set_level(spdlog::level::debug); - } catch (const std::exception&) { - // TODO should really report this - // OutputDebugStringA((boost::format("init exception: %1%\n") % - // e.what()).str().c_str()); - if (spdlog::get("usvfs").get() == nullptr) { - spdlog::create<spdlog::sinks::null_sink_mt>("usvfs"); - } - if (spdlog::get("hooks").get() == nullptr) { - spdlog::create<spdlog::sinks::null_sink_mt>("hooks"); - } - } - - spdlog::get("usvfs")->info("usvfs dll {} initialized in process {}", - USVFS_VERSION_STRING, GetCurrentProcessId()); -} - -void WINAPI usvfsInitLogging(bool toConsole) -{ - InitLoggingInternal(toConsole, false); -} - -extern "C" DLLEXPORT bool WINAPI usvfsGetLogMessages(LPSTR buffer, size_t size, - bool blocking) -{ - buffer[0] = '\0'; - try { - if (blocking) { - SHMLogger::instance().get(buffer, size); - return true; - } else { - return SHMLogger::instance().tryGet(buffer, size); - } - } catch (const std::exception& e) { - _snprintf_s(buffer, size, _TRUNCATE, "Failed to retrieve log messages: %s", - e.what()); - return false; - } -} - -void SetLogLevel(LogLevel level) -{ - spdlog::get("usvfs")->set_level(ConvertLogLevel(level)); - spdlog::get("hooks")->set_level(ConvertLogLevel(level)); -} - -void WINAPI usvfsUpdateParameters(usvfsParameters* p) -{ - spdlog::get("usvfs")->info("updating parameters:\n" - " . debugMode: {}\n" - " . log level: {}\n" - " . dump type: {}\n" - " . dump path: {}\n" - " . delay process: {}ms", - p->debugMode, usvfsLogLevelToString(p->logLevel), - usvfsCrashDumpTypeToString(p->crashDumpsType), - p->crashDumpsPath, p->delayProcessMs); - - // update actual values used: - usvfs_dump_type = p->crashDumpsType; - usvfs_dump_path = - ush::string_cast<std::wstring>(p->crashDumpsPath, ush::CodePage::UTF8); - SetLogLevel(p->logLevel); - - // update parameters in context so spawned process will inherit changes: - context->setDebugParameters(p->logLevel, p->crashDumpsType, p->crashDumpsPath, - std::chrono::milliseconds(p->delayProcessMs)); -} - -// -// Structured Exception handling -// - -std::wstring generate_minidump_name(const wchar_t* dumpPath) -{ - DWORD pid = GetCurrentProcessId(); - wchar_t pname[100]; - if (GetModuleBaseName(GetCurrentProcess(), NULL, pname, _countof(pname)) == 0) - return std::wstring(); - - // find an available name: - wchar_t dmpFile[MAX_PATH]; - int count = 0; - _snwprintf_s(dmpFile, _TRUNCATE, L"%s\\%s-%lu.dmp", dumpPath, pname, pid); - while (winapi::ex::wide::fileExists(dmpFile)) { - if (++count > 99) - return std::wstring(); - _snwprintf_s(dmpFile, _TRUNCATE, L"%s\\%s-%lu_%02d.dmp", dumpPath, pname, pid, - count); - } - return dmpFile; -} - -int createMiniDumpImpl(PEXCEPTION_POINTERS exceptionPtrs, CrashDumpsType type, - const wchar_t* dumpPath, HMODULE dbgDLL) -{ - typedef BOOL(WINAPI * FuncMiniDumpWriteDump)( - HANDLE process, DWORD pid, HANDLE file, MINIDUMP_TYPE dumpType, - const PMINIDUMP_EXCEPTION_INFORMATION exceptionParam, - const PMINIDUMP_USER_STREAM_INFORMATION userStreamParam, - const PMINIDUMP_CALLBACK_INFORMATION callbackParam); - - // notice we avoid logging here on purpose because this is called from the VEHandler - // and the logger can crash it in extreme cases. - // additionally it is also called for MO crashes which use it's own logging. - winapi::ex::wide::createPath(dumpPath); - - auto dmpName = generate_minidump_name(dumpPath); - if (dmpName.empty()) - return 4; - - FuncMiniDumpWriteDump funcDump = reinterpret_cast<FuncMiniDumpWriteDump>( - GetProcAddress(dbgDLL, "MiniDumpWriteDump")); - if (!funcDump) - return 5; - - HANDLE dumpFile = winapi::wide::createFile(dmpName) - .createAlways() - .access(GENERIC_WRITE) - .share(FILE_SHARE_WRITE)(); - if (dumpFile != INVALID_HANDLE_VALUE) { - DWORD dumpType = MiniDumpNormal | MiniDumpWithHandleData | - MiniDumpWithUnloadedModules | MiniDumpWithProcessThreadData; - if (type == CrashDumpsType::Data) - dumpType |= MiniDumpWithDataSegs; - if (type == CrashDumpsType::Full) - dumpType |= MiniDumpWithFullMemory; - - _MINIDUMP_EXCEPTION_INFORMATION exceptionInfo; - exceptionInfo.ThreadId = GetCurrentThreadId(); - exceptionInfo.ExceptionPointers = exceptionPtrs; - exceptionInfo.ClientPointers = FALSE; - - BOOL success = funcDump(GetCurrentProcess(), GetCurrentProcessId(), dumpFile, - static_cast<MINIDUMP_TYPE>(dumpType), &exceptionInfo, - nullptr, nullptr); - - CloseHandle(dumpFile); - - return success ? 0 : 7; - } else - return 6; -} - -int WINAPI usvfsCreateMiniDump(PEXCEPTION_POINTERS exceptionPtrs, CrashDumpsType type, - const wchar_t* dumpPath) -{ - if (type == CrashDumpsType::None) - return 0; - - int res = 1; - if (HMODULE dbgDLL = LoadLibraryW(L"dbghelp.dll")) { - try { - res = createMiniDumpImpl(exceptionPtrs, type, dumpPath, dbgDLL); - } catch (...) { - res = 2; - } - FreeLibrary(dbgDLL); - } - return res; -} - -static bool exceptionInUSVFS(PEXCEPTION_POINTERS exceptionPtrs) -{ - if (!dllModule) // shouldn't happen, check just in case - return true; // create dump to better understand how this could happen - - std::pair<uintptr_t, uintptr_t> range = winapi::ex::getSectionRange(dllModule); - - uintptr_t exceptionAddress = - reinterpret_cast<uintptr_t>(exceptionPtrs->ExceptionRecord->ExceptionAddress); - - return range.first <= exceptionAddress && exceptionAddress < range.second; -} - -LONG WINAPI VEHandler(PEXCEPTION_POINTERS exceptionPtrs) -{ - // NOTICE: don't use logger in VEHandler as it can cause another fault causing - // VEHandler to be called again and so on. - - if ((exceptionPtrs->ExceptionRecord->ExceptionCode < 0x80000000) // non-critical - || - (exceptionPtrs->ExceptionRecord->ExceptionCode == 0xe06d7363)) { // cpp exception - // don't report non-critical exceptions - return EXCEPTION_CONTINUE_SEARCH; - } - /* - if (((exceptionPtrs->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) != 0) - || (exceptionPtrs->ExceptionRecord->ExceptionCode == 0xe06d7363)) { - // don't want to break on non-critical exceptions. 0xe06d7363 indicates a C++ - exception. why are those marked non-continuable? return EXCEPTION_CONTINUE_SEARCH; - } - */ - - // VEHandler is called on "first-chance" exceptions which might be caught and handled. - // Ideally we would like to use an UnhandledExceptionFilter but that fails to catch - // crashes inside our hooks at least on x64, which is the main reason why want a crash - // collection from usvfs. As a workaround/compromise we catch vectored exception but - // only ones that originate directly within the usvfs code: - if (!exceptionInUSVFS(exceptionPtrs)) - return EXCEPTION_CONTINUE_SEARCH; - - // disable our hooking mechanism to increase chances the dump writing won't crash - HookLib::TrampolinePool& trampPool = HookLib::TrampolinePool::instance(); - if (&trampPool) { // need to test this in case of crash before TrampolinePool - // initialized - trampPool.forceUnlockBarrier(); - trampPool.setBlock(true); - } - - usvfsCreateMiniDump(exceptionPtrs, usvfs_dump_type, usvfs_dump_path.c_str()); - - return EXCEPTION_CONTINUE_SEARCH; -} - -// -// Exported functions -// - -void __cdecl InitHooks(LPVOID parameters, size_t) -{ - InitLoggingInternal(false, true); - - const usvfsParameters* params = reinterpret_cast<usvfsParameters*>(parameters); - - // there is already a wait in the constructor of HookManager, but this one is useful - // to debug code here (from experience... ), should not wait twice since the second - // will return true immediately - if (params->debugMode) { - while (!::IsDebuggerPresent()) { - // wait for debugger to attach - ::Sleep(100); - } - } - - usvfs_dump_type = params->crashDumpsType; - usvfs_dump_path = - ush::string_cast<std::wstring>(params->crashDumpsPath, ush::CodePage::UTF8); - - if (params->delayProcessMs > 0) { - ::Sleep(static_cast<unsigned long>(params->delayProcessMs)); - } - - SetLogLevel(params->logLevel); - - if (exceptionHandler == nullptr) { - if (usvfs_dump_type != CrashDumpsType::None) - exceptionHandler = ::AddVectoredExceptionHandler(0, VEHandler); - } else { - spdlog::get("usvfs")->info("vectored exception handler already active"); - // how did this happen?? - } - - spdlog::get("usvfs")->info( - "inithooks called {0} in process {1}:{2} (log level {3}, dump type {4}, dump " - "path {5})", - params->instanceName, winapi::ansi::getModuleFileName(nullptr), - ::GetCurrentProcessId(), static_cast<int>(params->logLevel), - static_cast<int>(params->crashDumpsType), params->crashDumpsPath); - - try { - manager = new usvfs::HookManager(*params, dllModule); - - auto context = manager->context(); - auto exePath = boost::dll::program_location(); - auto libraries = context->librariesToForceLoad(exePath.filename().c_str()); - for (auto library : libraries) { - if (std::filesystem::exists(library)) { - const auto ret = LoadLibraryExW(library.c_str(), NULL, 0); - if (ret) { - spdlog::get("usvfs")->info("inithooks succeeded to force load {0}", - ush::string_cast<std::string>(library).c_str()); - } else { - spdlog::get("usvfs")->critical( - "inithooks failed to force load {0}", - ush::string_cast<std::string>(library).c_str()); - } - } - } - - spdlog::get("usvfs")->info("inithooks in process {0} successful", - ::GetCurrentProcessId()); - - } catch (const std::exception& e) { - spdlog::get("usvfs")->debug("failed to initialise hooks: {0}", e.what()); - } -} - -void WINAPI usvfsGetCurrentVFSName(char* buffer, size_t size) -{ - ush::strncpy_sz(buffer, context->callParameters().currentSHMName, size); -} - -BOOL WINAPI usvfsCreateVFS(const usvfsParameters* p) -{ - usvfs::HookContext::remove(p->instanceName); - return usvfsConnectVFS(p); -} - -BOOL WINAPI usvfsConnectVFS(const usvfsParameters* params) -{ - if (spdlog::get("usvfs").get() == nullptr) { - // create temporary logger so we don't get null-pointer exceptions - spdlog::create<spdlog::sinks::null_sink_mt>("usvfs"); - } - - try { - usvfsDisconnectVFS(); - context = new usvfs::HookContext(*params, dllModule); - - return TRUE; - } catch (const std::exception& e) { - spdlog::get("usvfs")->debug("failed to connect to vfs: {}", e.what()); - return FALSE; - } -} - -void WINAPI usvfsDisconnectVFS() -{ - if (spdlog::get("usvfs").get() == nullptr) { - // create temporary logger so we don't get null-pointer exceptions - spdlog::create<spdlog::sinks::null_sink_mt>("usvfs"); - } - - spdlog::get("usvfs")->debug("remove from process {}", GetCurrentProcessId()); - - if (manager != nullptr) { - delete manager; - manager = nullptr; - } - - if (context != nullptr) { - delete context; - context = nullptr; - spdlog::get("usvfs")->debug("vfs unloaded"); - } -} - -bool processStillActive(DWORD pid) -{ - HANDLE proc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); - - if (proc == nullptr) { - return false; - } - - ON_BLOCK_EXIT([&]() { - if (proc != INVALID_HANDLE_VALUE) - ::CloseHandle(proc); - }); - - DWORD exitCode; - if (!GetExitCodeProcess(proc, &exitCode)) { - spdlog::get("usvfs")->warn("failed to query exit code on process {}: {}", pid, - ::GetLastError()); - return false; - } else { - return exitCode == STILL_ACTIVE; - } -} - -BOOL WINAPI usvfsGetVFSProcessList(size_t* count, LPDWORD processIDs) -{ - if (count == nullptr) { - SetLastError(ERROR_INVALID_PARAMETER); - return FALSE; - } - - if (context == nullptr) { - *count = 0; - } else { - std::vector<DWORD> pids = context->registeredProcesses(); - size_t realCount = 0; - for (DWORD pid : pids) { - if (processStillActive(pid)) { - if ((realCount < *count) && (processIDs != nullptr)) { - processIDs[realCount] = pid; - } - - ++realCount; - } // else the process has already ended - } - *count = realCount; - } - return TRUE; -} - -BOOL WINAPI usvfsGetVFSProcessList2(size_t* count, DWORD** buffer) -{ - if (!count || !buffer) { - SetLastError(ERROR_INVALID_PARAMETER); - return FALSE; - } - - *count = 0; - *buffer = nullptr; - - std::vector<DWORD> pids = context->registeredProcesses(); - auto last = std::remove_if(pids.begin(), pids.end(), [](DWORD id) { - return !processStillActive(id); - }); - - pids.erase(last, pids.end()); - - if (pids.empty()) { - return TRUE; - } - - *count = pids.size(); - *buffer = static_cast<DWORD*>(std::calloc(pids.size(), sizeof(DWORD))); - - std::copy(pids.begin(), pids.end(), *buffer); - - return TRUE; -} - -void WINAPI usvfsClearVirtualMappings() -{ - context->redirectionTable()->clear(); - context->inverseTable()->clear(); -} - -/// ensure the specified path exists. If a physical path of the same name -/// exists, it is inserted into the virtual directory as an empty reference. If -/// the path doesn't exist virtually and can't be cloned from a physical -/// directory, this returns false -/// \todo if this fails (i.e. not all intermediate directories exists) any -/// intermediate directories already created aren't removed -bool assertPathExists(usvfs::RedirectionTreeContainer& table, LPCWSTR path) -{ - bfs::path p(path); - p = p.parent_path(); - - usvfs::RedirectionTree::NodeT* current = table.get(); - - for (auto iter = p.begin(); iter != p.end(); iter = ush::nextIter(iter, p.end())) { - if (current->exists(iter->string().c_str())) { - // subdirectory exists virtually, all good - usvfs::RedirectionTree::NodePtrT found = current->node(iter->string().c_str()); - current = found.get().get(); - } else { - // targetPath is relative to the last rerouted "real" path. This means - // that if virtual c:/foo maps to real c:/windows then creating virtual - // c:/foo/bar will map to real c:/windows/bar - bfs::path targetPath = current->data().linkTarget.size() > 0 - ? bfs::path(current->data().linkTarget.c_str()) / *iter - : *iter / "\\"; - - // is_directory returns false for symlinks and reparse points, - // which causes this function to fail if the target path contains - // either of those. paths containing reparse points is a common - // scenario when running under Wine, so check for those explicitly. - // this check could have a false positive if the path contains a - // symlink to a file, but such a scenario is extremely unlikely. - if (is_directory(targetPath) || is_symlink(targetPath) || - status(targetPath).type() == bfs::file_type::reparse_file) { - usvfs::RedirectionTree::NodePtrT newNode = - table.addDirectory(current->path() / *iter, targetPath.string().c_str(), - ush::FLAG_DUMMY, false); - current = newNode.get().get(); - } else { - spdlog::get("usvfs")->info("{} doesn't exist", targetPath.c_str()); - return false; - } - } - } - - return true; -} - -static bool fileNameInSkipSuffixes(const std::string& fileNameUtf8, - const std::vector<std::string>& skipFileSuffixes) -{ - for (const auto& skipFileSuffix : skipFileSuffixes) { - if (boost::algorithm::iends_with(fileNameUtf8, skipFileSuffix)) { - spdlog::get("usvfs")->debug( - "file '{}' should be skipped, matches file suffix '{}'", fileNameUtf8, - skipFileSuffix); - return true; - } - } - return false; -} - -static bool fileNameInSkipDirectories(const std::string& directoryNameUtf8, - const std::vector<std::string>& skipDirectories) -{ - for (const auto& skipDir : skipDirectories) { - if (boost::algorithm::iequals(directoryNameUtf8, skipDir)) { - spdlog::get("usvfs")->debug("directory '{}' should be skipped", - directoryNameUtf8); - return true; - } - } - return false; -} - -BOOL WINAPI usvfsVirtualLinkFile(LPCWSTR source, LPCWSTR destination, - unsigned int flags) -{ - // TODO difference between winapi and ntdll api regarding system32 vs syswow64 - // (and other windows links?) - try { - if (!assertPathExists(context->redirectionTable(), destination)) { - SetLastError(ERROR_PATH_NOT_FOUND); - return FALSE; - } - - const auto skipFileSuffixes = context->skipFileSuffixes(); - - std::string sourceU8 = ush::string_cast<std::string>(source, ush::CodePage::UTF8); - - // Check if the file should be skipped - if (fileNameInSkipSuffixes(sourceU8, skipFileSuffixes)) { - // return false when we want to fail when the file is skipped - return (flags & LINKFLAG_FAILIFSKIPPED) ? FALSE : TRUE; - } - - auto res = context->redirectionTable().addFile( - bfs::path(destination), usvfs::RedirectionDataLocal(sourceU8), - !(flags & LINKFLAG_FAILIFEXISTS)); - - if (shouldAddToInverseTree(sourceU8)) { - std::string destinationU8 = - ush::string_cast<std::string>(destination, ush::CodePage::UTF8); - - context->inverseTable().addFile(bfs::path(source), - usvfs::RedirectionDataLocal(destinationU8), true); - } - - context->updateParameters(); - - if (res.get() == nullptr) { - // the tree structure currently doesn't provide useful error codes but - // this is currently the only reason - // we would return a nullptr. - SetLastError(ERROR_FILE_EXISTS); - return FALSE; - } else { - return TRUE; - } - } catch (const std::exception& e) { - spdlog::get("usvfs")->error("failed to copy file {}", e.what()); - // TODO: no clue what's wrong - SetLastError(ERROR_INVALID_DATA); - return FALSE; - } -} - -/** - * @brief extract the flags relevant to redirection - */ -static usvfs::shared::TreeFlags convertRedirectionFlags(unsigned int flags) -{ - usvfs::shared::TreeFlags result = 0; - if (flags & LINKFLAG_CREATETARGET) { - result |= usvfs::shared::FLAG_CREATETARGET; - } - return result; -} - -BOOL WINAPI usvfsVirtualLinkDirectoryStatic(LPCWSTR source, LPCWSTR destination, - unsigned int flags) -{ - // TODO change notification not yet implemented - try { - if ((flags & LINKFLAG_FAILIFEXISTS) && winapi::ex::wide::fileExists(destination)) { - SetLastError(ERROR_FILE_EXISTS); - return FALSE; - } - - if (!assertPathExists(context->redirectionTable(), destination)) { - SetLastError(ERROR_PATH_NOT_FOUND); - return FALSE; - } - - std::string sourceU8 = - ush::string_cast<std::string>(source, ush::CodePage::UTF8) + "\\"; - - context->redirectionTable().addDirectory( - destination, usvfs::RedirectionDataLocal(sourceU8), - usvfs::shared::FLAG_DIRECTORY | convertRedirectionFlags(flags), - (flags & LINKFLAG_CREATETARGET) != 0); - - const auto skipDirectories = context->skipDirectories(); - const auto skipFileSuffixes = context->skipFileSuffixes(); - - if ((flags & LINKFLAG_RECURSIVE) != 0) { - std::wstring sourceP(source); - std::wstring sourceW = sourceP + L"\\"; - std::wstring destinationW = std::wstring(destination) + L"\\"; - if (sourceP.length() >= MAX_PATH && !ush::startswith(sourceP.c_str(), LR"(\\?\)")) - sourceP = LR"(\\?\)" + sourceP; - - for (winapi::ex::wide::FileResult file : - winapi::ex::wide::quickFindFiles(sourceP.c_str(), L"*")) { - if (file.attributes & FILE_ATTRIBUTE_DIRECTORY) { - if ((file.fileName != L".") && (file.fileName != L"..")) { - - const auto nameU8 = ush::string_cast<std::string>(file.fileName.c_str(), - ush::CodePage::UTF8); - // Check if the directory should be skipped - if (fileNameInSkipDirectories(nameU8, skipDirectories)) { - // Fail if we desire to fail when a dir/file is skipped - if (flags & LINKFLAG_FAILIFSKIPPED) { - spdlog::get("usvfs")->debug( - "directory '{}' skipped, failing as defined by link flags", nameU8); - return FALSE; - } - - continue; - } - - usvfsVirtualLinkDirectoryStatic((sourceW + file.fileName).c_str(), - (destinationW + file.fileName).c_str(), - flags); - } - } else { - const auto nameU8 = - ush::string_cast<std::string>(file.fileName.c_str(), ush::CodePage::UTF8); - - // Check if the file should be skipped - if (fileNameInSkipSuffixes(nameU8, skipFileSuffixes)) { - // Fail if we desire to fail when a dir/file is skipped - if (flags & LINKFLAG_FAILIFSKIPPED) { - spdlog::get("usvfs")->debug( - "file '{}' skipped, failing as defined by link flags", nameU8); - return FALSE; - } - - continue; - } - - // TODO could save memory here by storing only the file name for the - // source and constructing the full name using the parent directory - context->redirectionTable().addFile( - bfs::path(destination) / nameU8, - usvfs::RedirectionDataLocal(sourceU8 + nameU8), true); - - if (shouldAddToInverseTree(nameU8)) { - std::string destinationU8 = - ush::string_cast<std::string>(destination, ush::CodePage::UTF8) + "\\"; - - context->inverseTable().addFile( - bfs::path(source) / nameU8, - usvfs::RedirectionDataLocal(destinationU8 + nameU8), true); - } - } - } - } - - context->updateParameters(); - - return TRUE; - } catch (const std::exception& e) { - spdlog::get("usvfs")->error("failed to copy file {}", e.what()); - // TODO: no clue what's wrong - SetLastError(ERROR_INVALID_DATA); - return FALSE; - } -} - -BOOL WINAPI usvfsCreateProcessHooked(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, - LPSECURITY_ATTRIBUTES lpProcessAttributes, - LPSECURITY_ATTRIBUTES lpThreadAttributes, - BOOL bInheritHandles, DWORD dwCreationFlags, - LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, - LPSTARTUPINFOW lpStartupInfo, - LPPROCESS_INFORMATION lpProcessInformation) -{ - BOOL susp = dwCreationFlags & CREATE_SUSPENDED; - DWORD flags = dwCreationFlags | CREATE_SUSPENDED; - - BOOL blacklisted = context->executableBlacklisted(lpApplicationName, lpCommandLine); - - BOOL res = CreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes, - lpThreadAttributes, bInheritHandles, flags, lpEnvironment, - lpCurrentDirectory, lpStartupInfo, lpProcessInformation); - if (!res) { - spdlog::get("usvfs")->error("failed to spawn {}", - ush::string_cast<std::string>(lpCommandLine)); - return FALSE; - } - - if (!blacklisted) { - std::wstring applicationDirPath = winapi::wide::getModuleFileName(dllModule); - boost::filesystem::path p(applicationDirPath); - try { - usvfs::injectProcess(p.parent_path().wstring(), context->callParameters(), - *lpProcessInformation); - } catch (const std::exception& e) { - spdlog::get("usvfs")->error("failed to inject: {}", e.what()); - logExtInfo(e, LogLevel::Error); - ::TerminateProcess(lpProcessInformation->hProcess, 1); - ::SetLastError(ERROR_INVALID_PARAMETER); - return FALSE; - } - } - - if (!susp) { - ResumeThread(lpProcessInformation->hThread); - } - - return TRUE; -} - -BOOL WINAPI usvfsCreateVFSDump(LPSTR buffer, size_t* size) -{ - assert(size != nullptr); - std::ostringstream output; - usvfs::shared::dumpTree(output, *context->redirectionTable().get()); - std::string str = output.str(); - if ((buffer != NULL) && (*size > 0)) { - strncpy_s(buffer, *size, str.c_str(), _TRUNCATE); - } - bool success = *size >= str.length(); - *size = str.length(); - return success ? TRUE : FALSE; -} - -VOID WINAPI usvfsBlacklistExecutable(LPCWSTR executableName) -{ - context->blacklistExecutable(executableName); -} - -VOID WINAPI usvfsClearExecutableBlacklist() -{ - context->clearExecutableBlacklist(); -} - -VOID WINAPI usvfsAddSkipFileSuffix(LPCWSTR fileSuffix) -{ - context->addSkipFileSuffix(fileSuffix); -} - -VOID WINAPI usvfsClearSkipFileSuffixes() -{ - context->clearSkipFileSuffixes(); -} - -VOID WINAPI usvfsAddSkipDirectory(LPCWSTR directory) -{ - context->addSkipDirectory(directory); -} - -VOID WINAPI usvfsClearSkipDirectories() -{ - context->clearSkipDirectories(); -} - -VOID WINAPI usvfsForceLoadLibrary(LPCWSTR processName, LPCWSTR libraryPath) -{ - context->forceLoadLibrary(processName, libraryPath); -} - -VOID WINAPI usvfsClearLibraryForceLoads() -{ - context->clearLibraryForceLoads(); -} - -VOID WINAPI usvfsPrintDebugInfo() -{ - spdlog::get("usvfs")->warn("===== debug {} =====", - context->redirectionTable().shmName()); - void* buffer = nullptr; - size_t bufferSize = 0; - context->redirectionTable().getBuffer(buffer, bufferSize); - std::ostringstream temp; - for (size_t i = 0; i < bufferSize; ++i) { - temp << std::hex << std::setfill('0') << std::setw(2) - << (unsigned)reinterpret_cast<char*>(buffer)[i] << " "; - if ((i % 16) == 15) { - spdlog::get("usvfs")->info("{}", temp.str()); - temp.str(""); - temp.clear(); - } - } - if (!temp.str().empty()) { - spdlog::get("usvfs")->info("{}", temp.str()); - } - spdlog::get("usvfs")->warn("===== / debug {} =====", - context->redirectionTable().shmName()); -} - -const char* WINAPI usvfsVersionString() -{ - return USVFS_VERSION_STRING; -} - -// -// DllMain -// - -BOOL APIENTRY DllMain(HMODULE module, DWORD reasonForCall, LPVOID) -{ - switch (reasonForCall) { - case DLL_PROCESS_ATTACH: { - dllModule = module; - } break; - case DLL_PROCESS_DETACH: { - if (exceptionHandler) - ::RemoveVectoredExceptionHandler(exceptionHandler); - } break; - case DLL_THREAD_ATTACH: { - } break; - case DLL_THREAD_DETACH: { - } break; - } - - return TRUE; -} diff --git a/libs/usvfs/src/usvfs_dll/usvfsparameters.cpp b/libs/usvfs/src/usvfs_dll/usvfsparameters.cpp deleted file mode 100644 index 7059ec2..0000000 --- a/libs/usvfs/src/usvfs_dll/usvfsparameters.cpp +++ /dev/null @@ -1,193 +0,0 @@ -#include "usvfsparametersprivate.h" -#include <algorithm> - -usvfsParameters::usvfsParameters() - : debugMode(false), logLevel(LogLevel::Debug), crashDumpsType(CrashDumpsType::None), - delayProcessMs(0) -{ - std::fill(std::begin(instanceName), std::end(instanceName), 0); - std::fill(std::begin(currentSHMName), std::end(currentSHMName), 0); - std::fill(std::begin(currentInverseSHMName), std::end(currentInverseSHMName), 0); - std::fill(std::begin(crashDumpsPath), std::end(crashDumpsPath), 0); -} - -usvfsParameters::usvfsParameters(const char* instanceName, const char* currentSHMName, - const char* currentInverseSHMName, bool debugMode, - LogLevel logLevel, CrashDumpsType crashDumpsType, - const char* crashDumpsPath, int delayProcessMs) - : usvfsParameters() -{ - strncpy_s(this->instanceName, instanceName, _TRUNCATE); - strncpy_s(this->currentSHMName, currentSHMName, _TRUNCATE); - strncpy_s(this->currentInverseSHMName, currentInverseSHMName, _TRUNCATE); - this->debugMode = debugMode; - this->logLevel = logLevel; - this->crashDumpsType = crashDumpsType; - strncpy_s(this->crashDumpsPath, crashDumpsPath, _TRUNCATE); - this->delayProcessMs = delayProcessMs; -} - -usvfsParameters::usvfsParameters(const USVFSParameters& oldParams) - : usvfsParameters(oldParams.instanceName, oldParams.currentSHMName, - oldParams.currentInverseSHMName, oldParams.debugMode, - oldParams.logLevel, oldParams.crashDumpsType, - oldParams.crashDumpsPath, 0) -{} - -void usvfsParameters::setInstanceName(const char* name) -{ - strncpy_s(instanceName, name, _TRUNCATE); - strncpy_s(currentSHMName, 60, name, _TRUNCATE); - memset(currentInverseSHMName, '\0', _countof(currentInverseSHMName)); - _snprintf(currentInverseSHMName, 60, "inv_%s", name); -} - -void usvfsParameters::setDebugMode(bool b) -{ - debugMode = b; -} - -void usvfsParameters::setLogLevel(LogLevel level) -{ - logLevel = level; -} - -void usvfsParameters::setCrashDumpType(CrashDumpsType type) -{ - crashDumpsType = type; -} - -void usvfsParameters::setCrashDumpPath(const char* path) -{ - if (path && *path && strlen(path) < _countof(crashDumpsPath)) { - memcpy(crashDumpsPath, path, strlen(path) + 1); - } else { - // crashDumpsPath invalid or overflow of USVFSParameters variable so disable - // crash dumps: - crashDumpsPath[0] = 0; - crashDumpsType = CrashDumpsType::None; - } -} - -void usvfsParameters::setProcessDelay(int milliseconds) -{ - delayProcessMs = milliseconds; -} - -extern "C" -{ - - const char* usvfsLogLevelToString(LogLevel lv) - { - switch (lv) { - case LogLevel::Debug: - return "debug"; - - case LogLevel::Info: - return "info"; - - case LogLevel::Warning: - return "warning"; - - case LogLevel::Error: - return "error"; - - default: - return "unknown"; - } - } - - const char* usvfsCrashDumpTypeToString(CrashDumpsType t) - { - switch (t) { - case CrashDumpsType::None: - return "none"; - - case CrashDumpsType::Mini: - return "mini"; - - case CrashDumpsType::Data: - return "data"; - - case CrashDumpsType::Full: - return "full"; - - default: - return "unknown"; - } - } - - usvfsParameters* usvfsCreateParameters() - { - return new (std::nothrow) usvfsParameters; - } - - usvfsParameters* usvfsDupeParameters(usvfsParameters* p) - { - if (!p) { - return nullptr; - } - - auto* dupe = usvfsCreateParameters(); - if (!dupe) { - return nullptr; - } - - *dupe = *p; - - return dupe; - } - - void usvfsCopyParameters(const usvfsParameters* source, usvfsParameters* dest) - { - *dest = *source; - } - - void usvfsFreeParameters(usvfsParameters* p) - { - delete p; - } - - void usvfsSetInstanceName(usvfsParameters* p, const char* name) - { - if (p) { - p->setInstanceName(name); - } - } - - void usvfsSetDebugMode(usvfsParameters* p, BOOL debugMode) - { - if (p) { - p->setDebugMode(debugMode); - } - } - - void usvfsSetLogLevel(usvfsParameters* p, LogLevel level) - { - if (p) { - p->setLogLevel(level); - } - } - - void usvfsSetCrashDumpType(usvfsParameters* p, CrashDumpsType type) - { - if (p) { - p->setCrashDumpType(type); - } - } - - void usvfsSetCrashDumpPath(usvfsParameters* p, const char* path) - { - if (p) { - p->setCrashDumpPath(path); - } - } - - void usvfsSetProcessDelay(usvfsParameters* p, int milliseconds) - { - if (p) { - p->setProcessDelay(milliseconds); - } - } - -} // extern "C" diff --git a/libs/usvfs/src/usvfs_dll/version.rc b/libs/usvfs/src/usvfs_dll/version.rc deleted file mode 100644 index 7dbf8e3..0000000 --- a/libs/usvfs/src/usvfs_dll/version.rc +++ /dev/null @@ -1,40 +0,0 @@ -#include "Winver.h" -#include "..\..\include\usvfs\usvfs_version.h" - -#define VER_FILEVERSION USVFS_VERSION_MAJOR,USVFS_VERSION_MINOR,USVFS_VERSION_BUILD,USVFS_VERSION_REVISION -#define VER_FILEVERSION_STR USVFS_VERSION_STRING - -#define VER_PRODUCTVERSION USVFS_VERSION_MAJOR,USVFS_VERSION_MINOR,USVFS_VERSION_BUILD,USVFS_VERSION_REVISION -#define VER_PRODUCTVERSION_STR USVFS_VERSION_STRING - -VS_VERSION_INFO VERSIONINFO -FILEVERSION VER_FILEVERSION -PRODUCTVERSION VER_PRODUCTVERSION -FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -FILEFLAGS (0) -FILEOS VOS__WINDOWS32 -FILETYPE VFT_DLL -FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904B0" - BEGIN - VALUE "FileVersion", VER_FILEVERSION_STR - VALUE "CompanyName", "Mod Organizer 2 Team\0" - VALUE "FileDescription", "USVFS\0" -#ifdef _WIN64 - VALUE "OriginalFilename", "usvfs_x64.dll\0" -#else - VALUE "OriginalFilename", "usvfs_x86.dll\0" -#endif - VALUE "ProductName", "USVFS\0" - VALUE "ProductVersion", VER_PRODUCTVERSION_STR - END - END - - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x0409L, 1200 - END -END diff --git a/libs/usvfs/src/usvfs_helper/CMakeLists.txt b/libs/usvfs/src/usvfs_helper/CMakeLists.txt deleted file mode 100644 index 5e083d9..0000000 --- a/libs/usvfs/src/usvfs_helper/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -cmake_minimum_required(VERSION 3.16) - -add_library(usvfs_helper STATIC inject.h inject.cpp) -target_link_libraries(usvfs_helper PUBLIC shared PRIVATE tinjectlib) -target_include_directories(usvfs_helper PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -set_target_properties(usvfs_helper PROPERTIES FOLDER injection) diff --git a/libs/usvfs/src/usvfs_helper/inject.cpp b/libs/usvfs/src/usvfs_helper/inject.cpp deleted file mode 100644 index bb1cb58..0000000 --- a/libs/usvfs/src/usvfs_helper/inject.cpp +++ /dev/null @@ -1,193 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#include <string> -#include <utility> - -#include <spdlog/spdlog.h> - -#include <boost/filesystem.hpp> - -#include "inject.h" -#include <exceptionex.h> -#include <formatters.h> -#include <injectlib.h> -#include <loghelpers.h> -#include <pch.h> -#include <stringcast.h> -#include <stringutils.h> -#include <usvfsparametersprivate.h> -#include <winapi.h> - -namespace ush = usvfs::shared; - -using namespace winapi; - -void usvfs::injectProcess(const std::wstring& applicationPath, - const usvfsParameters& parameters, - const PROCESS_INFORMATION& processInfo) -{ - injectProcess(applicationPath, parameters, processInfo.hProcess, processInfo.hThread); -} - -void usvfs::injectProcess(const std::wstring& applicationPath, - const usvfsParameters& parameters, HANDLE processHandle, - HANDLE threadHandle) -{ - bool proc64 = false; - bool sameBitness = false; - { - SYSTEM_INFO info; - GetSystemInfo(&info); - BOOL wow64; - IsWow64Process(processHandle, &wow64); - if (wow64) { - // process is running under wow64 so it has to be a 32bit process running on 64bit - // windows - proc64 = false; - BOOL temp; - IsWow64Process(GetCurrentProcess(), &temp); - sameBitness = temp == TRUE; - } else { - BOOL selfWow64; - IsWow64Process(GetCurrentProcess(), &selfWow64); - if (selfWow64) { - // WE are a 32 bit process running on 64bit windows. the other process isn't, so - // its 64bit - proc64 = true; - } else { - sameBitness = true; - // we have the same bitness as that other process, but which is it? -#ifdef _WIN64 - proc64 = true; -#else - proc64 = false; -#endif - } - } - } - boost::filesystem::path binPath = boost::filesystem::path(applicationPath); - spdlog::get("usvfs")->info("injecting to process {} with {} bitness", - ::GetProcessId(processHandle), - sameBitness ? "same" : "different"); - - if (sameBitness) { - static constexpr auto USVFS_DLL = -#ifdef _WIN64 - L"usvfs_x64.dll"; -#else - L"usvfs_x86.dll"; -#endif - const auto& preferedDll = binPath / USVFS_DLL; - boost::filesystem::path dllPath = preferedDll; - bool dllFound = boost::filesystem::exists(dllPath); - // support for runing tests using a usvfs dll in lib folder (and proxy under bin): - if (!dllFound && binPath.filename() == L"bin") { - dllPath = binPath.parent_path() / L"lib" / USVFS_DLL; - dllFound = boost::filesystem::exists(dllPath); - } - if (!dllFound) { - USVFS_THROW_EXCEPTION( - file_not_found_error() - << ex_msg(std::string("dll missing: ") + - ush::string_cast<std::string>(preferedDll.wstring()).c_str())); - } - - spdlog::get("usvfs")->info("dll path: {}", dllPath.wstring()); - - InjectLib::InjectDLL(processHandle, threadHandle, dllPath.c_str(), "InitHooks", - ¶meters, sizeof(parameters)); - - spdlog::get("usvfs")->info("injection to same bitness process {} successful", - ::GetProcessId(processHandle)); - } else { - // first try platform specific proxy exe: - static constexpr auto USVFS_PREFERED_EXE = -#ifdef _WIN64 - L"usvfs_proxy_x86.exe"; -#else - L"usvfs_proxy_x64.exe"; -#endif - const auto& preferedExe = binPath / USVFS_PREFERED_EXE; - boost::filesystem::path exePath = preferedExe; - bool exeFound = boost::filesystem::exists(exePath); - // support for runing tests using a usvfs dll in lib folder (and proxy under bin): - if (!exeFound && binPath.filename() == L"lib") { - exePath = binPath.parent_path() / L"bin" / USVFS_PREFERED_EXE; - exeFound = boost::filesystem::exists(exePath); - } - // finally fallback to old proxy naming (but only for 64bit as we don't have a 64bit - // proxy in this case): -#ifdef _WIN64 - if (!exeFound) { - exePath = binPath / L"usvfs_proxy.exe"; - exeFound = boost::filesystem::exists(exePath); - } -#endif - if (!exeFound) { - USVFS_THROW_EXCEPTION(file_not_found_error() << ex_msg( - std::string("usvfs proxy not found: ") + - ush::string_cast<std::string>(preferedExe.wstring()))); - } else - spdlog::get("usvfs")->info("using usvfs proxy: {}", - ush::string_cast<std::string>(preferedExe.wstring())); - // need to use proxy aplication to inject - auto proxyProcess = - std::move(wide::createProcess(exePath.wstring()) - .arg(L"--instance") - .arg(ush::string_cast<std::wstring>(parameters.instanceName)) - .arg(L"--pid") - .arg(GetProcessId(processHandle))); - - if (threadHandle != INVALID_HANDLE_VALUE) { - proxyProcess.arg("--tid").arg(GetThreadId(threadHandle)); - } - process::Result result = proxyProcess(); - if (!result.valid) { - USVFS_THROW_EXCEPTION(unknown_error() - << ex_msg(std::string("failed to start proxy ") + - ush::string_cast<std::string>(exePath.wstring())) - << ex_win_errcode(result.errorCode)); - } else { - // wait for proxy completion. this shouldn't take long, 15 seconds is very - // generous - switch (WaitForSingleObject(result.processInfo.hProcess, 15000)) { - case WAIT_TIMEOUT: { - spdlog::get("usvfs")->debug("proxy timeout"); - TerminateProcess(result.processInfo.hProcess, 1); - USVFS_THROW_EXCEPTION(timeout_error() - << ex_msg(std::string("proxy didn't complete in time"))); - } break; - case WAIT_FAILED: { - spdlog::get("usvfs")->debug("proxy wait failed"); - TerminateProcess(result.processInfo.hProcess, 1); - USVFS_THROW_EXCEPTION(unknown_error() - << ex_msg( - std::string("failed to wait for proxy completion")) - << ex_win_errcode(result.errorCode)); - } break; - default: { - spdlog::get("usvfs")->debug("proxy run successful"); - // nop - } break; - } - } - } -} diff --git a/libs/usvfs/src/usvfs_helper/inject.h b/libs/usvfs/src/usvfs_helper/inject.h deleted file mode 100644 index 10f179f..0000000 --- a/libs/usvfs/src/usvfs_helper/inject.h +++ /dev/null @@ -1,51 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once - -#include "usvfsparameters.h" -#include <string> -#include <windows_sane.h> - -namespace usvfs -{ - -/** - * @brief inject usvfs to a process - * @param applicationPath - * @param parameters - * @param processInfo - */ -void injectProcess(const std::wstring& applicationPath, - const usvfsParameters& parameters, - const PROCESS_INFORMATION& processInfo); - -/** - * @brief inject usvfs to a process - * @param applicationPath path to usvfs - * @param parameters - * @param process process handle to inject to - * @param thread main thread inside that process. This can be set to - * INVALID_HANDLE_VALUE in which case a new thread is created in the process - */ -void injectProcess(const std::wstring& applicationPath, - const usvfsParameters& parameters, HANDLE process, HANDLE thread); - -} // namespace usvfs diff --git a/libs/usvfs/src/usvfs_proxy/CMakeLists.txt b/libs/usvfs/src/usvfs_proxy/CMakeLists.txt deleted file mode 100644 index 47614ee..0000000 --- a/libs/usvfs/src/usvfs_proxy/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -cmake_minimum_required(VERSION 3.16) - -find_package(Boost CONFIG REQUIRED COMPONENTS filesystem) - -add_executable(usvfs_proxy main.cpp version.rc) -target_link_libraries(usvfs_proxy PRIVATE usvfs_dll shared usvfs_helper) -set_target_properties(usvfs_proxy - PROPERTIES - RUNTIME_OUTPUT_NAME usvfs_proxy${ARCH_POSTFIX} - RUNTIME_OUTPUT_DIRECTORY_DEBUG ${USVFS_BINDIR} - RUNTIME_OUTPUT_DIRECTORY_RELEASE ${USVFS_BINDIR} -) - -install(TARGETS usvfs_proxy EXPORT usvfs${ARCH_POSTFIX}Targets) -install(FILES $<TARGET_PDB_FILE:usvfs_proxy> DESTINATION pdb OPTIONAL) diff --git a/libs/usvfs/src/usvfs_proxy/main.cpp b/libs/usvfs/src/usvfs_proxy/main.cpp deleted file mode 100644 index 88d4696..0000000 --- a/libs/usvfs/src/usvfs_proxy/main.cpp +++ /dev/null @@ -1,221 +0,0 @@ -/* -Userspace Virtual Filesystem - -Copyright (C) 2015 Sebastian Herbord. All rights reserved. - -This file is part of usvfs. - -usvfs is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -usvfs is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with usvfs. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "pch.h" -#include <../usvfs_dll/hookcontext.h> -#include <Psapi.h> -#include <WinUser.h> -#include <boost/algorithm/string/predicate.hpp> -#include <boost/filesystem.hpp> -#include <boost/lexical_cast.hpp> -#include <inject.h> -#include <shared_memory.h> -#include <sharedparameters.h> -#include <shmlogger.h> -#include <spdlog/spdlog.h> -#include <usvfsparameters.h> -#include <winapi.h> - -namespace bi = boost::interprocess; -namespace bfs = boost::filesystem; -using usvfs::SharedParameters; -using usvfs::shared::SharedMemoryT; - -template <typename T> -T getParameter(std::vector<std::string>& arguments, const std::string& key, - bool consume) -{ - auto iter = std::find(arguments.begin(), arguments.end(), std::string("--") + key); - if ((iter != arguments.end()) && ((iter + 1) != arguments.end())) { - T result = boost::lexical_cast<T>(*(iter + 1)); - if (consume) { - arguments.erase(iter, iter + 2); - } - return result; - } else { - throw std::runtime_error(std::string("argument missing " + key)); - } -} - -template <typename T> -T getParameter(std::vector<std::string>& arguments, const std::string& key, - const T& def, bool consume) -{ - auto iter = std::find(arguments.begin(), arguments.end(), std::string("--") + key); - if ((iter != arguments.end()) && ((iter + 1) != arguments.end())) { - T result = boost::lexical_cast<T>(*(iter + 1)); - if (consume) { - arguments.erase(iter, iter + 2); - } - return result; - } else { - return def; - } -} - -static void exceptionDialog(int line, int num, ...) -{ - va_list args; - va_start(args, num); - - std::wstring wstr; - WCHAR buf[256]; - wstr.append(L"Unhandled USVFS proxy exception (line "); - wsprintf(buf, L"%d): ", line); - wstr.append(buf); - for (int i = 0; i < num; i++) { - wsprintf(buf, L"%S", va_arg(args, const char*)); - if (i < num - 1) - wsprintf(buf, L", "); - wstr.append(buf); - } - - MessageBox(NULL, wstr.data(), NULL, MB_OK); - - va_end(args); -} - -int main(int argc, char** argv) -{ - std::shared_ptr<spdlog::logger> logger; - - std::vector<std::string> arguments; - std::copy(argv + 1, argv + argc, std::back_inserter(arguments)); - - std::string instance; - try { - SHMLogger::open("usvfs"); - logger = spdlog::create<usvfs::sinks::shm_sink>("usvfs", "usvfs"); - logger->set_pattern("%H:%M:%S.%e [%L] (proxy) %v"); - - instance = getParameter<std::string>(arguments, "instance", true); - } catch (const std::exception& e) { - if (logger.get() == nullptr) { - exceptionDialog(__LINE__, 1, e.what()); - return 1; - } - try { - logger->critical("{}", e.what()); - } catch (const spdlog::spdlog_ex& e2) { - exceptionDialog(__LINE__, 2, e.what(), e2.what()); - // no way to log this - } catch (const std::exception&) { - logger->critical(e.what()); - } - return 1; - } - - try { - std::string executable = - getParameter<std::string>(arguments, "executable", "", true); - int pid = getParameter<int>(arguments, "pid", 0, true); - int tid = getParameter<int>(arguments, "tid", 0, true); - - logger->info("instance: {}", instance); - logger->info("exe: {}", executable); - logger->info("pid: {}", pid); - - if (executable.empty() && (pid == 0)) { - logger->warn("not all required settings set"); - return 1; - } - - SharedMemoryT configurationSHM(bi::open_only, instance.c_str()); - if (!configurationSHM.check_sanity()) { - logger->warn("failed to connect to vfs"); - return 1; - } - logger->info("size: {}", configurationSHM.get_size()); - logger->info("addr: {0:p}", configurationSHM.get_address()); - logger->info("objs: {}", configurationSHM.get_num_named_objects()); - std::pair<SharedParameters*, SharedMemoryT::size_type> params = - configurationSHM.find<SharedParameters>("parameters"); - if (params.first == nullptr) { - logger->error("failed to open shared configuration for {}", instance); - return 1; - } - - boost::filesystem::path p(winapi::wide::getModuleFileName(nullptr)); - - if (executable.empty()) { - HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); - HANDLE threadHandle = INVALID_HANDLE_VALUE; - if (tid != 0) { - threadHandle = OpenThread(THREAD_ALL_ACCESS, FALSE, tid); - } - - BOOL blacklisted = FALSE; - TCHAR szModName[MAX_PATH]; - - if (GetModuleFileNameEx(processHandle, NULL, szModName, - sizeof(szModName) / sizeof(TCHAR))) { - const auto appName = usvfs::shared::string_cast<std::string>(szModName); - - if (params.first->executableBlacklisted(appName, {})) { - logger->info("not injecting {} as application is blacklisted", appName); - blacklisted = TRUE; - } - } - - if (!blacklisted) { - usvfs::injectProcess(p.parent_path().wstring(), params.first->makeLocal(), - processHandle, threadHandle); - } - } else { - winapi::process::Result process = - winapi::ansi::createProcess(executable) - .arguments(arguments.begin(), arguments.end()) - .workingDirectory(bfs::path(executable).parent_path().string()) - .suspended()(); - - if (!process.valid) { - return 1; - } - - BOOL blacklisted = FALSE; - - if (params.first->executableBlacklisted(executable, {})) { - logger->info("not injecting {} as application is blacklisted", executable); - - blacklisted = TRUE; - } - - if (!blacklisted) { - usvfs::injectProcess(p.parent_path().wstring(), params.first->makeLocal(), - process.processInfo); - } - - ResumeThread(process.processInfo.hThread); - } - - return 0; - } catch (const std::exception& e) { - try { - logger->critical("unhandled exception: {}", e.what()); - logExtInfo(e); - } catch (const spdlog::spdlog_ex& e2) { - // no way to log this - exceptionDialog(__LINE__, 2, e.what(), e2.what()); - } catch (const std::exception&) { - logger->critical(e.what()); - } - } -} diff --git a/libs/usvfs/src/usvfs_proxy/version.rc b/libs/usvfs/src/usvfs_proxy/version.rc deleted file mode 100644 index 03652c2..0000000 --- a/libs/usvfs/src/usvfs_proxy/version.rc +++ /dev/null @@ -1,40 +0,0 @@ -#include "Winver.h" -#include "..\..\include\usvfs\usvfs_version.h" - -#define VER_FILEVERSION USVFS_VERSION_MAJOR,USVFS_VERSION_MINOR,USVFS_VERSION_BUILD,USVFS_VERSION_REVISION -#define VER_FILEVERSION_STR USVFS_VERSION_STRING - -#define VER_PRODUCTVERSION USVFS_VERSION_MAJOR,USVFS_VERSION_MINOR,USVFS_VERSION_BUILD,USVFS_VERSION_REVISION -#define VER_PRODUCTVERSION_STR USVFS_VERSION_STRING - -VS_VERSION_INFO VERSIONINFO -FILEVERSION VER_FILEVERSION -PRODUCTVERSION VER_PRODUCTVERSION -FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -FILEFLAGS (0) -FILEOS VOS__WINDOWS32 -FILETYPE VFT_DLL -FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904B0" - BEGIN - VALUE "FileVersion", VER_FILEVERSION_STR - VALUE "CompanyName", "Mod Organizer 2 Team\0" - VALUE "FileDescription", "USVFS Proxy\0" -#ifdef _WIN64 - VALUE "OriginalFilename", "usvfs_proxy_x64.exe\0" -#else - VALUE "OriginalFilename", "usvfs_proxy_x86.exe\0" -#endif - VALUE "ProductName", "USVFS\0" - VALUE "ProductVersion", VER_PRODUCTVERSION_STR - END - END - - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x0409L, 1200 - END -END |
