// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #if PLATFORM_WINDOWS #include "Engine/Platform/Platform.h" #include "Engine/Platform/Window.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Platform/CreateWindowSettings.h" #include "Engine/Platform/WindowsManager.h" #include "Engine/Platform/MemoryStats.h" #include "Engine/Platform/BatteryInfo.h" #include "Engine/Engine/Globals.h" #include "Engine/Core/Log.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Platform/MessageBox.h" #include "Engine/Engine/Engine.h" #include "../Win32/IncludeWindowsHeaders.h" #include #include #include #include #include #if CRASH_LOG_ENABLE #include #endif #include "resource.h" const Char* WindowsPlatform::ApplicationWindowClass = TEXT("FlaxWindow"); void* WindowsPlatform::Instance = nullptr; namespace { String UserLocale, ComputerName, UserName, WindowsName; HANDLE EngineMutex = nullptr; Rectangle VirtualScreenBounds = Rectangle(0.0f, 0.0f, 0.0f, 0.0f); int32 VersionMajor = 0; int32 VersionMinor = 0; int32 VersionBuild = 0; int32 SystemDpi = 96; #if CRASH_LOG_ENABLE CriticalSection SymLocker; #if TRACY_ENABLE bool SymInitialized = true; #else bool SymInitialized = false; #endif Array SymbolsPath; void OnSymbolsPathModified() { if (!SymInitialized) return; HANDLE process = GetCurrentProcess(); SymCleanup(process); String symbolSearchPath; for (auto& path : SymbolsPath) { symbolSearchPath += path; symbolSearchPath += ";"; } symbolSearchPath += Platform::GetWorkingDirectory(); SymInitializeW(process, *symbolSearchPath, TRUE); //SymSetSearchPathW(process, *symbolSearchPath); //SymRefreshModuleList(process); } #endif } HMONITOR GetPrimaryMonitorHandle() { const POINT ptZero = { 0, 0 }; return MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY); } int32 CalculateDpi(HMODULE shCoreDll) { int32 dpiX = 96, dpiY = 96; if (shCoreDll) { typedef HRESULT (STDAPICALLTYPE * GetDPIForMonitorProc)(HMONITOR hmonitor, UINT dpiType, UINT* dpiX, UINT* dpiY); const GetDPIForMonitorProc getDPIForMonitor = (GetDPIForMonitorProc)GetProcAddress(shCoreDll, "GetDpiForMonitor"); if (getDPIForMonitor) { HMONITOR monitor = GetPrimaryMonitorHandle(); UINT x = 0, y = 0; HRESULT hr = getDPIForMonitor(monitor, 0, &x, &y); if (SUCCEEDED(hr) && (x > 0) && (y > 0)) { dpiX = (int32)x; dpiY = (int32)y; } } FreeLibrary(shCoreDll); } return (dpiX + dpiY) / 2; } LONG GetStringRegKey(HKEY hKey, const Char* strValueName, String& strValue, const String& strDefaultValue) { strValue = strDefaultValue; WCHAR szBuffer[512]; DWORD dwBufferSize = sizeof(szBuffer); ULONG nError; nError = RegQueryValueExW(hKey, strValueName, nullptr, nullptr, reinterpret_cast(szBuffer), &dwBufferSize); if (ERROR_SUCCESS == nError) { strValue = szBuffer; } return nError; } LONG GetDWORDRegKey(HKEY hKey, const Char* strValueName, DWORD& nValue, DWORD nDefaultValue) { nValue = nDefaultValue; DWORD dwBufferSize(sizeof(DWORD)); DWORD nResult(0); LONG nError = RegQueryValueExW(hKey, strValueName, nullptr, nullptr, reinterpret_cast(&nResult), &dwBufferSize); if (ERROR_SUCCESS == nError) { nValue = nResult; } return nError; } void GetWindowsVersion(String& windowsName, int32& versionMajor, int32& versionMinor, int32& versionBuild) { // Get OS version HKEY hKey; LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &hKey); if (lRes == ERROR_SUCCESS) { GetStringRegKey(hKey, TEXT("ProductName"), windowsName, TEXT("Windows")); DWORD currentMajorVersionNumber; DWORD currentMinorVersionNumber; String currentBuildNumber; GetDWORDRegKey(hKey, TEXT("CurrentMajorVersionNumber"), currentMajorVersionNumber, 0); GetDWORDRegKey(hKey, TEXT("CurrentMinorVersionNumber"), currentMinorVersionNumber, 0); GetStringRegKey(hKey, TEXT("CurrentBuildNumber"), currentBuildNumber, TEXT("0")); VersionMajor = currentMajorVersionNumber; VersionMinor = currentMinorVersionNumber; StringUtils::Parse(currentBuildNumber.Get(), &VersionBuild); if (StringUtils::Compare(windowsName.Get(), TEXT("Windows 7"), 9) == 0) { VersionMajor = 6; VersionMinor = 2; } if (VersionMajor == 0 && VersionMinor == 0) { String windowsVersion; GetStringRegKey(hKey, TEXT("CurrentVersion"), windowsVersion, TEXT("")); if (windowsVersion.HasChars()) { const int32 dot = windowsVersion.Find('.'); if (dot != -1) { StringUtils::Parse(windowsVersion.Substring(0, dot).Get(), &VersionMajor); StringUtils::Parse(windowsVersion.Substring(dot + 1).Get(), &VersionMinor); } } } } else { if (IsWindowsServer()) { windowsName = TEXT("Windows Server"); versionMajor = 6; versionMinor = 3; } else if (IsWindows8Point1OrGreater()) { windowsName = TEXT("Windows 8.1"); versionMajor = 6; versionMinor = 3; } else if (IsWindows8OrGreater()) { windowsName = TEXT("Windows 8"); versionMajor = 6; versionMinor = 2; } else if (IsWindows7SP1OrGreater()) { windowsName = TEXT("Windows 7 SP1"); versionMajor = 6; versionMinor = 2; } else if (IsWindows7OrGreater()) { windowsName = TEXT("Windows 7"); versionMajor = 6; versionMinor = 1; } else if (IsWindowsVistaSP2OrGreater()) { windowsName = TEXT("Windows Vista SP2"); versionMajor = 6; versionMinor = 1; } else if (IsWindowsVistaSP1OrGreater()) { windowsName = TEXT("Windows Vista SP1"); versionMajor = 6; versionMinor = 1; } else if (IsWindowsVistaOrGreater()) { windowsName = TEXT("Windows Vista"); versionMajor = 6; versionMinor = 0; } } RegCloseKey(hKey); } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Find window to process that message if (hwnd != nullptr) { // Find window by handle const auto win = WindowsManager::GetByNativePtr(hwnd); if (win) { return static_cast(win)->WndProc(msg, wParam, lParam); } } // Default return DefWindowProc(hwnd, msg, wParam, lParam); } LONG CALLBACK SehExceptionHandler(EXCEPTION_POINTERS* ep) { // Skip if engine already crashed if (Globals::FatalErrorOccurred) return EXCEPTION_CONTINUE_SEARCH; // Get exception info String errorMsg(TEXT("Unhandled exception: ")); switch (ep->ExceptionRecord->ExceptionCode) { #define CASE(x) case x: errorMsg += TEXT(#x); break; CASE(EXCEPTION_ARRAY_BOUNDS_EXCEEDED) CASE(EXCEPTION_DATATYPE_MISALIGNMENT) CASE(EXCEPTION_FLT_DENORMAL_OPERAND) CASE(EXCEPTION_FLT_DIVIDE_BY_ZERO) CASE(EXCEPTION_FLT_INVALID_OPERATION) CASE(EXCEPTION_ILLEGAL_INSTRUCTION) CASE(EXCEPTION_INT_DIVIDE_BY_ZERO) CASE(EXCEPTION_PRIV_INSTRUCTION) CASE(EXCEPTION_STACK_OVERFLOW) #undef CASE case EXCEPTION_ACCESS_VIOLATION: errorMsg += TEXT("EXCEPTION_ACCESS_VIOLATION "); if (ep->ExceptionRecord->ExceptionInformation[0] == 0) { errorMsg += TEXT("reading address "); } else if (ep->ExceptionRecord->ExceptionInformation[0] == 1) { errorMsg += TEXT("writing address "); } errorMsg += String::Format(TEXT("{:#x}"), (uint32)ep->ExceptionRecord->ExceptionInformation[1]); break; default: errorMsg += String::Format(TEXT("{:#x}"), (uint32)ep->ExceptionRecord->ExceptionCode); } // Pause if debugging if (Platform::IsDebuggerPresent()) { PLATFORM_DEBUG_BREAK; } // Crash engine Platform::Fatal(errorMsg.Get(), ep); return EXCEPTION_CONTINUE_SEARCH; } #if CRASH_LOG_ENABLE bool GetModuleListPSAPI(HANDLE hProcess) { typedef BOOL (WINAPI* EnumProcessModulesFunc)(HANDLE hProcess, HMODULE* lphModule, DWORD cb, LPDWORD lpcbNeeded); typedef DWORD (WINAPI* GetModuleFileNameExFunc)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize); typedef DWORD (WINAPI* GetModuleBaseNameFunc)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize); typedef BOOL (WINAPI* GetModuleInformationFunc)(HANDLE hProcess, HMODULE hModule, LPMODULEINFO pmi, DWORD nSize); MODULEINFO mi; const SIZE_T bufferSize = 8096; HINSTANCE psapiDll = LoadLibraryW(TEXT("psapi.dll")); if (psapiDll == nullptr) return true; auto EnumProcessModulesFuncPtr = (EnumProcessModulesFunc)GetProcAddress(psapiDll, "EnumProcessModules"); auto GetModuleFileNameExFuncPtr = (GetModuleFileNameExFunc)GetProcAddress(psapiDll, "GetModuleFileNameExA"); auto GetModuleBaseNameFuncPtr = (GetModuleFileNameExFunc)GetProcAddress(psapiDll, "GetModuleBaseNameA"); auto GetModuleInformationFuncPtr = (GetModuleInformationFunc)GetProcAddress(psapiDll, "GetModuleInformation"); if ((EnumProcessModulesFuncPtr == nullptr) || (GetModuleFileNameExFuncPtr == nullptr) || (GetModuleBaseNameFuncPtr == nullptr) || (GetModuleInformationFuncPtr == nullptr)) { FreeLibrary(psapiDll); return true; } auto hMods = (HMODULE*)malloc(sizeof(HMODULE) * (bufferSize / sizeof(HMODULE))); auto tt = (char*)malloc(sizeof(char) * bufferSize); auto tt2 = (char*)malloc(sizeof(char) * bufferSize); if ((hMods == nullptr) || (tt == nullptr) || (tt2 == nullptr)) goto cleanup; DWORD cbNeeded; if (!EnumProcessModulesFuncPtr(hProcess, hMods, bufferSize, &cbNeeded)) goto cleanup; if (cbNeeded > bufferSize) goto cleanup; for (DWORD i = 0; i < cbNeeded / sizeof(hMods[0]); i++) { // Base address, Size GetModuleInformationFuncPtr(hProcess, hMods[i], &mi, sizeof(mi)); // Image file name tt[0] = 0; GetModuleFileNameExFuncPtr(hProcess, hMods[i], tt, bufferSize); // Module name tt2[0] = 0; GetModuleBaseNameFuncPtr(hProcess, hMods[i], tt2, bufferSize); SymLoadModule64(hProcess, 0, tt, tt2, (DWORD64)mi.lpBaseOfDll, mi.SizeOfImage); } cleanup: if (psapiDll != nullptr) FreeLibrary(psapiDll); if (tt2 != nullptr) free(tt2); if (tt != nullptr) free(tt); if (hMods != nullptr) free(hMods); return false; } #endif DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon) { // Construct input flags UINT flags = 0; switch (buttons) { case MessageBoxButtons::AbortRetryIgnore: flags |= MB_ABORTRETRYIGNORE; break; case MessageBoxButtons::OK: flags |= MB_OK; break; case MessageBoxButtons::OKCancel: flags |= MB_OKCANCEL; break; case MessageBoxButtons::RetryCancel: flags |= MB_RETRYCANCEL; break; case MessageBoxButtons::YesNo: flags |= MB_YESNO; break; case MessageBoxButtons::YesNoCancel: flags |= MB_YESNOCANCEL; break; default: break; } switch (icon) { case MessageBoxIcon::Asterisk: flags |= MB_ICONASTERISK; break; case MessageBoxIcon::Error: flags |= MB_ICONERROR; break; case MessageBoxIcon::Exclamation: flags |= MB_ICONEXCLAMATION; break; case MessageBoxIcon::Hand: flags |= MB_ICONHAND; break; case MessageBoxIcon::Information: flags |= MB_ICONINFORMATION; break; case MessageBoxIcon::Question: flags |= MB_ICONQUESTION; break; case MessageBoxIcon::Stop: flags |= MB_ICONSTOP; break; case MessageBoxIcon::Warning: flags |= MB_ICONWARNING; break; default: break; } // Show dialog int result = MessageBoxW(parent ? static_cast(parent->GetNativePtr()) : nullptr, text.GetText(), caption.GetText(), flags); // Translate result to dialog result DialogResult dialogResult; switch (result) { case IDABORT: dialogResult = DialogResult::Abort; break; case IDCANCEL: dialogResult = DialogResult::Cancel; break; case IDCONTINUE: dialogResult = DialogResult::OK; break; case IDIGNORE: dialogResult = DialogResult::Ignore; break; case IDNO: dialogResult = DialogResult::No; break; case IDOK: dialogResult = DialogResult::OK; break; case IDRETRY: dialogResult = DialogResult::Retry; break; case IDYES: dialogResult = DialogResult::Yes; break; default: dialogResult = DialogResult::None; break; } return dialogResult; } bool WindowsPlatform::CreateMutex(const Char* name) { EngineMutex = CreateMutexW(nullptr, true, name); if (EngineMutex && GetLastError() != ERROR_ALREADY_EXISTS) { return false; } return true; } void WindowsPlatform::ReleaseMutex() { if (EngineMutex) { ::ReleaseMutex(EngineMutex); EngineMutex = nullptr; } } void WindowsPlatform::PreInit(void* hInstance) { ASSERT(hInstance); Instance = hInstance; // Disable the process from being showing "ghosted" while not responding messages during slow tasks DisableProcessWindowsGhosting(); // Register window class WNDCLASS windowsClass; Platform::MemoryClear(&windowsClass, sizeof(WNDCLASS)); windowsClass.style = CS_DBLCLKS; windowsClass.lpfnWndProc = WndProc; windowsClass.hInstance = (HINSTANCE)Instance; windowsClass.hIcon = LoadIconW(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDR_MAINFRAME)); windowsClass.hCursor = LoadCursor(nullptr, IDC_ARROW); windowsClass.lpszClassName = ApplicationWindowClass; if (!RegisterClassW(&windowsClass)) { Error(TEXT("Window class registration failed!")); exit(-1); } // Init OLE if (OleInitialize(nullptr) != S_OK) { Error(TEXT("OLE initalization failed!")); exit(-1); } #if CRASH_LOG_ENABLE TCHAR buffer[MAX_PATH] = { 0 }; SymLocker.Lock(); if (::GetModuleFileNameW(::GetModuleHandleW(nullptr), buffer, MAX_PATH)) SymbolsPath.Add(StringUtils::GetDirectoryName(buffer)); if (::GetEnvironmentVariableW(TEXT("_NT_SYMBOL_PATH"), buffer, MAX_PATH)) SymbolsPath.Add(StringUtils::GetDirectoryName(buffer)); DWORD options = SymGetOptions(); options |= SYMOPT_LOAD_LINES | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_DEFERRED_LOADS | SYMOPT_EXACT_SYMBOLS; SymSetOptions(options); OnSymbolsPathModified(); SymLocker.Unlock(); #endif GetWindowsVersion(WindowsName, VersionMajor, VersionMinor, VersionBuild); // Validate platform if (VersionMajor < 6) { Error(TEXT("Not supported operating system version.")); exit(-1); } } bool WindowsPlatform::IsWindows10() { return VersionMajor >= 10; } bool WindowsPlatform::ReadRegValue(void* root, const String& key, const String& name, String* result) { HKEY hKey; if (RegOpenKeyExW((HKEY)root, *key, 0, KEY_READ, &hKey) != ERROR_SUCCESS) { // "Could not open registry key"; return true; } DWORD type; DWORD cbData; if (RegQueryValueExW(hKey, *name, nullptr, &type, nullptr, &cbData) != ERROR_SUCCESS) { RegCloseKey(hKey); // "Could not read registry value"; return true; } if (type != REG_SZ) { RegCloseKey(hKey); // "Incorrect registry value type"; return true; } Array data; data.Resize((int32)cbData / sizeof(Char)); if (RegQueryValueExW(hKey, *name, nullptr, nullptr, reinterpret_cast(data.Get()), &cbData) != ERROR_SUCCESS) { RegCloseKey(hKey); // "Could not read registry value"; return true; } RegCloseKey(hKey); *result = data.Get(); return false; } bool WindowsPlatform::Init() { if (Win32Platform::Init()) return true; // Check if can run Engine on current platform (requires Windows Vista SP1 or above) if (!IsWindowsVistaSP1OrGreater() && !IsWindowsServer()) { Platform::Fatal(TEXT("Flax Engine requires Windows Vista SP1 or higher.")); return true; } // Set lowest possible timer resolution for previous Windows versions if (VersionMajor < 10 || (VersionMajor == 10 && VersionBuild < 17134)) { timeBeginPeriod(1); } DWORD tmp; Char buffer[256]; // Get user locale string if (GetUserDefaultLocaleName(buffer, LOCALE_NAME_MAX_LENGTH)) { UserLocale = String(buffer); } // Get computer name string if (GetComputerNameW(buffer, &tmp)) { ComputerName = String(buffer); } // Get user name string if (GetUserNameW(buffer, &tmp)) { UserName = String(buffer); } WindowsInput::Init(); return false; } void WindowsPlatform::LogInfo() { Win32Platform::LogInfo(); LOG(Info, "Microsoft {0} {1}-bit ({2}.{3}.{4})", WindowsName, Platform::Is64BitPlatform() ? TEXT("64") : TEXT("32"), VersionMajor, VersionMinor, VersionBuild); // Check minimum amount of RAM auto memStats = Platform::GetMemoryStats(); uint64 mb = memStats.TotalPhysicalMemory / (1024 * 1024); uint64 mbMinimum = 2048; if (mb < mbMinimum * 0.8f) { auto msg = String::Format(TEXT("Not enough RAM memory for good application performance.\nDetected: {0} MB\nRecommended : {1} MB\nDo you want to continue ?"), mb, mbMinimum); if (MessageBoxW(nullptr, *msg, TEXT("Warning"), MB_ICONWARNING | MB_YESNO) == IDNO) { LOG(Warning, "Not enough RAM. Closing..."); exit(0); } } } void WindowsPlatform::Tick() { WindowsInput::Update(); // Check to see if any messages are waiting in the queue MSG msg; while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { // Translate the message and dispatch it to WindowProc() TranslateMessage(&msg); DispatchMessage(&msg); } } void WindowsPlatform::BeforeExit() { } void WindowsPlatform::Exit() { #if CRASH_LOG_ENABLE SymLocker.Lock(); #if !TRACY_ENABLE if (SymInitialized) { SymInitialized = false; SymCleanup(GetCurrentProcess()); } #endif SymbolsPath.Resize(0); SymLocker.Unlock(); #endif // Unregister app class UnregisterClassW(ApplicationWindowClass, nullptr); Win32Platform::Exit(); } #if !BUILD_RELEASE void WindowsPlatform::Log(const StringView& msg) { Char buffer[512]; Char* str; if (msg.Length() + 3 < ARRAY_COUNT(buffer)) str = buffer; else str = (Char*)Allocate((msg.Length() + 3) * sizeof(Char), 16); MemoryCopy(str, msg.Get(), msg.Length() * sizeof(Char)); str[msg.Length() + 0] = '\r'; str[msg.Length() + 1] = '\n'; str[msg.Length() + 2] = 0; OutputDebugStringW(str); if (str != buffer) Free(str); } bool WindowsPlatform::IsDebuggerPresent() { return !!::IsDebuggerPresent(); } #endif void WindowsPlatform::SetHighDpiAwarenessEnabled(bool enable) { const HMODULE shCoreDll = LoadLibraryW(L"Shcore.dll"); if (!shCoreDll) return; typedef enum _PROCESS_DPI_AWARENESS { PROCESS_DPI_UNAWARE = 0, PROCESS_SYSTEM_DPI_AWARE = 1, PROCESS_PER_MONITOR_DPI_AWARE = 2 } PROCESS_DPI_AWARENESS; typedef HRESULT (STDAPICALLTYPE *SetProcessDpiAwarenessProc)(PROCESS_DPI_AWARENESS Value); const SetProcessDpiAwarenessProc setProcessDpiAwareness = (SetProcessDpiAwarenessProc)GetProcAddress(shCoreDll, "SetProcessDpiAwareness"); if (setProcessDpiAwareness) { setProcessDpiAwareness(enable ? PROCESS_PER_MONITOR_DPI_AWARE : PROCESS_DPI_UNAWARE); } SystemDpi = CalculateDpi(shCoreDll); ::FreeLibrary(shCoreDll); } BatteryInfo WindowsPlatform::GetBatteryInfo() { BatteryInfo info; SYSTEM_POWER_STATUS status; GetSystemPowerStatus(&status); info.BatteryLifePercent = (float)status.BatteryLifePercent / 255.0f; if (status.BatteryFlag & 8) info.State = BatteryInfo::States::BatteryCharging; else if (status.BatteryFlag & 1 || status.BatteryFlag & 2 || status.BatteryFlag & 4) info.State = BatteryInfo::States::BatteryDischarging; else if (status.ACLineStatus == 1 || status.BatteryFlag & 128) info.State = BatteryInfo::States::Connected; return info; } int32 WindowsPlatform::GetDpi() { return SystemDpi; } String WindowsPlatform::GetUserLocaleName() { return UserLocale; } String WindowsPlatform::GetComputerName() { return ComputerName; } String WindowsPlatform::GetUserName() { return UserName; } bool WindowsPlatform::GetHasFocus() { DWORD foregroundProcess; GetWindowThreadProcessId(GetForegroundWindow(), &foregroundProcess); return foregroundProcess == ::GetCurrentProcessId(); } bool WindowsPlatform::CanOpenUrl(const StringView& url) { return true; } void WindowsPlatform::OpenUrl(const StringView& url) { ::ShellExecuteW(nullptr, TEXT("open"), *url, nullptr, nullptr, SW_SHOWNORMAL); } struct GetMonitorBoundsData { Vector2 Pos; Rectangle Result; GetMonitorBoundsData(const Vector2& pos) : Pos(pos) , Result(Vector2::Zero, WindowsPlatform::GetDesktopSize()) { } }; BOOL CALLBACK EnumMonitorSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { GetMonitorBoundsData* data = reinterpret_cast(dwData); Rectangle monitorRect( (float)lprcMonitor->left, (float)lprcMonitor->top, (float)(lprcMonitor->right - lprcMonitor->left), (float)(lprcMonitor->bottom - lprcMonitor->top)); if (monitorRect.Contains(data->Pos)) { data->Result = monitorRect; return FALSE; } return TRUE; } Rectangle WindowsPlatform::GetMonitorBounds(const Vector2& screenPos) { GetMonitorBoundsData data(screenPos); EnumDisplayMonitors(nullptr, nullptr, EnumMonitorSize, reinterpret_cast(&data)); return data.Result; } Vector2 WindowsPlatform::GetDesktopSize() { return Vector2( static_cast(GetSystemMetrics(SM_CXSCREEN)), static_cast(GetSystemMetrics(SM_CYSCREEN)) ); } BOOL CALLBACK EnumMonitorTotalBounds(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { LPRECT l = reinterpret_cast(dwData); UnionRect(l, l, lprcMonitor); return TRUE; } Rectangle WindowsPlatform::GetVirtualDesktopBounds() { if (VirtualScreenBounds.Size.X == 0) { RECT screenRect = {}; EnumDisplayMonitors(nullptr, nullptr, EnumMonitorTotalBounds, reinterpret_cast(&screenRect)); VirtualScreenBounds.Location.X = static_cast(screenRect.left); VirtualScreenBounds.Location.Y = static_cast(screenRect.top); VirtualScreenBounds.Size.X = static_cast(screenRect.right - screenRect.left); VirtualScreenBounds.Size.Y = static_cast(screenRect.bottom - screenRect.top); } return VirtualScreenBounds; } void WindowsPlatform::GetEnvironmentVariables(Dictionary& result) { const LPWCH environmentStr = GetEnvironmentStringsW(); if (environmentStr) { LPWCH env = environmentStr; while (*env != '\0') { if (*env != '=') { WCHAR* str = wcschr(env, '='); ASSERT(str); result[String(env, (int32)(str - env))] = str + 1; } while (*env != '\0') env++; env++; } FreeEnvironmentStringsW(environmentStr); } } bool WindowsPlatform::GetEnvironmentVariable(const String& name, String& value) { const int32 bufferSize = 512; Char buffer[bufferSize]; DWORD result = GetEnvironmentVariableW(*name, buffer, bufferSize); if (result == 0) { LOG_WIN32_LAST_ERROR; return true; } if (bufferSize < result) { value.ReserveSpace(result); result = GetEnvironmentVariableW(*name, *value, result); if (!result) { LOG_WIN32_LAST_ERROR; return FALSE; } } else { value.Set(buffer, result); } return false; } bool WindowsPlatform::SetEnvironmentVariable(const String& name, const String& value) { if (!SetEnvironmentVariableW(*name, *value)) { LOG_WIN32_LAST_ERROR; return true; } return false; } int32 WindowsPlatform::StartProcess(const StringView& filename, const StringView& args, const StringView& workingDir, bool hiddenWindow, bool waitForEnd) { // Info LOG(Info, "Command: {0} {1}", filename, args); if (workingDir.HasChars()) { LOG(Info, "Working directory: {0}", workingDir); } SHELLEXECUTEINFOW shExecInfo = { 0 }; shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW); shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; shExecInfo.lpFile = filename.GetText(); shExecInfo.lpParameters = args.HasChars() ? args.Get() : nullptr; shExecInfo.lpDirectory = workingDir.HasChars() ? workingDir.Get() : nullptr; shExecInfo.nShow = hiddenWindow ? SW_HIDE : SW_SHOW; if (ShellExecuteExW(&shExecInfo) == FALSE) { LOG(Warning, "Cannot start process '{0}' with arguments '{2}'. Error code: {1:x}", filename, (int64)GetLastError(), args); return 1; } int32 result = 0; if (waitForEnd) { WaitForSingleObject(shExecInfo.hProcess, INFINITE); DWORD exitCode; if (GetExitCodeProcess(shExecInfo.hProcess, &exitCode) != 0) result = exitCode; CloseHandle(shExecInfo.hProcess); } return result; } int32 WindowsPlatform::RunProcess(const StringView& cmdLine, const StringView& workingDir, bool hiddenWindow) { return RunProcess(cmdLine, workingDir, Dictionary(), hiddenWindow); } bool IsProcRunning(HANDLE handle) { return WaitForSingleObject(handle, 0) == WAIT_TIMEOUT; } void ReadPipe(HANDLE pipe, Array& rawData, Array& logData, LogType logType) { // Check if any data is ready to read DWORD bytesAvailable = 0; if (PeekNamedPipe(pipe, nullptr, 0, nullptr, &bytesAvailable, nullptr) && bytesAvailable > 0) { // Read data rawData.Clear(); rawData.Resize(bytesAvailable); DWORD bytesRead = 0; if (ReadFile(pipe, rawData.Get(), bytesAvailable, &bytesRead, nullptr) && bytesRead > 0) { // Skip Windows-style lines rawData.RemoveAllKeepOrder('\r'); // Remove last new line character if (rawData.Last() == '\n') rawData.RemoveLast(); // Log contents logData.Clear(); logData.Resize(rawData.Count() + 1); StringUtils::ConvertANSI2UTF16(rawData.Get(), logData.Get(), rawData.Count()); logData.Last() = '\0'; Log::Logger::Write(logType, StringView(logData.Get(), rawData.Count())); } } } int32 WindowsPlatform::RunProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary& environment, bool hiddenWindow) { const bool captureStdOut = true; // Info LOG(Info, "Command: {0}", cmdLine); if (workingDir.HasChars()) { LOG(Info, "Working directory: {0}", workingDir); } int32 result = -1; STARTUPINFOEX startupInfoEx; ZeroMemory(&startupInfoEx, sizeof(startupInfoEx)); startupInfoEx.StartupInfo.cb = sizeof(startupInfoEx); if (hiddenWindow) { startupInfoEx.StartupInfo.dwFlags |= STARTF_USESHOWWINDOW; startupInfoEx.StartupInfo.wShowWindow |= SW_HIDE | SW_SHOWNOACTIVATE; } DWORD dwCreationFlags = NORMAL_PRIORITY_CLASS | DETACHED_PROCESS; if (hiddenWindow) dwCreationFlags |= CREATE_NO_WINDOW; Char* environmentStr = nullptr; if (environment.HasItems()) { dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT; int32 totalLength = 1; for (auto& e : environment) totalLength += e.Key.Length() + e.Value.Length() + 2; environmentStr = (Char*)Allocator::Allocate(totalLength * sizeof(Char)); Char* env = environmentStr; for (auto& e : environment) { auto& key = e.Key; auto& value = e.Value; Platform::MemoryCopy(env, key.Get(), key.Length() * sizeof(Char)); env += key.Length(); *env++ = '='; Platform::MemoryCopy(env, value.Get(), value.Length() * sizeof(Char)); env += value.Length(); *env++ = 0; } *env++ = 0; ASSERT((uint64)env - (uint64)environmentStr == (uint64)(totalLength * sizeof(Char))); } HANDLE stdOutRead = nullptr; HANDLE stdErrRead = nullptr; Array attributeList; if (captureStdOut) { dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT; startupInfoEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; SECURITY_ATTRIBUTES securityAttributes; securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); securityAttributes.bInheritHandle = TRUE; securityAttributes.lpSecurityDescriptor = nullptr; if (!CreatePipe(&stdOutRead, &startupInfoEx.StartupInfo.hStdOutput, &securityAttributes, 0) || !CreatePipe(&stdErrRead, &startupInfoEx.StartupInfo.hStdError, &securityAttributes, 0)) { LOG(Warning, "CreatePipe failed"); return 1; } SIZE_T bufferSize = 0; if (!InitializeProcThreadAttributeList(nullptr, 1, 0, &bufferSize) && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { attributeList.Resize((int32)bufferSize); startupInfoEx.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)attributeList.Get(); if (!InitializeProcThreadAttributeList(startupInfoEx.lpAttributeList, 1, 0, &bufferSize)) { LOG(Warning, "InitializeProcThreadAttributeList failed"); goto ERROR_EXIT; } } HANDLE inheritHandles[2] = { startupInfoEx.StartupInfo.hStdOutput, startupInfoEx.StartupInfo.hStdError }; if (!UpdateProcThreadAttribute(startupInfoEx.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inheritHandles, sizeof(inheritHandles), nullptr, nullptr)) { LOG(Warning, "UpdateProcThreadAttribute failed"); goto ERROR_EXIT; } } // Create the process PROCESS_INFORMATION procInfo; if (!CreateProcessW(nullptr, const_cast(cmdLine.GetText()), nullptr, nullptr, TRUE, dwCreationFlags, (LPVOID)environmentStr, workingDir.HasChars() ? workingDir.Get() : nullptr, &startupInfoEx.StartupInfo, &procInfo)) { LOG(Warning, "Cannot start process '{0}'. Error code: 0x{1:x}", cmdLine, static_cast(GetLastError())); goto ERROR_EXIT; } if (environmentStr) Allocator::Free(environmentStr); if (stdOutRead != nullptr) { // Keep reading std output and std error streams until process is running Array rawData; Array logData; do { ReadPipe(stdOutRead, rawData, logData, LogType::Info); ReadPipe(stdErrRead, rawData, logData, LogType::Error); Sleep(1); } while (IsProcRunning(procInfo.hProcess)); ReadPipe(stdOutRead, rawData, logData, LogType::Info); ReadPipe(stdErrRead, rawData, logData, LogType::Error); } else { WaitForSingleObject(procInfo.hProcess, INFINITE); } DWORD exitCode; if (GetExitCodeProcess(procInfo.hProcess, &exitCode) != 0) result = exitCode; CloseHandle(procInfo.hProcess); CloseHandle(procInfo.hThread); // Cleanup ERROR_EXIT: if (startupInfoEx.StartupInfo.hStdOutput != nullptr) { CloseHandle(startupInfoEx.StartupInfo.hStdOutput); } if (startupInfoEx.StartupInfo.hStdError != nullptr) { CloseHandle(startupInfoEx.StartupInfo.hStdError); } if (stdOutRead != nullptr) { CloseHandle(stdOutRead); } if (stdErrRead != nullptr) { CloseHandle(stdErrRead); } if (startupInfoEx.lpAttributeList != nullptr) { DeleteProcThreadAttributeList(startupInfoEx.lpAttributeList); } return result; } Window* WindowsPlatform::CreateWindow(const CreateWindowSettings& settings) { return New(settings); } void* WindowsPlatform::LoadLibrary(const Char* filename) { ASSERT(filename); // Add folder to search path to load dependency libraries StringView folder = StringUtils::GetDirectoryName(filename); if (folder.HasChars() && FileSystem::IsRelative(folder)) folder = StringView::Empty; if (folder.HasChars()) { Char& end = ((Char*)folder.Get())[folder.Length()]; const Char c = end; end = 0; SetDllDirectoryW(*folder); end = c; } // Avoiding windows dialog boxes if missing const DWORD errorMode = SEM_NOOPENFILEERRORBOX; DWORD prevErrorMode = 0; const BOOL hasErrorMode = SetThreadErrorMode(errorMode, &prevErrorMode); // Load the DLL void* handle = ::LoadLibraryW(filename); if (!handle) { LOG(Warning, "Failed to load '{0}' (GetLastError={1})", filename, GetLastError()); } if (hasErrorMode) { SetThreadErrorMode(prevErrorMode, nullptr); } if (folder.HasChars()) { SetDllDirectoryW(nullptr); } #if CRASH_LOG_ENABLE // Refresh modules info during next stack trace collecting to have valid debug symbols information SymLocker.Lock(); if (folder.HasChars() && !SymbolsPath.Contains(folder)) { SymbolsPath.Add(folder); OnSymbolsPathModified(); } SymLocker.Unlock(); #endif return handle; } Array WindowsPlatform::GetStackFrames(int32 skipCount, int32 maxDepth, void* context) { Array result; #if CRASH_LOG_ENABLE SymLocker.Lock(); // Initialize HANDLE process = GetCurrentProcess(); HANDLE thread = GetCurrentThread(); if (!SymInitialized) { SymInitialized = true; String symbolSearchPath; for (auto& path : SymbolsPath) { symbolSearchPath += path; symbolSearchPath += ";"; } symbolSearchPath += Platform::GetWorkingDirectory(); SymInitializeW(process, *symbolSearchPath, TRUE); } // Capture the context if missing /*EXCEPTION_POINTERS exceptionPointers; CONTEXT contextData; if (!context) { exceptionPointers.ExceptionRecord = nullptr; exceptionPointers.ContextRecord = &contextData; MemoryClear(&contextData, sizeof(CONTEXT)); contextData.ContextFlags = CONTEXT_FULL; RtlCaptureContext(&contextData); context = &exceptionPointers; }*/ // Capture the backtrace int32 count; void* backtrace[100]; maxDepth = Math::Min(maxDepth, ARRAY_COUNT(backtrace)); if (context) { auto& ctx = *(((EXCEPTION_POINTERS*)context)->ContextRecord); STACKFRAME64 stack; memset(&stack, 0, sizeof(stack)); DWORD imageType; #ifdef _M_IX86 imageType = IMAGE_FILE_MACHINE_I386; stack.AddrPC.Offset = ctx.Eip; stack.AddrPC.Mode = AddrModeFlat; stack.AddrFrame.Offset = ctx.Ebp; stack.AddrFrame.Mode = AddrModeFlat; stack.AddrStack.Offset = ctx.Esp; stack.AddrStack.Mode = AddrModeFlat; #elif _M_X64 imageType = IMAGE_FILE_MACHINE_AMD64; stack.AddrPC.Offset = ctx.Rip; stack.AddrPC.Mode = AddrModeFlat; stack.AddrFrame.Offset = ctx.Rsp; stack.AddrFrame.Mode = AddrModeFlat; stack.AddrStack.Offset = ctx.Rsp; stack.AddrStack.Mode = AddrModeFlat; #elif _M_IA64 imageType = IMAGE_FILE_MACHINE_IA64; stack.AddrPC.Offset = ctx.StIIP; stack.AddrPC.Mode = AddrModeFlat; stack.AddrFrame.Offset = ctx.IntSp; stack.AddrFrame.Mode = AddrModeFlat; stack.AddrBStore.Offset = ctx.RsBSP; stack.AddrBStore.Mode = AddrModeFlat; stack.AddrStack.Offset = ctx.IntSp; stack.AddrStack.Mode = AddrModeFlat; #else #error "Platform not supported!" #endif count = 0; for (int32 i = 0; i < skipCount; i++) StackWalk64(imageType, process, thread, &stack, &ctx, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr); while (count < maxDepth && StackWalk64(imageType, process, thread, &stack, &ctx, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) backtrace[count++] = (void*)stack.AddrPC.Offset; } else { skipCount++; count = RtlCaptureStackBackTrace(skipCount, maxDepth, backtrace, nullptr); } // Walk the stack to collect the symbols result.Resize(count); Platform::MemoryClear(result.Get(), count * sizeof(StackFrame)); for (int32 i = 0; i < count; i++) { auto& frame = result[i]; frame.ProgramCounter = backtrace[i]; // Get function name alignas(IMAGEHLP_SYMBOL64) byte symbolData[sizeof(IMAGEHLP_SYMBOL64) + ARRAY_COUNT(frame.FunctionName)]; IMAGEHLP_SYMBOL64* symbol = (IMAGEHLP_SYMBOL64*)symbolData; symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); symbol->MaxNameLength = ARRAY_COUNT(frame.FunctionName) - 1; DWORD64 displacement = 0; if (SymGetSymFromAddr64(process, (DWORD64)frame.ProgramCounter, &displacement, symbol)) { UnDecorateSymbolName(symbol->Name, frame.FunctionName, ARRAY_COUNT(frame.FunctionName), UNDNAME_COMPLETE); } // Get filename and line number IMAGEHLP_LINE64 line; line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); DWORD offset; if (SymGetLineFromAddr64(process, (DWORD64)frame.ProgramCounter, &offset, &line)) { frame.LineNumber = line.LineNumber; const int32 fileNameLength = Math::Min(ARRAY_COUNT(frame.FileName) - 1, StringUtils::Length(line.FileName)); memcpy(frame.FileName, line.FileName, fileNameLength); frame.FileName[fileNameLength] = '\0'; } // Get module name IMAGEHLP_MODULE64 module; module.SizeOfStruct = sizeof(IMAGEHLP_MODULE64); if (SymGetModuleInfo64(process, (DWORD64)frame.ProgramCounter, &module)) { const int32 moduleNameLength = Math::Min(ARRAY_COUNT(frame.ModuleName) - 1, StringUtils::Length(module.ImageName)); memcpy(frame.ModuleName, module.ImageName, moduleNameLength); frame.ModuleName[moduleNameLength] = '\0'; } } SymLocker.Unlock(); #endif return result; } #if CRASH_LOG_ENABLE void WindowsPlatform::CollectCrashData(const String& crashDataFolder, void* context) { // Create mini dump file for crash debugging struct CrashInfo { LPEXCEPTION_POINTERS ExceptionPointers; DWORD CallerThreadId; String MiniDumpPath; }; CrashInfo crashInfo; crashInfo.ExceptionPointers = (PEXCEPTION_POINTERS)context; crashInfo.CallerThreadId = GetCurrentThreadId(); crashInfo.MiniDumpPath = crashDataFolder / TEXT("Minidump.dmp"); LOG(Error, "Creating Mini Dump to {0}", crashInfo.MiniDumpPath); auto threadFunc = [](void* data) -> DWORD { auto info = (CrashInfo*)data; const HANDLE process = GetCurrentProcess(); const HANDLE file = CreateFileW(*info->MiniDumpPath, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); const MINIDUMP_TYPE minidumpType = (MINIDUMP_TYPE)(MiniDumpWithFullMemoryInfo | MiniDumpFilterMemory | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules); MINIDUMP_EXCEPTION_INFORMATION minidumpExceptionInfo; minidumpExceptionInfo.ThreadId = info->CallerThreadId; minidumpExceptionInfo.ExceptionPointers = info->ExceptionPointers; minidumpExceptionInfo.ClientPointers = FALSE; MiniDumpWriteDump(process, GetProcessId(process), file, minidumpType, info->ExceptionPointers ? &minidumpExceptionInfo : nullptr, nullptr, nullptr); CloseHandle(file); return 0; }; DWORD threadID; const auto handle = CreateThread(0, 0x8000, threadFunc, &crashInfo, 0, &threadID); WaitForSingleObject(handle, INFINITE); } #endif #endif