From 7962f4006565272b7b0c8c1ae1536a2a3d1a0e22 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Fri, 25 Sep 2026 14:40:21 -0700 Subject: [PATCH] FileUtils: Implement RecursiveRemoveDirectory and WalkDirectory with NT syscalls A bit messy but it basically duplicates the Linux side. We don't have a way to include NT internal defines in to FEXCore currently so the two required symbols are just forward declared. --- FEXCore/Source/CMakeLists.txt | 4 + FEXCore/Source/Utils/FileUtils.cpp | 170 ++++++++++++++++++++++++++++- 2 files changed, 169 insertions(+), 5 deletions(-) diff --git a/FEXCore/Source/CMakeLists.txt b/FEXCore/Source/CMakeLists.txt index bfed4ad59..dd8e4aa24 100644 --- a/FEXCore/Source/CMakeLists.txt +++ b/FEXCore/Source/CMakeLists.txt @@ -262,6 +262,10 @@ endfunction() # Build FEXCore_Base static library add_library(FEXCore_Base STATIC ${FEXCORE_BASE_SRCS}) target_link_libraries(FEXCore_Base PUBLIC ${LIBS}) +if (MINGW) + target_link_libraries(FEXCore_Base PUBLIC ntdll) +endif() + AddDefaultOptionsToTarget(FEXCore_Base) if (ENABLE_FEXCORE_PROFILER AND FEXCORE_PROFILER_BACKEND STREQUAL "TRACY") diff --git a/FEXCore/Source/Utils/FileUtils.cpp b/FEXCore/Source/Utils/FileUtils.cpp index 1de611a02..d3e62cacd 100644 --- a/FEXCore/Source/Utils/FileUtils.cpp +++ b/FEXCore/Source/Utils/FileUtils.cpp @@ -9,6 +9,18 @@ #include #include #include +#else +#include +#include +#include +#include + +// FEX today doesn't have Windows include in FEXCore. +extern "C" NTSTATUS WINAPI NtQueryDirectoryFile(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID, PIO_STATUS_BLOCK, PVOID, ULONG, + FILE_INFORMATION_CLASS, BOOLEAN, PUNICODE_STRING, BOOLEAN); + +extern "C" NTSTATUS RtlUnicodeToUTF8N(OUT PCHAR UTF8StringDestination, IN ULONG UTF8StringMaxByteCount, + OUT PULONG UTF8StringActualByteCount, IN PCWCH UnicodeStringSource, IN ULONG UnicodeStringByteCount); #endif namespace FEXCore::FileUtils { @@ -190,17 +202,165 @@ end: } #else -FEX_DEFAULT_VISIBILITY bool RecursiveRemoveDirectory(const fextl::string& Directory) { - std::error_code ec; - return std::filesystem::remove_all(Directory, ec) != ~0ULL; +static inline std::optional PathToNTPath(std::string_view Path) { + UNICODE_STRING PathW; + if (!RtlCreateUnicodeStringFromAsciiz(&PathW, fextl::string(Path).c_str())) { + return std::nullopt; + } + UNICODE_STRING NTPath; + bool Success = RtlDosPathNameToNtPathName_U(PathW.Buffer, &NTPath, nullptr, nullptr); + RtlFreeUnicodeString(&PathW); + if (!Success) { + return std::nullopt; + } + + return NTPath; +} + +static inline void FreeNTPath(UNICODE_STRING Path) { + RtlFreeUnicodeString(&Path); } FEX_DEFAULT_VISIBILITY void WalkDirectory(std::string_view Directory, fextl::move_only_function Callback, const void* user_data) { - for (const auto& iter : std::filesystem::directory_iterator(Directory)) { - Callback(iter.path().string(), iter.is_directory(), user_data); + auto NTPath = PathToNTPath(Directory); + if (!NTPath) { + return; } + + NTSTATUS Status {}; + + HANDLE dir_fd {}; + OBJECT_ATTRIBUTES attr {}; + IO_STATUS_BLOCK io {}; + + InitializeObjectAttributes(&attr, &*NTPath, OBJ_CASE_INSENSITIVE, nullptr, nullptr); + Status = NtOpenFile(&dir_fd, FILE_LIST_DIRECTORY | SYNCHRONIZE, &attr, &io, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT); + + FreeNTPath(*NTPath); + + if (!NT_SUCCESS(Status)) { + return; + } + + BOOLEAN FirstQuery = TRUE; + size_t dirent_size = 4096 * 4; + uint8_t* dirent_buffer = reinterpret_cast(FEXCore::Allocator::malloc(dirent_size)); + + if (!dirent_buffer) { + // Ran out of memory? + goto end; + } + + while (true) { + Status = NtQueryDirectoryFile(dir_fd, + nullptr, // Event + nullptr, // ApcRoutine + nullptr, // ApcContext + &io, dirent_buffer, dirent_size, FileDirectoryInformation, + FALSE, // ReturnSingleEntry + nullptr, // FileName + FirstQuery); + FirstQuery = FALSE; + + if (Status == STATUS_NO_MORE_FILES) { + // No more files + break; + } + + if (!NT_SUCCESS(Status)) { + if (Status == STATUS_BUFFER_TOO_SMALL || Status == STATUS_INFO_LENGTH_MISMATCH) { + // Buffer too small? Scale and try again. + dirent_size *= 2; + FEXCore::Allocator::free(dirent_buffer); + uint8_t* dirent_buffer = reinterpret_cast(FEXCore::Allocator::malloc(dirent_size)); + + if (!dirent_buffer) { + // Ran out of memory? + goto end; + } + + continue; + } + + // Any other failure, exit loop. + break; + } + + // Iterate the entries returned. + for (size_t dirent_offset = 0;;) { + auto Info = reinterpret_cast(dirent_buffer + dirent_offset); + + std::wstring_view EntryName(Info->FileName, Info->FileNameLength / sizeof(wchar_t)); + + if (EntryName != L"." && EntryName != L"..") { + ULONG utf8_bytes_needed = 0; + ULONG actual_utf8_bytes = 0; + RtlUnicodeToUTF8N(nullptr, 0, &utf8_bytes_needed, Info->FileName, Info->FileNameLength); + char* dynamic_buf = reinterpret_cast(FEXCore::Allocator::malloc(utf8_bytes_needed + 1)); + if (dynamic_buf) { + RtlUnicodeToUTF8N(dynamic_buf, utf8_bytes_needed + 1, &actual_utf8_bytes, Info->FileName, Info->FileNameLength); + std::string_view name_view(dynamic_buf, actual_utf8_bytes); + bool is_dir = (Info->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + Callback(name_view, is_dir, user_data); + FEXCore::Allocator::free(dynamic_buf); + } + } + + if (Info->NextEntryOffset == 0) { + break; + } + + dirent_offset += Info->NextEntryOffset; + } + } + +end: + if (dirent_buffer) { + FEXCore::Allocator::free(dirent_buffer); + } + + NtClose(dir_fd); } + +FEX_DEFAULT_VISIBILITY bool RecursiveRemoveDirectory(const fextl::string& Directory) { + using CallbackType = void (*)(std::string_view name, bool is_dir, const void* user_data); + struct UserData { + CallbackType RemoveFile {}; + std::string_view base_path; + }; + + CallbackType RemoveFile = [](std::string_view name, bool is_dir, const void* user_data) { + auto Data = reinterpret_cast(user_data); + + auto full_path = std::format("{}/{}", Data->base_path, name); + if (is_dir) { + UserData NewData { + .RemoveFile = Data->RemoveFile, + .base_path = full_path, + }; + + WalkDirectory(full_path, Data->RemoveFile, &NewData); + } + + if (is_dir) { + RemoveDirectoryA(full_path.c_str()); + } else { + DeleteFileA(full_path.c_str()); + } + }; + + UserData Data { + .RemoveFile = RemoveFile, + .base_path = Directory, + }; + + WalkDirectory(Directory, RemoveFile, &Data); + + return RemoveDirectoryA(Directory.c_str()) != 0; +} + #endif } // namespace FEXCore::FileUtils