#include "kernel32.h" #include "sharedids.h" #include "../hookcallcontext.h" #include "../hookcontext.h" #include "../hookmanager.h" #include "../maptracker.h" #include #include #include #include #include #include #include #include namespace ush = usvfs::shared; using ush::CodePage; using ush::string_cast; namespace usvfs { MapTracker k32DeleteTracker; MapTracker k32FakeDirTracker; } // namespace usvfs class CurrentDirectoryTracker { public: using wstring = std::wstring; bool get(wstring& currentDir, const wchar_t* forRelativePath = nullptr) { int index = m_currentDrive; if (forRelativePath && *forRelativePath && forRelativePath[1] == ':') if (!getDriveIndex(forRelativePath, index)) spdlog::get("usvfs")->warn( "CurrentDirectoryTracker::get() invalid drive letter: {}, will use current " "drive {}", string_cast(forRelativePath), static_cast('A' + index)); // prints '@' for m_currentDrive == -1 if (index < 0) return false; std::shared_lock lock(m_mutex); if (m_perDrive[index].empty()) return false; else { currentDir = m_perDrive[index]; return true; } } bool set(const wstring& currentDir) { int index = -1; bool good = !currentDir.empty() && getDriveIndex(currentDir.c_str(), index) && currentDir[1] == ':'; std::unique_lock lock(m_mutex); m_currentDrive = good ? index : -1; if (good) m_perDrive[index] = currentDir; return good; } private: static bool getDriveIndex(const wchar_t* path, int& index) { if (*path >= 'a' && *path <= 'z') index = *path - 'a'; else if (*path >= 'A' && *path <= 'Z') index = *path - 'A'; else return false; return true; } mutable std::shared_mutex m_mutex; wstring m_perDrive['z' - 'a' + 1]; int m_currentDrive{-1}; }; CurrentDirectoryTracker k32CurrentDirectoryTracker; // attempts to copy source to destination and return the error code static inline DWORD copyFileDirect(LPCWSTR source, LPCWSTR destination, bool overwrite) { usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::SHELL_FILEOP); return CopyFileExW(source, destination, NULL, NULL, NULL, overwrite ? 0 : COPY_FILE_FAIL_IF_EXISTS) ? ERROR_SUCCESS : GetLastError(); } static inline WCHAR pathNameDriveLetter(LPCWSTR path) { if (!path || !path[0]) return 0; if (path[1] == ':') return path[0]; // if path is not ?: or \* then we need to get absolute path: std::wstring buf; if (path[0] != '\\') { buf = winapi::wide::getFullPathName(path).first; path = buf.c_str(); if (!path[0] || path[1] == ':') return path[0]; } // check for \??\C: if (path[1] && path[2] && path[3] && path[4] && path[0] == '\\' && path[3] == '\\' && path[5] == ':') return path[4]; // give up return 0; } // returns false also in case we fail to determine the drive letter of the path static inline bool pathsOnDifferentDrives(LPCWSTR path1, LPCWSTR path2) { WCHAR drive1 = pathNameDriveLetter(path1); WCHAR drive2 = pathNameDriveLetter(path2); return drive1 && drive2 && towupper(drive1) != towupper(drive2); } HMODULE WINAPI usvfs::hook_LoadLibraryExA(LPCSTR lpFileName, HANDLE hFile, DWORD dwFlags) { HMODULE res = nullptr; HOOK_START_GROUP(MutExHookGroup::LOAD_LIBRARY) const std::wstring fileName = ush::string_cast(lpFileName); PRE_REALCALL res = LoadLibraryExW(fileName.c_str(), hFile, dwFlags); POST_REALCALL HOOK_END return res; } HMODULE WINAPI usvfs::hook_LoadLibraryExW(LPCWSTR lpFileName, HANDLE hFile, DWORD dwFlags) { HMODULE res = nullptr; HOOK_START_GROUP(MutExHookGroup::LOAD_LIBRARY) // Why is the usual if (!callContext.active()... check missing? RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, lpFileName); PRE_REALCALL res = ::LoadLibraryExW(reroute.fileName(), hFile, dwFlags); POST_REALCALL if (reroute.wasRerouted()) { LOG_CALL() .PARAM(lpFileName) .PARAM(reroute.fileName()) .PARAM(res) .PARAM(callContext.lastError()); } HOOK_END return res; } /// determine name of the binary to run based on parameters for createprocess std::wstring getBinaryName(LPCWSTR applicationName, LPCWSTR lpCommandLine) { if (applicationName != nullptr) { std::pair fullPath = winapi::wide::getFullPathName(applicationName); return fullPath.second; } else { if (lpCommandLine[0] == '"') { const wchar_t* endQuote = wcschr(lpCommandLine, '"'); if (endQuote != nullptr) { return std::wstring(lpCommandLine + 1, endQuote - 1); } } // according to the documentation, if the commandline is unquoted and has // spaces, it will be interpreted in multiple ways, i.e. // c:\program.exe files\sub dir\program name // c:\program files\sub.exe dir\program name // c:\program files\sub dir\program.exe name // c:\program files\sub dir\program name.exe LPCWSTR space = wcschr(lpCommandLine, L' '); while (space != nullptr) { std::wstring subString(lpCommandLine, space); bool isDirectory = true; if (winapi::ex::wide::fileExists(subString.c_str(), &isDirectory) && !isDirectory) { return subString; } else { space = wcschr(space + 1, L' '); } } return std::wstring(lpCommandLine); } } BOOL(WINAPI* usvfs::CreateProcessInternalW)( LPVOID token, LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation, LPVOID newToken); BOOL WINAPI usvfs::hook_CreateProcessInternalW( LPVOID token, LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation, LPVOID newToken) { BOOL res = FALSE; HOOK_START_GROUP(MutExHookGroup::CREATE_PROCESS) if (!callContext.active()) { res = CreateProcessInternalW( token, lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation, newToken); callContext.updateLastError(); return res; } // remember if the caller wanted the process to be suspended. If so, we // don't resume when we're done BOOL susp = dwCreationFlags & CREATE_SUSPENDED; dwCreationFlags |= CREATE_SUSPENDED; RerouteW applicationReroute; RerouteW cmdReroute; LPWSTR cend = nullptr; std::wstring dllPath; usvfsParameters callParameters; { // scope for context lock auto context = READ_CONTEXT(); if (RerouteW::interestingPath(lpCommandLine)) { // First "argument" in the commandline is the command, we need to identify it and // reroute it: if (*lpCommandLine == '"') { // If the first argument is quoted we trust its is quoted correctly for (cend = lpCommandLine; *cend && *cend != ' '; ++cend) if (*cend == '"') { int escaped = 0; for (++cend; *cend && (*cend != '"' || escaped % 2 != 0); ++cend) escaped = *cend == '\\' ? escaped + 1 : 0; } if (*(cend - 1) == '"') --cend; auto old_cend = *cend; *cend = 0; cmdReroute = RerouteW::create(context, callContext, lpCommandLine + 1); *cend = old_cend; if (old_cend == '"') ++cend; } else { // If the first argument we have no choice but to test all the options to quote // the command as the real CreateProcess will do this: cend = lpCommandLine; while (true) { while (*cend && *cend != ' ') ++cend; auto old_cend = *cend; *cend = 0; cmdReroute = RerouteW::create(context, callContext, lpCommandLine); *cend = old_cend; if (cmdReroute.wasRerouted() || pathIsFile(cmdReroute.fileName())) break; while (*cend == ' ') ++cend; if (!*cend) { // if we reached the end of the string we'll just use the whole commandline // as is: cend = nullptr; break; } } } } applicationReroute = RerouteW::create(context, callContext, lpApplicationName); dllPath = context->dllPath(); callParameters = context->callParameters(); } std::wstring cmdline; if (cend && cmdReroute.fileName()) { auto fileName = cmdReroute.fileName(); cmdline.reserve(wcslen(fileName) + wcslen(cend) + 2); if (*fileName != '"') cmdline += L"\""; cmdline += fileName; if (*fileName != '"') cmdline += L"\""; cmdline += cend; } PRE_REALCALL res = CreateProcessInternalW(token, applicationReroute.fileName(), cmdline.empty() ? lpCommandLine : &cmdline[0], lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation, newToken); POST_REALCALL BOOL blacklisted = FALSE; { // limit scope of context auto context = READ_CONTEXT(); blacklisted = context->executableBlacklisted(applicationReroute.fileName(), cmdReroute.fileName()); } if (res) { if (!blacklisted) { try { injectProcess(dllPath, callParameters, *lpProcessInformation); } catch (const std::exception& e) { spdlog::get("hooks")->error("failed to inject into {0}: {1}", lpApplicationName != nullptr ? applicationReroute.fileName() : static_cast(lpCommandLine), e.what()); } } // resume unless process is supposed to start suspended if (!susp && (ResumeThread(lpProcessInformation->hThread) == (DWORD)-1)) { spdlog::get("hooks")->error("failed to inject into spawned process"); res = FALSE; } } LOG_CALL() .PARAM(lpApplicationName) .PARAM(applicationReroute.fileName()) .PARAM(cmdReroute.fileName()) .PARAM(res) .PARAM(callContext.lastError()) .PARAM(cmdline); HOOK_END return res; } BOOL WINAPI usvfs::hook_GetFileAttributesExA(LPCSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, LPVOID lpFileInformation) { BOOL res = FALSE; HOOK_START_GROUP(MutExHookGroup::FILE_ATTRIBUTES) if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) { res = GetFileAttributesExA(lpFileName, fInfoLevelId, lpFileInformation); callContext.updateLastError(); return res; } HOOK_END HOOK_START const std::wstring fileName = ush::string_cast(lpFileName); PRE_REALCALL res = GetFileAttributesExW(fileName.c_str(), fInfoLevelId, lpFileInformation); POST_REALCALL HOOK_END return res; } BOOL WINAPI usvfs::hook_GetFileAttributesExW(LPCWSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, LPVOID lpFileInformation) { BOOL res = FALSE; HOOK_START_GROUP(MutExHookGroup::FILE_ATTRIBUTES) if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) { res = GetFileAttributesExW(lpFileName, fInfoLevelId, lpFileInformation); callContext.updateLastError(); return res; } fs::path canonicalFile = RerouteW::canonizePath(RerouteW::absolutePath(lpFileName)); RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, canonicalFile.c_str()); PRE_REALCALL res = ::GetFileAttributesExW(reroute.fileName(), fInfoLevelId, lpFileInformation); POST_REALCALL DWORD originalError = callContext.lastError(); DWORD fixedError = originalError; // In case the target does not exist the error value varies to differentiate if the // parent folder exists (ERROR_FILE_NOT_FOUND) or not (ERROR_PATH_NOT_FOUND). // If the original target's parent folder doesn't actually exist it may exist in the // the virtualized sense, or if we rerouted the query the parent of the original path // might exist while the parent of the rerouted path might not: if (!res && fixedError == ERROR_PATH_NOT_FOUND) { // first query original file parent (if we rerouted it): fs::path originalParent = canonicalFile.parent_path(); WIN32_FILE_ATTRIBUTE_DATA parentAttr; if (reroute.wasRerouted() && ::GetFileAttributesExW(originalParent.c_str(), GetFileExInfoStandard, &parentAttr) && (parentAttr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) fixedError = ERROR_FILE_NOT_FOUND; else { // now query the rerouted path for parent (which can be different from the parent // of the rerouted path) RerouteW rerouteParent = RerouteW::create(READ_CONTEXT(), callContext, originalParent.c_str()); if (rerouteParent.wasRerouted() && ::GetFileAttributesExW(rerouteParent.fileName(), GetFileExInfoStandard, &parentAttr) && (parentAttr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) fixedError = ERROR_FILE_NOT_FOUND; } } if (fixedError != originalError) callContext.updateLastError(fixedError); if (reroute.wasRerouted() || fixedError != originalError) { DWORD resAttrib; if (res && fInfoLevelId == GetFileExInfoStandard && lpFileInformation) resAttrib = reinterpret_cast(lpFileInformation) ->dwFileAttributes; else resAttrib = (DWORD)-1; LOG_CALL() .PARAM(lpFileName) .PARAM(reroute.fileName()) .PARAMHEX(fInfoLevelId) .PARAMHEX(res) .PARAMHEX(resAttrib) .PARAM(originalError) .PARAM(fixedError); } HOOK_END return res; } DWORD WINAPI usvfs::hook_GetFileAttributesA(LPCSTR lpFileName) { BOOL res = FALSE; HOOK_START_GROUP(MutExHookGroup::FILE_ATTRIBUTES) if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) { res = GetFileAttributesA(lpFileName); callContext.updateLastError(); return res; } HOOK_END HOOK_START const std::wstring fileName = ush::string_cast(lpFileName); PRE_REALCALL res = GetFileAttributesW(fileName.c_str()); POST_REALCALL HOOK_END return res; } DWORD WINAPI usvfs::hook_GetFileAttributesW(LPCWSTR lpFileName) { DWORD res = 0UL; HOOK_START_GROUP(MutExHookGroup::FILE_ATTRIBUTES) if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) { res = GetFileAttributesW(lpFileName); callContext.updateLastError(); return res; } fs::path canonicalFile = RerouteW::canonizePath(RerouteW::absolutePath(lpFileName)); RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, canonicalFile.c_str()); if (reroute.wasRerouted()) PRE_REALCALL res = ::GetFileAttributesW(reroute.fileName()); POST_REALCALL DWORD originalError = callContext.lastError(); DWORD fixedError = originalError; // In case the target does not exist the error value varies to differentiate if the // parent folder exists (ERROR_FILE_NOT_FOUND) or not (ERROR_PATH_NOT_FOUND). // If the original target's parent folder doesn't actually exist it may exist in the // the virtualized sense, or if we rerouted the query the parent of the original path // might exist while the parent of the rerouted path might not: if (res == INVALID_FILE_ATTRIBUTES && fixedError == ERROR_PATH_NOT_FOUND) { // first query original file parent (if we rerouted it): fs::path originalParent = canonicalFile.parent_path(); DWORD attr; if (reroute.wasRerouted() && (attr = ::GetFileAttributesW(originalParent.c_str())) != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY)) fixedError = ERROR_FILE_NOT_FOUND; else { // now query the rerouted path for parent (which can be different from the parent // of the rerouted path) RerouteW rerouteParent = RerouteW::create(READ_CONTEXT(), callContext, originalParent.c_str()); if (rerouteParent.wasRerouted() && (attr = ::GetFileAttributesW(rerouteParent.fileName())) != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY)) fixedError = ERROR_FILE_NOT_FOUND; } } if (fixedError != originalError) callContext.updateLastError(fixedError); if (reroute.wasRerouted() || fixedError != originalError) { LOG_CALL() .PARAM(lpFileName) .PARAM(reroute.fileName()) .PARAMHEX(res) .PARAM(originalError) .PARAM(fixedError); } HOOK_ENDP(lpFileName); return res; } DWORD WINAPI usvfs::hook_SetFileAttributesW(LPCWSTR lpFileName, DWORD dwFileAttributes) { DWORD res = 0UL; HOOK_START_GROUP(MutExHookGroup::FILE_ATTRIBUTES) // Why is the usual if (!callContext.active()... check missing? RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, lpFileName); PRE_REALCALL res = ::SetFileAttributesW(reroute.fileName(), dwFileAttributes); POST_REALCALL if (reroute.wasRerouted()) { LOG_CALL().PARAM(reroute.fileName()).PARAM(res).PARAM(callContext.lastError()); } HOOK_END return res; } BOOL WINAPI usvfs::hook_DeleteFileW(LPCWSTR lpFileName) { BOOL res = FALSE; HOOK_START_GROUP(MutExHookGroup::DELETE_FILE) // Why is the usual if (!callContext.active()... check missing? const std::wstring path = RerouteW::canonizePath(RerouteW::absolutePath(lpFileName)).wstring(); RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, path.c_str()); PRE_REALCALL if (reroute.wasRerouted()) { res = ::DeleteFileW(reroute.fileName()); } else { res = ::DeleteFileW(path.c_str()); } POST_REALCALL if (res) { reroute.removeMapping(READ_CONTEXT()); } if (reroute.wasRerouted()) LOG_CALL() .PARAM(lpFileName) .PARAM(reroute.fileName()) .PARAM(res) .PARAM(callContext.lastError()); HOOK_END return res; } BOOL rewriteChangedDrives(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName, const usvfs::RerouteW& readReroute, const usvfs::CreateRerouter& writeReroute) { return ((readReroute.wasRerouted() || writeReroute.wasRerouted()) && pathsOnDifferentDrives(readReroute.fileName(), writeReroute.fileName()) && !pathsOnDifferentDrives(lpExistingFileName, lpNewFileName)); } BOOL WINAPI usvfs::hook_MoveFileA(LPCSTR lpExistingFileName, LPCSTR lpNewFileName) { BOOL res = FALSE; HOOK_START_GROUP(MutExHookGroup::SHELL_FILEOP) if (!callContext.active()) { res = MoveFileA(lpExistingFileName, lpNewFileName); callContext.updateLastError(); return res; } HOOK_END HOOK_START const auto& existingFileName = ush::string_cast(lpExistingFileName); const auto& newFileName = ush::string_cast(lpNewFileName); PRE_REALCALL res = MoveFileW(existingFileName.c_str(), newFileName.c_str()); POST_REALCALL HOOK_END return res; } BOOL WINAPI usvfs::hook_MoveFileW(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName) { BOOL res = FALSE; HOOK_START_GROUP(MutExHookGroup::SHELL_FILEOP) if (!callContext.active()) { res = MoveFileW(lpExistingFileName, lpNewFileName); callContext.updateLastError(); return res; } RerouteW readReroute; CreateRerouter writeReroute; bool callOriginal = true; DWORD newFlags = 0; { auto context = READ_CONTEXT(); readReroute = RerouteW::create(context, callContext, lpExistingFileName); callOriginal = writeReroute.rerouteNew(context, callContext, lpNewFileName, false, "hook_MoveFileW"); } if (callOriginal) { bool movedDrives = rewriteChangedDrives(lpExistingFileName, lpNewFileName, readReroute, writeReroute); if (movedDrives) newFlags |= MOVEFILE_COPY_ALLOWED; bool isDirectory = pathIsDirectory(readReroute.fileName()); PRE_REALCALL if (isDirectory && movedDrives) { SHFILEOPSTRUCTW sf = {0}; sf.wFunc = FO_MOVE; sf.hwnd = 0; sf.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI; sf.pFrom = readReroute.fileName(); sf.pTo = writeReroute.fileName(); int shRes = ::SHFileOperationW(&sf); switch (shRes) { case 0x78: callContext.updateLastError(ERROR_ACCESS_DENIED); break; case 0x7C: callContext.updateLastError(ERROR_FILE_NOT_FOUND); break; case 0x7E: case 0x80: callContext.updateLastError(ERROR_FILE_EXISTS); break; default: callContext.updateLastError(shRes); } res = shRes == 0; } else if (newFlags) res = ::MoveFileExW(readReroute.fileName(), writeReroute.fileName(), newFlags); else res = ::MoveFileW(readReroute.fileName(), writeReroute.fileName()); POST_REALCALL if (res) SetLastError(ERROR_SUCCESS); writeReroute.updateResult(callContext, res); if (res) { readReroute.removeMapping( READ_CONTEXT(), isDirectory); // Updating the rerouteCreate to check deleted // file entries should make this okay if (writeReroute.newReroute()) { if (isDirectory) RerouteW::addDirectoryMapping(WRITE_CONTEXT(), fs::path(lpNewFileName), fs::path(writeReroute.fileName())); else writeReroute.insertMapping(WRITE_CONTEXT()); } } if (readReroute.wasRerouted() || writeReroute.wasRerouted() || writeReroute.changedError()) LOG_CALL() .PARAM(readReroute.fileName()) .PARAM(writeReroute.fileName()) .PARAMWRAP(newFlags) .PARAM(res) .PARAM(writeReroute.originalError()) .PARAM(callContext.lastError()); } HOOK_END return res; } BOOL WINAPI usvfs::hook_MoveFileExA(LPCSTR lpExistingFileName, LPCSTR lpNewFileName, DWORD dwFlags) { BOOL res = FALSE; HOOK_START_GROUP(MutExHookGroup::SHELL_FILEOP) if (!callContext.active()) { res = MoveFileExA(lpExistingFileName, lpNewFileName, dwFlags); callContext.updateLastError(); return res; } HOOK_END HOOK_START const std::wstring existingFileName = ush::string_cast(lpExistingFileName); // careful: lpNewFileName can be null if dwFlags is // MOVEFILE_DELAY_UNTIL_REBOOT, so don't blindly cast the string and make sure // the null pointer is forwarded correctly std::wstring newFileNameWstring; const wchar_t* newFileName = nullptr; if (lpNewFileName) { newFileNameWstring = ush::string_cast(lpNewFileName); newFileName = newFileNameWstring.c_str(); } PRE_REALCALL res = MoveFileExW(existingFileName.c_str(), newFileName, dwFlags); POST_REALCALL HOOK_END return res; } BOOL WINAPI usvfs::hook_MoveFileExW(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName, DWORD dwFlags) { BOOL res = FALSE; HOOK_START_GROUP(MutExHookGroup::SHELL_FILEOP) if (!callContext.active()) { res = MoveFileExW(lpExistingFileName, lpNewFileName, dwFlags); callContext.updateLastError(); return res; } RerouteW readReroute; CreateRerouter writeReroute; bool callOriginal = true; DWORD newFlags = dwFlags; { auto context = READ_CONTEXT(); readReroute = RerouteW::create(context, callContext, lpExistingFileName); callOriginal = writeReroute.rerouteNew(context, callContext, lpNewFileName, newFlags & MOVEFILE_REPLACE_EXISTING, "hook_MoveFileExW"); } if (callOriginal) { bool movedDrives = rewriteChangedDrives(lpExistingFileName, lpNewFileName, readReroute, writeReroute); bool isDirectory = pathIsDirectory(readReroute.fileName()); PRE_REALCALL if (isDirectory && movedDrives) { SHFILEOPSTRUCTW sf = {0}; sf.wFunc = FO_MOVE; sf.hwnd = 0; sf.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI; sf.pFrom = readReroute.fileName(); sf.pTo = writeReroute.fileName(); int shRes = ::SHFileOperationW(&sf); switch (shRes) { case 0x78: callContext.updateLastError(ERROR_ACCESS_DENIED); break; case 0x7C: callContext.updateLastError(ERROR_FILE_NOT_FOUND); break; case 0x7E: case 0x80: callContext.updateLastError(ERROR_FILE_EXISTS); break; default: callContext.updateLastError(shRes); } res = shRes == 0; } else res = ::MoveFileExW(readReroute.fileName(), writeReroute.fileName(), newFlags); POST_REALCALL if (res) SetLastError(ERROR_SUCCESS); writeReroute.updateResult(callContext, res); if (res) { readReroute.removeMapping( READ_CONTEXT(), isDirectory); // Updating the rerouteCreate to check deleted // file entries should make this okay if (writeReroute.newReroute()) { if (isDirectory) RerouteW::addDirectoryMapping(WRITE_CONTEXT(), fs::path(lpNewFileName), fs::path(writeReroute.fileName())); else writeReroute.insertMapping(WRITE_CONTEXT()); } } if (readReroute.wasRerouted() || writeReroute.wasRerouted() || writeReroute.changedError()) LOG_CALL() .PARAM(readReroute.fileName()) .PARAM(writeReroute.fileName()) .PARAMWRAP(dwFlags) .PARAMWRAP(newFlags) .PARAM(res) .PARAM(writeReroute.originalError()) .PARAM(callContext.lastError()); } HOOK_END return res; } BOOL WINAPI usvfs::hook_MoveFileWithProgressA(LPCSTR lpExistingFileName, LPCSTR lpNewFileName, LPPROGRESS_ROUTINE lpProgressRoutine, LPVOID lpData, DWORD dwFlags) { BOOL res = FALSE; HOOK_START_GROUP(MutExHookGroup::SHELL_FILEOP) if (!callContext.active()) { res = MoveFileWithProgressA(lpExistingFileName, lpNewFileName, lpProgressRoutine, lpData, dwFlags); callContext.updateLastError(); return res; } HOOK_END HOOK_START const auto& existingFileName = ush::string_cast(lpExistingFileName); // careful: lpNewFileName can be null if dwFlags is // MOVEFILE_DELAY_UNTIL_REBOOT, so don't blindly cast the string and make sure // the null pointer is forwarded correctly std::wstring newFileNameWstring; const wchar_t* newFileName = nullptr; if (lpNewFileName) { newFileNameWstring = ush::string_cast(lpNewFileName); newFileName = newFileNameWstring.c_str(); } PRE_REALCALL res = MoveFileWithProgressW(existingFileName.c_str(), newFileName, lpProgressRoutine, lpData, dwFlags); POST_REALCALL HOOK_END return res; } BOOL WINAPI usvfs::hook_MoveFileWithProgressW(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName, LPPROGRESS_ROUTINE lpProgressRoutine, LPVOID lpData, DWORD dwFlags) { // TODO: Remove all redundant hooks to moveFile alternatives. // it would appear that all other moveFile functions end up calling this one with no // additional code. BOOL res = FALSE; HOOK_START_GROUP(MutExHookGroup::SHELL_FILEOP) if (!callContext.active()) { res = MoveFileWithProgressW(lpExistingFileName, lpNewFileName, lpProgressRoutine, lpData, dwFlags); callContext.updateLastError(); return res; } RerouteW readReroute; usvfs::CreateRerouter writeReroute; bool callOriginal = true; DWORD newFlags = dwFlags; { auto context = READ_CONTEXT(); readReroute = RerouteW::create(context, callContext, lpExistingFileName); callOriginal = writeReroute.rerouteNew(context, callContext, lpNewFileName, newFlags & MOVEFILE_REPLACE_EXISTING, "hook_MoveFileWithProgressW"); } if (callOriginal) { bool movedDrives = rewriteChangedDrives(lpExistingFileName, lpNewFileName, readReroute, writeReroute); if (movedDrives) newFlags |= MOVEFILE_COPY_ALLOWED; bool isDirectory = pathIsDirectory(readReroute.fileName()); PRE_REALCALL if (isDirectory && movedDrives) { SHFILEOPSTRUCTW sf = {0}; sf.wFunc = FO_MOVE; sf.hwnd = 0; sf.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI; sf.pFrom = readReroute.fileName(); sf.pTo = writeReroute.fileName(); int shRes = ::SHFileOperationW(&sf); switch (shRes) { case 0x78: callContext.updateLastError(ERROR_ACCESS_DENIED); break; case 0x7C: callContext.updateLastError(ERROR_FILE_NOT_FOUND); break; case 0x7E: case 0x80: callContext.updateLastError(ERROR_FILE_EXISTS); break; default: callContext.updateLastError(shRes); } res = shRes == 0; } else res = ::MoveFileWithProgressW(readReroute.fileName(), writeReroute.fileName(), lpProgressRoutine, lpData, newFlags); POST_REALCALL if (res) SetLastError(ERROR_SUCCESS); writeReroute.updateResult(callContext, res); if (res) { // TODO: this call causes the node to be removed twice in case of // MOVEFILE_COPY_ALLOWED as the deleteFile hook lower level already takes care of // it, but deleteFile can't be disabled since we are relying on it in case of // MOVEFILE_REPLACE_EXISTING for the destination file. readReroute.removeMapping( READ_CONTEXT(), isDirectory); // Updating the rerouteCreate to check deleted file entries // should make this okay (not related to comments above) if (writeReroute.newReroute()) { if (isDirectory) RerouteW::addDirectoryMapping(WRITE_CONTEXT(), fs::path(lpNewFileName), fs::path(writeReroute.fileName())); else writeReroute.insertMapping(WRITE_CONTEXT()); } } if (readReroute.wasRerouted() || writeReroute.wasRerouted() || writeReroute.changedError()) LOG_CALL() .PARAM(readReroute.fileName()) .PARAM(writeReroute.fileName()) .PARAMWRAP(dwFlags) .PARAMWRAP(newFlags) .PARAM(res) .PARAM(writeReroute.originalError()) .PARAM(callContext.lastError()); } HOOK_END return res; } BOOL WINAPI usvfs::hook_CopyFileExW(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName, LPPROGRESS_ROUTINE lpProgressRoutine, LPVOID lpData, LPBOOL pbCancel, DWORD dwCopyFlags) { BOOL res = FALSE; HOOK_START_GROUP(MutExHookGroup::SHELL_FILEOP) if (!callContext.active()) { res = CopyFileExW(lpExistingFileName, lpNewFileName, lpProgressRoutine, lpData, pbCancel, dwCopyFlags); callContext.updateLastError(); return res; } RerouteW readReroute; usvfs::CreateRerouter writeReroute; bool callOriginal = true; { auto context = READ_CONTEXT(); readReroute = RerouteW::create(context, callContext, lpExistingFileName); callOriginal = writeReroute.rerouteNew( context, callContext, lpNewFileName, (dwCopyFlags & COPY_FILE_FAIL_IF_EXISTS) == 0, "hook_CopyFileExW"); } if (callOriginal) { PRE_REALCALL res = ::CopyFileExW(readReroute.fileName(), writeReroute.fileName(), lpProgressRoutine, lpData, pbCancel, dwCopyFlags); POST_REALCALL writeReroute.updateResult(callContext, res); if (res && writeReroute.newReroute()) writeReroute.insertMapping(WRITE_CONTEXT()); if (readReroute.wasRerouted() || writeReroute.wasRerouted() || writeReroute.changedError()) LOG_CALL() .PARAM(readReroute.fileName()) .PARAM(writeReroute.fileName()) .PARAM(res) .PARAM(writeReroute.originalError()) .PARAM(callContext.lastError()); } HOOK_END return res; } DWORD WINAPI usvfs::hook_GetCurrentDirectoryA(DWORD nBufferLength, LPSTR lpBuffer) { DWORD res = 0; HOOK_START std::wstring buffer; buffer.resize(nBufferLength); PRE_REALCALL res = GetCurrentDirectoryW(nBufferLength, &buffer[0]); POST_REALCALL if (res > 0) { res = WideCharToMultiByte(CP_ACP, 0, buffer.c_str(), res + 1, lpBuffer, nBufferLength, nullptr, nullptr); } HOOK_END return res; } DWORD WINAPI usvfs::hook_GetCurrentDirectoryW(DWORD nBufferLength, LPWSTR lpBuffer) { DWORD res = FALSE; HOOK_START std::wstring actualCWD; if (!k32CurrentDirectoryTracker.get(actualCWD)) { PRE_REALCALL res = ::GetCurrentDirectoryW(nBufferLength, lpBuffer); POST_REALCALL } else { ush::wcsncpy_sz(lpBuffer, &actualCWD[0], std::min(static_cast(nBufferLength), actualCWD.size() + 1)); // yupp, that's how GetCurrentDirectory actually works... if (actualCWD.size() < nBufferLength) { res = static_cast(actualCWD.size()); } else { res = static_cast(actualCWD.size() + 1); } } if (nBufferLength) LOG_CALL() .PARAM(std::wstring(lpBuffer, res)) .PARAM(nBufferLength) .PARAM(actualCWD.size()) .PARAM(res) .PARAM(callContext.lastError()); HOOK_END return res; } BOOL WINAPI usvfs::hook_SetCurrentDirectoryA(LPCSTR lpPathName) { return SetCurrentDirectoryW(ush::string_cast(lpPathName).c_str()); } BOOL WINAPI usvfs::hook_SetCurrentDirectoryW(LPCWSTR lpPathName) { BOOL res = FALSE; HOOK_START const fs::path& realPath = RerouteW::canonizePath(RerouteW::absolutePath(lpPathName)); const std::wstring& realPathStr = realPath.wstring(); std::wstring finalRoute; BOOL found = FALSE; if (fs::exists(realPath)) finalRoute = realPathStr; else { WCHAR processDir[MAX_PATH]; if (::GetModuleFileNameW(NULL, processDir, MAX_PATH) != 0 && ::PathRemoveFileSpecW(processDir)) { WCHAR processName[MAX_PATH]; ::GetModuleFileNameW(NULL, processName, MAX_PATH); fs::path routedName = realPath / processName; RerouteW rerouteTest = RerouteW::create(READ_CONTEXT(), callContext, routedName.wstring().c_str()); if (rerouteTest.wasRerouted()) { std::wstring reroutedPath = rerouteTest.fileName(); if (routedName.wstring().find(processDir) != std::string::npos) { fs::path finalPath(reroutedPath); finalRoute = finalPath.parent_path().wstring(); found = TRUE; } } } if (!found) { RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, realPathStr.c_str()); finalRoute = reroute.fileName(); } } PRE_REALCALL res = ::SetCurrentDirectoryW(finalRoute.c_str()); POST_REALCALL if (res) if (!k32CurrentDirectoryTracker.set(realPathStr)) spdlog::get("usvfs")->warn("Updating actual current directory failed: {} ?!", string_cast(realPathStr)); LOG_CALL() .PARAM(lpPathName) .PARAM(realPathStr) .PARAM(finalRoute) .PARAM(res) .PARAM(callContext.lastError()); HOOK_END return res; } DLLEXPORT BOOL WINAPI usvfs::hook_CreateDirectoryW( LPCWSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes) { BOOL res = FALSE; HOOK_START RerouteW reroute = RerouteW::createOrNew(READ_CONTEXT(), callContext, lpPathName); PRE_REALCALL res = ::CreateDirectoryW(reroute.fileName(), lpSecurityAttributes); POST_REALCALL if (res && reroute.newReroute()) reroute.insertMapping(WRITE_CONTEXT(), true); if (reroute.wasRerouted()) LOG_CALL() .PARAM(lpPathName) .PARAM(reroute.fileName()) .PARAM(res) .PARAM(callContext.lastError()); HOOK_END return res; } DLLEXPORT BOOL WINAPI usvfs::hook_RemoveDirectoryW(LPCWSTR lpPathName) { BOOL res = FALSE; HOOK_START_GROUP(MutExHookGroup::DELETE_FILE) // Why is the usual if (!callContext.active()... check missing? RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, lpPathName); PRE_REALCALL if (reroute.wasRerouted()) { res = ::RemoveDirectoryW(reroute.fileName()); } else { res = ::RemoveDirectoryW(lpPathName); } POST_REALCALL if (res) { reroute.removeMapping(READ_CONTEXT(), true); } if (reroute.wasRerouted()) LOG_CALL() .PARAM(lpPathName) .PARAM(reroute.fileName()) .PARAM(res) .PARAM(callContext.lastError()); HOOK_END return res; } DWORD WINAPI usvfs::hook_GetFullPathNameA(LPCSTR lpFileName, DWORD nBufferLength, LPSTR lpBuffer, LPSTR* lpFilePart) { DWORD res = 0UL; HOOK_START_GROUP(MutExHookGroup::FULL_PATHNAME) if (!callContext.active()) { res = GetFullPathNameA(lpFileName, nBufferLength, lpBuffer, lpFilePart); callContext.updateLastError(); return res; } std::string resolvedWithCMD; std::wstring actualCWD; fs::path filePath = ush::string_cast(lpFileName, CodePage::UTF8); if (k32CurrentDirectoryTracker.get(actualCWD, filePath.wstring().c_str())) { if (!filePath.is_absolute()) resolvedWithCMD = ush::string_cast( (actualCWD / filePath.relative_path()).wstring()); } PRE_REALCALL res = ::GetFullPathNameA(resolvedWithCMD.empty() ? lpFileName : resolvedWithCMD.c_str(), nBufferLength, lpBuffer, lpFilePart); POST_REALCALL if (false && nBufferLength) LOG_CALL() .PARAM(lpFileName) .PARAM(resolvedWithCMD) .PARAM(std::string(lpBuffer, res)) .PARAM(nBufferLength) .PARAM(res) .PARAM(callContext.lastError()); HOOK_END return res; } DWORD WINAPI usvfs::hook_GetFullPathNameW(LPCWSTR lpFileName, DWORD nBufferLength, LPWSTR lpBuffer, LPWSTR* lpFilePart) { DWORD res = 0UL; HOOK_START_GROUP(MutExHookGroup::FULL_PATHNAME) if (!callContext.active()) { res = GetFullPathNameW(lpFileName, nBufferLength, lpBuffer, lpFilePart); callContext.updateLastError(); return res; } std::wstring resolvedWithCMD; std::wstring actualCWD; if (k32CurrentDirectoryTracker.get(actualCWD, lpFileName)) { fs::path filePath = lpFileName; if (!filePath.is_absolute()) resolvedWithCMD = (actualCWD / filePath.relative_path()).wstring(); } PRE_REALCALL res = ::GetFullPathNameW(resolvedWithCMD.empty() ? lpFileName : resolvedWithCMD.c_str(), nBufferLength, lpBuffer, lpFilePart); POST_REALCALL if (false && nBufferLength) LOG_CALL() .PARAM(lpFileName) .PARAM(resolvedWithCMD) .PARAM(std::wstring(lpBuffer, res)) .PARAM(nBufferLength) .PARAM(res) .PARAM(callContext.lastError()); HOOK_END return res; } DWORD WINAPI usvfs::hook_GetModuleFileNameA(HMODULE hModule, LPSTR lpFilename, DWORD nSize) { DWORD res = 0UL; HOOK_START_GROUP(MutExHookGroup::ALL_GROUPS) PRE_REALCALL res = ::GetModuleFileNameA(hModule, lpFilename, nSize); POST_REALCALL if ((res != 0) && callContext.active()) { std::vector buf; // If GetModuleFileNameA failed because the buffer is not large enough this // complicates matters because we are dealing with incomplete information // (consider for example the case that we have a long real path which will // be routed to a short virtual so the call should actually succeed in such // a case). To solve this we simply use our own buffer to find the complete // module path: DWORD full_res = res; size_t buf_size = nSize; while (full_res == buf_size) { buf_size = std::max(static_cast(MAX_PATH), buf_size * 2); buf.resize(buf_size); full_res = ::GetModuleFileNameA(hModule, buf.data(), buf_size); } RerouteW reroute = RerouteW::create( READ_CONTEXT(), callContext, ush::string_cast(buf.empty() ? lpFilename : buf.data()).c_str(), true); if (reroute.wasRerouted()) { DWORD reroutedSize = static_cast(wcslen(reroute.fileName())); if (reroutedSize >= nSize) { reroutedSize = nSize - 1; callContext.updateLastError(ERROR_INSUFFICIENT_BUFFER); res = nSize; } else res = reroutedSize; memcpy(lpFilename, ush::string_cast(reroute.fileName()).c_str(), reroutedSize * sizeof(lpFilename[0])); lpFilename[reroutedSize] = 0; LOG_CALL() .PARAM(hModule) .addParam("lpFilename", (res != 0UL) ? lpFilename : "") .PARAM(nSize) .PARAM(res) .PARAM(callContext.lastError()); } } HOOK_END return res; } DWORD WINAPI usvfs::hook_GetModuleFileNameW(HMODULE hModule, LPWSTR lpFilename, DWORD nSize) { DWORD res = 0UL; HOOK_START_GROUP(MutExHookGroup::ALL_GROUPS) PRE_REALCALL res = ::GetModuleFileNameW(hModule, lpFilename, nSize); POST_REALCALL if ((res != 0) && callContext.active()) { std::vector buf; // If GetModuleFileNameW failed because the buffer is not large enough this // complicates matters because we are dealing with incomplete information (consider // for example the case that we have a long real path which will be routed to a // short virtual so the call should actually succeed in such a case). To solve this // we simply use our own buffer to find the complete module path: DWORD full_res = res; size_t buf_size = nSize; while (full_res == buf_size) { buf_size = std::max(static_cast(MAX_PATH), buf_size * 2); buf.resize(buf_size); full_res = ::GetModuleFileNameW(hModule, buf.data(), buf_size); } RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, buf.empty() ? lpFilename : buf.data(), true); if (reroute.wasRerouted()) { DWORD reroutedSize = static_cast(wcslen(reroute.fileName())); if (reroutedSize >= nSize) { reroutedSize = nSize - 1; callContext.updateLastError(ERROR_INSUFFICIENT_BUFFER); res = nSize; } else res = reroutedSize; memcpy(lpFilename, reroute.fileName(), reroutedSize * sizeof(lpFilename[0])); lpFilename[reroutedSize] = 0; LOG_CALL() .PARAM(hModule) .addParam("lpFilename", (res != 0UL) ? lpFilename : L"") .PARAM(nSize) .PARAM(res) .PARAM(callContext.lastError()); } } HOOK_END return res; } HANDLE WINAPI usvfs::hook_FindFirstFileExW( LPCWSTR lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, LPVOID lpFindFileData, FINDEX_SEARCH_OPS fSearchOp, LPVOID lpSearchFilter, DWORD dwAdditionalFlags) { HANDLE res = INVALID_HANDLE_VALUE; HOOK_START_GROUP(MutExHookGroup::SEARCH_FILES) if (!callContext.active()) { res = FindFirstFileExW(lpFileName, fInfoLevelId, lpFindFileData, fSearchOp, lpSearchFilter, dwAdditionalFlags); callContext.updateLastError(); return res; } // FindFirstFileEx() must fail early if the path ends with a slash if (lpFileName) { const auto len = wcslen(lpFileName); if (len > 0) { if (lpFileName[len - 1] == L'\\' || lpFileName[len - 1] == L'/') { spdlog::get("usvfs")->warn( "hook_FindFirstFileExW(): path '{}' ends with slash, always fails", fs::path(lpFileName).string()); return INVALID_HANDLE_VALUE; } } } fs::path finalPath; RerouteW reroute; fs::path originalPath; bool usedRewrite = false; // We need to do some trickery here, since we only want to use the hooked // NtQueryDirectoryFile for rerouted locations we need to check if the Directory path // has been routed instead of the full path. originalPath = RerouteW::canonizePath(RerouteW::absolutePath(lpFileName)); PRE_REALCALL res = ::FindFirstFileExW(originalPath.c_str(), fInfoLevelId, lpFindFileData, fSearchOp, lpSearchFilter, dwAdditionalFlags); POST_REALCALL if (res == INVALID_HANDLE_VALUE) { fs::path searchPath = originalPath.filename(); fs::path parentPath = originalPath.parent_path(); std::wstring findPath = parentPath.wstring(); while (findPath.find(L"*?<>\"", 0, 1) != std::wstring::npos) { searchPath = parentPath.filename() / searchPath; parentPath = parentPath.parent_path(); findPath = parentPath.wstring(); } reroute = RerouteW::create(READ_CONTEXT(), callContext, parentPath.c_str()); if (reroute.wasRerouted()) { finalPath = reroute.fileName(); finalPath /= searchPath.wstring(); } if (!finalPath.empty()) { PRE_REALCALL usedRewrite = true; res = ::FindFirstFileExW(finalPath.c_str(), fInfoLevelId, lpFindFileData, fSearchOp, lpSearchFilter, dwAdditionalFlags); POST_REALCALL } } if (res != INVALID_HANDLE_VALUE) { // store the original search path for use during iteration WRITE_CONTEXT()->customData(SearchHandles)[res] = lpFileName; } LOG_CALL() .PARAM(lpFileName) .PARAM(originalPath.c_str()) .PARAM(res) .PARAM(callContext.lastError()); if (usedRewrite) LOG_CALL() .PARAM(lpFileName) .PARAM(finalPath.c_str()) .PARAM(res) .PARAM(callContext.lastError()); HOOK_END return res; } HRESULT(WINAPI* usvfs::CopyFile2)(PCWSTR pwszExistingFileName, PCWSTR pwszNewFileName, COPYFILE2_EXTENDED_PARAMETERS* pExtendedParameters); HRESULT WINAPI usvfs::hook_CopyFile2(PCWSTR pwszExistingFileName, PCWSTR pwszNewFileName, COPYFILE2_EXTENDED_PARAMETERS* pExtendedParameters) { HRESULT res = E_FAIL; typedef HRESULT(WINAPI * CopyFile2_t)(PCWSTR, PCWSTR, COPYFILE2_EXTENDED_PARAMETERS*); HOOK_START_GROUP(MutExHookGroup::SHELL_FILEOP) if (!callContext.active()) { res = CopyFile2(pwszExistingFileName, pwszNewFileName, pExtendedParameters); callContext.updateLastError(); return res; } RerouteW readReroute; CreateRerouter writeReroute; bool callOriginal = true; { auto context = READ_CONTEXT(); readReroute = RerouteW::create(context, callContext, pwszExistingFileName); callOriginal = writeReroute.rerouteNew( context, callContext, pwszNewFileName, pExtendedParameters && (pExtendedParameters->dwCopyFlags & COPY_FILE_FAIL_IF_EXISTS) == 0, "hook_CopyFile2"); } if (callOriginal) { PRE_REALCALL res = CopyFile2(readReroute.fileName(), writeReroute.fileName(), pExtendedParameters); POST_REALCALL writeReroute.updateResult(callContext, SUCCEEDED(res)); if (SUCCEEDED(res) && writeReroute.newReroute()) writeReroute.insertMapping(WRITE_CONTEXT()); if (readReroute.wasRerouted() || writeReroute.wasRerouted() || writeReroute.changedError()) LOG_CALL() .PARAM(readReroute.fileName()) .PARAM(writeReroute.fileName()) .PARAM(res) .PARAM(writeReroute.originalError()) .PARAM(callContext.lastError()); } HOOK_END return res; } DWORD WINAPI usvfs::hook_GetPrivateProfileStringA(LPCSTR lpAppName, LPCSTR lpKeyName, LPCSTR lpDefault, LPSTR lpReturnedString, DWORD nSize, LPCSTR lpFileName) { DWORD res = 0; HOOK_START_GROUP(MutExHookGroup::OPEN_FILE) if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) { res = ::GetPrivateProfileStringA(lpAppName, lpKeyName, lpDefault, lpReturnedString, nSize, lpFileName); callContext.updateLastError(); return res; } RerouteW reroute = RerouteW::create( READ_CONTEXT(), callContext, ush::string_cast(lpFileName).c_str()); PRE_REALCALL res = ::GetPrivateProfileStringA( lpAppName, lpKeyName, lpDefault, lpReturnedString, nSize, ush::string_cast(reroute.fileName()).c_str()); POST_REALCALL if (reroute.wasRerouted()) { LOG_CALL() .PARAM(lpAppName) .PARAM(lpKeyName) .PARAM(lpFileName) .PARAM(reroute.fileName()) .PARAMHEX(res) .PARAM(callContext.lastError()); } HOOK_END return res; } DWORD WINAPI usvfs::hook_GetPrivateProfileStringW(LPCWSTR lpAppName, LPCWSTR lpKeyName, LPCWSTR lpDefault, LPWSTR lpReturnedString, DWORD nSize, LPCWSTR lpFileName) { DWORD res = 0; HOOK_START_GROUP(MutExHookGroup::OPEN_FILE) if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) { res = ::GetPrivateProfileStringW(lpAppName, lpKeyName, lpDefault, lpReturnedString, nSize, lpFileName); callContext.updateLastError(); return res; } RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, lpFileName); PRE_REALCALL res = ::GetPrivateProfileStringW(lpAppName, lpKeyName, lpDefault, lpReturnedString, nSize, reroute.fileName()); POST_REALCALL if (reroute.wasRerouted()) { LOG_CALL() .PARAM(lpAppName) .PARAM(lpKeyName) .PARAM(lpFileName) .PARAM(reroute.fileName()) .PARAMHEX(res) .PARAM(callContext.lastError()); } HOOK_END return res; } DWORD WINAPI usvfs::hook_GetPrivateProfileSectionA(LPCSTR lpAppName, LPSTR lpReturnedString, DWORD nSize, LPCSTR lpFileName) { DWORD res = 0; HOOK_START_GROUP(MutExHookGroup::OPEN_FILE) if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) { res = ::GetPrivateProfileSectionA(lpAppName, lpReturnedString, nSize, lpFileName); callContext.updateLastError(); return res; } RerouteW reroute = RerouteW::create( READ_CONTEXT(), callContext, ush::string_cast(lpFileName).c_str()); PRE_REALCALL res = ::GetPrivateProfileSectionA( lpAppName, lpReturnedString, nSize, ush::string_cast(reroute.fileName()).c_str()); POST_REALCALL if (reroute.wasRerouted()) { LOG_CALL() .PARAM(lpAppName) .PARAM(lpFileName) .PARAM(reroute.fileName()) .PARAMHEX(res) .PARAM(callContext.lastError()); } HOOK_END return res; } DWORD WINAPI usvfs::hook_GetPrivateProfileSectionW(LPCWSTR lpAppName, LPWSTR lpReturnedString, DWORD nSize, LPCWSTR lpFileName) { DWORD res = 0; HOOK_START_GROUP(MutExHookGroup::OPEN_FILE) if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) { res = ::GetPrivateProfileSectionW(lpAppName, lpReturnedString, nSize, lpFileName); callContext.updateLastError(); return res; } RerouteW reroute = RerouteW::create(READ_CONTEXT(), callContext, lpFileName); PRE_REALCALL res = ::GetPrivateProfileSectionW(lpAppName, lpReturnedString, nSize, reroute.fileName()); POST_REALCALL if (reroute.wasRerouted()) { LOG_CALL() .PARAM(lpAppName) .PARAM(lpFileName) .PARAM(reroute.fileName()) .PARAMHEX(res) .PARAM(callContext.lastError()); } HOOK_END return res; } BOOL WINAPI usvfs::hook_WritePrivateProfileStringA(LPCSTR lpAppName, LPCSTR lpKeyName, LPCSTR lpString, LPCSTR lpFileName) { BOOL res = false; HOOK_START_GROUP(MutExHookGroup::OPEN_FILE) if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) { res = ::WritePrivateProfileStringA(lpAppName, lpKeyName, lpString, lpFileName); callContext.updateLastError(); return res; } CreateRerouter reroute; bool callOriginal = reroute.rerouteNew( READ_CONTEXT(), callContext, ush::string_cast(lpFileName).c_str(), true, "hook_WritePrivateProfileStringA"); if (callOriginal) { PRE_REALCALL res = ::WritePrivateProfileStringA( lpAppName, lpKeyName, lpString, ush::string_cast(reroute.fileName()).c_str()); POST_REALCALL reroute.updateResult(callContext, res); if (res && reroute.newReroute()) reroute.insertMapping(WRITE_CONTEXT()); if (reroute.wasRerouted() || reroute.changedError()) LOG_CALL() .PARAM(lpAppName) .PARAM(lpKeyName) .PARAM(lpFileName) .PARAM(reroute.fileName()) .PARAMHEX(res) .PARAM(reroute.originalError()) .PARAM(callContext.lastError()); } HOOK_END return res; } BOOL WINAPI usvfs::hook_WritePrivateProfileStringW(LPCWSTR lpAppName, LPCWSTR lpKeyName, LPCWSTR lpString, LPCWSTR lpFileName) { BOOL res = false; HOOK_START_GROUP(MutExHookGroup::OPEN_FILE) if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) { res = ::WritePrivateProfileStringW(lpAppName, lpKeyName, lpString, lpFileName); callContext.updateLastError(); return res; } CreateRerouter reroute; bool callOriginal = reroute.rerouteNew(READ_CONTEXT(), callContext, lpFileName, true, "hook_WritePrivateProfileStringW"); if (callOriginal) { PRE_REALCALL res = ::WritePrivateProfileStringW(lpAppName, lpKeyName, lpString, reroute.fileName()); POST_REALCALL reroute.updateResult(callContext, res); if (res && reroute.newReroute()) reroute.insertMapping(WRITE_CONTEXT()); if (reroute.wasRerouted() || reroute.changedError()) LOG_CALL() .PARAM(lpAppName) .PARAM(lpKeyName) .PARAM(lpFileName) .PARAM(reroute.fileName()) .PARAMHEX(res) .PARAM(reroute.originalError()) .PARAM(callContext.lastError()); } HOOK_END return res; } VOID WINAPI usvfs::hook_ExitProcess(UINT exitCode) { HOOK_START { HookContext::Ptr context = WRITE_CONTEXT(); std::vector>& delayed = context->delayed(); if (!delayed.empty()) { // ensure all delayed tasks are completed before we exit the process for (std::future& delayedOp : delayed) { delayedOp.get(); } delayed.clear(); } } // exitprocess doesn't return so logging the call after the real call doesn't // make much sense. // nor does any pre/post call macro LOG_CALL().PARAM(exitCode); usvfsDisconnectVFS(); // HookManager::instance().removeHook("ExitProcess"); // PRE_REALCALL ::ExitProcess(exitCode); // POST_REALCALL HOOK_END }