mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 09:33:25 -08:00
Pure refactor, no behavior change. cmake/NSIS.template.in is the CPack template, and CPack substitutes the @...@ placeholders in that file only, so everything without a substitution moves into cmake/nsis/ and is included from it: installer-log.nsh the CockLog macro and the two writers behind it process-guard.nsh running instance handling, installer and uninstaller copy user-data.nsh backup/restore around the previous version's uninstaller runtime-purge.nsh stale runtime purge, diagnostics and the holder probe The page and section order, the Vars the included files share, .onInit (it names the package version in the log), AbortInstall (its Sentry offer is substituted), componentsPagePre and the registry bookkeeping stay in the template. The includes resolve through NSIS_SOURCE_PATH, which the template already uses for the bitmaps, and an include does not change the compile directory, so the holder probe keeps its relative File path.
1048 lines
46 KiB
Text
1048 lines
46 KiB
Text
; Cockatrice NSIS installer.
|
|
;
|
|
; This file is the CPack template. CPack substitutes the @...@ placeholders in
|
|
; this file and in no other, so everything that has no substitution in it lives
|
|
; in cmake/nsis/ and is included from below:
|
|
;
|
|
; installer-log.nsh the CockLog macros and the writers behind them
|
|
; process-guard.nsh running instance handling, installer and uninstaller copy
|
|
; user-data.nsh backup/restore around the previous version's uninstaller
|
|
; runtime-purge.nsh stale runtime purge, diagnostics and the holder probe
|
|
;
|
|
; What stays here is the page and section order, the Vars those files share,
|
|
; .onInit (it names the package version in the log), AbortInstall (its Sentry
|
|
; offer is substituted), componentsPagePre and the registry bookkeeping.
|
|
!include ..\..\..\NSIS.definitions.nsh
|
|
Name "@CPACK_PACKAGE_NAME@"
|
|
BrandingText "@CPACK_PACKAGE_FILE_NAME@"
|
|
OutFile "@CPACK_TOPLEVEL_DIRECTORY@/@CPACK_OUTPUT_FILE_NAME@"
|
|
|
|
!define INST_DIR "@CPACK_TEMPORARY_DIRECTORY@"
|
|
|
|
RequestExecutionlevel admin
|
|
SetCompressor LZMA
|
|
|
|
Var NormalDestDir
|
|
Var PortableDestDir
|
|
Var PortableMode
|
|
Var ReinstallMode
|
|
Var GuardDir
|
|
Var OldUninstallString
|
|
Var OldInstallDir
|
|
; What ExecWait reported for the previous version's uninstaller, or "missing" /
|
|
; "launch-failed" when RunOldUninstaller could not start it. Read by WaitForOldPayloadToDisappear: an uninstaller that ran in place
|
|
; (_?=) and failed or was cancelled will not remove anything more, however long we wait.
|
|
Var OldUninstallExit
|
|
; Executable path parsed out of $OldUninstallString by ResolveOldUninstallExe. Used to
|
|
; tell an uninstaller that is missing or cannot be started ("missing" / "launch-failed"
|
|
; in $OldUninstallExit) from one that ran and failed.
|
|
Var OldUninstallExe
|
|
; Set to 1 by RemoveOldPayloadLeftovers only when every leftover it is responsible for is
|
|
; gone; WaitForOldPayloadToDisappear aborts while it is still 0.
|
|
Var OldPayloadRemoved
|
|
; Scratch state of AppendUninstallerLog.
|
|
Var UninstLogHandle
|
|
Var UninstLogLine
|
|
Var UninstLogChar
|
|
Var DataBackupDir
|
|
Var DataBackupDone
|
|
Var DataBackupForDir
|
|
Var LooseDecksBackupDone
|
|
Var InstallSubdirsBackupDone
|
|
Var LooseDeckNoticeShown
|
|
Var StaleFindHandle
|
|
Var StaleFindName
|
|
Var DataBackupFindHandle
|
|
Var DataBackupFindName
|
|
Var IsUpgrade
|
|
; Set together by whichever purge block ran: the verdict RetryDllPurge measured
|
|
; and whether a purge ran at all - a portable install into a directory without
|
|
; portable.dat is neither purge block, and the abort below measures it itself.
|
|
Var StaleRuntimeAfterPurge
|
|
Var DllPurgeRan
|
|
Var AbortReason
|
|
; Diagnostics for a failed purge: the holder probe in LogProcessesUsingInstallDir writes
|
|
; $HolderOutput (one log line) and sets $HolderFound when it named anything.
|
|
Var HolderExit
|
|
Var HolderOutput
|
|
Var HolderFound
|
|
; What the last RunHolderProbe call answered: its exit code and its line. Kept
|
|
; apart from $HolderExit/$HolderOutput because the guard only wants to know
|
|
; whether the payload is free, while the abort dialog reports what survived.
|
|
Var GuardExit
|
|
Var GuardOutput
|
|
; How many times the guard has asked the payload programs to close by itself. Bounded, and
|
|
; only repeated while the listing changed between rounds (see GuardAppsNotRunning).
|
|
Var GuardCloseRounds
|
|
; The listing the last automatic close was sent against, so a second round is spent only
|
|
; when something actually exited and the rest is worth asking again. Empty before round one.
|
|
Var GuardLastHolders
|
|
; How many times the user asked to check again after closing programs by hand (logged only).
|
|
Var GuardRecheckCount
|
|
; The question CockEndPrompt/CockEndRecheckPrompt assemble: fixed sentences around the
|
|
; probe's line, and the result has to stay inside the characters one NSIS string holds.
|
|
Var GuardPrompt
|
|
Var WaitPid
|
|
Var LauncherExited
|
|
; How long one WaitForSingleObject on the launcher's handle waits before asking the user
|
|
; (see WaitForLauncherToExit). A variable because the first slice is short and the ones
|
|
; after the first question are not.
|
|
Var WaitSlice
|
|
; Whether that handle carries PROCESS_TERMINATE as well as SYNCHRONIZE, and can therefore
|
|
; be ended from the answer the dialog offers. Cleared when only the synchronize right could
|
|
; be had (see WaitForLauncherToExit).
|
|
Var WaitCanEnd
|
|
; How far the launcher wait got with a launcher that has no window to ask: 0 before the
|
|
; first round, 1 after the grace it was given, 2 after it was ended. A launcher whose
|
|
; window is gone has answered its own close and is stuck in shutdown, so the dialog cannot
|
|
; reach it and the wait may end it instead of asking the user (see WaitForLauncherToExit).
|
|
Var WaitWindowless
|
|
Var InstallLogPath
|
|
Var LogHandle
|
|
Var LogMessage
|
|
Var LogFindHandle
|
|
Var LogFindName
|
|
Var LogMatchLabel
|
|
Var LogMatchPattern
|
|
; Sentry installer-log hook state (see AbortInstall)
|
|
Var SentryResult
|
|
Var SentryOutput
|
|
|
|
!include LogicLib.nsh
|
|
!include FileFunc.nsh
|
|
!include MUI2.nsh
|
|
!include x64.nsh
|
|
|
|
!define MUI_ABORTWARNING
|
|
!define MUI_WELCOMEFINISHPAGE_BITMAP "${NSIS_SOURCE_PATH}\cmake\leftimage.bmp"
|
|
!define MUI_UNWELCOMEFINISHPAGE_BITMAP "${NSIS_SOURCE_PATH}\cmake\leftimage.bmp"
|
|
!define MUI_HEADERIMAGE
|
|
!define MUI_HEADERIMAGE_BITMAP "${NSIS_SOURCE_PATH}\cmake\headerimage.bmp"
|
|
!define MUI_HEADERIMAGE_UNBITMAP "${NSIS_SOURCE_PATH}\cmake\headerimage.bmp"
|
|
!define MUI_WELCOMEPAGE_TEXT "This wizard will guide you through the installation of Cockatrice.$\r$\n$\r$\nClick Next to continue."
|
|
!define MUI_FINISHPAGE_RUN "$INSTDIR/cockatrice.exe"
|
|
!define MUI_FINISHPAGE_RUN_TEXT "Run 'Cockatrice' now"
|
|
!define MUI_ICON "${NSIS_SOURCE_PATH}\cockatrice\resources\appicon.ico"
|
|
|
|
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
|
|
!insertmacro MUI_PAGE_WELCOME
|
|
|
|
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
|
|
!insertmacro MUI_PAGE_LICENSE "${NSIS_SOURCE_PATH}\LICENSE"
|
|
|
|
Page Custom PortableModePageCreate PortableModePageLeave
|
|
!define MUI_PAGE_CUSTOMFUNCTION_PRE componentsPagePre
|
|
!insertmacro MUI_PAGE_COMPONENTS
|
|
|
|
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
|
|
!insertmacro MUI_PAGE_DIRECTORY
|
|
|
|
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
|
|
!insertmacro MUI_PAGE_INSTFILES
|
|
|
|
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
|
|
!insertmacro MUI_PAGE_FINISH
|
|
|
|
!insertmacro MUI_UNPAGE_CONFIRM
|
|
!insertmacro MUI_UNPAGE_COMPONENTS
|
|
!insertmacro MUI_UNPAGE_INSTFILES
|
|
!insertmacro MUI_UNPAGE_FINISH
|
|
|
|
!insertmacro MUI_LANGUAGE English
|
|
|
|
; Installer log helpers (CockLog and friends).
|
|
!include "${NSIS_SOURCE_PATH}\cmake\nsis\installer-log.nsh"
|
|
|
|
Function .onInit
|
|
|
|
StrCpy $InstallLogPath "$TEMP\Cockatrice-installer.log"
|
|
; No backup has been taken yet; RestoreUserDataAfterUninstall must be a no-op
|
|
; until BackupUserDataBeforeUninstall sets this after actually copying something.
|
|
StrCpy $DataBackupDir ""
|
|
StrCpy $DataBackupDone ""
|
|
StrCpy $DataBackupForDir ""
|
|
StrCpy $LooseDecksBackupDone ""
|
|
StrCpy $InstallSubdirsBackupDone ""
|
|
StrCpy $LooseDeckNoticeShown ""
|
|
; No old install location resolved yet; Backup/Restore fall back to $INSTDIR
|
|
; when this is empty (the /R path where $INSTDIR already is the old dir).
|
|
StrCpy $OldInstallDir ""
|
|
StrCpy $OldUninstallExit ""
|
|
; No purge has run and nothing has been measured yet. The section sets both
|
|
; after a purge, or measures the directory itself when neither purge applies.
|
|
StrCpy $StaleRuntimeAfterPurge 0
|
|
StrCpy $DllPurgeRan 0
|
|
; Start each run from a clean file so the log that gets sent back is the last
|
|
; run, not every past run glued together.
|
|
FileOpen $LogHandle "$InstallLogPath" w
|
|
FileClose $LogHandle
|
|
!insertmacro CockLog "=================================================================="
|
|
!insertmacro CockLog "installer start: @CPACK_PACKAGE_NAME@ @CPACK_PACKAGE_VERSION@"
|
|
|
|
${If} ${NSIS_IS_64_BIT} == 1 #NSIS 64bit
|
|
${IfNot} ${RunningX64}
|
|
MessageBox MB_OK|MB_ICONSTOP "This version of Cockatrice requires a 64-bit Windows system."
|
|
Abort
|
|
${EndIf}
|
|
StrCpy $NormalDestDir "$ProgramFiles64\Cockatrice"
|
|
SetRegView 64
|
|
${Else} #NSIS 32bit
|
|
${If} ${RunningX64}
|
|
MessageBox MB_OK|MB_ICONEXCLAMATION \
|
|
"You are about to install a 32-bit version of Cockatrice on a 64-bit Windows system.$\n\
|
|
We advise you to use the correct 64-bit installer instead to get around potential issues.$\n$\n\
|
|
Download from our webpage: https://cockatrice.github.io"
|
|
${EndIf}
|
|
StrCpy $NormalDestDir "$ProgramFiles\Cockatrice"
|
|
${EndIf}
|
|
|
|
StrCpy $PortableDestDir "$Desktop\CockatricePortable"
|
|
|
|
${GetParameters} $9
|
|
|
|
ClearErrors
|
|
${GetOptions} $9 "/?" $8
|
|
${IfNot} ${Errors}
|
|
MessageBox MB_ICONINFORMATION|MB_SETFOREGROUND "\
|
|
/PORTABLE : Install in portable mode$\n\
|
|
/S : Silent install$\n\
|
|
/R : Silent upgrade$\n\
|
|
/PID=%pid% : Process to wait for before replacing files (used by the in-app updater)$\n\
|
|
/D=%directory% : Specify destination directory$\n"
|
|
Quit
|
|
${EndIf}
|
|
|
|
ClearErrors
|
|
${GetOptions} $9 "/PORTABLE" $8
|
|
${IfNot} ${Errors}
|
|
StrCpy $PortableMode 1
|
|
StrCpy $0 $PortableDestDir
|
|
${Else}
|
|
StrCpy $PortableMode 0
|
|
StrCpy $0 $NormalDestDir
|
|
${If} ${Silent}
|
|
Call RequireAdmin
|
|
${EndIf}
|
|
${EndIf}
|
|
|
|
ClearErrors
|
|
${GetOptions} $9 "/R" $8
|
|
${IfNot} ${Errors}
|
|
StrCpy $ReinstallMode 1
|
|
SetSilent silent
|
|
SetAutoClose true
|
|
${Else}
|
|
StrCpy $ReinstallMode 0
|
|
${EndIf}
|
|
|
|
; /PID= carries the process id of the application that started this installer.
|
|
; The in-app updater launches the installer and only then quits itself, so
|
|
; without it the installer has to guess whether the launching process is still
|
|
; alive from the files it still has open - a guess that can come back "nothing
|
|
; is holding them" while the client is very much alive and still has its Qt DLLs
|
|
; mapped. An older client does not pass /PID= and an older installer ignores it,
|
|
; so both sides of the pair have to keep working.
|
|
; Note that /D= must stay the last parameter on the command line, which is why
|
|
; the client puts it last and this one anywhere.
|
|
StrCpy $WaitPid ""
|
|
StrCpy $LauncherExited 0
|
|
ClearErrors
|
|
${GetOptions} $9 "/PID=" $8
|
|
${IfNot} ${Errors}
|
|
StrCpy $WaitPid $8
|
|
${EndIf}
|
|
|
|
${If} $InstDir == ""
|
|
; User did not use /D to specify a directory,
|
|
; we need to set a default based on the install mode
|
|
StrCpy $InstDir $0
|
|
${EndIf}
|
|
|
|
; --- Detect portable install when using /R (must come BEFORE SetModeDestinationFromInstdir) ---
|
|
${If} $ReinstallMode = 1
|
|
IfFileExists "$InstDir\portable.dat" 0 not_portable
|
|
StrCpy $PortableMode 1
|
|
Goto portable_done
|
|
not_portable:
|
|
StrCpy $PortableMode 0
|
|
portable_done:
|
|
${EndIf}
|
|
|
|
; Now that $PortableMode reflects reality, commit InstDir into the correct slot
|
|
Call SetModeDestinationFromInstdir
|
|
|
|
!insertmacro CockLog "options: InstDir=$InstDir PortableMode=$PortableMode ReinstallMode=$ReinstallMode WaitPid=$WaitPid"
|
|
${If} ${Silent}
|
|
!insertmacro CockLog "options: running silently"
|
|
${Else}
|
|
!insertmacro CockLog "options: interactive"
|
|
${EndIf}
|
|
|
|
; Wait for the process that launched this installer to exit before anything is
|
|
; replaced or deleted. It goes first because a real handle on that process is
|
|
; authority: the holder probe that follows asks the same question indirectly, by
|
|
; looking at which files are still open.
|
|
;
|
|
; Then make sure no *other* application instance is still running (and holding
|
|
; file locks) before the previous version is uninstalled. The probe names them
|
|
; and the user is asked to close them; nothing is force-stopped without that
|
|
; answer, because Cockatrice may hold an unsaved deck and Oracle writes the card
|
|
; database in place. On /R the old version is about to be removed, so an answer
|
|
; the probe cannot give cancels the upgrade before it starts rather than after it.
|
|
; Only needed this early for /R, where AutoUninstallIfNeeded runs below; for
|
|
; interactive installs the section re-checks after the directory page, when
|
|
; $INSTDIR is the directory the user actually picked.
|
|
${If} $ReinstallMode = 1
|
|
!insertmacro CockLog "reinstall: waiting for launcher and other instances"
|
|
Call WaitForLauncherToExit
|
|
Call EnsureAppsNotRunning
|
|
${EndIf}
|
|
|
|
${If} $ReinstallMode = 1
|
|
${AndIf} $PortableMode = 0
|
|
!insertmacro CockLog "reinstall: auto-uninstalling previous version"
|
|
Call AutoUninstallIfNeeded
|
|
${EndIf}
|
|
|
|
FunctionEnd
|
|
|
|
Function un.onInit
|
|
|
|
${If} ${NSIS_IS_64_BIT} == 1
|
|
SetRegView 64
|
|
${EndIf}
|
|
|
|
; The uninstaller logs to its own file: the installer truncates its log on every run, so
|
|
; sharing one would mean either side erasing the other's account of a failed upgrade.
|
|
StrCpy $InstallLogPath "$TEMP\Cockatrice-uninstaller.log"
|
|
FileOpen $LogHandle "$InstallLogPath" w
|
|
FileClose $LogHandle
|
|
!insertmacro CockLogUn "=================================================================="
|
|
!insertmacro CockLogUn "uninstaller start: @CPACK_PACKAGE_NAME@ @CPACK_PACKAGE_VERSION@ dir=$INSTDIR"
|
|
|
|
; Ensure no application instance is still running before removing files.
|
|
Call un.EnsureAppsNotRunning
|
|
|
|
FunctionEnd
|
|
|
|
Function RequireAdmin
|
|
UserInfo::GetAccountType
|
|
Pop $8
|
|
${If} $8 != "admin"
|
|
MessageBox MB_ICONSTOP "You need administrator rights to install Cockatrice"
|
|
SetErrorLevel 740 ;ERROR_ELEVATION_REQUIRED
|
|
Abort
|
|
${EndIf}
|
|
FunctionEnd
|
|
|
|
|
|
Function SetModeDestinationFromInstdir
|
|
${If} $PortableMode = 0
|
|
StrCpy $NormalDestDir $InstDir
|
|
${Else}
|
|
StrCpy $PortableDestDir $InstDir
|
|
${EndIf}
|
|
FunctionEnd
|
|
|
|
Function SkipIfReinstall
|
|
${If} $ReinstallMode = 1
|
|
Abort
|
|
${EndIf}
|
|
FunctionEnd
|
|
|
|
; Sets $OldUninstallExe to the executable named in $OldUninstallString (everything up to
|
|
; and including the first ".exe", without a leading quote), or "" when there is none.
|
|
Function ResolveOldUninstallExe
|
|
Push $R7
|
|
Push $R8
|
|
StrLen $R8 $OldUninstallString
|
|
StrCpy $R7 0
|
|
roue_loop:
|
|
${If} $R7 >= $R8
|
|
StrCpy $OldUninstallExe ""
|
|
Goto roue_out
|
|
${EndIf}
|
|
StrCpy $OldUninstallExe $OldUninstallString 4 $R7
|
|
${If} $OldUninstallExe == ".exe"
|
|
IntOp $R7 $R7 + 4
|
|
StrCpy $OldUninstallExe $OldUninstallString $R7
|
|
StrCpy $R8 $OldUninstallExe 1
|
|
${If} $R8 == '"'
|
|
StrCpy $OldUninstallExe $OldUninstallExe "" 1
|
|
${EndIf}
|
|
Goto roue_out
|
|
${EndIf}
|
|
IntOp $R7 $R7 + 1
|
|
Goto roue_loop
|
|
roue_out:
|
|
Pop $R8
|
|
Pop $R7
|
|
FunctionEnd
|
|
|
|
; Runs "$OldUninstallString _?=$OldInstallDir" in place and sets $OldUninstallExit:
|
|
; the uninstaller's own exit code when it ran, "missing" when the executable the
|
|
; registry names does not exist, "launch-failed" when ExecWait could not start it.
|
|
; The last two used to leave the code empty, which WaitForOldPayloadToDisappear read
|
|
; as a cancelled uninstall and aborted on, although nobody had cancelled anything.
|
|
; Uses $R5 for the exit code, as the callers did before.
|
|
Function RunOldUninstaller
|
|
Call ResolveOldUninstallExe
|
|
StrCpy $R5 ""
|
|
${If} $OldUninstallExe == ""
|
|
${OrIfNot} ${FileExists} "$OldUninstallExe"
|
|
StrCpy $OldUninstallExit "missing"
|
|
!insertmacro CockLog "old uninstall: uninstaller '$OldUninstallExe' does not exist (command '$OldUninstallString')"
|
|
Return
|
|
${EndIf}
|
|
ClearErrors
|
|
ExecWait '$OldUninstallString _?=$OldInstallDir' $R5
|
|
${If} ${Errors}
|
|
${OrIf} $R5 == ""
|
|
StrCpy $OldUninstallExit "launch-failed"
|
|
!insertmacro CockLog "old uninstall: could not start '$OldUninstallString _?=$OldInstallDir'"
|
|
Return
|
|
${EndIf}
|
|
StrCpy $OldUninstallExit $R5
|
|
FunctionEnd
|
|
|
|
Function AutoUninstallIfNeeded
|
|
|
|
SetShellVarContext all
|
|
|
|
; _?=$INSTDIR keeps the old uninstaller running in place. An NSIS uninstaller
|
|
; normally copies itself to %TEMP% and runs from there so that it can delete its
|
|
; own directory, and the process we started exits at once. ExecWait then returns
|
|
; before the uninstall has run, and the temp copy keeps deleting files - from
|
|
; 3.1.0 and 3.1.1 on disk a blanket "RMDir /r $INSTDIR" - while the section
|
|
; below is still writing the new payload. _?= disables the copy and makes
|
|
; ExecWait block until the uninstall finishes. It must be the last argument
|
|
; and must not be quoted, even when the path contains spaces.
|
|
;
|
|
; --- 32-bit uninstall ---
|
|
SetRegView 32
|
|
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "QuietUninstallString"
|
|
|
|
; On the /R path the updater passes /D=<appDir>, so $INSTDIR already is the old
|
|
; install dir. Publish it for the backup/restore pair below.
|
|
StrCpy $OldInstallDir $INSTDIR
|
|
!insertmacro CockLog "uninstall (32-bit): '$R0 _?=$OldInstallDir'"
|
|
StrCmp $R0 "" done32
|
|
DetailPrint "Removing previous version (32-bit)..."
|
|
Call BackupUserDataBeforeUninstall
|
|
StrCpy $OldUninstallString $R0
|
|
Call RunOldUninstaller
|
|
!insertmacro CockLog "uninstall (32-bit): exit=$OldUninstallExit"
|
|
Call WaitForOldPayloadToDisappear
|
|
Call RestoreUserDataAfterUninstall
|
|
|
|
done32:
|
|
|
|
; --- 64-bit uninstall ---
|
|
${If} ${RunningX64}
|
|
SetRegView 64
|
|
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "QuietUninstallString"
|
|
|
|
StrCpy $OldInstallDir $INSTDIR
|
|
!insertmacro CockLog "uninstall (64-bit): '$R0 _?=$OldInstallDir'"
|
|
StrCmp $R0 "" done64
|
|
DetailPrint "Removing previous version (64-bit)..."
|
|
Call BackupUserDataBeforeUninstall
|
|
StrCpy $OldUninstallString $R0
|
|
Call RunOldUninstaller
|
|
!insertmacro CockLog "uninstall (64-bit): exit=$OldUninstallExit"
|
|
Call WaitForOldPayloadToDisappear
|
|
Call RestoreUserDataAfterUninstall
|
|
|
|
done64:
|
|
${EndIf}
|
|
|
|
FunctionEnd
|
|
|
|
; User data backup/restore around the old uninstaller.
|
|
!include "${NSIS_SOURCE_PATH}\cmake\nsis\user-data.nsh"
|
|
|
|
; Running instance handling, installer and uninstaller copy.
|
|
!include "${NSIS_SOURCE_PATH}\cmake\nsis\process-guard.nsh"
|
|
|
|
; Aborts the installation, showing $AbortReason to the user.
|
|
;
|
|
; Every abort goes through here so no failure ends silently: the in-app updater
|
|
; runs this installer silent and detached and never looks at its exit code, and
|
|
; a /S install without /R has nobody watching its exit code either, so a plain
|
|
; Abort would leave the user with no update and no explanation. An error always
|
|
; shows its dialog, in every mode; a silent run stays silent only for as long
|
|
; as nothing goes wrong. The log path is part of the dialog, so a report can be
|
|
; acted on without asking the user to find the file.
|
|
Function AbortInstall
|
|
!insertmacro CockLog "ABORT: $AbortReason"
|
|
!insertmacro CockLog "full installer log: $InstallLogPath"
|
|
!insertmacro CockLog "manual update: ${LATEST_RELEASE_URL}"
|
|
; The reason names what stopped the run; the manual-update paragraph is the way forward
|
|
; that does not need this installer to free anything. Shown in every mode, exactly like
|
|
; the reason, because a /R upgrade has nobody watching its exit code.
|
|
MessageBox MB_OK|MB_ICONEXCLAMATION "$AbortReason$\r$\n$\r$\n${MANUAL_UPDATE_GUIDANCE}$\r$\n$\r$\nA detailed installer log was written to:$\r$\n$InstallLogPath"
|
|
; Offer the page as well: copying a URL out of a message box is a step users skip.
|
|
; explorer.exe hands the URL to the already-running shell, so the browser opens at the
|
|
; user's integrity level rather than as the elevated installer.
|
|
MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "Open the download page now?$\r$\n$\r$\n${LATEST_RELEASE_URL}" IDNO abort_no_link
|
|
!insertmacro CockLog "manual update: opening the download page"
|
|
Exec '"$WINDIR\explorer.exe" "${LATEST_RELEASE_URL}"'
|
|
abort_no_link:
|
|
; Offer to send the installer log to Sentry so aborted installs can be diagnosed.
|
|
; Only a package built with a baked DSN offers to send, so the prompt can only appear
|
|
; when a send can actually go out. A preprocessor guard rather than a runtime test: a
|
|
; package built without a DSN compiles the block out entirely, so it cannot even carry
|
|
; the helper or its baked endpoint. No /SD: the abort is exactly the case the offer
|
|
; exists for, including on the silent /R upgrade.
|
|
!if "@CPACK_SENTRY_ENABLED@" == "1"
|
|
MessageBox MB_YESNO|MB_ICONQUESTION "Send the installer log to the developers to help fix this? The log contains file paths from this machine, which may include your Windows user name." IDYES sentry_send
|
|
!insertmacro CockLog "sentry send: declined by user"
|
|
Goto sentry_done
|
|
sentry_send:
|
|
; The abort reason is already in the log file (CockLog writes "ABORT: ..." as the
|
|
; first thing AbortInstall does), so the helper reads it from there. Nothing
|
|
; reason-shaped crosses the command line, which keeps quoting trivial and needs
|
|
; no NSIS string functions.
|
|
InitPluginsDir
|
|
; Embedded by the same relative path that resolves NSIS.definitions.nsh above (both are
|
|
; configure_file'd into the build root, which is three levels up from project.nsi). An
|
|
; absolute CMake path here carries forward slashes, which makensis does not treat as
|
|
; absolute and so fails to find the file at package time.
|
|
File "/oname=$PLUGINSDIR\send-installer-log.ps1" "..\..\..\SendInstallerLog.ps1"
|
|
; The send can take up to the /TIMEOUT below, so put something on the details pane
|
|
; the moment the user says yes rather than leaving the window blank until it returns.
|
|
DetailPrint "Sending installer log..."
|
|
nsExec::ExecToStack /TIMEOUT=60000 'powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$PLUGINSDIR\send-installer-log.ps1" -LogPath "$InstallLogPath"'
|
|
Pop $SentryResult
|
|
Pop $SentryOutput
|
|
; $SentryOutput is a single line by construction: the helper flattens its message
|
|
; and this log file is uploaded verbatim, so a stray newline must not be able to
|
|
; forge extra lines into the artifact we send.
|
|
!insertmacro CockLog "sentry send: result=$SentryResult output=$SentryOutput"
|
|
${If} $SentryResult == "0"
|
|
MessageBox MB_OK|MB_ICONINFORMATION "The installer log was sent. Thank you for helping improve Cockatrice!"
|
|
${Else}
|
|
MessageBox MB_OK|MB_ICONEXCLAMATION "The installer log could not be sent. You can still share it manually; it was written to:$\r$\n$InstallLogPath"
|
|
${EndIf}
|
|
sentry_done:
|
|
!endif
|
|
SetErrorLevel 1
|
|
Abort
|
|
FunctionEnd
|
|
|
|
; Stale runtime purge, diagnostics and the holder probe.
|
|
!include "${NSIS_SOURCE_PATH}\cmake\nsis\runtime-purge.nsh"
|
|
|
|
Function PortableModePageCreate
|
|
|
|
${If} $ReinstallMode = 1
|
|
Abort
|
|
${EndIf}
|
|
|
|
Call SetModeDestinationFromInstdir ; If the user clicks BACK on the directory page we will remember their mode specific directory
|
|
!insertmacro MUI_HEADER_TEXT "Install Mode" "Choose how you want to install Cockatrice."
|
|
nsDialogs::Create 1018
|
|
Pop $0
|
|
${NSD_CreateLabel} 0 10u 100% 24u "Select install mode:"
|
|
Pop $0
|
|
${NSD_CreateRadioButton} 30u 50u -30u 8u "Normal installation"
|
|
Pop $1
|
|
${NSD_CreateRadioButton} 30u 70u -30u 8u "Portable mode (all files in a single folder)"
|
|
Pop $2
|
|
${If} $PortableMode = 0
|
|
SendMessage $1 ${BM_SETCHECK} ${BST_CHECKED} 0
|
|
${Else}
|
|
SendMessage $2 ${BM_SETCHECK} ${BST_CHECKED} 0
|
|
${EndIf}
|
|
nsDialogs::Show
|
|
FunctionEnd
|
|
|
|
Function PortableModePageLeave
|
|
${NSD_GetState} $1 $0
|
|
${If} $0 <> ${BST_UNCHECKED}
|
|
StrCpy $PortableMode 0
|
|
StrCpy $InstDir $NormalDestDir
|
|
Call RequireAdmin
|
|
${Else}
|
|
StrCpy $PortableMode 1
|
|
StrCpy $InstDir $PortableDestDir
|
|
${EndIf}
|
|
FunctionEnd
|
|
|
|
Function componentsPagePre
|
|
|
|
${If} $ReinstallMode = 1
|
|
Return
|
|
${EndIf}
|
|
|
|
${If} $PortableMode = 0
|
|
SetShellVarContext all
|
|
|
|
# uninstall 32bit version
|
|
SetRegView 32
|
|
|
|
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString"
|
|
StrCmp $R0 "" done32
|
|
|
|
${If} $ReinstallMode = 0
|
|
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst32
|
|
; Abort would only skip this page and install over the old version anyway;
|
|
; the message said the old one must go first, so Cancel means cancel.
|
|
!insertmacro CockLog "componentsPagePre: cancelled at the previous-version prompt"
|
|
Quit
|
|
${Else}
|
|
Goto uninst32
|
|
${EndIf}
|
|
|
|
uninst32:
|
|
; the old uninstaller is run here, before the directory page, and every
|
|
; uninstaller predating this change has no guard of its own: make sure no
|
|
; instance of the application is still running out of the *old* install
|
|
; directory before letting it delete anything. The uninstall string is
|
|
; stashed away first, the check works on $R0.
|
|
; $INSTDIR is still the default here (directory page comes later), so the
|
|
; backup/restore dir comes from the registry InstallLocation via $GuardDir,
|
|
; not from $INSTDIR - otherwise a custom-location install is unprotected.
|
|
StrCpy $OldUninstallString $R0
|
|
Call EnsureOldInstallNotRunning
|
|
StrCpy $OldInstallDir $GuardDir
|
|
; Run it silently. The question above is the one the user answered; the
|
|
; uninstaller's own confirmation page would be a second one, and that one opens
|
|
; behind this window, where nobody can answer it. The installer then looks hung,
|
|
; the user clicks Next on it, the new version is installed, and the uninstaller
|
|
; removes everything once this window closes. /S leaves no window to be behind,
|
|
; so ExecWait is waiting for something that cannot outlive it.
|
|
ReadRegStr $R1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "QuietUninstallString"
|
|
${If} $R1 == ""
|
|
StrCpy $OldUninstallString "$OldUninstallString /S"
|
|
${Else}
|
|
StrCpy $OldUninstallString $R1
|
|
${EndIf}
|
|
!insertmacro CockLog "componentsPagePre: old 32-bit uninstall '$OldUninstallString _?=$OldInstallDir' (oldDir=$OldInstallDir InstDir=$INSTDIR)"
|
|
ClearErrors
|
|
Call BackupUserDataBeforeUninstall
|
|
Call RunOldUninstaller
|
|
!insertmacro CockLog "componentsPagePre: old 32-bit uninstall exit=$OldUninstallExit"
|
|
Call WaitForOldPayloadToDisappear
|
|
Call RestoreUserDataAfterUninstall
|
|
|
|
done32:
|
|
|
|
# uninstall 64bit version
|
|
${If} ${NSIS_IS_64_BIT} == 1
|
|
SetRegView 64
|
|
|
|
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString"
|
|
StrCmp $R0 "" done64
|
|
|
|
${If} $ReinstallMode = 0
|
|
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst64
|
|
!insertmacro CockLog "componentsPagePre: cancelled at the previous-version prompt"
|
|
Quit
|
|
${Else}
|
|
Goto uninst64
|
|
${EndIf}
|
|
|
|
uninst64:
|
|
StrCpy $OldUninstallString $R0
|
|
Call EnsureOldInstallNotRunning
|
|
StrCpy $OldInstallDir $GuardDir
|
|
; Silent, for the reason given in the 32-bit branch above.
|
|
ReadRegStr $R1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "QuietUninstallString"
|
|
${If} $R1 == ""
|
|
StrCpy $OldUninstallString "$OldUninstallString /S"
|
|
${Else}
|
|
StrCpy $OldUninstallString $R1
|
|
${EndIf}
|
|
!insertmacro CockLog "componentsPagePre: old 64-bit uninstall '$OldUninstallString _?=$OldInstallDir' (oldDir=$OldInstallDir InstDir=$INSTDIR)"
|
|
ClearErrors
|
|
Call BackupUserDataBeforeUninstall
|
|
Call RunOldUninstaller
|
|
!insertmacro CockLog "componentsPagePre: old 64-bit uninstall exit=$OldUninstallExit"
|
|
Call WaitForOldPayloadToDisappear
|
|
Call RestoreUserDataAfterUninstall
|
|
|
|
done64:
|
|
${EndIf}
|
|
|
|
${Else}
|
|
Abort
|
|
${EndIf}
|
|
FunctionEnd
|
|
|
|
Section "Application" SecApplication
|
|
|
|
SetShellVarContext all
|
|
SetOutPath "$INSTDIR"
|
|
|
|
!insertmacro CockLog "section: InstDir=$INSTDIR PortableMode=$PortableMode ReinstallMode=$ReinstallMode"
|
|
|
|
; No application instance may be running while any file below is deleted or
|
|
; replaced: both upgrade blocks start by removing the old executables and
|
|
; runtime DLLs, and NSIS silently skips whatever is locked, which is how an
|
|
; install ends up with a mix of old and new runtimes next to the new executable.
|
|
; The next start then fails with "The procedure entry point X could not be
|
|
; located in the dynamic link library ...Qt6Network.dll" for the Qt ones, or
|
|
; "abseil_dll.dll is either not designed to run on Windows or it contains an
|
|
; error" for the protobuf ones. So the check has to come before the first
|
|
; Delete/RMDir and not after it: an interactive install into an existing portable
|
|
; directory with cockatrice.exe still running would otherwise delete the old
|
|
; files under its feet.
|
|
;
|
|
; It is skipped on the /R path: .onInit already ran it before
|
|
; AutoUninstallIfNeeded removed the previous installation, so by the time this
|
|
; section starts the old files are gone and a second Abort here would leave the
|
|
; user with the old version uninstalled and the new one not installed at all,
|
|
; which is worse than the locked files being prevented. The leftover runtime
|
|
; files are reported at the end of the purge instead.
|
|
${If} $ReinstallMode = 0
|
|
Call EnsureAppsNotRunning
|
|
${EndIf}
|
|
|
|
; Measured before either purge block, because it has to describe the directory as
|
|
; it arrived here and not what a purge left of it: IsExistingCockatriceInstall
|
|
; looks for cockatrice.exe, oracle.exe, servatrice.exe, uninstall.exe, Qt6*.dll
|
|
; and qt.conf, and the portable purge below deletes exactly those. Taken after
|
|
; that purge it answered "not an install" as soon as the only survivor was a file
|
|
; it does not look for - a locked abseil_dll.dll or libssl-3-x64.dll, say - and
|
|
; the abort further down was then skipped for the very directory the purge had
|
|
; just failed in.
|
|
; Remembered because the stale-runtime check below must only ever fire for a
|
|
; directory this installer recognised as a Cockatrice install. Scanning for
|
|
; leftovers in an unrelated directory (a first-time install into a folder that
|
|
; happens to hold some Qt5 DLLs, say) would otherwise turn those files into an
|
|
; abort reason even though the payload is about to overwrite nothing of theirs.
|
|
Call IsExistingCockatriceInstall
|
|
!insertmacro CockLog "section: IsExistingCockatriceInstall=$R0"
|
|
StrCpy $IsUpgrade $R0
|
|
|
|
${If} $PortableMode = 1
|
|
${AndIf} ${FileExists} "$INSTDIR\portable.dat"
|
|
; upgrade portable mode
|
|
!insertmacro CockLog "section: portable upgrade purge"
|
|
!insertmacro CockLogListing "before-portable-purge" "*.dll"
|
|
; plugins and translations are payload and are rewritten by the payload
|
|
; below, so removing them here is safe. sounds and themes are payload
|
|
; too - the default packs are rewritten below as well - but a portable
|
|
; user's custom sounds and themes live in exactly those folders, so a
|
|
; recursive purge here would delete user content along with the defaults.
|
|
; Stale default files from an older release are overwritten or replaced
|
|
; by the payload, never by a purge.
|
|
RMDir /r "$INSTDIR\plugins"
|
|
RMDir /r "$INSTDIR\translations"
|
|
Delete "$INSTDIR\uninstall.exe"
|
|
Delete "$INSTDIR\cockatrice.exe"
|
|
Delete "$INSTDIR\oracle.exe"
|
|
Delete "$INSTDIR\servatrice.exe"
|
|
; Every top-level DLL, not a list of expected ones. The payload is written
|
|
; after this block, so a DLL the new build still needs is put straight back;
|
|
; what this has to guarantee is that nothing from the previous build is left
|
|
; to be loaded next to the new executables. Naming the families by hand is
|
|
; what let abseil_dll.dll through; see HasStaleRuntimeFiles.
|
|
Call RetryDllPurge
|
|
; The purge's own verdict: HasStaleRuntimeFiles measures *.dll + Plugins\*.*,
|
|
; the set that catches any leftover runtime DLL, Qt or otherwise. Held in a
|
|
; variable because $R0 is reused by the next function call in this section.
|
|
StrCpy $StaleRuntimeAfterPurge $R0
|
|
StrCpy $DllPurgeRan 1
|
|
Delete "$INSTDIR\qt.conf"
|
|
Delete "$INSTDIR\qdebug.txt"
|
|
Delete "$INSTDIR\servatrice.sql"
|
|
Delete "$INSTDIR\servatrice.ini.example"
|
|
RMDir "$INSTDIR"
|
|
!insertmacro CockLogListing "after-portable-purge" "*.dll"
|
|
${EndIf}
|
|
|
|
; Remove any runtime DLLs left over from older versions, so a mismatched
|
|
; Qt/OpenSSL/protobuf set cannot survive next to the new executables.
|
|
;
|
|
; A purge that fails is reported with evidence, not only as a survivor list: the
|
|
; abort below logs which files survived, what Windows said about each of them and
|
|
; which processes still have the install directory mapped (see
|
|
; LogStaleRuntimeDiagnostics).
|
|
;
|
|
; Re-running the running-instance check here was considered and dropped: it would only
|
|
; narrow the window the uninstaller ExecWait and the backup/restore copy open between
|
|
; .onInit and this point, and on /R it would now abort after the old version has already
|
|
; been removed - leaving no install at all. The surviving files are the authority on
|
|
; whether anything still holds the directory, and they are measured anyway.
|
|
;
|
|
; The holder list comes from Restart Manager (RmRegisterResources/RmGetList), the API
|
|
; that names every process and service with one of these files open or mapped. It is
|
|
; called from tools/installer-holder-probe rather than from this script, because the
|
|
; RM_PROCESS_INFO array is a table of hand-computed offsets when it is marshalled
|
|
; through the System plugin, and that table is the one thing here that cannot be
|
|
; checked without a Windows toolchain. Compiled by the same build that compiles this
|
|
; installer, the layout belongs to the Windows SDK, and .ci/compile.sh runs the probe
|
|
; on the Windows runners instead of trusting that it compiles.
|
|
${If} $PortableMode = 0
|
|
${AndIf} $IsUpgrade = 1
|
|
!insertmacro CockLog "section: non-portable upgrade purge"
|
|
!insertmacro CockLogListing "before-purge" "*.dll"
|
|
Call RetryDllPurge
|
|
StrCpy $StaleRuntimeAfterPurge $R0
|
|
StrCpy $DllPurgeRan 1
|
|
!insertmacro CockLogListing "after-purge" "*.dll"
|
|
!insertmacro CockLogListing "after-plugins" "Plugins\*.*"
|
|
${EndIf}
|
|
|
|
; Whatever was locked during the purge (by an instance that started while the
|
|
; installer was running, or because the running instance check could not be
|
|
; performed at all) is still on disk, and the new executables are about to be
|
|
; written next to it. That mix is exactly what makes the next start fail with
|
|
; "The procedure entry point X could not be located in the dynamic link library
|
|
; ...Qt6QuickWidgets.dll", "The code execution cannot proceed because
|
|
; Qt6Multimedia.dll was not found", or "abseil_dll.dll is either not designed to
|
|
; run on Windows or it contains an error", so stop here instead of compounding
|
|
; it. Nothing of the new payload has been written at this point, so the next run,
|
|
; with the applications closed, gets a clean install.
|
|
; $StaleRuntimeAfterPurge is RetryDllPurge's verdict, measured with
|
|
; HasStaleRuntimeFiles (*.dll + Plugins\*.*) - the set that catches any leftover
|
|
; runtime DLL, Qt or otherwise. It exists only when a purge ran, and the two
|
|
; purge blocks above cover every upgrade except one: a portable install into a
|
|
; directory without portable.dat is neither of them, so that directory is
|
|
; measured here instead. A test of $IsUpgrade alone could not stand in for it -
|
|
; that only says the directory was ours before the purge, not what the purge
|
|
; left behind.
|
|
; A directory this installer did not recognise as an install never aborts: the
|
|
; unrelated DLLs already in it (a first-time install into a folder that happens
|
|
; to hold some Qt5 DLLs, say) are not leftovers, and the payload overwrites
|
|
; nothing of theirs.
|
|
${If} $IsUpgrade = 1
|
|
${If} $DllPurgeRan = 0
|
|
!insertmacro CockLog "section: no purge applies, measuring the directory instead"
|
|
Call HasStaleRuntimeFiles
|
|
StrCpy $StaleRuntimeAfterPurge $R0
|
|
${EndIf}
|
|
!insertmacro CockLog "section: stale-runtime after purge=$StaleRuntimeAfterPurge"
|
|
${Else}
|
|
StrCpy $StaleRuntimeAfterPurge 0
|
|
${EndIf}
|
|
${If} $StaleRuntimeAfterPurge = 1
|
|
!insertmacro CockLog "section: files of a previous version survived the purge"
|
|
; Who is holding the payload is recorded first, so the log that reaches us
|
|
; answers the question the message can only guess at, and so the prompt can
|
|
; name them and offer to close the ones this installer owns.
|
|
Call LogStaleRuntimeDiagnostics
|
|
${If} $HolderFound = 1
|
|
; On /R the previous version is already uninstalled at this point, so an abort
|
|
; leaves the user with no working Cockatrice. It closes once, then offers to end
|
|
; what is left; this updates $StaleRuntimeAfterPurge with the verdict after that
|
|
; close, or after the end when the user asked for one.
|
|
Call RecoverFromHolders
|
|
${Else}
|
|
; Nothing is holding them. The three attempts in RetryDllPurge were two seconds
|
|
; apart and all failed with no process to blame, which is the transient case a
|
|
; virus scanner or a background indexer creates - or a permission problem that
|
|
; will not clear. Give the delete one last chance before giving up: it costs a
|
|
; second on a path that is about to abort anyway, and it keeps a lock that was
|
|
; released in the meantime from turning into a failed upgrade.
|
|
!insertmacro CockLog "section: no holder named, one final purge attempt"
|
|
Sleep 1000
|
|
Call RetryDllPurge
|
|
StrCpy $StaleRuntimeAfterPurge $R0
|
|
!insertmacro CockLog "section: final-purge stale-runtime=$StaleRuntimeAfterPurge"
|
|
${EndIf}
|
|
${EndIf}
|
|
${If} $StaleRuntimeAfterPurge = 1
|
|
!insertmacro CockLog "section: aborting, files of a previous version survived the purge"
|
|
DetailPrint "Files of a previous version could not be removed; aborting the installation."
|
|
; Say what is known and nothing more: the guard ran before anything was replaced,
|
|
; so "most likely still running" is no longer a fair guess and would send the user
|
|
; to close programs that are already closed.
|
|
StrCpy $AbortReason \
|
|
"The installation could not remove all files of a previous version of Cockatrice, and was cancelled rather than leave a mismatched installation behind."
|
|
${If} $HolderFound = 1
|
|
StrCpy $AbortReason "$AbortReason$\r$\n$\r$\nStill using the install directory:$\r$\n$HolderOutput$\r$\n$\r$\nClose them and run the installer again."
|
|
${ElseIf} $HolderExit == "0"
|
|
StrCpy $AbortReason "$AbortReason$\r$\n$\r$\nNo running program was found using those files. A file permission or a virus scanner may be preventing their removal; the installer log records what Windows reported for each file."
|
|
${Else}
|
|
; The check itself failed, so "no running program was found" would be a claim this
|
|
; installer cannot make. Name the failure instead, and leave the per-file codes to
|
|
; the log, where they already are.
|
|
StrCpy $AbortReason "$AbortReason$\r$\n$\r$\nThe check for a running program could not be completed: $HolderOutput$\r$\n$\r$\nClose Cockatrice, Oracle and Servatrice and run the installer again; the installer log records what Windows reported for each file."
|
|
${EndIf}
|
|
; "Run the installer again" is not enough on an in-app update: the old version
|
|
; was uninstalled before the purge, so point at the installer that was downloaded.
|
|
${If} $ReinstallMode = 1
|
|
${AndIf} $PortableMode = 0
|
|
StrCpy $AbortReason "$AbortReason$\r$\n$\r$\nThe previous version has already been removed. To finish the update, run this installer again:$\r$\n$EXEPATH"
|
|
${EndIf}
|
|
Call AbortInstall
|
|
${EndIf}
|
|
|
|
@CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS@
|
|
!insertmacro CockLog "section: writing payload to $INSTDIR"
|
|
@CPACK_NSIS_FULL_INSTALL@
|
|
!insertmacro CockLog "section: payload written"
|
|
; The pre-uninstall backup/restore above runs before the directory page, so a
|
|
; user picking a different target folder would otherwise leave the restored data
|
|
; orphaned in the old directory. Move it across when they differ (no-op on the
|
|
; /R path where they are equal).
|
|
Call RelocateRestoredDataToInstDir
|
|
!insertmacro CockLogListing "final" "Qt6*.dll"
|
|
|
|
${If} $PortableMode = 0
|
|
WriteUninstaller "$INSTDIR\uninstall.exe"
|
|
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
|
|
IntFmt $0 "0x%08X" $0
|
|
|
|
; No Windows Error Reporting LocalDumps registration: crash capture belongs to
|
|
; the in-app reporter (Sentry/breakpad, MiniDumpNormal-class data only). Opting
|
|
; machines into OS-level full dumps (DumpType 2) would leave unredacted memory
|
|
; dumps on user machines that nothing consumes.
|
|
; https://learn.microsoft.com/en-us/windows/win32/wer/collecting-user-mode-dumps
|
|
|
|
; Older installers did register cockatrice.exe above (and the removed
|
|
; MiniDumpWithFullMemory writer filled that folder), and that key survives this
|
|
; install, so WER would keep writing full dumps. Remove it, and clear the
|
|
; legacy dumps nothing consumes anymore. $LOCALAPPDATA resolves per shell context:
|
|
; under "all" it points at ProgramData, so switch to "current" for the per-user
|
|
; dumps folder and back afterwards; the registry delete works in either context.
|
|
DeleteRegKey HKLM "Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\cockatrice.exe"
|
|
SetShellVarContext current
|
|
RMDir /r "$LOCALAPPDATA\CrashDumps\Cockatrice"
|
|
SetShellVarContext all
|
|
|
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayIcon" "$INSTDIR\cockatrice.exe"
|
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayName" "Cockatrice"
|
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayVersion" "@CPACK_PACKAGE_VERSION_MAJOR@.@CPACK_PACKAGE_VERSION_MINOR@.@CPACK_PACKAGE_VERSION_PATCH@"
|
|
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "EstimatedSize" "$0"
|
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "InstallLocation" "$INSTDIR"
|
|
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "NoModify" "1"
|
|
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "NoRepair" "1"
|
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "Publisher" "Cockatrice team"
|
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
|
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
|
|
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "VersionMajor" "@CPACK_PACKAGE_VERSION_MAJOR@"
|
|
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "VersionMinor" "@CPACK_PACKAGE_VERSION_MINOR@"
|
|
|
|
; --- Register .cod file type ---
|
|
WriteRegStr HKCR ".cod" "" "Cockatrice"
|
|
WriteRegStr HKCR "Cockatrice" "" "Cockatrice Deck File"
|
|
WriteRegStr HKCR "Cockatrice\shell\open\command" "" '"$INSTDIR\cockatrice.exe" "%1"'
|
|
|
|
; --- Register custom URI protocol ---
|
|
WriteRegStr HKCR "cockatrice" "" "URL: Cockatrice Protocol"
|
|
WriteRegStr HKCR "cockatrice" "URL Protocol" ""
|
|
WriteRegStr HKCR "cockatrice\shell\open\command" "" '"$INSTDIR\cockatrice.exe" "%1"'
|
|
|
|
IfFileExists "$INSTDIR\vc_redist.x86.exe" VcRedist86Exists PastVcRedist86Check
|
|
VcRedist86Exists:
|
|
ExecWait '"$INSTDIR\vc_redist.x86.exe" /passive /norestart'
|
|
DetailPrint "Wait to ensure unlock of vc_redist file after installation..."
|
|
Sleep 3000
|
|
Delete "$INSTDIR\vc_redist.x86.exe"
|
|
PastVcRedist86Check:
|
|
|
|
IfFileExists "$INSTDIR\vc_redist.x64.exe" VcRedist64Exists PastVcRedist64Check
|
|
VcRedist64Exists:
|
|
ExecWait '"$INSTDIR\vc_redist.x64.exe" /passive /norestart'
|
|
DetailPrint "Sleep to ensure unlock of vc_redist file after installation..."
|
|
Sleep 3000
|
|
Delete "$INSTDIR\vc_redist.x64.exe"
|
|
PastVcRedist64Check:
|
|
|
|
${Else}
|
|
; Create the file the application uses to detect portable mode
|
|
FileOpen $0 "$INSTDIR\portable.dat" w
|
|
FileWrite $0 "PORTABLE"
|
|
FileClose $0
|
|
${EndIf}
|
|
|
|
${If} $ReinstallMode = 1
|
|
IfFileExists "$INSTDIR\cockatrice.exe" 0 +2
|
|
Exec '"$INSTDIR\cockatrice.exe"'
|
|
${EndIf}
|
|
|
|
!insertmacro CockLog "section: application install complete"
|
|
SectionEnd
|
|
|
|
Section "Start menu item" SecStartMenu
|
|
${If} $PortableMode = 0
|
|
SetShellVarContext all
|
|
createDirectory "$SMPROGRAMS\Cockatrice"
|
|
createShortCut "$SMPROGRAMS\Cockatrice\Cockatrice.lnk" "$INSTDIR\cockatrice.exe"
|
|
createShortCut "$SMPROGRAMS\Cockatrice\Oracle.lnk" "$INSTDIR\oracle.exe"
|
|
createShortCut "$SMPROGRAMS\Cockatrice\Servatrice.lnk" "$INSTDIR\servatrice.exe"
|
|
${EndIf}
|
|
SectionEnd
|
|
|
|
Section "un.Application" UnSecApplication
|
|
SetShellVarContext all
|
|
StrCpy $R1 0
|
|
|
|
; Record whether this was a portable install before portable.dat is
|
|
; removed below.
|
|
IfFileExists "$INSTDIR\portable.dat" 0 +2
|
|
StrCpy $R1 1
|
|
|
|
; Remove only what the installer wrote. The two lists below are generated
|
|
; by CPack from the staged install tree: one Delete per installed file and
|
|
; one non-recursive RMDir per installed directory, children first.
|
|
; Anything the user put in $INSTDIR - decks, pictures, portable data
|
|
; folders - is not on those lists and survives, and $INSTDIR itself is
|
|
; only removed when nothing else is left in it. An earlier version of this
|
|
; section ran a blanket "RMDir /r $INSTDIR" to keep build-tree artifacts
|
|
; out of a fresh reinstall; that also deleted user decks whenever a user
|
|
; had pointed their data folders into the install directory, so payload
|
|
; purity may never be bought at that price again.
|
|
@CPACK_NSIS_DELETE_FILES@
|
|
@CPACK_NSIS_DELETE_DIRECTORIES@
|
|
|
|
; Written by the installer itself, so absent from the staged payload
|
|
; above; qdebug.txt is app-generated log output next to the executables.
|
|
Delete "$INSTDIR\uninstall.exe"
|
|
Delete "$INSTDIR\portable.dat"
|
|
Delete "$INSTDIR\qdebug.txt"
|
|
|
|
; Succeeds only when no foreign files remain.
|
|
RMDir "$INSTDIR"
|
|
|
|
RMDir /r "$SMPROGRAMS\Cockatrice"
|
|
|
|
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice"
|
|
|
|
; Leftover WER LocalDumps key from pre-Sentry installers (see the installer note
|
|
; above): removing it here covers machines that uninstall without reinstalling.
|
|
; The legacy full-memory dumps go with it; nothing consumes them anymore. Same shell
|
|
; context note as the installer: the per-user dumps folder needs "current".
|
|
DeleteRegKey HKLM "Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\cockatrice.exe"
|
|
SetShellVarContext current
|
|
RMDir /r "$LOCALAPPDATA\CrashDumps\Cockatrice"
|
|
SetShellVarContext all
|
|
|
|
; Only remove the file/protocol associations if we registered them (i.e. the
|
|
; install was not portable) and .cod is still owned by Cockatrice, so we don't
|
|
; clobber a .cod association installed by another application.
|
|
${If} $R1 == 0
|
|
ReadRegStr $0 HKCR ".cod" ""
|
|
${If} $0 == "Cockatrice"
|
|
DeleteRegKey HKCR ".cod"
|
|
DeleteRegKey HKCR "Cockatrice"
|
|
DeleteRegKey HKCR "cockatrice"
|
|
${EndIf}
|
|
${EndIf}
|
|
SectionEnd
|
|
|
|
; unselected because it is /o
|
|
Section /o "un.Configurations, decks, cards, pics" UnSecConfiguration
|
|
SetShellVarContext current
|
|
DeleteRegKey HKCU "Software\Cockatrice"
|
|
|
|
RMDir /r "$LOCALAPPDATA\Cockatrice"
|
|
SectionEnd
|
|
|
|
LangString DESC_SecApplication ${LANG_ENGLISH} "Cockatrice program files"
|
|
LangString DESC_SecStartMenu ${LANG_ENGLISH} "Create start menu items for Cockatrice and Oracle."
|
|
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
|
|
!insertmacro MUI_DESCRIPTION_TEXT ${SecApplication} $(DESC_SecApplication)
|
|
!insertmacro MUI_DESCRIPTION_TEXT ${SecStartMenu} $(DESC_SecStartMenu)
|
|
!insertmacro MUI_FUNCTION_DESCRIPTION_END
|
|
|
|
LangString DESC_UnSecApplication ${LANG_ENGLISH} "Cockatrice program files and start menu items"
|
|
LangString DESC_UnSecConfiguration ${LANG_ENGLISH} "Configurations, decks, card database, pictures"
|
|
!insertmacro MUI_UNFUNCTION_DESCRIPTION_BEGIN
|
|
!insertmacro MUI_DESCRIPTION_TEXT ${UnSecApplication} $(DESC_UnSecApplication)
|
|
!insertmacro MUI_DESCRIPTION_TEXT ${UnSecConfiguration} $(DESC_UnSecConfiguration)
|
|
!insertmacro MUI_UNFUNCTION_DESCRIPTION_END
|