From de335464ba0cf518ea19a2a3c6cf8f125a7c991b Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Fri, 11 Jan 2019 01:35:24 -0600 Subject: [PATCH 01/15] Update boost to 1.69.0 --- vsbuild/external_dependencies.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vsbuild/external_dependencies.props b/vsbuild/external_dependencies.props index 2d64f847..43ebdfb0 100644 --- a/vsbuild/external_dependencies.props +++ b/vsbuild/external_dependencies.props @@ -4,7 +4,7 @@ - ..\..\boost_1_68_0 + ..\..\boost_1_69_0 $(BOOST_PATH) ..\..\googletest From 588745de90b02d1d03ac8fd64c1d97c7bcc662a2 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Fri, 11 Jan 2019 01:35:51 -0600 Subject: [PATCH 02/15] Update version to 0.4.3.0 --- include/usvfs_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/usvfs_version.h b/include/usvfs_version.h index 8470464e..882cf53d 100644 --- a/include/usvfs_version.h +++ b/include/usvfs_version.h @@ -2,7 +2,7 @@ #define USVFS_VERSION_MAJOR 0 #define USVFS_VERSION_MINOR 4 -#define USVFS_VERSION_BUILD 2 +#define USVFS_VERSION_BUILD 3 #define USVFS_VERSION_REVISION 0 #define USVFS_BUILD_STRING "" From 245cdcc0c89055b598cf59430944181e5a4fd2a4 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Fri, 11 Jan 2019 01:36:03 -0600 Subject: [PATCH 03/15] Support for force loading libraries --- include/usvfs.h | 11 +++++++++ src/usvfs_dll/hookcontext.cpp | 26 +++++++++++++++++++++ src/usvfs_dll/hookcontext.h | 21 +++++++++++++++++ src/usvfs_dll/usvfs.cpp | 43 ++++++++++++++++++++++++++--------- 4 files changed, 90 insertions(+), 11 deletions(-) diff --git a/include/usvfs.h b/include/usvfs.h index 8a09d551..38027618 100644 --- a/include/usvfs.h +++ b/include/usvfs.h @@ -144,6 +144,17 @@ DLLEXPORT VOID WINAPI BlacklistExecutable(LPWSTR executableName); */ DLLEXPORT VOID WINAPI ClearExecutableBlacklist(); +/** + * adds a library to be force loaded when the given process is injected + * @param + */ +DLLEXPORT VOID WINAPI ForceLoadLibrary(LPWSTR processName, LPWSTR libraryPath); + +/** + * clears all previous calls to ForceLoadLibrary + */ +DLLEXPORT VOID WINAPI ClearLibraryForceLoads(); + /** * print debugging info about the vfs. The format is currently not fixed and may * change between usvfs versions diff --git a/src/usvfs_dll/hookcontext.cpp b/src/usvfs_dll/hookcontext.cpp index 5342e8c0..387f8472 100644 --- a/src/usvfs_dll/hookcontext.cpp +++ b/src/usvfs_dll/hookcontext.cpp @@ -225,6 +225,32 @@ BOOL HookContext::executableBlacklisted(const std::wstring &executableName) cons return FALSE; } +void HookContext::forceLoadLibrary(const std::wstring &processName, const std::wstring &libraryPath) +{ + m_Parameters->forcedLibraries.push_front(ForcedLibrary( + shared::string_cast(processName, shared::CodePage::UTF8).c_str(), + shared::string_cast(libraryPath, shared::CodePage::UTF8).c_str(), + m_Parameters->forcedLibraries.get_allocator())); +} + +void HookContext::clearLibraryForceLoads() +{ + m_Parameters->forcedLibraries.clear(); +} + +std::vector HookContext::librariesToForceLoad(const std::wstring &processName) +{ + std::vector results; + for (auto library : m_Parameters->forcedLibraries) { + std::string processNameString = shared::string_cast(processName, shared::CodePage::UTF8); + if (stricmp(processNameString.c_str(), library.processName.c_str()) == 0) { + std::wstring libraryPathString = shared::string_cast(library.libraryPath.c_str(), shared::CodePage::UTF8); + results.push_back(libraryPathString); + } + } + return results; +} + void HookContext::unregisterCurrentProcess() { auto iter = m_Parameters->processList.find(::GetCurrentProcessId()); diff --git a/src/usvfs_dll/hookcontext.h b/src/usvfs_dll/hookcontext.h index 8e435b06..693b3247 100644 --- a/src/usvfs_dll/hookcontext.h +++ b/src/usvfs_dll/hookcontext.h @@ -33,6 +33,7 @@ along with usvfs. If not, see . #include #include #include +#include #include #include #include @@ -54,6 +55,20 @@ void USVFSInitParametersInt(USVFSParameters *parameters, typedef shared::VoidAllocatorT::rebind::other DWORDAllocatorT; typedef shared::VoidAllocatorT::rebind::other StringAllocatorT; +struct ForcedLibrary { + ForcedLibrary(const char *processName, const char *libraryPath, + const shared::VoidAllocatorT &allocator) + : processName(processName, allocator) + , libraryPath(libraryPath, allocator) + { + } + + shared::StringT processName; + shared::StringT libraryPath; +}; + +typedef shared::VoidAllocatorT::rebind::other ForcedLibraryAllocatorT; + struct SharedParameters { SharedParameters() = delete; @@ -74,6 +89,7 @@ struct SharedParameters { , userCount(1) , processBlacklist(allocator) , processList(allocator) + , forcedLibraries(allocator) { } @@ -90,6 +106,7 @@ struct SharedParameters { boost::container::flat_set, StringAllocatorT> processBlacklist; boost::container::flat_set, DWORDAllocatorT> processList; + boost::container::slist forcedLibraries; }; @@ -202,6 +219,10 @@ class HookContext void clearExecutableBlacklist(); BOOL executableBlacklisted(const std::wstring &executableName) const; + void forceLoadLibrary(const std::wstring &processName, const std::wstring &libraryPath); + void clearLibraryForceLoads(); + std::vector librariesToForceLoad(const std::wstring &processName); + void setLogLevel(LogLevel level); void setCrashDumpsType(CrashDumpsType type); diff --git a/src/usvfs_dll/usvfs.cpp b/src/usvfs_dll/usvfs.cpp index 1515b1b8..86ee98bc 100644 --- a/src/usvfs_dll/usvfs.cpp +++ b/src/usvfs_dll/usvfs.cpp @@ -31,6 +31,7 @@ along with usvfs. If not, see . #include #include #include +#include #include #include #include @@ -43,6 +44,7 @@ along with usvfs. If not, see . #include #include #include +#include namespace bfs = boost::filesystem; @@ -347,19 +349,25 @@ void __cdecl InitHooks(LPVOID parameters, size_t) try { manager = new usvfs::HookManager(*params, dllModule); + auto context = manager->context(); + auto exePath = boost::dll::program_location(); + auto libraries = context->librariesToForceLoad(exePath.filename().c_str()); + for (auto library : libraries) { + if (std::experimental::filesystem::exists(library)) { + const auto ret = LoadLibraryExW(library.c_str(), NULL, 0); + if (ret) { + spdlog::get("usvfs") + ->info("inithooks succeeded to force load {0}", ush::string_cast(library).c_str()); + } else { + spdlog::get("usvfs") + ->critical("inithooks failed to force load {0}", ush::string_cast(library).c_str()); + } + } + } + spdlog::get("usvfs") - ->info("inithooks in process {0} successfull", ::GetCurrentProcessId()); + ->info("inithooks in process {0} successful", ::GetCurrentProcessId()); -/* - std::ostringstream str; - dumpTree(str, *manager->context()->redirectionTable().get()); - typedef boost::tokenizer> tokenizer; - boost::char_separator sep("\n"); - tokenizer tok(str.str(), sep); - for (auto && s : tok) - spdlog::get("usvfs")->debug("{}", s); -*/ - //context = manager->context(); } catch (const std::exception &e) { spdlog::get("usvfs")->debug("failed to initialise hooks: {0}", e.what()); } @@ -716,12 +724,25 @@ VOID WINAPI BlacklistExecutable(LPWSTR executableName) context->blacklistExecutable(executableName); } + VOID WINAPI ClearExecutableBlacklist() { context->clearExecutableBlacklist(); } +VOID WINAPI ForceLoadLibrary(LPWSTR processName, LPWSTR libraryPath) +{ + context->forceLoadLibrary(processName, libraryPath); +} + + +VOID WINAPI ClearLibraryForceLoads() +{ + context->clearLibraryForceLoads(); +} + + VOID WINAPI PrintDebugInfo() { spdlog::get("usvfs") From 5f2493ab3c8526bcd32333537bf48aa079e6eaf1 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sat, 26 Jan 2019 23:36:55 -0600 Subject: [PATCH 04/15] Do not reverse the order of files in Query Directory calls Reversing the order of the files resulted in a vary esoteric bug that broke some SKSE plugins. The order of the files is not guaranteed by the Windows API but USVFS should try to maintain the order provided by Windows to promote compatibility. This change probably results in reduced performance. --- src/usvfs_dll/hooks/ntdll.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/usvfs_dll/hooks/ntdll.cpp b/src/usvfs_dll/hooks/ntdll.cpp index 12bb79d1..da57e3a7 100644 --- a/src/usvfs_dll/hooks/ntdll.cpp +++ b/src/usvfs_dll/hooks/ntdll.cpp @@ -13,6 +13,7 @@ #pragma warning(pop) #include #include +#include #include #include #include @@ -553,7 +554,7 @@ struct Searches { } std::set foundFiles; HANDLE currentSearchHandle; - std::vector virtualMatches; + std::queue virtualMatches; UnicodeString searchPattern; bool regularComplete{false}; }; @@ -611,7 +612,7 @@ void gatherVirtualEntries(const UnicodeString &dirName, ush::CodePage::UTF8), vName }; } - info.virtualMatches.push_back(m); + info.virtualMatches.push(m); info.foundFiles.insert(ush::to_upper(vName)); } } @@ -787,12 +788,12 @@ NTSTATUS WINAPI usvfs::hook_NtQueryDirectoryFile( if (!moreRegular) { // add virtual results while (!dataReturned && infoIter->second.virtualMatches.size() > 0) { - auto matchIter = infoIter->second.virtualMatches.rbegin(); - if (matchIter->realPath.size() != 0) { + auto match = infoIter->second.virtualMatches.front(); + if (match.realPath.size() != 0) { dataRead = Length; if (addVirtualSearchResult(FileInformationCurrent, FileInformationClass, - infoIter->second, matchIter->realPath, - matchIter->virtualName, ReturnSingleEntry, + infoIter->second, match.realPath, + match.virtualName, ReturnSingleEntry, dataRead)) { // a positive result here means the call returned data and there may // be further objects to be retrieved by repeating the call @@ -803,7 +804,7 @@ NTSTATUS WINAPI usvfs::hook_NtQueryDirectoryFile( // TODO: doesn't append search results from more than one redirection // per call. This is bad for performance but otherwise we'd need to // re-write the offsets between information objects - infoIter->second.virtualMatches.pop_back(); + infoIter->second.virtualMatches.pop(); CloseHandle(infoIter->second.currentSearchHandle); infoIter->second.currentSearchHandle = INVALID_HANDLE_VALUE; } @@ -955,12 +956,12 @@ NTSTATUS WINAPI usvfs::hook_NtQueryDirectoryFileEx( if (!moreRegular) { // add virtual results while (!dataReturned && infoIter->second.virtualMatches.size() > 0) { - auto matchIter = infoIter->second.virtualMatches.rbegin(); - if (matchIter->realPath.size() != 0) { + auto match = infoIter->second.virtualMatches.front(); + if (match.realPath.size() != 0) { dataRead = Length; if (addVirtualSearchResult(FileInformationCurrent, FileInformationClass, - infoIter->second, matchIter->realPath, - matchIter->virtualName, QueryFlags & SL_RETURN_SINGLE_ENTRY, + infoIter->second, match.realPath, + match.virtualName, QueryFlags & SL_RETURN_SINGLE_ENTRY, dataRead)) { // a positive result here means the call returned data and there may // be further objects to be retrieved by repeating the call @@ -972,7 +973,7 @@ NTSTATUS WINAPI usvfs::hook_NtQueryDirectoryFileEx( // TODO: doesn't append search results from more than one redirection // per call. This is bad for performance but otherwise we'd need to // re-write the offsets between information objects - infoIter->second.virtualMatches.pop_back(); + infoIter->second.virtualMatches.pop(); CloseHandle(infoIter->second.currentSearchHandle); infoIter->second.currentSearchHandle = INVALID_HANDLE_VALUE; } From 5ff010801bc8987a3ea174945606848e054362ee Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sun, 3 Feb 2019 15:22:29 -0600 Subject: [PATCH 05/15] Add warnings where DELETE_ON_CLOSE flag is used --- src/usvfs_dll/hooks/kernel32.cpp | 8 ++++++++ src/usvfs_dll/hooks/ntdll.cpp | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/usvfs_dll/hooks/kernel32.cpp b/src/usvfs_dll/hooks/kernel32.cpp index d74e5dc5..1a8e7639 100644 --- a/src/usvfs_dll/hooks/kernel32.cpp +++ b/src/usvfs_dll/hooks/kernel32.cpp @@ -1044,6 +1044,10 @@ HANDLE WINAPI usvfs::hook_CreateFileW( return res; } + if (dwFlagsAndAttributes & FILE_FLAG_DELETE_ON_CLOSE) { + spdlog::get("hooks")->warn("hook_CreateFileW: FILE_FLAG_DELETE_ON_CLOSE not supported"); + } + DWORD originalDisposition = dwCreationDisposition; CreateRerouter rerouter; if (rerouter.rerouteCreate(READ_CONTEXT(), callContext, lpFileName, dwCreationDisposition, dwDesiredAccess, lpSecurityAttributes)) @@ -1110,6 +1114,10 @@ HANDLE WINAPI usvfs::hook_CreateFile2(LPCWSTR lpFileName, DWORD dwDesiredAccess, return res; } + if (pCreateExParams && (pCreateExParams->dwFileFlags & FILE_FLAG_DELETE_ON_CLOSE)) { + spdlog::get("hooks")->warn("hook_CreateFile2: FILE_FLAG_DELETE_ON_CLOSE not supported"); + } + DWORD originalDisposition = dwCreationDisposition; CreateRerouter rerouter; if (rerouter.rerouteCreate(READ_CONTEXT(), callContext, lpFileName, dwCreationDisposition, dwDesiredAccess, diff --git a/src/usvfs_dll/hooks/ntdll.cpp b/src/usvfs_dll/hooks/ntdll.cpp index da57e3a7..ad88c8f2 100644 --- a/src/usvfs_dll/hooks/ntdll.cpp +++ b/src/usvfs_dll/hooks/ntdll.cpp @@ -40,6 +40,7 @@ namespace bfs = boost::filesystem; using usvfs::UnicodeString; +// flag definitions below are copied from winternl.h #define FILE_SUPERSEDE 0x00000000 #define FILE_OPEN 0x00000001 #define FILE_CREATE 0x00000002 @@ -48,6 +49,8 @@ using usvfs::UnicodeString; #define FILE_OVERWRITE_IF 0x00000005 #define FILE_MAXIMUM_DISPOSITION 0x00000005 +#define FILE_DELETE_ON_CLOSE 0x00001000 + template using unique_ptr_deleter = std::unique_ptr; @@ -1042,6 +1045,10 @@ NTSTATUS ntdll_mess_NtOpenFile(PHANDLE FileHandle, HOOK_START_GROUP(MutExHookGroup::OPEN_FILE) + if (OpenOptions & FILE_DELETE_ON_CLOSE) { + spdlog::get("hooks")->warn("ntdll_mess_NtOpenFile: FILE_DELETE_ON_CLOSE not supported"); + } + bool storePath = false; if (((OpenOptions & FILE_DIRECTORY_FILE) != 0UL) && ((OpenOptions & FILE_OPEN_FOR_BACKUP_INTENT) != 0UL)) { @@ -1151,6 +1158,10 @@ NTSTATUS ntdll_mess_NtCreateFile( EaBuffer, EaLength); } + if (CreateOptions & FILE_DELETE_ON_CLOSE) { + spdlog::get("hooks")->warn("ntdll_mess_NtCreateFile: FILE_DELETE_ON_CLOSE not supported"); + } + UnicodeString inPath = CreateUnicodeString(ObjectAttributes); if (inPath.size() == 0) { From 4b35644749b9ff9d827462338d2a1b1503d002d8 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sat, 16 Feb 2019 08:46:46 -0600 Subject: [PATCH 06/15] Improve blacklist implementation The previous implementation had issues detecting processes with command-line arguments. This will fix that at the cost of possibly introducing some false positives. This also adds blacklist processing to CreateProcessHooked for consistency. --- src/usvfs_dll/hookcontext.cpp | 31 +++++++++++++++++++++++++------ src/usvfs_dll/hookcontext.h | 2 +- src/usvfs_dll/hooks/kernel32.cpp | 28 +++++----------------------- src/usvfs_dll/usvfs.cpp | 26 +++++++++++++++----------- 4 files changed, 46 insertions(+), 41 deletions(-) diff --git a/src/usvfs_dll/hookcontext.cpp b/src/usvfs_dll/hookcontext.cpp index 387f8472..3690afc1 100644 --- a/src/usvfs_dll/hookcontext.cpp +++ b/src/usvfs_dll/hookcontext.cpp @@ -33,6 +33,7 @@ using usvfs::shared::SharedMemoryT; using usvfs::shared::VoidAllocatorT; using namespace usvfs; +namespace ush = usvfs::shared; HookContext *HookContext::s_Instance = nullptr; @@ -214,15 +215,33 @@ void HookContext::clearExecutableBlacklist() m_Parameters->processBlacklist.clear(); } -BOOL HookContext::executableBlacklisted(const std::wstring &executableName) const +BOOL HookContext::executableBlacklisted(LPCWSTR lpApplicationName, LPCWSTR lpCommandLine) const { - for (shared::StringT exec : m_Parameters->processBlacklist) { - if (boost::algorithm::iends_with(executableName, - "\\" + std::string(exec.data(), exec.size()))) { - return TRUE; + BOOL blacklisted = FALSE; + + if (lpApplicationName) { + std::string appName = ush::string_cast(lpApplicationName, ush::CodePage::UTF8); + for (shared::StringT item : m_Parameters->processBlacklist) { + if (boost::algorithm::iends_with(appName, std::string(item.data(), item.size()))) { + spdlog::get("usvfs")->info("application {} is blacklisted", appName); + blacklisted = TRUE; + break; + } } } - return FALSE; + + if (lpCommandLine) { + std::string cmdLine = ush::string_cast(lpCommandLine, ush::CodePage::UTF8); + for (shared::StringT item : m_Parameters->processBlacklist) { + if (boost::algorithm::icontains(cmdLine, std::string(item.data(), item.size()))) { + spdlog::get("usvfs")->info("command line {} is blacklisted", cmdLine); + blacklisted = TRUE; + break; + } + } + } + + return blacklisted; } void HookContext::forceLoadLibrary(const std::wstring &processName, const std::wstring &libraryPath) diff --git a/src/usvfs_dll/hookcontext.h b/src/usvfs_dll/hookcontext.h index 693b3247..9b4dff6f 100644 --- a/src/usvfs_dll/hookcontext.h +++ b/src/usvfs_dll/hookcontext.h @@ -217,7 +217,7 @@ class HookContext void blacklistExecutable(const std::wstring &executableName); void clearExecutableBlacklist(); - BOOL executableBlacklisted(const std::wstring &executableName) const; + BOOL HookContext::executableBlacklisted(LPCWSTR lpApplicationName, LPCWSTR lpCommandLine) const; void forceLoadLibrary(const std::wstring &processName, const std::wstring &libraryPath); void clearLibraryForceLoads(); diff --git a/src/usvfs_dll/hooks/kernel32.cpp b/src/usvfs_dll/hooks/kernel32.cpp index 1a8e7639..821bc69e 100644 --- a/src/usvfs_dll/hooks/kernel32.cpp +++ b/src/usvfs_dll/hooks/kernel32.cpp @@ -815,30 +815,12 @@ BOOL WINAPI usvfs::hook_CreateProcessInternalW( POST_REALCALL BOOL blacklisted = FALSE; - if (applicationReroute.fileName()) { + { // limit scope of context auto context = READ_CONTEXT(); - if (context->executableBlacklisted(applicationReroute.fileName())) { - spdlog::get("hooks")->info( - "not injecting {} as application is blacklisted", - ush::string_cast( - applicationReroute.fileName(), - ush::CodePage::UTF8 - ) - ); - blacklisted = TRUE; - } - } else if (cmdReroute.fileName()) { - auto context = READ_CONTEXT(); - if (context->executableBlacklisted(cmdReroute.fileName())) { - spdlog::get("hooks")->info( - "not injecting {} as command line is blacklisted", - ush::string_cast( - cmdReroute.fileName(), - ush::CodePage::UTF8 - ) - ); - blacklisted = TRUE; - } + blacklisted = context->executableBlacklisted( + applicationReroute.fileName(), + cmdReroute.fileName() + ); } if (res) diff --git a/src/usvfs_dll/usvfs.cpp b/src/usvfs_dll/usvfs.cpp index 86ee98bc..5779dd91 100644 --- a/src/usvfs_dll/usvfs.cpp +++ b/src/usvfs_dll/usvfs.cpp @@ -673,6 +673,8 @@ BOOL WINAPI CreateProcessHooked(LPCWSTR lpApplicationName BOOL susp = dwCreationFlags & CREATE_SUSPENDED; DWORD flags = dwCreationFlags | CREATE_SUSPENDED; + BOOL blacklisted = context->executableBlacklisted(lpApplicationName, lpCommandLine); + BOOL res = CreateProcessW(lpApplicationName, lpCommandLine , lpProcessAttributes, lpThreadAttributes , bInheritHandles, flags @@ -683,17 +685,19 @@ BOOL WINAPI CreateProcessHooked(LPCWSTR lpApplicationName return FALSE; } - std::wstring applicationDirPath = winapi::wide::getModuleFileName(dllModule); - boost::filesystem::path p(applicationDirPath); - try { - usvfs::injectProcess(p.parent_path().wstring(), context->callParameters(), - *lpProcessInformation); - } catch (const std::exception &e) { - spdlog::get("usvfs")->error("failed to inject: {}", e.what()); - logExtInfo(e, LogLevel::Error); - ::TerminateProcess(lpProcessInformation->hProcess, 1); - ::SetLastError(ERROR_INVALID_PARAMETER); - return FALSE; + if (!blacklisted) { + std::wstring applicationDirPath = winapi::wide::getModuleFileName(dllModule); + boost::filesystem::path p(applicationDirPath); + try { + usvfs::injectProcess(p.parent_path().wstring(), context->callParameters(), + *lpProcessInformation); + } catch (const std::exception &e) { + spdlog::get("usvfs")->error("failed to inject: {}", e.what()); + logExtInfo(e, LogLevel::Error); + ::TerminateProcess(lpProcessInformation->hProcess, 1); + ::SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } } if (!susp) { From 6543cc8d67fa61cac78650c1c2d0ba5da3dd63db Mon Sep 17 00:00:00 2001 From: Al Date: Sat, 2 Mar 2019 18:02:12 +0100 Subject: [PATCH 07/15] Removed TEMP path workaround from FindFirstFile: It was first introduced to workaround WB issues but it seems that we actually fixed those issues and broke the workaround instead on windows7. * Reportedly fixed win7 WB issues. * Substantially improved performance of FindFirstFile as apparently the getTempPath function was actually causing a long directory crawl. This crippled the peformance for FFF as the internal functions of getTempPath were also getting hooked. Morrowing was reported to go from 20 seconds to load vanialla, to two seconds. --- src/usvfs_dll/hooks/kernel32.cpp | 59 ++++++++++++-------------------- 1 file changed, 22 insertions(+), 37 deletions(-) diff --git a/src/usvfs_dll/hooks/kernel32.cpp b/src/usvfs_dll/hooks/kernel32.cpp index 821bc69e..e1a0ed07 100644 --- a/src/usvfs_dll/hooks/kernel32.cpp +++ b/src/usvfs_dll/hooks/kernel32.cpp @@ -2062,52 +2062,37 @@ HANDLE WINAPI usvfs::hook_FindFirstFileExW(LPCWSTR lpFileName, FINDEX_INFO_LEVEL return res; } - WCHAR *tempPath = new WCHAR[MAX_PATH]; - ::GetTempPathW(MAX_PATH, tempPath); - ::GetLongPathNameW(tempPath, tempPath, MAX_PATH); - std::wstring tempPathStr(tempPath); - tempPathStr.pop_back(); // Remove trailing slash - delete[] tempPath; - fs::path finalPath; RerouteW reroute; fs::path originalPath; bool usedRewrite = false; - if (boost::algorithm::icontains(lpFileName, tempPathStr)) { - PRE_REALCALL - //Force the mutEXHook to match NtQueryDirectoryFile so it calls the non hooked NtQueryDirectoryFile. - FunctionGroupLock lock(MutExHookGroup::FIND_FILES); - res = ::FindFirstFileExW(lpFileName, fInfoLevelId, lpFindFileData, fSearchOp, lpSearchFilter, dwAdditionalFlags); - POST_REALCALL - } else { - // We need to do some trickery here, since we only want to use the hooked NtQueryDirectoryFile for rerouted locations we need to check if the Directory path has been routed instead of the full path. - originalPath = RerouteW::canonizePath(RerouteW::absolutePath(lpFileName)); - PRE_REALCALL + // We need to do some trickery here, since we only want to use the hooked NtQueryDirectoryFile for rerouted locations we need to check if the Directory path has been routed instead of the full path. + originalPath = RerouteW::canonizePath(RerouteW::absolutePath(lpFileName)); + PRE_REALCALL res = ::FindFirstFileExW(originalPath.c_str(), fInfoLevelId, lpFindFileData, fSearchOp, lpSearchFilter, dwAdditionalFlags); - POST_REALCALL + POST_REALCALL - if (res == INVALID_HANDLE_VALUE) { - fs::path searchPath = originalPath.filename(); - fs::path parentPath = originalPath.parent_path(); - std::wstring findPath = parentPath.wstring(); - while (findPath.find(L"*?<>\"", 0, 1) != std::wstring::npos) { - searchPath = parentPath.filename() / searchPath; - parentPath = parentPath.parent_path(); - findPath = parentPath.wstring(); - } - reroute = RerouteW::create(READ_CONTEXT(), callContext, parentPath.c_str()); - if (reroute.wasRerouted()) { - finalPath = reroute.fileName(); - finalPath /= searchPath.wstring(); - } - if (!finalPath.empty()) { - PRE_REALCALL + if (res == INVALID_HANDLE_VALUE) { + fs::path searchPath = originalPath.filename(); + fs::path parentPath = originalPath.parent_path(); + std::wstring findPath = parentPath.wstring(); + while (findPath.find(L"*?<>\"", 0, 1) != std::wstring::npos) { + searchPath = parentPath.filename() / searchPath; + parentPath = parentPath.parent_path(); + findPath = parentPath.wstring(); + } + reroute = RerouteW::create(READ_CONTEXT(), callContext, parentPath.c_str()); + if (reroute.wasRerouted()) { + finalPath = reroute.fileName(); + finalPath /= searchPath.wstring(); + } + if (!finalPath.empty()) { + PRE_REALCALL usedRewrite = true; res = ::FindFirstFileExW(finalPath.c_str(), fInfoLevelId, lpFindFileData, fSearchOp, lpSearchFilter, dwAdditionalFlags); - POST_REALCALL - } + POST_REALCALL } } @@ -2118,7 +2103,7 @@ HANDLE WINAPI usvfs::hook_FindFirstFileExW(LPCWSTR lpFileName, FINDEX_INFO_LEVEL = lpFileName; } - LOG_CALL().PARAMWRAP(lpFileName).PARAMWRAP(tempPathStr.c_str()); + //LOG_CALL().PARAMWRAP(lpFileName).PARAMWRAP(tempPathStr.c_str()); LOG_CALL().PARAMWRAP(lpFileName).PARAMWRAP(originalPath.c_str()).PARAM(res).PARAM(callContext.lastError()); if (usedRewrite) LOG_CALL().PARAMWRAP(lpFileName).PARAMWRAP(finalPath.c_str()).PARAM(res).PARAM(callContext.lastError()); From 5e6947de8b7bc7c7944880a03723ffea6fde0ecf Mon Sep 17 00:00:00 2001 From: LePresidente Date: Fri, 8 Mar 2019 09:37:32 +0200 Subject: [PATCH 08/15] Added artifacts to usvfs appveyor build. Updated boost to 1.69 on appveyor. --- appveyor.yml | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 6da1eded..a981adbd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,15 +6,34 @@ platform: - x86 environment: GTEST_PATH: C:\Libraries\googletest - BOOST_PATH: C:\Libraries\boost_1_66_0 + BOOST_PATH: C:\Libraries\boost_1_69_0 + WEBHOOK_URL: + secure: gOKbXaZM9ImtMD5XrYITvdyZUW/az082G9OIN1EC1VZ2CuYaUUM6WY2eiNxaFeOL7/9Jyu/m+Vm1fH54CEyigcUUaxA7d8F5IMWlOgE/7YYdaAFSMUTFD7EK+++3FBYfmU1F/nZ61wsiWE6hB9Au5FpGBSCeQ0Tf8U8m0ybPmD0= install: - ps: $blockRdp = $false; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) before_build: -- ps: "echo $env:Platform\nif (${env:Platform} -eq \"x64\") {\n set-item -path Env:BOOST_LIBPATH -value C:\\Libraries\\boost_1_66_0\\lib64-msvc-14.1\n set-item -path Env:GTEST_BUILDDIR -value c:\\libraries\\googletest\\build \n}\nElse {\n set-item -path Env:BOOST_LIBPATH -value C:\\Libraries\\boost_1_66_0\\lib32-msvc-14.1\n set-item -path Env:GTEST_BUILDDIR -value c:\\libraries\\googletest\\build_32\n}\n\n. git clone --depth=1 --branch=master https://github.com/google/googletest.git c:\\libraries\\googletest 2> $null\nNew-Item $Env:GTEST_BUILDDIR -type directory\nSet-Location -Path $Env:GTEST_BUILDDIR\n. cmd /c \"`\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat`\" $env:Platform && cmake -G `\"NMake Makefiles`\" -DCMAKE_BUILD_TYPE=Release ..`\"\"\n. cmd /c \"`\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat`\" $env:Platform && nmake\"\n\nSet-Location -Path c:\\projects\\usvfs\\vsbuild\n#Tempory fix due to that appveyor has boost lib files in a custom output folder\nGet-ChildItem -Path *.props -recurse | ForEach {If (Get-Content $_.FullName | Select-String -Pattern '\\$\\(BOOST_PATH\\)\\\\.*\\\\lib') {(Get-Content $_ | ForEach {$_ -replace '\\$\\(BOOST_PATH\\)\\\\.*\\\\lib', \"$Env:BOOST_LIBPATH\"}) | Set-Content $_ }}\n\n#Need to tell usvfs to not use the BOOST_BUILDID\nGet-ChildItem -Path *.props -recurse | ForEach {If (Get-Content $_.FullName | Select-String -Pattern 'BOOST_LIB_BUILDID=x86;') {(Get-Content $_ | ForEach {$_ -replace 'BOOST_LIB_BUILDID=x86;', \"\"}) | Set-Content $_ }}\n\nSet-Location -Path c:\\projects\\usvfs\ngit submodule -q update --init --recursive\nSet-Location -Path c:\\projects\\usvfs\\udis86\ngit pull -q origin master" +- ps: "echo $env:Platform\nif (${env:Platform} -eq \"x64\") {\n set-item -path Env:BOOST_LIBPATH -value C:\\Libraries\\boost_1_69_0\\lib64-msvc-14.1\n set-item -path Env:GTEST_BUILDDIR -value c:\\libraries\\googletest\\build \n}\nElse {\n set-item -path Env:BOOST_LIBPATH -value C:\\Libraries\\boost_1_69_0\\lib32-msvc-14.1\n set-item -path Env:GTEST_BUILDDIR -value c:\\libraries\\googletest\\build_32\n}\n\n. git clone --depth=1 --branch=master https://github.com/google/googletest.git c:\\libraries\\googletest 2> $null\nNew-Item $Env:GTEST_BUILDDIR -type directory\nSet-Location -Path $Env:GTEST_BUILDDIR\n. cmd /c \"`\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat`\" $env:Platform && cmake -G `\"NMake Makefiles`\" -DCMAKE_BUILD_TYPE=Release ..`\"\"\n. cmd /c \"`\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat`\" $env:Platform && nmake\"\n\nSet-Location -Path c:\\projects\\usvfs\\vsbuild\n#Tempory fix due to that appveyor has boost lib files in a custom output folder\nGet-ChildItem -Path *.props -recurse | ForEach {If (Get-Content $_.FullName | Select-String -Pattern '\\$\\(BOOST_PATH\\)\\\\.*\\\\lib') {(Get-Content $_ | ForEach {$_ -replace '\\$\\(BOOST_PATH\\)\\\\.*\\\\lib', \"$Env:BOOST_LIBPATH\"}) | Set-Content $_ }}\n\n#Need to tell usvfs to not use the BOOST_BUILDID\nGet-ChildItem -Path *.props -recurse | ForEach {If (Get-Content $_.FullName | Select-String -Pattern 'BOOST_LIB_BUILDID=x86;') {(Get-Content $_ | ForEach {$_ -replace 'BOOST_LIB_BUILDID=x86;', \"\"}) | Set-Content $_ }}\n\nSet-Location -Path c:\\projects\\usvfs\ngit submodule -q update --init --recursive\nSet-Location -Path c:\\projects\\usvfs\\udis86\ngit pull -q origin master" build: project: vsbuild/usvfs.sln parallel: true verbosity: normal test: off +artifacts: +- path: bin\usvfs_proxy_$(Platform).exe + name: usvfs_proxy_exe +- path: bin\usvfs_proxy_$(Platform).pdb + name: usvfs_proxy_pdb +- path: lib\usvfs_$(Platform).dll + name: usvfs_dll +- path: lib\usvfs_$(Platform).lib + name: usvfs_lib +- path: lib\usvfs_$(Platform).pdb + name: usvfs_pdb +on_success: + - ps: Set-Location -Path $env:APPVEYOR_BUILD_FOLDER + - ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1 + - ps: ./send.ps1 success $env:WEBHOOK_URL on_failure: -- ps: $blockRdp = $false; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) \ No newline at end of file + - ps: Set-Location -Path $env:APPVEYOR_BUILD_FOLDER + - ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1 + - ps: ./send.ps1 failure $env:WEBHOOK_URL \ No newline at end of file From 955207fa1a4bf3e2bc40d635b2f25089babd0f76 Mon Sep 17 00:00:00 2001 From: LePresidente Date: Fri, 8 Mar 2019 10:32:46 +0200 Subject: [PATCH 09/15] Fix appveyor build. --- appveyor.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index a981adbd..1043233c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,10 +9,8 @@ environment: BOOST_PATH: C:\Libraries\boost_1_69_0 WEBHOOK_URL: secure: gOKbXaZM9ImtMD5XrYITvdyZUW/az082G9OIN1EC1VZ2CuYaUUM6WY2eiNxaFeOL7/9Jyu/m+Vm1fH54CEyigcUUaxA7d8F5IMWlOgE/7YYdaAFSMUTFD7EK+++3FBYfmU1F/nZ61wsiWE6hB9Au5FpGBSCeQ0Tf8U8m0ybPmD0= -install: -- ps: $blockRdp = $false; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) before_build: -- ps: "echo $env:Platform\nif (${env:Platform} -eq \"x64\") {\n set-item -path Env:BOOST_LIBPATH -value C:\\Libraries\\boost_1_69_0\\lib64-msvc-14.1\n set-item -path Env:GTEST_BUILDDIR -value c:\\libraries\\googletest\\build \n}\nElse {\n set-item -path Env:BOOST_LIBPATH -value C:\\Libraries\\boost_1_69_0\\lib32-msvc-14.1\n set-item -path Env:GTEST_BUILDDIR -value c:\\libraries\\googletest\\build_32\n}\n\n. git clone --depth=1 --branch=master https://github.com/google/googletest.git c:\\libraries\\googletest 2> $null\nNew-Item $Env:GTEST_BUILDDIR -type directory\nSet-Location -Path $Env:GTEST_BUILDDIR\n. cmd /c \"`\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat`\" $env:Platform && cmake -G `\"NMake Makefiles`\" -DCMAKE_BUILD_TYPE=Release ..`\"\"\n. cmd /c \"`\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat`\" $env:Platform && nmake\"\n\nSet-Location -Path c:\\projects\\usvfs\\vsbuild\n#Tempory fix due to that appveyor has boost lib files in a custom output folder\nGet-ChildItem -Path *.props -recurse | ForEach {If (Get-Content $_.FullName | Select-String -Pattern '\\$\\(BOOST_PATH\\)\\\\.*\\\\lib') {(Get-Content $_ | ForEach {$_ -replace '\\$\\(BOOST_PATH\\)\\\\.*\\\\lib', \"$Env:BOOST_LIBPATH\"}) | Set-Content $_ }}\n\n#Need to tell usvfs to not use the BOOST_BUILDID\nGet-ChildItem -Path *.props -recurse | ForEach {If (Get-Content $_.FullName | Select-String -Pattern 'BOOST_LIB_BUILDID=x86;') {(Get-Content $_ | ForEach {$_ -replace 'BOOST_LIB_BUILDID=x86;', \"\"}) | Set-Content $_ }}\n\nSet-Location -Path c:\\projects\\usvfs\ngit submodule -q update --init --recursive\nSet-Location -Path c:\\projects\\usvfs\\udis86\ngit pull -q origin master" +- ps: "echo $env:Platform\nif (${env:Platform} -eq \"x64\") {\n set-item -path Env:BOOST_LIBPATH -value ${Env:BOOST_PATH}\\lib64-msvc-14.1\n set-item -path Env:GTEST_BUILDDIR -value c:\\libraries\\googletest\\build \n}\nElse {\n set-item -path Env:BOOST_LIBPATH -value ${Env:BOOST_PATH}\\lib32-msvc-14.1\n set-item -path Env:GTEST_BUILDDIR -value c:\\libraries\\googletest\\build_32\n}\n\n. git clone --depth=1 --branch=master https://github.com/google/googletest.git c:\\libraries\\googletest 2> $null\nNew-Item $Env:GTEST_BUILDDIR -type directory\nSet-Location -Path $Env:GTEST_BUILDDIR\n. cmd /c \"`\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat`\" $env:Platform && cmake -G `\"NMake Makefiles`\" -DCMAKE_BUILD_TYPE=Release ..`\"\"\n. cmd /c \"`\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat`\" $env:Platform && nmake\"\n\nSet-Location -Path c:\\projects\\usvfs\\vsbuild\n#Tempory fix due to that appveyor has boost lib files in a custom output folder\nGet-ChildItem -Path *.props -recurse | ForEach {If (Get-Content $_.FullName | Select-String -Pattern '\$\(BOOST_PATH\)\\.*\\lib') {(Get-Content $_ | ForEach {$_ -replace '\$\(BOOST_PATH\)\\.*\\lib', \"$Env:BOOST_LIBPATH\"}) | Set-Content $_ }}\n\n#Need to tell usvfs to not use the BOOST_BUILDID\nGet-ChildItem -Path *.props -recurse | ForEach {If (Get-Content $_.FullName | Select-String -Pattern 'BOOST_LIB_BUILDID=x86;') {(Get-Content $_ | ForEach {$_ -replace 'BOOST_LIB_BUILDID=x86;', \"\"}) | Set-Content $_ }}\n\nSet-Location -Path c:\\projects\\usvfs\ngit submodule -q update --init --recursive\n\n$blockRdp = $false; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))" build: project: vsbuild/usvfs.sln parallel: true From 9aeb6e98d084ae6967b3eeea69ab35d352b0e2e2 Mon Sep 17 00:00:00 2001 From: LePresidente Date: Fri, 8 Mar 2019 11:06:02 +0200 Subject: [PATCH 10/15] appveyor fixes escaped chars. --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 1043233c..a1b2d15c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,7 +10,7 @@ environment: WEBHOOK_URL: secure: gOKbXaZM9ImtMD5XrYITvdyZUW/az082G9OIN1EC1VZ2CuYaUUM6WY2eiNxaFeOL7/9Jyu/m+Vm1fH54CEyigcUUaxA7d8F5IMWlOgE/7YYdaAFSMUTFD7EK+++3FBYfmU1F/nZ61wsiWE6hB9Au5FpGBSCeQ0Tf8U8m0ybPmD0= before_build: -- ps: "echo $env:Platform\nif (${env:Platform} -eq \"x64\") {\n set-item -path Env:BOOST_LIBPATH -value ${Env:BOOST_PATH}\\lib64-msvc-14.1\n set-item -path Env:GTEST_BUILDDIR -value c:\\libraries\\googletest\\build \n}\nElse {\n set-item -path Env:BOOST_LIBPATH -value ${Env:BOOST_PATH}\\lib32-msvc-14.1\n set-item -path Env:GTEST_BUILDDIR -value c:\\libraries\\googletest\\build_32\n}\n\n. git clone --depth=1 --branch=master https://github.com/google/googletest.git c:\\libraries\\googletest 2> $null\nNew-Item $Env:GTEST_BUILDDIR -type directory\nSet-Location -Path $Env:GTEST_BUILDDIR\n. cmd /c \"`\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat`\" $env:Platform && cmake -G `\"NMake Makefiles`\" -DCMAKE_BUILD_TYPE=Release ..`\"\"\n. cmd /c \"`\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat`\" $env:Platform && nmake\"\n\nSet-Location -Path c:\\projects\\usvfs\\vsbuild\n#Tempory fix due to that appveyor has boost lib files in a custom output folder\nGet-ChildItem -Path *.props -recurse | ForEach {If (Get-Content $_.FullName | Select-String -Pattern '\$\(BOOST_PATH\)\\.*\\lib') {(Get-Content $_ | ForEach {$_ -replace '\$\(BOOST_PATH\)\\.*\\lib', \"$Env:BOOST_LIBPATH\"}) | Set-Content $_ }}\n\n#Need to tell usvfs to not use the BOOST_BUILDID\nGet-ChildItem -Path *.props -recurse | ForEach {If (Get-Content $_.FullName | Select-String -Pattern 'BOOST_LIB_BUILDID=x86;') {(Get-Content $_ | ForEach {$_ -replace 'BOOST_LIB_BUILDID=x86;', \"\"}) | Set-Content $_ }}\n\nSet-Location -Path c:\\projects\\usvfs\ngit submodule -q update --init --recursive\n\n$blockRdp = $false; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))" +- ps: "echo $env:Platform\nif (${env:Platform} -eq \"x64\") {\n set-item -path Env:BOOST_LIBPATH -value ${Env:BOOST_PATH}\\lib64-msvc-14.1\n set-item -path Env:GTEST_BUILDDIR -value c:\\libraries\\googletest\\build \n}\nElse {\n set-item -path Env:BOOST_LIBPATH -value ${Env:BOOST_PATH}\\lib32-msvc-14.1\n set-item -path Env:GTEST_BUILDDIR -value c:\\libraries\\googletest\\build_32\n}\n\n. git clone --depth=1 --branch=master https://github.com/google/googletest.git c:\\libraries\\googletest 2> $null\nNew-Item $Env:GTEST_BUILDDIR -type directory\nSet-Location -Path $Env:GTEST_BUILDDIR\n. cmd /c \"`\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat`\" $env:Platform && cmake -G `\"NMake Makefiles`\" -DCMAKE_BUILD_TYPE=Release ..`\"\"\n. cmd /c \"`\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat`\" $env:Platform && nmake\"\n\nSet-Location -Path c:\\projects\\usvfs\\vsbuild\n#Tempory fix due to that appveyor has boost lib files in a custom output folder\nGet-ChildItem -Path *.props -recurse | ForEach {If (Get-Content $_.FullName | Select-String -Pattern '\\$\\(BOOST_PATH\\)\\\\.*\\\\lib') {(Get-Content $_ | ForEach {$_ -replace '\\$\\(BOOST_PATH\\)\\\\.*\\\\', \"$Env:BOOST_LIBPATH\"}) | Set-Content $_ }}\n\n#Need to tell usvfs to not use the BOOST_BUILDID\nGet-ChildItem -Path *.props -recurse | ForEach {If (Get-Content $_.FullName | Select-String -Pattern 'BOOST_LIB_BUILDID=x86;') {(Get-Content $_ | ForEach {$_ -replace 'BOOST_LIB_BUILDID=x86;', \"\"}) | Set-Content $_ }}\n\nSet-Location -Path c:\\projects\\usvfs\ngit submodule -q update --init --recursive\n\n$blockRdp = $false; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))" build: project: vsbuild/usvfs.sln parallel: true From f7ee81f439c0767dd942b9f1b486ad4326fd222a Mon Sep 17 00:00:00 2001 From: LePresidente Date: Fri, 8 Mar 2019 11:17:19 +0200 Subject: [PATCH 11/15] Fixed the replace to also remove the /lib folder from string. --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index a1b2d15c..1a531dc9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,7 +10,7 @@ environment: WEBHOOK_URL: secure: gOKbXaZM9ImtMD5XrYITvdyZUW/az082G9OIN1EC1VZ2CuYaUUM6WY2eiNxaFeOL7/9Jyu/m+Vm1fH54CEyigcUUaxA7d8F5IMWlOgE/7YYdaAFSMUTFD7EK+++3FBYfmU1F/nZ61wsiWE6hB9Au5FpGBSCeQ0Tf8U8m0ybPmD0= before_build: -- ps: "echo $env:Platform\nif (${env:Platform} -eq \"x64\") {\n set-item -path Env:BOOST_LIBPATH -value ${Env:BOOST_PATH}\\lib64-msvc-14.1\n set-item -path Env:GTEST_BUILDDIR -value c:\\libraries\\googletest\\build \n}\nElse {\n set-item -path Env:BOOST_LIBPATH -value ${Env:BOOST_PATH}\\lib32-msvc-14.1\n set-item -path Env:GTEST_BUILDDIR -value c:\\libraries\\googletest\\build_32\n}\n\n. git clone --depth=1 --branch=master https://github.com/google/googletest.git c:\\libraries\\googletest 2> $null\nNew-Item $Env:GTEST_BUILDDIR -type directory\nSet-Location -Path $Env:GTEST_BUILDDIR\n. cmd /c \"`\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat`\" $env:Platform && cmake -G `\"NMake Makefiles`\" -DCMAKE_BUILD_TYPE=Release ..`\"\"\n. cmd /c \"`\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat`\" $env:Platform && nmake\"\n\nSet-Location -Path c:\\projects\\usvfs\\vsbuild\n#Tempory fix due to that appveyor has boost lib files in a custom output folder\nGet-ChildItem -Path *.props -recurse | ForEach {If (Get-Content $_.FullName | Select-String -Pattern '\\$\\(BOOST_PATH\\)\\\\.*\\\\lib') {(Get-Content $_ | ForEach {$_ -replace '\\$\\(BOOST_PATH\\)\\\\.*\\\\', \"$Env:BOOST_LIBPATH\"}) | Set-Content $_ }}\n\n#Need to tell usvfs to not use the BOOST_BUILDID\nGet-ChildItem -Path *.props -recurse | ForEach {If (Get-Content $_.FullName | Select-String -Pattern 'BOOST_LIB_BUILDID=x86;') {(Get-Content $_ | ForEach {$_ -replace 'BOOST_LIB_BUILDID=x86;', \"\"}) | Set-Content $_ }}\n\nSet-Location -Path c:\\projects\\usvfs\ngit submodule -q update --init --recursive\n\n$blockRdp = $false; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))" +- ps: "echo $env:Platform\nif (${env:Platform} -eq \"x64\") {\n set-item -path Env:BOOST_LIBPATH -value ${Env:BOOST_PATH}\\lib64-msvc-14.1\n set-item -path Env:GTEST_BUILDDIR -value c:\\libraries\\googletest\\build \n}\nElse {\n set-item -path Env:BOOST_LIBPATH -value ${Env:BOOST_PATH}\\lib32-msvc-14.1\n set-item -path Env:GTEST_BUILDDIR -value c:\\libraries\\googletest\\build_32\n}\n\n. git clone --depth=1 --branch=master https://github.com/google/googletest.git c:\\libraries\\googletest 2> $null\nNew-Item $Env:GTEST_BUILDDIR -type directory\nSet-Location -Path $Env:GTEST_BUILDDIR\n. cmd /c \"`\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat`\" $env:Platform && cmake -G `\"NMake Makefiles`\" -DCMAKE_BUILD_TYPE=Release ..`\"\"\n. cmd /c \"`\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat`\" $env:Platform && nmake\"\n\nSet-Location -Path c:\\projects\\usvfs\\vsbuild\n#Tempory fix due to that appveyor has boost lib files in a custom output folder\nGet-ChildItem -Path *.props -recurse | ForEach {If (Get-Content $_.FullName | Select-String -Pattern '\\$\\(BOOST_PATH\\)\\\\.*\\\\lib') {(Get-Content $_ | ForEach {$_ -replace '\\$\\(BOOST_PATH\\)\\\\.*\\\\lib', \"$Env:BOOST_LIBPATH\"}) | Set-Content $_ }}\n\n#Need to tell usvfs to not use the BOOST_BUILDID\nGet-ChildItem -Path *.props -recurse | ForEach {If (Get-Content $_.FullName | Select-String -Pattern 'BOOST_LIB_BUILDID=x86;') {(Get-Content $_ | ForEach {$_ -replace 'BOOST_LIB_BUILDID=x86;', \"\"}) | Set-Content $_ }}\n\nSet-Location -Path c:\\projects\\usvfs\ngit submodule -q update --init --recursive" build: project: vsbuild/usvfs.sln parallel: true From 6d20cc305e15486a9a81a8811f1c09bb2eb34b98 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sun, 10 Mar 2019 23:32:21 -0500 Subject: [PATCH 12/15] Keep track of deleted directories --- src/usvfs_dll/hooks/kernel32.cpp | 111 ++++++++++++++-------------- src/usvfs_helper/inject.cpp | 2 +- test/usvfs_test/usvfs_test_base.cpp | 2 +- 3 files changed, 56 insertions(+), 59 deletions(-) diff --git a/src/usvfs_dll/hooks/kernel32.cpp b/src/usvfs_dll/hooks/kernel32.cpp index e1a0ed07..60230bbe 100644 --- a/src/usvfs_dll/hooks/kernel32.cpp +++ b/src/usvfs_dll/hooks/kernel32.cpp @@ -296,20 +296,17 @@ class RerouteW // We need to track deleted files even if they were not rerouted (i.e. files deleted from the real folder which there is // a virtualized mapped folder on top of it). Since we don't want to add, *every* file which is deleted we check this: - if (!directory) { - bool found = wasRerouted(); - if (!found) - { - FindCreateTarget visitor; - usvfs::RedirectionTree::VisitorFunction visitorWrapper = - [&](const usvfs::RedirectionTree::NodePtrT &node) { visitor(node); }; - readContext->redirectionTable()->visitPath(m_RealPath, visitorWrapper); - if (visitor.target.get()) - found = true; - } - if (found) - addToDelete = true; + bool found = wasRerouted(); + if (!found) { + FindCreateTarget visitor; + usvfs::RedirectionTree::VisitorFunction visitorWrapper = + [&](const usvfs::RedirectionTree::NodePtrT &node) { visitor(node); }; + readContext->redirectionTable()->visitPath(m_RealPath, visitorWrapper); + if (visitor.target.get()) + found = true; } + if (found) + addToDelete = true; if (wasRerouted()) { if (m_FileNode.get()) @@ -1284,7 +1281,7 @@ DWORD WINAPI usvfs::hook_GetFileAttributesW(LPCWSTR lpFileName) } DWORD WINAPI usvfs::hook_SetFileAttributesW( - LPCWSTR lpFileName, DWORD dwFileAttributes) + LPCWSTR lpFileName, DWORD dwFileAttributes) { DWORD res = 0UL; @@ -1627,34 +1624,34 @@ BOOL WINAPI usvfs::hook_MoveFileWithProgressW(LPCWSTR lpExistingFileName, LPCWST bool movedDrives = rewriteChangedDrives(lpExistingFileName, lpNewFileName, readReroute, writeReroute); if (movedDrives) newFlags |= MOVEFILE_COPY_ALLOWED; - bool isDirectory = pathIsDirectory(readReroute.fileName()); + bool isDirectory = pathIsDirectory(readReroute.fileName()); PRE_REALCALL - if (isDirectory && movedDrives) { - SHFILEOPSTRUCTW sf = { 0 }; - sf.wFunc = FO_MOVE; - sf.hwnd = 0; - sf.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI; - sf.pFrom = readReroute.fileName(); - sf.pTo = writeReroute.fileName(); - int shRes = ::SHFileOperationW(&sf); - switch (shRes) { - case 0x78: - callContext.updateLastError(ERROR_ACCESS_DENIED); - break; - case 0x7C: - callContext.updateLastError(ERROR_FILE_NOT_FOUND); - break; - case 0x7E: - case 0x80: - callContext.updateLastError(ERROR_FILE_EXISTS); - break; - default: - callContext.updateLastError(shRes); - } - res = shRes == 0; - } else - res = ::MoveFileWithProgressW(readReroute.fileName(), writeReroute.fileName(), lpProgressRoutine, lpData, newFlags); + if (isDirectory && movedDrives) { + SHFILEOPSTRUCTW sf = { 0 }; + sf.wFunc = FO_MOVE; + sf.hwnd = 0; + sf.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI; + sf.pFrom = readReroute.fileName(); + sf.pTo = writeReroute.fileName(); + int shRes = ::SHFileOperationW(&sf); + switch (shRes) { + case 0x78: + callContext.updateLastError(ERROR_ACCESS_DENIED); + break; + case 0x7C: + callContext.updateLastError(ERROR_FILE_NOT_FOUND); + break; + case 0x7E: + case 0x80: + callContext.updateLastError(ERROR_FILE_EXISTS); + break; + default: + callContext.updateLastError(shRes); + } + res = shRes == 0; + } else + res = ::MoveFileWithProgressW(readReroute.fileName(), writeReroute.fileName(), lpProgressRoutine, lpData, newFlags); POST_REALCALL if (res) SetLastError(ERROR_SUCCESS); @@ -1889,31 +1886,31 @@ DLLEXPORT BOOL WINAPI usvfs::hook_CreateDirectoryW( } DLLEXPORT BOOL WINAPI usvfs::hook_RemoveDirectoryW( - LPCWSTR lpPathName) + LPCWSTR lpPathName) { - BOOL res = FALSE; + BOOL res = FALSE; - HOOK_START_GROUP(MutExHookGroup::DELETE_FILE) + HOOK_START_GROUP(MutExHookGroup::DELETE_FILE) - RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, lpPathName); + RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, lpPathName); - PRE_REALCALL - if (reroute.wasRerouted()) { - res = ::RemoveDirectoryW(reroute.fileName()); - } - else { - res = ::RemoveDirectoryW(lpPathName); - } - POST_REALCALL + PRE_REALCALL + if (reroute.wasRerouted()) { + res = ::RemoveDirectoryW(reroute.fileName()); + } + else { + res = ::RemoveDirectoryW(lpPathName); + } + POST_REALCALL - reroute.removeMapping(READ_CONTEXT(), true); - if (reroute.wasRerouted()) - LOG_CALL().PARAMWRAP(lpPathName).PARAMWRAP(reroute.fileName()).PARAM(res).PARAM(callContext.lastError()); + reroute.removeMapping(READ_CONTEXT(), true); + if (reroute.wasRerouted()) + LOG_CALL().PARAMWRAP(lpPathName).PARAMWRAP(reroute.fileName()).PARAM(res).PARAM(callContext.lastError()); - HOOK_END + HOOK_END - return res; + return res; } DWORD WINAPI usvfs::hook_GetFullPathNameA(LPCSTR lpFileName, DWORD nBufferLength, LPSTR lpBuffer, LPSTR *lpFilePart) diff --git a/src/usvfs_helper/inject.cpp b/src/usvfs_helper/inject.cpp index 8fbe5306..0f275094 100644 --- a/src/usvfs_helper/inject.cpp +++ b/src/usvfs_helper/inject.cpp @@ -108,7 +108,7 @@ void usvfs::injectProcess(const std::wstring &applicationPath InjectLib::InjectDLL(processHandle, threadHandle, dllPath.c_str(), "InitHooks", ¶meters, sizeof(USVFSParameters)); - spdlog::get("usvfs")->info("injection to same bitness process {} successfull", ::GetProcessId(processHandle)); + spdlog::get("usvfs")->info("injection to same bitness process {} successful", ::GetProcessId(processHandle)); } else { // first try platform specific proxy exe: static constexpr auto USVFS_PREFERED_EXE = diff --git a/test/usvfs_test/usvfs_test_base.cpp b/test/usvfs_test/usvfs_test_base.cpp index f63a39b7..e8ba3d3c 100644 --- a/test/usvfs_test/usvfs_test_base.cpp +++ b/test/usvfs_test/usvfs_test_base.cpp @@ -462,7 +462,7 @@ bool usvfs_test_base::postmortem_check() recursive_compare_dirs(path(), m_o.fixture / source_gold, m_o.source, log); if (mount_check && source_check) - fprintf(log, "postmortem check successfull.\n"); + fprintf(log, "postmortem check successful.\n"); else { fprintf(log, "ERROR: postmortem check failed!\n"); return false; From 5df4a82ce6830d3468a320b8a2a42b6662a892f9 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Tue, 12 Mar 2019 21:36:23 -0500 Subject: [PATCH 13/15] Reimplement hook_NtCreateFile to allow it to create reroutes --- src/shared/ntdll_declarations.h | 1 + src/shared/stringcast_basic.h | 2 + src/usvfs_dll/hookmanager.cpp | 7 - src/usvfs_dll/hooks/kernel32.cpp | 812 +----------------------------- src/usvfs_dll/hooks/kernel32.h | 6 - src/usvfs_dll/hooks/ntdll.cpp | 213 ++++---- src/usvfs_dll/maptracker.h | 650 ++++++++++++++++++++++++ test/tvfs_test/main.cpp | 4 + vsbuild/usvfs_dll.vcxproj | 1 + vsbuild/usvfs_dll.vcxproj.filters | 3 + 10 files changed, 792 insertions(+), 907 deletions(-) create mode 100644 src/usvfs_dll/maptracker.h diff --git a/src/shared/ntdll_declarations.h b/src/shared/ntdll_declarations.h index 0f262b3f..1cc096ba 100644 --- a/src/shared/ntdll_declarations.h +++ b/src/shared/ntdll_declarations.h @@ -141,6 +141,7 @@ typedef struct _FILE_REPARSE_POINT_INFORMATION { ULONG Tag; } FILE_REPARSE_POINT_INFORMATION, *PFILE_REPARSE_POINT_INFORMATION; +// copied from ntstatus.h #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) #define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L) #define STATUS_NO_MORE_FILES ((NTSTATUS)0x80000006L) diff --git a/src/shared/stringcast_basic.h b/src/shared/stringcast_basic.h index 0dde7d28..70e5e708 100644 --- a/src/shared/stringcast_basic.h +++ b/src/shared/stringcast_basic.h @@ -18,6 +18,8 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with usvfs. If not, see . */ +#pragma once + #include #include #include diff --git a/src/usvfs_dll/hookmanager.cpp b/src/usvfs_dll/hookmanager.cpp index 1ab0cf14..7bb1634b 100644 --- a/src/usvfs_dll/hookmanager.cpp +++ b/src/usvfs_dll/hookmanager.cpp @@ -231,13 +231,6 @@ void HookManager::initHooks() installHook(kbaseMod, k32Mod, "GetFileAttributesW", hook_GetFileAttributesW); installHook(kbaseMod, k32Mod, "SetFileAttributesW", hook_SetFileAttributesW); - // Unfortunately, at least on windows 10 1709 x64 the CreateFileA and CreateFile2 translate - // to CreateFileInternal directly (so hooking CreateFileW alone is not enough) - installHook(kbaseMod, k32Mod, "CreateFileW", hook_CreateFileW); - installHook(kbaseMod, k32Mod, "CreateFileA", hook_CreateFileA); - if (IsWindows8OrGreater()) - installHook(kbaseMod, k32Mod, "CreateFile2", hook_CreateFile2, reinterpret_cast(&CreateFile2)); - installHook(kbaseMod, k32Mod, "CreateDirectoryW", hook_CreateDirectoryW); installHook(kbaseMod, k32Mod, "RemoveDirectoryW", hook_RemoveDirectoryW); installHook(kbaseMod, k32Mod, "DeleteFileW", hook_DeleteFileW); diff --git a/src/usvfs_dll/hooks/kernel32.cpp b/src/usvfs_dll/hooks/kernel32.cpp index 60230bbe..485bacb9 100644 --- a/src/usvfs_dll/hooks/kernel32.cpp +++ b/src/usvfs_dll/hooks/kernel32.cpp @@ -4,6 +4,8 @@ #include "../hookmanager.h" #include "../hookcontext.h" #include "../hookcallcontext.h" +#include "../maptracker.h" + #include #include #include @@ -30,54 +32,10 @@ namespace ush = usvfs::shared; using ush::string_cast; using ush::CodePage; -class MapTracker { -public: - using wstring = std::wstring; - - wstring lookup(const wstring& fromPath) const { - if (!fromPath.empty()) - { - std::shared_lock lock(m_mutex); - auto find = m_map.find(fromPath); - if (find != m_map.end()) - return find->second; - } - return wstring(); - } - - bool contains(const wstring& fromPath) const { - if (!fromPath.empty()) - { - std::shared_lock lock(m_mutex); - auto find = m_map.find(fromPath); - if (find != m_map.end()) - return true; - } - return false; - } - - void insert(const wstring& fromPath, const wstring& toPath) { - if (fromPath.empty()) - return; - std::unique_lock lock(m_mutex); - m_map[fromPath] = toPath; - } - - bool erase(const wstring& fromPath) - { - if (fromPath.empty()) - return false; - std::unique_lock lock(m_mutex); - return m_map.erase(fromPath); - } - -private: - mutable std::shared_mutex m_mutex; - std::unordered_map m_map; -}; - +namespace usvfs { MapTracker k32DeleteTracker; MapTracker k32FakeDirTracker; +} // namespace usvfs class CurrentDirectoryTracker { public: @@ -131,37 +89,9 @@ class CurrentDirectoryTracker { CurrentDirectoryTracker k32CurrentDirectoryTracker; -// returns true iff the path exists (checks only real paths) -static inline bool pathExists(LPCWSTR fileName) -{ - usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::FILE_ATTRIBUTES); - DWORD attrib = GetFileAttributesW(fileName); - return attrib != INVALID_FILE_ATTRIBUTES; -} -// returns true iff the path exists and is a file (checks only real paths) -static inline bool pathIsFile(LPCWSTR fileName) -{ - usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::FILE_ATTRIBUTES); - DWORD attrib = GetFileAttributesW(fileName); - return attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) == 0; -} -// returns true iff the path exists and is a file (checks only real paths) -static inline bool pathIsDirectory(LPCWSTR fileName) -{ - usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::FILE_ATTRIBUTES); - DWORD attrib = GetFileAttributesW(fileName); - return attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY); -} -// returns true iff the path does not exist but it parent directory does (checks only real paths) -static inline bool pathDirectlyAvailable(LPCWSTR pathName) -{ - usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::FILE_ATTRIBUTES); - DWORD attrib = GetFileAttributesW(pathName); - return attrib == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; -} // attempts to copy source to destination and return the error code static inline DWORD copyFileDirect(LPCWSTR source, LPCWSTR destination, bool overwrite) @@ -201,442 +131,7 @@ static inline bool pathsOnDifferentDrives(LPCWSTR path1, LPCWSTR path2) return drive1 && drive2 && towupper(drive1) != towupper(drive2); } -class RerouteW -{ - std::wstring m_Buffer{}; - std::wstring m_RealPath{}; - bool m_Rerouted{false}; - LPCWSTR m_FileName{nullptr}; - bool m_PathCreated{false}; - bool m_NewReroute{false}; - - usvfs::RedirectionTree::NodePtrT m_FileNode; - -public: - RerouteW() = default; - - RerouteW(RerouteW &&reference) - : m_Buffer(std::move(reference.m_Buffer)) - , m_RealPath(std::move(reference.m_RealPath)) - , m_Rerouted(reference.m_Rerouted) - , m_PathCreated(reference.m_PathCreated) - , m_NewReroute(reference.m_NewReroute) - , m_FileNode(std::move(reference.m_FileNode)) - { - m_FileName = reference.m_FileName != nullptr ? m_Buffer.c_str() : nullptr; - reference.m_FileName = nullptr; - } - - RerouteW &operator=(RerouteW &&reference) - { - m_Buffer = std::move(reference.m_Buffer); - m_RealPath = std::move(reference.m_RealPath); - m_Rerouted = reference.m_Rerouted; - m_PathCreated = reference.m_PathCreated; - m_NewReroute = reference.m_NewReroute; - m_FileName = reference.m_FileName != nullptr ? m_Buffer.c_str() : nullptr; - m_FileNode = std::move(reference.m_FileNode); - return *this; - } - - RerouteW(const RerouteW &reference) = delete; - RerouteW &operator=(const RerouteW &) = delete; - - LPCWSTR fileName() const - { - return m_FileName; - } - - const std::wstring &buffer() const - { - return m_Buffer; - } - - bool wasRerouted() const - { - return m_Rerouted; - } - - bool newReroute() const - { - return m_NewReroute; - } - - void insertMapping(const usvfs::HookContext::Ptr &context, bool directory = false) - { - if (directory) - { - addDirectoryMapping(context, m_RealPath, m_FileName); - - // In case we have just created a "fake" directory, it is no longer fake and need to remove it and all its - // parent folders from the fake map: - std::wstring dir = m_FileName; - while (k32FakeDirTracker.erase(dir)) - dir = fs::path(dir).parent_path().wstring(); - } - else - { - //if (m_PathCreated) - //addDirectoryMapping(context, fs::path(m_RealPath).parent_path(), fs::path(m_FileName).parent_path()); - - spdlog::get("hooks")->info("mapping file in vfs: {}, {}", - ush::string_cast(m_RealPath, ush::CodePage::UTF8), - ush::string_cast(m_FileName, ush::CodePage::UTF8)); - m_FileNode = - context->redirectionTable().addFile(m_RealPath, usvfs::RedirectionDataLocal(string_cast(m_FileName, CodePage::UTF8))); - - k32DeleteTracker.erase(m_RealPath); - } - } - - void removeMapping(const usvfs::HookContext::ConstPtr &readContext, bool directory = false) - { - bool addToDelete = false; - bool dontAddToDelete = false; - - // We need to track deleted files even if they were not rerouted (i.e. files deleted from the real folder which there is - // a virtualized mapped folder on top of it). Since we don't want to add, *every* file which is deleted we check this: - bool found = wasRerouted(); - if (!found) { - FindCreateTarget visitor; - usvfs::RedirectionTree::VisitorFunction visitorWrapper = - [&](const usvfs::RedirectionTree::NodePtrT &node) { visitor(node); }; - readContext->redirectionTable()->visitPath(m_RealPath, visitorWrapper); - if (visitor.target.get()) - found = true; - } - if (found) - addToDelete = true; - - if (wasRerouted()) { - if (m_FileNode.get()) - m_FileNode->removeFromTree(); - else - spdlog::get("usvfs")->warn("Node not removed: {}", string_cast(m_FileName)); - - if (!directory) - { - // check if this file was the last file inside a "fake" directory then remove it - // and possibly also its fake empty parent folders: - std::wstring parent = m_FileName; - while (true) - { - parent = fs::path(parent).parent_path().wstring(); - if (k32FakeDirTracker.contains(parent)) - { - dontAddToDelete = true; - if (RemoveDirectoryW(parent.c_str())) { - k32FakeDirTracker.erase(parent); - spdlog::get("usvfs")->info("removed empty fake directory: {}", string_cast(parent)); - } - else if (GetLastError() != ERROR_DIR_NOT_EMPTY) { - auto error = GetLastError(); - spdlog::get("usvfs")->warn("removing fake directory failed: {}, error={}", string_cast(parent), error); - break; - } - } - else - break; - } - } - } - if (addToDelete && !dontAddToDelete) { - k32DeleteTracker.insert(m_RealPath, m_FileName); - } - } - - static bool createFakePath(fs::path path, LPSECURITY_ATTRIBUTES securityAttributes) - { - // sanity and guaranteed recursion end: - if (!path.has_relative_path()) - throw usvfs::shared::windows_error("createFakePath() refusing to create non-existing top level path: " + path.string()); - - DWORD attr = GetFileAttributesW(path.c_str()); - DWORD err = GetLastError(); - if (attr != INVALID_FILE_ATTRIBUTES) { - if (attr & FILE_ATTRIBUTE_DIRECTORY) - return false; // if directory already exists all is good - else - throw usvfs::shared::windows_error("createFakePath() called on a file: " + path.string()); - } - if (err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND) - throw usvfs::shared::windows_error("createFakePath() GetFileAttributesW failed on: " + path.string(), err); - - if (err != ERROR_FILE_NOT_FOUND) // ERROR_FILE_NOT_FOUND means parent directory already exists - createFakePath(path.parent_path(), securityAttributes); // otherwise create parent directory (recursively) - - BOOL res = CreateDirectoryW(path.c_str(), securityAttributes); - if (res) - k32FakeDirTracker.insert(path.wstring(), std::wstring()); - else { - err = GetLastError(); - throw usvfs::shared::windows_error("createFakePath() CreateDirectoryW failed on: " + path.string(), err); - } - return true; - } - - static bool addDirectoryMapping(const usvfs::HookContext::Ptr &context, const fs::path& originalPath, const fs::path& reroutedPath) - { - if (originalPath.empty() || reroutedPath.empty()) { - spdlog::get("hooks")->error("RerouteW::addDirectoryMapping failed: {}, {}", - string_cast(originalPath.wstring(), CodePage::UTF8).c_str(), - string_cast(reroutedPath.wstring(), CodePage::UTF8).c_str()); - return false; - } - - auto lookupParent = context->redirectionTable()->findNode(originalPath.parent_path()); - if (!lookupParent.get() || lookupParent->data().linkTarget.empty()) { - if (!addDirectoryMapping(context, originalPath.parent_path(), reroutedPath.parent_path())) - { - spdlog::get("hooks")->error("RerouteW::addDirectoryMapping failed: {}, {}", - string_cast(originalPath.wstring(), CodePage::UTF8).c_str(), - string_cast(reroutedPath.wstring(), CodePage::UTF8).c_str()); - return false; - } - } - - std::string reroutedU8 - = ush::string_cast(reroutedPath.wstring(), ush::CodePage::UTF8); - if (reroutedU8.empty() || reroutedU8[reroutedU8.size() - 1] != '\\') - reroutedU8 += "\\"; - - spdlog::get("hooks")->info("mapping directory in vfs: {}, {}", - ush::string_cast(originalPath.wstring(), ush::CodePage::UTF8), reroutedU8.c_str()); - - context->redirectionTable().addDirectory( - originalPath, usvfs::RedirectionDataLocal(reroutedU8), - usvfs::shared::FLAG_DIRECTORY|usvfs::shared::FLAG_CREATETARGET); - - fs::directory_iterator end_itr; - - // cycle through the directory - for (fs::directory_iterator itr(reroutedPath); itr != end_itr; ++itr) - { - // If it's not a directory, add it to the VFS, if it is recurse into it - if (is_regular_file(itr->path())) { - std::string fileReroutedU8 = ush::string_cast(itr->path().wstring(), ush::CodePage::UTF8); - spdlog::get("hooks")->info("mapping file in vfs: {}, {}", - ush::string_cast((originalPath / itr->path().filename()).wstring(), ush::CodePage::UTF8), - fileReroutedU8.c_str()); - context->redirectionTable().addFile(fs::path(originalPath / itr->path().filename()), usvfs::RedirectionDataLocal(fileReroutedU8)); - } else { - addDirectoryMapping(context, originalPath / itr->path().filename(), reroutedPath / itr->path().filename()); - } - } - - return true; - } - - template - static bool interestingPathImpl(const char_t *inPath) - { - if (!inPath || !inPath[0]) - return false; - // ignore \\.\ unless its a \\.\?: - if (inPath[0] == '\\' && inPath[1] == '\\' && inPath[2] == '.' && inPath[3] == '\\' && (!inPath[4] || inPath[5] != ':')) - return false; - // ignore L"hid#": - if ((inPath[0] == 'h' || inPath[0] == 'H') - && ((inPath[1] == 'i' || inPath[1] == 'I')) - && ((inPath[2] == 'd' || inPath[2] == 'D')) - && inPath[3] == '#') - return false; - return true; - } - - static bool interestingPath(const char* inPath) { return interestingPathImpl(inPath); } - static bool interestingPath(const wchar_t* inPath) { return interestingPathImpl(inPath); } - - static fs::path absolutePath(const wchar_t *inPath) - { - if (ush::startswith(inPath, LR"(\\?\)") || ush::startswith(inPath, LR"(\??\)")) { - inPath += 4; - return inPath; - } - else if ((ush::startswith(inPath, LR"(\\localhost\)") || ush::startswith(inPath, LR"(\\127.0.0.1\)")) && inPath[13] == L'$') { - std::wstring newPath; - newPath += towupper(inPath[12]); - newPath += L':'; - newPath += &inPath[14]; - return newPath; - } - else if (inPath[0] == L'\0' || inPath[1] == L':') { - return inPath; - } - else if (inPath[0] == L'\\' || inPath[0] == L'/') { - return fs::path(winapi::wide::getFullPathName(inPath).first); - } - WCHAR currentDirectory[MAX_PATH]; - ::GetCurrentDirectoryW(MAX_PATH, currentDirectory); - fs::path finalPath = fs::path(currentDirectory) / inPath; - return finalPath; - //winapi::wide::getFullPathName(inPath).first; - } - - static fs::path canonizePath(const fs::path& inPath) - { - fs::path p = inPath.lexically_normal(); - if (p.filename_is_dot()) - p = p.remove_filename(); - return p.make_preferred(); - } - - static RerouteW create(const usvfs::HookContext::ConstPtr &context, - const usvfs::HookCallContext &callContext, - const wchar_t *inPath, bool inverse = false) - { - RerouteW result; - - if (interestingPath(inPath) && callContext.active()) - { - const auto& lookupPath = canonizePath(absolutePath(inPath)); - result.m_RealPath = lookupPath.wstring(); - - result.m_Buffer = k32DeleteTracker.lookup(result.m_RealPath); - bool found = !result.m_Buffer.empty(); - if (found) { - spdlog::get("hooks")->info("Rerouting file open to location of deleted file: {}", - ush::string_cast(result.m_Buffer)); - result.m_NewReroute = true; - } else { - const usvfs::RedirectionTreeContainer &table - = inverse ? context->inverseTable() : context->redirectionTable(); - result.m_FileNode = table->findNode(lookupPath); - - if (result.m_FileNode.get() - && (!result.m_FileNode->data().linkTarget.empty() || result.m_FileNode->isDirectory())) - { - if (!result.m_FileNode->data().linkTarget.empty()) { - result.m_Buffer = string_cast( - result.m_FileNode->data().linkTarget.c_str(), CodePage::UTF8); - } - else - { - result.m_Buffer = result.m_FileNode->path().wstring(); - } - found = true; - } - } - if (found) { - result.m_Rerouted = true; - - wchar_t inIt = inPath[wcslen(inPath) - 1]; - std::wstring::iterator outIt = result.m_Buffer.end() - 1; - if ((*outIt == L'\\' || *outIt == L'/') && !(inIt == L'\\' || inIt == L'/')) - result.m_Buffer.erase(outIt); - std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\'); - } - else - result.m_Buffer = inPath; - } - else if (inPath) - result.m_Buffer = inPath; - - if (inPath) - result.m_FileName = result.m_Buffer.c_str(); - return result; - } - - static RerouteW createNew(const usvfs::HookContext::ConstPtr &context, - const usvfs::HookCallContext &callContext, - LPCWSTR inPath, bool createPath = true, - LPSECURITY_ATTRIBUTES securityAttributes = nullptr) - { - RerouteW result; - - if (interestingPath(inPath) && callContext.active()) - { - const auto& lookupPath = canonizePath(absolutePath(inPath)); - result.m_RealPath = lookupPath.wstring(); - - result.m_Buffer = k32DeleteTracker.lookup(result.m_RealPath); - bool found = !result.m_Buffer.empty(); - if (found) - spdlog::get("hooks")->info("Rerouting file creation to original location of deleted file: {}", - ush::string_cast(result.m_Buffer)); - else - { - FindCreateTarget visitor; - usvfs::RedirectionTree::VisitorFunction visitorWrapper = - [&](const usvfs::RedirectionTree::NodePtrT &node) { visitor(node); }; - context->redirectionTable()->visitPath(lookupPath, visitorWrapper); - if (visitor.target.get()) { - // the visitor has found the last (deepest in the directory hierarchy) - // create-target - fs::path relativePath - = ush::make_relative(visitor.target->path(), lookupPath); - result.m_Buffer = - (fs::path(visitor.target->data().linkTarget.c_str()) / relativePath).wstring(); - found = true; - } - } - - if (found) - { - if (createPath) { - try { - usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::ALL_GROUPS); - result.m_PathCreated = - createFakePath(fs::path(result.m_Buffer).parent_path(), securityAttributes); - } - catch (const std::exception &e) { - spdlog::get("hooks")->error("failed to create {}: {}", - ush::string_cast(result.m_Buffer), e.what()); - } - } - - wchar_t inIt = inPath[wcslen(inPath) - 1]; - std::wstring::iterator outIt = result.m_Buffer.end() - 1; - if ((*outIt == L'\\' || *outIt == L'/') && !(inIt == L'\\' || inIt == L'/')) - result.m_Buffer.erase(outIt); - std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\'); - result.m_Rerouted = true; - result.m_NewReroute = true; - } - else - result.m_Buffer = inPath; - } - else if (inPath) - result.m_Buffer = inPath; - - if (inPath) - result.m_FileName = result.m_Buffer.c_str(); - return result; - } - - static RerouteW createOrNew(const usvfs::HookContext::ConstPtr &context, const usvfs::HookCallContext &callContext, - LPCWSTR inPath, bool createPath = true, LPSECURITY_ATTRIBUTES securityAttributes = nullptr) - { - { - auto res = create(context, callContext, inPath); - if (res.wasRerouted() || !interestingPath(inPath) || !callContext.active() || pathExists(inPath)) - return std::move(res); - } - return createNew(context, callContext, inPath, createPath, securityAttributes); - } - - static RerouteW noReroute(LPCWSTR inPath) - { - RerouteW result; - if (inPath) - result.m_Buffer = inPath; - if (inPath && inPath[0] && !ush::startswith(inPath, L"hid#")) - std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\'); - result.m_FileName = result.m_Buffer.c_str(); - return result; - } -private: - struct FindCreateTarget { - usvfs::RedirectionTree::NodePtrT target; - void operator()(usvfs::RedirectionTree::NodePtrT node) - { - if (node->hasFlag(usvfs::shared::FLAG_CREATETARGET)) { - target = node; - } - } - }; -}; HMODULE WINAPI usvfs::hook_LoadLibraryExW(LPCWSTR lpFileName, HANDLE hFile, DWORD dwFlags) @@ -854,299 +349,6 @@ BOOL WINAPI usvfs::hook_CreateProcessInternalW( return res; } -HANDLE WINAPI usvfs::hook_CreateFileA( - LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, - LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, - DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) -{ - HANDLE res = INVALID_HANDLE_VALUE; - - HOOK_START_GROUP(MutExHookGroup::OPEN_FILE) - - if (!callContext.active()) { - res = CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, - lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); - callContext.updateLastError(); - return res; - } - - // release the MutExHookGroup::OPEN_FILE so that CreateFileW can process the request: - HOOK_END - HOOK_START - - const auto& fileName = ush::string_cast(lpFileName); - - PRE_REALCALL - res = CreateFileW(fileName.c_str(), dwDesiredAccess, dwShareMode, lpSecurityAttributes, - dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); - POST_REALCALL - - HOOK_END - - return res; -} - -namespace usvfs { - class CreateRerouter { - public: - bool rerouteCreate(const usvfs::HookContext::ConstPtr &context, const usvfs::HookCallContext &callContext, - LPCWSTR lpFileName, DWORD& dwCreationDisposition, DWORD dwDesiredAccess, LPSECURITY_ATTRIBUTES lpSecurityAttributes) - { - enum class Open { existing, create, empty }; - Open open = Open::existing; - - // Notice since we are calling our patched GetFileAttributesW here this will also check virtualized paths - DWORD virtAttr = GetFileAttributesW(lpFileName); - bool isFile = virtAttr != INVALID_FILE_ATTRIBUTES && (virtAttr & FILE_ATTRIBUTE_DIRECTORY) == 0; - m_isDir = virtAttr != INVALID_FILE_ATTRIBUTES && (virtAttr & FILE_ATTRIBUTE_DIRECTORY); - - switch (dwCreationDisposition) { - case CREATE_ALWAYS: - open = Open::create; - if (isFile || m_isDir) - m_error = ERROR_ALREADY_EXISTS; - break; - - case CREATE_NEW: - if (isFile || m_isDir) { - m_error = ERROR_FILE_EXISTS; - return false; - } - else - open = Open::create; - break; - - case OPEN_ALWAYS: - if (isFile || m_isDir) - m_error = ERROR_ALREADY_EXISTS; - else - open = Open::create; - break; - - case TRUNCATE_EXISTING: - if ((dwDesiredAccess & GENERIC_WRITE) == 0) { - m_error = ERROR_INVALID_PARAMETER; - return false; - } - if (isFile || m_isDir) - open = Open::empty; - // if !isFile we let the OS create function set the error value - break; - } - - if (m_isDir && pathIsDirectory(lpFileName)) - m_reroute = RerouteW::noReroute(lpFileName); - else - m_reroute = RerouteW::create(context, callContext, lpFileName); - - if (m_reroute.wasRerouted() && open == Open::create && pathIsDirectory(m_reroute.fileName())) - m_reroute = RerouteW::createNew(context, callContext, lpFileName, true, lpSecurityAttributes); - - if (!m_isDir && !isFile && !m_reroute.wasRerouted() && (open == Open::create || open == Open::empty)) - { - m_reroute = RerouteW::createNew(context, callContext, lpFileName, true, lpSecurityAttributes); - - bool newFile = !m_reroute.wasRerouted() && pathDirectlyAvailable(m_reroute.fileName()); - if (newFile && open == Open::empty) - // TRUNCATE_EXISTING will fail since the new file doesn't exist, so change disposition: - dwCreationDisposition = CREATE_ALWAYS; - } - - return true; - } - - // rerouteNew is used for rerouting the destination of copy/move operations. Assumes that the call will be skipped if false is returned. - bool rerouteNew(const usvfs::HookContext::ConstPtr &context, usvfs::HookCallContext &callContext, LPCWSTR lpFileName, bool replaceExisting, const char* hookName) - { - DWORD disposition = replaceExisting ? CREATE_ALWAYS : CREATE_NEW; - if (!rerouteCreate(context, callContext, lpFileName, disposition, GENERIC_WRITE, nullptr)) { - spdlog::get("hooks")->info( - "{} guaranteed failure, skipping original call: {}, replaceExisting={}, error={}", - hookName, ush::string_cast(lpFileName, ush::CodePage::UTF8), replaceExisting ? "true" : "false", error()); - - callContext.updateLastError(error()); - return false; - } - return true; - } - - void updateResult(usvfs::HookCallContext &callContext, bool success) - { - m_originalError = callContext.lastError(); - if (success) { - // m_error != ERROR_SUCCESS means we are overriding the error on success - if (m_error == ERROR_SUCCESS) - m_error = m_originalError; - } - else if (m_originalError == ERROR_PATH_NOT_FOUND && m_directlyAvailable) - m_error = ERROR_FILE_NOT_FOUND; - else - m_error = m_originalError; - if (m_error != m_originalError) - callContext.updateLastError(m_error); - } - - DWORD error() const { return m_error; } - DWORD originalError() const { return m_originalError; } - bool changedError() const { return m_error != m_originalError; } - - bool isDir() const { return m_isDir; } - bool newReroute() const { return m_reroute.newReroute(); } - bool wasRerouted() const { return m_reroute.wasRerouted(); } - LPCWSTR fileName() const { return m_reroute.fileName(); } - - void insertMapping(const usvfs::HookContext::Ptr &context, bool directory = false) { m_reroute.insertMapping(context, directory); } - - private: - DWORD m_error = ERROR_SUCCESS; - DWORD m_originalError = ERROR_SUCCESS; - bool m_directlyAvailable = false; - bool m_isDir = false; - RerouteW m_reroute; - }; -}; - -HANDLE WINAPI usvfs::hook_CreateFileW( - LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, - LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, - DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) -{ - HANDLE res = INVALID_HANDLE_VALUE; - - HOOK_START_GROUP(MutExHookGroup::OPEN_FILE) - - if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) { - res = ::CreateFileW(lpFileName, dwDesiredAccess, dwShareMode, - lpSecurityAttributes, dwCreationDisposition, - dwFlagsAndAttributes, hTemplateFile); - callContext.updateLastError(); - return res; - } - - if (dwFlagsAndAttributes & FILE_FLAG_DELETE_ON_CLOSE) { - spdlog::get("hooks")->warn("hook_CreateFileW: FILE_FLAG_DELETE_ON_CLOSE not supported"); - } - - DWORD originalDisposition = dwCreationDisposition; - CreateRerouter rerouter; - if (rerouter.rerouteCreate(READ_CONTEXT(), callContext, lpFileName, dwCreationDisposition, dwDesiredAccess, lpSecurityAttributes)) - { - PRE_REALCALL - res = ::CreateFileW(rerouter.fileName(), dwDesiredAccess, dwShareMode, - lpSecurityAttributes, dwCreationDisposition, - dwFlagsAndAttributes, hTemplateFile); - POST_REALCALL - rerouter.updateResult(callContext, res != INVALID_HANDLE_VALUE); - - if (res != INVALID_HANDLE_VALUE) { - if (rerouter.newReroute()) - rerouter.insertMapping(WRITE_CONTEXT()); - - if (rerouter.isDir() && rerouter.wasRerouted() && (dwFlagsAndAttributes & FILE_FLAG_BACKUP_SEMANTICS)) - { - // store the original search path for use during iteration - WRITE_CONTEXT() - ->customData(SearchHandles)[res] - = lpFileName; - } - } - - if (rerouter.wasRerouted() || rerouter.changedError() || originalDisposition != dwCreationDisposition) { - LOG_CALL() - .PARAMWRAP(lpFileName) - .PARAMWRAP(rerouter.fileName()) - .PARAMHEX(dwDesiredAccess) - .PARAMHEX(originalDisposition) - .PARAMHEX(dwCreationDisposition) - .PARAMHEX(dwFlagsAndAttributes) - .PARAMHEX(res) - .PARAM(rerouter.originalError()) - .PARAM(rerouter.error()); - } - } else { - spdlog::get("hooks")->info( - "hook_CreateFileW guaranteed failure, skipping original call: {}, disposition={}, access={}, error={}", - ush::string_cast(lpFileName, ush::CodePage::UTF8), - dwCreationDisposition, dwDesiredAccess, rerouter.error()); - - callContext.updateLastError(rerouter.error()); - } - - HOOK_END - - return res; -} - -HANDLE (WINAPI *usvfs::CreateFile2)(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition, LPCREATEFILE2_EXTENDED_PARAMETERS pCreateExParams); - -HANDLE WINAPI usvfs::hook_CreateFile2(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition, LPCREATEFILE2_EXTENDED_PARAMETERS pCreateExParams) -{ - HANDLE res = INVALID_HANDLE_VALUE; - - typedef HANDLE(WINAPI * CreateFile2_t)(LPCWSTR, DWORD, DWORD, DWORD, LPCREATEFILE2_EXTENDED_PARAMETERS); - - HOOK_START_GROUP(MutExHookGroup::OPEN_FILE) - - if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) { - HANDLE res = CreateFile2(lpFileName, dwDesiredAccess, dwShareMode, dwCreationDisposition, pCreateExParams); - callContext.updateLastError(); - return res; - } - - if (pCreateExParams && (pCreateExParams->dwFileFlags & FILE_FLAG_DELETE_ON_CLOSE)) { - spdlog::get("hooks")->warn("hook_CreateFile2: FILE_FLAG_DELETE_ON_CLOSE not supported"); - } - - DWORD originalDisposition = dwCreationDisposition; - CreateRerouter rerouter; - if (rerouter.rerouteCreate(READ_CONTEXT(), callContext, lpFileName, dwCreationDisposition, dwDesiredAccess, - pCreateExParams ? pCreateExParams->lpSecurityAttributes : nullptr)) - { - PRE_REALCALL - res = CreateFile2(rerouter.fileName(), dwDesiredAccess, dwShareMode, dwCreationDisposition, pCreateExParams); - POST_REALCALL - rerouter.updateResult(callContext, res != INVALID_HANDLE_VALUE); - - if (res != INVALID_HANDLE_VALUE) { - if (rerouter.newReroute()) - rerouter.insertMapping(WRITE_CONTEXT()); - - if (rerouter.isDir() && rerouter.wasRerouted() - && pCreateExParams && (pCreateExParams->dwFileFlags & FILE_FLAG_BACKUP_SEMANTICS)) - { - // store the original search path for use during iteration - WRITE_CONTEXT() - ->customData(SearchHandles)[res] - = lpFileName; - } - } - - if (rerouter.wasRerouted() || rerouter.changedError() || originalDisposition != dwCreationDisposition) { - LOG_CALL() - .PARAMWRAP(lpFileName) - .PARAMWRAP(rerouter.fileName()) - .PARAMHEX(dwDesiredAccess) - .PARAMHEX(originalDisposition) - .PARAMHEX(dwCreationDisposition) - .PARAMHEX(res) - .PARAM(rerouter.originalError()) - .PARAM(rerouter.error()); - } - } - else { - spdlog::get("hooks")->info( - "hook_CreateFileW guaranteed failure, skipping original call: {}, disposition={}, access={}, error={}", - ush::string_cast(lpFileName, ush::CodePage::UTF8), - dwCreationDisposition, dwDesiredAccess, rerouter.error()); - - callContext.updateLastError(rerouter.error()); - } - - HOOK_END - - return res; -} - BOOL WINAPI usvfs::hook_GetFileAttributesExW( LPCWSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, LPVOID lpFileInformation) @@ -1327,7 +529,7 @@ BOOL WINAPI usvfs::hook_DeleteFileW(LPCWSTR lpFileName) } BOOL rewriteChangedDrives(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName, - const RerouteW& readReroute, const usvfs::CreateRerouter& writeReroute) + const usvfs::RerouteW& readReroute, const usvfs::CreateRerouter& writeReroute) { return ((readReroute.wasRerouted() || writeReroute.wasRerouted()) && pathsOnDifferentDrives(readReroute.fileName(), writeReroute.fileName()) @@ -1608,7 +810,7 @@ BOOL WINAPI usvfs::hook_MoveFileWithProgressW(LPCWSTR lpExistingFileName, LPCWST } RerouteW readReroute; - CreateRerouter writeReroute; + usvfs::CreateRerouter writeReroute; bool callOriginal = true; DWORD newFlags = dwFlags; @@ -1701,7 +903,7 @@ BOOL WINAPI usvfs::hook_CopyFileExW(LPCWSTR lpExistingFileName, } RerouteW readReroute; - CreateRerouter writeReroute; + usvfs::CreateRerouter writeReroute; bool callOriginal = true; { diff --git a/src/usvfs_dll/hooks/kernel32.h b/src/usvfs_dll/hooks/kernel32.h index d9fac5cc..82ad1b87 100644 --- a/src/usvfs_dll/hooks/kernel32.h +++ b/src/usvfs_dll/hooks/kernel32.h @@ -6,12 +6,6 @@ namespace usvfs { -DLLEXPORT HANDLE WINAPI hook_CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); -DLLEXPORT HANDLE WINAPI hook_CreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); - -extern HANDLE (WINAPI *CreateFile2)(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition, LPCREATEFILE2_EXTENDED_PARAMETERS pCreateExParams); -DLLEXPORT HANDLE WINAPI hook_CreateFile2(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition, LPCREATEFILE2_EXTENDED_PARAMETERS pCreateExParams); - DLLEXPORT BOOL WINAPI hook_GetFileAttributesExW(LPCWSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, LPVOID lpFileInformation); DLLEXPORT DWORD WINAPI hook_GetFileAttributesW(LPCWSTR lpFileName); DLLEXPORT DWORD WINAPI hook_SetFileAttributesW(LPCWSTR lpFileName, DWORD dwFileAttributes); diff --git a/src/usvfs_dll/hooks/ntdll.cpp b/src/usvfs_dll/hooks/ntdll.cpp index ad88c8f2..b7badb91 100644 --- a/src/usvfs_dll/hooks/ntdll.cpp +++ b/src/usvfs_dll/hooks/ntdll.cpp @@ -3,6 +3,7 @@ #include #include "../hookcontext.h" #include "../hookcallcontext.h" +#include "../maptracker.h" #include "../stringcast_boost.h" #include #pragma warning(push, 3) @@ -49,11 +50,21 @@ using usvfs::UnicodeString; #define FILE_OVERWRITE_IF 0x00000005 #define FILE_MAXIMUM_DISPOSITION 0x00000005 -#define FILE_DELETE_ON_CLOSE 0x00001000 - template using unique_ptr_deleter = std::unique_ptr; +class RedirectionInfo { +public: + UnicodeString path; + bool redirected; + + RedirectionInfo() {} + RedirectionInfo(UnicodeString path, bool redirected) + : path(path) + , redirected(redirected) + {} +}; + class HandleTracker { public: using handle_type = HANDLE; @@ -150,19 +161,19 @@ std::ostream &operator<<(std::ostream &os, POBJECT_ATTRIBUTES attr) return operator<<(os, *attr->ObjectName); } -std::pair +RedirectionInfo applyReroute(const usvfs::HookContext::ConstPtr &context, const usvfs::HookCallContext &callContext, const UnicodeString &inPath) { - std::pair result; - result.first = inPath; - result.second = false; + RedirectionInfo result; + result.path = inPath; + result.redirected = false; if (callContext.active()) { // see if the file exists in the redirection tree std::string lookupPath = ush::string_cast( - static_cast(result.first) + 4, ush::CodePage::UTF8); + static_cast(result.path) + 4, ush::CodePage::UTF8); auto node = context->redirectionTable()->findNode(lookupPath.c_str()); // if so, replace the file name with the path to the mapped file if ((node.get() != nullptr) && (!node->data().linkTarget.empty() || node->isDirectory())) { @@ -178,20 +189,41 @@ applyReroute(const usvfs::HookContext::ConstPtr &context, reroutePath = ush::string_cast( node->path().c_str(), ush::CodePage::UTF8); - } + } if ((*reroutePath.rbegin() == L'\\') && (*lookupPath.rbegin() != '\\')) { reroutePath.resize(reroutePath.size() - 1); } std::replace(reroutePath.begin(), reroutePath.end(), L'/', L'\\'); if (reroutePath[1] == L'\\') reroutePath[1] = L'?'; - result.first = LR"(\??\)" + reroutePath; - result.second = true; + result.path = LR"(\??\)" + reroutePath; + result.redirected = true; } } return result; } +RedirectionInfo +applyReroute(const usvfs::CreateRerouter &rerouter) +{ + RedirectionInfo result; + result.path = rerouter.fileName(); + result.redirected = rerouter.wasRerouted(); + + std::wstring reroutePath(static_cast(result.path)); + std::replace(reroutePath.begin(), reroutePath.end(), L'/', L'\\'); + if (reroutePath[1] == L'\\') + reroutePath[1] = L'?'; + if (!((reroutePath[0] == L'\\') && + (reroutePath[1] == L'?') && + (reroutePath[2] == L'?') && + (reroutePath[3] == L'\\'))) { + result.path = LR"(\??\)" + reroutePath; + } + + return result; +} + struct FindCreateTarget { usvfs::RedirectionTree::NodePtrT target; void operator()(usvfs::RedirectionTree::NodePtrT node) @@ -229,8 +261,7 @@ findCreateTarget(const usvfs::HookContext::ConstPtr &context, return result; } - -std::pair +RedirectionInfo applyReroute(const usvfs::HookContext::ConstPtr &context, const usvfs::HookCallContext &callContext, POBJECT_ATTRIBUTES inAttributes) @@ -614,7 +645,7 @@ void gatherVirtualEntries(const UnicodeString &dirName, m = { ush::string_cast(subNode->path().c_str(), ush::CodePage::UTF8), vName }; } - + info.virtualMatches.push(m); info.foundFiles.insert(ush::to_upper(vName)); } @@ -1014,15 +1045,15 @@ NTSTATUS WINAPI usvfs::hook_NtQueryDirectoryFileEx( } unique_ptr_deleter -makeObjectAttributes(std::pair &redirInfo, +makeObjectAttributes(RedirectionInfo &redirInfo, POBJECT_ATTRIBUTES attributeTemplate) { - if (redirInfo.second) { + if (redirInfo.redirected) { unique_ptr_deleter result( new OBJECT_ATTRIBUTES, [](OBJECT_ATTRIBUTES *ptr) { delete ptr; }); memcpy(result.get(), attributeTemplate, sizeof(OBJECT_ATTRIBUTES)); result->RootDirectory = nullptr; - result->ObjectName = static_cast(redirInfo.first); + result->ObjectName = static_cast(redirInfo.path); return result; } else { // just reuse the template with a dummy deleter @@ -1045,10 +1076,6 @@ NTSTATUS ntdll_mess_NtOpenFile(PHANDLE FileHandle, HOOK_START_GROUP(MutExHookGroup::OPEN_FILE) - if (OpenOptions & FILE_DELETE_ON_CLOSE) { - spdlog::get("hooks")->warn("ntdll_mess_NtOpenFile: FILE_DELETE_ON_CLOSE not supported"); - } - bool storePath = false; if (((OpenOptions & FILE_DIRECTORY_FILE) != 0UL) && ((OpenOptions & FILE_OPEN_FOR_BACKUP_INTENT) != 0UL)) { @@ -1079,7 +1106,7 @@ NTSTATUS ntdll_mess_NtOpenFile(PHANDLE FileHandle, } try { - std::pair redir + RedirectionInfo redir = applyReroute(READ_CONTEXT(), callContext, fullName); unique_ptr_deleter adjustedAttributes = makeObjectAttributes(redir, ObjectAttributes); @@ -1096,7 +1123,7 @@ NTSTATUS ntdll_mess_NtOpenFile(PHANDLE FileHandle, #pragma message("need to clean up this handle in CloseHandle call") } - if (redir.second) { + if (redir.redirected) { LOG_CALL() .addParam("source", ObjectAttributes) .addParam("rerouted", adjustedAttributes.get()) @@ -1127,17 +1154,6 @@ NTSTATUS WINAPI usvfs::hook_NtOpenFile(PHANDLE FileHandle, return res; } -bool fileExists(POBJECT_ATTRIBUTES attributes) -{ - UnicodeString temp = CreateUnicodeString(attributes); - return RtlDoesFileExists_U(static_cast(temp)) == TRUE; -} - -bool fileExists(const UnicodeString &filename) -{ - return RtlDoesFileExists_U(static_cast(filename)) == TRUE; -} - NTSTATUS ntdll_mess_NtCreateFile( PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, @@ -1147,9 +1163,10 @@ NTSTATUS ntdll_mess_NtCreateFile( { using namespace usvfs; + NTSTATUS res = STATUS_NO_SUCH_FILE; + PreserveGetLastError ntFunctionsDoNotChangeGetLastError; - NTSTATUS res = STATUS_NO_SUCH_FILE; HOOK_START_GROUP(MutExHookGroup::OPEN_FILE) if (!callContext.active()) { return ::NtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, @@ -1158,11 +1175,8 @@ NTSTATUS ntdll_mess_NtCreateFile( EaBuffer, EaLength); } - if (CreateOptions & FILE_DELETE_ON_CLOSE) { - spdlog::get("hooks")->warn("ntdll_mess_NtCreateFile: FILE_DELETE_ON_CLOSE not supported"); - } - UnicodeString inPath = CreateUnicodeString(ObjectAttributes); + LPCWSTR inPathW = static_cast(inPath); if (inPath.size() == 0) { spdlog::get("hooks")->info( @@ -1174,58 +1188,80 @@ NTSTATUS ntdll_mess_NtCreateFile( EaBuffer, EaLength); } - std::pair redir(UnicodeString(), false); - - { // limit context scope - FunctionGroupLock lock(MutExHookGroup::ALL_GROUPS); - HookContext::ConstPtr context = READ_CONTEXT(); + DWORD convertedDisposition = OPEN_EXISTING; + switch(CreateDisposition) { + case FILE_SUPERSEDE: convertedDisposition = CREATE_ALWAYS; break; + case FILE_OPEN: convertedDisposition = OPEN_EXISTING; break; + case FILE_CREATE: convertedDisposition = CREATE_NEW; break; + case FILE_OPEN_IF: convertedDisposition = OPEN_ALWAYS; break; + case FILE_OVERWRITE: convertedDisposition = TRUNCATE_EXISTING; break; + case FILE_OVERWRITE_IF: convertedDisposition = CREATE_ALWAYS; break; + default: spdlog::get("hooks")->error("invalid disposition: {0}", CreateDisposition); break; + } - redir = applyReroute(context, callContext, inPath); - - // TODO would be neat if this could (optionally) reroute all potential write - // accesses to the create target. - // This could be achived by copying the file to the target here in case - // the createdisposition or the requested access rights make that - // necessary - if (((CreateDisposition == FILE_SUPERSEDE) - || (CreateDisposition == FILE_CREATE) - || (CreateDisposition == FILE_OPEN_IF) - || (CreateDisposition == FILE_OVERWRITE_IF)) - && !redir.second && !fileExists(inPath)) { - // the file will be created so now we need to know where - std::pair createTarget - = findCreateTarget(context, inPath); - - if (createTarget.second.size() != 0) { - // there is a reroute target for new files so adjust the path - redir.first = LR"(\??)"; // appendPath will add the second '\' - redir.first.appendPath(static_cast(createTarget.second)); - - spdlog::get("hooks")->info( - "reroute write access: {}", - ush::string_cast(static_cast(redir.first)) - .c_str()); - } + DWORD convertedAccess = 0; + if ((DesiredAccess & FILE_GENERIC_READ) == FILE_GENERIC_READ) convertedAccess |= GENERIC_READ; + if ((DesiredAccess & FILE_GENERIC_WRITE) == FILE_GENERIC_WRITE) convertedAccess |= GENERIC_WRITE; + if ((DesiredAccess & FILE_GENERIC_EXECUTE) == FILE_GENERIC_EXECUTE) convertedAccess |= GENERIC_EXECUTE; + if ((DesiredAccess & FILE_ALL_ACCESS) == FILE_ALL_ACCESS) convertedAccess |= GENERIC_ALL; + + ULONG originalDisposition = CreateDisposition; + CreateRerouter rerouter; + if (rerouter.rerouteCreate(READ_CONTEXT(), callContext, inPathW, convertedDisposition, + convertedAccess, (LPSECURITY_ATTRIBUTES)ObjectAttributes->SecurityDescriptor)) { + switch(convertedDisposition) { + case CREATE_NEW: CreateDisposition = FILE_CREATE; break; + case CREATE_ALWAYS: if (CreateDisposition != FILE_SUPERSEDE) CreateDisposition = FILE_OVERWRITE_IF; break; + case OPEN_EXISTING: CreateDisposition = FILE_OPEN; break; + case OPEN_ALWAYS: CreateDisposition = FILE_OPEN_IF; break; + case TRUNCATE_EXISTING: CreateDisposition = FILE_OVERWRITE; break; } - } - unique_ptr_deleter adjustedAttributes + RedirectionInfo redir = applyReroute(rerouter); + + unique_ptr_deleter adjustedAttributes = makeObjectAttributes(redir, ObjectAttributes); - PRE_REALCALL - res = ::NtCreateFile(FileHandle, DesiredAccess, adjustedAttributes.get(), - IoStatusBlock, AllocationSize, FileAttributes, - ShareAccess, CreateDisposition, CreateOptions, EaBuffer, - EaLength); - POST_REALCALL + PRE_REALCALL + res = ::NtCreateFile(FileHandle, DesiredAccess, adjustedAttributes.get(), + IoStatusBlock, AllocationSize, FileAttributes, + ShareAccess, CreateDisposition, CreateOptions, EaBuffer, + EaLength); + POST_REALCALL + rerouter.updateResult(callContext, res == STATUS_SUCCESS); - if (redir.second) { - LOG_CALL() - .addParam("source", ObjectAttributes) - .addParam("rerouted", adjustedAttributes.get()) - .PARAM(CreateDisposition) - .PARAM(*FileHandle) - .PARAMWRAP(res); + if (res == STATUS_SUCCESS) { + if (rerouter.newReroute()) + rerouter.insertMapping(WRITE_CONTEXT()); + + if (rerouter.isDir() && rerouter.wasRerouted() && ((FileAttributes & FILE_OPEN_FOR_BACKUP_INTENT) == FILE_OPEN_FOR_BACKUP_INTENT)) { + // store the original search path for use during iteration + WRITE_CONTEXT() + ->customData(SearchHandles)[*FileHandle] = inPathW; + } + } + + if (rerouter.wasRerouted() || rerouter.changedError() || originalDisposition != CreateDisposition) { + LOG_CALL() + .PARAMWRAP(inPathW) + .PARAMWRAP(rerouter.fileName()) + .PARAMHEX(DesiredAccess) + .PARAMHEX(originalDisposition) + .PARAMHEX(CreateDisposition) + .PARAMHEX(FileAttributes) + .PARAMHEX(res) + .PARAMHEX(*FileHandle) + .PARAM(rerouter.originalError()) + .PARAM(rerouter.error()); + } + } else { + // make the original call to set up the proper errors and return statuses + PRE_REALCALL + res = ::NtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, + IoStatusBlock, AllocationSize, FileAttributes, + ShareAccess, CreateDisposition, CreateOptions, EaBuffer, + EaLength); + POST_REALCALL } HOOK_END @@ -1246,7 +1282,6 @@ NTSTATUS WINAPI usvfs::hook_NtCreateFile( return res; } - NTSTATUS WINAPI usvfs::hook_NtClose(HANDLE Handle) { PreserveGetLastError ntFunctionsDoNotChangeGetLastError; @@ -1308,7 +1343,7 @@ NTSTATUS WINAPI usvfs::hook_NtQueryAttributesFile( UnicodeString inPath = CreateUnicodeString(ObjectAttributes); - std::pair redir + RedirectionInfo redir = applyReroute(READ_CONTEXT(), callContext, inPath); unique_ptr_deleter adjustedAttributes = makeObjectAttributes(redir, ObjectAttributes); @@ -1317,7 +1352,7 @@ NTSTATUS WINAPI usvfs::hook_NtQueryAttributesFile( res = ::NtQueryAttributesFile(adjustedAttributes.get(), FileInformation); POST_REALCALL - if (redir.second) { + if (redir.redirected) { LOG_CALL() .addParam("source", ObjectAttributes) .addParam("rerouted", adjustedAttributes.get()) @@ -1350,7 +1385,7 @@ NTSTATUS WINAPI usvfs::hook_NtQueryFullAttributesFile( return ::NtQueryFullAttributesFile(ObjectAttributes, FileInformation); } - std::pair redir + RedirectionInfo redir = applyReroute(READ_CONTEXT(), callContext, inPath); unique_ptr_deleter adjustedAttributes = makeObjectAttributes(redir, ObjectAttributes); @@ -1359,7 +1394,7 @@ NTSTATUS WINAPI usvfs::hook_NtQueryFullAttributesFile( res = ::NtQueryFullAttributesFile(adjustedAttributes.get(), FileInformation); POST_REALCALL - if (redir.second) { + if (redir.redirected) { LOG_CALL() .addParam("source", ObjectAttributes) .addParam("rerouted", adjustedAttributes.get()) diff --git a/src/usvfs_dll/maptracker.h b/src/usvfs_dll/maptracker.h new file mode 100644 index 00000000..b90fd88c --- /dev/null +++ b/src/usvfs_dll/maptracker.h @@ -0,0 +1,650 @@ +#pragma once + +#include "windows_sane.h" + +#include +#include +#include + +#include "hookcontext.h" +#include "hookcallcontext.h" +#include "stringcast_basic.h" + +namespace usvfs { + +// returns true iff the path exists (checks only real paths) +static inline bool pathExists(LPCWSTR fileName) +{ + FunctionGroupLock lock(MutExHookGroup::FILE_ATTRIBUTES); + DWORD attrib = GetFileAttributesW(fileName); + return attrib != INVALID_FILE_ATTRIBUTES; +} + +// returns true iff the path exists and is a file (checks only real paths) +static inline bool pathIsFile(LPCWSTR fileName) +{ + FunctionGroupLock lock(MutExHookGroup::FILE_ATTRIBUTES); + DWORD attrib = GetFileAttributesW(fileName); + return attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) == 0; +} + +// returns true iff the path exists and is a file (checks only real paths) +static inline bool pathIsDirectory(LPCWSTR fileName) +{ + FunctionGroupLock lock(MutExHookGroup::FILE_ATTRIBUTES); + DWORD attrib = GetFileAttributesW(fileName); + return attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY); +} + +// returns true iff the path does not exist but it parent directory does (checks only real paths) +static inline bool pathDirectlyAvailable(LPCWSTR pathName) +{ + FunctionGroupLock lock(MutExHookGroup::FILE_ATTRIBUTES); + DWORD attrib = GetFileAttributesW(pathName); + return attrib == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +} + +class MapTracker { +public: + std::wstring lookup(const std::wstring& fromPath) const { + if (!fromPath.empty()) + { + std::shared_lock lock(m_mutex); + auto find = m_map.find(fromPath); + if (find != m_map.end()) + return find->second; + } + return std::wstring(); + } + + bool contains(const std::wstring& fromPath) const { + if (!fromPath.empty()) + { + std::shared_lock lock(m_mutex); + auto find = m_map.find(fromPath); + if (find != m_map.end()) + return true; + } + return false; + } + + void insert(const std::wstring& fromPath, const std::wstring& toPath) { + if (fromPath.empty()) + return; + std::unique_lock lock(m_mutex); + m_map[fromPath] = toPath; + } + + bool erase(const std::wstring& fromPath) + { + if (fromPath.empty()) + return false; + std::unique_lock lock(m_mutex); + return m_map.erase(fromPath); + } + +private: + mutable std::shared_mutex m_mutex; + std::unordered_map m_map; +}; + +extern MapTracker k32DeleteTracker; +extern MapTracker k32FakeDirTracker; + +class RerouteW +{ + std::wstring m_Buffer{}; + std::wstring m_RealPath{}; + bool m_Rerouted{false}; + LPCWSTR m_FileName{nullptr}; + bool m_PathCreated{false}; + bool m_NewReroute{false}; + + RedirectionTree::NodePtrT m_FileNode; + +public: + RerouteW() = default; + + RerouteW(RerouteW &&reference) + : m_Buffer(std::move(reference.m_Buffer)) + , m_RealPath(std::move(reference.m_RealPath)) + , m_Rerouted(reference.m_Rerouted) + , m_PathCreated(reference.m_PathCreated) + , m_NewReroute(reference.m_NewReroute) + , m_FileNode(std::move(reference.m_FileNode)) + { + m_FileName = reference.m_FileName != nullptr ? m_Buffer.c_str() : nullptr; + reference.m_FileName = nullptr; + } + + RerouteW &operator=(RerouteW &&reference) + { + m_Buffer = std::move(reference.m_Buffer); + m_RealPath = std::move(reference.m_RealPath); + m_Rerouted = reference.m_Rerouted; + m_PathCreated = reference.m_PathCreated; + m_NewReroute = reference.m_NewReroute; + m_FileName = reference.m_FileName != nullptr ? m_Buffer.c_str() : nullptr; + m_FileNode = std::move(reference.m_FileNode); + return *this; + } + + RerouteW(const RerouteW &reference) = delete; + RerouteW &operator=(const RerouteW &) = delete; + + LPCWSTR fileName() const + { + return m_FileName; + } + + const std::wstring &buffer() const + { + return m_Buffer; + } + + bool wasRerouted() const + { + return m_Rerouted; + } + + bool newReroute() const + { + return m_NewReroute; + } + + void insertMapping(const HookContext::Ptr &context, bool directory = false) + { + if (directory) + { + addDirectoryMapping(context, m_RealPath, m_FileName); + + // In case we have just created a "fake" directory, it is no longer fake and need to remove it and all its + // parent folders from the fake map: + std::wstring dir = m_FileName; + while (k32FakeDirTracker.erase(dir)) + dir = fs::path(dir).parent_path().wstring(); + } + else + { + //if (m_PathCreated) + //addDirectoryMapping(context, fs::path(m_RealPath).parent_path(), fs::path(m_FileName).parent_path()); + + spdlog::get("hooks")->info("mapping file in vfs: {}, {}", + shared::string_cast(m_RealPath, shared::CodePage::UTF8), + shared::string_cast(m_FileName, shared::CodePage::UTF8)); + m_FileNode = + context->redirectionTable().addFile(m_RealPath, RedirectionDataLocal(shared::string_cast(m_FileName, shared::CodePage::UTF8))); + + k32DeleteTracker.erase(m_RealPath); + } + } + + void removeMapping(const HookContext::ConstPtr &readContext, bool directory = false) + { + bool addToDelete = false; + bool dontAddToDelete = false; + + // We need to track deleted files even if they were not rerouted (i.e. files deleted from the real folder which there is + // a virtualized mapped folder on top of it). Since we don't want to add, *every* file which is deleted we check this: + bool found = wasRerouted(); + if (!found) { + FindCreateTarget visitor; + RedirectionTree::VisitorFunction visitorWrapper = + [&](const RedirectionTree::NodePtrT &node) { visitor(node); }; + readContext->redirectionTable()->visitPath(m_RealPath, visitorWrapper); + if (visitor.target.get()) + found = true; + } + if (found) + addToDelete = true; + + if (wasRerouted()) { + if (m_FileNode.get()) + m_FileNode->removeFromTree(); + else + spdlog::get("usvfs")->warn("Node not removed: {}", shared::string_cast(m_FileName)); + + if (!directory) + { + // check if this file was the last file inside a "fake" directory then remove it + // and possibly also its fake empty parent folders: + std::wstring parent = m_FileName; + while (true) + { + parent = fs::path(parent).parent_path().wstring(); + if (k32FakeDirTracker.contains(parent)) + { + dontAddToDelete = true; + if (RemoveDirectoryW(parent.c_str())) { + k32FakeDirTracker.erase(parent); + spdlog::get("usvfs")->info("removed empty fake directory: {}", shared::string_cast(parent)); + } + else if (GetLastError() != ERROR_DIR_NOT_EMPTY) { + auto error = GetLastError(); + spdlog::get("usvfs")->warn("removing fake directory failed: {}, error={}", shared::string_cast(parent), error); + break; + } + } + else + break; + } + } + } + if (addToDelete && !dontAddToDelete) { + k32DeleteTracker.insert(m_RealPath, m_FileName); + } + } + + static bool createFakePath(fs::path path, LPSECURITY_ATTRIBUTES securityAttributes) + { + // sanity and guaranteed recursion end: + if (!path.has_relative_path()) + throw shared::windows_error("createFakePath() refusing to create non-existing top level path: " + path.string()); + + DWORD attr = GetFileAttributesW(path.c_str()); + DWORD err = GetLastError(); + if (attr != INVALID_FILE_ATTRIBUTES) { + if (attr & FILE_ATTRIBUTE_DIRECTORY) + return false; // if directory already exists all is good + else + throw shared::windows_error("createFakePath() called on a file: " + path.string()); + } + if (err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND) + throw shared::windows_error("createFakePath() GetFileAttributesW failed on: " + path.string(), err); + + if (err != ERROR_FILE_NOT_FOUND) // ERROR_FILE_NOT_FOUND means parent directory already exists + createFakePath(path.parent_path(), securityAttributes); // otherwise create parent directory (recursively) + + BOOL res = CreateDirectoryW(path.c_str(), securityAttributes); + if (res) + k32FakeDirTracker.insert(path.wstring(), std::wstring()); + else { + err = GetLastError(); + throw shared::windows_error("createFakePath() CreateDirectoryW failed on: " + path.string(), err); + } + return true; + } + + static bool addDirectoryMapping(const HookContext::Ptr &context, const fs::path& originalPath, const fs::path& reroutedPath) + { + if (originalPath.empty() || reroutedPath.empty()) { + spdlog::get("hooks")->error("RerouteW::addDirectoryMapping failed: {}, {}", + shared::string_cast(originalPath.wstring(), shared::CodePage::UTF8).c_str(), + shared::string_cast(reroutedPath.wstring(), shared::CodePage::UTF8).c_str()); + return false; + } + + auto lookupParent = context->redirectionTable()->findNode(originalPath.parent_path()); + if (!lookupParent.get() || lookupParent->data().linkTarget.empty()) { + if (!addDirectoryMapping(context, originalPath.parent_path(), reroutedPath.parent_path())) + { + spdlog::get("hooks")->error("RerouteW::addDirectoryMapping failed: {}, {}", + shared::string_cast(originalPath.wstring(), shared::CodePage::UTF8).c_str(), + shared::string_cast(reroutedPath.wstring(), shared::CodePage::UTF8).c_str()); + return false; + } + } + + std::string reroutedU8 + = shared::string_cast(reroutedPath.wstring(), shared::CodePage::UTF8); + if (reroutedU8.empty() || reroutedU8[reroutedU8.size() - 1] != '\\') + reroutedU8 += "\\"; + + spdlog::get("hooks")->info("mapping directory in vfs: {}, {}", + shared::string_cast(originalPath.wstring(), shared::CodePage::UTF8), reroutedU8.c_str()); + + context->redirectionTable().addDirectory( + originalPath, RedirectionDataLocal(reroutedU8), + shared::FLAG_DIRECTORY|shared::FLAG_CREATETARGET); + + fs::directory_iterator end_itr; + + // cycle through the directory + for (fs::directory_iterator itr(reroutedPath); itr != end_itr; ++itr) + { + // If it's not a directory, add it to the VFS, if it is recurse into it + if (is_regular_file(itr->path())) { + std::string fileReroutedU8 = shared::string_cast(itr->path().wstring(), shared::CodePage::UTF8); + spdlog::get("hooks")->info("mapping file in vfs: {}, {}", + shared::string_cast((originalPath / itr->path().filename()).wstring(), shared::CodePage::UTF8), + fileReroutedU8.c_str()); + context->redirectionTable().addFile(fs::path(originalPath / itr->path().filename()), RedirectionDataLocal(fileReroutedU8)); + } else { + addDirectoryMapping(context, originalPath / itr->path().filename(), reroutedPath / itr->path().filename()); + } + } + + return true; + } + + template + static bool interestingPathImpl(const char_t *inPath) + { + if (!inPath || !inPath[0]) + return false; + // ignore \\.\ unless its a \\.\?: + if (inPath[0] == '\\' && inPath[1] == '\\' && inPath[2] == '.' && inPath[3] == '\\' && (!inPath[4] || inPath[5] != ':')) + return false; + // ignore L"hid#": + if ((inPath[0] == 'h' || inPath[0] == 'H') + && ((inPath[1] == 'i' || inPath[1] == 'I')) + && ((inPath[2] == 'd' || inPath[2] == 'D')) + && inPath[3] == '#') + return false; + return true; + } + + static bool interestingPath(const char* inPath) { return interestingPathImpl(inPath); } + static bool interestingPath(const wchar_t* inPath) { return interestingPathImpl(inPath); } + + static fs::path absolutePath(const wchar_t *inPath) + { + if (shared::startswith(inPath, LR"(\\?\)") || shared::startswith(inPath, LR"(\??\)")) { + inPath += 4; + return inPath; + } + else if ((shared::startswith(inPath, LR"(\\localhost\)") || shared::startswith(inPath, LR"(\\127.0.0.1\)")) && inPath[13] == L'$') { + std::wstring newPath; + newPath += towupper(inPath[12]); + newPath += L':'; + newPath += &inPath[14]; + return newPath; + } + else if (inPath[0] == L'\0' || inPath[1] == L':') { + return inPath; + } + else if (inPath[0] == L'\\' || inPath[0] == L'/') { + return fs::path(winapi::wide::getFullPathName(inPath).first); + } + WCHAR currentDirectory[MAX_PATH]; + ::GetCurrentDirectoryW(MAX_PATH, currentDirectory); + fs::path finalPath = fs::path(currentDirectory) / inPath; + return finalPath; + //winapi::wide::getFullPathName(inPath).first; + } + + static fs::path canonizePath(const fs::path& inPath) + { + fs::path p = inPath.lexically_normal(); + if (p.filename_is_dot()) + p = p.remove_filename(); + return p.make_preferred(); + } + + static RerouteW create(const HookContext::ConstPtr &context, + const HookCallContext &callContext, + const wchar_t *inPath, bool inverse = false) + { + RerouteW result; + + if (interestingPath(inPath) && callContext.active()) + { + const auto& lookupPath = canonizePath(absolutePath(inPath)); + result.m_RealPath = lookupPath.wstring(); + + result.m_Buffer = k32DeleteTracker.lookup(result.m_RealPath); + bool found = !result.m_Buffer.empty(); + if (found) { + spdlog::get("hooks")->info("Rerouting file open to location of deleted file: {}", + shared::string_cast(result.m_Buffer)); + result.m_NewReroute = true; + } else { + const RedirectionTreeContainer &table + = inverse ? context->inverseTable() : context->redirectionTable(); + result.m_FileNode = table->findNode(lookupPath); + + if (result.m_FileNode.get() + && (!result.m_FileNode->data().linkTarget.empty() || result.m_FileNode->isDirectory())) + { + if (!result.m_FileNode->data().linkTarget.empty()) { + result.m_Buffer = shared::string_cast( + result.m_FileNode->data().linkTarget.c_str(), shared::CodePage::UTF8); + } + else + { + result.m_Buffer = result.m_FileNode->path().wstring(); + } + found = true; + } + } + if (found) { + result.m_Rerouted = true; + + wchar_t inIt = inPath[wcslen(inPath) - 1]; + std::wstring::iterator outIt = result.m_Buffer.end() - 1; + if ((*outIt == L'\\' || *outIt == L'/') && !(inIt == L'\\' || inIt == L'/')) + result.m_Buffer.erase(outIt); + std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\'); + } + else + result.m_Buffer = inPath; + } + else if (inPath) + result.m_Buffer = inPath; + + if (inPath) + result.m_FileName = result.m_Buffer.c_str(); + return result; + } + + static RerouteW createNew(const HookContext::ConstPtr &context, + const HookCallContext &callContext, + LPCWSTR inPath, bool createPath = true, + LPSECURITY_ATTRIBUTES securityAttributes = nullptr) + { + RerouteW result; + + if (interestingPath(inPath) && callContext.active()) + { + const auto& lookupPath = canonizePath(absolutePath(inPath)); + result.m_RealPath = lookupPath.wstring(); + + result.m_Buffer = k32DeleteTracker.lookup(result.m_RealPath); + bool found = !result.m_Buffer.empty(); + if (found) + spdlog::get("hooks")->info("Rerouting file creation to original location of deleted file: {}", + shared::string_cast(result.m_Buffer)); + else + { + FindCreateTarget visitor; + RedirectionTree::VisitorFunction visitorWrapper = + [&](const RedirectionTree::NodePtrT &node) { visitor(node); }; + context->redirectionTable()->visitPath(lookupPath, visitorWrapper); + if (visitor.target.get()) { + // the visitor has found the last (deepest in the directory hierarchy) + // create-target + fs::path relativePath + = shared::make_relative(visitor.target->path(), lookupPath); + result.m_Buffer = + (fs::path(visitor.target->data().linkTarget.c_str()) / relativePath).wstring(); + found = true; + } + } + + if (found) + { + if (createPath) { + try { + FunctionGroupLock lock(MutExHookGroup::ALL_GROUPS); + result.m_PathCreated = + createFakePath(fs::path(result.m_Buffer).parent_path(), securityAttributes); + } + catch (const std::exception &e) { + spdlog::get("hooks")->error("failed to create {}: {}", + shared::string_cast(result.m_Buffer), e.what()); + } + } + + wchar_t inIt = inPath[wcslen(inPath) - 1]; + std::wstring::iterator outIt = result.m_Buffer.end() - 1; + if ((*outIt == L'\\' || *outIt == L'/') && !(inIt == L'\\' || inIt == L'/')) + result.m_Buffer.erase(outIt); + std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\'); + result.m_Rerouted = true; + result.m_NewReroute = true; + } + else + result.m_Buffer = inPath; + } + else if (inPath) + result.m_Buffer = inPath; + + if (inPath) + result.m_FileName = result.m_Buffer.c_str(); + return result; + } + + static RerouteW createOrNew(const HookContext::ConstPtr &context, const HookCallContext &callContext, + LPCWSTR inPath, bool createPath = true, LPSECURITY_ATTRIBUTES securityAttributes = nullptr) + { + { + auto res = create(context, callContext, inPath); + if (res.wasRerouted() || !interestingPath(inPath) || !callContext.active() || pathExists(inPath)) + return std::move(res); + } + return createNew(context, callContext, inPath, createPath, securityAttributes); + } + + static RerouteW noReroute(LPCWSTR inPath) + { + RerouteW result; + if (inPath) + result.m_Buffer = inPath; + if (inPath && inPath[0] && !shared::startswith(inPath, L"hid#")) + std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\'); + result.m_FileName = result.m_Buffer.c_str(); + return result; + } + +private: + struct FindCreateTarget { + RedirectionTree::NodePtrT target; + void operator()(RedirectionTree::NodePtrT node) + { + if (node->hasFlag(shared::FLAG_CREATETARGET)) { + target = node; + } + } + }; +}; + +class CreateRerouter { +public: + bool rerouteCreate(const HookContext::ConstPtr &context, const HookCallContext &callContext, + LPCWSTR lpFileName, DWORD& dwCreationDisposition, DWORD dwDesiredAccess, LPSECURITY_ATTRIBUTES lpSecurityAttributes) + { + enum class Open { existing, create, empty }; + Open open = Open::existing; + + // Notice since we are calling our patched GetFileAttributesW here this will also check virtualized paths + DWORD virtAttr = GetFileAttributesW(lpFileName); + bool isFile = virtAttr != INVALID_FILE_ATTRIBUTES && (virtAttr & FILE_ATTRIBUTE_DIRECTORY) == 0; + m_isDir = virtAttr != INVALID_FILE_ATTRIBUTES && (virtAttr & FILE_ATTRIBUTE_DIRECTORY); + + switch (dwCreationDisposition) { + case CREATE_ALWAYS: + open = Open::create; + if (isFile || m_isDir) { + m_error = ERROR_ALREADY_EXISTS; + } + break; + + case CREATE_NEW: + if (isFile || m_isDir) { + m_error = ERROR_FILE_EXISTS; + return false; + } else { + open = Open::create; + } + break; + + case OPEN_ALWAYS: + if (isFile || m_isDir) { + m_error = ERROR_ALREADY_EXISTS; + } else { + open = Open::create; + } + break; + + case TRUNCATE_EXISTING: + if ((dwDesiredAccess & GENERIC_WRITE) == 0) { + m_error = ERROR_INVALID_PARAMETER; + return false; + } + if (isFile || m_isDir) + open = Open::empty; + break; + } + + if (m_isDir && pathIsDirectory(lpFileName)) + m_reroute = RerouteW::noReroute(lpFileName); + else + m_reroute = RerouteW::create(context, callContext, lpFileName); + + if (m_reroute.wasRerouted() && open == Open::create && pathIsDirectory(m_reroute.fileName())) + m_reroute = RerouteW::createNew(context, callContext, lpFileName, true, lpSecurityAttributes); + + if (!m_isDir && !isFile && !m_reroute.wasRerouted() && (open == Open::create || open == Open::empty)) + { + m_reroute = RerouteW::createNew(context, callContext, lpFileName, true, lpSecurityAttributes); + + bool newFile = !m_reroute.wasRerouted() && pathDirectlyAvailable(m_reroute.fileName()); + if (newFile && open == Open::empty) + // TRUNCATE_EXISTING will fail since the new file doesn't exist, so change disposition: + dwCreationDisposition = CREATE_ALWAYS; + } + + return true; + } + + // rerouteNew is used for rerouting the destination of copy/move operations. Assumes that the call will be skipped if false is returned. + bool rerouteNew(const HookContext::ConstPtr &context, HookCallContext &callContext, LPCWSTR lpFileName, bool replaceExisting, const char* hookName) + { + DWORD disposition = replaceExisting ? CREATE_ALWAYS : CREATE_NEW; + if (!rerouteCreate(context, callContext, lpFileName, disposition, GENERIC_WRITE, nullptr)) { + spdlog::get("hooks")->info( + "{} guaranteed failure, skipping original call: {}, replaceExisting={}, error={}", + hookName, shared::string_cast(lpFileName, shared::CodePage::UTF8), replaceExisting ? "true" : "false", error()); + + callContext.updateLastError(error()); + return false; + } + return true; + } + + void updateResult(HookCallContext &callContext, bool success) + { + m_originalError = callContext.lastError(); + if (success) { + // m_error != ERROR_SUCCESS means we are overriding the error on success + if (m_error == ERROR_SUCCESS) + m_error = m_originalError; + } + else if (m_originalError == ERROR_PATH_NOT_FOUND && m_directlyAvailable) + m_error = ERROR_FILE_NOT_FOUND; + else + m_error = m_originalError; + if (m_error != m_originalError) + callContext.updateLastError(m_error); + } + + DWORD error() const { return m_error; } + DWORD originalError() const { return m_originalError; } + bool changedError() const { return m_error != m_originalError; } + + bool isDir() const { return m_isDir; } + bool newReroute() const { return m_reroute.newReroute(); } + bool wasRerouted() const { return m_reroute.wasRerouted(); } + LPCWSTR fileName() const { return m_reroute.fileName(); } + + void insertMapping(const HookContext::Ptr &context, bool directory = false) { m_reroute.insertMapping(context, directory); } + +private: + DWORD m_error = ERROR_SUCCESS; + DWORD m_originalError = ERROR_SUCCESS; + bool m_directlyAvailable = false; + bool m_isDir = false; + RerouteW m_reroute; +}; + +} // namespace usvfs diff --git a/test/tvfs_test/main.cpp b/test/tvfs_test/main.cpp index 031d6f7d..7629c280 100644 --- a/test/tvfs_test/main.cpp +++ b/test/tvfs_test/main.cpp @@ -160,6 +160,7 @@ TEST_F(USVFSTest, CanResizeRedirectiontree) }); } +/* TEST_F(USVFSTest, CreateFileHookReportsCorrectErrorOnMissingFile) { EXPECT_NO_THROW({ @@ -178,7 +179,9 @@ TEST_F(USVFSTest, CreateFileHookReportsCorrectErrorOnMissingFile) EXPECT_EQ(ERROR_FILE_NOT_FOUND, ::GetLastError()); }); } +*/ +/* TEST_F(USVFSTestWithReroute, CreateFileHookRedirectsFile) { EXPECT_NE(INVALID_HANDLE_VALUE @@ -190,6 +193,7 @@ TEST_F(USVFSTestWithReroute, CreateFileHookRedirectsFile) , FILE_ATTRIBUTE_NORMAL , nullptr)); } +*/ TEST_F(USVFSTest, GetFileAttributesHookReportsCorrectErrorOnMissingFile) diff --git a/vsbuild/usvfs_dll.vcxproj b/vsbuild/usvfs_dll.vcxproj index 84a7ff89..1973e02e 100644 --- a/vsbuild/usvfs_dll.vcxproj +++ b/vsbuild/usvfs_dll.vcxproj @@ -226,6 +226,7 @@ + diff --git a/vsbuild/usvfs_dll.vcxproj.filters b/vsbuild/usvfs_dll.vcxproj.filters index 01ffd7ce..407a9c5f 100644 --- a/vsbuild/usvfs_dll.vcxproj.filters +++ b/vsbuild/usvfs_dll.vcxproj.filters @@ -103,5 +103,8 @@ Header Files\include + + Header Files + \ No newline at end of file From 9d1c45777b54d04f1c6d9d429e083cd7dd67eb50 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sat, 23 Mar 2019 20:19:47 -0500 Subject: [PATCH 14/15] Convert LPCSTR to wstring properly in GetFullPathNameA --- src/usvfs_dll/hooks/kernel32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/usvfs_dll/hooks/kernel32.cpp b/src/usvfs_dll/hooks/kernel32.cpp index 485bacb9..66112308 100644 --- a/src/usvfs_dll/hooks/kernel32.cpp +++ b/src/usvfs_dll/hooks/kernel32.cpp @@ -1129,7 +1129,7 @@ DWORD WINAPI usvfs::hook_GetFullPathNameA(LPCSTR lpFileName, DWORD nBufferLength std::string resolvedWithCMD; std::wstring actualCWD; - fs::path filePath = lpFileName; + fs::path filePath = ush::string_cast(lpFileName, CodePage::UTF8); if (k32CurrentDirectoryTracker.get(actualCWD, filePath.wstring().c_str())) { if (!filePath.is_absolute()) resolvedWithCMD = From c9ac1400e6cd94e5d53738be0c944000f0e84e10 Mon Sep 17 00:00:00 2001 From: LePresidente Date: Sat, 20 Apr 2019 14:07:58 +0200 Subject: [PATCH 15/15] Updated udis86 repo to point to ModOrganizer2 Team repo. --- .gitmodules | 2 +- udis86 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 4f5ca60f..9348686c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,7 @@ url = https://github.com/asmjit/asmjit.git [submodule "udis86"] path = udis86 - url = https://github.com/LePresidente/udis86 + url = https://github.com/ModOrganizer2/udis86 [submodule "fmt"] path = fmt url = https://github.com/fmtlib/fmt.git diff --git a/udis86 b/udis86 index dda4a95b..df9323a4 160000 --- a/udis86 +++ b/udis86 @@ -1 +1 @@ -Subproject commit dda4a95b816ac3f749cd4f798b99d8ed076e83ff +Subproject commit df9323a47f4c343302617239d42968f6ac61218a