aboutsummaryrefslogtreecommitdiff
path: root/libs/usvfs/src/shared
diff options
context:
space:
mode:
Diffstat (limited to 'libs/usvfs/src/shared')
-rw-r--r--libs/usvfs/src/shared/CMakeLists.txt23
-rw-r--r--libs/usvfs/src/shared/addrtools.h64
-rw-r--r--libs/usvfs/src/shared/directory_tree.cpp43
-rw-r--r--libs/usvfs/src/shared/directory_tree.h629
-rw-r--r--libs/usvfs/src/shared/exceptionex.cpp82
-rw-r--r--libs/usvfs/src/shared/exceptionex.h99
-rw-r--r--libs/usvfs/src/shared/formatters.h158
-rw-r--r--libs/usvfs/src/shared/loghelpers.cpp96
-rw-r--r--libs/usvfs/src/shared/loghelpers.h123
-rw-r--r--libs/usvfs/src/shared/ntdll_declarations.cpp75
-rw-r--r--libs/usvfs/src/shared/ntdll_declarations.h612
-rw-r--r--libs/usvfs/src/shared/pch.cpp1
-rw-r--r--libs/usvfs/src/shared/pch.h87
-rw-r--r--libs/usvfs/src/shared/shared_memory.h53
-rw-r--r--libs/usvfs/src/shared/shmlogger.cpp205
-rw-r--r--libs/usvfs/src/shared/shmlogger.h105
-rw-r--r--libs/usvfs/src/shared/stringcast.cpp40
-rw-r--r--libs/usvfs/src/shared/stringcast.h170
-rw-r--r--libs/usvfs/src/shared/stringutils.cpp146
-rw-r--r--libs/usvfs/src/shared/stringutils.h57
-rw-r--r--libs/usvfs/src/shared/tree_container.h630
-rw-r--r--libs/usvfs/src/shared/unicodestring.cpp77
-rw-r--r--libs/usvfs/src/shared/unicodestring.h110
-rw-r--r--libs/usvfs/src/shared/wildcard.cpp194
-rw-r--r--libs/usvfs/src/shared/wildcard.h67
-rw-r--r--libs/usvfs/src/shared/winapi.cpp490
-rw-r--r--libs/usvfs/src/shared/winapi.h610
-rw-r--r--libs/usvfs/src/shared/windows_sane.h28
28 files changed, 5074 insertions, 0 deletions
diff --git a/libs/usvfs/src/shared/CMakeLists.txt b/libs/usvfs/src/shared/CMakeLists.txt
new file mode 100644
index 0000000..b1a7c05
--- /dev/null
+++ b/libs/usvfs/src/shared/CMakeLists.txt
@@ -0,0 +1,23 @@
+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
new file mode 100644
index 0000000..b888ebf
--- /dev/null
+++ b/libs/usvfs/src/shared/addrtools.h
@@ -0,0 +1,64 @@
+/*
+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
new file mode 100644
index 0000000..34cc797
--- /dev/null
+++ b/libs/usvfs/src/shared/directory_tree.cpp
@@ -0,0 +1,43 @@
+/*
+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)
+{
+ ++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
new file mode 100644
index 0000000..f82187f
--- /dev/null
+++ b/libs/usvfs/src/shared/directory_tree.h
@@ -0,0 +1,629 @@
+/*
+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();
+ 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();
+ 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();
+ 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)
+ {
+ 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
+ {
+ 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
+ {
+ 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
new file mode 100644
index 0000000..4a814b5
--- /dev/null
+++ b/libs/usvfs/src/shared/exceptionex.cpp
@@ -0,0 +1,82 @@
+/*
+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
new file mode 100644
index 0000000..4c298a5
--- /dev/null
+++ b/libs/usvfs/src/shared/exceptionex.h
@@ -0,0 +1,99 @@
+/*
+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
new file mode 100644
index 0000000..3d0e47e
--- /dev/null
+++ b/libs/usvfs/src/shared/formatters.h
@@ -0,0 +1,158 @@
+/*
+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
new file mode 100644
index 0000000..bfcabf1
--- /dev/null
+++ b/libs/usvfs/src/shared/loghelpers.cpp
@@ -0,0 +1,96 @@
+/*
+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
new file mode 100644
index 0000000..071ad9d
--- /dev/null
+++ b/libs/usvfs/src/shared/loghelpers.h
@@ -0,0 +1,123 @@
+/*
+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
new file mode 100644
index 0000000..b4bf0f5
--- /dev/null
+++ b/libs/usvfs/src/shared/ntdll_declarations.cpp
@@ -0,0 +1,75 @@
+/*
+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
new file mode 100644
index 0000000..813ad8c
--- /dev/null
+++ b/libs/usvfs/src/shared/ntdll_declarations.h
@@ -0,0 +1,612 @@
+/*
+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
new file mode 100644
index 0000000..1d9f38c
--- /dev/null
+++ b/libs/usvfs/src/shared/pch.cpp
@@ -0,0 +1 @@
+#include "pch.h"
diff --git a/libs/usvfs/src/shared/pch.h b/libs/usvfs/src/shared/pch.h
new file mode 100644
index 0000000..e787f68
--- /dev/null
+++ b/libs/usvfs/src/shared/pch.h
@@ -0,0 +1,87 @@
+#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
new file mode 100644
index 0000000..28da34b
--- /dev/null
+++ b/libs/usvfs/src/shared/shared_memory.h
@@ -0,0 +1,53 @@
+/*
+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
new file mode 100644
index 0000000..66512de
--- /dev/null
+++ b/libs/usvfs/src/shared/shmlogger.cpp
@@ -0,0 +1,205 @@
+/*
+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
new file mode 100644
index 0000000..158f941
--- /dev/null
+++ b/libs/usvfs/src/shared/shmlogger.h
@@ -0,0 +1,105 @@
+/*
+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
new file mode 100644
index 0000000..47822d5
--- /dev/null
+++ b/libs/usvfs/src/shared/stringcast.cpp
@@ -0,0 +1,40 @@
+/*
+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
new file mode 100644
index 0000000..0ce56d1
--- /dev/null
+++ b/libs/usvfs/src/shared/stringcast.h
@@ -0,0 +1,170 @@
+/*
+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
new file mode 100644
index 0000000..2ddbf64
--- /dev/null
+++ b/libs/usvfs/src/shared/stringutils.cpp
@@ -0,0 +1,146 @@
+/*
+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
new file mode 100644
index 0000000..64d0af7
--- /dev/null
+++ b/libs/usvfs/src/shared/stringutils.h
@@ -0,0 +1,57 @@
+/*
+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
new file mode 100644
index 0000000..a2f61fa
--- /dev/null
+++ b/libs/usvfs/src/shared/tree_container.h
@@ -0,0 +1,630 @@
+#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
new file mode 100644
index 0000000..f997e7f
--- /dev/null
+++ b/libs/usvfs/src/shared/unicodestring.cpp
@@ -0,0 +1,77 @@
+/*
+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
new file mode 100644
index 0000000..77071ad
--- /dev/null
+++ b/libs/usvfs/src/shared/unicodestring.h
@@ -0,0 +1,110 @@
+/*
+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
new file mode 100644
index 0000000..a2efe6d
--- /dev/null
+++ b/libs/usvfs/src/shared/wildcard.cpp
@@ -0,0 +1,194 @@
+/*
+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
new file mode 100644
index 0000000..bf5b4a3
--- /dev/null
+++ b/libs/usvfs/src/shared/wildcard.h
@@ -0,0 +1,67 @@
+/*
+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
new file mode 100644
index 0000000..e28a13c
--- /dev/null
+++ b/libs/usvfs/src/shared/winapi.cpp
@@ -0,0 +1,490 @@
+/*
+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
new file mode 100644
index 0000000..f94eff6
--- /dev/null
+++ b/libs/usvfs/src/shared/winapi.h
@@ -0,0 +1,610 @@
+/*
+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
new file mode 100644
index 0000000..7bb3726
--- /dev/null
+++ b/libs/usvfs/src/shared/windows_sane.h
@@ -0,0 +1,28 @@
+/*
+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>