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>
373 lines
14 KiB
PowerShell
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
|