summaryrefslogtreecommitdiff
path: root/indra/newview/llappviewerwin32.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/llappviewerwin32.cpp')
-rw-r--r--indra/newview/llappviewerwin32.cpp353
1 files changed, 335 insertions, 18 deletions
diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp
index 5288dce69c..01ea24d211 100644
--- a/indra/newview/llappviewerwin32.cpp
+++ b/indra/newview/llappviewerwin32.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -31,7 +31,7 @@
#endif
#include "llwin32headers.h"
-#if !LL_SDL
+#if !LL_SDL_WINDOW
#include "llwindowwin32.h" // *FIX: for setting gIconResource.
#endif
@@ -76,11 +76,18 @@
#include <fstream>
#include <exception>
+// Velopack installer and update framework
+#if LL_VELOPACK
+#include "llvelopack.h"
+#endif
+#include "llversioninfovars.h"
+
// Bugsplat (http://bugsplat.com) crash reporting tool
#ifdef LL_BUGSPLAT
#include "BugSplat.h"
#include "boost/json.hpp" // Boost.Json
#include "llagent.h" // for agent location
+#include "llmemory.h"
#include "llstartup.h"
#include "llviewerregion.h"
#include "llvoavatarself.h" // for agent name
@@ -118,6 +125,7 @@ namespace
// MiniDmpSender pointer. As things stand, though, we must define an
// actual function and store the pointer statically.
static MiniDmpSender *sBugSplatSender = nullptr;
+ static std::string sBugsplatDescriptionField;
bool bugsplatSendLog(UINT nCode, LPVOID lpVal1, LPVOID lpVal2)
{
@@ -154,8 +162,21 @@ namespace
WCSTR(gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "settings_per_account.xml")));
}
- // LL_ERRS message, when there is one
- sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage()));
+ if (!sBugsplatDescriptionField.empty())
+ {
+ // Can be set by watchdog or other code that detects a problem
+ // and wants to add some context to the crash report.
+ // Will be visible in the BugSplat web UI.
+ sBugSplatSender->setDefaultUserDescription(WCSTR(sBugsplatDescriptionField));
+ // This type of crash is not necessarily a crash, or final.
+ // Prepare for the next one.
+ sBugsplatDescriptionField.clear();
+ }
+ else
+ {
+ // LL_ERRS message, when there is one
+ sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage()));
+ }
sBugSplatSender->setAttribute(WCSTR(L"OS"), WCSTR(LLOSInfo::instance().getOSStringSimple())); // In case we ever stop using email for this
sBugSplatSender->setAttribute(WCSTR(L"AppState"), WCSTR(LLStartUp::getStartupStateString()));
@@ -166,6 +187,16 @@ namespace
sBugSplatSender->setAttribute(WCSTR(L"VRAM"), WCSTR(STRINGIZE(gGLManager.mVRAM)));
sBugSplatSender->setAttribute(WCSTR(L"RAM"), WCSTR(STRINGIZE(gSysMemory.getPhysicalMemoryKB().value())));
+ const U32 avail_kb = LLMemory::getAvailableMemKB().value();
+ if (avail_kb != U32_MAX) // filter out initial values, if one is not set, all are not set
+ {
+ // Memory usage at crash time (can be 1s obsolete)
+ sBugSplatSender->setAttribute(WCSTR(L"MemAllocatedKB"), WCSTR(std::to_string(LLMemory::getAllocatedMemKB().value())));
+ sBugSplatSender->setAttribute(WCSTR(L"MemAvailableKB"), WCSTR(std::to_string(LLMemory::getAvailableMemKB().value())));
+ sBugSplatSender->setAttribute(WCSTR(L"MemMaxPhysicalKB"), WCSTR(std::to_string(LLMemory::getMaxMemKB().value())));
+ sBugSplatSender->setAttribute(WCSTR(L"MemAvailCommitMB"), WCSTR(std::to_string(LLMemory::getAvailableCommitMemMB().value())));
+ }
+
if (gAgent.getRegion())
{
// region location, when we have it
@@ -178,6 +209,14 @@ namespace
}
LLAppViewer* app = LLAppViewer::instance();
+
+ // Include mainloop watchdog state if available
+ std::string watchdog_state = app->getMainloopWatchdogState();
+ if (!watchdog_state.empty())
+ {
+ sBugSplatSender->setAttribute(WCSTR(L"WatchdogState"), WCSTR(watchdog_state));
+ }
+
if (!app->isSecondInstance() && !app->errorMarkerExists())
{
// If marker doesn't exist, create a marker with 'other' or 'logout' code for next launch
@@ -199,6 +238,8 @@ namespace
}
#endif // LL_BUGSPLAT
+extern bool gGPUBenchmarkMode;
+
namespace
{
void (*gOldTerminateHandler)() = NULL;
@@ -224,7 +265,6 @@ LONG WINAPI catchallCrashHandler(EXCEPTION_POINTERS * /*ExceptionInfo*/)
return 0;
}
-const std::string LLAppViewerWin32::sWindowClass = "Second Life";
#if !_M_ARM64
/*
@@ -432,6 +472,17 @@ int APIENTRY WINMAIN(HINSTANCE hInstance,
PWSTR pCmdLine,
int nCmdShow)
{
+#if LL_VELOPACK
+ // Velopack MUST be initialized first - it may handle install/uninstall
+ // commands and exit the process before we do anything else.
+ if (!velopack_initialize())
+ {
+ // Obsolete? Always return true
+ // Velopack handled the invocation (install/uninstall hook)
+ return 0;
+ }
+#endif
+
// Call Tracy first thing to have it allocate memory
// https://github.com/wolfpld/tracy/issues/196
LL_PROFILER_FRAME_END;
@@ -441,7 +492,7 @@ int APIENTRY WINMAIN(HINSTANCE hInstance,
DWORD heap_enable_lfh_error[MAX_HEAPS];
S32 num_heaps = 0;
-#if !LL_SDL
+#if !LL_SDL_WINDOW
LLWindowWin32::setDPIAwareness();
#endif
@@ -472,18 +523,27 @@ int APIENTRY WINMAIN(HINSTANCE hInstance,
#endif
#endif
-#if !LL_SDL
+#if !LL_SDL_WINDOW
// *FIX: global
gIconResource = MAKEINTRESOURCE(IDI_LL_ICON);
#endif
gIconSmallResource = MAKEINTRESOURCE(IDI_LL_ICON_SMALL);
+ // Benchmark subprocess mode before full init for LLFeatureManager::loadGPUClass().
+ {
+ std::wstring cmdLineStr(pCmdLine ? pCmdLine : L"");
+ if (cmdLineStr.find(L"--gpubenchmark") != std::wstring::npos)
+ {
+ gGPUBenchmarkMode = true;
+ }
+ }
+
LLAppViewerWin32* viewer_app_ptr = new LLAppViewerWin32(ll_convert_wide_to_string(pCmdLine).c_str());
gOldTerminateHandler = std::set_terminate(exceptionTerminateHandler);
// Set a debug info flag to indicate if multiple instances are running.
- bool found_other_instance = !create_app_mutex();
+ bool found_other_instance = gGPUBenchmarkMode || !create_app_mutex();
gDebugInfo["FoundOtherInstanceAtStartup"] = LLSD::Boolean(found_other_instance);
bool ok = viewer_app_ptr->init();
@@ -827,12 +887,7 @@ bool LLAppViewerWin32::cleanup()
bool result = LLAppViewer::cleanup();
gDXHardware.cleanup();
-
- if (mIsConsoleAllocated)
- {
- FreeConsole();
- mIsConsoleAllocated = false;
- }
+ cleanupConsole();
return result;
}
@@ -849,6 +904,38 @@ bool LLAppViewerWin32::reportCrashToBugsplat(void* pExcepInfo)
return false;
}
+#if defined(LL_BUGSPLAT)
+static int reportCustomToBugsplatFilter(EXCEPTION_POINTERS* pExcepInfo)
+{
+ if (sBugSplatSender)
+ {
+ sBugSplatSender->createReport(pExcepInfo);
+ }
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+#endif
+
+bool LLAppViewerWin32::reportCustomToBugsplat(const std::string &description)
+{
+#if defined(LL_BUGSPLAT)
+ if (sBugSplatSender)
+ {
+ sBugsplatDescriptionField = description;
+
+ __try
+ {
+ // Generate a custom exception code
+ RaiseException(0xE0000001, 0, 0, NULL);
+ }
+ __except (reportCustomToBugsplatFilter(GetExceptionInformation()))
+ {
+ }
+ return true;
+ }
+#endif // LL_BUGSPLAT
+ return false;
+}
+
bool LLAppViewerWin32::initWindow()
{
// This is a workaround/hotfix for a change in Windows 11 24H2 (and possibly later)
@@ -880,10 +967,22 @@ void LLAppViewerWin32::initLoggingAndGetLastDuration()
void LLAppViewerWin32::initConsole()
{
// pop up debug console
- mIsConsoleAllocated = create_console();
+ if (!gGPUBenchmarkMode)
+ {
+ mIsConsoleAllocated = create_console();
+ }
return LLAppViewer::initConsole();
}
+void LLAppViewerWin32::cleanupConsole()
+{
+ if (mIsConsoleAllocated)
+ {
+ FreeConsole();
+ mIsConsoleAllocated = false;
+ }
+}
+
void write_debug_dx(const char* str)
{
std::string value = gDebugInfo["DXInfo"].asString();
@@ -949,14 +1048,14 @@ bool LLAppViewerWin32::restoreErrorTrap()
bool LLAppViewerWin32::sendURLToOtherInstance(const std::string& url)
{
wchar_t window_class[256]; /* Flawfinder: ignore */ // Assume max length < 255 chars.
- mbstowcs(window_class, sWindowClass.c_str(), 255);
+ mbstowcs(window_class, sWindowClass, 255);
window_class[255] = 0;
// Use the class instead of the window name.
HWND other_window = FindWindow(window_class, NULL);
if (other_window != NULL)
{
- LL_DEBUGS() << "Found other window with the name '" << getWindowTitle() << "'" << LL_ENDL;
+ LL_DEBUGS("AppInit") << "Found other window with the name '" << getWindowTitle() << "'" << LL_ENDL;
COPYDATASTRUCT cds;
const S32 SLURL_MESSAGE_TYPE = 0;
cds.dwData = SLURL_MESSAGE_TYPE;
@@ -964,13 +1063,231 @@ bool LLAppViewerWin32::sendURLToOtherInstance(const std::string& url)
cds.lpData = (void*)url.c_str();
LRESULT msg_result = SendMessage(other_window, WM_COPYDATA, NULL, (LPARAM)&cds);
- LL_DEBUGS() << "SendMessage(WM_COPYDATA) to other window '"
+ LL_DEBUGS("AppInit") << "SendMessage(WM_COPYDATA) to other window '"
<< getWindowTitle() << "' returned " << msg_result << LL_ENDL;
return true;
}
return false;
}
+bool LLAppViewerWin32::sendShutdownToOtherInstances(const std::wstring& install_dir)
+{
+ // Velopack installs viewer like this:
+ // %appdata%\Local\ChannelNameViewer\Update.exe // which is our uninstaller
+ // %appdata%\Local\ChannelNameViewer\SecondLifeViewer.exe // wrapper, redirects to main executable
+ // %appdata%\Local\ChannelNameViewer\current\SecondLifeViewer.exe // main executable
+ // For reliability don't expect install_dir to be actually in the base path, strip 'current'
+
+ const std::wstring current_suffix = L"\\current";
+ std::wstring normalized_path(install_dir);
+ if (normalized_path.length() >= current_suffix.length() &&
+ _wcsicmp(normalized_path.c_str() + normalized_path.length() - current_suffix.length(),
+ current_suffix.c_str()) == 0)
+ {
+ normalized_path.resize(normalized_path.length() - current_suffix.length());
+ }
+
+ wchar_t window_class[256]; // Assume max length < 255 chars.
+ mbstowcs(window_class, sWindowClass, 255);
+ window_class[255] = 0;
+
+ // Normalize the directory path
+ wchar_t our_dir_normalized[MAX_PATH];
+ wchar_t* file_part = nullptr;
+ DWORD result = GetFullPathNameW(normalized_path.c_str(), MAX_PATH, our_dir_normalized, &file_part);
+ if (result == 0 || result >= MAX_PATH)
+ {
+ LL_WARNS() << "Failed to normalize our executable path" << LL_ENDL;
+ return false;
+ }
+
+ // Remove trailing backslash if present
+ size_t dir_len = wcslen(our_dir_normalized);
+ if (dir_len > 0 && our_dir_normalized[dir_len - 1] == L'\\')
+ {
+ our_dir_normalized[dir_len - 1] = L'\0';
+ dir_len--;
+ }
+
+ // This message is meant for velopack, so we don't expect to have
+ // a window of our own, store any matching windows.
+ struct EnumData
+ {
+ const wchar_t* target_class;
+ const wchar_t* our_dir_normalized;
+ std::vector<HWND> found_windows;
+ };
+
+ EnumData enum_data;
+ enum_data.target_class = window_class;
+ enum_data.our_dir_normalized = our_dir_normalized;
+
+ // Callback function to find all matching windows
+ auto find_windows_callback = [](HWND hwnd, LPARAM lParam) -> BOOL
+ {
+ EnumData* data = reinterpret_cast<EnumData*>(lParam);
+ wchar_t class_name[256];
+
+ if (GetClassName(hwnd, class_name, 256) > 0)
+ {
+ if (wcscmp(class_name, data->target_class) == 0)
+ {
+ // Get the process ID for this window
+ DWORD process_id = 0;
+ GetWindowThreadProcessId(hwnd, &process_id);
+
+ // Open the process to query its executable path
+ HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id);
+ if (hProcess)
+ {
+ wchar_t exe_path[MAX_PATH];
+ DWORD size = MAX_PATH;
+ if (QueryFullProcessImageNameW(hProcess, 0, exe_path, &size))
+ {
+ // Normalize the other process's path
+ wchar_t other_dir_normalized[MAX_PATH];
+ wchar_t* other_file_part = nullptr;
+ DWORD result = GetFullPathNameW(exe_path, MAX_PATH, other_dir_normalized, &other_file_part);
+
+ if (result > 0 && result < MAX_PATH)
+ {
+ // Remove the filename part to get just the directory
+ // We are doing this to avoid incidents, like having
+ // multiple viewer version exes in the same folder.
+ if (other_file_part)
+ {
+ *other_file_part = L'\0';
+ }
+
+ // Remove trailing backslash if present
+ size_t other_dir_len = wcslen(other_dir_normalized);
+ if (other_dir_len > 0 && other_dir_normalized[other_dir_len - 1] == L'\\')
+ {
+ other_dir_normalized[other_dir_len - 1] = L'\0';
+ other_dir_len--;
+ }
+
+ // Strip "\current" suffix if present to normalize comparison
+ // This handles both release (with \current) and debug builds (without)
+ const std::wstring current_suffix = L"\\current";
+ if (other_dir_len >= current_suffix.length())
+ {
+ size_t offset = other_dir_len - current_suffix.length();
+ if (_wcsicmp(other_dir_normalized + offset, current_suffix.c_str()) == 0)
+ {
+ other_dir_normalized[offset] = L'\0';
+ }
+ }
+
+ // Compare directories (case-insensitive)
+ if (_wcsicmp(other_dir_normalized, data->our_dir_normalized) == 0)
+ {
+ data->found_windows.push_back(hwnd);
+ }
+ }
+ }
+ CloseHandle(hProcess);
+ }
+ }
+ }
+
+ return TRUE; // Continue enumeration
+ };
+
+ // Find all matching windows and send shutdown messages
+ EnumWindows(find_windows_callback, reinterpret_cast<LPARAM>(&enum_data));
+
+ if (enum_data.found_windows.empty())
+ {
+ LL_DEBUGS("AppInit") << "No other instances found" << LL_ENDL;
+ return false;
+ }
+
+ LL_INFOS("AppInit") << "Found " << (S32)(enum_data.found_windows.size()) << " other instance(s), sending shutdown messages" << LL_ENDL;
+
+ // Get our own process ID to include in the message
+ DWORD our_process_id = GetCurrentProcessId();
+
+ constexpr UINT timeout_ms = 2000; // 2s. Viewer's message thread is supposed to be fast.
+ for (HWND other_window : enum_data.found_windows)
+ {
+ if (IsWindow(other_window))
+ {
+ DWORD_PTR result = 0;
+ LRESULT send_result = SendMessageTimeout(
+ other_window,
+ WM_POST_UNINSTALL_,
+ static_cast<WPARAM>(our_process_id),
+ static_cast<LPARAM>(WM_POST_UNINSTALL_MSG_SHUTDOWN),
+ SMTO_ABORTIFHUNG | SMTO_BLOCK,
+ timeout_ms,
+ &result
+ );
+
+ if (send_result == 0)
+ {
+ DWORD error = GetLastError();
+ if (error == ERROR_TIMEOUT)
+ {
+ LL_WARNS("AppInit") << "Shutdown message timed out for window " << std::hex << other_window << std::dec << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("AppInit") << "Failed to send shutdown message to window " << std::hex << other_window
+ << ", error: " << error << std::dec << LL_ENDL;
+ }
+
+ PostMessage(other_window, WM_CLOSE, 0, 0);
+ }
+ else
+ {
+ LL_DEBUGS("AppInit") << "Shutdown message sent successfully to window " << std::hex << other_window << std::dec << LL_ENDL;
+ }
+ }
+ }
+
+ // Poll for up to 30 seconds, checking every 5 seconds
+ const S32 MAX_WAIT_TIME_MS = 60000; // 30 seconds
+ const S32 POLL_INTERVAL_MS = 5000; // 5 seconds
+ S32 elapsed_time_ms = 0;
+ size_t still_open_count = enum_data.found_windows.size();
+
+ while (elapsed_time_ms < MAX_WAIT_TIME_MS)
+ {
+ LL_INFOS("AppInit") << "Waiting for " << (S32)still_open_count << " instance(s) to close... ("
+ << (S32)(elapsed_time_ms / 1000) << "s elapsed)" << LL_ENDL;
+
+ ms_sleep(POLL_INTERVAL_MS);
+ elapsed_time_ms += POLL_INTERVAL_MS;
+
+ // Check if the specific windows we found still exist
+ // Don't enumerate all windows for new ones, assume that
+ // no instances were reused and assume user won't open
+ // the app again. For now just check our list.
+ still_open_count = 0;
+ for (HWND hwnd : enum_data.found_windows)
+ {
+ if (IsWindow(hwnd))
+ {
+ still_open_count++;
+ }
+ }
+
+ if (still_open_count == 0)
+ {
+ LL_INFOS("AppInit") << "All other instances have closed after " << (S32)(elapsed_time_ms / 1000) << " seconds" << LL_ENDL;
+ return false;
+ }
+ }
+
+ if (still_open_count != 0)
+ {
+ LL_WARNS("AppInit") << "Proceeding with uninstall with " << (S32)still_open_count << " instance(s) still open." << LL_ENDL;
+ }
+
+ return true;
+}
+
std::string LLAppViewerWin32::generateSerialNumber()
{