1406 lines
44 KiB
C++
1406 lines
44 KiB
C++
// Copyright (c) 2012-2023 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/CreateProcessSettings.h"
|
|
#include "Engine/Platform/WindowsManager.h"
|
|
#include "Engine/Platform/MemoryStats.h"
|
|
#include "Engine/Platform/BatteryInfo.h"
|
|
#include "Engine/Platform/Base/PlatformUtils.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 "Engine/Engine/CommandLine.h"
|
|
#include "../Win32/IncludeWindowsHeaders.h"
|
|
#include <VersionHelpers.h>
|
|
#include <ShellAPI.h>
|
|
#include <Psapi.h>
|
|
#include <objbase.h>
|
|
#include <cstdio>
|
|
#undef ShellExecute
|
|
#if CRASH_LOG_ENABLE
|
|
#include <dbghelp.h>
|
|
#endif
|
|
#include "resource.h"
|
|
|
|
const Char* WindowsPlatform::ApplicationWindowClass = TEXT("FlaxWindow");
|
|
void* WindowsPlatform::Instance = nullptr;
|
|
|
|
#if CRASH_LOG_ENABLE || TRACY_ENABLE
|
|
// Lock for symbols list, shared with Tracy
|
|
extern "C" {
|
|
static HANDLE dbgHelpLock;
|
|
|
|
void DbgHelpInit()
|
|
{
|
|
dbgHelpLock = CreateMutexW(nullptr, FALSE, nullptr);
|
|
}
|
|
|
|
void DbgHelpLock()
|
|
{
|
|
WaitForSingleObject(dbgHelpLock, INFINITE);
|
|
}
|
|
|
|
void DbgHelpUnlock()
|
|
{
|
|
ReleaseMutex(dbgHelpLock);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
namespace
|
|
{
|
|
String UserLocale, ComputerName, WindowsName;
|
|
HANDLE EngineMutex = nullptr;
|
|
Rectangle VirtualScreenBounds(0.0f, 0.0f, 0.0f, 0.0f);
|
|
int32 VersionMajor = 0;
|
|
int32 VersionMinor = 0;
|
|
int32 VersionBuild = 0;
|
|
int32 SystemDpi = 96;
|
|
#if CRASH_LOG_ENABLE
|
|
#if TRACY_ENABLE
|
|
bool SymInitialized = true;
|
|
#else
|
|
bool SymInitialized = false;
|
|
#endif
|
|
Array<String> 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<LPBYTE>(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<LPBYTE>(&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;
|
|
}
|
|
else if (VersionMajor >= 10 && VersionBuild >= 22000)
|
|
{
|
|
// Windows 11
|
|
windowsName.Replace(TEXT("10"), TEXT("11"));
|
|
}
|
|
else 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<WindowsWindow*>(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);
|
|
}
|
|
|
|
// Log exception and return to the crash location when using debugger
|
|
if (Platform::IsDebuggerPresent())
|
|
{
|
|
LOG_STR(Error, errorMsg);
|
|
const String stackTrace = Platform::GetStackTrace(0, 60, ep);
|
|
LOG_STR(Error, stackTrace);
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
// 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<HWND>(parent->GetNativePtr()) : nullptr, String(text).GetText(), String(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 };
|
|
DbgHelpLock();
|
|
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();
|
|
DbgHelpUnlock();
|
|
#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<Char> data;
|
|
data.Resize((int32)cbData / sizeof(Char));
|
|
if (RegQueryValueExW(hKey, *name, nullptr, nullptr, reinterpret_cast<LPBYTE>(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;
|
|
|
|
// Init console output (engine is linked with /SUBSYSTEM:WINDOWS so it lacks of proper console output on Windows)
|
|
if (CommandLine::Options.Std)
|
|
{
|
|
// Attaches output of application to parent console, returns true if running in console-mode
|
|
// [Reference: https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application]
|
|
if (AttachConsole(ATTACH_PARENT_PROCESS))
|
|
{
|
|
const HANDLE consoleHandleOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
if (consoleHandleOut != INVALID_HANDLE_VALUE)
|
|
{
|
|
freopen("CONOUT$", "w", stdout);
|
|
setvbuf(stdout, NULL, _IONBF, 0);
|
|
}
|
|
const HANDLE consoleHandleError = GetStdHandle(STD_ERROR_HANDLE);
|
|
if (consoleHandleError != INVALID_HANDLE_VALUE)
|
|
{
|
|
freopen("CONOUT$", "w", stderr);
|
|
setvbuf(stderr, NULL, _IONBF, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 the lowest possible timer resolution
|
|
const HMODULE ntdll = LoadLibraryW(L"ntdll.dll");
|
|
if (ntdll)
|
|
{
|
|
typedef LONG (WIN_API_CALLCONV *NtSetTimerResolution)(ULONG DesiredResolution, unsigned char SetResolution, ULONG* CurrentResolution);
|
|
const NtSetTimerResolution ntSetTimerResolution = (NtSetTimerResolution)GetProcAddress(ntdll, "NtSetTimerResolution");
|
|
if (ntSetTimerResolution)
|
|
{
|
|
ULONG currentResolution;
|
|
ntSetTimerResolution(1, TRUE, ¤tResolution);
|
|
}
|
|
::FreeLibrary(ntdll);
|
|
}
|
|
|
|
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
|
|
String userName;
|
|
if (GetUserNameW(buffer, &tmp))
|
|
{
|
|
userName = String(buffer);
|
|
}
|
|
OnPlatformUserAdd(New<User>(userName));
|
|
|
|
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
|
|
DbgHelpLock();
|
|
#if !TRACY_ENABLE
|
|
if (SymInitialized)
|
|
{
|
|
SymInitialized = false;
|
|
SymCleanup(GetCurrentProcess());
|
|
}
|
|
#endif
|
|
SymbolsPath.Resize(0);
|
|
DbgHelpUnlock();
|
|
#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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
Float2 WindowsPlatform::GetMousePosition()
|
|
{
|
|
POINT cursorPos;
|
|
GetCursorPos(&cursorPos);
|
|
return Float2((float)cursorPos.x, (float)cursorPos.y);
|
|
}
|
|
|
|
void WindowsPlatform::SetMousePosition(const Float2& pos)
|
|
{
|
|
::SetCursorPos((int)pos.X, (int)pos.Y);
|
|
}
|
|
|
|
struct GetMonitorBoundsData
|
|
{
|
|
Float2 Pos;
|
|
Rectangle Result;
|
|
|
|
GetMonitorBoundsData(const Float2& pos)
|
|
: Pos(pos)
|
|
, Result(Float2::Zero, WindowsPlatform::GetDesktopSize())
|
|
{
|
|
}
|
|
};
|
|
|
|
BOOL CALLBACK EnumMonitorSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
|
|
{
|
|
GetMonitorBoundsData* data = reinterpret_cast<GetMonitorBoundsData*>(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 Float2& screenPos)
|
|
{
|
|
GetMonitorBoundsData data(screenPos);
|
|
EnumDisplayMonitors(nullptr, nullptr, EnumMonitorSize, reinterpret_cast<LPARAM>(&data));
|
|
return data.Result;
|
|
}
|
|
|
|
Float2 WindowsPlatform::GetDesktopSize()
|
|
{
|
|
return Float2(
|
|
static_cast<float>(GetSystemMetrics(SM_CXSCREEN)),
|
|
static_cast<float>(GetSystemMetrics(SM_CYSCREEN))
|
|
);
|
|
}
|
|
|
|
BOOL CALLBACK EnumMonitorTotalBounds(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
|
|
{
|
|
LPRECT l = reinterpret_cast<LPRECT>(dwData);
|
|
UnionRect(l, l, lprcMonitor);
|
|
return TRUE;
|
|
}
|
|
|
|
Rectangle WindowsPlatform::GetVirtualDesktopBounds()
|
|
{
|
|
if (VirtualScreenBounds.Size.X == 0)
|
|
{
|
|
RECT screenRect = {};
|
|
EnumDisplayMonitors(nullptr, nullptr, EnumMonitorTotalBounds, reinterpret_cast<LPARAM>(&screenRect));
|
|
VirtualScreenBounds.Location.X = static_cast<float>(screenRect.left);
|
|
VirtualScreenBounds.Location.Y = static_cast<float>(screenRect.top);
|
|
VirtualScreenBounds.Size.X = static_cast<float>(screenRect.right - screenRect.left);
|
|
VirtualScreenBounds.Size.Y = static_cast<float>(screenRect.bottom - screenRect.top);
|
|
}
|
|
return VirtualScreenBounds;
|
|
}
|
|
|
|
void WindowsPlatform::GetEnvironmentVariables(Dictionary<String, String>& 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;
|
|
}
|
|
|
|
bool IsProcRunning(HANDLE handle)
|
|
{
|
|
return WaitForSingleObject(handle, 0) == WAIT_TIMEOUT;
|
|
}
|
|
|
|
void ReadPipe(HANDLE pipe, Array<char>& rawData, Array<Char>& logData, LogType logType, CreateProcessSettings& settings)
|
|
{
|
|
// 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);
|
|
int32 tmp;
|
|
StringUtils::ConvertANSI2UTF16(rawData.Get(), logData.Get(), rawData.Count(), tmp);
|
|
logData.Last() = '\0';
|
|
if (settings.LogOutput)
|
|
Log::Logger::Write(logType, StringView(logData.Get(), rawData.Count()));
|
|
if (settings.SaveOutput)
|
|
settings.Output.Add(logData.Get(), rawData.Count());
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 WindowsPlatform::CreateProcess(CreateProcessSettings& settings)
|
|
{
|
|
LOG(Info, "Command: {0} {1}", settings.FileName, settings.Arguments);
|
|
if (settings.WorkingDirectory.HasChars())
|
|
{
|
|
LOG(Info, "Working directory: {0}", settings.WorkingDirectory);
|
|
}
|
|
const bool captureStdOut = settings.LogOutput || settings.SaveOutput;
|
|
|
|
int32 result = 0;
|
|
if (settings.ShellExecute)
|
|
{
|
|
SHELLEXECUTEINFOW shExecInfo = { 0 };
|
|
shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
|
|
shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
|
|
shExecInfo.lpFile = settings.FileName.GetText();
|
|
shExecInfo.lpParameters = settings.Arguments.HasChars() ? settings.Arguments.Get() : nullptr;
|
|
shExecInfo.lpDirectory = settings.WorkingDirectory.HasChars() ? settings.WorkingDirectory.Get() : nullptr;
|
|
shExecInfo.nShow = settings.HiddenWindow ? SW_HIDE : SW_SHOW;
|
|
if (ShellExecuteExW(&shExecInfo) == FALSE)
|
|
{
|
|
result = 1;
|
|
LOG(Warning, "Cannot start process. Error code: 0x{0:x}", (uint64)GetLastError());
|
|
}
|
|
else if (settings.WaitForEnd)
|
|
{
|
|
WaitForSingleObject(shExecInfo.hProcess, INFINITE);
|
|
DWORD exitCode;
|
|
if (GetExitCodeProcess(shExecInfo.hProcess, &exitCode) != 0)
|
|
result = exitCode;
|
|
CloseHandle(shExecInfo.hProcess);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = -1;
|
|
const String cmdLine = settings.FileName + TEXT(" ") + settings.Arguments;
|
|
|
|
STARTUPINFOEX startupInfoEx;
|
|
ZeroMemory(&startupInfoEx, sizeof(startupInfoEx));
|
|
startupInfoEx.StartupInfo.cb = sizeof(startupInfoEx);
|
|
if (settings.HiddenWindow)
|
|
{
|
|
startupInfoEx.StartupInfo.dwFlags |= STARTF_USESHOWWINDOW;
|
|
startupInfoEx.StartupInfo.wShowWindow |= SW_HIDE | SW_SHOWNOACTIVATE;
|
|
}
|
|
|
|
DWORD dwCreationFlags = NORMAL_PRIORITY_CLASS | DETACHED_PROCESS;
|
|
if (settings.HiddenWindow)
|
|
dwCreationFlags |= CREATE_NO_WINDOW;
|
|
|
|
Char* environmentStr = nullptr;
|
|
if (settings.Environment.HasItems())
|
|
{
|
|
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
|
|
|
|
int32 totalLength = 1;
|
|
for (auto& e : settings.Environment)
|
|
totalLength += e.Key.Length() + e.Value.Length() + 2;
|
|
|
|
environmentStr = (Char*)Allocator::Allocate(totalLength * sizeof(Char));
|
|
|
|
Char* env = environmentStr;
|
|
for (auto& e : settings.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;
|
|
|
|
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)
|
|
{
|
|
startupInfoEx.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)Allocator::Allocate(bufferSize);
|
|
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<LPWSTR>(cmdLine.GetText()), nullptr, nullptr, TRUE, dwCreationFlags, (LPVOID)environmentStr, settings.WorkingDirectory.HasChars() ? settings.WorkingDirectory.Get() : nullptr, &startupInfoEx.StartupInfo, &procInfo))
|
|
{
|
|
LOG(Warning, "Cannot start process. Error code: 0x{0:x}", (uint64)GetLastError());
|
|
goto ERROR_EXIT;
|
|
}
|
|
|
|
if (stdOutRead != nullptr)
|
|
{
|
|
// Keep reading std output and std error streams until process is running
|
|
Array<char> rawData;
|
|
Array<Char> logData;
|
|
do
|
|
{
|
|
ReadPipe(stdOutRead, rawData, logData, LogType::Info, settings);
|
|
ReadPipe(stdErrRead, rawData, logData, LogType::Error, settings);
|
|
Sleep(1);
|
|
} while (IsProcRunning(procInfo.hProcess));
|
|
ReadPipe(stdOutRead, rawData, logData, LogType::Info, settings);
|
|
ReadPipe(stdErrRead, rawData, logData, LogType::Error, settings);
|
|
}
|
|
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);
|
|
Allocator::Free(startupInfoEx.lpAttributeList);
|
|
}
|
|
if (environmentStr)
|
|
Allocator::Free(environmentStr);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
Window* WindowsPlatform::CreateWindow(const CreateWindowSettings& settings)
|
|
{
|
|
return New<WindowsWindow>(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
|
|
DbgHelpLock();
|
|
if (folder.HasChars() && !SymbolsPath.Contains(folder))
|
|
{
|
|
SymbolsPath.Add(folder);
|
|
SymbolsPath.Last().Replace('/', '\\');
|
|
OnSymbolsPathModified();
|
|
}
|
|
DbgHelpUnlock();
|
|
#endif
|
|
|
|
return handle;
|
|
}
|
|
|
|
#if CRASH_LOG_ENABLE
|
|
|
|
Array<PlatformBase::StackFrame> WindowsPlatform::GetStackFrames(int32 skipCount, int32 maxDepth, void* context)
|
|
{
|
|
Array<StackFrame> result;
|
|
DbgHelpLock();
|
|
|
|
// 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<int32>(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<int32>(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<int32>(ARRAY_COUNT(frame.ModuleName) - 1, StringUtils::Length(module.ImageName));
|
|
memcpy(frame.ModuleName, module.ImageName, moduleNameLength);
|
|
frame.ModuleName[moduleNameLength] = '\0';
|
|
}
|
|
}
|
|
|
|
DbgHelpUnlock();
|
|
return result;
|
|
}
|
|
|
|
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
|