dolphin/Externals/WIL/tests/FileSystemTests.cpp

461 lines
20 KiB
C++

#include <windows.h>
#include <winstring.h> // For wil::unique_hstring
#include <wil/common.h>
#ifdef WIL_ENABLE_EXCEPTIONS
#include <string>
#endif
// TODO: str_raw_ptr is not two-phase name lookup clean (https://github.com/Microsoft/wil/issues/8)
namespace wil
{
PCWSTR str_raw_ptr(HSTRING);
#ifdef WIL_ENABLE_EXCEPTIONS
PCWSTR str_raw_ptr(const std::wstring&);
#endif
}
#include <wil/filesystem.h>
#ifdef WIL_ENABLE_EXCEPTIONS
#include <wil/stl.h> // For std::wstring string_maker
#endif
#include "common.h"
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
bool DirectoryExists(_In_ PCWSTR path)
{
DWORD dwAttrib = GetFileAttributesW(path);
return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
}
TEST_CASE("FileSystemTests::CreateDirectory", "[filesystem]")
{
wchar_t basePath[MAX_PATH];
REQUIRE(GetTempPathW(ARRAYSIZE(basePath), basePath));
REQUIRE_SUCCEEDED(PathCchAppend(basePath, ARRAYSIZE(basePath), L"FileSystemTests"));
REQUIRE_FALSE(DirectoryExists(basePath));
REQUIRE(SUCCEEDED(wil::CreateDirectoryDeepNoThrow(basePath)));
REQUIRE(DirectoryExists(basePath));
auto scopeGuard = wil::scope_exit([&]
{
REQUIRE_SUCCEEDED(wil::RemoveDirectoryRecursiveNoThrow(basePath));
});
PCWSTR relativeTestPath = L"folder1\\folder2\\folder3\\folder4\\folder5\\folder6\\folder7\\folder8";
wchar_t absoluteTestPath[MAX_PATH];
REQUIRE_SUCCEEDED(StringCchCopyW(absoluteTestPath, ARRAYSIZE(absoluteTestPath), basePath));
REQUIRE_SUCCEEDED(PathCchAppend(absoluteTestPath, ARRAYSIZE(absoluteTestPath), relativeTestPath));
REQUIRE_FALSE(DirectoryExists(absoluteTestPath));
REQUIRE_SUCCEEDED(wil::CreateDirectoryDeepNoThrow(absoluteTestPath));
PCWSTR invalidCharsPath = L"Bad?Char|";
wchar_t absoluteInvalidPath[MAX_PATH];
REQUIRE_SUCCEEDED(StringCchCopyW(absoluteInvalidPath, ARRAYSIZE(absoluteInvalidPath), basePath));
REQUIRE_SUCCEEDED(PathCchAppend(absoluteInvalidPath, ARRAYSIZE(absoluteInvalidPath), invalidCharsPath));
REQUIRE_FALSE(DirectoryExists(absoluteInvalidPath));
REQUIRE_FALSE(SUCCEEDED(wil::CreateDirectoryDeepNoThrow(absoluteInvalidPath)));
PCWSTR testPath3 = L"folder1\\folder2\\folder3";
wchar_t absoluteTestPath3[MAX_PATH];
REQUIRE_SUCCEEDED(StringCchCopyW(absoluteTestPath3, ARRAYSIZE(absoluteTestPath3), basePath));
REQUIRE_SUCCEEDED(PathCchAppend(absoluteTestPath3, ARRAYSIZE(absoluteTestPath3), testPath3));
REQUIRE(DirectoryExists(absoluteTestPath3));
PCWSTR testPath4 = L"folder1\\folder2\\folder3\\folder4";
wchar_t absoluteTestPath4[MAX_PATH];
REQUIRE_SUCCEEDED(StringCchCopyW(absoluteTestPath4, ARRAYSIZE(absoluteTestPath4), basePath));
REQUIRE_SUCCEEDED(PathCchAppend(absoluteTestPath4, ARRAYSIZE(absoluteTestPath4), testPath4));
REQUIRE(DirectoryExists(absoluteTestPath4));
REQUIRE_SUCCEEDED(wil::RemoveDirectoryRecursiveNoThrow(absoluteTestPath3, wil::RemoveDirectoryOptions::KeepRootDirectory));
REQUIRE(DirectoryExists(absoluteTestPath3));
REQUIRE_FALSE(DirectoryExists(absoluteTestPath4));
}
#ifdef WIL_ENABLE_EXCEPTIONS
// Learn about the Win32 API normalization here: https://blogs.msdn.microsoft.com/jeremykuhne/2016/04/21/path-normalization/
// This test verifies the ability of RemoveDirectoryRecursive to be able to delete files
// that are in the non-normalized form.
TEST_CASE("FileSystemTests::VerifyRemoveDirectoryRecursiveCanDeleteFoldersWithNonNormalizedNames", "[filesystem]")
{
// Extended length paths can access files with non-normalized names.
// This function creates a path with that ability.
auto CreatePathThatCanAccessNonNormalizedNames = [](PCWSTR root, PCWSTR name)
{
wil::unique_hlocal_string path;
THROW_IF_FAILED(PathAllocCombine(root, name, PATHCCH_DO_NOT_NORMALIZE_SEGMENTS | PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, &path));
REQUIRE(wil::is_extended_length_path(path.get()));
return path;
};
// Regular paths are normalized in the Win32 APIs thus can't address files in the non-normalized form.
// This function creates a regular path form but preserves the non-normalized parts of the input (for testing)
auto CreateRegularPath = [](PCWSTR root, PCWSTR name)
{
wil::unique_hlocal_string path;
THROW_IF_FAILED(PathAllocCombine(root, name, PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, &path));
REQUIRE_FALSE(wil::is_extended_length_path(path.get()));
return path;
};
struct TestCases
{
PCWSTR CreateWithName;
PCWSTR DeleteWithName;
wil::unique_hlocal_string (*CreatePathFunction)(PCWSTR root, PCWSTR name);
HRESULT ExpectedResult;
};
PCWSTR NormalizedName = L"Foo";
PCWSTR NonNormalizedName = L"Foo."; // The dot at the end is what makes this non-normalized.
const auto PathNotFoundError = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
TestCases tests[] =
{
{ NormalizedName, NormalizedName, CreateRegularPath, S_OK },
{ NonNormalizedName, NormalizedName, CreateRegularPath, PathNotFoundError },
{ NormalizedName, NonNormalizedName, CreateRegularPath, S_OK },
{ NonNormalizedName, NonNormalizedName, CreateRegularPath, PathNotFoundError },
{ NormalizedName, NormalizedName, CreatePathThatCanAccessNonNormalizedNames, S_OK },
{ NonNormalizedName, NormalizedName, CreatePathThatCanAccessNonNormalizedNames, PathNotFoundError },
{ NormalizedName, NonNormalizedName, CreatePathThatCanAccessNonNormalizedNames, PathNotFoundError },
{ NonNormalizedName, NonNormalizedName, CreatePathThatCanAccessNonNormalizedNames, S_OK },
};
auto folderRoot = wil::ExpandEnvironmentStringsW(LR"(%TEMP%)");
REQUIRE_FALSE(wil::is_extended_length_path(folderRoot.get()));
auto EnsureFolderWithNonCanonicalNameAndContentsExists = [&](const TestCases& test)
{
const auto enableNonNormalized = PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH | PATHCCH_DO_NOT_NORMALIZE_SEGMENTS;
wil::unique_hlocal_string targetFolder;
// Create a folder for testing using the extended length form to enable
// access to non-normalized forms of the path
THROW_IF_FAILED(PathAllocCombine(folderRoot.get(), test.CreateWithName, enableNonNormalized, &targetFolder));
// This ensures the folder is there and won't fail if it already exists (common when testing).
wil::CreateDirectoryDeep(targetFolder.get());
// Create a file in that folder with a non-normalized name (with the dot at the end).
wil::unique_hlocal_string extendedFilePath;
THROW_IF_FAILED(PathAllocCombine(targetFolder.get(), L"NonNormalized.", enableNonNormalized, &extendedFilePath));
wil::unique_hfile fileHandle(CreateFileW(extendedFilePath.get(), FILE_WRITE_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr));
THROW_LAST_ERROR_IF(!fileHandle);
};
for (auto const& test : tests)
{
// remove remnants from previous test that will cause failures
wil::RemoveDirectoryRecursiveNoThrow(CreatePathThatCanAccessNonNormalizedNames(folderRoot.get(), NormalizedName).get());
wil::RemoveDirectoryRecursiveNoThrow(CreatePathThatCanAccessNonNormalizedNames(folderRoot.get(), NonNormalizedName).get());
EnsureFolderWithNonCanonicalNameAndContentsExists(test);
auto deleteWithPath = test.CreatePathFunction(folderRoot.get(), test.DeleteWithName);
const auto hr = wil::RemoveDirectoryRecursiveNoThrow(deleteWithPath.get());
REQUIRE(test.ExpectedResult == hr);
}
}
#endif
// real paths to test
const wchar_t c_variablePath[] = L"%systemdrive%\\Windows\\System32\\Windows.Storage.dll";
const wchar_t c_expandedPath[] = L"c:\\Windows\\System32\\Windows.Storage.dll";
// // paths that should not exist on the system
const wchar_t c_missingVariable[] = L"%doesnotexist%\\doesnotexist.dll";
const wchar_t c_missingPath[] = L"c:\\Windows\\System32\\doesnotexist.dll";
const int c_stackBufferLimitTest = 5;
#ifdef WIL_ENABLE_EXCEPTIONS
TEST_CASE("FileSystemTests::VerifyGetCurrentDirectory", "[filesystem]")
{
auto pwd = wil::GetCurrentDirectoryW();
REQUIRE(*pwd.get() != L'\0');
}
TEST_CASE("FileSystemTests::VerifyGetFullPathName", "[filesystem]")
{
PCWSTR fileName = L"ReadMe.txt";
auto result = wil::GetFullPathNameW<wil::unique_cotaskmem_string>(fileName, nullptr);
PCWSTR fileNameResult;
result = wil::GetFullPathNameW<wil::unique_cotaskmem_string>(fileName, &fileNameResult);
REQUIRE(wcscmp(fileName, fileNameResult) == 0);
auto result2 = wil::GetFullPathNameW<wil::unique_cotaskmem_string, c_stackBufferLimitTest>(fileName, &fileNameResult);
REQUIRE(wcscmp(fileName, fileNameResult) == 0);
REQUIRE(wcscmp(result.get(), result2.get()) == 0);
// The only negative test case I've found is a path > 32k.
std::wstring big(1024 * 32, L'a');
wil::unique_hstring output;
auto hr = wil::GetFullPathNameW(big.c_str(), output, nullptr);
REQUIRE(hr == HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE));
}
TEST_CASE("FileSystemTests::VerifyGetFinalPathNameByHandle", "[filesystem]")
{
wil::unique_hfile fileHandle(CreateFileW(c_expandedPath, FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, nullptr));
THROW_LAST_ERROR_IF(!fileHandle);
auto name = wil::GetFinalPathNameByHandleW(fileHandle.get());
auto name2 = wil::GetFinalPathNameByHandleW<wil::unique_cotaskmem_string, c_stackBufferLimitTest>(fileHandle.get());
REQUIRE(wcscmp(name.get(), name2.get()) == 0);
std::wstring path;
auto hr = wil::GetFinalPathNameByHandleW(nullptr, path);
REQUIRE(hr == E_HANDLE); // should be a usage error so be a fail fast.
// A more legitimate case is a non file handler like a drive volume.
wil::unique_hfile volumeHandle(CreateFileW(LR"(\\?\C:)", FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, nullptr));
THROW_LAST_ERROR_IF(!volumeHandle);
const auto hr2 = wil::GetFinalPathNameByHandleW(volumeHandle.get(), path);
REQUIRE(hr2 == HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION));
}
TEST_CASE("FileSystemTests::VerifyTrySearchPathW", "[filesystem]")
{
auto pathToTest = wil::TrySearchPathW(nullptr, c_expandedPath, nullptr);
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
pathToTest = wil::TrySearchPathW(nullptr, c_missingPath, nullptr);
REQUIRE(wil::string_get_not_null(pathToTest)[0] == L'\0');
}
#endif
// Simple test to expand an environmental string
TEST_CASE("FileSystemTests::VerifyExpandEnvironmentStringsW", "[filesystem]")
{
wil::unique_cotaskmem_string pathToTest;
REQUIRE_SUCCEEDED(wil::ExpandEnvironmentStringsW(c_variablePath, pathToTest));
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
// This should effectively be a no-op
REQUIRE_SUCCEEDED(wil::ExpandEnvironmentStringsW(c_expandedPath, pathToTest));
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
// Environment variable does not exist, but the call should still succeed
REQUIRE_SUCCEEDED(wil::ExpandEnvironmentStringsW(c_missingVariable, pathToTest));
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_missingVariable, -1, TRUE) == CSTR_EQUAL);
}
TEST_CASE("FileSystemTests::VerifySearchPathW", "[filesystem]")
{
wil::unique_cotaskmem_string pathToTest;
REQUIRE_SUCCEEDED(wil::SearchPathW(nullptr, c_expandedPath, nullptr, pathToTest));
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
REQUIRE(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == wil::SearchPathW(nullptr, c_missingPath, nullptr, pathToTest));
}
TEST_CASE("FileSystemTests::VerifyExpandEnvAndSearchPath", "[filesystem]")
{
wil::unique_cotaskmem_string pathToTest;
REQUIRE_SUCCEEDED(wil::ExpandEnvAndSearchPath(c_variablePath, pathToTest));
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
// This test will exercise the case where AdaptFixedSizeToAllocatedResult will need to
// reallocate the initial buffer to fit the final string.
// This test is sufficient to test both wil::ExpandEnvironmentStringsW and wil::SeachPathW
REQUIRE_SUCCEEDED((wil::ExpandEnvAndSearchPath<wil::unique_cotaskmem_string, c_stackBufferLimitTest>(c_variablePath, pathToTest)));
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
pathToTest.reset();
REQUIRE(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == wil::ExpandEnvAndSearchPath(c_missingVariable, pathToTest));
REQUIRE(pathToTest.get() == nullptr);
}
TEST_CASE("FileSystemTests::VerifyGetSystemDirectoryW", "[filesystem]")
{
wil::unique_cotaskmem_string pathToTest;
REQUIRE_SUCCEEDED(wil::GetSystemDirectoryW(pathToTest));
// allocate based on the string that wil::GetSystemDirectoryW returned
size_t length = wcslen(pathToTest.get()) + 1;
auto trueSystemDir = wil::make_cotaskmem_string_nothrow(nullptr, length);
REQUIRE(GetSystemDirectoryW(trueSystemDir.get(), static_cast<UINT>(length)) > 0);
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, trueSystemDir.get(), -1, TRUE) == CSTR_EQUAL);
// Force AdaptFixed* to realloc. Test stack boundary with small initial buffer limit, c_stackBufferLimitTest
REQUIRE_SUCCEEDED((wil::GetSystemDirectoryW<wil::unique_cotaskmem_string, c_stackBufferLimitTest>(pathToTest)));
// allocate based on the string that wil::GetSystemDirectoryW returned
length = wcslen(pathToTest.get()) + 1;
trueSystemDir = wil::make_cotaskmem_string_nothrow(nullptr, length);
REQUIRE(GetSystemDirectoryW(trueSystemDir.get(), static_cast<UINT>(length)) > 0);
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, trueSystemDir.get(), -1, TRUE) == CSTR_EQUAL);
}
struct has_operator_pcwstr
{
PCWSTR value;
operator PCWSTR() const
{
return value;
}
};
struct has_operator_pwstr
{
PWSTR value;
operator PWSTR() const
{
return value;
}
};
#ifdef WIL_ENABLE_EXCEPTIONS
struct has_operator_wstr_ref
{
std::wstring value;
operator const std::wstring&() const
{
return value;
}
};
// E.g. mimics something like std::filesystem::path
struct has_operator_wstr
{
std::wstring value;
operator std::wstring() const
{
return value;
}
};
#endif
TEST_CASE("FileSystemTests::VerifyStrConcat", "[filesystem]")
{
SECTION("Concat with multiple strings")
{
PCWSTR test1 = L"Test1";
#ifdef WIL_ENABLE_EXCEPTIONS
std::wstring test2 = L"Test2";
#else
PCWSTR test2 = L"Test2";
#endif
WCHAR test3[6] = L"Test3";
wil::unique_cotaskmem_string test4 = wil::make_unique_string_nothrow<wil::unique_cotaskmem_string>(L"test4");
wil::unique_hstring test5 = wil::make_unique_string_nothrow<wil::unique_hstring>(L"test5");
has_operator_pcwstr test6{ L"Test6" };
WCHAR test7Buffer[] = L"Test7";
has_operator_pwstr test7{ test7Buffer };
#ifdef WIL_ENABLE_EXCEPTIONS
has_operator_wstr_ref test8{ L"Test8" };
has_operator_wstr test9{ L"Test9" };
#else
PCWSTR test8 = L"Test8";
PCWSTR test9 = L"Test9";
#endif
PCWSTR expectedStr = L"Test1Test2Test3Test4Test5Test6Test7Test8Test9";
#ifdef WIL_ENABLE_EXCEPTIONS
auto combinedString = wil::str_concat<wil::unique_cotaskmem_string>(test1, test2, test3, test4, test5, test6, test7, test8, test9);
REQUIRE(CompareStringOrdinal(combinedString.get(), -1, expectedStr, -1, TRUE) == CSTR_EQUAL);
#endif
wil::unique_cotaskmem_string combinedStringNT;
REQUIRE_SUCCEEDED(wil::str_concat_nothrow(combinedStringNT, test1, test2, test3, test4, test5, test6, test7, test8, test9));
REQUIRE(CompareStringOrdinal(combinedStringNT.get(), -1, expectedStr, -1, TRUE) == CSTR_EQUAL);
auto combinedStringFF = wil::str_concat_failfast<wil::unique_cotaskmem_string>(test1, test2, test3, test4, test5, test6, test7, test8, test9);
REQUIRE(CompareStringOrdinal(combinedStringFF.get(), -1, expectedStr, -1, TRUE) == CSTR_EQUAL);
}
SECTION("Concat with single string")
{
PCWSTR test1 = L"Test1";
#ifdef WIL_ENABLE_EXCEPTIONS
auto combinedString = wil::str_concat<wil::unique_cotaskmem_string>(test1);
REQUIRE(CompareStringOrdinal(combinedString.get(), -1, test1, -1, TRUE) == CSTR_EQUAL);
#endif
wil::unique_cotaskmem_string combinedStringNT;
REQUIRE_SUCCEEDED(wil::str_concat_nothrow(combinedStringNT, test1));
REQUIRE(CompareStringOrdinal(combinedStringNT.get(), -1, test1, -1, TRUE) == CSTR_EQUAL);
auto combinedStringFF = wil::str_concat_failfast<wil::unique_cotaskmem_string>(test1);
REQUIRE(CompareStringOrdinal(combinedStringFF.get(), -1, test1, -1, TRUE) == CSTR_EQUAL);
}
SECTION("Concat with existing string")
{
std::wstring test2 = L"Test2";
WCHAR test3[6] = L"Test3";
PCWSTR expectedStr = L"Test1Test2Test3";
wil::unique_cotaskmem_string combinedStringNT = wil::make_unique_string_nothrow<wil::unique_cotaskmem_string>(L"Test1");
REQUIRE_SUCCEEDED(wil::str_concat_nothrow(combinedStringNT, test2.c_str(), test3));
REQUIRE(CompareStringOrdinal(combinedStringNT.get(), -1, expectedStr, -1, TRUE) == CSTR_EQUAL);
}
}
TEST_CASE("FileSystemTests::VerifyStrPrintf", "[filesystem]")
{
#ifdef WIL_ENABLE_EXCEPTIONS
auto formattedString = wil::str_printf<wil::unique_cotaskmem_string>(L"Test %s %c %d %4.2f", L"String", L'c', 42, 6.28);
REQUIRE(CompareStringOrdinal(formattedString.get(), -1, L"Test String c 42 6.28", -1, TRUE) == CSTR_EQUAL);
#endif
wil::unique_cotaskmem_string formattedStringNT;
REQUIRE_SUCCEEDED(wil::str_printf_nothrow(formattedStringNT, L"Test %s %c %d %4.2f", L"String", L'c', 42, 6.28));
REQUIRE(CompareStringOrdinal(formattedStringNT.get(), -1, L"Test String c 42 6.28", -1, TRUE) == CSTR_EQUAL);
auto formattedStringFF = wil::str_printf_failfast<wil::unique_cotaskmem_string>(L"Test %s %c %d %4.2f", L"String", L'c', 42, 6.28);
REQUIRE(CompareStringOrdinal(formattedStringFF.get(), -1, L"Test String c 42 6.28", -1, TRUE) == CSTR_EQUAL);
}
TEST_CASE("FileSystemTests::VerifyGetModuleFileNameW", "[filesystem]")
{
wil::unique_cotaskmem_string path;
REQUIRE_SUCCEEDED(wil::GetModuleFileNameW(nullptr, path));
auto len = wcslen(path.get());
REQUIRE(((len >= 4) && (wcscmp(path.get() + len - 4, L".exe") == 0)));
// Call again, but force multiple retries through a small initial buffer
wil::unique_cotaskmem_string path2;
REQUIRE_SUCCEEDED((wil::GetModuleFileNameW<wil::unique_cotaskmem_string, 4>(nullptr, path2)));
REQUIRE(wcscmp(path.get(), path2.get()) == 0);
REQUIRE_FAILED(wil::GetModuleFileNameW((HMODULE)INVALID_HANDLE_VALUE, path));
}
TEST_CASE("FileSystemTests::VerifyGetModuleFileNameExW", "[filesystem]")
{
wil::unique_cotaskmem_string path;
REQUIRE_SUCCEEDED(wil::GetModuleFileNameExW(nullptr, nullptr, path));
auto len = wcslen(path.get());
REQUIRE(((len >= 4) && (wcscmp(path.get() + len - 4, L".exe") == 0)));
// Call again, but force multiple retries through a small initial buffer
wil::unique_cotaskmem_string path2;
REQUIRE_SUCCEEDED((wil::GetModuleFileNameExW<wil::unique_cotaskmem_string, 4>(nullptr, nullptr, path2)));
REQUIRE(wcscmp(path.get(), path2.get()) == 0);
REQUIRE_FAILED(wil::GetModuleFileNameExW(nullptr, (HMODULE)INVALID_HANDLE_VALUE, path));
}
#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)