| 1 | Unicode true |
| 2 | |
| 3 | #### |
| 4 | ## Reasonix per-user NSIS installer. |
| 5 | ## |
| 6 | ## This file is COMMITTED and customized (Wails leaves an existing project.nsi |
| 7 | ## untouched and only regenerates wails_tools.nsh). The customizations vs. |
| 8 | ## Wails' default template: |
| 9 | ## |
| 10 | ## 1. REQUEST_EXECUTION_LEVEL "user" + InstallDir under $LOCALAPPDATA - install |
| 11 | ## without administrator rights. This lets the auto-updater re-run a freshly |
| 12 | ## downloaded installer in a visible progress-only mode with no UAC prompt. |
| 13 | ## 2. Uninstall registry under HKCU (not HKLM). Wails' wails.writeUninstaller / |
| 14 | ## wails.deleteUninstaller macros hard-code HKLM, which a non-admin install |
| 15 | ## cannot write - so we inline HKCU versions below instead. |
| 16 | ## 3. InstallDir is remembered across updates via InstallDirRegKey + |
| 17 | ## InstallLocation (HKCU\...\Uninstall\InstallLocation). When upgrading from |
| 18 | ## a build that did not write InstallLocation yet, .onInit falls back to the |
| 19 | ## old DisplayIcon path before using the default. Without this, every release |
| 20 | ## forces the user back to %LOCALAPPDATA%\Programs\Reasonix even if they had |
| 21 | ## moved the install to a different drive (e.g. D:\Tools\Reasonix); the |
| 22 | ## auto-updater would overwrite the wrong dir, leaving the old install |
| 23 | ## orphaned. |
| 24 | ## |
| 25 | ## Everything else mirrors Wails' generated default. Defines below override the |
| 26 | ## ProjectInfo values that wails_tools.nsh would otherwise populate. |
| 27 | #### |
| 28 | |
| 29 | ## Install per-user (no admin). Must be defined BEFORE including wails_tools.nsh, |
| 30 | ## which only sets the "admin" default when REQUEST_EXECUTION_LEVEL is undefined. |
| 31 | !define REQUEST_EXECUTION_LEVEL "user" |
| 32 | |
| 33 | #### |
| 34 | ## Include the wails tools (auto-generated; provides INFO_* defines and the |
| 35 | ## wails.* macros used below). |
| 36 | #### |
| 37 | !include "wails_tools.nsh" |
| 38 | !include "FileFunc.nsh" |
| 39 | !include "LogicLib.nsh" |
| 40 | |
| 41 | # The version information for this two must consist of 4 parts |
| 42 | VIProductVersion "${INFO_PRODUCTVERSION}.0" |
| 43 | VIFileVersion "${INFO_PRODUCTVERSION}.0" |
| 44 | |
| 45 | VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" |
| 46 | VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" |
| 47 | VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" |
| 48 | VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" |
| 49 | VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" |
| 50 | VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" |
| 51 | |
| 52 | # Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware |
| 53 | ManifestDPIAware true |
| 54 | |
| 55 | !include "MUI.nsh" |
| 56 | |
| 57 | !define MUI_ICON "..\icon.ico" |
| 58 | !define MUI_UNICON "..\icon.ico" |
| 59 | # !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 |
| 60 | !define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps |
| 61 | !define MUI_ABORTWARNING # This will warn the user if they exit from the installer. |
| 62 | |
| 63 | !define MUI_PAGE_CUSTOMFUNCTION_PRE reasonix.skipSetupPageForUpdate |
| 64 | !insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. |
| 65 | # !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer |
| 66 | !define MUI_PAGE_CUSTOMFUNCTION_PRE reasonix.skipSetupPageForUpdate |
| 67 | !insertmacro MUI_PAGE_DIRECTORY # In which folder install page. |
| 68 | !define MUI_PAGE_CUSTOMFUNCTION_SHOW reasonix.showUpdateProgress |
| 69 | !insertmacro MUI_PAGE_INSTFILES # Installing page. |
| 70 | !define MUI_PAGE_CUSTOMFUNCTION_PRE reasonix.skipFinishPageForUpdate |
| 71 | !insertmacro MUI_PAGE_FINISH # Finished installation page. |
| 72 | |
| 73 | !insertmacro MUI_UNPAGE_INSTFILES # Uinstalling page |
| 74 | |
| 75 | !insertmacro MUI_LANGUAGE "English" |
| 76 | !insertmacro MUI_LANGUAGE "SimpChinese" |
| 77 | !insertmacro MUI_LANGUAGE "TradChinese" |
| 78 | |
| 79 | LangString reasonixUpdateTitle ${LANG_ENGLISH} "Updating Reasonix" |
| 80 | LangString reasonixUpdateTitle ${LANG_SIMPCHINESE} "正在更新 Reasonix" |
| 81 | LangString reasonixUpdateTitle ${LANG_TRADCHINESE} "正在更新 Reasonix" |
| 82 | LangString reasonixUpdateSubtitle ${LANG_ENGLISH} "Installing the verified update. Reasonix will restart automatically." |
| 83 | LangString reasonixUpdateSubtitle ${LANG_SIMPCHINESE} "正在安装已验证的更新,完成后 Reasonix 将自动重启。" |
| 84 | LangString reasonixUpdateSubtitle ${LANG_TRADCHINESE} "正在安裝已驗證的更新,完成後 Reasonix 將自動重新啟動。" |
| 85 | |
| 86 | ## Preserve the first-pass generated uninstaller so the release workflow can |
| 87 | ## Authenticode-sign it together with the other installed payload files. |
| 88 | ## The second pass provides ARG_REASONIX_SIGNED_UNINSTALLER and embeds that |
| 89 | ## signed binary instead of generating another unsigned uninstaller. |
| 90 | !ifndef ARG_REASONIX_SIGNED_UNINSTALLER |
| 91 | !uninstfinalize 'cmd.exe /C copy /Y "%1" "reasonix-uninstall.exe" >NUL' |
| 92 | !endif |
| 93 | #!finalize 'signtool --file "%1"' |
| 94 | |
| 95 | Name "${INFO_PRODUCTNAME}" |
| 96 | OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. |
| 97 | !define REASONIX_DEFAULT_INSTALLDIR "$LOCALAPPDATA\Programs\${INFO_PRODUCTNAME}" |
| 98 | !define REASONIX_UPDATE_HELPER "reasonix-update-helper.exe" |
| 99 | !define REASONIX_GUARD "reasonix-guard.exe" |
| 100 | !define REASONIX_LAUNCHER "reasonix-launcher.exe" |
| 101 | !define REASONIX_CLI "reasonix-cli.exe" |
| 102 | !define REASONIX_PORTABLE_ENTRY "Reasonix.exe" |
| 103 | !define REASONIX_LAYOUT_INSTALLER "reasonix-layout-installer.exe" |
| 104 | !define REASONIX_PAYLOAD_MANIFEST "reasonix-payload.json" |
| 105 | !define REASONIX_PAYLOAD_SIGNATURE "reasonix-payload.json.minisig" |
| 106 | !define REASONIX_LEGACY_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\Reasonix" |
| 107 | !define REASONIX_LEGACY_PRODUCT_KEY "Software\reasonix\Reasonix" |
| 108 | !define REASONIX_UNLOCK_RETRIES 60 |
| 109 | Var ReasonixUpdateMode |
| 110 | Var ReasonixStageMode |
| 111 | InstallDirRegKey HKCU "${UNINST_KEY}" "InstallLocation" # Reuse the previous install path on update; .onInit falls back to the default on first install. |
| 112 | InstallDir "${REASONIX_DEFAULT_INSTALLDIR}" # Per-user install location (no admin rights required). |
| 113 | ShowInstDetails show # This will always show the installation details. |
| 114 | |
| 115 | #### |
| 116 | ## Per-user uninstaller registry (HKCU). Replaces wails.writeUninstaller / |
| 117 | ## wails.deleteUninstaller, which write HKLM and would fail without admin rights. |
| 118 | #### |
| 119 | !macro reasonix.writeUninstaller |
| 120 | !ifdef ARG_REASONIX_SIGNED_UNINSTALLER |
| 121 | File "/oname=uninstall.exe" "${ARG_REASONIX_SIGNED_UNINSTALLER}" |
| 122 | !else |
| 123 | WriteUninstaller "$INSTDIR\uninstall.exe" |
| 124 | !endif |
| 125 | |
| 126 | WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" |
| 127 | WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" |
| 128 | WriteRegStr HKCU "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" |
| 129 | !if /FileExists "${REASONIX_LAUNCHER}" |
| 130 | WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${REASONIX_LAUNCHER}" |
| 131 | !else |
| 132 | WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" |
| 133 | !endif |
| 134 | WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" |
| 135 | WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" |
| 136 | # Persist the resolved install path so a subsequent update picks it up |
| 137 | # via InstallDirRegKey above. Without this, every release would force the |
| 138 | # user back to %LOCALAPPDATA%\Programs\Reasonix even if they had moved |
| 139 | # the install to a different drive (e.g. D:\Tools\Reasonix). The auto- |
| 140 | # updater trusts this persisted path, so it has to be present before the |
| 141 | # visible progress-only re-install. |
| 142 | WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR" |
| 143 | |
| 144 | ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 |
| 145 | IntFmt $0 "0x%08X" $0 |
| 146 | WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0" |
| 147 | !macroend |
| 148 | |
| 149 | ; Tauri 0.53 separately persisted $INSTDIR under its manufacturer/product key |
| 150 | ; and restores that value before every later install. Clear only a same-root |
| 151 | ; value so re-running 0.53 cannot overwrite the current uninstaller; preserve a |
| 152 | ; genuinely separate legacy installation. If this cleanup fails, retain the old |
| 153 | ; uninstall alias so a later update can retry the migration. |
| 154 | !macro reasonix.deleteLegacyInstallerStateIfOwned |
| 155 | StrCpy $1 "1" |
| 156 | ClearErrors |
| 157 | ReadRegStr $0 HKCU "${REASONIX_LEGACY_PRODUCT_KEY}" "" |
| 158 | ${If} $0 == "$INSTDIR" |
| 159 | ClearErrors |
| 160 | DeleteRegValue HKCU "${REASONIX_LEGACY_PRODUCT_KEY}" "" |
| 161 | ${If} ${Errors} |
| 162 | StrCpy $1 "0" |
| 163 | ${EndIf} |
| 164 | ${ElseIf} $0 == "$\"$INSTDIR$\"" |
| 165 | ClearErrors |
| 166 | DeleteRegValue HKCU "${REASONIX_LEGACY_PRODUCT_KEY}" "" |
| 167 | ${If} ${Errors} |
| 168 | StrCpy $1 "0" |
| 169 | ${EndIf} |
| 170 | ${EndIf} |
| 171 | |
| 172 | ${If} $1 == "1" |
| 173 | ClearErrors |
| 174 | ReadRegStr $0 HKCU "${REASONIX_LEGACY_UNINST_KEY}" "InstallLocation" |
| 175 | ${If} $0 == "$INSTDIR" |
| 176 | DeleteRegKey HKCU "${REASONIX_LEGACY_UNINST_KEY}" |
| 177 | ${ElseIf} $0 == "$\"$INSTDIR$\"" |
| 178 | DeleteRegKey HKCU "${REASONIX_LEGACY_UNINST_KEY}" |
| 179 | ${Else} |
| 180 | ClearErrors |
| 181 | ReadRegStr $0 HKCU "${REASONIX_LEGACY_UNINST_KEY}" "UninstallString" |
| 182 | ${If} $0 == "$INSTDIR\uninstall.exe" |
| 183 | DeleteRegKey HKCU "${REASONIX_LEGACY_UNINST_KEY}" |
| 184 | ${ElseIf} $0 == "$\"$INSTDIR\uninstall.exe$\"" |
| 185 | DeleteRegKey HKCU "${REASONIX_LEGACY_UNINST_KEY}" |
| 186 | ${EndIf} |
| 187 | ${EndIf} |
| 188 | ${EndIf} |
| 189 | !macroend |
| 190 | |
| 191 | !macro reasonix.deleteUninstaller |
| 192 | Delete "$INSTDIR\uninstall.exe" |
| 193 | DeleteRegKey HKCU "${UNINST_KEY}" |
| 194 | !macroend |
| 195 | |
| 196 | Function .onInit |
| 197 | !insertmacro wails.checkArchitecture |
| 198 | |
| 199 | ; The helper passes /REASONIXUPDATE=1 and a final /D=<current directory>. |
| 200 | ; This mode remains visible but skips every page that could change the |
| 201 | ; destination, then closes automatically after the file copy so the helper |
| 202 | ; can relaunch Reasonix. A normal manual installer keeps the full wizard. |
| 203 | StrCpy $ReasonixUpdateMode "0" |
| 204 | StrCpy $ReasonixStageMode "0" |
| 205 | ${GetParameters} $R0 |
| 206 | ClearErrors |
| 207 | ${GetOptions} $R0 "/REASONIXUPDATE=" $R1 |
| 208 | IfErrors reasonix_update_mode_done |
| 209 | StrCmp $R1 "1" 0 reasonix_update_mode_done |
| 210 | StrCpy $ReasonixUpdateMode "1" |
| 211 | |
| 212 | reasonix_update_mode_done: |
| 213 | ClearErrors |
| 214 | ${GetOptions} $R0 "/REASONIXSTAGE=" $R2 |
| 215 | IfErrors reasonix_stage_mode_done |
| 216 | StrCmp $R2 "1" 0 reasonix_stage_mode_done |
| 217 | StrCpy $ReasonixStageMode "1" |
| 218 | |
| 219 | reasonix_stage_mode_done: |
| 220 | |
| 221 | ; InstallDirRegKey leaves $INSTDIR empty when the InstallLocation value is |
| 222 | ; missing. Older installers still wrote DisplayIcon, so use its parent folder |
| 223 | ; as a compatibility bridge before falling back to the per-user default. |
| 224 | StrCmp $INSTDIR "" 0 done |
| 225 | ClearErrors |
| 226 | ReadRegStr $0 HKCU "${UNINST_KEY}" "DisplayIcon" |
| 227 | IfErrors legacy_location |
| 228 | StrCmp $0 "" legacy_location |
| 229 | ${GetParent} "$0" $INSTDIR |
| 230 | StrCmp $INSTDIR "" legacy_location done |
| 231 | |
| 232 | legacy_location: |
| 233 | ; Tauri 0.53 used a different uninstall key and may have stored the selected |
| 234 | ; directory with surrounding quotes (for example "D:\Reasonix"). Reuse it |
| 235 | ; only while its uninstaller still exists so a stale registry value cannot |
| 236 | ; redirect the repair installer into an unrelated directory. |
| 237 | ClearErrors |
| 238 | ReadRegStr $0 HKCU "${REASONIX_LEGACY_UNINST_KEY}" "InstallLocation" |
| 239 | IfErrors legacy_uninstaller |
| 240 | StrCmp $0 "" legacy_uninstaller |
| 241 | StrCpy $1 $0 1 |
| 242 | StrCmp $1 "$\"" 0 legacy_location_ready |
| 243 | StrCpy $1 $0 1 -1 |
| 244 | StrCmp $1 "$\"" 0 legacy_location_ready |
| 245 | StrCpy $0 $0 -1 1 |
| 246 | |
| 247 | legacy_location_ready: |
| 248 | IfFileExists "$0\uninstall.exe" 0 legacy_uninstaller |
| 249 | StrCpy $INSTDIR $0 |
| 250 | Goto done |
| 251 | |
| 252 | legacy_uninstaller: |
| 253 | ClearErrors |
| 254 | ReadRegStr $0 HKCU "${REASONIX_LEGACY_UNINST_KEY}" "UninstallString" |
| 255 | IfErrors fallback |
| 256 | StrCmp $0 "" fallback |
| 257 | StrCpy $1 $0 1 |
| 258 | StrCmp $1 "$\"" 0 legacy_uninstaller_ready |
| 259 | StrCpy $1 $0 1 -1 |
| 260 | StrCmp $1 "$\"" 0 legacy_uninstaller_ready |
| 261 | StrCpy $0 $0 -1 1 |
| 262 | |
| 263 | legacy_uninstaller_ready: |
| 264 | IfFileExists "$0" 0 fallback |
| 265 | ${GetParent} "$0" $INSTDIR |
| 266 | StrCmp $INSTDIR "" fallback done |
| 267 | |
| 268 | fallback: |
| 269 | StrCpy $INSTDIR "${REASONIX_DEFAULT_INSTALLDIR}" |
| 270 | done: |
| 271 | FunctionEnd |
| 272 | |
| 273 | Function reasonix.skipSetupPageForUpdate |
| 274 | StrCmp $ReasonixUpdateMode "1" 0 reasonix_show_setup_page |
| 275 | Abort |
| 276 | |
| 277 | reasonix_show_setup_page: |
| 278 | FunctionEnd |
| 279 | |
| 280 | Function reasonix.showUpdateProgress |
| 281 | StrCmp $ReasonixUpdateMode "1" 0 reasonix_update_progress_done |
| 282 | !insertmacro MUI_HEADER_TEXT "$(reasonixUpdateTitle)" "$(reasonixUpdateSubtitle)" |
| 283 | SetDetailsView hide |
| 284 | SetAutoClose true |
| 285 | BringToFront |
| 286 | |
| 287 | reasonix_update_progress_done: |
| 288 | FunctionEnd |
| 289 | |
| 290 | Function reasonix.skipFinishPageForUpdate |
| 291 | StrCmp $ReasonixUpdateMode "1" 0 reasonix_show_finish_page |
| 292 | Abort |
| 293 | |
| 294 | reasonix_show_finish_page: |
| 295 | FunctionEnd |
| 296 | |
| 297 | Function reasonix.waitForExecutableUnlock |
| 298 | StrCpy $0 0 |
| 299 | |
| 300 | retry: |
| 301 | IfFileExists "$INSTDIR\${PRODUCT_EXECUTABLE}" 0 check_versioned_target |
| 302 | ClearErrors |
| 303 | FileOpen $1 "$INSTDIR\${PRODUCT_EXECUTABLE}" a |
| 304 | IfErrors locked |
| 305 | FileClose $1 |
| 306 | |
| 307 | check_versioned_target: |
| 308 | ; A same-version recovery install replaces this directory transactionally. |
| 309 | ; Detect the running active binary before asking the Go activator to rename it. |
| 310 | IfFileExists "$INSTDIR\versions\v${INFO_PRODUCTVERSION}\${PRODUCT_EXECUTABLE}" 0 check_guard |
| 311 | ClearErrors |
| 312 | FileOpen $1 "$INSTDIR\versions\v${INFO_PRODUCTVERSION}\${PRODUCT_EXECUTABLE}" a |
| 313 | IfErrors locked |
| 314 | FileClose $1 |
| 315 | |
| 316 | check_guard: |
| 317 | IfFileExists "$INSTDIR\${REASONIX_GUARD}" 0 check_launcher |
| 318 | ClearErrors |
| 319 | FileOpen $1 "$INSTDIR\${REASONIX_GUARD}" a |
| 320 | IfErrors locked |
| 321 | FileClose $1 |
| 322 | |
| 323 | check_launcher: |
| 324 | IfFileExists "$INSTDIR\${REASONIX_LAUNCHER}" 0 check_cli |
| 325 | ClearErrors |
| 326 | FileOpen $1 "$INSTDIR\${REASONIX_LAUNCHER}" a |
| 327 | IfErrors locked |
| 328 | FileClose $1 |
| 329 | |
| 330 | check_cli: |
| 331 | IfFileExists "$INSTDIR\${REASONIX_CLI}" 0 check_portable_entry |
| 332 | ClearErrors |
| 333 | FileOpen $1 "$INSTDIR\${REASONIX_CLI}" a |
| 334 | IfErrors locked |
| 335 | FileClose $1 |
| 336 | |
| 337 | check_portable_entry: |
| 338 | IfFileExists "$INSTDIR\${REASONIX_PORTABLE_ENTRY}" 0 done |
| 339 | ClearErrors |
| 340 | FileOpen $1 "$INSTDIR\${REASONIX_PORTABLE_ENTRY}" a |
| 341 | IfErrors locked |
| 342 | FileClose $1 |
| 343 | Goto done |
| 344 | |
| 345 | locked: |
| 346 | IntOp $0 $0 + 1 |
| 347 | IntCmp $0 ${REASONIX_UNLOCK_RETRIES} failed 0 0 |
| 348 | Sleep 1000 |
| 349 | Goto retry |
| 350 | |
| 351 | failed: |
| 352 | IfSilent silent interactive |
| 353 | |
| 354 | interactive: |
| 355 | MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION "Reasonix is still running. Close Reasonix, then click Retry to continue the installation." IDRETRY retry IDCANCEL abort |
| 356 | Goto retry |
| 357 | |
| 358 | silent: |
| 359 | SetErrorLevel 1618 |
| 360 | |
| 361 | abort: |
| 362 | Abort "Reasonix is still running. Close Reasonix and run the installer again." |
| 363 | |
| 364 | done: |
| 365 | FunctionEnd |
| 366 | |
| 367 | Section |
| 368 | !insertmacro wails.setShellContext |
| 369 | |
| 370 | ; /REASONIXSTAGE=1: flat six-member payload for 1.18–1.19.1 helpers (and |
| 371 | ; the new helper's staging extract). Do not write shortcuts/uninstaller. |
| 372 | ; Normal install: versioned-v1 layout under versions/v${INFO_PRODUCTVERSION}/ |
| 373 | ; with a permanent thin launcher at InstallRoot. Guard is only present in |
| 374 | ; STAGE payloads (as the one-shot legacy migrator) and is not persisted on |
| 375 | ; a normal install. |
| 376 | StrCmp $ReasonixStageMode "1" reasonix_stage_payload |
| 377 | !insertmacro wails.webview2runtime |
| 378 | Call reasonix.waitForExecutableUnlock |
| 379 | Goto reasonix_normal_install |
| 380 | |
| 381 | reasonix_stage_payload: |
| 382 | SetOutPath $INSTDIR |
| 383 | !if /FileExists "${REASONIX_PAYLOAD_MANIFEST}" |
| 384 | File "/oname=${REASONIX_PAYLOAD_MANIFEST}" "${REASONIX_PAYLOAD_MANIFEST}" |
| 385 | !endif |
| 386 | !if /FileExists "${REASONIX_PAYLOAD_SIGNATURE}" |
| 387 | File "/oname=${REASONIX_PAYLOAD_SIGNATURE}" "${REASONIX_PAYLOAD_SIGNATURE}" |
| 388 | !endif |
| 389 | !insertmacro wails.files |
| 390 | !if /FileExists "${REASONIX_UPDATE_HELPER}" |
| 391 | File "/oname=${REASONIX_UPDATE_HELPER}" "${REASONIX_UPDATE_HELPER}" |
| 392 | !endif |
| 393 | !if /FileExists "${REASONIX_GUARD}" |
| 394 | File "/oname=${REASONIX_GUARD}" "${REASONIX_GUARD}" |
| 395 | !endif |
| 396 | !if /FileExists "${REASONIX_LAUNCHER}" |
| 397 | File "/oname=${REASONIX_LAUNCHER}" "${REASONIX_LAUNCHER}" |
| 398 | !endif |
| 399 | !if /FileExists "${REASONIX_CLI}" |
| 400 | File "/oname=${REASONIX_CLI}" "${REASONIX_CLI}" |
| 401 | !endif |
| 402 | Goto reasonix_section_done |
| 403 | |
| 404 | reasonix_normal_install: |
| 405 | ; Extract into an install-local temporary directory, then let the signed Go |
| 406 | ; activator validate the complete release unit, transactionally publish the |
| 407 | ; version/root entries, and strictly atomically replace current.json last. |
| 408 | ; The normal/recovery installer therefore shares the same commit protocol as |
| 409 | ; automatic updates instead of writing live files or current.json in place. |
| 410 | System::Call 'kernel32::GetCurrentProcessId() i .R8' |
| 411 | CreateDirectory "$INSTDIR\versions" |
| 412 | StrCpy $R9 "$INSTDIR\versions\.installer-v${INFO_PRODUCTVERSION}-$R8" |
| 413 | RMDir /r "$R9" |
| 414 | CreateDirectory "$R9" |
| 415 | SetOutPath "$R9" |
| 416 | !insertmacro wails.files |
| 417 | !if /FileExists "${REASONIX_UPDATE_HELPER}" |
| 418 | File "/oname=${REASONIX_UPDATE_HELPER}" "${REASONIX_UPDATE_HELPER}" |
| 419 | !else |
| 420 | !warning "${REASONIX_UPDATE_HELPER} was not found; Windows auto-update will fail safely until the helper is installed." |
| 421 | !endif |
| 422 | !if /FileExists "${REASONIX_CLI}" |
| 423 | File "/oname=${REASONIX_CLI}" "${REASONIX_CLI}" |
| 424 | !else |
| 425 | !warning "${REASONIX_CLI} was not found; remote upload installation will be unavailable." |
| 426 | !endif |
| 427 | !if /FileExists "${REASONIX_LAUNCHER}" |
| 428 | File "/oname=${REASONIX_LAUNCHER}" "${REASONIX_LAUNCHER}" |
| 429 | !endif |
| 430 | |
| 431 | SetOutPath "$PLUGINSDIR" |
| 432 | !if /FileExists "${REASONIX_GUARD}" |
| 433 | File "/oname=${REASONIX_LAYOUT_INSTALLER}" "${REASONIX_GUARD}" |
| 434 | !else |
| 435 | !error "${REASONIX_GUARD} was not found; normal installs require the signed layout activator." |
| 436 | !endif |
| 437 | DetailPrint "Reasonix layout activator output:" |
| 438 | nsExec::ExecToLog /OEM '"$PLUGINSDIR\${REASONIX_LAYOUT_INSTALLER}" --install-root "$INSTDIR" --version "v${INFO_PRODUCTVERSION}" --activate-staging "$R9" --no-relaunch' |
| 439 | Pop $0 |
| 440 | StrCmp $0 "0" reasonix_layout_activated |
| 441 | DetailPrint "Reasonix layout activation failed with exit code $0; the previous version remains active." |
| 442 | RMDir /r "$R9" |
| 443 | SetErrorLevel 1 |
| 444 | Abort "Reasonix could not activate the verified release. The previous version was left unchanged." |
| 445 | |
| 446 | reasonix_layout_activated: |
| 447 | RMDir /r "$R9" |
| 448 | SetOutPath "$INSTDIR" |
| 449 | |
| 450 | ; Remove flat leftovers from prior 1.18–1.19 installs when overwriting. |
| 451 | Delete "$INSTDIR\${PRODUCT_EXECUTABLE}" |
| 452 | Delete "$INSTDIR\${REASONIX_GUARD}" |
| 453 | Delete "$INSTDIR\${REASONIX_UPDATE_HELPER}" |
| 454 | |
| 455 | !if /FileExists "${REASONIX_LAUNCHER}" |
| 456 | ; Keep both target and icon on the stable launcher. Pointing IconLocation at |
| 457 | ; versions\vX\reasonix-desktop.exe leaves a blank shortcut as soon as version |
| 458 | ; retention removes that directory after a later update. |
| 459 | CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${REASONIX_LAUNCHER}" "" "$INSTDIR\${REASONIX_LAUNCHER}" 0 |
| 460 | CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${REASONIX_LAUNCHER}" "" "$INSTDIR\${REASONIX_LAUNCHER}" 0 |
| 461 | !else |
| 462 | CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\versions\v${INFO_PRODUCTVERSION}\${PRODUCT_EXECUTABLE}" |
| 463 | CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\versions\v${INFO_PRODUCTVERSION}\${PRODUCT_EXECUTABLE}" |
| 464 | !endif |
| 465 | |
| 466 | !insertmacro wails.associateFiles |
| 467 | !insertmacro wails.associateCustomProtocols |
| 468 | !insertmacro reasonix.writeUninstaller |
| 469 | !insertmacro reasonix.deleteLegacyInstallerStateIfOwned |
| 470 | |
| 471 | reasonix_section_done: |
| 472 | SectionEnd |
| 473 | |
| 474 | Section "uninstall" |
| 475 | !insertmacro wails.setShellContext |
| 476 | |
| 477 | RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath |
| 478 | |
| 479 | ; Precision uninstall: flat leftovers, thin entry points, and version trees. |
| 480 | Delete "$INSTDIR\${PRODUCT_EXECUTABLE}" |
| 481 | Delete "$INSTDIR\${REASONIX_UPDATE_HELPER}" |
| 482 | Delete "$INSTDIR\${REASONIX_GUARD}" |
| 483 | Delete "$INSTDIR\${REASONIX_LAUNCHER}" |
| 484 | Delete "$INSTDIR\${REASONIX_CLI}" |
| 485 | Delete "$INSTDIR\${REASONIX_PORTABLE_ENTRY}" |
| 486 | Delete "$INSTDIR\current.json" |
| 487 | RMDir /r "$INSTDIR\versions" |
| 488 | |
| 489 | Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" |
| 490 | Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" |
| 491 | |
| 492 | !insertmacro wails.unassociateFiles |
| 493 | !insertmacro wails.unassociateCustomProtocols |
| 494 | |
| 495 | !insertmacro reasonix.deleteUninstaller |
| 496 | !insertmacro reasonix.deleteLegacyInstallerStateIfOwned |
| 497 | |
| 498 | ; Only remove the installation directory if it is empty to prevent data loss |
| 499 | RMDir $INSTDIR |
| 500 | SectionEnd |
| 501 |