Files
navi/scripts/pack-msix.ps1
T
NavisandCursor 50f7f71393 Add site contacts/FAQ and accept AWG 2.0 / naive pastes in Add config.
ImportSubscription no longer requires only http(s): paste awg://, AWG .conf/JSON, or naive links; subscription parser also reads hosted AWG and Clash wireguard/naive. Site lists support emails and FAQ; Store listing texts/tools updated.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 15:40:13 +03:00

373 lines
14 KiB
PowerShell

#Requires -Version 5.1
<#
.SYNOPSIS
Pack EvilFox.exe into per-arch MSIX (and optional .msixbundle) for Microsoft Store.
.DESCRIPTION
Builds Store assets, stages AppxManifest + EvilFox.exe per architecture, runs MakeAppx.
Default output: dist\microsoft-store\EvilFox_<version>_<arch>.msix
Prefer MSIX over PWA: EvilFox needs Win32/WebView2 VPN and system-proxy access.
resource.syso note (arm64 cross-build): goversioninfo -64 emits an amd64 .syso.
For GOARCH=arm64 builds, temporarily rename cmd/vpnapp/resource.syso aside, then restore.
.EXAMPLE
.\scripts\pack-msix.ps1
.\scripts\pack-msix.ps1 -Arch x64 -Version 4.2.8.0
.\scripts\pack-msix.ps1 -Arch arm64 -ExePath dist\navis-release\windows\arm64\EvilFox.exe
.\scripts\pack-msix.ps1 -Arch all -OutDir dist\microsoft-store
Store note: Identity Version MUST end with .0 (revision). Partner Center rejects 4.2.8.1.
#>
[CmdletBinding()]
param(
[ValidateSet("x64", "arm64", "all")]
[string]$Arch = "all",
[string]$ExePath = "",
[string]$OutDir = "",
[string]$Version = "4.2.8.0",
[string]$Name = "EvilFox.evilfox",
[string]$Publisher = "CN=9D58DFF7-7918-460E-845B-B3904A30562A",
[string]$DisplayName = "EvilFox",
[string]$PublisherDisplayName = "EvilFox",
[string]$CertPath = "",
[string]$CertPassword = "",
[switch]$SkipAssets,
[switch]$SkipBundle
)
$ErrorActionPreference = "Stop"
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
Set-Location $Root
if (-not $OutDir) {
$OutDir = Join-Path $Root "dist\microsoft-store"
}
if (-not [System.IO.Path]::IsPathRooted($OutDir)) {
$OutDir = Join-Path $Root $OutDir
}
$OutDir = [System.IO.Path]::GetFullPath($OutDir)
function Find-SdkTool([string]$name) {
$candidates = @()
$kits = Join-Path ${env:ProgramFiles(x86)} "Windows Kits\10\bin"
if (Test-Path $kits) {
Get-ChildItem -Path $kits -Directory -ErrorAction SilentlyContinue |
Sort-Object Name -Descending |
ForEach-Object {
$candidates += (Join-Path $_.FullName "x64\$name")
$candidates += (Join-Path $_.FullName "x86\$name")
}
}
# MSIX Toolkit via winget (MakeAppx without full Windows SDK)
$wingetPkgs = Join-Path $env:LOCALAPPDATA "Microsoft\WinGet\Packages"
if (Test-Path $wingetPkgs) {
Get-ChildItem -Path $wingetPkgs -Directory -Filter "Microsoft.MSIX-Toolkit*" -ErrorAction SilentlyContinue |
ForEach-Object {
$candidates += (Join-Path $_.FullName "MSIX-Toolkit.x64\$name")
$candidates += (Join-Path $_.FullName "MSIX-Toolkit.x86\$name")
Get-ChildItem -Path $_.FullName -Recurse -Filter $name -ErrorAction SilentlyContinue |
ForEach-Object { $candidates += $_.FullName }
}
}
foreach ($p in $candidates) {
if (Test-Path -LiteralPath $p) { return $p }
}
$cmd = Get-Command $name -ErrorAction SilentlyContinue
if ($cmd) { return $cmd.Source }
return $null
}
function Resolve-ArchExe([string]$arch, [string]$explicit) {
if ($explicit) { return $explicit }
switch ($arch) {
"x64" {
return Join-Path $Root "dist\navis-release\windows\EvilFox.exe"
}
"arm64" {
return Join-Path $Root "dist\navis-release\windows\arm64\EvilFox.exe"
}
default { throw "Unsupported arch: $arch" }
}
}
function Find-WintunDll([string]$arch) {
$candidates = @()
if ($arch -eq "arm64") {
$candidates += @(
(Join-Path $Root "assets\wintun\arm64\wintun.dll"),
(Join-Path $Root "dist\navis-release\windows\arm64\wintun.dll")
)
} else {
$candidates += @(
(Join-Path $Root "assets\wintun\x64\wintun.dll"),
(Join-Path $Root "assets\wintun\amd64\wintun.dll"),
(Join-Path $Root "dist\navis-release\windows\wintun.dll")
)
}
$candidates += @(
(Join-Path $Root "assets\wintun\$arch\wintun.dll"),
(Join-Path $Root "assets\wintun\wintun.dll"),
(Join-Path $Root "wintun.dll")
)
foreach ($p in $candidates) {
if (Test-Path -LiteralPath $p) { return $p }
}
return $null
}
function Write-StoreReadme(
[string]$dir,
[string]$version,
[string[]]$stagedArchs,
[string[]]$msixPaths,
[string]$bundlePath,
[string]$makeAppxPath,
[hashtable]$makeAppxCmds,
[bool]$missingWintun
) {
$tplPath = Join-Path $Root "packaging\msix\STORE_README_TEMPLATE.txt"
if (-not (Test-Path -LiteralPath $tplPath)) {
throw "Missing template: $tplPath"
}
$text = [System.IO.File]::ReadAllText($tplPath, [System.Text.Encoding]::UTF8)
$text = $text.Replace("{{VERSION}}", $version)
$text = $text.Replace("{{GENERATED}}", (Get-Date -Format "yyyy-MM-dd HH:mm:ss"))
$artifacts = New-Object System.Collections.Generic.List[string]
if ($bundlePath -and (Test-Path -LiteralPath $bundlePath)) {
$artifacts.Add("Bundle: $bundlePath")
} else {
$artifacts.Add("Bundle: (not created)")
}
foreach ($p in $msixPaths) {
if (Test-Path -LiteralPath $p) {
$artifacts.Add("MSIX: $p")
}
}
foreach ($a in $stagedArchs) {
$stg = Join-Path $Root ("packaging\msix\_staging_" + $a)
$artifacts.Add("Staging ${a}: $stg")
}
$text = $text.Replace("{{ARTIFACTS}}", ($artifacts -join "`r`n"))
$makeLines = New-Object System.Collections.Generic.List[string]
if ($makeAppxPath) {
$makeLines.Add("Found: $makeAppxPath")
} else {
$makeLines.Add("MakeAppx.exe NOT FOUND on this machine.")
$makeLines.Add("Install Windows 10/11 SDK, then run:")
foreach ($a in $stagedArchs) {
if ($makeAppxCmds.ContainsKey($a)) {
$makeLines.Add(" $($makeAppxCmds[$a])")
}
}
if ($stagedArchs.Count -ge 2) {
$bundleDir = Join-Path $dir "_bundle_input"
$bundleOut = Join-Path $dir ("EvilFox_" + $version + ".msixbundle")
$makeLines.Add(" makeappx bundle /d `"$bundleDir`" /p `"$bundleOut`" /o")
$makeLines.Add(" (copy both .msix into _bundle_input first)")
}
}
$text = $text.Replace("{{MAKEAPPX}}", ($makeLines -join "`r`n"))
if ($missingWintun) {
$wintun = @(
"wintun.dll was NOT found under assets/ or dist/. Packaging continued.",
"VPN (TUN) mode needs wintun.dll next to EvilFox.exe.",
"Store users can still use proxy mode without it.",
"Add arch-specific wintun.dll later under assets/wintun/<arch>/ if needed."
) -join "`r`n"
} else {
$wintun = "wintun.dll was included in staging where found for the target arch."
}
$text = $text.Replace("{{WINTUN}}", $wintun)
$utf8 = New-Object System.Text.UTF8Encoding $false
[System.IO.File]::WriteAllText((Join-Path $dir "README.txt"), $text, $utf8)
}
$MakeAppx = Find-SdkTool "makeappx.exe"
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
$archList = @()
if ($Arch -eq "all") {
$archList = @("x64", "arm64")
} else {
$archList = @($Arch)
}
# When -Arch all, prefer per-arch default exe paths (ignore -ExePath).
$explicitExe = ""
if ($Arch -ne "all") { $explicitExe = $ExePath }
if (-not $SkipAssets) {
Write-Host "Generating Store assets..."
go run ./tools/mkstoreassets
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}
$producedMsix = @()
$stagedArchs = @()
$makeAppxCmds = @{}
$anyMissingExe = $false
$missingWintun = $false
$packFailed = $false
$makeAppxMissing = -not [bool]$MakeAppx
foreach ($a in $archList) {
$exe = Resolve-ArchExe $a $explicitExe
if (-not (Test-Path -LiteralPath $exe)) {
Write-Host "SKIP ${a}: EvilFox.exe not found at '$exe'" -ForegroundColor Yellow
$anyMissingExe = $true
continue
}
$Staging = Join-Path $Root ("packaging\msix\_staging_" + $a)
if (Test-Path $Staging) { Remove-Item -Recurse -Force $Staging }
New-Item -ItemType Directory -Path $Staging | Out-Null
New-Item -ItemType Directory -Path (Join-Path $Staging "Assets") | Out-Null
$AssetsSrc = Join-Path $Root "packaging\msix\Assets"
if (-not (Test-Path $AssetsSrc)) {
throw "Store assets missing at $AssetsSrc (run without -SkipAssets)."
}
Copy-Item -Path (Join-Path $AssetsSrc "*") -Destination (Join-Path $Staging "Assets") -Force
Copy-Item -LiteralPath $exe -Destination (Join-Path $Staging "EvilFox.exe") -Force
$wintun = Find-WintunDll $a
if ($wintun) {
Copy-Item -LiteralPath $wintun -Destination (Join-Path $Staging "wintun.dll") -Force
Write-Host "Included wintun.dll for $a from $wintun"
} else {
$missingWintun = $true
Write-Host "No wintun.dll for $a (proxy mode still OK; TUN needs dll next to exe)." -ForegroundColor DarkYellow
}
$ManifestSrc = Join-Path $Root "packaging\msix\AppxManifest.xml"
$ManifestDst = Join-Path $Staging "AppxManifest.xml"
$manifestText = Get-Content -LiteralPath $ManifestSrc -Raw -Encoding UTF8
# Prefer exact placeholder swaps so we never touch <?xml version=...?>.
$manifestText = $manifestText.Replace('Name="EvilFox.evilfox"', "Name=`"$Name`"")
$manifestText = $manifestText.Replace('Name="EvilFox.EvilFox"', "Name=`"$Name`"")
$manifestText = $manifestText.Replace('Publisher="CN=9D58DFF7-7918-460E-845B-B3904A30562A"', "Publisher=`"$Publisher`"")
$manifestText = $manifestText.Replace('Publisher="CN=EvilFox"', "Publisher=`"$Publisher`"")
# Store requires Identity Version revision (4th component) to be 0.
if ($Version -notmatch '^\d+\.\d+\.\d+\.0$') {
Write-Host "WARNING: Partner Center rejects non-zero revision; use e.g. 4.2.8.0 (got '$Version')." -ForegroundColor Yellow
}
$manifestText = $manifestText.Replace('Version="4.2.8.0"', "Version=`"$Version`"")
$manifestText = $manifestText.Replace('Version="4.2.8.1"', "Version=`"$Version`"")
# Also accept older template version if someone reverts the manifest placeholder.
$manifestText = $manifestText.Replace('Version="4.2.3.1"', "Version=`"$Version`"")
$manifestText = $manifestText.Replace('ProcessorArchitecture="x64"', "ProcessorArchitecture=`"$a`"")
$manifestText = $manifestText.Replace('ProcessorArchitecture="arm64"', "ProcessorArchitecture=`"$a`"")
$manifestText = $manifestText.Replace('<DisplayName>EvilFox</DisplayName>', "<DisplayName>$DisplayName</DisplayName>")
$manifestText = $manifestText.Replace('<PublisherDisplayName>EvilFox</PublisherDisplayName>', "<PublisherDisplayName>$PublisherDisplayName</PublisherDisplayName>")
$manifestText = $manifestText.Replace('DisplayName="EvilFox"', "DisplayName=`"$DisplayName`"")
[System.IO.File]::WriteAllText($ManifestDst, $manifestText, (New-Object System.Text.UTF8Encoding $false))
$MsixPath = Join-Path $OutDir ("EvilFox_" + $Version + "_" + $a + ".msix")
$makeAppxCmds[$a] = ("makeappx pack /d `"{0}`" /p `"{1}`" /o" -f $Staging, $MsixPath)
$stagedArchs += $a
if ($makeAppxMissing) {
Write-Host "Staged $a (MakeAppx missing): $Staging" -ForegroundColor Yellow
continue
}
if (Test-Path $MsixPath) { Remove-Item -Force $MsixPath }
Write-Host "Packing $MsixPath ..."
& $MakeAppx pack /d $Staging /p $MsixPath /o
if ($LASTEXITCODE -ne 0) {
Write-Host "MakeAppx pack failed for $a (exit $LASTEXITCODE)" -ForegroundColor Red
$packFailed = $true
continue
}
if ($CertPath) {
$SignTool = Find-SdkTool "signtool.exe"
if (-not $SignTool) { throw "signtool.exe not found (needed for -CertPath)." }
Write-Host "Signing $a with $CertPath ..."
if ($CertPassword) {
& $SignTool sign /fd SHA256 /a /f $CertPath /p $CertPassword $MsixPath
} else {
& $SignTool sign /fd SHA256 /a /f $CertPath $MsixPath
}
if ($LASTEXITCODE -ne 0) {
Write-Host "Sign failed for $a" -ForegroundColor Red
$packFailed = $true
continue
}
}
$producedMsix += $MsixPath
Write-Host "Packed: $MsixPath"
}
$BundlePath = Join-Path $OutDir ("EvilFox_" + $Version + ".msixbundle")
if (-not $SkipBundle -and -not $makeAppxMissing -and $producedMsix.Count -ge 2) {
$bundleInput = Join-Path $OutDir "_bundle_input"
if (Test-Path $bundleInput) { Remove-Item -Recurse -Force $bundleInput }
New-Item -ItemType Directory -Path $bundleInput | Out-Null
foreach ($p in $producedMsix) {
Copy-Item -LiteralPath $p -Destination (Join-Path $bundleInput (Split-Path $p -Leaf)) -Force
}
if (Test-Path $BundlePath) { Remove-Item -Force $BundlePath }
Write-Host "Bundling $BundlePath ..."
& $MakeAppx bundle /d $bundleInput /p $BundlePath /o
if ($LASTEXITCODE -ne 0) {
Write-Host "makeappx bundle failed (exit $LASTEXITCODE)" -ForegroundColor Yellow
$BundlePath = ""
} else {
Write-Host "Bundle: $BundlePath"
}
} elseif ($producedMsix.Count -eq 1) {
$BundlePath = ""
}
Write-StoreReadme -dir $OutDir -version $Version -stagedArchs $stagedArchs `
-msixPaths $producedMsix -bundlePath $BundlePath -makeAppxPath $MakeAppx `
-makeAppxCmds $makeAppxCmds -missingWintun $missingWintun
# Copy Partner Center listing inputs when present in the repo.
$listingDir = Join-Path $OutDir "listing"
$listingScreen = Join-Path $listingDir "screen"
New-Item -ItemType Directory -Force -Path $listingScreen | Out-Null
$storeCsv = Join-Path $Root "store.csv"
if (Test-Path -LiteralPath $storeCsv) {
Copy-Item -LiteralPath $storeCsv -Destination (Join-Path $listingDir "store.csv") -Force
}
$screenSrc = Join-Path $Root "screen"
if (Test-Path -LiteralPath $screenSrc) {
Copy-Item -Path (Join-Path $screenSrc "*") -Destination $listingScreen -Force
}
Write-Host ""
Write-Host "Store folder: $OutDir"
Write-Host "README: $(Join-Path $OutDir 'README.txt')"
Write-Host "Staged: $($stagedArchs -join ', ')"
if ($producedMsix.Count -gt 0) {
Write-Host "MSIX: $($producedMsix -join ', ')"
}
# Exit non-zero only after staging both (when requested) if MakeAppx missing,
# or if packing failed / required exes missing.
if ($makeAppxMissing) {
if ($Arch -eq "all" -and $stagedArchs.Count -lt 2) {
Write-Host "MakeAppx missing and could not stage both arches." -ForegroundColor Red
exit 2
}
if ($stagedArchs.Count -eq 0) {
Write-Host "Nothing staged." -ForegroundColor Red
exit 2
}
Write-Host "MakeAppx missing - staging complete; see README.txt for pack commands." -ForegroundColor Yellow
exit 2
}
if ($packFailed) { exit 1 }
if ($anyMissingExe -and $Arch -eq "all" -and $producedMsix.Count -lt 2) { exit 1 }
if ($producedMsix.Count -eq 0) { exit 1 }
exit 0