diff options
| author | Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> | 2026-02-10 22:29:52 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-10 15:29:52 -0500 |
| commit | a6b978b3e00f2e7e186b97d8a4997374adecdafc (patch) | |
| tree | 82cefb540d98ff60bc9c8955817f395f66bc2fe4 | |
| parent | 85298ebb341c6038dba92bc21311566ff5e86708 (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.txt | 1 | ||||
| -rw-r--r-- | indra/newview/app_settings/settings.xml | 12 | ||||
| -rw-r--r-- | indra/newview/installers/windows/installer_template.nsi | 33 | ||||
| -rw-r--r-- | indra/newview/llstartup.cpp | 76 | ||||
| -rw-r--r-- | indra/newview/llvelopack.cpp | 97 | ||||
| -rw-r--r-- | indra/newview/llvelopack.h | 4 | ||||
| -rw-r--r-- | indra/newview/skins/default/xui/en/notifications.xml | 14 |
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> |
