From 7ee008e150bc5bcf76082d726f719ee0fdfda982 Mon Sep 17 00:00:00 2001 From: SulfurNitride Date: Wed, 11 Feb 2026 02:37:39 -0600 Subject: Fluorine Manager: full Linux port of Mod Organizer 2 Complete native Linux port with FUSE-based virtual filesystem, Proton/umu-run integration, and Flatpak packaging. Key features: - FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak) - Proton/GE-Proton/umu-run launcher with env var forwarding - Flatpak support (sandbox-aware VFS, NXM handler, umu-run) - Wine prefix management UI - Case-insensitive path resolution for Linux filesystems - QSettings-safe INI handling (avoids Bethesda INI corruption) - Portable instance support with auto-generated launcher scripts Co-Authored-By: Claude Opus 4.6 --- libs/usvfs/src/shared/winapi.cpp | 490 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 490 insertions(+) create mode 100644 libs/usvfs/src/shared/winapi.cpp (limited to 'libs/usvfs/src/shared/winapi.cpp') diff --git a/libs/usvfs/src/shared/winapi.cpp b/libs/usvfs/src/shared/winapi.cpp new file mode 100644 index 0000000..e28a13c --- /dev/null +++ b/libs/usvfs/src/shared/winapi.cpp @@ -0,0 +1,490 @@ +/* +Userspace Virtual Filesystem + +Copyright (C) 2015 Sebastian Herbord. All rights reserved. + +This file is part of usvfs. + +usvfs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +usvfs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with usvfs. If not, see . +*/ +#include "winapi.h" +#include "exceptionex.h" +#include "logging.h" +#include "ntdll_declarations.h" +#include "stringcast.h" +#include "stringutils.h" +#include "unicodestring.h" + +namespace winapi::ansi +{ + +std::string getModuleFileName(HMODULE module, HANDLE process) +{ + std::wstring result = wide::getModuleFileName(module, process); + return usvfs::shared::string_cast(result); +} + +std::string getCurrentDirectory() +{ + std::string result; + DWORD required = GetCurrentDirectoryA(0, nullptr); + if (required == 0UL) { + throw usvfs::shared::windows_error("failed to determine current working directory"); + } + result.resize(required); + GetCurrentDirectoryA(required, &result[0]); + result.resize(required - 1); + return result; +} + +std::pair getFullPathName(LPCSTR fileName) +{ + static const int INIT_SIZE = 128; + std::string result; + result.resize(INIT_SIZE); + LPSTR filePart = nullptr; + DWORD requiredSize = GetFullPathNameA(fileName, INIT_SIZE, &result[0], &filePart); + if (requiredSize >= INIT_SIZE) { + result.resize(requiredSize); + GetFullPathNameA(fileName, requiredSize, &result[0], &filePart); + } + if (requiredSize != 0UL) { + return std::make_pair(result, std::string(filePart != nullptr ? filePart : "")); + } else { + return make_pair(result, std::string()); + } +} + +} // namespace winapi::ansi + +namespace winapi::wide +{ + +std::wstring getModuleFileName(HMODULE module, HANDLE process) +{ + std::wstring result; + result.resize(64); + DWORD rc = 0UL; + + while ((rc = (process == INVALID_HANDLE_VALUE) + ? ::GetModuleFileNameW(module, &result[0], + static_cast(result.size())) + : ::GetModuleFileNameExW(process, module, &result[0], + static_cast(result.size()))) == + result.size()) { + result.resize(result.size() * 2); + } + + if (rc == 0UL) { + if (::GetLastError() == ERROR_PARTIAL_COPY) { +#if BOOST_ARCH_X86_64 + return L"unknown (32-bit process)"; +#else + return L"unknown (64-bit process)"; +#endif + } else { + throw usvfs::shared::windows_error("failed to retrieve module file name"); + } + } + + result.resize(rc); + + return result; +} + +std::pair getFullPathName(LPCWSTR fileName) +{ + wchar_t buf1[MAX_PATH]; + std::vector buf2; + wchar_t* result = buf1; + LPWSTR filePart = nullptr; + DWORD requiredSize = GetFullPathNameW(fileName, MAX_PATH, result, &filePart); + if (requiredSize >= MAX_PATH) { + buf2.resize(requiredSize); + result = &buf2[0]; + requiredSize = GetFullPathNameW(fileName, requiredSize, result, &filePart); + } + return make_pair(std::wstring(result, requiredSize), + std::wstring((requiredSize && filePart) ? filePart : L"")); +} + +std::wstring getCurrentDirectory() +{ + // really great api this (::GetCurrentDirectoryW) + // - if it succeeds, returns size in characters WITHOUT zero termination + // - if it fails due to buffer too small, returns size in characters WITH zero + // termination + // - if it fails for other reasons, returns 0 + std::wstring result; + DWORD required = GetCurrentDirectoryW(0, nullptr); + if (required == 0UL) { + throw usvfs::shared::windows_error("failed to determine current working directory"); + } + result.resize(required); + GetCurrentDirectoryW(required, &result[0]); + result.resize(required - 1); + return result; +} + +std::wstring getKnownFolderPath(REFKNOWNFOLDERID folderID) +{ + PWSTR writablePath; + + ::SHGetKnownFolderPath(folderID, 0, nullptr, &writablePath); + + ON_BLOCK_EXIT([writablePath]() { + ::CoTaskMemFree(writablePath); + }); + + return std::wstring(writablePath); +} + +} // namespace winapi::wide + +namespace winapi::ex +{ + +std::pair getSectionRange(HANDLE moduleHandle) +{ + std::pair result; + bool found = false; + uintptr_t exeModule = reinterpret_cast(moduleHandle); + if (exeModule == 0) { + throw std::runtime_error("failed to determine address range of executable"); + } + + std::pair totalRange{UINT_MAX, 0}; + + PIMAGE_DOS_HEADER dosHeader = reinterpret_cast(exeModule); + PIMAGE_NT_HEADERS ntHeader = + reinterpret_cast(exeModule + dosHeader->e_lfanew); + PIMAGE_SECTION_HEADER sectionHeader = + reinterpret_cast(ntHeader + 1); + for (int i = 0; i < ntHeader->FileHeader.NumberOfSections && !found; ++i) { + if (memcmp(sectionHeader->Name, ".text", 5) == 0) { + result.first = exeModule + sectionHeader->VirtualAddress; + result.second = result.first + sectionHeader->Misc.VirtualSize; + found = true; + } else { + uintptr_t start = exeModule + sectionHeader->VirtualAddress; + totalRange.first = std::min(totalRange.first, start); + totalRange.second = std::max(totalRange.second, + start + sectionHeader->Misc.VirtualSize); + } + ++sectionHeader; + } + + if (!found) { + return totalRange; + } + + return result; +} + +OSVersion getOSVersion() +{ + RTL_OSVERSIONINFOEXW versionInfo; + ZeroMemory(&versionInfo, sizeof(RTL_OSVERSIONINFOEXW)); + versionInfo.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW); + RtlGetVersion((PRTL_OSVERSIONINFOW)&versionInfo); + + OSVersion result; + result.major = versionInfo.dwMajorVersion; + result.minor = versionInfo.dwMinorVersion; + result.build = versionInfo.dwBuildNumber; + result.platformid = versionInfo.dwPlatformId; + result.servicpack = + versionInfo.wServicePackMajor << 16 | versionInfo.wServicePackMinor; + return result; +} + +} // namespace winapi::ex + +namespace winapi::ex::ansi +{ + +std::string errorString(DWORD errorCode) +{ + std::ostringstream finalMessage; + + LPSTR buffer = nullptr; + + DWORD currentErrorCode = GetLastError(); + + errorCode = + errorCode != std::numeric_limits::max() ? errorCode : currentErrorCode; + + // TODO: the message is not english? + if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, errorCode, + 0 //, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) + , + (LPSTR)&buffer, 0, nullptr) == 0) { + finalMessage << "(unknown error [" << errorCode << "])"; + } else { + if (buffer != nullptr) { + size_t end = strlen(buffer) - 1; + while ((buffer[end] == '\n') || (buffer[end] == '\r')) { + buffer[end--] = '\0'; + } + finalMessage << "(" << buffer << " [" << errorCode << "])"; + LocalFree(buffer); // allocated by FormatMessage + } + } + + SetLastError(currentErrorCode); // restore error code because FormatMessage might + // have modified it + return finalMessage.str(); +} + +std::string toString(const FILETIME& time) +{ + SYSTEMTIME temp; + FileTimeToSystemTime(&time, &temp); + std::ostringstream stream; + stream << temp.wYear << "-" << temp.wMonth << "-" << temp.wDay << temp.wHour << ":" + << temp.wMinute << ":" << temp.wSecond; + return stream.str(); +} + +LPCSTR GetBaseName(LPCSTR string) +{ + LPCSTR result = string + strlen(string) - 1; + while (result > string) { + if ((*result == '\\') || (*result == '/')) { + ++result; + break; + } else { + --result; + } + } + return result; +} + +} // namespace winapi::ex::ansi + +namespace winapi::ex::wide +{ + +bool fileExists(LPCWSTR fileName, bool* isDirectory) +{ + DWORD attrib = GetFileAttributesW(fileName); + + if (attrib == INVALID_FILE_ATTRIBUTES) { + return false; + } else { + if (isDirectory != nullptr) { + *isDirectory = (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0; + } + return true; + } +} + +std::wstring errorString(DWORD errorCode) +{ + std::wostringstream finalMessage; + + LPWSTR buffer = nullptr; + + DWORD currentErrorCode = GetLastError(); + + errorCode = + errorCode != std::numeric_limits::max() ? errorCode : currentErrorCode; + + // TODO: the message is not english? + if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, errorCode, + 0 //, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) + , + (LPWSTR)&buffer, 0, nullptr) == 0) { + finalMessage << L"(unknown error [" << errorCode << "])"; + } else { + if (buffer != nullptr) { + size_t end = wcslen(buffer) - 1; + while ((buffer[end] == L'\n') || (buffer[end] == L'\r')) { + buffer[end--] = L'\0'; + } + finalMessage << L"(" << buffer << L" [" << errorCode << L"])"; + LocalFree(buffer); // allocated by FormatMessage + } + } + + SetLastError(currentErrorCode); // restore error code because FormatMessage might + // have modified it + return finalMessage.str(); +} + +std::wstring toString(const FILETIME& time) +{ + SYSTEMTIME temp; + FileTimeToSystemTime(&time, &temp); + std::wostringstream stream; + stream << temp.wYear << "-" << temp.wMonth << "-" << temp.wDay << temp.wHour << ":" + << temp.wMinute << ":" << temp.wSecond; + return stream.str(); +} + +LPCWSTR GetBaseName(LPCWSTR string) +{ + LPCWSTR result; + if ((string == nullptr) || (string[0] == L'\0')) { + result = string; + } else { + result = string + wcslen(string) - 1; + } + + while (result > string) { + if ((*result == L'\\') || (*result == L'/')) { + ++result; + break; + } else { + --result; + } + } + return result; +} + +LPWSTR GetBaseName(LPWSTR path) +{ + LPCWSTR result = GetBaseName(static_cast(path)); + return const_cast(result); +} + +std::wstring getSectionName(PVOID addressIn, HANDLE process) +{ + if (process == nullptr) { + process = GetCurrentProcess(); + } + HMODULE modules[1024]; + intptr_t address = reinterpret_cast(addressIn); + DWORD required; + if (::EnumProcessModules(process, modules, sizeof(modules), &required)) { + for (DWORD i = 0; i < (std::min(1024UL, required) / sizeof(HMODULE)); ++i) { + std::pair range = getSectionRange(modules[i]); + if ((address > range.first) && (address < range.second)) { + try { + return winapi::wide::getModuleFileName(modules[i], process); + } catch (const std::exception&) { + return std::wstring(L"unknown"); + } + } + } + } + return std::wstring(L"unknown"); +} + +std::vector quickFindFiles(LPCWSTR directoryName, LPCWSTR pattern) +{ + std::vector result; + + static const unsigned int BUFFER_SIZE = 1024; + + HANDLE hdl = CreateFileW(directoryName, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + + ON_BLOCK_EXIT([hdl]() { + CloseHandle(hdl); + }); + + uint8_t buffer[BUFFER_SIZE]; + + NTSTATUS res = STATUS_SUCCESS; // status success + while (res == STATUS_SUCCESS) { + IO_STATUS_BLOCK status; + + res = NtQueryDirectoryFile( + hdl, nullptr, nullptr, nullptr, &status, buffer, BUFFER_SIZE, + FileFullDirectoryInformation, FALSE, + static_cast(usvfs::UnicodeString(pattern)), FALSE); + if (res == STATUS_SUCCESS) { + FILE_FULL_DIR_INFORMATION* info = + reinterpret_cast(buffer); + void* endPos = buffer + status.Information; + while (info < endPos) { + FileResult file; + file.fileName = + std::wstring(info->FileName, info->FileNameLength / sizeof(wchar_t)); + file.attributes = info->FileAttributes; + + result.push_back(file); + if (info->NextEntryOffset == 0) { + break; + } else { + info = reinterpret_cast( + reinterpret_cast(info) + info->NextEntryOffset); + } + } + } + } + + return result; +} + +bool createPath(boost::filesystem::path path, LPSECURITY_ATTRIBUTES securityAttributes) +{ + // sanity and guaranteed recursion end: + if (!path.has_relative_path()) + throw usvfs::shared::windows_error( + "createPath() refusing to create non-existing top level path: " + + path.string()); + + DWORD attr = GetFileAttributesW(path.c_str()); + DWORD err = GetLastError(); + if (attr != INVALID_FILE_ATTRIBUTES) { + if (attr & FILE_ATTRIBUTE_DIRECTORY) + return false; // if directory already exists all is good + else + throw usvfs::shared::windows_error("createPath() called on a file: " + + path.string()); + } + if (err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND) + throw usvfs::shared::windows_error( + "createPath() GetFileAttributesW failed on: " + path.string(), err); + + if (err != ERROR_FILE_NOT_FOUND) // ERROR_FILE_NOT_FOUND means parent directory + // already exists + createPath(path.parent_path(), + securityAttributes); // otherwise create parent directory (recursively) + + BOOL res = CreateDirectoryW(path.c_str(), securityAttributes); + if (!res) { + err = GetLastError(); + throw usvfs::shared::windows_error( + "createPath() CreateDirectoryW failed on: " + path.string(), err); + } + return true; +} + +std::wstring getWindowsBuildLab(bool ex) +{ + HKEY hKey = nullptr; + auto res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, + LR"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", 0, + KEY_READ, &hKey); + if (res != ERROR_SUCCESS || !hKey) + return L"Opening HKLM Windows NT\\CurrentVersion failed?!"; + WCHAR buf[200]; + DWORD size = static_cast(sizeof(buf)); + res = RegQueryValueExW(hKey, ex ? L"BuildLabEx" : L"BuildLab", NULL, NULL, + reinterpret_cast(buf), &size); + if (res != ERROR_SUCCESS || size > sizeof(buf)) + return ex ? L"BuildLabEx reg value not found?!" : L"BuildLab reg value not found?!"; + size /= sizeof(buf[0]); + if (size && !buf[size - 1]) + --size; + return std::wstring(buf, size); +} + +} // namespace winapi::ex::wide -- cgit v1.3.1