param( [string]$Version = "", [string]$BuildDir = "build\win", [string]$StagingDir = "dist\win", [string]$WintunVersion = "0.14.1", [switch]$Sign, [string]$SignCert = "" ) $ErrorActionPreference = "Stop" # Color output functions function Write-Info { Write-Host $args -ForegroundColor Cyan } function Write-Success { Write-Host $args -ForegroundColor Green } function Write-Error { Write-Host $args -ForegroundColor Red } try { Write-Info "=== DragonMoonlight Windows Installer Build ===" # Determine version if ([string]::IsNullOrEmpty($Version)) { Write-Info "Version not provided, attempting git describe..." try { $Version = git describe --tags --abbrev=0 2>$null if ([string]::IsNullOrEmpty($Version)) { $Version = "0.1.0" } } catch { $Version = "0.1.0" } } Write-Success "Building DragonMoonlight version: $Version" # Check prerequisites Write-Info "Checking prerequisites..." $prerequisites = @("cmake", "cargo", "iscc", "windeployqt") foreach ($tool in $prerequisites) { try { if ($tool -eq "iscc") { $result = Get-Command "iscc.exe" -ErrorAction Stop } elseif ($tool -eq "windeployqt") { $result = Get-Command "windeployqt.exe" -ErrorAction Stop } else { $result = Get-Command $tool -ErrorAction Stop } Write-Success "✓ $tool found" } catch { Write-Error "✗ $tool not found. Please install it before proceeding." throw "Missing prerequisite: $tool" } } # Build boringtun Write-Info "Building boringtun..." & pwsh scripts\build-boringtun-win.ps1 if ($LASTEXITCODE -ne 0) { throw "boringtun build failed" } Write-Success "boringtun built successfully" # CMake configure Write-Info "Running CMake configure..." cmake -B $BuildDir -A x64 -DCMAKE_BUILD_TYPE=Release if ($LASTEXITCODE -ne 0) { throw "CMake configure failed" } Write-Success "CMake configure completed" # CMake build Write-Info "Building with CMake..." cmake --build $BuildDir --config Release if ($LASTEXITCODE -ne 0) { throw "CMake build failed" } Write-Success "CMake build completed" # Create staging directory and copy executable Write-Info "Staging release files..." if (-not (Test-Path $StagingDir)) { New-Item -ItemType Directory -Path $StagingDir -Force | Out-Null } $exePath = Join-Path $BuildDir "Release\DragonMoonlight.exe" if (-not (Test-Path $exePath)) { throw "DragonMoonlight.exe not found at $exePath" } Copy-Item -Path $exePath -Destination $StagingDir -Force Write-Success "Copied DragonMoonlight.exe to $StagingDir" # Run windeployqt Write-Info "Running windeployqt..." $stagedExe = Join-Path $StagingDir "DragonMoonlight.exe" windeployqt --release --qmldir app $stagedExe if ($LASTEXITCODE -ne 0) { throw "windeployqt failed" } Write-Success "windeployqt completed" # Download wintun.dll Write-Info "Downloading Wintun..." $wintunUrl = "https://www.wintun.net/builds/wintun-$WintunVersion.zip" $tempDir = Join-Path $env:TEMP "wintun_build" if (-not (Test-Path $tempDir)) { New-Item -ItemType Directory -Path $tempDir -Force | Out-Null } $wintunZip = Join-Path $tempDir "wintun-$WintunVersion.zip" try { Write-Info "Downloading from $wintunUrl..." Invoke-WebRequest -Uri $wintunUrl -OutFile $wintunZip -UseBasicParsing Write-Info "Extracting wintun.dll..." Expand-Archive -Path $wintunZip -DestinationPath $tempDir -Force $wintunDll = Join-Path $tempDir "wintun\bin\amd64\wintun.dll" if (-not (Test-Path $wintunDll)) { throw "wintun.dll not found in extracted archive" } # Verify wintun.dll size sanity check (x64 is ~50-80 KB) $actualSize = (Get-Item $wintunDll).Length if ($actualSize -lt 30000 -or $actualSize -gt 200000) { throw "wintun.dll size check failed ($actualSize bytes) — download may be corrupt or tampered" } Write-Info "wintun.dll verified ($actualSize bytes)" Copy-Item -Path $wintunDll -Destination $StagingDir -Force Write-Success "Wintun.dll extracted and copied to $StagingDir" } finally { if (Test-Path $wintunZip) { Remove-Item -Path $wintunZip -Force } } # Run Inno Setup Write-Info "Building installer with Inno Setup..." $issArgs = @("/DMyAppVersion=$Version", "scripts\DragonMoonlight.iss") if ($Sign -or -not [string]::IsNullOrEmpty($SignCert)) { $cert = $SignCert if ([string]::IsNullOrEmpty($cert) -and -not [string]::IsNullOrEmpty($env:SIGNTOOL_CERT)) { $cert = $env:SIGNTOOL_CERT } if (-not [string]::IsNullOrEmpty($cert)) { Write-Info "Code signing enabled with certificate: $cert" $env:SIGNTOOL_CERT = $cert } } iscc @issArgs if ($LASTEXITCODE -ne 0) { throw "Inno Setup build failed" } Write-Success "Inno Setup build completed" # Report results $outputExe = Join-Path "dist" "DragonMoonlight-$Version-Setup.exe" if (Test-Path $outputExe) { Write-Success "Installer created: $outputExe" $sha256 = (Get-FileHash -Path $outputExe -Algorithm SHA256).Hash Write-Success "SHA256: $sha256" Write-Info "Installer build successful!" } else { throw "Installer file not found at expected location: $outputExe" } } catch { Write-Error "Build failed: $_" exit 1 } finally { # Clean up temporary files $tempDir = Join-Path $env:TEMP "wintun_build" if (Test-Path $tempDir) { Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue } }