/* * Copyright (c) 2023 Lain Bailey * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include "base.h" #include "platform.h" #include "darray.h" #include "dstr.h" #include "obsconfig.h" #include "util_uint64.h" #include "windows/win-registry.h" #include "windows/win-version.h" #include "../../deps/w32-pthreads/pthread.h" #define MAX_SZ_LEN 256 static bool have_clockfreq = false; static LARGE_INTEGER clock_freq; static uint32_t winver = 0; static char win_release_id[MAX_SZ_LEN] = "unavailable"; static inline uint64_t get_clockfreq(void) { if (!have_clockfreq) { QueryPerformanceFrequency(&clock_freq); have_clockfreq = true; } return clock_freq.QuadPart; } static inline uint32_t get_winver(void) { if (!winver) { struct win_version_info ver; get_win_ver(&ver); winver = (ver.major << 8) | ver.minor; } return winver; } void *os_dlopen(const char *path) { struct dstr dll_name; wchar_t *wpath; wchar_t *wpath_slash; HMODULE h_library = NULL; if (!path) return NULL; dstr_init_copy(&dll_name, path); dstr_replace(&dll_name, "\\", "/"); if (!dstr_find(&dll_name, ".dll")) dstr_cat(&dll_name, ".dll"); os_utf8_to_wcs_ptr(dll_name.array, 0, &wpath); dstr_free(&dll_name); /* to make module dependency issues easier to deal with, allow * dynamically loaded libraries on windows to search for dependent * libraries that are within the library's own directory */ wpath_slash = wcsrchr(wpath, L'/'); if (wpath_slash) { *wpath_slash = 0; SetDllDirectoryW(wpath); *wpath_slash = L'/'; } h_library = LoadLibraryW(wpath); bfree(wpath); if (wpath_slash) SetDllDirectoryW(NULL); if (!h_library) { DWORD error = GetLastError(); /* don't print error for libraries that aren't meant to be * dynamically linked */ if (error == ERROR_PROC_NOT_FOUND) return NULL; char *message = NULL; FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, error, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), (LPSTR)&message, 0, NULL); blog(LOG_INFO, "LoadLibrary failed for '%s': %s (%lu)", path, message, error); if (message) LocalFree(message); } return h_library; } void *os_dlsym(void *module, const char *func) { void *handle; handle = (void *)GetProcAddress(module, func); return handle; } void os_dlclose(void *module) { FreeLibrary(module); } static bool has_qt5_import(VOID *base, PIMAGE_NT_HEADERS nt_headers) { __try { PIMAGE_DATA_DIRECTORY data_dir; data_dir = &nt_headers->OptionalHeader .DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; if (data_dir->Size == 0) return false; PIMAGE_SECTION_HEADER section, last_section; section = IMAGE_FIRST_SECTION(nt_headers); last_section = section; /* find the section that contains the export directory */ int i; for (i = 0; i < nt_headers->FileHeader.NumberOfSections; i++) { if (section->VirtualAddress <= data_dir->VirtualAddress) { last_section = section; section++; continue; } else { break; } } /* double check in case we exited early */ if (last_section->VirtualAddress > data_dir->VirtualAddress || section->VirtualAddress <= data_dir->VirtualAddress) return false; section = last_section; /* get a pointer to the import directory */ PIMAGE_IMPORT_DESCRIPTOR import; import = (PIMAGE_IMPORT_DESCRIPTOR)((byte *)base + data_dir->VirtualAddress - section->VirtualAddress + section->PointerToRawData); while (import->Name != 0) { char *name = (char *)((byte *)base + import->Name - section->VirtualAddress + section->PointerToRawData); /* qt5? bingo, reject this library */ if (astrcmpi_n(name, "qt5", 3) == 0) { return true; } import++; } } __except (EXCEPTION_EXECUTE_HANDLER) { /* we failed somehow, for compatibility assume no qt5 import */ return false; } return false; } static bool has_obs_export(VOID *base, PIMAGE_NT_HEADERS nt_headers) { __try { PIMAGE_DATA_DIRECTORY data_dir; data_dir = &nt_headers->OptionalHeader .DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; if (data_dir->Size == 0) return false; PIMAGE_SECTION_HEADER section, last_section; section = IMAGE_FIRST_SECTION(nt_headers); last_section = section; /* find the section that contains the export directory */ int i; for (i = 0; i < nt_headers->FileHeader.NumberOfSections; i++) { if (section->VirtualAddress <= data_dir->VirtualAddress) { last_section = section; section++; continue; } else { break; } } /* double check in case we exited early */ if (last_section->VirtualAddress > data_dir->VirtualAddress || section->VirtualAddress <= data_dir->VirtualAddress) return false; section = last_section; /* get a pointer to the export directory */ PIMAGE_EXPORT_DIRECTORY export; export = (PIMAGE_EXPORT_DIRECTORY)((byte *)base + data_dir->VirtualAddress - section->VirtualAddress + section->PointerToRawData); if (export->NumberOfNames == 0) return false; /* get a pointer to the export directory names */ DWORD *names_ptr; names_ptr = (DWORD *)((byte *)base + export->AddressOfNames - section->VirtualAddress + section->PointerToRawData); /* iterate through each name and see if its an obs plugin */ CHAR *name; size_t j; for (j = 0; j < export->NumberOfNames; j++) { name = (CHAR *)base + names_ptr[j] - section->VirtualAddress + section->PointerToRawData; if (!strcmp(name, "obs_module_load")) { return true; } } } __except (EXCEPTION_EXECUTE_HANDLER) { /* we failed somehow, for compatibility let's assume it * was a valid plugin and let the loader deal with it */ return true; } return false; } void get_plugin_info(const char *path, bool *is_obs_plugin, bool *can_load) { struct dstr dll_name; wchar_t *wpath; HANDLE hFile = INVALID_HANDLE_VALUE; HANDLE hFileMapping = NULL; VOID *base = NULL; PIMAGE_DOS_HEADER dos_header; PIMAGE_NT_HEADERS nt_headers; *is_obs_plugin = false; *can_load = false; if (!path) return; dstr_init_copy(&dll_name, path); dstr_replace(&dll_name, "\\", "/"); if (!dstr_find(&dll_name, ".dll")) dstr_cat(&dll_name, ".dll"); os_utf8_to_wcs_ptr(dll_name.array, 0, &wpath); dstr_free(&dll_name); hFile = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); bfree(wpath); if (hFile == INVALID_HANDLE_VALUE) goto cleanup; hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (hFileMapping == NULL) goto cleanup; base = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0); if (!base) goto cleanup; /* all mapped file i/o must be prepared to handle exceptions */ __try { dos_header = (PIMAGE_DOS_HEADER)base; if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) goto cleanup; nt_headers = (PIMAGE_NT_HEADERS)((byte *)dos_header + dos_header->e_lfanew); if (nt_headers->Signature != IMAGE_NT_SIGNATURE) goto cleanup; *is_obs_plugin = has_obs_export(base, nt_headers); if (*is_obs_plugin) { *can_load = !has_qt5_import(base, nt_headers); } } __except (EXCEPTION_EXECUTE_HANDLER) { /* we failed somehow, for compatibility let's assume it * was a valid plugin and let the loader deal with it */ *is_obs_plugin = true; *can_load = true; goto cleanup; } cleanup: if (base) UnmapViewOfFile(base); if (hFileMapping != NULL) CloseHandle(hFileMapping); if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); } bool os_is_obs_plugin(const char *path) { bool is_obs_plugin; bool can_load; get_plugin_info(path, &is_obs_plugin, &can_load); return is_obs_plugin && can_load; } union time_data { FILETIME ft; unsigned long long val; }; struct os_cpu_usage_info { union time_data last_time, last_sys_time, last_user_time; DWORD core_count; }; os_cpu_usage_info_t *os_cpu_usage_info_start(void) { struct os_cpu_usage_info *info = bzalloc(sizeof(*info)); SYSTEM_INFO si; FILETIME dummy; GetSystemInfo(&si); GetSystemTimeAsFileTime(&info->last_time.ft); GetProcessTimes(GetCurrentProcess(), &dummy, &dummy, &info->last_sys_time.ft, &info->last_user_time.ft); info->core_count = si.dwNumberOfProcessors; return info; } double os_cpu_usage_info_query(os_cpu_usage_info_t *info) { union time_data cur_time, cur_sys_time, cur_user_time; FILETIME dummy; double percent; if (!info) return 0.0; GetSystemTimeAsFileTime(&cur_time.ft); GetProcessTimes(GetCurrentProcess(), &dummy, &dummy, &cur_sys_time.ft, &cur_user_time.ft); percent = (double)(cur_sys_time.val - info->last_sys_time.val + (cur_user_time.val - info->last_user_time.val)); percent /= (double)(cur_time.val - info->last_time.val); percent /= (double)info->core_count; info->last_time.val = cur_time.val; info->last_sys_time.val = cur_sys_time.val; info->last_user_time.val = cur_user_time.val; return percent * 100.0; } void os_cpu_usage_info_destroy(os_cpu_usage_info_t *info) { if (info) bfree(info); } bool os_sleepto_ns(uint64_t time_target) { const uint64_t freq = get_clockfreq(); const LONGLONG count_target = util_mul_div64(time_target, freq, 1000000000); LARGE_INTEGER count; QueryPerformanceCounter(&count); const bool stall = count.QuadPart < count_target; if (stall) { const DWORD milliseconds = (DWORD)(((count_target - count.QuadPart) * 1000.0) / freq); if (milliseconds > 1) Sleep(milliseconds - 1); for (;;) { QueryPerformanceCounter(&count); if (count.QuadPart >= count_target) break; YieldProcessor(); } } return stall; } bool os_sleepto_ns_fast(uint64_t time_target) { uint64_t current = os_gettime_ns(); if (time_target < current) return false; do { uint64_t remain_ms = (time_target - current) / 1000000; if (!remain_ms) remain_ms = 1; Sleep((DWORD)remain_ms); current = os_gettime_ns(); } while (time_target > current); return true; } void os_sleep_ms(uint32_t duration) { /* windows 8+ appears to have decreased sleep precision */ if (get_winver() >= 0x0602 && duration > 0) duration--; Sleep(duration); } uint64_t os_gettime_ns(void) { LARGE_INTEGER current_time; QueryPerformanceCounter(¤t_time); return util_mul_div64(current_time.QuadPart, 1000000000, get_clockfreq()); } /* returns [folder]\[name] on windows */ static int os_get_path_internal(char *dst, size_t size, const char *name, int folder) { wchar_t path_utf16[MAX_PATH]; SHGetFolderPathW(NULL, folder, NULL, SHGFP_TYPE_CURRENT, path_utf16); if (os_wcs_to_utf8(path_utf16, 0, dst, size) != 0) { if (!name || !*name) { return (int)strlen(dst); } if (strcat_s(dst, size, "\\") == 0) { if (strcat_s(dst, size, name) == 0) { return (int)strlen(dst); } } } return -1; } static char *os_get_path_ptr_internal(const char *name, int folder) { char *ptr; wchar_t path_utf16[MAX_PATH]; struct dstr path; SHGetFolderPathW(NULL, folder, NULL, SHGFP_TYPE_CURRENT, path_utf16); os_wcs_to_utf8_ptr(path_utf16, 0, &ptr); dstr_init_move_array(&path, ptr); dstr_cat(&path, "\\"); dstr_cat(&path, name); return path.array; } int os_get_config_path(char *dst, size_t size, const char *name) { return os_get_path_internal(dst, size, name, CSIDL_APPDATA); } char *os_get_config_path_ptr(const char *name) { return os_get_path_ptr_internal(name, CSIDL_APPDATA); } int os_get_program_data_path(char *dst, size_t size, const char *name) { return os_get_path_internal(dst, size, name, CSIDL_COMMON_APPDATA); } char *os_get_program_data_path_ptr(const char *name) { return os_get_path_ptr_internal(name, CSIDL_COMMON_APPDATA); } char *os_get_executable_path_ptr(const char *name) { char *ptr; char *slash; wchar_t path_utf16[MAX_PATH]; struct dstr path; GetModuleFileNameW(NULL, path_utf16, MAX_PATH); os_wcs_to_utf8_ptr(path_utf16, 0, &ptr); dstr_init_move_array(&path, ptr); dstr_replace(&path, "\\", "/"); slash = strrchr(path.array, '/'); if (slash) { size_t len = slash - path.array + 1; dstr_resize(&path, len); } if (name && *name) { dstr_cat(&path, name); } return path.array; } bool os_file_exists(const char *path) { WIN32_FIND_DATAW wfd; HANDLE hFind; wchar_t *path_utf16; if (!os_utf8_to_wcs_ptr(path, 0, &path_utf16)) return false; hFind = FindFirstFileW(path_utf16, &wfd); if (hFind != INVALID_HANDLE_VALUE) FindClose(hFind); bfree(path_utf16); return hFind != INVALID_HANDLE_VALUE; } size_t os_get_abs_path(const char *path, char *abspath, size_t size) { wchar_t wpath[MAX_PATH]; wchar_t wabspath[MAX_PATH]; size_t out_len = 0; size_t len; if (!abspath) return 0; len = os_utf8_to_wcs(path, 0, wpath, MAX_PATH); if (!len) return 0; if (_wfullpath(wabspath, wpath, MAX_PATH) != NULL) out_len = os_wcs_to_utf8(wabspath, 0, abspath, size); return out_len; } char *os_get_abs_path_ptr(const char *path) { char *ptr = bmalloc(MAX_PATH); if (!os_get_abs_path(path, ptr, MAX_PATH)) { bfree(ptr); ptr = NULL; } return ptr; } struct os_dir { HANDLE handle; WIN32_FIND_DATA wfd; bool first; struct os_dirent out; }; os_dir_t *os_opendir(const char *path) { struct dstr path_str = {0}; struct os_dir *dir = NULL; WIN32_FIND_DATA wfd; HANDLE handle; wchar_t *w_path; dstr_copy(&path_str, path); dstr_cat(&path_str, "/*.*"); if (os_utf8_to_wcs_ptr(path_str.array, path_str.len, &w_path) > 0) { handle = FindFirstFileW(w_path, &wfd); if (handle != INVALID_HANDLE_VALUE) { dir = bzalloc(sizeof(struct os_dir)); dir->handle = handle; dir->first = true; dir->wfd = wfd; } bfree(w_path); } dstr_free(&path_str); return dir; } static inline bool is_dir(WIN32_FIND_DATA *wfd) { return !!(wfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); } struct os_dirent *os_readdir(os_dir_t *dir) { if (!dir) return NULL; if (dir->first) { dir->first = false; } else { if (!FindNextFileW(dir->handle, &dir->wfd)) return NULL; } os_wcs_to_utf8(dir->wfd.cFileName, 0, dir->out.d_name, sizeof(dir->out.d_name)); dir->out.directory = is_dir(&dir->wfd); return &dir->out; } void os_closedir(os_dir_t *dir) { if (dir) { FindClose(dir->handle); bfree(dir); } } int64_t os_get_free_space(const char *path) { ULARGE_INTEGER remainingSpace; char abs_path[512]; wchar_t w_abs_path[512]; if (os_get_abs_path(path, abs_path, 512) > 0) { if (os_utf8_to_wcs(abs_path, 0, w_abs_path, 512) > 0) { BOOL success = GetDiskFreeSpaceExW( w_abs_path, (PULARGE_INTEGER)&remainingSpace, NULL, NULL); if (success) return (int64_t)remainingSpace.QuadPart; } } return -1; } static void make_globent(struct os_globent *ent, WIN32_FIND_DATA *wfd, const char *pattern) { struct dstr name = {0}; struct dstr path = {0}; char *slash; dstr_from_wcs(&name, wfd->cFileName); dstr_copy(&path, pattern); if (path.array) { slash = strrchr(path.array, '/'); if (slash) dstr_resize(&path, slash + 1 - path.array); else dstr_free(&path); } dstr_cat_dstr(&path, &name); ent->path = path.array; ent->directory = is_dir(wfd); dstr_free(&name); } int os_glob(const char *pattern, int flags, os_glob_t **pglob) { DARRAY(struct os_globent) files; HANDLE handle; WIN32_FIND_DATA wfd; int ret = -1; wchar_t *w_path; da_init(files); if (os_utf8_to_wcs_ptr(pattern, 0, &w_path) > 0) { handle = FindFirstFileW(w_path, &wfd); if (handle != INVALID_HANDLE_VALUE) { do { struct os_globent ent = {0}; make_globent(&ent, &wfd, pattern); if (ent.path) da_push_back(files, &ent); } while (FindNextFile(handle, &wfd)); FindClose(handle); *pglob = bmalloc(sizeof(**pglob)); (*pglob)->gl_pathc = files.num; (*pglob)->gl_pathv = files.array; ret = 0; } bfree(w_path); } if (ret != 0) *pglob = NULL; UNUSED_PARAMETER(flags); return ret; } void os_globfree(os_glob_t *pglob) { if (pglob) { for (size_t i = 0; i < pglob->gl_pathc; i++) bfree(pglob->gl_pathv[i].path); bfree(pglob->gl_pathv); bfree(pglob); } } int os_unlink(const char *path) { wchar_t *w_path; bool success; os_utf8_to_wcs_ptr(path, 0, &w_path); if (!w_path) return -1; success = !!DeleteFileW(w_path); bfree(w_path); return success ? 0 : -1; } int os_rmdir(const char *path) { wchar_t *w_path; bool success; os_utf8_to_wcs_ptr(path, 0, &w_path); if (!w_path) return -1; success = !!RemoveDirectoryW(w_path); bfree(w_path); return success ? 0 : -1; } int os_mkdir(const char *path) { wchar_t *path_utf16; BOOL success; if (!os_utf8_to_wcs_ptr(path, 0, &path_utf16)) return MKDIR_ERROR; success = CreateDirectory(path_utf16, NULL); bfree(path_utf16); if (!success) return (GetLastError() == ERROR_ALREADY_EXISTS) ? MKDIR_EXISTS : MKDIR_ERROR; return MKDIR_SUCCESS; } int os_rename(const char *old_path, const char *new_path) { wchar_t *old_path_utf16 = NULL; wchar_t *new_path_utf16 = NULL; int code = -1; if (!os_utf8_to_wcs_ptr(old_path, 0, &old_path_utf16)) { return -1; } if (!os_utf8_to_wcs_ptr(new_path, 0, &new_path_utf16)) { goto error; } code = MoveFileExW(old_path_utf16, new_path_utf16, MOVEFILE_REPLACE_EXISTING) ? 0 : -1; error: bfree(old_path_utf16); bfree(new_path_utf16); return code; } int os_safe_replace(const char *target, const char *from, const char *backup) { wchar_t *wtarget = NULL; wchar_t *wfrom = NULL; wchar_t *wbackup = NULL; int code = -1; if (!target || !from) return -1; if (!os_utf8_to_wcs_ptr(target, 0, &wtarget)) return -1; if (!os_utf8_to_wcs_ptr(from, 0, &wfrom)) goto fail; if (backup && !os_utf8_to_wcs_ptr(backup, 0, &wbackup)) goto fail; if (ReplaceFileW(wtarget, wfrom, wbackup, 0, NULL, NULL)) { code = 0; } else if (GetLastError() == ERROR_FILE_NOT_FOUND) { code = MoveFileExW(wfrom, wtarget, MOVEFILE_REPLACE_EXISTING) ? 0 : -1; } fail: bfree(wtarget); bfree(wfrom); bfree(wbackup); return code; } BOOL WINAPI DllMain(HINSTANCE hinst_dll, DWORD reason, LPVOID reserved) { switch (reason) { case DLL_PROCESS_ATTACH: timeBeginPeriod(1); #ifdef PTW32_STATIC_LIB pthread_win32_process_attach_np(); #endif break; case DLL_PROCESS_DETACH: timeEndPeriod(1); #ifdef PTW32_STATIC_LIB pthread_win32_process_detach_np(); #endif break; case DLL_THREAD_ATTACH: #ifdef PTW32_STATIC_LIB pthread_win32_thread_attach_np(); #endif break; case DLL_THREAD_DETACH: #ifdef PTW32_STATIC_LIB pthread_win32_thread_detach_np(); #endif break; } UNUSED_PARAMETER(hinst_dll); UNUSED_PARAMETER(reserved); return true; } os_performance_token_t *os_request_high_performance(const char *reason) { UNUSED_PARAMETER(reason); return NULL; } void os_end_high_performance(os_performance_token_t *token) { UNUSED_PARAMETER(token); } int os_copyfile(const char *file_in, const char *file_out) { wchar_t *file_in_utf16 = NULL; wchar_t *file_out_utf16 = NULL; int code = -1; if (!os_utf8_to_wcs_ptr(file_in, 0, &file_in_utf16)) { return -1; } if (!os_utf8_to_wcs_ptr(file_out, 0, &file_out_utf16)) { goto error; } code = CopyFileW(file_in_utf16, file_out_utf16, true) ? 0 : -1; error: bfree(file_in_utf16); bfree(file_out_utf16); return code; } char *os_getcwd(char *path, size_t size) { wchar_t *path_w; DWORD len; len = GetCurrentDirectoryW(0, NULL); if (!len) return NULL; path_w = bmalloc(((size_t)len + 1) * sizeof(wchar_t)); GetCurrentDirectoryW(len + 1, path_w); os_wcs_to_utf8(path_w, (size_t)len, path, size); bfree(path_w); return path; } int os_chdir(const char *path) { wchar_t *path_w = NULL; size_t size; int ret; size = os_utf8_to_wcs_ptr(path, 0, &path_w); if (!path_w) return -1; ret = SetCurrentDirectoryW(path_w) ? 0 : -1; bfree(path_w); return ret; } typedef DWORD(WINAPI *get_file_version_info_size_w_t)(LPCWSTR module, LPDWORD unused); typedef BOOL(WINAPI *get_file_version_info_w_t)(LPCWSTR module, DWORD unused, DWORD len, LPVOID data); typedef BOOL(WINAPI *ver_query_value_w_t)(LPVOID data, LPCWSTR subblock, LPVOID *buf, PUINT sizeout); static get_file_version_info_size_w_t get_file_version_info_size = NULL; static get_file_version_info_w_t get_file_version_info = NULL; static ver_query_value_w_t ver_query_value = NULL; static bool ver_initialized = false; static bool ver_initialize_success = false; static bool initialize_version_functions(void) { HMODULE ver = GetModuleHandleW(L"version"); ver_initialized = true; if (!ver) { ver = LoadLibraryW(L"version"); if (!ver) { blog(LOG_ERROR, "Failed to load windows " "version library"); return false; } } get_file_version_info_size = (get_file_version_info_size_w_t)GetProcAddress( ver, "GetFileVersionInfoSizeW"); get_file_version_info = (get_file_version_info_w_t)GetProcAddress( ver, "GetFileVersionInfoW"); ver_query_value = (ver_query_value_w_t)GetProcAddress(ver, "VerQueryValueW"); if (!get_file_version_info_size || !get_file_version_info || !ver_query_value) { blog(LOG_ERROR, "Failed to load windows version " "functions"); return false; } ver_initialize_success = true; return true; } bool get_dll_ver(const wchar_t *lib, struct win_version_info *ver_info) { VS_FIXEDFILEINFO *info = NULL; UINT len = 0; BOOL success; LPVOID data; DWORD size; char utf8_lib[512]; if (!ver_initialized && !initialize_version_functions()) return false; if (!ver_initialize_success) return false; os_wcs_to_utf8(lib, 0, utf8_lib, sizeof(utf8_lib)); size = get_file_version_info_size(lib, NULL); if (!size) { blog(LOG_ERROR, "Failed to get %s version info size", utf8_lib); return false; } data = bmalloc(size); if (!get_file_version_info(lib, 0, size, data)) { blog(LOG_ERROR, "Failed to get %s version info", utf8_lib); bfree(data); return false; } success = ver_query_value(data, L"\\", (LPVOID *)&info, &len); if (!success || !info || !len) { blog(LOG_ERROR, "Failed to get %s version info value", utf8_lib); bfree(data); return false; } ver_info->major = (int)HIWORD(info->dwFileVersionMS); ver_info->minor = (int)LOWORD(info->dwFileVersionMS); ver_info->build = (int)HIWORD(info->dwFileVersionLS); ver_info->revis = (int)LOWORD(info->dwFileVersionLS); bfree(data); return true; } bool is_64_bit_windows(void) { #if defined(_WIN64) return true; #elif defined(_WIN32) BOOL b64 = false; return IsWow64Process(GetCurrentProcess(), &b64) && b64; #endif } bool is_arm64_windows(void) { #if defined(_M_ARM64) || defined(_M_ARM64EC) return true; #else USHORT processMachine; USHORT nativeMachine; bool result = IsWow64Process2(GetCurrentProcess(), &processMachine, &nativeMachine); return (result && (nativeMachine == IMAGE_FILE_MACHINE_ARM64)); #endif } bool os_get_emulation_status(void) { #if defined(_M_ARM64) || defined(_M_ARM64EC) return false; #else return is_arm64_windows(); #endif } void get_reg_dword(HKEY hkey, LPCWSTR sub_key, LPCWSTR value_name, struct reg_dword *info) { struct reg_dword reg = {0}; HKEY key; LSTATUS status; status = RegOpenKeyEx(hkey, sub_key, 0, KEY_READ, &key); if (status != ERROR_SUCCESS) { info->status = status; info->size = 0; info->return_value = 0; return; } reg.size = sizeof(reg.return_value); reg.status = RegQueryValueExW(key, value_name, NULL, NULL, (LPBYTE)®.return_value, ®.size); RegCloseKey(key); *info = reg; } #define WINVER_REG_KEY L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion" static inline void rtl_get_ver(struct win_version_info *ver) { HMODULE ntdll = GetModuleHandleW(L"ntdll"); if (!ntdll) return; NTSTATUS(WINAPI * get_ver) (RTL_OSVERSIONINFOEXW *) = (void *)GetProcAddress(ntdll, "RtlGetVersion"); if (!get_ver) { return; } RTL_OSVERSIONINFOEXW osver = {0}; osver.dwOSVersionInfoSize = sizeof(osver); NTSTATUS s = get_ver(&osver); if (s < 0) { return; } ver->major = osver.dwMajorVersion; ver->minor = osver.dwMinorVersion; ver->build = osver.dwBuildNumber; ver->revis = 0; } static inline bool get_reg_sz(HKEY key, const wchar_t *val, wchar_t *buf, DWORD size) { const LSTATUS status = RegGetValueW(key, NULL, val, RRF_RT_REG_SZ, NULL, buf, &size); return status == ERROR_SUCCESS; } static inline void get_reg_ver(struct win_version_info *ver) { HKEY key; DWORD size, dw_val; LSTATUS status; wchar_t str[MAX_SZ_LEN]; status = RegOpenKeyW(HKEY_LOCAL_MACHINE, WINVER_REG_KEY, &key); if (status != ERROR_SUCCESS) return; size = sizeof(dw_val); status = RegQueryValueExW(key, L"CurrentMajorVersionNumber", NULL, NULL, (LPBYTE)&dw_val, &size); if (status == ERROR_SUCCESS) ver->major = (int)dw_val; status = RegQueryValueExW(key, L"CurrentMinorVersionNumber", NULL, NULL, (LPBYTE)&dw_val, &size); if (status == ERROR_SUCCESS) ver->minor = (int)dw_val; status = RegQueryValueExW(key, L"UBR", NULL, NULL, (LPBYTE)&dw_val, &size); if (status == ERROR_SUCCESS) ver->revis = (int)dw_val; if (get_reg_sz(key, L"CurrentBuildNumber", str, sizeof(str))) { ver->build = wcstol(str, NULL, 10); } const wchar_t *release_key = ver->build > 19041 ? L"DisplayVersion" : L"ReleaseId"; if (get_reg_sz(key, release_key, str, sizeof(str))) { os_wcs_to_utf8(str, 0, win_release_id, MAX_SZ_LEN); } RegCloseKey(key); } static inline bool version_higher(struct win_version_info *cur, struct win_version_info *new) { if (new->major > cur->major) { return true; } if (new->major == cur->major) { if (new->minor > cur->minor) { return true; } if (new->minor == cur->minor) { if (new->build > cur->build) { return true; } if (new->build == cur->build) { return new->revis > cur->revis; } } } return false; } static inline void use_higher_ver(struct win_version_info *cur, struct win_version_info *new) { if (version_higher(cur, new)) *cur = *new; } void get_win_ver(struct win_version_info *info) { static struct win_version_info ver = {0}; static bool got_version = false; if (!info) return; if (!got_version) { struct win_version_info reg_ver = {0}; struct win_version_info rtl_ver = {0}; struct win_version_info nto_ver = {0}; get_reg_ver(®_ver); rtl_get_ver(&rtl_ver); get_dll_ver(L"ntoskrnl.exe", &nto_ver); ver = reg_ver; use_higher_ver(&ver, &rtl_ver); use_higher_ver(&ver, &nto_ver); got_version = true; } *info = ver; } const char *get_win_release_id(void) { return win_release_id; } uint32_t get_win_ver_int(void) { return get_winver(); } struct os_inhibit_info { bool active; }; os_inhibit_t *os_inhibit_sleep_create(const char *reason) { UNUSED_PARAMETER(reason); return bzalloc(sizeof(struct os_inhibit_info)); } bool os_inhibit_sleep_set_active(os_inhibit_t *info, bool active) { if (!info) return false; if (info->active == active) return false; if (active) { SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_AWAYMODE_REQUIRED | ES_DISPLAY_REQUIRED); } else { SetThreadExecutionState(ES_CONTINUOUS); } info->active = active; return true; } void os_inhibit_sleep_destroy(os_inhibit_t *info) { if (info) { os_inhibit_sleep_set_active(info, false); bfree(info); } } void os_breakpoint(void) { __debugbreak(); } DWORD num_logical_cores(ULONG_PTR mask) { DWORD left_shift = sizeof(ULONG_PTR) * 8 - 1; DWORD bit_set_count = 0; ULONG_PTR bit_test = (ULONG_PTR)1 << left_shift; for (DWORD i = 0; i <= left_shift; ++i) { bit_set_count += ((mask & bit_test) ? 1 : 0); bit_test /= 2; } return bit_set_count; } static int physical_cores = 0; static int logical_cores = 0; static bool core_count_initialized = false; static void os_get_cores_internal(void) { PSYSTEM_LOGICAL_PROCESSOR_INFORMATION info = NULL, temp = NULL; DWORD len = 0; if (core_count_initialized) return; core_count_initialized = true; GetLogicalProcessorInformation(info, &len); if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) return; info = malloc(len); if (info) { if (GetLogicalProcessorInformation(info, &len)) { DWORD num = len / sizeof(*info); temp = info; for (DWORD i = 0; i < num; i++) { if (temp->Relationship == RelationProcessorCore) { ULONG_PTR mask = temp->ProcessorMask; physical_cores++; logical_cores += num_logical_cores(mask); } temp++; } } free(info); } } int os_get_physical_cores(void) { if (!core_count_initialized) os_get_cores_internal(); return physical_cores; } int os_get_logical_cores(void) { if (!core_count_initialized) os_get_cores_internal(); return logical_cores; } static inline bool os_get_sys_memory_usage_internal(MEMORYSTATUSEX *msex) { if (!GlobalMemoryStatusEx(msex)) return false; return true; } uint64_t os_get_sys_free_size(void) { MEMORYSTATUSEX msex = {sizeof(MEMORYSTATUSEX)}; if (!os_get_sys_memory_usage_internal(&msex)) return 0; return msex.ullAvailPhys; } static uint64_t total_memory = 0; static bool total_memory_initialized = false; static void os_get_sys_total_size_internal() { total_memory_initialized = true; MEMORYSTATUSEX msex = {sizeof(MEMORYSTATUSEX)}; if (!os_get_sys_memory_usage_internal(&msex)) return; total_memory = msex.ullTotalPhys; } uint64_t os_get_sys_total_size(void) { if (!total_memory_initialized) os_get_sys_total_size_internal(); return total_memory; } static inline bool os_get_proc_memory_usage_internal(PROCESS_MEMORY_COUNTERS *pmc) { if (!GetProcessMemoryInfo(GetCurrentProcess(), pmc, sizeof(*pmc))) return false; return true; } bool os_get_proc_memory_usage(os_proc_memory_usage_t *usage) { PROCESS_MEMORY_COUNTERS pmc = {sizeof(PROCESS_MEMORY_COUNTERS)}; if (!os_get_proc_memory_usage_internal(&pmc)) return false; usage->resident_size = pmc.WorkingSetSize; usage->virtual_size = pmc.PagefileUsage; return true; } uint64_t os_get_proc_resident_size(void) { PROCESS_MEMORY_COUNTERS pmc = {sizeof(PROCESS_MEMORY_COUNTERS)}; if (!os_get_proc_memory_usage_internal(&pmc)) return 0; return pmc.WorkingSetSize; } uint64_t os_get_proc_virtual_size(void) { PROCESS_MEMORY_COUNTERS pmc = {sizeof(PROCESS_MEMORY_COUNTERS)}; if (!os_get_proc_memory_usage_internal(&pmc)) return 0; return pmc.PagefileUsage; } uint64_t os_get_free_disk_space(const char *dir) { wchar_t *wdir = NULL; os_utf8_to_wcs_ptr(dir, 0, &wdir); if (!wdir) return 0; ULARGE_INTEGER free; bool success = !!GetDiskFreeSpaceExW(wdir, &free, NULL, NULL); bfree(wdir); return success ? free.QuadPart : 0; } char *os_generate_uuid(void) { UUID uuid; RPC_STATUS res = UuidCreate(&uuid); if (res != RPC_S_OK && res != RPC_S_UUID_LOCAL_ONLY) bcrash("Failed to get UUID, RPC_STATUS: %l", res); struct dstr uuid_str = {0}; dstr_printf(&uuid_str, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", uuid.Data1, uuid.Data2, uuid.Data3, uuid.Data4[0], uuid.Data4[1], uuid.Data4[2], uuid.Data4[3], uuid.Data4[4], uuid.Data4[5], uuid.Data4[6], uuid.Data4[7]); return uuid_str.array; }