Mercurial > minori
comparison dep/animia/src/fd/win32.cc @ 138:28842a8d0c6b
dep/animia: huge refactor (again...)
but this time, it actually compiles! and it WORKS! (on win32... not sure about
other platforms...)
configuring players is still not supported: at some point I'll prune something
up...
| author | Paper <mrpapersonic@gmail.com> |
|---|---|
| date | Sun, 12 Nov 2023 04:53:19 -0500 |
| parents | |
| children | 478f3b366199 |
comparison
equal
deleted
inserted
replaced
| 137:69db40272acd | 138:28842a8d0c6b |
|---|---|
| 1 /** | |
| 2 * win32.cpp | |
| 3 * - provides support for Windows clients | |
| 4 * | |
| 5 **/ | |
| 6 #include "animia/fd/win32.h" | |
| 7 #include <fileapi.h> | |
| 8 #include <handleapi.h> | |
| 9 #include <iostream> | |
| 10 #include <libloaderapi.h> | |
| 11 #include <ntdef.h> | |
| 12 #include <psapi.h> | |
| 13 #include <shlobj.h> | |
| 14 #include <stdexcept> | |
| 15 #include <string> | |
| 16 #include <stringapiset.h> | |
| 17 #include <tlhelp32.h> | |
| 18 #include <unordered_map> | |
| 19 #include <vector> | |
| 20 #include <windows.h> | |
| 21 #include <winternl.h> | |
| 22 | |
| 23 /* This file is noticably more complex than Unix and Linux, and that's because | |
| 24 there is no "simple" way to get the paths of a file. In fact, this thing requires | |
| 25 you to use *internal functions* that can't even be linked to, hence why we have to | |
| 26 use GetProcAddress and such. What a mess. */ | |
| 27 | |
| 28 #define SystemExtendedHandleInformation ((SYSTEM_INFORMATION_CLASS)0x40) | |
| 29 constexpr NTSTATUS STATUS_INFO_LENGTH_MISMATCH = 0xC0000004UL; | |
| 30 constexpr NTSTATUS STATUS_SUCCESS = 0x00000000UL; | |
| 31 | |
| 32 static unsigned short file_type_index = 0; | |
| 33 | |
| 34 struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX { | |
| 35 PVOID Object; | |
| 36 ULONG_PTR UniqueProcessId; | |
| 37 HANDLE HandleValue; | |
| 38 ACCESS_MASK GrantedAccess; | |
| 39 USHORT CreatorBackTraceIndex; | |
| 40 USHORT ObjectTypeIndex; | |
| 41 ULONG HandleAttributes; | |
| 42 ULONG Reserved; | |
| 43 }; | |
| 44 | |
| 45 struct SYSTEM_HANDLE_INFORMATION_EX { | |
| 46 ULONG_PTR NumberOfHandles; | |
| 47 ULONG_PTR Reserved; | |
| 48 SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1]; | |
| 49 }; | |
| 50 | |
| 51 namespace animia::internal::win32 { | |
| 52 | |
| 53 static HANDLE DuplicateHandle(HANDLE process_handle, HANDLE handle) { | |
| 54 HANDLE dup_handle = nullptr; | |
| 55 const bool result = | |
| 56 ::DuplicateHandle(process_handle, handle, ::GetCurrentProcess(), &dup_handle, 0, false, DUPLICATE_SAME_ACCESS); | |
| 57 return result ? dup_handle : nullptr; | |
| 58 } | |
| 59 | |
| 60 static PVOID GetNTDLLAddress(LPCSTR proc_name) { | |
| 61 return reinterpret_cast<PVOID>(::GetProcAddress(::GetModuleHandleA("ntdll.dll"), proc_name)); | |
| 62 } | |
| 63 | |
| 64 static NTSTATUS QuerySystemInformation(SYSTEM_INFORMATION_CLASS cls, PVOID sysinfo, ULONG len, PULONG retlen) { | |
| 65 static const auto func = | |
| 66 reinterpret_cast<decltype(::NtQuerySystemInformation)*>(GetNTDLLAddress("NtQuerySystemInformation")); | |
| 67 return func(cls, sysinfo, len, retlen); | |
| 68 } | |
| 69 | |
| 70 static NTSTATUS QueryObject(HANDLE handle, OBJECT_INFORMATION_CLASS cls, PVOID objinf, ULONG objinflen, PULONG retlen) { | |
| 71 static const auto func = reinterpret_cast<decltype(::NtQueryObject)*>(GetNTDLLAddress("NtQueryObject")); | |
| 72 return func(handle, cls, objinf, objinflen, retlen); | |
| 73 } | |
| 74 | |
| 75 static std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> GetSystemHandleInformation() { | |
| 76 std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> res; | |
| 77 /* we should really put a cap on this */ | |
| 78 ULONG cb = 1 << 19; | |
| 79 NTSTATUS status = STATUS_SUCCESS; | |
| 80 SYSTEM_HANDLE_INFORMATION_EX* info; | |
| 81 | |
| 82 do { | |
| 83 status = STATUS_NO_MEMORY; | |
| 84 | |
| 85 if (!(info = (SYSTEM_HANDLE_INFORMATION_EX*)malloc(cb *= 2))) | |
| 86 continue; | |
| 87 | |
| 88 res.reserve(cb); | |
| 89 | |
| 90 if (0 <= (status = QuerySystemInformation(SystemExtendedHandleInformation, info, cb, &cb))) { | |
| 91 if (ULONG_PTR handles = info->NumberOfHandles) { | |
| 92 SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX* entry = info->Handles; | |
| 93 do { | |
| 94 if (entry) | |
| 95 res.push_back(*entry); | |
| 96 } while (entry++, --handles); | |
| 97 } | |
| 98 } | |
| 99 free(info); | |
| 100 } while (status == STATUS_INFO_LENGTH_MISMATCH); | |
| 101 | |
| 102 return res; | |
| 103 } | |
| 104 | |
| 105 static OBJECT_TYPE_INFORMATION QueryObjectTypeInfo(HANDLE handle) { | |
| 106 OBJECT_TYPE_INFORMATION info; | |
| 107 QueryObject(handle, ObjectTypeInformation, &info, sizeof(info), NULL); | |
| 108 return info; | |
| 109 } | |
| 110 | |
| 111 /* we're using UTF-8. originally, I had used just the ANSI versions of functions, but that | |
| 112 sucks massive dick. this way we get unicode in the way every single other OS does it */ | |
| 113 static std::string UnicodeStringToUtf8(std::wstring string) { | |
| 114 unsigned long size = ::WideCharToMultiByte(CP_UTF8, 0, string.c_str(), string.length(), NULL, 0, NULL, NULL); | |
| 115 std::string ret = std::string(size, '\0'); | |
| 116 ::WideCharToMultiByte(CP_UTF8, 0, string.c_str(), string.length(), &ret.front(), ret.length(), NULL, NULL); | |
| 117 return ret; | |
| 118 } | |
| 119 | |
| 120 static std::string UnicodeStringToUtf8(UNICODE_STRING string) { | |
| 121 unsigned long size = ::WideCharToMultiByte(CP_UTF8, 0, string.Buffer, string.Length, NULL, 0, NULL, NULL); | |
| 122 std::string ret = std::string(size, '\0'); | |
| 123 ::WideCharToMultiByte(CP_UTF8, 0, string.Buffer, string.Length, &ret.front(), ret.length(), NULL, NULL); | |
| 124 return ret; | |
| 125 } | |
| 126 | |
| 127 static std::wstring Utf8StringToUnicode(std::string string) { | |
| 128 unsigned long size = ::MultiByteToWideChar(CP_UTF8, 0, string.c_str(), string.length(), NULL, 0); | |
| 129 std::wstring ret = std::wstring(size, L'\0'); | |
| 130 ::MultiByteToWideChar(CP_UTF8, 0, string.c_str(), string.length(), &ret.front(), ret.length()); | |
| 131 return ret; | |
| 132 } | |
| 133 | |
| 134 static std::string GetHandleType(HANDLE handle) { | |
| 135 OBJECT_TYPE_INFORMATION info = QueryObjectTypeInfo(handle); | |
| 136 return UnicodeStringToUtf8(info.TypeName); | |
| 137 } | |
| 138 | |
| 139 static std::string GetFinalPathNameByHandle(HANDLE handle) { | |
| 140 std::wstring buffer; | |
| 141 | |
| 142 int result = ::GetFinalPathNameByHandleW(handle, NULL, 0, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); | |
| 143 buffer.resize(result); | |
| 144 ::GetFinalPathNameByHandleW(handle, &buffer.front(), buffer.size(), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); | |
| 145 buffer.resize(buffer.find('\0')); | |
| 146 | |
| 147 return UnicodeStringToUtf8(buffer); | |
| 148 } | |
| 149 | |
| 150 static bool IsFileHandle(HANDLE handle, unsigned short object_type_index) { | |
| 151 if (file_type_index) | |
| 152 return object_type_index == file_type_index; | |
| 153 else if (!handle) | |
| 154 return true; | |
| 155 else if (GetHandleType(handle) == "File") { | |
| 156 file_type_index = object_type_index; | |
| 157 return true; | |
| 158 } | |
| 159 return false; | |
| 160 } | |
| 161 | |
| 162 static bool IsFileMaskOk(ACCESS_MASK access_mask) { | |
| 163 if (!(access_mask & FILE_READ_DATA)) | |
| 164 return false; | |
| 165 | |
| 166 if ((access_mask & FILE_APPEND_DATA) || (access_mask & FILE_WRITE_EA) || (access_mask & FILE_WRITE_ATTRIBUTES)) | |
| 167 return false; | |
| 168 | |
| 169 return true; | |
| 170 } | |
| 171 | |
| 172 static std::string GetSystemDirectory() { | |
| 173 PWSTR path_wch; | |
| 174 SHGetKnownFolderPath(FOLDERID_Windows, 0, NULL, &path_wch); | |
| 175 std::wstring path_wstr(path_wch); | |
| 176 CoTaskMemFree(path_wch); | |
| 177 return UnicodeStringToUtf8(path_wstr); | |
| 178 } | |
| 179 | |
| 180 static bool IsSystemFile(const std::string& path) { | |
| 181 std::wstring path_w = Utf8StringToUnicode(path); | |
| 182 CharUpperBuffW(&path_w.front(), path_w.length()); | |
| 183 std::wstring windir_w = Utf8StringToUnicode(GetSystemDirectory()); | |
| 184 CharUpperBuffW(&windir_w.front(), windir_w.length()); | |
| 185 return path_w.find(windir_w) == 4; | |
| 186 } | |
| 187 | |
| 188 static bool IsFilePathOk(const std::string& path) { | |
| 189 if (path.empty()) | |
| 190 return false; | |
| 191 | |
| 192 if (IsSystemFile(path)) | |
| 193 return false; | |
| 194 | |
| 195 const auto file_attributes = GetFileAttributesA(path.c_str()); | |
| 196 if ((file_attributes == INVALID_FILE_ATTRIBUTES) || (file_attributes & FILE_ATTRIBUTE_DIRECTORY)) | |
| 197 return false; | |
| 198 | |
| 199 return true; | |
| 200 } | |
| 201 | |
| 202 bool GetAllPids(std::set<pid_t>& pids) { | |
| 203 HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); | |
| 204 if (hProcessSnap == INVALID_HANDLE_VALUE) | |
| 205 return false; | |
| 206 | |
| 207 PROCESSENTRY32 pe32; | |
| 208 pe32.dwSize = sizeof(PROCESSENTRY32); | |
| 209 | |
| 210 if (!::Process32First(hProcessSnap, &pe32)) | |
| 211 return false; | |
| 212 | |
| 213 pids.insert(pe32.th32ProcessID); | |
| 214 | |
| 215 while (::Process32Next(hProcessSnap, &pe32)) | |
| 216 pids.insert(pe32.th32ProcessID); | |
| 217 | |
| 218 ::CloseHandle(hProcessSnap); | |
| 219 | |
| 220 return true; | |
| 221 } | |
| 222 | |
| 223 bool GetProcessName(pid_t pid, std::string& result) { | |
| 224 unsigned long ret_size = 0; // size given by GetModuleBaseNameW | |
| 225 Handle handle(::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); | |
| 226 if (!handle.get()) | |
| 227 return false; | |
| 228 | |
| 229 /* agh... */ | |
| 230 std::wstring ret(256, L'\0'); | |
| 231 for (; ret.length() < 32768; ret.resize(ret.length() * 2)) { | |
| 232 if (!(ret_size = ::GetModuleBaseNameW(handle.get(), 0, &ret.front(), ret.length()))) { | |
| 233 return false; | |
| 234 } else if (ret.length() > ret_size) { | |
| 235 ret.resize(ret.find(L'\0')); | |
| 236 result = UnicodeStringToUtf8(ret); | |
| 237 break; | |
| 238 } | |
| 239 } | |
| 240 | |
| 241 return true; | |
| 242 } | |
| 243 | |
| 244 /* this could be changed to being a callback, but... I'm too lazy right now :) */ | |
| 245 bool EnumerateOpenFiles(const std::set<pid_t>& pids, std::vector<std::tuple<pid_t, std::string>>& files) { | |
| 246 std::unordered_map<pid_t, Handle> proc_handles; | |
| 247 | |
| 248 for (const pid_t& pid : pids) { | |
| 249 const HANDLE handle = ::OpenProcess(PROCESS_DUP_HANDLE, false, pid); | |
| 250 if (handle) | |
| 251 proc_handles[pid] = Handle(handle); | |
| 252 } | |
| 253 | |
| 254 if (proc_handles.empty()) | |
| 255 return false; | |
| 256 | |
| 257 std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> info = GetSystemHandleInformation(); | |
| 258 | |
| 259 for (const auto& h : info) { | |
| 260 const pid_t pid = h.UniqueProcessId; | |
| 261 if (!pids.count(pid)) | |
| 262 continue; | |
| 263 | |
| 264 if (!IsFileHandle(nullptr, h.ObjectTypeIndex)) | |
| 265 continue; | |
| 266 | |
| 267 if (!IsFileMaskOk(h.GrantedAccess)) | |
| 268 continue; | |
| 269 | |
| 270 Handle handle(DuplicateHandle(proc_handles[pid].get(), h.HandleValue)); | |
| 271 if (!handle.get()) | |
| 272 continue; | |
| 273 | |
| 274 if (GetFileType(handle.get()) != FILE_TYPE_DISK) | |
| 275 continue; | |
| 276 | |
| 277 const std::string path = GetFinalPathNameByHandle(handle.get()); | |
| 278 if (!IsFilePathOk(path)) | |
| 279 continue; | |
| 280 | |
| 281 /* create an empty vector if it doesn't exist, probably unnecessary */ | |
| 282 files.push_back({pid, path}); | |
| 283 } | |
| 284 | |
| 285 return true; | |
| 286 } | |
| 287 | |
| 288 } // namespace animia::internal::win32 |
