diff options
Diffstat (limited to 'indra/newview/llappviewerwin32.cpp')
| -rw-r--r-- | indra/newview/llappviewerwin32.cpp | 353 |
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() { |
