summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrey Kleshchev <117672381+akleshchev@users.noreply.github.com>2026-02-10 22:29:52 +0200
committerGitHub <noreply@github.com>2026-02-10 15:29:52 -0500
commita6b978b3e00f2e7e186b97d8a4997374adecdafc (patch)
tree82cefb540d98ff60bc9c8955817f395f66bc2fe4
parent85298ebb341c6038dba92bc21311566ff5e86708 (diff)
#5346 Uninstall older non-velopack viewer (#5363)
* #5335 Fix silent uninstall asking about registry * #5346 Uninstall older non-velopack viewer if of the same channel
-rw-r--r--indra/cmake/CMakeLists.txt1
-rw-r--r--indra/newview/app_settings/settings.xml12
-rw-r--r--indra/newview/installers/windows/installer_template.nsi33
-rw-r--r--indra/newview/llstartup.cpp76
-rw-r--r--indra/newview/llvelopack.cpp97
-rw-r--r--indra/newview/llvelopack.h4
-rw-r--r--indra/newview/skins/default/xui/en/notifications.xml14
7 files changed, 233 insertions, 4 deletions
diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt
index 2ba282bdb7..c10f6ec934 100644
--- a/indra/cmake/CMakeLists.txt
+++ b/indra/cmake/CMakeLists.txt
@@ -62,6 +62,7 @@ set(cmake_SOURCE_FILES
UI.cmake
UnixInstall.cmake
Variables.cmake
+ Velopack.cmake
VHACD.cmake
ViewerMiscLibs.cmake
VisualLeakDetector.cmake
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index fe31a00ba3..2f2d9f3ece 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -4237,7 +4237,17 @@
<key>Value</key>
<string>0.0.0</string>
</map>
-
+ <key>LastInstallVersion</key>
+ <map>
+ <key>Comment</key>
+ <string>Version number of last instance of the viewer that you installed</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string></string>
+ </map>
<key>LimitDragDistance</key>
<map>
<key>Comment</key>
diff --git a/indra/newview/installers/windows/installer_template.nsi b/indra/newview/installers/windows/installer_template.nsi
index 0e36698018..ae40e8830f 100644
--- a/indra/newview/installers/windows/installer_template.nsi
+++ b/indra/newview/installers/windows/installer_template.nsi
@@ -767,8 +767,21 @@ Function un.UserSettingsFiles
StrCmp $DO_UNINSTALL_V2 "true" Keep # Don't remove user's settings files on auto upgrade
-# Ask if user wants to keep data files or not
-MessageBox MB_YESNO|MB_ICONQUESTION $(RemoveDataFilesMB) IDYES Remove IDNO Keep
+ClearErrors
+Push $0
+${GetParameters} $COMMANDLINE
+${GetOptionsS} $COMMANDLINE "/clrusrfiles" $0
+# GetOptionsS returns an error if option does not exist, jump past Goto.
+IfErrors +3 0
+ Pop $0
+ Goto Remove
+
+Pop $0
+ClearErrors
+
+ifSilent Keep 0
+ # Ask if user wants to keep data files or not
+ MessageBox MB_YESNO|MB_ICONQUESTION $(RemoveDataFilesMB) IDYES Remove IDNO Keep
Remove:
Push $0
@@ -864,11 +877,25 @@ RMDir "$INSTDIR"
IfFileExists "$INSTDIR" FOLDERFOUND NOFOLDER
FOLDERFOUND:
+ifSilent NOFOLDER 0
MessageBox MB_OK $(DeleteProgramFilesMB) /SD IDOK IDOK NOFOLDER
NOFOLDER:
-MessageBox MB_YESNO $(DeleteRegistryKeysMB) IDYES DeleteKeys IDNO NoDelete
+ClearErrors
+Push $0
+${GetParameters} $COMMANDLINE
+${GetOptionsS} $COMMANDLINE "/clearreg" $0
+# GetOptionsS returns an error if option does not exist, jump past Goto.
+IfErrors +3 0
+ Pop $0
+ Goto DeleteKeys
+
+Pop $0
+ClearErrors
+
+ifSilent NoDelete 0
+ MessageBox MB_YESNO $(DeleteRegistryKeysMB) IDYES DeleteKeys IDNO NoDelete
DeleteKeys:
DeleteRegKey SHELL_CONTEXT "SOFTWARE\Classes\x-grid-location-info"
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 59d97943e3..675693045e 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -29,6 +29,11 @@
#include "llappviewer.h"
#include "llstartup.h"
+#if LL_VELOPACK && LL_WINDOWS
+#include "llvelopack.h"
+#include <shellapi.h>
+#endif
+
#if LL_WINDOWS
# include <process.h> // _spawnl()
#else
@@ -266,6 +271,7 @@ std::unique_ptr<LLViewerStats::PhaseMap> LLStartUp::sPhases(new LLViewerStats::P
void login_show();
void login_callback(S32 option, void* userdata);
+void uninstall_nsis_if_required();
void show_release_notes_if_required();
void show_first_run_dialog();
bool first_run_dialog_callback(const LLSD& notification, const LLSD& response);
@@ -921,6 +927,7 @@ bool idle_startup()
LL_DEBUGS("AppInit") << "PeekMessage processed" << LL_ENDL;
#endif
do_startup_frame();
+ uninstall_nsis_if_required();
timeout.reset();
return false;
}
@@ -2605,6 +2612,75 @@ void release_notes_coro(const std::string url)
LLWeb::loadURLInternal(url);
}
+/**
+* Check if this is a fresh velopack install and
+* if uninstallation of old viewer is needed.
+*/
+void uninstall_nsis_if_required()
+{
+#if LL_VELOPACK && LL_WINDOWS
+ // Todo: perhaps use marker files?
+ // Debug variable isn't specific to one channel
+ // and something channel specific is needed.
+ std::string last_install_ver = gSavedSettings.getString("LastInstallVersion");
+ LLVersionInfo* ver_inst = LLVersionInfo::getInstance();
+ if (ver_inst->getChannelAndVersion() == last_install_ver)
+ {
+ return;
+ }
+ gSavedSettings.setString("LastInstallVersion",
+ ver_inst->getChannelAndVersion());
+
+ LL_INFOS() << "Looking for previous NSIS installs" << LL_ENDL;
+
+ wchar_t buffer[MAX_PATH];
+ if (!get_nsis_uninstaller_path( buffer,
+ MAX_PATH,
+ ver_inst->getMajor(),
+ ver_inst->getMinor(),
+ ver_inst->getPatch(),
+ ver_inst->getBuild())
+ )
+ {
+ return;
+ }
+
+ // Compose command line: "<uninstaller_path>" /S /clearreg
+ std::wstring params = L"\"";
+ params += buffer;
+ params += L"\"";
+ // params += L" /S /clearreg"; // silent uninstall and clear registry entries
+
+ LLNotificationsUtil::add("PromptRemoveNsisInstallation", LLSD(), LLSD(),
+ [params](const LLSD& notification, const LLSD& response)
+ {
+ S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
+ if (option == 1) // cancel
+ {
+ return;
+ }
+
+ LL_INFOS() << "Triggering NSIS uninstall from " << ll_convert_wide_to_string(params) << LL_ENDL;
+
+ // Launch uninstaller using explorer.exe
+ SHELLEXECUTEINFOW sei = { 0 };
+ sei.cbSize = sizeof(sei);
+ sei.fMask = SEE_MASK_DEFAULT;
+ sei.hwnd = NULL;
+ sei.lpVerb = L"runas"; // Request elevation
+ sei.lpFile = L"explorer.exe";
+ sei.lpParameters = params.c_str();
+
+ sei.nShow = SW_HIDE;
+
+ if (!ShellExecuteExW(&sei))
+ {
+ LL_WARNS("AppInit") << "Failed to launch NSIS uninstaller, error code: " << GetLastError() << LL_ENDL;
+ }
+ });
+#endif
+}
+
void validate_release_notes_coro(const std::string url)
{
LLVersionInfo& versionInfo(LLVersionInfo::instance());
diff --git a/indra/newview/llvelopack.cpp b/indra/newview/llvelopack.cpp
index 32bad18f38..14be2b0cae 100644
--- a/indra/newview/llvelopack.cpp
+++ b/indra/newview/llvelopack.cpp
@@ -28,6 +28,7 @@
#include "llviewerprecompiledheaders.h"
#include "llvelopack.h"
+#include "llstring.h"
#include "Velopack.h"
@@ -37,6 +38,7 @@
#include <shobjidl.h>
#include <shlwapi.h>
#include <objbase.h>
+#include <filesystem>
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "ole32.lib")
@@ -175,6 +177,89 @@ static void register_protocol_handler(const std::wstring& protocol,
}
}
+static void parse_version(const wchar_t* version_str, int& major, int& minor, int& patch, uint64_t& build)
+{
+ major = minor = patch = 0;
+ build = 0;
+ if (!version_str) return;
+ // Use swscanf for wide strings
+ swscanf(version_str, L"%d.%d.%d.%llu", &major, &minor, &patch, &build);
+}
+
+bool get_nsis_uninstaller_path(wchar_t* path_buffer, DWORD bufSize, S32 cur_major_ver, S32 cur_minor_ver, S32 cur_patch_ver, U64 cur_build_ver)
+{
+ // Test for presence of NSIS viewer registration, then
+ // attempt to read uninstall info
+ std::wstring app_name_oneword = get_app_name_oneword();
+ std::wstring uninstall_key_path = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + app_name_oneword;
+ HKEY hkey;
+ LONG result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, uninstall_key_path.c_str(), 0, KEY_READ, &hkey);
+ if (result != ERROR_SUCCESS)
+ {
+ return false;
+ }
+
+ // Read DisplayVersion
+ wchar_t version_buf[64] = { 0 };
+ DWORD version_buf_size = sizeof(version_buf);
+ DWORD type = 0;
+ LONG ver_rv = RegGetValueW(hkey, nullptr, L"DisplayVersion", RRF_RT_REG_SZ, &type, version_buf, &version_buf_size);
+
+ if (ver_rv != ERROR_SUCCESS)
+ {
+ RegCloseKey(hkey);
+ return false;
+ }
+
+ int nsis_major = 0, nsis_minor = 0, nsis_patch = 0;
+ uint64_t nsis_build = 0;
+ parse_version(version_buf, nsis_major, nsis_minor, nsis_patch, nsis_build);
+
+ // Compare numerically
+ if ((nsis_major > cur_major_ver) ||
+ (nsis_major == cur_major_ver && nsis_minor > cur_minor_ver) ||
+ (nsis_major == cur_major_ver && nsis_minor == cur_minor_ver && nsis_patch > cur_patch_ver) ||
+ // Assume that bigger build number means newer version, which is not always true but works for our purposes
+ (nsis_major == cur_major_ver && nsis_minor == cur_minor_ver && nsis_patch == cur_patch_ver && nsis_build > cur_build_ver))
+ {
+ LL_INFOS() << "Found installed nsis version that is newer" << nsis_major << "." << nsis_minor << "." << nsis_patch << LL_ENDL;
+ RegCloseKey(hkey);
+ return false;
+ }
+
+ LONG rv = RegGetValueW(hkey, nullptr, L"UninstallString", RRF_RT_REG_SZ, &type, path_buffer, &bufSize);
+ RegCloseKey(hkey);
+ if (rv != ERROR_SUCCESS)
+ {
+ return false;
+ }
+ size_t len = wcslen(path_buffer);
+ if (len > 0)
+ {
+ if (path_buffer[0] == L'\"')
+ {
+ // Likely to contain leading "
+ memmove(path_buffer, path_buffer + 1, len * sizeof(wchar_t));
+ }
+ wchar_t* pos = wcsstr(path_buffer, L"uninst.exe");
+ if (pos)
+ {
+ // Likely to contain trailing "
+ pos[wcslen(L"uninst.exe")] = L'\0';
+ }
+ }
+ std::error_code ec;
+ std::filesystem::path path(path_buffer);
+ if (!std::filesystem::exists(path, ec))
+ {
+ return false;
+ }
+
+ // Todo: check codesigning?
+
+ return true;
+}
+
static void unregister_protocol_handler(const std::wstring& protocol)
{
std::wstring key_path = L"SOFTWARE\\Classes\\" + protocol;
@@ -206,6 +291,18 @@ static void register_uninstall_info(const std::wstring& install_dir,
RegSetValueExW(hkey, L"DisplayIcon", 0, REG_SZ,
(BYTE*)exe_path.c_str(), (DWORD)((exe_path.size() + 1) * sizeof(wchar_t)));
+ std::wstring link_url = L"https://support.secondlife.com/contact-support/";
+ RegSetValueExW(hkey, L"HelpLink", 0, REG_SZ,
+ (BYTE*)link_url.c_str(), (DWORD)((link_url.size() + 1) * sizeof(wchar_t)));
+
+ link_url = L"https://secondlife.com/whatis/";
+ RegSetValueExW(hkey, L"URLInfoAbout", 0, REG_SZ,
+ (BYTE*)link_url.c_str(), (DWORD)((link_url.size() + 1) * sizeof(wchar_t)));
+
+ link_url = L"http://secondlife.com/support/downloads/";
+ RegSetValueExW(hkey, L"URLUpdateInfo", 0, REG_SZ,
+ (BYTE*)link_url.c_str(), (DWORD)((link_url.size() + 1) * sizeof(wchar_t)));
+
DWORD no_modify = 1;
RegSetValueExW(hkey, L"NoModify", 0, REG_DWORD, (BYTE*)&no_modify, sizeof(DWORD));
RegSetValueExW(hkey, L"NoRepair", 0, REG_DWORD, (BYTE*)&no_modify, sizeof(DWORD));
diff --git a/indra/newview/llvelopack.h b/indra/newview/llvelopack.h
index 430ea9e518..93d1ca4776 100644
--- a/indra/newview/llvelopack.h
+++ b/indra/newview/llvelopack.h
@@ -40,6 +40,10 @@ void velopack_apply_pending_update(bool restart = true);
void velopack_set_update_url(const std::string& url);
void velopack_set_progress_callback(std::function<void(int)> callback);
+#if LL_WINDOWS
+bool get_nsis_uninstaller_path(wchar_t* path_buffer, DWORD bufSize, S32 cur_major_ver, S32 cur_minor_ver, S32 cur_patch_ver, U64 cur_build_ver);
+#endif
+
#endif // LL_VELOPACK
#endif
// EOF
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index d0261a930c..fe77fbb5c5 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -202,6 +202,20 @@ No tutorial is currently available.
<notification
icon="alertmodal.tga"
+ name="PromptRemoveNsisInstallation"
+ type="alertmodal">
+[APP_NAME] found an installation from an older version. Do you want to uninstall the previous version now?
+
+The uninstaller may display additional prompts requesting permission to access or modify files on your disk.
+ <tag>confirm</tag>
+ <usetemplate
+ name="okcancelbuttons"
+ notext="Cancel"
+ yestext="Uninstall"/>
+ </notification>
+
+ <notification
+ icon="alertmodal.tga"
name="LoginFailedNoNetwork"
type="alertmodal">
<tag>fail</tag>