diff --git a/console/installer/installer.nsi b/console/installer/installer.nsi index a7aa422..c1272e0 100644 --- a/console/installer/installer.nsi +++ b/console/installer/installer.nsi @@ -162,6 +162,8 @@ Var dragWy Var gDparam ; 命令行 /D= 指定的安装目录(要在 customInit 的记忆目录之后盖回去) Var keepShortcuts ; 快捷方式保留策略,由安装流程写定(上游在安装段里声明,我们提前到全局) Var dragging +Var pathSet ; 我们最后一次写进「安装位置」输入框的值(用来判断用户有没有手改过) +Var pathKeep ; 计算路径时临时存一份(上游那两个宏会重写 $INSTDIR,用它盖回来) ; ── include ──────────────────────────────────────────────────────────────── @@ -1183,16 +1185,82 @@ Function OnLicNext Call GoNext FunctionEnd +; 当前安装模式对应的「默认目录」(结果放 $R0)。 +; 「为所有用户」= 公共目录:本包是 x64 应用,所以优先 $PROGRAMFILES64(真机期望就是 C:\Program Files\…), +; 32 位系统上退回 $PROGRAMFILES;「仅为我」= %LOCALAPPDATA%\Programs\…(与上游 setInstallModePerUser 一致)。 +Function ModeDefaultDir + ${If} $modeAll == 1 + StrCpy $R0 "$PROGRAMFILES" + !ifdef APP_64 + ${If} ${RunningX64} + StrCpy $R0 "$PROGRAMFILES64" + ${EndIf} + !endif + StrCpy $R0 "$R0\${APP_FILENAME}" + ${Else} + ; 与上游 setInstallModePerUser 同源:优先问 shell 要「用户程序目录」(FOLDERID_UserProgramFiles)。 + ; 不能直接用 $LOCALAPPDATA:在提权/无人值守(session 0,没有加载用户配置)的会话里, + ; NSIS 的 $LOCALAPPDATA 会解析成 C:\ProgramData —— 真机实测就是装到了 + ; 「C:\ProgramData\Programs\Scoreboard_Console」,那不是用户的程序目录。 + StrCpy $R0 "$LOCALAPPDATA\Programs" + Push $0 + Push $1 + Push $2 + StrCpy $2 0 + System::Call 'SHELL32::SHGetKnownFolderPath(g "${FOLDERID_UserProgramFiles}", i ${KF_FLAG_CREATE}, p 0, *p .r2) i .r1' + ${If} $1 == 0 + System::Call 'KERNEL32::lstrcpynW(w .r0, p r2, i ${NSIS_MAX_STRLEN}) p' + StrCpy $R0 "$0" + ${EndIf} + ${If} $2 != 0 + System::Call 'OLE32::CoTaskMemFree(p r2)' + ${EndIf} + Pop $2 + Pop $1 + Pop $0 + StrCpy $R0 "$R0\${APP_FILENAME}" + ${EndIf} +FunctionEnd + +; 用户有没有手改过「安装位置」框?结果放 $R9(1 = 改过)。 +; 判据是「框里的文本 != 我们最后一次写进去的值」——不需要额外的控件通知,浏览/手输都盖得住。 +Function PathTouched + ${NSD_GetText} $hPath $R1 + StrCpy $R9 0 + ${If} $R1 != $pathSet + StrCpy $R9 1 + ${EndIf} +FunctionEnd + +; 切模式时把路径与输入框同步到该模式的默认目录。 +; 策略(说得通的理由):**用户没手改过**才跟着模式变;用户改过就保持他填的, +; 免得「改完路径再点一下单选」把用户刚填的东西悄悄冲掉。 +; 之前这里只改单选状态位图、不动框 —— 于是「选了为所有用户,框里还是用户目录」, +; 真机复验的两条症状(记忆目录看不出、选公共安装却像在装用户目录)都是它引起的。 +Function SyncPathToMode + Call PathTouched + ${If} $R9 == 0 + Call ModeDefaultDir + StrCpy $INSTDIR $R0 + ${If} $hPath != 0 + ${NSD_SetText} $hPath "$INSTDIR" + StrCpy $pathSet "$INSTDIR" + ${EndIf} + ${EndIf} +FunctionEnd + Function OnModeUser StrCpy $modeAll 0 !insertmacro SetBmpBase $hRadio1 "radio-on.bmp" 16 16 !insertmacro SetBmpBase $hRadio2 "radio-off.bmp" 16 16 + Call SyncPathToMode FunctionEnd Function OnModeAll StrCpy $modeAll 1 !insertmacro SetBmpBase $hRadio1 "radio-off.bmp" 16 16 !insertmacro SetBmpBase $hRadio2 "radio-on.bmp" 16 16 + Call SyncPathToMode FunctionEnd ; 「浏览…」:系统文件夹选择框没有 NSIS 包装,用 System::Call 直接调 shell32。 @@ -1213,6 +1281,8 @@ Function OnBrowse ${If} $R2 != "" StrCpy $tmpTxt $R2 ${NSD_SetText} $hPath "$tmpTxt" + ; 浏览选出来的目录也算用户改过:别让切模式把它冲掉 + StrCpy $pathSet "" ${EndIf} ${EndIf} System::Free $R7 @@ -1365,7 +1435,11 @@ Function PageDir Call Chrome !insertmacro NewEdit "${ES_AUTOHSCROLL}|${WS_CHILD}|${WS_VISIBLE}" ${PATH_EDIT_X} ${PATH_EDIT_Y} ${PATH_EDIT_W} ${PATH_EDIT_H} $hPath + ; 进页面时框里显示的就是当前 $INSTDIR:initMultiUser + customInit 已经把「上次装到哪」 + ; (我们自己的记忆键,HKCU 优先、再 HKLM)读进来了,所以这里就是「记忆目录」。 + ; $pathSet 记下我们写的这份值,之后 SyncPathToMode / PageDirLeave 靠它判断用户有没有手改过。 ${NSD_SetText} $hPath "$INSTDIR" + StrCpy $pathSet "$INSTDIR" !insertmacro NewBmpBtn ${BROWSE_X} ${BROWSE_Y} ${BROWSE_W} ${BROWSE_H} $hBrowse "btn-browse-normal.bmp" ${NSD_OnClick} $hBrowse OnBrowse @@ -1414,12 +1488,17 @@ Function PageDirLeave StrCpy $INSTDIR "$INSTDIR\${APP_FILENAME}" ${EndIf} - ; 兜底:万一路径是空的(记忆为空 + 默认值也没算出来),给一个合法的按用户目录, + ; 兜底:万一路径是空的(记忆为空 + 默认值也没算出来),按当前模式给一个合法默认目录, ; 绝不把空 $INSTDIR 带进安装流程(空路径是「点了没反应 / 装到莫名其妙的地方」的经典来源)。 + ; 走 ModeDefaultDir 而不是直接拼 $LOCALAPPDATA —— 后者在无人值守会话里会解析成 C:\ProgramData。 ${If} $INSTDIR == "" - StrCpy $INSTDIR "$LOCALAPPDATA\Programs\${APP_FILENAME}" + Call ModeDefaultDir + StrCpy $INSTDIR "$R0" ${EndIf} + ; 先把定下来的路径存一份:下面这两个上游宏会按注册表记忆/模式默认**重新算 $INSTDIR**, + ; 而这一页的规矩是「框里是什么就装什么」(记忆、模式默认、用户手改、/D= 都已经体现在框里了)。 + StrCpy $pathKeep "$INSTDIR" ${If} $modeAll == 1 ; 「为所有用户安装」。执行级别是 highest,所以**管理员账户的机器上启动时就提过权了**, ; 这里不需要(也不该)再走一次 UAC_RunElevated:中途提权会把窗口藏起来、让用户看见 @@ -1436,6 +1515,8 @@ Function PageDirLeave ${Else} !insertmacro setInstallModePerUser ${EndIf} + ; 盖回框里定下的路径($installMode / SHELL_CONTEXT 已经在宏里设好了,这里只保路径) + StrCpy $INSTDIR "$pathKeep" FunctionEnd ; ── P4 正在安装 ──────────────────────────────────────────────────────────── @@ -1572,6 +1653,22 @@ Function .onInit !ifmacrodef customInit !insertmacro customInit !endif + ; 记忆目录要跟**安装模式**对齐,这才是「原版安装器」的行为:initMultiUser 读的就是该模式对应的键 + ; (为所有用户 → HKLM,仅为我 → HKCU)。构建期生成的 customInit 是「HKCU 的自家记忆优先」, + ; 于是一条更早的用户级记忆会把刚装好的机器级目录顶掉 —— 真机实测:机器上最后一次装在 + ; C:\Program Files\Scoreboard_Console,安装位置页却填着更早的 D:\Program Files\Scoreboard_Console。 + ; 这里按当前模式再读一遍自家记忆键,有值就盖回去;两个 hive 都空时保留 initMultiUser/customInit 的结果。 + ${If} $installMode == "all" + ReadRegStr $R9 HKLM "${SCOREBOARD_INSTALL_KEY}" "InstallLocation" + ${If} $R9 != "" + StrCpy $INSTDIR $R9 + ${EndIf} + ${Else} + ReadRegStr $R9 HKCU "${SCOREBOARD_INSTALL_KEY}" "InstallLocation" + ${If} $R9 != "" + StrCpy $INSTDIR $R9 + ${EndIf} + ${EndIf} ${If} $gDparam != "" StrCpy $INSTDIR $gDparam ${EndIf}