summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indra/llwindow/llwindowwin32.cpp144
-rw-r--r--indra/llwindow/llwindowwin32.h6
-rw-r--r--indra/newview/llappviewerwin32.cpp222
-rw-r--r--indra/newview/llappviewerwin32.h3
-rw-r--r--indra/newview/llvelopack.cpp7
5 files changed, 379 insertions, 3 deletions
diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp
index c185fc6c4a..3cb2911f9a 100644
--- a/indra/llwindow/llwindowwin32.cpp
+++ b/indra/llwindow/llwindowwin32.cpp
@@ -2605,6 +2605,150 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
// if session is ending OS is going to take care of it.
return 0;
}
+ case WM_POST_UNINSTALL_:
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_POST_UNINSTALL_");
+ // Other instance, likely velopack, requested we quit.
+ // Don't trust PID alone (can be spoofed), verify the
+ // path for security purposes before processing.
+ // Verifying path isn't a strong varranty, if this turns
+ // up to be a risk, we will want something more secure.
+ // See sendShutdownToOtherInstances for the sender.
+
+ // LPARAM contains message type.
+ DWORD message_type = static_cast<DWORD>(l_param);
+ if (message_type == WM_POST_UNINSTALL_MSG_SHUTDOWN || message_type == WM_POST_UNINSTALL_MSG_UPDATE)
+ {
+ DWORD sender_process_id = static_cast<DWORD>(w_param);
+
+ // Make sure something didn't just send us our own process
+ DWORD our_process_id = GetCurrentProcessId();
+ if (our_process_id == sender_process_id)
+ {
+ LL_WARNS("Window") << "Received WM_POST_UNINSTALL_ from our own process, ignoring" << LL_ENDL;
+ break;
+ }
+
+ if (sender_process_id == 0)
+ {
+ LL_WARNS("Window") << "Received WM_POST_UNINSTALL_ but couldn't get sender process ID" << LL_ENDL;
+ break;
+ }
+
+ // Open the existing sender process to verify its executable path
+ HANDLE hSenderProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, sender_process_id);
+ if (!hSenderProcess)
+ {
+ LL_WARNS("Window") << "Received WM_POST_UNINSTALL_ but couldn't open sender process" << LL_ENDL;
+ break;
+ }
+
+ // Get the actual executable path of the sender
+ wchar_t sender_exe_path[MAX_PATH];
+ DWORD size = MAX_PATH;
+ bool got_sender_path = QueryFullProcessImageNameW(hSenderProcess, 0, sender_exe_path, &size) != 0;
+ CloseHandle(hSenderProcess);
+
+ if (!got_sender_path)
+ {
+ LL_WARNS("Window") << "Received WM_POST_UNINSTALL_ but couldn't query sender executable path" << LL_ENDL;
+ break;
+ }
+
+ // Extract directory from sender's executable path
+ wchar_t sender_dir[MAX_PATH];
+ wchar_t* file_part = nullptr;
+ DWORD result = GetFullPathNameW(sender_exe_path, MAX_PATH, sender_dir, &file_part);
+
+ if (result == 0 || result >= MAX_PATH)
+ {
+ LL_WARNS("Window") << "Failed to normalize sender executable path" << LL_ENDL;
+ break;
+ }
+
+ // Remove the filename to get just directory
+ if (file_part)
+ {
+ *file_part = L'\0';
+ }
+
+ // Remove trailing backslash
+ size_t sender_dir_len = wcslen(sender_dir);
+ if (sender_dir_len > 0 && sender_dir[sender_dir_len - 1] == L'\\')
+ {
+ sender_dir[sender_dir_len - 1] = L'\0';
+ sender_dir_len--;
+ }
+
+ // Remove "\current" suffix from sender's path if present
+ const std::wstring current_suffix = L"\\current";
+ std::wstring sender_normalized_str(sender_dir);
+ if (sender_normalized_str.length() >= current_suffix.length() &&
+ _wcsicmp(sender_normalized_str.c_str() + sender_normalized_str.length() - current_suffix.length(),
+ current_suffix.c_str()) == 0)
+ {
+ sender_normalized_str.resize(sender_normalized_str.length() - current_suffix.length());
+ }
+
+ // Get our executable directory for comparison
+ std::wstring our_wide = ll_convert<std::wstring>(gDirUtilp->getExecutableDir());
+
+ // Normalize our path
+ wchar_t our_normalized[MAX_PATH];
+ file_part = nullptr;
+
+ DWORD result2 = GetFullPathNameW(our_wide.c_str(), MAX_PATH, our_normalized, &file_part);
+
+ if (result2 == 0 || result2 >= MAX_PATH)
+ {
+ LL_WARNS("Window") << "Failed to normalize our executable path" << LL_ENDL;
+ break;
+ }
+
+ // Remove trailing backslash
+ size_t our_len = wcslen(our_normalized);
+ if (our_len > 0 && our_normalized[our_len - 1] == L'\\')
+ {
+ our_normalized[our_len - 1] = L'\0';
+ our_len--;
+ }
+
+ // Remove "\current" suffix from our path if present
+ std::wstring our_normalized_str(our_normalized);
+ if (our_normalized_str.length() >= current_suffix.length() &&
+ _wcsicmp(our_normalized_str.c_str() + our_normalized_str.length() - current_suffix.length(),
+ current_suffix.c_str()) == 0)
+ {
+ our_normalized_str.resize(our_normalized_str.length() - current_suffix.length());
+ }
+
+ // Compare the normalized base installation paths (case-insensitive)
+ if (_wcsicmp(sender_normalized_str.c_str(), our_normalized_str.c_str()) == 0)
+ {
+ window_imp->post([=]()
+ {
+ LL_INFOS("Window") << "Received valid shutdown request from verified same installation directory" << LL_ENDL;
+ // Check if app needs cleanup or can be closed immediately.
+ if (window_imp->mCallbacks->handleCloseRequest(window_imp, false))
+ {
+ // Get the app to initiate cleanup.
+ window_imp->mCallbacks->handleQuit(window_imp);
+ }
+ });
+ }
+ else
+ {
+ LL_WARNS("Window") << "Rejected shutdown request - sender not from our installation directory. "
+ << "Sender: " << ll_convert_wide_to_string(sender_normalized_str)
+ << " Our: " << ll_convert_wide_to_string(our_normalized_str) << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_WARNS("Window") << "Received invalid WM_POST_UNINSTALL_ message" << LL_ENDL;
+ }
+ break;
+ }
case WM_COMMAND:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_COMMAND");
diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h
index aab2635a34..afff3d5cb6 100644
--- a/indra/llwindow/llwindowwin32.h
+++ b/indra/llwindow/llwindowwin32.h
@@ -40,6 +40,12 @@
// Hack for async host by name
#define LL_WM_HOST_RESOLVED (WM_APP + 1)
+// For requesting shutdown on uninstall,
+// make sure it does not conflict with messages like WM_DUMMY_
+inline constexpr UINT WM_POST_UNINSTALL_ = WM_USER + 0x0019;
+inline constexpr DWORD WM_POST_UNINSTALL_MSG_SHUTDOWN = 1;
+inline constexpr DWORD WM_POST_UNINSTALL_MSG_UPDATE = 2;
+
typedef void (*LLW32MsgCallback)(const MSG &msg);
class LLWindowWin32 : public LLWindow
diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp
index 94a5f7951e..0dee17010c 100644
--- a/indra/newview/llappviewerwin32.cpp
+++ b/indra/newview/llappviewerwin32.cpp
@@ -969,7 +969,7 @@ bool LLAppViewerWin32::sendURLToOtherInstance(const std::string& url)
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;
@@ -977,13 +977,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()
{
diff --git a/indra/newview/llappviewerwin32.h b/indra/newview/llappviewerwin32.h
index 0741758a0c..d0a3f2f5ce 100644
--- a/indra/newview/llappviewerwin32.h
+++ b/indra/newview/llappviewerwin32.h
@@ -45,6 +45,9 @@ public:
bool reportCrashToBugsplat(void* pExcepInfo) override;
+ // returns true if other windows were found and are still running.
+ static bool sendShutdownToOtherInstances(const std::wstring& install_dir);
+
protected:
bool initWindow() override; // Override to initialize the viewer's window.
void initLoggingAndGetLastDuration() override; // Override to clean stack_trace info.
diff --git a/indra/newview/llvelopack.cpp b/indra/newview/llvelopack.cpp
index 8975f7d369..d34d43cc48 100644
--- a/indra/newview/llvelopack.cpp
+++ b/indra/newview/llvelopack.cpp
@@ -43,6 +43,7 @@
#include "Velopack.h"
#if LL_WINDOWS
+#include "llappviewerwin32.h"
#include <windows.h>
#include <shlobj.h>
#include <shobjidl.h>
@@ -666,8 +667,12 @@ static void on_before_uninstall(void* user_data, const char* app_version)
unregister_protocol_handler(PROTOCOL_SECONDLIFE);
unregister_protocol_handler(PROTOCOL_GRID_INFO);
- unregister_uninstall_info();
remove_shortcuts(app_name);
+
+ std::wstring install_dir = get_install_dir();
+ LLAppViewerWin32::sendShutdownToOtherInstances(install_dir);
+
+ unregister_uninstall_info();
}
static void on_log_message(void* user_data, const char* level, const char* message)