打包与安装器
一条命令出两个产物加一份清单:powershell -ExecutionPolicy Bypass -File tools\build-installer.ps1。
构建链路
脚本分五步:dotnet publish -c Release -o %TEMP%\starcloud-publish(win-x64 自包含)→ 扫产物做合规闸门并生成 NSIS 载荷清单 → 压成便携版 zip → makensis 编译安装器 exe → 生成 latest.json。
Starcloud Minecraft Launcher_0.3.9_x64-portable-20261022-153012.zip
Starcloud Minecraft Launcher_0.3.9_x64-setup-20261022-153012.exe
latest.json时间戳只取一次,保证同一批 zip 和 setup 后缀一致、能配对。
dotnet publish 的产物就是完整可运行目录(WindowsPackageType=None + SelfContained=true + WindowsAppSdkSelfContained=true),目标机器不需要装 .NET 运行时。所以「打包」本身没有 MSIX、没有 WiX,就是 publish + zip + NSIS 套壳。
版本号怎么流到各处
单一来源是 Starcloud.App.csproj 的 <Version>。脚本读 XML 拿到它,传给 NSIS(/DPRODUCT_VERSION= 和 /DPRODUCT_VERSION4=,后者填 VIProductVersion,要求四段),写进 latest.json 的 version 字段,也进了两个产物的文件名。
NSIS 侧 PRODUCT_VERSION 有默认值 0.0.0,但 SRCDIR 缺失直接 !error——静默打出一个空安装器比报错更糟。
图标也要两处
<ApplicationIcon>Assets\AppIcon.ico</ApplicationIcon> 把图标嵌进 exe 资源(资源管理器、任务栏、卸载列表看它),另一条 <Content Include="Assets\AppIcon.ico" CopyToOutputDirectory="PreserveNewest" /> 让文件随输出发布(运行时 AppWindow.SetIcon 按 AppContext.BaseDirectory 加载)。只做前者运行时读不到文件,只做后者 exe 本身没图标。
安装器的三条克制
RequestExecutionLevel user。 整个安装器跑在用户权限下,不弹 UAC。默认目录是 $LOCALAPPDATA\Programs\Starcloud,卸载也不需要提权。
只写 HKCU,不写 HKLM。 登记位置是 HKCU\Software\Starcloud\Starcloud Minecraft Launcher(安装目录)和 HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\...(卸载项 9 个字段,控制面板「应用」能正常显示)。
不写自启动,不写协议关联。 这两件事一旦做了就没法安静地撤销——用户会收到「为什么我的电脑开机就多了一个程序」这类提问,而它们对启动器本身没有任何必要。
升级时沿用原目录:.onInit 里 ReadRegStr $0 HKCU "${APP_KEY}" "",有值就 StrCpy $INSTDIR $0。装过就不会变成第二份。
安装器脚本的其它坑
升级清理要先认产品痕迹。 铺文件前 RMDir /r "$INSTDIR",但只在目录里有 uninstall.exe / Starcloud.App.exe / starcloud-launcher.exe 任一痕迹时才做——不认痕迹就会把用户自选目录里的其它文件一起删掉。盘符根目录(E:\ 这类,长度为 3)禁止整目录清空。
静默安装必须能自己结束进程。 旧的 FindWindow + 重试框在 /S 时没人点弹窗,安装器就一直挂着,表现为「自动更新卡死」。现在直接 taskkill /F /T /IM Starcloud.App.exe(顺带 Starcloud.Bridge.exe)。
卸载器的工作目录必须移出安装目录。 un.onInit 里 SetOutPath "$TEMP"——cwd 占住 $INSTDIR 的话 RMDir /r 删不掉,NSIS 会一直卡在删除阶段,表现为「卸载完全卡死」。
NSIS 的位图是三档 DPI。 100 / 150 / 200 三套素材,125% 这类非整档缩放会把底图和控件摆错位。所以窗口缩放系数被对齐到档位,位图 1:1 贴上。
XAML 编译需要 MSVC 工具链
Windows App SDK 的 XAML 编译器会去 <VS 安装根>\VC\Tools\MSVC 找 vcmeta.dll。那台 VS 没装 C++ 组件时,清掉 obj/ 重新编译会报 GetLatestMSVCVersion 任务意外失败。显式指定一个装了 C++ 工具的目录即可:
dotnet build dotnet/src/Starcloud.App -p:VCToolsInstallDir="C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.44.35207\"核心坑:关窗没停定时器
症状:窗口关了,进程还在。没有窗口,内存还占着,Starcloud.App.exe 被锁住。后果:下一次 dotnet build 报 MSB3021——文件正被另一个进程使用。
根因:WinUI 3 的退出流程要等 DispatcherQueue 停下来,而循环的 DispatcherQueueTimer 会让它一直有活可干——窗口已经关了,消息循环永不返回。
正确做法:MainWindow.xaml.cs 的 StopBackgroundTimers() 里停掉所有常驻循环定时器再退出——_taskRefresh.Stop()(300ms 调度中心刷新)、NovaRuntime.StopTimers()(Nova 游戏内观察循环),设备链路的 Stop() 丢到后台线程(等在 UI 线程就是「关窗卡一下」)。
定时器的 Interval 必须显式设
DispatcherQueueTimer 的默认 Interval 是 0,意思是「尽最快速度」——它会自旋。仓库里每一处 new DispatcherTimer() 后面都紧跟一行 Interval = TimeSpan.FromMilliseconds(...),不写就自旋。