param( [string]$OutputDirectory = (Join-Path $PSScriptRoot 'dist'), [string]$BuildDirectory = (Join-Path $PSScriptRoot '..\..\build') ) $ErrorActionPreference = 'Stop' $firmwareRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..')) $sourcePath = Join-Path $PSScriptRoot 'EchoEarFlasher.cs' $compiler = Join-Path $env:WINDIR 'Microsoft.NET\Framework64\v4.0.30319\csc.exe' if (-not (Test-Path -LiteralPath $compiler)) { throw 'Windows .NET Framework C# compiler is required.' } $outputPath = [IO.Path]::GetFullPath($OutputDirectory) $buildPath = (Resolve-Path -LiteralPath $BuildDirectory).Path $manifestPath = Join-Path $buildPath 'flasher_args.json' $manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json if ($manifest.extra_esptool_args.chip -ne 'esp32s3') { throw 'The build must target ESP32-S3.' } $null = New-Item -ItemType Directory -Path $outputPath -Force $enginePath = Join-Path $outputPath 'engine' $packageBuild = Join-Path $outputPath 'firmware-build' $null = New-Item -ItemType Directory -Path $enginePath -Force $null = New-Item -ItemType Directory -Path $packageBuild -Force # Allowlist only application code; never copy private credentials or backups. foreach ($name in @('ble_factory.py', 'device_prepare.py', 'device_flash.py', 'flash_app_bridge.py')) { Copy-Item -LiteralPath (Join-Path $firmwareRoot "tools\$name") -Destination (Join-Path $enginePath $name) -Force } Copy-Item -LiteralPath $manifestPath -Destination (Join-Path $packageBuild 'flasher_args.json') -Force foreach ($entry in $manifest.flash_files.PSObject.Properties) { $relative = [string]$entry.Value if ([IO.Path]::IsPathRooted($relative) -or ($relative -split '[/\\]') -contains '..') { throw 'Unsafe image path.' } $imagePath = [IO.Path]::GetFullPath((Join-Path $buildPath $relative)) if (-not $imagePath.StartsWith($buildPath.TrimEnd('\') + '\', [StringComparison]::OrdinalIgnoreCase)) { throw 'Image escapes build directory.' } $target = Join-Path $packageBuild $relative $null = New-Item -ItemType Directory -Path (Split-Path -Parent $target) -Force Copy-Item -LiteralPath $imagePath -Destination $target -Force } $executable = Join-Path $outputPath 'EchoEarFlasher.exe' & $compiler /nologo /target:winexe /platform:x64 /optimize+ /codepage:65001 ` /r:System.dll /r:System.Core.dll /r:System.Drawing.dll /r:System.Windows.Forms.dll /r:System.Web.Extensions.dll ` "/out:$executable" $sourcePath if ($LASTEXITCODE -ne 0) { throw 'Application compilation failed.' } $guide = Join-Path $PSScriptRoot '使用说明.txt' if (Test-Path -LiteralPath $guide) { Copy-Item -LiteralPath $guide -Destination $outputPath -Force } $records = Get-ChildItem -LiteralPath $outputPath -File -Recurse | Where-Object { $_.FullName -ne (Join-Path $outputPath 'package-files.json') } | ForEach-Object { [ordered]@{ file = $_.FullName.Substring($outputPath.Length + 1); size = $_.Length; sha256 = (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant() } } [ordered]@{ application = 'EchoEar 烧录助手'; hardware = 'ESP32-S3 / 16MB'; built_at = (Get-Date).ToString('o'); files = @($records) } | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $outputPath 'package-files.json') -Encoding UTF8 Write-Output "Built: $executable"