diff --git a/.github/workflows/README.md b/.github/workflows/README.md
index fc62f73c..643cb9a5 100644
--- a/.github/workflows/README.md
+++ b/.github/workflows/README.md
@@ -1,108 +1,128 @@
-# GitHub Actions CI/CD 配置说明
+# CI/CD 工作流说明
-## 概述
+## 文件总览
-为VirtualPaper项目配置了完整的CI/CD流程,包含两个工作流文件:
+| 文件 | 触发时机 | 职责 |
+|---|---|---|
+| `pre-publish-branch-ci-check.yml` | push / PR → dev · release · bugfix | 构建 + 单元测试 |
+| `branch-protection.yml` | PR → main | 校验源分支合法性 |
+| `auto-version-release.yml` | PR 合并到 main | 版本递增 · 打包 · 冒烟测试 · 发布 |
-- **dev-ci.yml**: 当代码推送到dev分支时自动运行4个测试项目,并创建状态检查
-- **branch-protection.yml**: 确保只有dev分支可以合并到main分支
+---
-## 工作流程
+## 整体流程
-### 1. Dev分支持续集成 (dev-ci.yml)
+```
+ feature/* ──► dev ──┐
+ │ CI 通过后创建 PR → main
+ release ──────────── ┤
+ │
+ bugfix ──────────── ┘
+ │
+ ▼
+ main ──► 自动构建打包 ──► 草稿 Release
+ │
+ 人工验证后发布
+```
-**触发条件:**
-- 推送到dev分支
-- 创建针对dev分支的PR
+---
-**执行步骤:**
-1. 设置.NET环境(8.0.x)
-2. 恢复项目依赖
-3. 构建解决方案
-4. 按顺序运行4个测试项目:
- - VirtualPaper.Core.Test
- - VirtualPaper.UI.Test
- - VirtualPaper.ML.Test
- - VirtualPaper.Shader.Test
-5. 发布测试结果报告
-6. 创建commit状态检查(context: `ci/dev-tests`)
+## 工作流一:Pre-publish CI
-### 2. 分支保护检查 (branch-protection.yml)
+> **文件:** `pre-publish-branch-ci-check.yml`
+> **触发:** push 或 PR 到 `dev` / `release` / `bugfix`
-**触发条件:**
-- 创建针对main分支的PR
-- 更新针对main分支的PR
+对预发布分支进行持续集成,确保代码在合并到 main 之前质量达标。
-**检查逻辑:**
-1. 验证源分支是否为dev
-2. 如果不是dev分支,创建失败状态并阻止合并
-3. 在PR中添加友好的提示评论
+**执行内容:**
+1. 构建整个解决方案(Release 配置)
+2. 并行运行 4 项单元测试:Core · UI · ML · Shader
+3. 汇总测试结果,写入 commit status `ci-check/pre-publish-tests`
-## 分支保护规则设置
+该 status 是合并到 main 的**必需检查项**之一。
-为确保只有测试通过的代码才能合并到main分支,需要在GitHub仓库中设置分支保护规则:
+---
-### 设置步骤:
+## 工作流二:分支保护
-1. 进入GitHub仓库 → Settings → Branches
-2. 为main分支添加或编辑保护规则:
+> **文件:** `branch-protection.yml`
+> **触发:** 创建 / 更新 PR → `main`
-```
-分支名称模式: main
+只允许 `dev`、`release`、`bugfix` 三个分支向 main 发起 PR,其他分支一律拦截。
-☑️ Require a pull request before merging
- ☑️ Require approvals: 1
- ☑️ Dismiss stale reviews when new commits are pushed
+| 源分支 | 版本变化 | 说明 |
+|---|---|---|
+| `release` | Minor +1 | 0.4.x.x → 0.5.0.0 |
+| `dev` | Build +1 | 0.5.2.x → 0.5.3.0 |
+| `bugfix` | Revision +1 | 0.5.3.0 → 0.5.3.1 |
-☑️ Require status checks to pass before merging
- ☑️ Require branches to be up to date before merging
- 添加必需的状态检查:
- - ci/dev-tests
- - branch-protection/source-validation
+---
-☑️ Include administrators
-```
+## 工作流三:自动发布
-## 工作原理
+> **文件:** `auto-version-release.yml`
+> **触发:** PR 成功合并到 `main`
-1. **开发阶段**:
- - Feature分支合并到dev时,自动运行测试
- - 只有dev分支可以创建到main的PR
-2. **发布阶段**:
- - 创建dev→main的PR时,检查两个状态:
- - `ci/dev-tests`: dev分支最新commit的测试结果
- - `branch-protection/source-validation`: 源分支验证结果
-3. **合并控制**:
- - 只有两个状态都为success时,PR才能被合并
+### 执行流程
+```
+prepare
+ │ 计算新版本号,记录各分支 SHA,捕获 PR 标题和正文
+ ▼
+build
+ │ MSBuild Release 构建,产物上传为 Artifact
+ ▼
+package
+ │ InnoSetup 编译安装包,上传为 Artifact(保留 30 天)
+ ▼
+smoke_test ← 失败则终止并回滚
+ │ 静默安装 → 启动主程序 → 等待 UI 被拉起(最多 20s)
+ │ 若未自动拉起:兜底手动启动 UI
+ │ 再次尝试启动 UI → 断言只有 1 个 UI 进程(单例验证)
+ ▼
+bump
+ │ 更新 csproj / InnoSetup / README badge 版本号并提交到 main
+ ▼
+sync
+ │ 将版本文件同步到 release / dev / bugfix 分支
+ ▼
+summary
+ │ 打印构建摘要
+ ▼
+release(草稿)
+ 创建 Draft GitHub Release
+ - Tag 和标题:v{版本号}
+ - 正文:本次 PR 的描述
+ - 附件:安装包 exe
+ - 状态:草稿,不公开,等待人工验证后发布
+```
-## 使用流程
+> 任意步骤失败时,`rollback` job 自动触发,对 main 及三个预发布分支执行 `git revert`。
-### 日常开发:
-1. Feature分支 → dev分支(触发测试)
-2. 查看dev分支commit旁的测试状态
+---
-### 发布流程:
-1. 创建dev → main的PR
-2. 系统自动检查:
- - 源分支是否为dev(`branch-protection/source-validation`)
- - dev分支最新commit的测试结果(`ci/dev-tests`)
-3. 两个检查都通过时才能合并
+## 发版操作步骤
-## 错误处理
+```
+1. 在 dev / release / bugfix 分支完成开发
+2. 确认 CI 全绿(ci-check/pre-publish-tests)
+3. 创建 PR → main,等待审核通过并合并
+4. CI 自动完成:构建 → 打包 → 冒烟测试 → 提交版本号 → 创建草稿 Release
+5. 在 GitHub Releases 页面下载草稿附件,本地安装验证
+6. 确认无误后点击 Edit → Publish release 正式发布
+```
-### 如果从非dev分支创建PR到main:
-- ❌ 工作流会失败并创建失败状态
-- 📝 PR中会显示友好的错误说明
-- 🔒 PR会被阻止合并
+---
-### 如果dev分支测试失败:
-- ❌ `ci/dev-tests`状态为failure
-- 🔒 PR会被阻止合并
-- 🛠️ 需要修复测试问题后重新推送到dev
+## 分支保护规则(需手动配置)
-## 注意事项
+> Settings → Branches → Add branch protection rule → `main`
-- 确保dev分支有最新的测试结果
-- 分支保护规则中需要添加两个状态检查
-- 只有dev分支可以合并到main分支
\ No newline at end of file
+```
+☑ Require a pull request before merging
+☑ Require status checks to pass before merging
+ 必需检查:
+ - ci-check/pre-publish-tests
+ - Check Source Branch
+☑ Include administrators
+```
diff --git a/.github/workflows/auto-version-release.yml b/.github/workflows/auto-version-release.yml
index 4955bfa5..edf6870d 100644
--- a/.github/workflows/auto-version-release.yml
+++ b/.github/workflows/auto-version-release.yml
@@ -1,5 +1,5 @@
-name: Auto Version & Build on Main Merge
-run-name: "[Auto] Pre-publish | ${{ github.event.pull_request.head.ref }} -> main | ${{ github.event.pull_request.title }}"
+name: Auto Sync and Publish
+run-name: "[Auto] Publish | ${{ github.event.pull_request.head.ref }} -> main | ${{ github.event.pull_request.title }}"
on:
pull_request:
@@ -26,15 +26,18 @@ jobs:
runs-on: windows-latest
if: github.event.pull_request.merged == true
outputs:
- source_branch: ${{ steps.branch_info.outputs.source_branch }}
- increment_type: ${{ steps.branch_info.outputs.increment_type }}
+ source_branch: ${{ steps.branch_info.outputs.source_branch }}
+ increment_type: ${{ steps.branch_info.outputs.increment_type }}
current_version: ${{ steps.version.outputs.current_version }}
- new_version: ${{ steps.version.outputs.new_version }}
+ new_version: ${{ steps.version.outputs.new_version }}
new_version_text: ${{ steps.version.outputs.new_version_text }}
- main_sha: ${{ steps.shas.outputs.main_sha }}
- dev_sha: ${{ steps.shas.outputs.dev_sha }}
- bugfix_sha: ${{ steps.shas.outputs.bugfix_sha }}
- release_sha: ${{ steps.shas.outputs.release_sha }}
+ current_version_text: ${{ steps.version.outputs.current_version_text }}
+ main_sha: ${{ steps.shas.outputs.main_sha }}
+ dev_sha: ${{ steps.shas.outputs.dev_sha }}
+ bugfix_sha: ${{ steps.shas.outputs.bugfix_sha }}
+ release_sha: ${{ steps.shas.outputs.release_sha }}
+ pr_title: ${{ steps.pr_info.outputs.pr_title }}
+ pr_body: ${{ steps.pr_info.outputs.pr_body }}
steps:
- name: Checkout with full history
@@ -69,7 +72,7 @@ jobs:
env:
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
- $sourceBranch = $env:PR_HEAD_REF
+ $sourceBranch = $env:PR_HEAD_REF
$incrementType = "patch"
if ($sourceBranch -eq "release") {
@@ -85,15 +88,15 @@ jobs:
Write-Host "Unknown source branch: $sourceBranch - defaulting to patch"
}
- Write-Output "source_branch=$sourceBranch" >> $env:GITHUB_OUTPUT
- Write-Output "increment_type=$incrementType" >> $env:GITHUB_OUTPUT
+ Write-Output "source_branch=$sourceBranch" >> $env:GITHUB_OUTPUT
+ Write-Output "increment_type=$incrementType" >> $env:GITHUB_OUTPUT
- name: Calculate new version
id: version
shell: powershell
run: |
- $csprojPath = "src/VirtualPaper/VirtualPaper.csproj"
- $content = Get-Content $csprojPath -Raw
+ $csprojPath = "src/VirtualPaper/VirtualPaper.csproj"
+ $content = Get-Content $csprojPath -Raw
$versionMatch = [regex]::Match($content, '(\d+)\.(\d+)\.(\d+)\.(\d+)')
if (-not $versionMatch.Success) {
@@ -101,33 +104,56 @@ jobs:
exit 1
}
- $major = [int]$versionMatch.Groups[1].Value
- $minor = [int]$versionMatch.Groups[2].Value
- $build = [int]$versionMatch.Groups[3].Value
+ $major = [int]$versionMatch.Groups[1].Value
+ $minor = [int]$versionMatch.Groups[2].Value
+ $build = [int]$versionMatch.Groups[3].Value
$revision = [int]$versionMatch.Groups[4].Value
$currentVersion = "$major.$minor.$build.$revision"
$incrementType = "${{ steps.branch_info.outputs.increment_type }}"
if ($incrementType -eq "feature") {
- $minor = $minor + 1 # 第2位 +1
- $build = 0 # 第3位 清零
- $revision = 0 # 第4位 清零
+ $minor = $minor + 1 # 第2位 +1
+ $build = 0 # 第3位 清零
+ $revision = 0 # 第4位 清零
} elseif ($incrementType -eq "minor") {
- $build = $build + 1 # 第3位 +1
- $revision = 0 # 第4位 清零
+ $build = $build + 1 # 第3位 +1
+ $revision = 0 # 第4位 清零
} else {
- $revision = $revision + 1 # 第4位 +1
+ $revision = $revision + 1 # 第4位 +1
}
- $newVersion = "$major.$minor.$build.$revision"
+ $newVersion = "$major.$minor.$build.$revision"
$newVersionText = "$major$minor$build$revision"
Write-Host "Version: $currentVersion -> $newVersion"
- Write-Output "current_version=$currentVersion" >> $env:GITHUB_OUTPUT
- Write-Output "new_version=$newVersion" >> $env:GITHUB_OUTPUT
- Write-Output "new_version_text=$newVersionText" >> $env:GITHUB_OUTPUT
+ Write-Output "current_version=$currentVersion" >> $env:GITHUB_OUTPUT
+ Write-Output "new_version=$newVersion" >> $env:GITHUB_OUTPUT
+ Write-Output "new_version_text=$newVersionText" >> $env:GITHUB_OUTPUT
+
+ - name: Capture PR title and body
+ id: pr_info
+ shell: powershell
+ env:
+ PR_TITLE: ${{ github.event.pull_request.title }}
+ PR_BODY: ${{ github.event.pull_request.body }}
+ run: |
+ # PowerShell 5.1 的 >> 和 Out-File -Encoding utf8 均会写入 UTF-16 LE 或带 BOM 的 UTF-8
+ # $GITHUB_OUTPUT 必须是无 BOM 的 UTF-8,使用 .NET API 显式指定
+ $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
+
+ # PR title(单行)
+ [System.IO.File]::AppendAllText($env:GITHUB_OUTPUT, "pr_title=$env:PR_TITLE`n", $utf8NoBom)
+ Write-Host "PR title: $env:PR_TITLE"
+
+ # PR body(多行,heredoc delimiter 格式,完整保留换行和特殊字符)
+ $delimiter = [System.Guid]::NewGuid().ToString("N")
+ $body = $env:PR_BODY -replace "`r`n", "`n" # 统一为 LF,避免 CRLF 写入 GITHUB_OUTPUT
+ [System.IO.File]::AppendAllText($env:GITHUB_OUTPUT, "pr_body<<$delimiter`n", $utf8NoBom)
+ [System.IO.File]::AppendAllText($env:GITHUB_OUTPUT, "$body`n", $utf8NoBom)
+ [System.IO.File]::AppendAllText($env:GITHUB_OUTPUT, "$delimiter`n", $utf8NoBom)
+ Write-Host "PR body captured ($($body.Length) chars)"
# ─────────────────────────────────────────────────────────────
# Job 2: 编译 .NET Release
@@ -159,17 +185,17 @@ jobs:
shell: powershell
run: |
$csprojPath = "src/VirtualPaper/VirtualPaper.csproj"
- $content = Get-Content $csprojPath -Raw
+ $content = Get-Content $csprojPath -Raw
$newVersion = "${{ needs.prepare.outputs.new_version }}"
$newContent = $content -replace '\d+\.\d+\.\d+\.\d+', "$newVersion"
Set-Content $csprojPath -Value $newContent -NoNewline
Write-Host "Patched AssemblyVersion to $newVersion (build-time only)"
- - name: Restore dependencies
- run: dotnet restore src/VirtualPaper.sln
+ - name: Setup MSBuild
+ uses: microsoft/setup-msbuild@v2
- - name: Build Release
- run: dotnet build src/VirtualPaper.sln --configuration Release --no-restore
+ - name: Rebuild solution
+ run: msbuild src/VirtualPaper.sln /t:Rebuild /p:Configuration=Release /p:Platform="Any CPU" /restore /m
- name: Upload build output
uses: actions/upload-artifact@v4
@@ -202,35 +228,50 @@ jobs:
path: src/
- name: Install Inno Setup 6.7.1
- uses: Minionguyjpro/Inno-Setup-Action@v1.2.4
- with:
- version: 6.7.1
- options: /VERYSILENT
+ shell: powershell
+ run: choco install innosetup --version=6.7.1 --no-progress -y
- name: Patch version in InnoSetup script (local only, not committed)
shell: powershell
run: |
- $setupPath = "InnoSetup/setup.iss"
- $content = Get-Content $setupPath -Raw
- $newVersion = "${{ needs.prepare.outputs.new_version }}"
+ $setupPath = "InnoSetup/setup.iss"
+ $content = Get-Content $setupPath -Raw
+ $newVersion = "${{ needs.prepare.outputs.new_version }}"
$newVersionText = "${{ needs.prepare.outputs.new_version_text }}"
- $newContent = $content -replace '#define MyAppVersion "[\d\.]+"', "#define MyAppVersion `"$newVersion`""
+ $newContent = $content -replace '#define MyAppVersion "[\d\.]+"', "#define MyAppVersion `"$newVersion`""
$newContent = $newContent -replace '#define MyAppVersionText "[\d]+"', "#define MyAppVersionText `"$newVersionText`""
Set-Content $setupPath -Value $newContent -NoNewline
Write-Host "Patched InnoSetup version to $newVersion (build-time only)"
+ - name: Download InnoDependencyInstaller
+ shell: powershell
+ run: |
+ $url = "https://raw.githubusercontent.com/DomGries/InnoDependencyInstaller/master/CodeDependencies.iss"
+ $dest = "InnoSetup\CodeDependencies.iss"
+ Invoke-WebRequest -Uri $url -OutFile $dest -UseBasicParsing
+ Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " CodeDependencies.iss downloaded"
+
- name: Compile installer
id: build_installer
- uses: Minionguyjpro/Inno-Setup-Action@v1.2.4
- with:
- path: InnoSetup/setup.iss
- options: /O+
+ shell: powershell
+ run: |
+ $iscc = "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe"
+ if (-not (Test-Path $iscc)) {
+ Write-Error "ISCC.exe not found at: $iscc"
+ exit 1
+ }
+ & $iscc "InnoSetup\setup.iss" /O+
+ if ($LASTEXITCODE -ne 0) {
+ Write-Error "Inno Setup compilation failed (exit code $LASTEXITCODE)"
+ exit 1
+ }
+ Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " Installer compiled successfully"
- name: Record installer info
id: installer_info
shell: powershell
run: |
- $outputDir = "InnoSetup\Output"
+ $outputDir = "InnoSetup\Output"
$installerFiles = Get-ChildItem -Path $outputDir -Filter "*.exe" -ErrorAction SilentlyContinue
if ($installerFiles.Count -eq 0) {
Write-Error "No installer found in output directory!"
@@ -241,33 +282,230 @@ jobs:
Write-Output "installer_path=$installerPath" >> $env:GITHUB_OUTPUT
Write-Output "installer_name=$($installerFiles[0].Name)" >> $env:GITHUB_OUTPUT
- - name: Upload installer as artifact
+ - name: Generate SHA256 for installer
+ shell: powershell
+ run: |
+ $hash = (Get-FileHash -Path "${{ steps.installer_info.outputs.installer_path }}" -Algorithm SHA256).Hash.ToLower()
+ Set-Content -Path "${{ github.workspace }}\InnoSetup\Output\SHA256.txt" -Value $hash
+
+ - name: Upload installer and SHA256 as artifact
uses: actions/upload-artifact@v4
with:
- name: VirtualPaper-installer-v${{ needs.prepare.outputs.new_version }}
- path: ${{ steps.installer_info.outputs.installer_path }}
+ name: VirtualPaper-Installer-v${{ needs.prepare.outputs.new_version }}
+ path: |
+ ${{ steps.installer_info.outputs.installer_path }}
+ InnoSetup/Output/SHA256.txt
retention-days: 30
# ─────────────────────────────────────────────────────────────
- # Job 4: 提交版本号变更到 main(安装包制作成功后才提交)
+ # Job 4: 安装包冒烟测试(静默安装 + 启动验证)
+ # 阶段 1 - 等待 VirtualPaper.exe 自动拉起 UI(最多 10s)
+ # 阶段 2 - 兜底:若未自动拉起则手动启动 UI
+ # 阶段 3 - 再次尝试启动 UI,断言全局只有 1 个 UI 进程(单例守卫)
+ # 阶段 4 - 验证 UI 无法在主进程未启动的情况下独立运行(应弹出错误 MessageBox 并阻塞)
# ─────────────────────────────────────────────────────────────
- publish:
- name: Commit Version to Main
+ smoke_test:
+ name: Smoke Test Installer
runs-on: windows-latest
needs: [prepare, package]
+ steps:
+ - name: Setup .NET (app runtime dependency)
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '8.0.x'
+
+ - name: Download installer artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: VirtualPaper-Installer-v${{ needs.prepare.outputs.new_version }}
+ path: dist/
+
+ - name: Silent install
+ id: install
+ shell: powershell
+ run: |
+ $installer = Get-ChildItem "dist\" -Filter "*.exe" | Select-Object -First 1
+ if (-not $installer) {
+ Write-Error "No installer found in dist\"
+ exit 1
+ }
+ $installDir = "C:\VirtualPaperSmokeTest"
+ Write-Host "Installer : $($installer.FullName)"
+ Write-Host "Target dir: $installDir"
+ $proc = Start-Process -FilePath $installer.FullName `
+ -ArgumentList "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /DIR=`"$installDir`"" `
+ -Wait -PassThru
+ if ($proc.ExitCode -ne 0) {
+ Write-Error "Installer failed with exit code $($proc.ExitCode)"
+ exit 1
+ }
+ Write-Output "install_dir=$installDir" >> $env:GITHUB_OUTPUT
+ Write-Host "[OK] Installation completed"
+
+ - name: Verify key files exist
+ shell: powershell
+ run: |
+ $base = "${{ steps.install.outputs.install_dir }}"
+ $required = @(
+ "$base\VirtualPaper.exe",
+ "$base\Plugins\UI\VirtualPaper.UI.exe"
+ )
+ $failed = $false
+ foreach ($f in $required) {
+ if (Test-Path $f) {
+ Write-Host "[OK] $f"
+ } else {
+ Write-Host "[MISSING] $f" -ForegroundColor Red
+ $failed = $true
+ }
+ }
+ if ($failed) {
+ Write-Error "Required files missing after installation"
+ exit 1
+ }
+
+ - name: Launch and verify UI process
+ shell: powershell
+ run: |
+ $installDir = "${{ steps.install.outputs.install_dir }}"
+ $mainExe = "$installDir\VirtualPaper.exe"
+ $uiExe = "$installDir\Plugins\UI\VirtualPaper.UI.exe"
+
+ # ── 阶段 1:启动主程序,等待其自动拉起 UI(最多 10s)──────────────
+ Write-Host "Launching: $mainExe"
+ $main = Start-Process -FilePath $mainExe -PassThru -ErrorAction Stop
+
+ $uiProc = $null
+ $autoWait = 10
+ for ($i = 1; $i -le $autoWait; $i++) {
+ Start-Sleep -Seconds 1
+ $main.Refresh()
+ if ($main.HasExited) {
+ Write-Error "VirtualPaper.exe exited unexpectedly after ${i}s (exit code: $($main.ExitCode))"
+ exit 1
+ }
+ $uiProc = Get-Process -Name "VirtualPaper.UI" -ErrorAction SilentlyContinue | Select-Object -First 1
+ if ($uiProc) {
+ Write-Host "[OK] VirtualPaper.UI auto-started after ${i}s (PID: $($uiProc.Id))"
+ break
+ }
+ Write-Host " [wait] ${i}s - waiting for VirtualPaper.UI to be auto-spawned..."
+ }
+
+ # ── 阶段 2:兜底 - 若未自动拉起则手动启动 UI ──────────────────────
+ if (-not $uiProc) {
+ Write-Host "[FALLBACK] UI not auto-spawned after ${autoWait}s, launching manually: $uiExe"
+ Start-Process -FilePath $uiExe -ErrorAction Stop
+ Start-Sleep -Seconds 5
+ $uiProc = Get-Process -Name "VirtualPaper.UI" -ErrorAction SilentlyContinue | Select-Object -First 1
+ if (-not $uiProc) {
+ Write-Error "VirtualPaper.UI.exe failed to start even when launched manually"
+ taskkill /F /T /PID $main.Id 2>$null
+ exit 1
+ }
+ Write-Host "[OK] VirtualPaper.UI started via fallback (PID: $($uiProc.Id))"
+ }
+
+ # ── 阶段 3:单例守卫验证 - 再次启动 UI,期望仍只有 1 个进程 ────────
+ Write-Host "Verifying singleton: launching VirtualPaper.UI again..."
+ Start-Process -FilePath $uiExe -ErrorAction SilentlyContinue
+ Start-Sleep -Seconds 3
+
+ $uiProcs = @(Get-Process -Name "VirtualPaper.UI" -ErrorAction SilentlyContinue)
+ if ($uiProcs.Count -ne 1) {
+ Write-Error "Singleton check FAILED: expected 1 VirtualPaper.UI process, found $($uiProcs.Count)"
+ taskkill /F /T /PID $main.Id 2>$null
+ Get-Process -Name "VirtualPaper.UI" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
+ exit 1
+ }
+ Write-Host "[OK] Singleton check passed: only 1 VirtualPaper.UI process (PID: $($uiProcs[0].Id))"
+
+ # ── 清理 ───────────────────────────────────────────────────────────
+ taskkill /F /T /PID $main.Id 2>$null
+ Get-Process -Name "VirtualPaper.UI" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
+ Write-Host "[OK] Smoke test passed"
+
+ - name: Verify UI cannot start without main process
+ shell: powershell
+ run: |
+ $installDir = "${{ steps.install.outputs.install_dir }}"
+ $uiExe = "$installDir\Plugins\UI\VirtualPaper.UI.exe"
+
+ # 前置清理:确保主进程与 UI 进程均未运行(强杀残留)
+ @("VirtualPaper", "VirtualPaper.UI") | ForEach-Object {
+ $procs = Get-Process -Name $_ -ErrorAction SilentlyContinue
+ if ($procs) {
+ Write-Host "[CLEANUP] Killing residual process: $_"
+ $procs | Stop-Process -Force -ErrorAction SilentlyContinue
+ }
+ }
+ Start-Sleep -Seconds 1
+
+ # ── 直接启动 UI(不启动主进程)──────────────────────────────────────
+ Write-Host "Launching VirtualPaper.UI.exe without main process..."
+ $uiProc = Start-Process -FilePath $uiExe -PassThru -ErrorAction Stop
+
+ # 等待足够时间让 MessageBox 弹出
+ Start-Sleep -Seconds 3
+
+ # ── 断言:进程应仍在运行(被 MessageBox 阻塞,未正常退出)────────────
+ $uiProc.Refresh()
+ if ($uiProc.HasExited) {
+ Write-Error "VirtualPaper.UI exited on its own - expected it to be blocked by a MessageBox"
+ exit 1
+ }
+
+ # ── 进一步断言:确认是 MessageBox(#32770)而非 UI 正常启动 ──────────
+ # 通过 UIAutomation 枚举该进程的顶层窗口并读取 Win32 ClassName
+ # MessageBox 的 ClassName 固定为 #32770,无需注入 C# 代码
+ Add-Type -AssemblyName UIAutomationClient
+ Add-Type -AssemblyName UIAutomationTypes
+
+ $desktop = [System.Windows.Automation.AutomationElement]::RootElement
+ $pidCond = New-Object System.Windows.Automation.PropertyCondition(
+ [System.Windows.Automation.AutomationElement]::ProcessIdProperty,
+ $uiProc.Id)
+ $wins = $desktop.FindAll([System.Windows.Automation.TreeScope]::Children, $pidCond)
+ $classes = @($wins | ForEach-Object { $_.Current.ClassName })
+ Write-Host " Visible window classes: $($classes -join ', ')"
+
+ if (-not ($classes -contains '#32770')) {
+ Write-Error "Expected a MessageBox (#32770) but none found - UI may have started normally, guard logic broken"
+ Stop-Process -Id $uiProc.Id -Force -ErrorAction SilentlyContinue
+ exit 1
+ }
+ Write-Host "[OK] VirtualPaper.UI is blocked by a MessageBox (#32770) as expected (PID: $($uiProc.Id))"
+
+ # ── 清理:强杀被阻塞的 UI 进程 ──────────────────────────────────────
+ Stop-Process -Id $uiProc.Id -Force -ErrorAction SilentlyContinue
+ Start-Sleep -Seconds 1
+ $uiProc.Refresh()
+ if (-not $uiProc.HasExited) {
+ taskkill /F /PID $uiProc.Id 2>$null
+ }
+ Write-Host "[OK] Standalone-UI guard test passed"
+
+ # ─────────────────────────────────────────────────────────────
+ # Job 5: 将版本号递增变更提交到 main(冒烟测试通过后才提交)
+ # ─────────────────────────────────────────────────────────────
+ bump:
+ name: Bump Version to main
+ runs-on: windows-latest
+ needs: [prepare, package, smoke_test]
+
steps:
- name: Checkout main with full history
uses: actions/checkout@v4
with:
- token: ${{ secrets.GITHUB_TOKEN }}
+ token: ${{ secrets.RELEASE_PAT }}
fetch-depth: 0
- name: Update version in csproj
shell: powershell
run: |
$csprojPath = "src/VirtualPaper/VirtualPaper.csproj"
- $content = Get-Content $csprojPath -Raw
+ $content = Get-Content $csprojPath -Raw
$newVersion = "${{ needs.prepare.outputs.new_version }}"
$newContent = $content -replace '\d+\.\d+\.\d+\.\d+', "$newVersion"
Set-Content $csprojPath -Value $newContent -NoNewline
@@ -276,11 +514,11 @@ jobs:
- name: Update version in InnoSetup script
shell: powershell
run: |
- $setupPath = "InnoSetup/setup.iss"
- $content = Get-Content $setupPath -Raw
- $newVersion = "${{ needs.prepare.outputs.new_version }}"
+ $setupPath = "InnoSetup/setup.iss"
+ $content = Get-Content $setupPath -Raw
+ $newVersion = "${{ needs.prepare.outputs.new_version }}"
$newVersionText = "${{ needs.prepare.outputs.new_version_text }}"
- $newContent = $content -replace '#define MyAppVersion "[\d\.]+"', "#define MyAppVersion `"$newVersion`""
+ $newContent = $content -replace '#define MyAppVersion "[\d\.]+"', "#define MyAppVersion `"$newVersion`""
$newContent = $newContent -replace '#define MyAppVersionText "[\d]+"', "#define MyAppVersionText `"$newVersionText`""
Set-Content $setupPath -Value $newContent -NoNewline
Write-Host "Updated InnoSetup version to: $newVersion"
@@ -289,7 +527,7 @@ jobs:
shell: powershell
run: |
$readmePath = "README.md"
- $content = Get-Content $readmePath -Raw
+ $content = Get-Content $readmePath -Raw
$newVersion = "${{ needs.prepare.outputs.new_version }}"
$newContent = $content -replace '(img\.shields\.io/badge/release-v)[\d\.]+(-blue)', "`${1}$newVersion`$2"
Set-Content $readmePath -Value $newContent -NoNewline
@@ -306,57 +544,70 @@ jobs:
Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " Version ${{ needs.prepare.outputs.new_version }} committed to main"
# ─────────────────────────────────────────────────────────────
- # Job 5: 将版本号同步回源分支(dev / bugfix)
+ # Job 6: 将版本号同步到 pre-publish branches
# ─────────────────────────────────────────────────────────────
sync:
- name: Sync Version to Source Branch
+ name: Sync Version to Pre-publish Branches
runs-on: windows-latest
- needs: [prepare, publish]
+ needs: [prepare, bump]
steps:
- name: Checkout with full history
uses: actions/checkout@v4
with:
- token: ${{ secrets.GITHUB_TOKEN }}
+ token: ${{ secrets.RELEASE_PAT }}
fetch-depth: 0
- - name: Sync to source branch
+ - name: Sync version files to pre-publish branches
shell: powershell
run: |
- $sourceBranch = "${{ needs.prepare.outputs.source_branch }}"
+ $newVersion = "${{ needs.prepare.outputs.new_version }}"
- if ($sourceBranch -eq "dev" -or $sourceBranch -eq "bugfix" -or $sourceBranch -eq "release") {
- Write-Host "Syncing version back to $sourceBranch..."
- git config --local user.email "action@github.com"
- git config --local user.name "GitHub Action"
+ git config --local user.email "action@github.com"
+ git config --local user.name "GitHub Action"
- # 必须先 fetch,确保 origin/main 拿到 publish job 刚推送的最新 commit
- $ErrorActionPreference = 'Continue'
- git fetch origin main 2>&1 | Out-Null
- $ErrorActionPreference = 'Stop'
+ # 确保拿到 bump job 刚推送的最新 main
+ $ErrorActionPreference = 'Continue'
+ git fetch origin 2>&1 | Out-Null
+ $ErrorActionPreference = 'Stop'
- git checkout -B $sourceBranch origin/$sourceBranch
- # 用 origin/main 而不是 local main,避免本地引用未更新导致分支分叉
- git merge origin/main --ff-only
- git push origin $sourceBranch
- Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " Version synced to $sourceBranch"
- } else {
- Write-Host "Unknown source branch: $sourceBranch, skipping sync"
+ foreach ($branch in @("release", "dev", "bugfix")) {
+ Write-Host ""
+ Write-Host "--- Syncing version to $branch ---"
+
+ git checkout -B $branch origin/$branch
+
+ # 直接从 origin/main 取版本相关文件,无需 merge,不产生冲突
+ git checkout origin/main -- src/VirtualPaper/VirtualPaper.csproj InnoSetup/setup.iss README.md
+ Write-Host " [OK] version files pulled from origin/main"
+
+ git add src/VirtualPaper/VirtualPaper.csproj InnoSetup/setup.iss README.md
+
+ # 若无变更则跳过 commit(分支本来就在最新版本时)
+ $status = git status --porcelain
+ if ($status) {
+ git commit -m "[Github CI] chore: sync version $newVersion from main [skip ci]"
+ git push origin $branch
+ Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " Version $newVersion synced to $branch"
+ } else {
+ Write-Host "[SKIP] $branch already at version $newVersion"
+ }
}
# ─────────────────────────────────────────────────────────────
- # Job 6: 汇总输出(始终执行)
+ # Job 7: 汇总输出(始终执行)
# ─────────────────────────────────────────────────────────────
summary:
name: Summary
runs-on: ubuntu-latest
- needs: [prepare, build, package, publish, sync]
+ needs: [prepare, build, package, smoke_test, bump, sync]
if: >-
!cancelled() &&
needs.prepare.result == 'success' &&
needs.build.result == 'success' &&
needs.package.result == 'success' &&
- needs.publish.result == 'success' &&
+ needs.smoke_test.result == 'success' &&
+ needs.bump.result == 'success' &&
needs.sync.result == 'success'
steps:
@@ -376,24 +627,60 @@ jobs:
echo -e "\033[0;36mPackage : ${{ needs.package.outputs.installer_name }}\033[0m"
echo ""
echo -e "\033[0;32m[OK]\033[0m Version updated and committed to main"
- echo -e "\033[0;32m[OK]\033[0m Version synced to ${{ needs.prepare.outputs.source_branch }}"
+ echo -e "\033[0;32m[OK]\033[0m Version synced to pre-publish branches (release / dev / bugfix)"
echo -e "\033[0;32m[OK]\033[0m Installer uploaded as Artifact"
+ echo -e "\033[0;32m[OK]\033[0m Smoke test passed (install + launch + singleton)"
+ echo -e "\033[0;32m[OK]\033[0m Draft GitHub Release will be created: v${{ needs.prepare.outputs.new_version }}"
echo -e "\033[0;32m==========================================\033[0m"
# ─────────────────────────────────────────────────────────────
- # Job 7: 失败回滚(任意 job 失败时执行)
+ # Job 8: 创建草稿 GitHub Release(所有步骤均成功后执行)
+ # - 等待 summary 通过,即代表全流程无任何错误
+ # - 以 PR body 作为 Release 正文
+ # - Tag / Release 标题均为 "v版本号"
+ # - 将 Installer exe 作为 Release 附件上传
+ # - 创建为草稿(draft),需人工验证后手动点击发布
+ # ─────────────────────────────────────────────────────────────
+ release:
+ name: Create Draft GitHub Release
+ runs-on: ubuntu-latest
+ needs: [prepare, package, summary]
+ if: needs.summary.result == 'success'
+
+ steps:
+ - name: Download installer artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: VirtualPaper-Installer-v${{ needs.prepare.outputs.new_version }}
+ path: dist/
+
+ - name: Create Draft Release & Tag
+ uses: softprops/action-gh-release@v2
+ with:
+ tag_name: v${{ needs.prepare.outputs.new_version }}
+ name: v${{ needs.prepare.outputs.new_version }}
+ body: ${{ needs.prepare.outputs.pr_body }}
+ files: |
+ dist/*.exe
+ dist/SHA256.txt
+ draft: true # 草稿状态,不对外公开,等待人工验证后发布
+ make_latest: false # 草稿阶段不标记为最新,发布时再生效
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ # ─────────────────────────────────────────────────────────────
+ # Job 9: 失败回滚(任意 job 失败时执行)
# ─────────────────────────────────────────────────────────────
rollback:
name: Rollback on Failure
runs-on: windows-latest
- needs: [prepare, build, package, publish, sync]
+ needs: [prepare, build, package, smoke_test, bump, sync]
if: always() && failure() && needs.prepare.result == 'success'
steps:
- name: Checkout with full history
uses: actions/checkout@v4
with:
- token: ${{ secrets.GITHUB_TOKEN }}
+ token: ${{ secrets.RELEASE_PAT }}
fetch-depth: 0
- name: Rollback changes
@@ -405,16 +692,14 @@ jobs:
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
- # 拉取最新远端状态(用 Continue 避免进度信息触发 NativeCommandError)
$ErrorActionPreference = 'Continue'
git fetch origin 2>&1 | Out-Null
$ErrorActionPreference = 'Stop'
- $mainSha = "${{ needs.prepare.outputs.main_sha }}"
- $devSha = "${{ needs.prepare.outputs.dev_sha }}"
- $bugfixSha = "${{ needs.prepare.outputs.bugfix_sha }}"
- $releaseSha = "${{ needs.prepare.outputs.release_sha }}"
- $sourceBranch = "${{ needs.prepare.outputs.source_branch }}"
+ $mainSha = "${{ needs.prepare.outputs.main_sha }}"
+ $devSha = "${{ needs.prepare.outputs.dev_sha }}"
+ $bugfixSha = "${{ needs.prepare.outputs.bugfix_sha }}"
+ $releaseSha = "${{ needs.prepare.outputs.release_sha }}"
# --- 回滚 main ---
if ($mainSha) {
@@ -422,57 +707,40 @@ jobs:
$currentMain = git rev-parse origin/main 2>&1
$ErrorActionPreference = 'Stop'
if ($LASTEXITCODE -eq 0 -and $currentMain -ne $mainSha) {
- Write-Host "[ROLLBACK]" -ForegroundColor Cyan -NoNewline; Write-Host " main: $currentMain -> $mainSha"
+ Write-Host "[ROLLBACK]" -ForegroundColor Cyan -NoNewline; Write-Host " main: reverting version bump commit"
git checkout -B main origin/main
- git reset --hard $mainSha
- # 追加空 commit,防止 force push 后再次触发本 workflow
- git commit --allow-empty -m "[Github CI] rollback: reverted version bump [skip ci]"
- git push --force origin main
+ git revert HEAD --no-commit
+ git commit -m "[Github CI] rollback: revert version bump [skip ci]"
+ git push origin main
Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " main rolled back"
} else {
Write-Host "[SKIP] main: no changes detected, rollback not needed"
}
}
- # --- 回滚源分支(dev / bugfix)---
- if ($sourceBranch -eq "dev" -and $devSha) {
- $ErrorActionPreference = 'Continue'
- $currentDev = git rev-parse origin/dev 2>&1
- $ErrorActionPreference = 'Stop'
- if ($LASTEXITCODE -eq 0 -and $currentDev -ne $devSha) {
- Write-Host "[ROLLBACK]" -ForegroundColor Cyan -NoNewline; Write-Host " dev: $currentDev -> $devSha"
- git checkout -B dev origin/dev
- git reset --hard $devSha
- git push --force origin dev
- Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " dev rolled back"
- } else {
- Write-Host "[SKIP] dev: no changes detected, rollback not needed"
- }
- } elseif ($sourceBranch -eq "bugfix" -and $bugfixSha) {
- $ErrorActionPreference = 'Continue'
- $currentBugfix = git rev-parse origin/bugfix 2>&1
- $ErrorActionPreference = 'Stop'
- if ($LASTEXITCODE -eq 0 -and $currentBugfix -ne $bugfixSha) {
- Write-Host "[ROLLBACK]" -ForegroundColor Cyan -NoNewline; Write-Host " bugfix: $currentBugfix -> $bugfixSha"
- git checkout -B bugfix origin/bugfix
- git reset --hard $bugfixSha
- git push --force origin bugfix
- Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " bugfix rolled back"
- } else {
- Write-Host "[SKIP] bugfix: no changes detected, rollback not needed"
- }
- } elseif ($sourceBranch -eq "release" -and $releaseSha) {
+ # --- 回滚 pre-publish branches (release / dev / bugfix) ---
+ $branchShaMap = @{
+ "release" = $releaseSha
+ "dev" = $devSha
+ "bugfix" = $bugfixSha
+ }
+
+ foreach ($b in $branchShaMap.Keys) {
+ $targetSha = $branchShaMap[$b]
+ if (-not $targetSha) { continue }
+
$ErrorActionPreference = 'Continue'
- $currentRelease = git rev-parse origin/release 2>&1
+ $currentSha = git rev-parse origin/$b 2>&1
$ErrorActionPreference = 'Stop'
- if ($LASTEXITCODE -eq 0 -and $currentRelease -ne $releaseSha) {
- Write-Host "[ROLLBACK]" -ForegroundColor Cyan -NoNewline; Write-Host " release: $currentRelease -> $releaseSha"
- git checkout -B release origin/release
- git reset --hard $releaseSha
- git push --force origin release
- Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " release rolled back"
+ if ($LASTEXITCODE -eq 0 -and $currentSha -ne $targetSha) {
+ Write-Host "[ROLLBACK]" -ForegroundColor Cyan -NoNewline; Write-Host " ${b}: reverting version sync commit"
+ git checkout -B $b origin/$b
+ git revert HEAD --no-commit
+ git commit -m "[Github CI] rollback: revert version sync [skip ci]"
+ git push origin $b
+ Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " $b rolled back"
} else {
- Write-Host "[SKIP] release: no changes detected, rollback not needed"
+ Write-Host "[SKIP] ${b}: no changes detected, rollback not needed"
}
}
diff --git a/.github/workflows/branch-protection.yml b/.github/workflows/branch-protection.yml
index 7cbeb592..29298aff 100644
--- a/.github/workflows/branch-protection.yml
+++ b/.github/workflows/branch-protection.yml
@@ -13,7 +13,6 @@ concurrency:
permissions:
contents: read
- statuses: write
jobs:
check-source-branch:
@@ -41,24 +40,3 @@ jobs:
echo " bugfix -> main : Urgent bugfix (patch version +1)"
exit 1
fi
-
- - name: Create status check
- if: always()
- uses: actions/github-script@v7
- with:
- script: |
- const sourceBranch = context.payload.pull_request.head.ref;
- const allowed = ['release', 'dev', 'bugfix'];
- const isAllowed = allowed.includes(sourceBranch);
-
- await github.rest.repos.createCommitStatus({
- owner: context.repo.owner,
- repo: context.repo.repo,
- sha: context.payload.pull_request.head.sha,
- state: isAllowed ? 'success' : 'failure',
- target_url: context.payload.repository.html_url + '/actions/runs/' + context.runId,
- description: isAllowed
- ? `[OK] Source branch validation passed: ${sourceBranch} -> main`
- : `[FAILED] Only release, dev and bugfix branches allowed to merge to main`,
- context: 'branch-protection/source-validation'
- });
diff --git a/InnoSetup/CodeDependencies.iss b/InnoSetup/CodeDependencies.iss
new file mode 100644
index 00000000..99f1419f
--- /dev/null
+++ b/InnoSetup/CodeDependencies.iss
@@ -0,0 +1,853 @@
+[Code]
+// https://github.com/DomGries/InnoDependencyInstaller
+
+// types and variables
+type
+ TDependency_Entry = record
+ Filename: String;
+ Parameters: String;
+ Title: String;
+ URL: String;
+ Checksum: String;
+ ForceSuccess: Boolean;
+ RestartAfter: Boolean;
+ Components: String;
+ end;
+
+var
+ Dependency_List: array of TDependency_Entry;
+ Dependency_NeedToRestart, Dependency_ForceX86, Dependency_ForceX64: Boolean;
+ Dependency_Components: String;
+ Dependency_DownloadPage: TDownloadWizardPage;
+
+function Dependency_IsEntryActive(const Entry: TDependency_Entry): Boolean;
+begin
+ Result := (Entry.Components = '') or WizardIsComponentSelected(Entry.Components);
+end;
+
+procedure Dependency_Add(const Filename, Parameters, Title, URL, Checksum: String; const ForceSuccess, RestartAfter: Boolean);
+var
+ Dependency: TDependency_Entry;
+ DependencyCount: Integer;
+begin
+ Dependency.Filename := Filename;
+ Dependency.Parameters := Parameters;
+ Dependency.Title := Title;
+
+ if FileExists(ExpandConstant('{tmp}{\}') + Filename) then begin
+ Dependency.URL := '';
+ end else begin
+ Dependency.URL := URL;
+ end;
+
+ Dependency.Checksum := Checksum;
+ Dependency.ForceSuccess := ForceSuccess;
+ Dependency.RestartAfter := RestartAfter;
+ Dependency.Components := Dependency_Components;
+
+ DependencyCount := GetArrayLength(Dependency_List);
+ SetArrayLength(Dependency_List, DependencyCount + 1);
+ Dependency_List[DependencyCount] := Dependency;
+end;
+
+
+procedure Dependency_InitializeWizard;
+begin
+ Dependency_DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), nil);
+end;
+
+
+function Dependency_PrepareToInstall(var NeedsRestart: Boolean): String;
+var
+ DependencyCount, DependencyIndex, ResultCode: Integer;
+ Retry: Boolean;
+ TempValue: String;
+begin
+ DependencyCount := GetArrayLength(Dependency_List);
+
+ if DependencyCount > 0 then begin
+ Dependency_DownloadPage.Show;
+
+ for DependencyIndex := 0 to DependencyCount - 1 do begin
+ if not Dependency_IsEntryActive(Dependency_List[DependencyIndex]) then begin
+ continue;
+ end;
+ if Dependency_List[DependencyIndex].URL <> '' then begin
+ Dependency_DownloadPage.Clear;
+ Dependency_DownloadPage.Add(Dependency_List[DependencyIndex].URL, Dependency_List[DependencyIndex].Filename, Dependency_List[DependencyIndex].Checksum);
+
+ Retry := True;
+ while Retry do begin
+ Retry := False;
+
+ try
+ Dependency_DownloadPage.Download;
+ except
+ if Dependency_DownloadPage.AbortedByUser then begin
+ Result := Dependency_List[DependencyIndex].Title;
+ DependencyIndex := DependencyCount;
+ end else begin
+ case SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbError, MB_ABORTRETRYIGNORE, IDIGNORE) of
+ IDABORT: begin
+ Result := Dependency_List[DependencyIndex].Title;
+ DependencyIndex := DependencyCount;
+ end;
+ IDRETRY: begin
+ Retry := True;
+ end;
+ end;
+ end;
+ end;
+ end;
+ end;
+ end;
+
+ if Result = '' then begin
+ for DependencyIndex := 0 to DependencyCount - 1 do begin
+ if not Dependency_IsEntryActive(Dependency_List[DependencyIndex]) then begin
+ continue;
+ end;
+ Dependency_DownloadPage.SetText(Dependency_List[DependencyIndex].Title, '');
+ Dependency_DownloadPage.SetProgress(DependencyIndex + 1, DependencyCount + 1);
+
+ while True do begin
+ ResultCode := 0;
+#ifdef Dependency_CustomExecute
+ if {#Dependency_CustomExecute}(ExpandConstant('{tmp}{\}') + Dependency_List[DependencyIndex].Filename, Dependency_List[DependencyIndex].Parameters, ResultCode) then begin
+#else
+ if ShellExec('', ExpandConstant('{tmp}{\}') + Dependency_List[DependencyIndex].Filename, Dependency_List[DependencyIndex].Parameters, '', SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode) then begin
+#endif
+ if Dependency_List[DependencyIndex].RestartAfter then begin
+ if DependencyIndex = DependencyCount - 1 then begin
+ Dependency_NeedToRestart := True;
+ end else begin
+ NeedsRestart := True;
+ Result := Dependency_List[DependencyIndex].Title;
+ end;
+ break;
+ end else if (ResultCode = 0) or Dependency_List[DependencyIndex].ForceSuccess then begin // ERROR_SUCCESS (0)
+ break;
+ end else if ResultCode = 1641 then begin // ERROR_SUCCESS_REBOOT_INITIATED (1641)
+ NeedsRestart := True;
+ Result := Dependency_List[DependencyIndex].Title;
+ break;
+ end else if ResultCode = 3010 then begin // ERROR_SUCCESS_REBOOT_REQUIRED (3010)
+ Dependency_NeedToRestart := True;
+ break;
+ end;
+ end;
+
+ case SuppressibleMsgBox(FmtMessage(SetupMessage(msgErrorFunctionFailed), [Dependency_List[DependencyIndex].Title, IntToStr(ResultCode)]), mbError, MB_ABORTRETRYIGNORE, IDIGNORE) of
+ IDABORT: begin
+ Result := Dependency_List[DependencyIndex].Title;
+ break;
+ end;
+ IDIGNORE: begin
+ break;
+ end;
+ end;
+ end;
+
+ if Result <> '' then begin
+ break;
+ end;
+ end;
+
+ if NeedsRestart then begin
+ TempValue := '"' + ExpandConstant('{srcexe}') + '" /restart=1 /LANG="' + ExpandConstant('{language}') + '" /DIR="' + WizardDirValue + '" /GROUP="' + WizardGroupValue + '" /TYPE="' + WizardSetupType(False) + '" /COMPONENTS="' + WizardSelectedComponents(False) + '" /TASKS="' + WizardSelectedTasks(False) + '"';
+ if WizardNoIcons then begin
+ TempValue := TempValue + ' /NOICONS';
+ end;
+ RegWriteStringValue(HKA, 'SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', '{#SetupSetting("AppName")}', TempValue);
+ end;
+ end;
+
+ Dependency_DownloadPage.Hide;
+ end;
+end;
+
+#ifndef Dependency_NoUpdateReadyMemo
+
+#endif
+function Dependency_UpdateReadyMemo(const Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
+var
+ DependencyIndex: Integer;
+ DependencyMemo: String;
+begin
+ Result := '';
+ if MemoUserInfoInfo <> '' then begin
+ Result := Result + MemoUserInfoInfo + Newline + NewLine;
+ end;
+ if MemoDirInfo <> '' then begin
+ Result := Result + MemoDirInfo + Newline + NewLine;
+ end;
+ if MemoTypeInfo <> '' then begin
+ Result := Result + MemoTypeInfo + Newline + NewLine;
+ end;
+ if MemoComponentsInfo <> '' then begin
+ Result := Result + MemoComponentsInfo + Newline + NewLine;
+ end;
+ if MemoGroupInfo <> '' then begin
+ Result := Result + MemoGroupInfo + Newline + NewLine;
+ end;
+ if MemoTasksInfo <> '' then begin
+ Result := Result + MemoTasksInfo;
+ end;
+
+ DependencyMemo := '';
+ for DependencyIndex := 0 to GetArrayLength(Dependency_List) - 1 do begin
+ if Dependency_IsEntryActive(Dependency_List[DependencyIndex]) then begin
+ DependencyMemo := DependencyMemo + #13#10 + '%1' + Dependency_List[DependencyIndex].Title;
+ end;
+ end;
+
+ if DependencyMemo <> '' then begin
+ if MemoTasksInfo = '' then begin
+ Result := Result + SetupMessage(msgReadyMemoTasks);
+ end;
+ Result := Result + FmtMessage(DependencyMemo, [Space]);
+ end;
+end;
+
+
+function Dependency_NeedRestart: Boolean;
+begin
+ Result := Dependency_NeedToRestart;
+end;
+
+function Dependency_IsArm64: Boolean;
+begin
+ Result := not Dependency_ForceX86 and not Dependency_ForceX64 and IsArm64;
+end;
+
+function Dependency_IsX64: Boolean;
+begin
+ Result := not Dependency_ForceX86 and Is64BitInstallMode;
+end;
+
+function Dependency_String(const x86, x64, arm64: String): String;
+begin
+ if Dependency_IsArm64 then begin
+ Result := arm64;
+ end else if Dependency_IsX64 then begin
+ Result := x64;
+ end else begin
+ Result := x86;
+ end;
+end;
+
+function Dependency_ArchSuffix: String;
+begin
+ Result := Dependency_String('', '_x64', '_arm64');
+end;
+
+function Dependency_ArchTitle: String;
+begin
+ Result := Dependency_String(' (x86)', ' (x64)', ' (arm64)');
+end;
+
+function Dependency_IsNetCoreInstalled(Runtime: String; Major, Minor, Revision: Word): Boolean;
+var
+ Path: String;
+ ResultCode: Integer;
+ Output: TExecOutput;
+ LineIndex: Integer;
+ LineParts: TArrayOfString;
+ PackedVersion: Int64;
+ LineMajor, LineMinor, LineRevision, LineBuild: Word;
+begin
+ if not RegQueryStringValue(HKLM32, 'SOFTWARE\dotnet\Setup\InstalledVersions\' + Dependency_String('x86', 'x64', 'arm64'), 'InstallLocation', Path) or not FileExists(Path + 'dotnet.exe') then begin
+ Path := ExpandConstant(Dependency_String('{commonpf32}', '{commonpf64}', '{commonpf64}')) + '\dotnet\';
+ end;
+ if ExecAndCaptureOutput(Path + 'dotnet.exe', '--list-runtimes', '', SW_HIDE, ewWaitUntilTerminated, ResultCode, Output) and (ResultCode = 0) then begin
+ for LineIndex := 0 to Length(Output.StdOut) - 1 do begin
+ LineParts := StringSplit(Trim(Output.StdOut[LineIndex]), [' '], stExcludeEmpty);
+
+ if (Length(LineParts) > 1) and (Lowercase(LineParts[0]) = Lowercase(Runtime)) and StrToVersion(LineParts[1], PackedVersion) then begin
+ UnpackVersionComponents(PackedVersion, LineMajor, LineMinor, LineRevision, LineBuild);
+
+ if (LineMajor = Major) and (LineMinor = Minor) and (LineRevision >= Revision) then begin
+ Result := True;
+ exit;
+ end;
+ end;
+ end;
+ end;
+ Result := False;
+end;
+
+procedure Dependency_AddDotNet35;
+begin
+ // https://dotnet.microsoft.com/download/dotnet-framework/net35-sp1
+ if not IsDotNetInstalled(net35, 1) then begin
+ Dependency_Add('dotnetfx35.exe',
+ '/lang:enu /passive /norestart',
+ '.NET Framework 3.5 Service Pack 1',
+ 'https://download.microsoft.com/download/2/0/E/20E90413-712F-438C-988E-FDAA79A8AC3D/dotnetfx35.exe',
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet40;
+begin
+ // https://dotnet.microsoft.com/download/dotnet-framework/net40
+ if not IsDotNetInstalled(net4full, 0) then begin
+ Dependency_Add('dotNetFx40_Full_setup.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Framework 4.0',
+ 'https://download.microsoft.com/download/1/B/E/1BE39E79-7E39-46A3-96FF-047F95396215/dotNetFx40_Full_setup.exe',
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet45;
+begin
+ // https://dotnet.microsoft.com/download/dotnet-framework/net452
+ if not IsDotNetInstalled(net452, 0) then begin
+ Dependency_Add('dotnetfx45.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Framework 4.5.2',
+ 'https://go.microsoft.com/fwlink/?LinkId=397707',
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet46;
+begin
+ // https://dotnet.microsoft.com/download/dotnet-framework/net462
+ if not IsDotNetInstalled(net462, 0) then begin
+ Dependency_Add('dotnetfx46.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Framework 4.6.2',
+ 'https://go.microsoft.com/fwlink/?linkid=780596',
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet47;
+begin
+ // https://dotnet.microsoft.com/download/dotnet-framework/net472
+ if not IsDotNetInstalled(net472, 0) then begin
+ Dependency_Add('dotnetfx47.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Framework 4.7.2',
+ 'https://go.microsoft.com/fwlink/?LinkId=863262',
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet48;
+begin
+ // https://dotnet.microsoft.com/download/dotnet-framework/net48
+ if not IsDotNetInstalled(net48, 0) then begin
+ Dependency_Add('dotnetfx48.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Framework 4.8',
+ 'https://go.microsoft.com/fwlink/?LinkId=2085155',
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet481;
+begin
+ // https://dotnet.microsoft.com/download/dotnet-framework/net481
+ if not IsDotNetInstalled(net481, 0) then begin
+ Dependency_Add('dotnetfx481.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Framework 4.8.1',
+ 'https://go.microsoft.com/fwlink/?LinkId=2203304',
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddNetCore31;
+begin
+ // https://dotnet.microsoft.com/download/dotnet-core/3.1
+ if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App', 3, 1, 32) then begin
+ Dependency_Add('netcore31' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Core Runtime 3.1.32' + Dependency_ArchTitle,
+ Dependency_String('https://download.visualstudio.microsoft.com/download/pr/de4b3438-24a2-4d1d-a845-97355cf97b71/515abb880478b49f7c1bced8fbf07b16/dotnet-runtime-3.1.32-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/476eba79-f17f-49c8-a213-0f24a22cd026/37c02de81ff5b76ac57a5427462395f1/dotnet-runtime-3.1.32-win-x64.exe', 'https://download.visualstudio.microsoft.com/download/pr/476eba79-f17f-49c8-a213-0f24a22cd026/37c02de81ff5b76ac57a5427462395f1/dotnet-runtime-3.1.32-win-x64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddNetCore31Asp;
+begin
+ // https://dotnet.microsoft.com/download/dotnet-core/3.1
+ if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App', 3, 1, 32) then begin
+ Dependency_Add('netcore31asp' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ 'ASP.NET Core Runtime 3.1.32' + Dependency_ArchTitle,
+ Dependency_String('https://download.visualstudio.microsoft.com/download/pr/63b482d2-04b2-4dd4-baaf-d1e78de80738/40321091c872f4e77337b68fc61a5a07/aspnetcore-runtime-3.1.32-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/98910750-2644-472c-ab2b-17f315ccb953/c2a4c223ee11e2eec7d13744e7a45547/aspnetcore-runtime-3.1.32-win-x64.exe', 'https://download.visualstudio.microsoft.com/download/pr/98910750-2644-472c-ab2b-17f315ccb953/c2a4c223ee11e2eec7d13744e7a45547/aspnetcore-runtime-3.1.32-win-x64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddNetCore31Desktop;
+begin
+ // https://dotnet.microsoft.com/download/dotnet-core/3.1
+ if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App', 3, 1, 32) then begin
+ Dependency_Add('netcore31desktop' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Desktop Runtime 3.1.32' + Dependency_ArchTitle,
+ Dependency_String('https://download.visualstudio.microsoft.com/download/pr/3f353d2c-0431-48c5-bdf6-fbbe8f901bb5/542a4af07c1df5136a98a1c2df6f3d62/windowsdesktop-runtime-3.1.32-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/b92958c6-ae36-4efa-aafe-569fced953a5/1654639ef3b20eb576174c1cc200f33a/windowsdesktop-runtime-3.1.32-win-x64.exe', 'https://download.visualstudio.microsoft.com/download/pr/b92958c6-ae36-4efa-aafe-569fced953a5/1654639ef3b20eb576174c1cc200f33a/windowsdesktop-runtime-3.1.32-win-x64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet50;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/5.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App', 5, 0, 17) then begin
+ Dependency_Add('dotnet50' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Runtime 5.0.17' + Dependency_ArchTitle,
+ Dependency_String('https://download.visualstudio.microsoft.com/download/pr/54683c13-6b04-4d7d-b4d4-1f055b50ea43/e99048e2840d57040e8312058853a5b9/dotnet-runtime-5.0.17-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/a0832b5a-6900-442b-af79-6ffddddd6ba4/e2df0b25dd851ee0b38a86947dd0e42e/dotnet-runtime-5.0.17-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/5.0.17/dotnet-runtime-5.0.17-win-arm64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet50Asp;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/5.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App', 5, 0, 17) then begin
+ Dependency_Add('dotnet50asp' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ 'ASP.NET Core Runtime 5.0.17' + Dependency_ArchTitle,
+ Dependency_String('https://download.visualstudio.microsoft.com/download/pr/4bfa247d-321d-4b29-a34b-62320849059b/8df7a17d9aad4044efe9b5b1c423e82c/aspnetcore-runtime-5.0.17-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/3789ec90-2717-424f-8b9c-3adbbcea6c16/2085cc5ff077b8789ff938015392e406/aspnetcore-runtime-5.0.17-win-x64.exe', 'https://download.visualstudio.microsoft.com/download/pr/3789ec90-2717-424f-8b9c-3adbbcea6c16/2085cc5ff077b8789ff938015392e406/aspnetcore-runtime-5.0.17-win-x64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet50Desktop;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/5.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App', 5, 0, 17) then begin
+ Dependency_Add('dotnet50desktop' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Desktop Runtime 5.0.17' + Dependency_ArchTitle,
+ Dependency_String('https://download.visualstudio.microsoft.com/download/pr/b6fe5f2a-95f4-46f1-9824-f5994f10bc69/db5ec9b47ec877b5276f83a185fdb6a0/windowsdesktop-runtime-5.0.17-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/3aa4e942-42cd-4bf5-afe7-fc23bd9c69c5/64da54c8864e473c19a7d3de15790418/windowsdesktop-runtime-5.0.17-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/5.0.17/windowsdesktop-runtime-5.0.17-win-arm64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet60;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/6.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App', 6, 0, 36) then begin
+ Dependency_Add('dotnet60' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Runtime 6.0.36' + Dependency_ArchTitle,
+ Dependency_String('https://download.visualstudio.microsoft.com/download/pr/727d79cb-6a4c-4a6b-bd9e-af99ad62de0b/5cd3550f1589a2f1b3a240c745dd1023/dotnet-runtime-6.0.36-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/1a5fc50a-9222-4f33-8f73-3c78485a55c7/1cb55899b68fcb9d98d206ba56f28b66/dotnet-runtime-6.0.36-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/6.0.36/dotnet-runtime-6.0.36-win-arm64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet60Asp;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/6.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App', 6, 0, 36) then begin
+ Dependency_Add('dotnet60asp' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ 'ASP.NET Core Runtime 6.0.36' + Dependency_ArchTitle,
+ Dependency_String('https://download.visualstudio.microsoft.com/download/pr/8cfa7f46-88f2-4521-a2d8-59b827420344/447de18a48115ac0fe6f381f0528e7a5/aspnetcore-runtime-6.0.36-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/0f0ea01c-ef7c-4493-8960-d1e9269b718b/3f95c5bd383be65c2c3384e9fa984078/aspnetcore-runtime-6.0.36-win-x64.exe', 'https://download.visualstudio.microsoft.com/download/pr/0f0ea01c-ef7c-4493-8960-d1e9269b718b/3f95c5bd383be65c2c3384e9fa984078/aspnetcore-runtime-6.0.36-win-x64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet60Desktop;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/6.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App', 6, 0, 36) then begin
+ Dependency_Add('dotnet60desktop' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Desktop Runtime 6.0.36' + Dependency_ArchTitle,
+ Dependency_String('https://download.visualstudio.microsoft.com/download/pr/cdc314df-4a4c-4709-868d-b974f336f77f/acd5ab7637e456c8a3aa667661324f6d/windowsdesktop-runtime-6.0.36-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/f6b6c5dc-e02d-4738-9559-296e938dabcb/b66d365729359df8e8ea131197715076/windowsdesktop-runtime-6.0.36-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/6.0.36/windowsdesktop-runtime-6.0.36-win-arm64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet70;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/7.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App', 7, 0, 20) then begin
+ Dependency_Add('dotnet70' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Runtime 7.0.20' + Dependency_ArchTitle,
+ Dependency_String('https://download.visualstudio.microsoft.com/download/pr/b2e820bd-b591-43df-ab10-1eeb7998cc18/661ca79db4934c6247f5c7a809a62238/dotnet-runtime-7.0.20-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/be7eaed0-4e32-472b-b53e-b08ac3433a22/fc99a5977c57cbfb93b4afb401953818/dotnet-runtime-7.0.20-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/7.0.20/dotnet-runtime-7.0.20-win-arm64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet70Asp;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/7.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App', 7, 0, 20) then begin
+ Dependency_Add('dotnet70asp' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ 'ASP.NET Core Runtime 7.0.20' + Dependency_ArchTitle,
+ Dependency_String('https://download.visualstudio.microsoft.com/download/pr/d84ac38e-a248-4c8d-b1fe-4ee092d6b4b1/9f0bf370619ab3da8869e467827a6dc6/aspnetcore-runtime-7.0.20-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/10651a65-8afc-46e3-9287-fecb0e68504e/4c2bf0cdb44612f29d9b3f901098e13e/aspnetcore-runtime-7.0.20-win-x64.exe', 'https://download.visualstudio.microsoft.com/download/pr/10651a65-8afc-46e3-9287-fecb0e68504e/4c2bf0cdb44612f29d9b3f901098e13e/aspnetcore-runtime-7.0.20-win-x64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet70Desktop;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/7.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App', 7, 0, 20) then begin
+ Dependency_Add('dotnet70desktop' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Desktop Runtime 7.0.20' + Dependency_ArchTitle,
+ Dependency_String('https://download.visualstudio.microsoft.com/download/pr/b840017b-c69f-4724-a152-11020a0039e6/b74aa12e4ee765a3387a7dcd4ba56187/windowsdesktop-runtime-7.0.20-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/08bbfe8f-812d-479f-803b-23ea0bffce47/c320e4b037f3e92ab7ea92c3d7ea3ca1/windowsdesktop-runtime-7.0.20-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/7.0.20/windowsdesktop-runtime-7.0.20-win-arm64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet80;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/8.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App', 8, 0, 27) then begin
+ Dependency_Add('dotnet80' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Runtime 8.0.27' + Dependency_ArchTitle,
+ Dependency_String('https://builds.dotnet.microsoft.com/dotnet/Runtime/8.0.27/dotnet-runtime-8.0.27-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/8.0.27/dotnet-runtime-8.0.27-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/8.0.27/dotnet-runtime-8.0.27-win-arm64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet80Asp;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/8.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App', 8, 0, 27) then begin
+ Dependency_Add('dotnet80asp' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ 'ASP.NET Core Runtime 8.0.27' + Dependency_ArchTitle,
+ Dependency_String('https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/8.0.27/aspnetcore-runtime-8.0.27-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/8.0.27/aspnetcore-runtime-8.0.27-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/8.0.27/aspnetcore-runtime-8.0.27-win-arm64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet80Desktop;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/8.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App', 8, 0, 27) then begin
+ Dependency_Add('dotnet80desktop' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Desktop Runtime 8.0.27' + Dependency_ArchTitle,
+ Dependency_String('https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/8.0.27/windowsdesktop-runtime-8.0.27-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/8.0.27/windowsdesktop-runtime-8.0.27-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/8.0.27/windowsdesktop-runtime-8.0.27-win-arm64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet90;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/9.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App', 9, 0, 16) then begin
+ Dependency_Add('dotnet90' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Runtime 9.0.16' + Dependency_ArchTitle,
+ Dependency_String('https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.16/dotnet-runtime-9.0.16-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.16/dotnet-runtime-9.0.16-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.16/dotnet-runtime-9.0.16-win-arm64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet90Asp;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/9.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App', 9, 0, 16) then begin
+ Dependency_Add('dotnet90asp' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ 'ASP.NET Core Runtime 9.0.16' + Dependency_ArchTitle,
+ Dependency_String('https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/9.0.16/aspnetcore-runtime-9.0.16-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/9.0.16/aspnetcore-runtime-9.0.16-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/9.0.16/aspnetcore-runtime-9.0.16-win-arm64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet90Desktop;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/9.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App', 9, 0, 16) then begin
+ Dependency_Add('dotnet90desktop' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Desktop Runtime 9.0.16' + Dependency_ArchTitle,
+ Dependency_String('https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/9.0.16/windowsdesktop-runtime-9.0.16-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/9.0.16/windowsdesktop-runtime-9.0.16-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/9.0.16/windowsdesktop-runtime-9.0.16-win-arm64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet100;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/10.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App', 10, 0, 8) then begin
+ Dependency_Add('dotnet100' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Runtime 10.0.8' + Dependency_ArchTitle,
+ Dependency_String('https://builds.dotnet.microsoft.com/dotnet/Runtime/10.0.8/dotnet-runtime-10.0.8-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/10.0.8/dotnet-runtime-10.0.8-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/10.0.8/dotnet-runtime-10.0.8-win-arm64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet100Asp;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/10.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App', 10, 0, 8) then begin
+ Dependency_Add('dotnet100asp' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ 'ASP.NET Core Runtime 10.0.8' + Dependency_ArchTitle,
+ Dependency_String('https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/10.0.8/aspnetcore-runtime-10.0.8-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/10.0.8/aspnetcore-runtime-10.0.8-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/10.0.8/aspnetcore-runtime-10.0.8-win-arm64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDotNet100Desktop;
+begin
+ // https://dotnet.microsoft.com/download/dotnet/10.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App', 10, 0, 8) then begin
+ Dependency_Add('dotnet100desktop' + Dependency_ArchSuffix + '.exe',
+ '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+ '.NET Desktop Runtime 10.0.8' + Dependency_ArchTitle,
+ Dependency_String('https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/10.0.8/windowsdesktop-runtime-10.0.8-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/10.0.8/windowsdesktop-runtime-10.0.8-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/10.0.8/windowsdesktop-runtime-10.0.8-win-arm64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddVC2005;
+begin
+ // https://www.microsoft.com/en-us/download/details.aspx?id=26347
+ if not IsMsiProductInstalled(Dependency_String('{86C9D5AA-F00C-4921-B3F2-C60AF92E2844}', '{A8D19029-8E5C-4E22-8011-48070F9E796E}', '{A8D19029-8E5C-4E22-8011-48070F9E796E}'), PackVersionComponents(8, 0, 61000, 0)) then begin
+ Dependency_Add('vcredist2005' + Dependency_ArchSuffix + '.exe',
+ '/q',
+ 'Visual C++ 2005 Service Pack 1 Redistributable' + Dependency_ArchTitle,
+ Dependency_String('https://download.microsoft.com/download/8/B/4/8B42259F-5D70-43F4-AC2E-4B208FD8D66A/vcredist_x86.EXE', 'https://download.microsoft.com/download/8/B/4/8B42259F-5D70-43F4-AC2E-4B208FD8D66A/vcredist_x64.EXE', 'https://download.microsoft.com/download/8/B/4/8B42259F-5D70-43F4-AC2E-4B208FD8D66A/vcredist_x64.EXE'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddVC2008;
+begin
+ // https://www.microsoft.com/en-us/download/details.aspx?id=26368
+ if not IsMsiProductInstalled(Dependency_String('{DE2C306F-A067-38EF-B86C-03DE4B0312F9}', '{FDA45DDF-8E17-336F-A3ED-356B7B7C688A}', '{FDA45DDF-8E17-336F-A3ED-356B7B7C688A}'), PackVersionComponents(9, 0, 30729, 6161)) then begin
+ Dependency_Add('vcredist2008' + Dependency_ArchSuffix + '.exe',
+ '/q',
+ 'Visual C++ 2008 Service Pack 1 Redistributable' + Dependency_ArchTitle,
+ Dependency_String('https://download.microsoft.com/download/5/D/8/5D8C65CB-C849-4025-8E95-C3966CAFD8AE/vcredist_x86.exe', 'https://download.microsoft.com/download/5/D/8/5D8C65CB-C849-4025-8E95-C3966CAFD8AE/vcredist_x64.exe', 'https://download.microsoft.com/download/5/D/8/5D8C65CB-C849-4025-8E95-C3966CAFD8AE/vcredist_x64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddVC2010;
+begin
+ // https://www.microsoft.com/en-us/download/details.aspx?id=26999
+ if not IsMsiProductInstalled(Dependency_String('{1F4F1D2A-D9DA-32CF-9909-48485DA06DD5}', '{5B75F761-BAC8-33BC-A381-464DDDD813A3}', '{5B75F761-BAC8-33BC-A381-464DDDD813A3}'), PackVersionComponents(10, 0, 40219, 0)) then begin
+ Dependency_Add('vcredist2010' + Dependency_ArchSuffix + '.exe',
+ '/passive /norestart',
+ 'Visual C++ 2010 Service Pack 1 Redistributable' + Dependency_ArchTitle,
+ Dependency_String('https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe', 'https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x64.exe', 'https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddVC2012;
+begin
+ // https://www.microsoft.com/en-us/download/details.aspx?id=30679
+ if not IsMsiProductInstalled(Dependency_String('{4121ED58-4BD9-3E7B-A8B5-9F8BAAE045B7}', '{EFA6AFA1-738E-3E00-8101-FD03B86B29D1}', '{EFA6AFA1-738E-3E00-8101-FD03B86B29D1}'), PackVersionComponents(11, 0, 61030, 0)) then begin
+ Dependency_Add('vcredist2012' + Dependency_ArchSuffix + '.exe',
+ '/passive /norestart',
+ 'Visual C++ 2012 Update 4 Redistributable' + Dependency_ArchTitle,
+ Dependency_String('https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x86.exe', 'https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x64.exe', 'https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddVC2013;
+begin
+ // https://support.microsoft.com/en-us/help/4032938
+ if not IsMsiProductInstalled(Dependency_String('{B59F5BF1-67C8-3802-8E59-2CE551A39FC5}', '{20400CF0-DE7C-327E-9AE4-F0F38D9085F8}', '{20400CF0-DE7C-327E-9AE4-F0F38D9085F8}'), PackVersionComponents(12, 0, 40664, 0)) then begin
+ Dependency_Add('vcredist2013' + Dependency_ArchSuffix + '.exe',
+ '/passive /norestart',
+ 'Visual C++ 2013 Update 5 Redistributable' + Dependency_ArchTitle,
+ Dependency_String('https://download.visualstudio.microsoft.com/download/pr/10912113/5da66ddebb0ad32ebd4b922fd82e8e25/vcredist_x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/10912041/cee5d6bca2ddbcd039da727bf4acb48a/vcredist_x64.exe', 'https://download.visualstudio.microsoft.com/download/pr/10912041/cee5d6bca2ddbcd039da727bf4acb48a/vcredist_x64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddVC2015To2022;
+var
+ Version: String;
+ PackedVersion: Int64;
+begin
+ // https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist
+ if RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\' + Dependency_String('x86', 'x64', 'arm64'), 'Version', Version) and (Copy(Version, 1, 1) = 'v') then begin
+ Delete(Version, 1, 1);
+ end;
+ if not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(14, 50, 35719, 0)) < 0) then begin
+ Dependency_Add('vcredist2022' + Dependency_ArchSuffix + '.exe',
+ '/passive /norestart',
+ 'Visual C++ 2015-2022 Redistributable' + Dependency_ArchTitle,
+ Dependency_String('https://aka.ms/vc14/vc_redist.x86.exe', 'https://aka.ms/vc14/vc_redist.x64.exe', 'https://aka.ms/vc14/vc_redist.arm64.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddDirectX;
+begin
+#ifdef Dependency_Files_DirectX
+ ExtractTemporaryFile('dxwebsetup.exe');
+#endif
+ // https://www.microsoft.com/en-us/download/details.aspx?id=35
+ Dependency_Add('dxwebsetup.exe',
+ '/q',
+ 'DirectX Runtime',
+ 'https://download.microsoft.com/download/1/7/1/1718CCC4-6315-4D8E-9543-8E28A4E18C4C/dxwebsetup.exe',
+ '', True, False);
+end;
+
+procedure Dependency_AddSql2008Express;
+var
+ Version: String;
+ PackedVersion: Int64;
+begin
+ // https://www.microsoft.com/en-us/download/details.aspx?id=30438
+ if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(10, 50, 4000, 0)) < 0) then begin
+ Dependency_Add('sql2008express' + Dependency_ArchSuffix + '.exe',
+ '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
+ 'SQL Server 2008 R2 Service Pack 2 Express',
+ Dependency_String('https://download.microsoft.com/download/0/4/B/04BE03CD-EAF3-4797-9D8D-2E08E316C998/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/0/4/B/04BE03CD-EAF3-4797-9D8D-2E08E316C998/SQLEXPR_x64_ENU.exe', 'https://download.microsoft.com/download/0/4/B/04BE03CD-EAF3-4797-9D8D-2E08E316C998/SQLEXPR_x64_ENU.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddSql2012Express;
+var
+ Version: String;
+ PackedVersion: Int64;
+begin
+ // https://www.microsoft.com/en-us/download/details.aspx?id=56042
+ if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(11, 0, 7001, 0)) < 0) then begin
+ Dependency_Add('sql2012express' + Dependency_ArchSuffix + '.exe',
+ '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
+ 'SQL Server 2012 Service Pack 4 Express',
+ Dependency_String('https://download.microsoft.com/download/B/D/E/BDE8FAD6-33E5-44F6-B714-348F73E602B6/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/B/D/E/BDE8FAD6-33E5-44F6-B714-348F73E602B6/SQLEXPR_x64_ENU.exe', 'https://download.microsoft.com/download/B/D/E/BDE8FAD6-33E5-44F6-B714-348F73E602B6/SQLEXPR_x64_ENU.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddSql2014Express;
+var
+ Version: String;
+ PackedVersion: Int64;
+begin
+ // https://www.microsoft.com/en-us/download/details.aspx?id=57473
+ if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(12, 0, 6024, 0)) < 0) then begin
+ Dependency_Add('sql2014express' + Dependency_ArchSuffix + '.exe',
+ '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
+ 'SQL Server 2014 Service Pack 3 Express',
+ Dependency_String('https://download.microsoft.com/download/3/9/F/39F968FA-DEBB-4960-8F9E-0E7BB3035959/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/3/9/F/39F968FA-DEBB-4960-8F9E-0E7BB3035959/SQLEXPR_x64_ENU.exe', 'https://download.microsoft.com/download/3/9/F/39F968FA-DEBB-4960-8F9E-0E7BB3035959/SQLEXPR_x64_ENU.exe'),
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddSql2016Express;
+var
+ Version: String;
+ PackedVersion: Int64;
+begin
+ // https://www.microsoft.com/en-us/download/details.aspx?id=103447
+ if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(13, 0, 6404, 1)) < 0) then begin
+ Dependency_Add('sql2016express' + Dependency_ArchSuffix + '.exe',
+ '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
+ 'SQL Server 2016 Service Pack 3 Express',
+ 'https://download.microsoft.com/download/f/a/8/fa83d147-63d1-449c-b22d-5fef9bd5bb46/SQLServer2016-SSEI-Expr.exe',
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddSql2017Express;
+var
+ Version: String;
+ PackedVersion: Int64;
+begin
+ // https://www.microsoft.com/en-us/download/details.aspx?id=55994
+ if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL14.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(14, 0, 0, 0)) < 0) then begin
+ Dependency_Add('sql2017express' + Dependency_ArchSuffix + '.exe',
+ '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
+ 'SQL Server 2017 Express',
+ 'https://download.microsoft.com/download/5/E/9/5E9B18CC-8FD5-467E-B5BF-BADE39C51F73/SQLServer2017-SSEI-Expr.exe',
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddSql2019Express;
+var
+ Version: String;
+ PackedVersion: Int64;
+begin
+ // https://www.microsoft.com/en-us/download/details.aspx?id=101064
+ if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(15, 0, 0, 0)) < 0) then begin
+ Dependency_Add('sql2019express' + Dependency_ArchSuffix + '.exe',
+ '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
+ 'SQL Server 2019 Express',
+ 'https://download.microsoft.com/download/7/f/8/7f8a9c43-8c8a-4f7c-9f92-83c18d96b681/SQL2019-SSEI-Expr.exe',
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddSql2022Express;
+var
+ Version: String;
+ PackedVersion: Int64;
+begin
+ // https://www.microsoft.com/en-us/download/details.aspx?id=104781
+ if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL16.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(16, 0, 1000, 6)) < 0) then begin
+ Dependency_Add('sql2022express' + Dependency_ArchSuffix + '.exe',
+ '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
+ 'SQL Server 2022 Express',
+ 'https://go.microsoft.com/fwlink/p/?linkid=2216019',
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddSql2025Express;
+var
+ Version: String;
+ PackedVersion: Int64;
+begin
+ // https://www.microsoft.com/en-us/sql-server/sql-server-downloads
+ if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL17.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(17, 0, 1000, 7)) < 0) then begin
+ Dependency_Add('sql2025express' + Dependency_ArchSuffix + '.exe',
+ '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
+ 'SQL Server 2025 Express',
+ 'https://download.microsoft.com/download/7ab8f535-7eb8-4b16-82eb-eca0fa2d38f3/SQL2025-SSEI-Expr.exe',
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddWebView2;
+begin
+ // https://developer.microsoft.com/en-us/microsoft-edge/webview2
+ if not RegValueExists(HKLM, Dependency_String('SOFTWARE', 'SOFTWARE\WOW6432Node', 'SOFTWARE\WOW6432Node') + '\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}', 'pv') then begin
+ Dependency_Add('MicrosoftEdgeWebview2Setup.exe',
+ '/silent /install',
+ 'WebView2 Runtime',
+ 'https://go.microsoft.com/fwlink/p/?LinkId=2124703',
+ '', False, False);
+ end;
+end;
+
+procedure Dependency_AddAccessDatabaseEngine2016;
+begin
+ // https://www.microsoft.com/en-us/download/details.aspx?id=54920
+ if not RegKeyExists(HKLM, 'SOFTWARE\Microsoft\Office\16.0\Access Connectivity Engine\Engines\ACE') then begin
+ Dependency_Add('AccessDatabaseEngine2016' + Dependency_ArchSuffix + '.exe',
+ '/quiet',
+ 'Microsoft Access Database Engine 2016' + Dependency_ArchTitle,
+ Dependency_String('https://download.microsoft.com/download/3/5/C/35C84C36-661A-44E6-9324-8786B8DBE231/accessdatabaseengine.exe', 'https://download.microsoft.com/download/3/5/C/35C84C36-661A-44E6-9324-8786B8DBE231/accessdatabaseengine_X64.exe', 'https://download.microsoft.com/download/3/5/C/35C84C36-661A-44E6-9324-8786B8DBE231/accessdatabaseengine_X64.exe'),
+ '', False, False);
+ end;
+end;
+
+[Files]
+#ifdef Dependency_Path_DirectX
+Source: "{#Dependency_Path_DirectX}dxwebsetup.exe"; Flags: dontcopy noencryption
+#endif
diff --git a/InnoSetup/setup.iss b/InnoSetup/setup.iss
index 81eb054f..f70a992b 100644
--- a/InnoSetup/setup.iss
+++ b/InnoSetup/setup.iss
@@ -1,6 +1,8 @@
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
+#include "CodeDependencies.iss"
+
#define AppId "{{95F4CECE-7C7C-40E1-B485-07DA64D1905F}"
#define MyAppName "Virtual Paper"
#define MyAppVersion "0.4.0.1"
@@ -11,9 +13,9 @@
[CustomMessages]
english.DeleteEverythigMsgBox=Do you want to delete data folder?
-chinese.DeleteEverythigMsgBox=Ҫɾļ
+chinese.DeleteEverythigMsgBox=您要删除数据文件夹吗?
english.ClosingMsgBox=Application is running, do you want to close it?
-chinese.ClosingMsgBox=ӦóУǷرգ
+chinese.ClosingMsgBox=应用程序正在运行,是否关闭?
[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
@@ -27,8 +29,12 @@ AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
AppCopyright=Virtual@PaperHammer
+VersionInfoVersion={#MyAppVersion}
+VersionInfoProductName={#MyAppName}
+VersionInfoCompany={#MyAppPublisher}
+VersionInfoDescription={#MyAppName} Installer
DefaultDirName={autopf}\{#MyAppName}
-ChangesAssociations=yes
+ChangesAssociations=no
DisableProgramGroupPage=yes
OutputBaseFilename=virtualpaper_setup_x64_full_v{#MyAppVersionText}
; Uncomment the following line to run in non administrative install mode (install for current user only.)
@@ -46,15 +52,14 @@ ArchitecturesInstallIn64BitMode=x64compatible
UsePreviousAppDir=yes
[Languages]
-Name: "english"; MessagesFile: "compiler:Default.isl"; LicenseFile: "License\License.txt";
+Name: "english"; MessagesFile: "compiler:Default.isl"; LicenseFile: "License\LICENSE.txt";
Name: "chinese"; MessagesFile: "Languages\ChineseSimplified.isl";
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
-; Name: "windowsstartup";Description: "{cm:AutoStartProgram,{#MyAppName}}"
+; Name: "windowsstartup"; Description: "{cm:AutoStartProgram,{#MyAppName}}"
[Files]
-Source: "..\src\VirtualPaper\bin\Release\net8.0-windows10.0.19041.0\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\src\VirtualPaper\bin\Release\net8.0-windows10.0.19041.0\*"; DestDir: "{app}"; Excludes: "*.pdb,*.xml"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
@@ -67,40 +72,35 @@ Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: de
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall; Check: AutoLaunch
-;skipifsilent
[Code]
-function GetPreviousInstallDir(): string;
-var
- path: string;
+// InnoDependencyInstaller 初始化向导 UI
+procedure InitializeWizard;
begin
- if RegQueryStringValue(HKLM,
- 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1',
- 'InstallLocation', path) then
- begin
- Result := path;
- end
- else if RegQueryStringValue(HKCU,
- 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1',
- 'InstallLocation', path) then
- begin
- Result := path;
- end
- else
- Result := '';
+ Dependency_InitializeWizard;
end;
-procedure InitializeWizard();
-var
- oldDir: string;
+// 在"准备安装"页面列出待安装的依赖项
+function UpdateReadyMemo(const Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
begin
- oldDir := GetPreviousInstallDir();
+ Result := Dependency_UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo);
+end;
- if oldDir <> '' then
- begin
- WizardForm.DirEdit.Text := oldDir;
- end;
+// 声明 .NET 8.0 Desktop Runtime 为安装前置依赖
+// InnoDependencyInstaller 自动处理检测、下载、安装
+// 静默安装时跳过(CI 环境已由 setup-dotnet action 提供运行时)
+function PrepareToInstall(var NeedsRestart: Boolean): String;
+begin
+ if not WizardSilent() then
+ Dependency_AddDotNet80Desktop;
+ Result := Dependency_PrepareToInstall(NeedsRestart);
+end;
+
+// 安装完成后是否需要重启
+function NeedRestart: Boolean;
+begin
+ Result := Dependency_NeedRestart;
end;
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
@@ -124,7 +124,7 @@ begin
Result := True;
if CheckForMutexes('Virtual:WALLPAPERSYSTEM') then
begin
- if SuppressibleMsgBox('{cm:ClosingMsgBox}',
+ if SuppressibleMsgBox(ExpandConstant('{cm:ClosingMsgBox}'),
mbConfirmation, MB_OKCANCEL, IDOK) = IDOK then
begin
ShellExec('open', 'taskkill.exe', '/f /im {#MyAppExeName}',
@@ -136,20 +136,6 @@ begin
end;
end;
-function NetCoreNeedsInstall(version: String): Boolean;
-var
- netcoreRuntime: String;
- resultCode: Integer;
-begin
- netcoreRuntime := 'Microsoft.WindowsDesktop.App';
- Result := not (
- Exec(ExpandConstant('{tmp}\netcorecheck.exe'),
- netcoreRuntime + ' ' + version,
- '', SW_HIDE, ewWaitUntilTerminated, resultCode)
- and (resultCode = 0)
- );
-end;
-
function CmdLineParamNotExists(const Value: string): Boolean;
var
I: Integer;
@@ -165,5 +151,7 @@ end;
function AutoLaunch(): Boolean;
begin
- Result := CmdLineParamNotExists('/NOAUTOLAUNCH');
+ // /NOAUTOLAUNCH 显式禁止,或 /VERYSILENT(CI 静默安装)时不自动启动
+ // /SILENT(业务自动更新)时仍正常重启 App
+ Result := CmdLineParamNotExists('/NOAUTOLAUNCH') and CmdLineParamNotExists('/VERYSILENT');
end;
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
new file mode 100644
index 00000000..aca8cfc6
--- /dev/null
+++ b/src/Directory.Build.targets
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/src/VirtualPaper.Core.Test/T_AppUpdate/GithubUpdaterServiceTests.cs b/src/VirtualPaper.Core.Test/T_AppUpdate/GithubUpdaterServiceTests.cs
index 94539c6a..563b8d26 100644
--- a/src/VirtualPaper.Core.Test/T_AppUpdate/GithubUpdaterServiceTests.cs
+++ b/src/VirtualPaper.Core.Test/T_AppUpdate/GithubUpdaterServiceTests.cs
@@ -12,7 +12,7 @@ public class GithubUpdaterServiceTests {
private static readonly Uri FakeUri = new("https://fake/setup.exe");
private static readonly Uri FakeShaUri = new("https://fake/SHA256.txt");
- private static readonly Version FakeVersion = new(1, 2, 3, 4);
+ private static readonly Version FakeVersion = new(0, 0, 0, 0);
private const string FakeChangelog = "- bug fix";
[TestInitialize]
diff --git a/src/VirtualPaper.DraftPanel/Views/WorkSpace.xaml.cs b/src/VirtualPaper.DraftPanel/Views/WorkSpace.xaml.cs
index d69f086c..562aeb6e 100644
--- a/src/VirtualPaper.DraftPanel/Views/WorkSpace.xaml.cs
+++ b/src/VirtualPaper.DraftPanel/Views/WorkSpace.xaml.cs
@@ -104,7 +104,7 @@ private void SyncWorkspaceUI() {
// 根据当前的选中项,控制可见性
var selectedItem = TabViewControl.SelectedItem as ArcTabViewItem;
foreach (var kvp in _tabToFrame) {
- kvp.Value.Visibility = (kvp.Key == selectedItem) ? Visibility.Visible : Visibility.Collapsed;
+ kvp.Value.Visibility = (kvp.Key as ArcTabViewItem == selectedItem) ? Visibility.Visible : Visibility.Collapsed;
}
}
#endregion
diff --git a/src/VirtualPaper.IntelligentPanel/Views/StyleTransferComp/StyleTransferIntroduction.xaml b/src/VirtualPaper.IntelligentPanel/Views/StyleTransferComp/StyleTransferIntroduction.xaml
index 79c2ee46..9defbb68 100644
--- a/src/VirtualPaper.IntelligentPanel/Views/StyleTransferComp/StyleTransferIntroduction.xaml
+++ b/src/VirtualPaper.IntelligentPanel/Views/StyleTransferComp/StyleTransferIntroduction.xaml
@@ -9,6 +9,29 @@
xmlns:markup="using:VirtualPaper.UIComponent.ArcMarkupExtension"
mc:Ignorable="d">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -22,17 +45,20 @@
Width="480"
Height="270"
CornerRadius="12"
- Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
VerticalAlignment="Center">
-
-
+
+
+
+
+
-
+
-
-
-
-
+ BeforeImageSourceKey="{x:Bind _intelli_Demo_SuperResolution_Origin}"
+ AfterImageSourceKey="{x:Bind _intelli_Demo_SuperResolution_Result}"
+ AutoAnimate="True"
+ AnimationDuration="0:0:2.5"
+ PauseDuration="0:0:1"/>
enable
true
+
+
+
+
+
diff --git a/src/VirtualPaper.Shader.Test/VirtualPaper.Shader.Test.csproj b/src/VirtualPaper.Shader.Test/VirtualPaper.Shader.Test.csproj
index 50550ad0..764c66a8 100644
--- a/src/VirtualPaper.Shader.Test/VirtualPaper.Shader.Test.csproj
+++ b/src/VirtualPaper.Shader.Test/VirtualPaper.Shader.Test.csproj
@@ -7,6 +7,13 @@
enable
true
+
+
+
+
+
diff --git a/src/VirtualPaper.UI.Test/VirtualPaper.UI.Test.csproj b/src/VirtualPaper.UI.Test/VirtualPaper.UI.Test.csproj
index e1db636c..2077bc10 100644
--- a/src/VirtualPaper.UI.Test/VirtualPaper.UI.Test.csproj
+++ b/src/VirtualPaper.UI.Test/VirtualPaper.UI.Test.csproj
@@ -9,6 +9,19 @@
true
+
+
+
+
+
diff --git a/src/VirtualPaper.UI/Assets/intelligent/demo/style_transfer/origin.jpg b/src/VirtualPaper.UI/Assets/intelligent/demo/style_transfer/origin.jpg
new file mode 100644
index 00000000..2dd270fa
Binary files /dev/null and b/src/VirtualPaper.UI/Assets/intelligent/demo/style_transfer/origin.jpg differ
diff --git a/src/VirtualPaper.UI/Assets/intelligent/demo/style_transfer/result.jpg b/src/VirtualPaper.UI/Assets/intelligent/demo/style_transfer/result.jpg
new file mode 100644
index 00000000..7a229f68
Binary files /dev/null and b/src/VirtualPaper.UI/Assets/intelligent/demo/style_transfer/result.jpg differ
diff --git a/src/VirtualPaper.UI/Assets/intelligent/demo/super_resolution/origin.jpg b/src/VirtualPaper.UI/Assets/intelligent/demo/super_resolution/origin.jpg
new file mode 100644
index 00000000..8fac6178
Binary files /dev/null and b/src/VirtualPaper.UI/Assets/intelligent/demo/super_resolution/origin.jpg differ
diff --git a/src/VirtualPaper.UI/Assets/intelligent/demo/super_resolution/result.jpg b/src/VirtualPaper.UI/Assets/intelligent/demo/super_resolution/result.jpg
new file mode 100644
index 00000000..7d4a26e8
Binary files /dev/null and b/src/VirtualPaper.UI/Assets/intelligent/demo/super_resolution/result.jpg differ
diff --git a/src/VirtualPaper.UI/VirtualPaper.UI.csproj b/src/VirtualPaper.UI/VirtualPaper.UI.csproj
index e8adcde4..ac485ff9 100644
--- a/src/VirtualPaper.UI/VirtualPaper.UI.csproj
+++ b/src/VirtualPaper.UI/VirtualPaper.UI.csproj
@@ -68,6 +68,10 @@
+
+
+
+
@@ -119,6 +123,18 @@
PreserveNewest
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
PreserveNewest
diff --git a/src/VirtualPaper.UIComponent/Resources/ResourceDictionary.xaml b/src/VirtualPaper.UIComponent/Resources/ResourceDictionary.xaml
index 3b7ead62..445e9abf 100644
--- a/src/VirtualPaper.UIComponent/Resources/ResourceDictionary.xaml
+++ b/src/VirtualPaper.UIComponent/Resources/ResourceDictionary.xaml
@@ -30,6 +30,11 @@
+
+
+
+
+