mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-10-14 17:47:13 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ae2d41378 | ||
|
|
070603b0fe | ||
|
|
8f47b5cedf | ||
|
|
646ad029f0 | ||
|
|
d47963247d |
@@ -1,428 +0,0 @@
|
||||
name: Windows MSI/MSIX Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- 'release/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
windows_msi_build:
|
||||
runs-on: windows-2022
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
attestations: write
|
||||
artifact-metadata: write
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [x64]
|
||||
arch: [x64]
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.14.3'
|
||||
architecture: ${{ matrix.arch }}
|
||||
|
||||
- name: Install Dependencies
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -Name "deps" -ItemType "Directory" -Force | Out-Null
|
||||
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install cffi
|
||||
python -m pip install zstandard
|
||||
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
function Download-WithRetry {
|
||||
param(
|
||||
[string]$Url,
|
||||
[string]$OutFile,
|
||||
[int]$MaxAttempts = 5,
|
||||
[int]$InitialDelaySeconds = 2
|
||||
)
|
||||
for ($attempt = 1; $attempt -le $MaxAttempts; $attempt++) {
|
||||
try {
|
||||
Invoke-WebRequest -Uri $Url -OutFile $OutFile -ErrorAction Stop
|
||||
return
|
||||
}
|
||||
catch {
|
||||
if ($attempt -eq $MaxAttempts) {
|
||||
throw
|
||||
}
|
||||
Start-Sleep -Seconds ($InitialDelaySeconds * [math]::Pow(2, $attempt - 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# The existing solution includes installer.vcxproj, so Inno Setup is
|
||||
# still required to build the unmodified solution. This workflow does
|
||||
# not publish that EXE; it packages the same staged rel tree as MSI/MSIX.
|
||||
Download-WithRetry -Url https://github.com/jrsoftware/issrc/releases/download/is-6_7_1/innosetup-6.7.1.exe -OutFile deps\innosetup-unicode.exe
|
||||
& deps\innosetup-unicode.exe /VERYSILENT | Out-Null
|
||||
|
||||
# Use the same GTK runtime as the existing Windows EXE workflow.
|
||||
Download-WithRetry -Url https://github.com/ZoiteChat/gvsbuild/releases/download/zoitechat-2.20.0-alpha.1/GTK3_Gvsbuild_zoitechat-2.20.0-alpha.1_x64.zip -OutFile deps\gtk-${{ matrix.arch }}.zip
|
||||
Expand-Archive -LiteralPath deps\gtk-${{ matrix.arch }}.zip -DestinationPath C:\gtk-build\gtk\x64\release -Force
|
||||
|
||||
# The GTK3 bundle does not contain librsvg. Restore the renderer,
|
||||
# pixbuf loader, and license from the matching GTK4 bundle. Do not
|
||||
# replace the rest of the GTK3 runtime with GTK4 libraries.
|
||||
Download-WithRetry -Url https://github.com/ZoiteChat/gvsbuild/releases/download/zoitechat-2.20.0-alpha.1/GTK4_Gvsbuild_zoitechat-2.20.0-alpha.1_x64.zip -OutFile deps\svg-runtime.zip
|
||||
if ((Get-FileHash deps\svg-runtime.zip -Algorithm SHA256).Hash -ne 'fb657f9ebae3a6807defb15e66091d643a703ee66cb37488e3e11e5646f45adb') {
|
||||
throw 'Unexpected SVG runtime archive checksum'
|
||||
}
|
||||
$svgArchive = [System.IO.Compression.ZipFile]::OpenRead((Resolve-Path deps\svg-runtime.zip))
|
||||
try {
|
||||
foreach ($name in @('bin/rsvg-2-2.dll', 'lib/gdk-pixbuf-2.0/2.10.0/loaders/pixbufloader_svg.dll', 'share/doc/librsvg/COPYING.LIB')) {
|
||||
$entry = $svgArchive.GetEntry($name)
|
||||
if ($null -eq $entry) { throw "Missing SVG runtime file: $name" }
|
||||
$destination = Join-Path 'C:\gtk-build\gtk\x64\release' $name
|
||||
New-Item -ItemType Directory -Path (Split-Path $destination) -Force | Out-Null
|
||||
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $destination, $true)
|
||||
}
|
||||
}
|
||||
finally { $svgArchive.Dispose() }
|
||||
|
||||
# Ensure SVG remains loadable before compiling or packaging.
|
||||
& C:\gtk-build\gtk\x64\release\bin\gdk-pixbuf-query-loaders.exe --update-cache
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Could not generate the pixbuf loader cache' }
|
||||
if (-not (Select-String -LiteralPath 'C:\gtk-build\gtk\x64\release\lib\gdk-pixbuf-2.0\2.10.0\loaders.cache' -SimpleMatch '"svg"')) {
|
||||
throw 'SVG loader could not be loaded with the GTK3 runtime'
|
||||
}
|
||||
|
||||
Download-WithRetry -Url https://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-hicolor-icon-theme-0.18-1-any.pkg.tar.zst -OutFile deps\hicolor-icon-theme.pkg.tar.zst
|
||||
python -c "import tarfile,zstandard,pathlib;archive=pathlib.Path(r'deps\\hicolor-icon-theme.pkg.tar.zst');target=pathlib.Path(r'C:\\gtk-build\\gtk\\x64\\release');dctx=zstandard.ZstdDecompressor();f=archive.open('rb');reader=dctx.stream_reader(f);tf=tarfile.open(fileobj=reader,mode='r|');[tf.extract(m,path=target) for m in tf if m.name.startswith('mingw64/share/icons/hicolor/')];tf.close();reader.close();f.close()"
|
||||
|
||||
Download-WithRetry -Url https://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-libarchive-3.8.6-1-any.pkg.tar.zst -OutFile deps\libarchive.pkg.tar.zst
|
||||
python -c "import tarfile,zstandard,pathlib;archive=pathlib.Path(r'deps\\libarchive.pkg.tar.zst');target=pathlib.Path(r'C:\\gtk-build\\gtk\\x64\\release');dctx=zstandard.ZstdDecompressor();f=archive.open('rb');reader=dctx.stream_reader(f);tf=tarfile.open(fileobj=reader,mode='r|');[tf.extract(m,path=target) for m in tf if m.name.startswith(('mingw64/include/archive','mingw64/lib/libarchive','mingw64/bin/libarchive'))];tf.close();reader.close();f.close()"
|
||||
|
||||
if (Test-Path C:\gtk-build\gtk\x64\release\mingw64\share\icons\hicolor) {
|
||||
New-Item -Path C:\gtk-build\gtk\x64\release\share\icons -ItemType Directory -Force | Out-Null
|
||||
Copy-Item -Path C:\gtk-build\gtk\x64\release\mingw64\share\icons\hicolor -Destination C:\gtk-build\gtk\x64\release\share\icons\hicolor -Recurse -Force
|
||||
}
|
||||
if (Test-Path C:\gtk-build\gtk\x64\release\mingw64\include) {
|
||||
New-Item -Path C:\gtk-build\gtk\x64\release\include -ItemType Directory -Force | Out-Null
|
||||
Copy-Item -Path C:\gtk-build\gtk\x64\release\mingw64\include\archive* -Destination C:\gtk-build\gtk\x64\release\include -Recurse -Force
|
||||
}
|
||||
if (Test-Path C:\gtk-build\gtk\x64\release\mingw64\lib) {
|
||||
New-Item -Path C:\gtk-build\gtk\x64\release\lib -ItemType Directory -Force | Out-Null
|
||||
Copy-Item -Path C:\gtk-build\gtk\x64\release\mingw64\lib\libarchive* -Destination C:\gtk-build\gtk\x64\release\lib -Force
|
||||
}
|
||||
if (Test-Path C:\gtk-build\gtk\x64\release\mingw64\bin) {
|
||||
New-Item -Path C:\gtk-build\gtk\x64\release\bin -ItemType Directory -Force | Out-Null
|
||||
Copy-Item -Path C:\gtk-build\gtk\x64\release\mingw64\bin\libarchive*.dll -Destination C:\gtk-build\gtk\x64\release\bin -Force
|
||||
}
|
||||
if (Test-Path C:\gtk-build\gtk\x64\release\mingw64) {
|
||||
Remove-Item -Path C:\gtk-build\gtk\x64\release\mingw64 -Recurse -Force
|
||||
}
|
||||
|
||||
Download-WithRetry -Url https://github.com/ZoiteChat/gvsbuild/releases/download/zoitechat-2.18.0-pre4/gendef20260315.7z -OutFile deps\gendef.7z
|
||||
& 7z.exe x deps\gendef.7z -oC:\gtk-build
|
||||
|
||||
Download-WithRetry -Url https://github.com/ZoiteChat/gvsbuild/releases/download/zoitechat-2.18.0-pre4/WinSparkle-20260315.7z -OutFile deps\WinSparkle.7z
|
||||
& 7z.exe x deps\WinSparkle.7z -oC:\gtk-build\WinSparkle
|
||||
|
||||
Download-WithRetry -Url https://github.com/ZoiteChat/gvsbuild/releases/download/zoitechat-2.18.0-pre4/perl-5.42.0.1-${{ matrix.arch }}.7z -OutFile deps\perl-${{ matrix.arch }}.7z
|
||||
& 7z.exe x deps\perl-${{ matrix.arch }}.7z -oC:\gtk-build\perl-5.42.0.1\${{ matrix.platform }}
|
||||
|
||||
$pyRoot = $env:pythonLocation
|
||||
if (-not $pyRoot) { $pyRoot = & python -c "import sys; print(sys.prefix)" }
|
||||
|
||||
foreach ($pyDir in @("C:\gtk-build\python-3.14.3", "C:\gtk-build\python-3.14")) {
|
||||
New-Item -Path $pyDir -ItemType Directory -Force | Out-Null
|
||||
$target = Join-Path $pyDir "${{ matrix.platform }}"
|
||||
if (Test-Path $target) { Remove-Item $target -Recurse -Force }
|
||||
New-Item -Path $pyDir -Name "${{ matrix.platform }}" -ItemType Junction -Value $pyRoot | Out-Null
|
||||
}
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.8.10'
|
||||
architecture: ${{ matrix.arch }}
|
||||
|
||||
- name: Prepare Python 3.8
|
||||
shell: pwsh
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install cffi
|
||||
|
||||
$pyRoot = $env:pythonLocation
|
||||
if (-not $pyRoot) { $pyRoot = & python -c "import sys; print(sys.prefix)" }
|
||||
|
||||
foreach ($pyDir in @("C:\gtk-build\python-3.8.10", "C:\gtk-build\python-3.8")) {
|
||||
New-Item -Path $pyDir -ItemType Directory -Force | Out-Null
|
||||
$target = Join-Path $pyDir "${{ matrix.platform }}"
|
||||
if (Test-Path $target) { Remove-Item $target -Recurse -Force }
|
||||
New-Item -Path $pyDir -Name "${{ matrix.platform }}" -ItemType Junction -Value $pyRoot | Out-Null
|
||||
}
|
||||
|
||||
- name: Build
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat"
|
||||
|
||||
set "PYTHON_DIR=C:\gtk-build\python-3.14.3\${{ matrix.platform }}"
|
||||
if not exist "%PYTHON_DIR%\libs\python314.lib" (
|
||||
echo Missing %PYTHON_DIR%\libs\python314.lib
|
||||
dir "%PYTHON_DIR%\libs"
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
set "PYTHON38_DIR=C:\gtk-build\python-3.8\${{ matrix.platform }}"
|
||||
if not exist "%PYTHON38_DIR%\libs\python38.lib" (
|
||||
echo Missing %PYTHON38_DIR%\libs\python38.lib
|
||||
dir "%PYTHON38_DIR%\libs"
|
||||
exit /b 1
|
||||
)
|
||||
if not exist "%PYTHON38_DIR%\Lib\site-packages\_cffi_backend.cp38*.pyd" (
|
||||
echo Missing %PYTHON38_DIR%\Lib\site-packages\_cffi_backend.cp38*.pyd
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
set "LIB=%PYTHON_DIR%\libs;%LIB%"
|
||||
set "INCLUDE=%PYTHON_DIR%\include;%INCLUDE%"
|
||||
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -Command "$archiveLib='C:\gtk-build\gtk\x64\release\lib\libarchive.lib'; if (-not (Test-Path $archiveLib)) { $archiveDll = Get-ChildItem 'C:\gtk-build\gtk\x64\release\bin\libarchive*.dll' | Select-Object -First 1; if ($archiveDll) { Push-Location 'C:\gtk-build\gtk\x64\release\lib'; & 'C:\gtk-build\gendef\gendef.exe' $archiveDll.FullName | Out-Null; $archiveDef = Get-ChildItem 'libarchive*.def' | Select-Object -First 1; if ($archiveDef) { & lib /def:$archiveDef.Name /machine:${{ matrix.platform }} /out:libarchive.lib | Out-Null }; Pop-Location } }"
|
||||
|
||||
msbuild win32\zoitechat.sln /m /verbosity:minimal /p:Configuration=Release /p:Platform=${{ matrix.platform }}
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
if not exist "..\zoitechat-build\${{ matrix.platform }}\rel\plugins\hcpython38.dll" (
|
||||
echo hcpython38.dll was not built
|
||||
exit /b 1
|
||||
)
|
||||
if not exist "..\zoitechat-build\${{ matrix.platform }}\rel\_cffi_backend.cp38*.pyd" (
|
||||
echo _cffi_backend for Python 3.8 was not staged for the installer
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
# Master added native emoji font staging and tests after the release
|
||||
# branch split. Run these before packaging so MSI/MSIX use verified files.
|
||||
- name: Test emoji catalog, font coverage and picker
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat"
|
||||
set "PATH=%CD%\..\zoitechat-build\x64\rel;%PATH%"
|
||||
set "GDK_PIXBUF_MODULE_FILE=%CD%\..\zoitechat-build\x64\rel\lib\gdk-pixbuf-2.0\2.10.0\loaders.cache"
|
||||
set "ZOITECHAT_EMOJI_FONT=%CD%\..\zoitechat-build\x64\rel\share\fonts\zoitechat\NotoColorEmoji.ttf"
|
||||
fc /b data\fonts\NotoColorEmoji.ttf "%ZOITECHAT_EMOJI_FONT%"
|
||||
if errorlevel 1 exit /b 1
|
||||
for %%T in (data font ui) do (
|
||||
msbuild win32\emoji-tests.vcxproj /m /verbosity:minimal /p:Configuration=Release /p:Platform=x64 /p:EmojiTestKind=%%T
|
||||
if errorlevel 1 exit /b 1
|
||||
..\zoitechat-build\x64\tests\emoji-%%T-tests.exe --tap
|
||||
if errorlevel 1 exit /b 1
|
||||
)
|
||||
|
||||
- name: Install WiX Toolset
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
# Keep this pinned. WiX v6 introduced OSMF terms and WiX v7 adds
|
||||
# explicit EULA acceptance; do not silently accept new terms in CI.
|
||||
dotnet tool install --tool-path .wix wix --version 5.0.2
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Failed to install WiX Toolset 5.0.2' }
|
||||
& .\.wix\wix.exe --version
|
||||
if ($LASTEXITCODE -ne 0) { throw 'WiX Toolset could not be executed' }
|
||||
|
||||
- name: Build MSI
|
||||
id: build_msi
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$rawVersion = (Get-Content VERSION -Raw).Trim()
|
||||
if ($rawVersion -notmatch '^(\d+)\.(\d+)\.(\d+)') {
|
||||
throw "VERSION '$rawVersion' does not begin with an MSI-compatible major.minor.patch version"
|
||||
}
|
||||
|
||||
# Keep the full development version in the filename, while MSI's
|
||||
# ProductVersion is numeric (e.g. 2.20.0-dev -> 2.20.0).
|
||||
$msiVersion = "$($Matches[1]).$($Matches[2]).$($Matches[3])"
|
||||
$safeVersion = $rawVersion -replace '[^0-9A-Za-z._-]', '-'
|
||||
$sourceDir = (Resolve-Path "..\zoitechat-build\${{ matrix.platform }}\rel").Path
|
||||
$outputPath = Join-Path $PWD "ZoiteChat-${safeVersion}_${{ matrix.arch }}.msi"
|
||||
|
||||
if (-not (Test-Path (Join-Path $sourceDir 'zoitechat.exe'))) {
|
||||
throw "Staged ZoiteChat executable is missing from $sourceDir"
|
||||
}
|
||||
|
||||
& .\.wix\wix.exe build win32\wix\zoitechat.wxs `
|
||||
-arch x64 `
|
||||
-d "SourceDir=$sourceDir" `
|
||||
-d "ProductVersion=$msiVersion" `
|
||||
-out $outputPath
|
||||
if ($LASTEXITCODE -ne 0) { throw 'WiX MSI build failed' }
|
||||
|
||||
if (-not (Test-Path $outputPath)) {
|
||||
throw "Expected MSI was not created: $outputPath"
|
||||
}
|
||||
|
||||
"path=$outputPath" >> $env:GITHUB_OUTPUT
|
||||
"filename=$(Split-Path $outputPath -Leaf)" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Test MSI administrative extraction
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$msi = '${{ steps.build_msi.outputs.path }}'
|
||||
$adminImage = Join-Path $PWD 'msi-admin-image'
|
||||
New-Item -ItemType Directory -Path $adminImage -Force | Out-Null
|
||||
|
||||
$arguments = "/a `"$msi`" /qn TARGETDIR=`"$adminImage`""
|
||||
$process = Start-Process -FilePath msiexec.exe -ArgumentList $arguments -Wait -PassThru
|
||||
if ($process.ExitCode -ne 0) {
|
||||
throw "MSI administrative extraction failed with exit code $($process.ExitCode)"
|
||||
}
|
||||
|
||||
foreach ($requiredFile in @(
|
||||
'zoitechat.exe',
|
||||
'hclua.dll',
|
||||
'hcupd.dll',
|
||||
'WinSparkle.dll',
|
||||
'hcchecksum.dll',
|
||||
'hcexec.dll',
|
||||
'hcfishlim.dll',
|
||||
'hcsysinfo.dll',
|
||||
'system.png',
|
||||
'hcperl.dll',
|
||||
'hcpython3.dll',
|
||||
'hcpython38.dll',
|
||||
'NotoColorEmoji.ttf',
|
||||
'OFL.txt'
|
||||
)) {
|
||||
$match = Get-ChildItem -Path $adminImage -Filter $requiredFile -File -Recurse | Select-Object -First 1
|
||||
if ($null -eq $match) {
|
||||
throw "Administrative image does not contain required normal-install file: $requiredFile"
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($requiredPattern in @('_cffi_backend.cp314*.pyd', '_cffi_backend.cp38*.pyd')) {
|
||||
$match = Get-ChildItem -Path $adminImage -Filter $requiredPattern -File -Recurse | Select-Object -First 1
|
||||
if ($null -eq $match) {
|
||||
throw "Administrative image does not contain required Python support file: $requiredPattern"
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($requiredPythonFile in @('zoitechat.py', '_zoitechat.py', 'hexchat.py', 'xchat.py')) {
|
||||
$match = Get-ChildItem -Path $adminImage -Filter $requiredPythonFile -File -Recurse |
|
||||
Where-Object { $_.Directory.Name -eq 'python' } |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $match) {
|
||||
throw "Administrative image does not contain required Python bridge file: $requiredPythonFile"
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($excludedFile in @(
|
||||
'portable-mode',
|
||||
'zoitechat-text.exe'
|
||||
)) {
|
||||
$match = Get-ChildItem -Path $adminImage -Filter $excludedFile -File -Recurse | Select-Object -First 1
|
||||
if ($null -ne $match) {
|
||||
throw "MSI incorrectly contains Inno custom/portable-only file: $excludedFile"
|
||||
}
|
||||
}
|
||||
|
||||
- name: Build MSIX
|
||||
id: build_msix
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$rawVersion = (Get-Content VERSION -Raw).Trim()
|
||||
if ($rawVersion -notmatch '^(\d+)\.(\d+)\.(\d+)') {
|
||||
throw "VERSION '$rawVersion' does not begin with a Store-compatible major.minor.build version"
|
||||
}
|
||||
|
||||
$versionParts = @(
|
||||
[int]$Matches[1],
|
||||
[int]$Matches[2],
|
||||
[int]$Matches[3]
|
||||
)
|
||||
if ($versionParts[0] -eq 0) {
|
||||
throw 'The Microsoft Store requires a non-zero major package version.'
|
||||
}
|
||||
foreach ($part in $versionParts) {
|
||||
if ($part -lt 0 -or $part -gt 65535) {
|
||||
throw "MSIX version component '$part' is outside the Store-supported 0-65535 range."
|
||||
}
|
||||
}
|
||||
|
||||
# Partner Center reserves the fourth field as 0. A -dev version
|
||||
# is packaged as a valid 2.20.0.0, without changing VERSION.
|
||||
$packageVersion = "$($versionParts[0]).$($versionParts[1]).$($versionParts[2]).0"
|
||||
$safeVersion = $rawVersion -replace '[^0-9A-Za-z._-]', '-'
|
||||
$sourceDir = (Resolve-Path "..\zoitechat-build\${{ matrix.platform }}\rel").Path
|
||||
$outputPath = Join-Path $PWD "ZoiteChat-${safeVersion}_${{ matrix.arch }}.msix"
|
||||
|
||||
# Reserved ZoiteChat identity from the release branch. Adjust only
|
||||
# if the Product identity page in Partner Center changes.
|
||||
$identityName = 'deependtilde.ZoiteChat'
|
||||
$publisher = 'CN=6C7766BB-6AC6-488B-A09E-E3F8E68E63E4'
|
||||
$publisherDisplayName = 'deepend_tilde'
|
||||
$artifactName = 'MSIX Store Package ${{ matrix.arch }}'
|
||||
|
||||
Write-Host "Building Store MSIX for identity: $identityName"
|
||||
|
||||
& .\win32\msix\build-msix.ps1 `
|
||||
-SourceDir $sourceDir `
|
||||
-OutputPath $outputPath `
|
||||
-PackageVersion $packageVersion `
|
||||
-IdentityName $identityName `
|
||||
-Publisher $publisher `
|
||||
-PublisherDisplayName $publisherDisplayName `
|
||||
-SelfSign
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw 'MSIX build script failed'
|
||||
}
|
||||
|
||||
if (-not (Test-Path $outputPath)) {
|
||||
throw "Expected MSIX was not created: $outputPath"
|
||||
}
|
||||
|
||||
"path=$outputPath" >> $env:GITHUB_OUTPUT
|
||||
"filename=$(Split-Path $outputPath -Leaf)" >> $env:GITHUB_OUTPUT
|
||||
"package_version=$packageVersion" >> $env:GITHUB_OUTPUT
|
||||
"store_ready=true" >> $env:GITHUB_OUTPUT
|
||||
"artifact_name=$artifactName" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Upload MSI Installer
|
||||
id: upload_msi
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: MSI Installer ${{ matrix.arch }}
|
||||
path: ${{ steps.build_msi.outputs.path }}
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Attest MSI Installer (Artifact Attestation)
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
|
||||
uses: actions/attest-build-provenance@v3
|
||||
with:
|
||||
subject-name: MSI Installer ${{ matrix.arch }}
|
||||
subject-digest: sha256:${{ steps.upload_msi.outputs.artifact-digest }}
|
||||
|
||||
- name: Upload MSIX Package
|
||||
id: upload_msix
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ steps.build_msix.outputs.artifact_name }}
|
||||
path: ${{ steps.build_msix.outputs.path }}
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Attest MSIX Package (Artifact Attestation)
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
|
||||
uses: actions/attest-build-provenance@v3
|
||||
with:
|
||||
subject-name: ${{ steps.build_msix.outputs.artifact_name }}
|
||||
subject-digest: sha256:${{ steps.upload_msix.outputs.artifact-digest }}
|
||||
+2
-2
@@ -271,7 +271,7 @@ ignore_read_next_entry (char *my_cfg, struct ignore *ignore)
|
||||
}
|
||||
|
||||
void
|
||||
ignore_load ()
|
||||
ignore_load (void)
|
||||
{
|
||||
struct ignore *ignore;
|
||||
struct stat st;
|
||||
@@ -302,7 +302,7 @@ ignore_load ()
|
||||
}
|
||||
|
||||
void
|
||||
ignore_save ()
|
||||
ignore_save (void)
|
||||
{
|
||||
char buf[1024];
|
||||
int fh;
|
||||
|
||||
+1
-1
@@ -662,7 +662,7 @@ notify_isnotify (struct session *sess, char *name)
|
||||
}
|
||||
|
||||
void
|
||||
notify_cleanup ()
|
||||
notify_cleanup (void)
|
||||
{
|
||||
GSList *list = notify_list;
|
||||
GSList *nslist, *srvlist;
|
||||
|
||||
@@ -1288,6 +1288,11 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
|
||||
char *user = net && net->user ? net->user : prefs.hex_irc_user_name;
|
||||
|
||||
response = challengeauth_response (user, serv->password, word[5]);
|
||||
if (response == NULL)
|
||||
{
|
||||
PrintTextf (serv->server_session, _("Error building CHALLENGEAUTH for %s"), user);
|
||||
return;
|
||||
}
|
||||
|
||||
tcp_sendf (serv, "PRIVMSG %s :CHALLENGEAUTH %s %s %s\r\n",
|
||||
CHALLENGEAUTH_NICK,
|
||||
|
||||
+3
-3
@@ -1808,7 +1808,7 @@ pevent_check_all_loaded (void)
|
||||
}
|
||||
|
||||
void
|
||||
load_text_events ()
|
||||
load_text_events (void)
|
||||
{
|
||||
memset (&pntevts_text, 0, sizeof (char *) * (NUM_XP));
|
||||
memset (&pntevts, 0, sizeof (char *) * (NUM_XP));
|
||||
@@ -2488,7 +2488,7 @@ sound_load_event (char *evt, char *file)
|
||||
}
|
||||
|
||||
void
|
||||
sound_load ()
|
||||
sound_load (void)
|
||||
{
|
||||
int fd;
|
||||
char buf[512];
|
||||
@@ -2521,7 +2521,7 @@ sound_load ()
|
||||
}
|
||||
|
||||
void
|
||||
sound_save ()
|
||||
sound_save (void)
|
||||
{
|
||||
int fd, i;
|
||||
char buf[512];
|
||||
|
||||
+7
-2
@@ -1453,7 +1453,7 @@ challengeauth_response (const char *username, const char *password, const char *
|
||||
char *passhash;
|
||||
char *key;
|
||||
char *keyhash;
|
||||
unsigned char *digest;
|
||||
unsigned char digest[SHA256_DIGEST_LENGTH];
|
||||
GString *buf = g_string_new_len (NULL, SHA256_DIGEST_LENGTH * 2);
|
||||
|
||||
user = rfc_strlower (username); /* convert username to lowercase as per the RFC */
|
||||
@@ -1469,7 +1469,12 @@ challengeauth_response (const char *username, const char *password, const char *
|
||||
keyhash = str_sha256hash (key);
|
||||
g_free (key);
|
||||
|
||||
digest = HMAC (EVP_sha256 (), keyhash, strlen (keyhash), (unsigned char *) challenge, strlen (challenge), NULL, NULL);
|
||||
if (HMAC (EVP_sha256 (), keyhash, strlen (keyhash), (unsigned char *) challenge, strlen (challenge), digest, NULL) == NULL)
|
||||
{
|
||||
g_free (keyhash);
|
||||
g_string_free (buf, TRUE);
|
||||
return NULL;
|
||||
}
|
||||
g_free (keyhash);
|
||||
|
||||
for (i = 0; i < SHA256_DIGEST_LENGTH; i++)
|
||||
|
||||
+2
-2
@@ -426,7 +426,7 @@ key_binding_is_builtin (struct key_binding *kb)
|
||||
}
|
||||
|
||||
void
|
||||
key_init ()
|
||||
key_init (void)
|
||||
{
|
||||
if (key_load_kbs () == 1)
|
||||
{
|
||||
@@ -1219,7 +1219,7 @@ key_dialog_load (GtkListStore *store)
|
||||
}
|
||||
|
||||
void
|
||||
key_dialog_show ()
|
||||
key_dialog_show (void)
|
||||
{
|
||||
GtkWidget *vbox, *box, *pane, *scroll;
|
||||
GtkWidget *view, *xtext, *delete_button;
|
||||
|
||||
@@ -336,7 +336,7 @@ ignore_stats_entry (GtkWidget * box, char *label, int value)
|
||||
}
|
||||
|
||||
void
|
||||
ignore_gui_open ()
|
||||
ignore_gui_open (void)
|
||||
{
|
||||
GtkWidget *vbox, *box, *stat_box, *frame;
|
||||
GtkWidget *view;
|
||||
|
||||
@@ -297,40 +297,6 @@ mg_color_component_to_pango (double value)
|
||||
return (guint16)(value * 65535.0 + 0.5);
|
||||
}
|
||||
|
||||
static void
|
||||
mg_apply_font_css (GtkWidget *widget, const PangoFontDescription *desc,
|
||||
const char *class_name, const char *provider_key)
|
||||
{
|
||||
GtkStyleContext *context;
|
||||
GtkCssProvider *provider;
|
||||
GString *css;
|
||||
|
||||
if (!widget || !desc)
|
||||
return;
|
||||
|
||||
context = gtk_widget_get_style_context (widget);
|
||||
if (!context)
|
||||
return;
|
||||
|
||||
provider = g_object_get_data (G_OBJECT (widget), provider_key);
|
||||
if (!provider)
|
||||
{
|
||||
provider = gtk_css_provider_new ();
|
||||
g_object_set_data_full (G_OBJECT (widget), provider_key, provider, g_object_unref);
|
||||
}
|
||||
|
||||
css = g_string_new (".");
|
||||
g_string_append (css, class_name);
|
||||
g_string_append (css, " {");
|
||||
gtkutil_append_font_css (css, desc);
|
||||
g_string_append (css, " }");
|
||||
gtk_css_provider_load_from_data (provider, css->str, -1, NULL);
|
||||
g_string_free (css, TRUE);
|
||||
|
||||
gtk_style_context_add_class (context, class_name);
|
||||
theme_css_apply_widget_provider (widget, GTK_STYLE_PROVIDER (provider));
|
||||
}
|
||||
|
||||
static void
|
||||
mg_set_label_alignment_start (GtkWidget *widget)
|
||||
{
|
||||
|
||||
@@ -1962,7 +1962,10 @@ menu_about (GtkWidget *wid, gpointer sess)
|
||||
gtk_about_dialog_set_logo (dialog, pix_zoitechat);
|
||||
gtk_about_dialog_set_copyright (dialog, "\302\251 1998-2010 Peter \305\275elezn\303\275\n\302\251 2009-2014 Berke Viktor\n\302\251 2015-2025 Patrick Griffis\n\302\251 2026 deepend");
|
||||
gtk_about_dialog_set_comments (dialog, comment);
|
||||
/* Preserve the About dialog's existing action-area button layout. */
|
||||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||
actions = gtk_dialog_get_action_area (GTK_DIALOG (dialog));
|
||||
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||
children = gtk_container_get_children (GTK_CONTAINER (actions));
|
||||
for (child = children; child; child = child->next)
|
||||
gtk_widget_destroy (GTK_WIDGET (child->data));
|
||||
|
||||
@@ -486,7 +486,7 @@ pevent_hlist_treeview_new (GtkWidget *box)
|
||||
}
|
||||
|
||||
void
|
||||
pevent_dialog_show ()
|
||||
pevent_dialog_show (void)
|
||||
{
|
||||
GtkWidget *vbox, *hbox, *wid, *pane, *preview_pane;
|
||||
XTextColor xtext_palette[XTEXT_COLS];
|
||||
|
||||
@@ -187,7 +187,7 @@ populate_cb (char *urltext, gpointer userdata)
|
||||
}
|
||||
|
||||
void
|
||||
url_opengui ()
|
||||
url_opengui (void)
|
||||
{
|
||||
GtkWidget *vbox, *hbox, *view;
|
||||
char buf[128];
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
||||
xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap uap3 uap10 rescap">
|
||||
|
||||
<!--
|
||||
Identity values are filled by build-msix.ps1. For Microsoft Store
|
||||
submissions they must exactly match Product management > Product identity
|
||||
in Partner Center.
|
||||
-->
|
||||
<Identity
|
||||
Name="@IDENTITY_NAME@"
|
||||
Publisher="@PUBLISHER@"
|
||||
Version="@PACKAGE_VERSION@"
|
||||
ProcessorArchitecture="x64" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>ZoiteChat</DisplayName>
|
||||
<PublisherDisplayName>@PUBLISHER_DISPLAY_NAME@</PublisherDisplayName>
|
||||
<Description>ZoiteChat IRC client</Description>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="en-us" />
|
||||
</Resources>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily
|
||||
Name="Windows.Desktop"
|
||||
MinVersion="10.0.19041.0"
|
||||
MaxVersionTested="10.0.26100.0" />
|
||||
<PackageDependency
|
||||
Name="Microsoft.VCLibs.140.00.UWPDesktop"
|
||||
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
|
||||
MinVersion="14.0.30704.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
|
||||
<Applications>
|
||||
<Application
|
||||
Id="ZoiteChat"
|
||||
Executable="ZoiteChat\zoitechat.exe"
|
||||
uap10:RuntimeBehavior="packagedClassicApp"
|
||||
uap10:TrustLevel="mediumIL">
|
||||
|
||||
<uap:VisualElements
|
||||
DisplayName="ZoiteChat"
|
||||
Description="ZoiteChat IRC client"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png" />
|
||||
|
||||
<Extensions>
|
||||
<uap3:Extension Category="windows.protocol">
|
||||
<uap3:Protocol
|
||||
Name="irc"
|
||||
Parameters="--url="%1"" />
|
||||
</uap3:Extension>
|
||||
|
||||
<uap3:Extension Category="windows.fileTypeAssociation">
|
||||
<uap3:FileTypeAssociation
|
||||
Name="zoitechattheme"
|
||||
Parameters=""%1"">
|
||||
<uap:DisplayName>ZoiteChat Theme</uap:DisplayName>
|
||||
<uap:SupportedFileTypes>
|
||||
<uap:FileType>.zct</uap:FileType>
|
||||
<uap:FileType>.hct</uap:FileType>
|
||||
</uap:SupportedFileTypes>
|
||||
</uap3:FileTypeAssociation>
|
||||
</uap3:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
</Package>
|
||||
@@ -1,392 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $SourceDir,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $OutputPath,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidatePattern('^\d+\.\d+\.\d+\.0$')]
|
||||
[string] $PackageVersion,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $IdentityName,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $Publisher,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $PublisherDisplayName,
|
||||
|
||||
[switch] $SelfSign
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
function Find-WindowsSdkTool {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $ToolName
|
||||
)
|
||||
|
||||
$command = Get-Command $ToolName -ErrorAction SilentlyContinue
|
||||
if ($command) {
|
||||
return $command.Source
|
||||
}
|
||||
|
||||
$kitsRoot = Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\bin'
|
||||
if (-not (Test-Path $kitsRoot)) {
|
||||
throw "Windows SDK bin directory was not found: $kitsRoot"
|
||||
}
|
||||
|
||||
$tool = Get-ChildItem -Path $kitsRoot -Filter $ToolName -File -Recurse |
|
||||
Where-Object { $_.FullName -match '\\x64\\' } |
|
||||
Sort-Object {
|
||||
$versionDirectory = Split-Path (Split-Path $_.DirectoryName -Parent) -Leaf
|
||||
try {
|
||||
[version] $versionDirectory
|
||||
}
|
||||
catch {
|
||||
[version] '0.0'
|
||||
}
|
||||
} -Descending |
|
||||
Select-Object -First 1
|
||||
|
||||
if (-not $tool) {
|
||||
throw "$ToolName was not found in the installed Windows SDK."
|
||||
}
|
||||
|
||||
return $tool.FullName
|
||||
}
|
||||
|
||||
function Escape-Xml {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $Value
|
||||
)
|
||||
|
||||
return [System.Security.SecurityElement]::Escape($Value)
|
||||
}
|
||||
|
||||
function New-MsixAsset {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[System.Drawing.Image] $SourceImage,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[int] $Width,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[int] $Height,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $Path
|
||||
)
|
||||
|
||||
$bitmap = [System.Drawing.Bitmap]::new($Width, $Height)
|
||||
try {
|
||||
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
||||
try {
|
||||
$graphics.Clear([System.Drawing.Color]::Transparent)
|
||||
$graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
|
||||
$graphics.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
|
||||
$graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
|
||||
|
||||
$scale = [Math]::Min($Width / $SourceImage.Width, $Height / $SourceImage.Height)
|
||||
$drawWidth = [Math]::Max(1, [int][Math]::Round($SourceImage.Width * $scale))
|
||||
$drawHeight = [Math]::Max(1, [int][Math]::Round($SourceImage.Height * $scale))
|
||||
$x = [int](($Width - $drawWidth) / 2)
|
||||
$y = [int](($Height - $drawHeight) / 2)
|
||||
|
||||
$graphics.DrawImage($SourceImage, $x, $y, $drawWidth, $drawHeight)
|
||||
}
|
||||
finally {
|
||||
$graphics.Dispose()
|
||||
}
|
||||
|
||||
$bitmap.Save($Path, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
}
|
||||
finally {
|
||||
$bitmap.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
$source = (Resolve-Path $SourceDir).Path
|
||||
if (-not (Test-Path (Join-Path $source 'zoitechat.exe'))) {
|
||||
throw "Staged ZoiteChat executable is missing from $source"
|
||||
}
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
||||
$templatePath = Join-Path $PSScriptRoot 'AppxManifest.xml.in'
|
||||
$iconPath = Join-Path $repoRoot 'data\icons\zoitechat.png'
|
||||
|
||||
if (-not (Test-Path $templatePath)) {
|
||||
throw "MSIX manifest template is missing: $templatePath"
|
||||
}
|
||||
if (-not (Test-Path $iconPath)) {
|
||||
throw "ZoiteChat PNG icon is missing: $iconPath"
|
||||
}
|
||||
|
||||
if ([System.IO.Path]::IsPathRooted($OutputPath)) {
|
||||
$output = [System.IO.Path]::GetFullPath($OutputPath)
|
||||
}
|
||||
else {
|
||||
$output = [System.IO.Path]::GetFullPath((Join-Path $PWD $OutputPath))
|
||||
}
|
||||
$outputDirectory = Split-Path $output -Parent
|
||||
New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null
|
||||
|
||||
$stagingRoot = Join-Path $env:RUNNER_TEMP 'zoitechat-msix'
|
||||
$verifyRoot = Join-Path $env:RUNNER_TEMP 'zoitechat-msix-verify'
|
||||
$pfxPath = Join-Path $env:RUNNER_TEMP 'zoitechat-msix-dev.pfx'
|
||||
|
||||
foreach ($path in @($stagingRoot, $verifyRoot)) {
|
||||
if (Test-Path $path) {
|
||||
Remove-Item -Path $path -Recurse -Force
|
||||
}
|
||||
}
|
||||
if (Test-Path $pfxPath) {
|
||||
Remove-Item -Path $pfxPath -Force
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Path $stagingRoot -Force | Out-Null
|
||||
$appRoot = Join-Path $stagingRoot 'ZoiteChat'
|
||||
New-Item -ItemType Directory -Path $appRoot -Force | Out-Null
|
||||
Copy-Item -Path (Join-Path $source '*') -Destination $appRoot -Recurse -Force
|
||||
|
||||
# copy.vcxproj stages everything needed by every Inno component. The Store
|
||||
# package keeps the useful bundled plugins and scripting bridges while letting
|
||||
# the Microsoft Store own application updates. The WinSparkle updater is the
|
||||
# only bundled plugin deliberately removed from the Store package.
|
||||
$excludeFiles = @(
|
||||
'portable-mode',
|
||||
'zoitechat-text.exe',
|
||||
'plugins\hcupd.dll',
|
||||
'WinSparkle.dll',
|
||||
'share\download.png'
|
||||
)
|
||||
|
||||
foreach ($relativePath in $excludeFiles) {
|
||||
$path = Join-Path $appRoot $relativePath
|
||||
if (Test-Path $path) {
|
||||
Remove-Item -Path $path -Force
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($relativePath in @('share\doc\WinSparkle')) {
|
||||
$path = Join-Path $appRoot $relativePath
|
||||
if (Test-Path $path) {
|
||||
Remove-Item -Path $path -Recurse -Force
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($requiredPath in @(
|
||||
'zoitechat.exe',
|
||||
'plugins\hcnotifications-winrt.dll',
|
||||
'plugins\hcchecksum.dll',
|
||||
'plugins\hcexec.dll',
|
||||
'plugins\hcfishlim.dll',
|
||||
'plugins\hcsysinfo.dll',
|
||||
'share\system.png',
|
||||
'share\fonts\zoitechat\NotoColorEmoji.ttf',
|
||||
'share\doc\zoitechat\fonts\OFL.txt',
|
||||
'plugins\hclua.dll',
|
||||
'plugins\hcperl.dll',
|
||||
'plugins\hcpython3.dll',
|
||||
'plugins\hcpython38.dll'
|
||||
)) {
|
||||
if (-not (Test-Path (Join-Path $appRoot $requiredPath))) {
|
||||
throw "MSIX staging is missing required file: $requiredPath"
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($requiredPattern in @('_cffi_backend.cp314*.pyd', '_cffi_backend.cp38*.pyd')) {
|
||||
$match = Get-ChildItem -Path $appRoot -Filter $requiredPattern -File -ErrorAction SilentlyContinue |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $match) {
|
||||
throw "MSIX staging is missing required Python support file: $requiredPattern"
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($requiredPythonFile in @('zoitechat.py', '_zoitechat.py', 'hexchat.py', 'xchat.py')) {
|
||||
$path = Join-Path (Join-Path $appRoot 'python') $requiredPythonFile
|
||||
if (-not (Test-Path $path)) {
|
||||
throw "MSIX staging is missing required Python bridge file: $requiredPythonFile"
|
||||
}
|
||||
}
|
||||
|
||||
$assets = Join-Path $stagingRoot 'Assets'
|
||||
New-Item -ItemType Directory -Path $assets -Force | Out-Null
|
||||
|
||||
$image = [System.Drawing.Image]::FromFile($iconPath)
|
||||
try {
|
||||
New-MsixAsset -SourceImage $image -Width 50 -Height 50 `
|
||||
-Path (Join-Path $assets 'StoreLogo.png')
|
||||
New-MsixAsset -SourceImage $image -Width 44 -Height 44 `
|
||||
-Path (Join-Path $assets 'Square44x44Logo.png')
|
||||
New-MsixAsset -SourceImage $image -Width 150 -Height 150 `
|
||||
-Path (Join-Path $assets 'Square150x150Logo.png')
|
||||
}
|
||||
finally {
|
||||
$image.Dispose()
|
||||
}
|
||||
|
||||
$manifest = Get-Content $templatePath -Raw -Encoding UTF8
|
||||
$manifest = $manifest.Replace('@IDENTITY_NAME@', (Escape-Xml $IdentityName))
|
||||
$manifest = $manifest.Replace('@PUBLISHER@', (Escape-Xml $Publisher))
|
||||
$manifest = $manifest.Replace('@PUBLISHER_DISPLAY_NAME@', (Escape-Xml $PublisherDisplayName))
|
||||
$manifest = $manifest.Replace('@PACKAGE_VERSION@', (Escape-Xml $PackageVersion))
|
||||
|
||||
if ($manifest -match '@[A-Z0-9_]+@') {
|
||||
throw "Unexpanded MSIX manifest placeholder: $($Matches[0])"
|
||||
}
|
||||
|
||||
$manifestPath = Join-Path $stagingRoot 'AppxManifest.xml'
|
||||
[System.IO.File]::WriteAllText(
|
||||
$manifestPath,
|
||||
$manifest,
|
||||
[System.Text.UTF8Encoding]::new($false)
|
||||
)
|
||||
|
||||
# Catch malformed XML before MakeAppx so CI reports a useful error.
|
||||
[xml](Get-Content $manifestPath -Raw -Encoding UTF8) | Out-Null
|
||||
|
||||
$makeAppx = Find-WindowsSdkTool -ToolName 'MakeAppx.exe'
|
||||
$signtool = Find-WindowsSdkTool -ToolName 'SignTool.exe'
|
||||
|
||||
if (Test-Path $output) {
|
||||
Remove-Item -Path $output -Force
|
||||
}
|
||||
|
||||
& $makeAppx pack /d $stagingRoot /p $output /o
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "MakeAppx failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
$certificate = $null
|
||||
try {
|
||||
if ($SelfSign) {
|
||||
# Partner Center replaces this development signature with Microsoft's
|
||||
# trusted Store signature. The certificate subject must exactly match
|
||||
# the manifest Publisher for SignTool to accept the package.
|
||||
$certificate = New-SelfSignedCertificate `
|
||||
-Type Custom `
|
||||
-KeyUsage DigitalSignature `
|
||||
-Subject $Publisher `
|
||||
-CertStoreLocation 'Cert:\CurrentUser\My' `
|
||||
-HashAlgorithm SHA256 `
|
||||
-TextExtension @(
|
||||
'2.5.29.37={text}1.3.6.1.5.5.7.3.3',
|
||||
'2.5.29.19={text}'
|
||||
) `
|
||||
-FriendlyName 'ZoiteChat MSIX CI signing certificate'
|
||||
|
||||
if ($certificate.Subject -cne $Publisher) {
|
||||
throw "Generated certificate subject '$($certificate.Subject)' does not exactly match manifest Publisher '$Publisher'."
|
||||
}
|
||||
|
||||
$passwordText = [Guid]::NewGuid().ToString('N')
|
||||
$password = ConvertTo-SecureString -String $passwordText -AsPlainText -Force
|
||||
Export-PfxCertificate -Cert $certificate -FilePath $pfxPath -Password $password | Out-Null
|
||||
|
||||
& $signtool sign /fd SHA256 /f $pfxPath /p $passwordText $output
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "SignTool failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
$signature = Get-AuthenticodeSignature -FilePath $output
|
||||
if (-not $signature.SignerCertificate -or
|
||||
$signature.SignerCertificate.Subject -cne $Publisher) {
|
||||
throw 'MSIX signature does not match the manifest Publisher.'
|
||||
}
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Path $verifyRoot -Force | Out-Null
|
||||
& $makeAppx unpack /p $output /d $verifyRoot /o
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "MakeAppx validation unpack failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
[xml] $verifiedManifest = Get-Content (Join-Path $verifyRoot 'AppxManifest.xml') -Raw -Encoding UTF8
|
||||
$ns = New-Object System.Xml.XmlNamespaceManager($verifiedManifest.NameTable)
|
||||
$ns.AddNamespace('f', 'http://schemas.microsoft.com/appx/manifest/foundation/windows10')
|
||||
$identity = $verifiedManifest.SelectSingleNode('/f:Package/f:Identity', $ns)
|
||||
if (-not $identity) {
|
||||
throw 'Validated MSIX does not contain a package Identity.'
|
||||
}
|
||||
if ($identity.Name -cne $IdentityName -or
|
||||
$identity.Publisher -cne $Publisher -or
|
||||
$identity.Version -cne $PackageVersion) {
|
||||
throw 'Validated MSIX identity does not match the requested Store identity.'
|
||||
}
|
||||
|
||||
foreach ($requiredPath in @(
|
||||
'ZoiteChat\zoitechat.exe',
|
||||
'ZoiteChat\plugins\hcnotifications-winrt.dll',
|
||||
'ZoiteChat\plugins\hcchecksum.dll',
|
||||
'ZoiteChat\plugins\hcexec.dll',
|
||||
'ZoiteChat\plugins\hcfishlim.dll',
|
||||
'ZoiteChat\plugins\hcsysinfo.dll',
|
||||
'ZoiteChat\share\system.png',
|
||||
'ZoiteChat\share\fonts\zoitechat\NotoColorEmoji.ttf',
|
||||
'ZoiteChat\share\doc\zoitechat\fonts\OFL.txt',
|
||||
'ZoiteChat\plugins\hclua.dll',
|
||||
'ZoiteChat\plugins\hcperl.dll',
|
||||
'ZoiteChat\plugins\hcpython3.dll',
|
||||
'ZoiteChat\plugins\hcpython38.dll'
|
||||
)) {
|
||||
if (-not (Test-Path (Join-Path $verifyRoot $requiredPath))) {
|
||||
throw "Validated MSIX is missing required file: $requiredPath"
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($requiredPattern in @('_cffi_backend.cp314*.pyd', '_cffi_backend.cp38*.pyd')) {
|
||||
$match = Get-ChildItem -Path (Join-Path $verifyRoot 'ZoiteChat') -Filter $requiredPattern -File -ErrorAction SilentlyContinue |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $match) {
|
||||
throw "Validated MSIX is missing required Python support file: $requiredPattern"
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($requiredPythonFile in @('zoitechat.py', '_zoitechat.py', 'hexchat.py', 'xchat.py')) {
|
||||
$path = Join-Path (Join-Path $verifyRoot 'ZoiteChat\python') $requiredPythonFile
|
||||
if (-not (Test-Path $path)) {
|
||||
throw "Validated MSIX is missing required Python bridge file: $requiredPythonFile"
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($excludedPath in @(
|
||||
'ZoiteChat\portable-mode',
|
||||
'ZoiteChat\zoitechat-text.exe',
|
||||
'ZoiteChat\plugins\hcupd.dll',
|
||||
'ZoiteChat\WinSparkle.dll',
|
||||
'ZoiteChat\share\download.png'
|
||||
)) {
|
||||
if (Test-Path (Join-Path $verifyRoot $excludedPath)) {
|
||||
throw "MSIX incorrectly contains excluded file/directory: $excludedPath"
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if ($certificate) {
|
||||
Remove-Item -Path "Cert:\CurrentUser\My\$($certificate.Thumbprint)" -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
if (Test-Path $pfxPath) {
|
||||
Remove-Item -Path $pfxPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
foreach ($path in @($stagingRoot, $verifyRoot)) {
|
||||
if (Test-Path $path) {
|
||||
Remove-Item -Path $path -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path $output)) {
|
||||
throw "Expected MSIX was not created: $output"
|
||||
}
|
||||
|
||||
Write-Host "Created MSIX: $output"
|
||||
@@ -1,162 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<!--
|
||||
This MSI intentionally mirrors the staged portion of the normal Inno Setup
|
||||
payload. Optional/custom-only files are excluded below. In particular,
|
||||
portable-mode must never be harvested because copy.vcxproj always stages
|
||||
that marker. Inno-only download-at-install components remain outside the MSI.
|
||||
-->
|
||||
<Package
|
||||
Name="ZoiteChat"
|
||||
Manufacturer="ZoiteChat"
|
||||
Version="$(ProductVersion)"
|
||||
UpgradeCode="{E9F09DF3-12FA-4BDF-A8AA-A6D62A327ED5}"
|
||||
Scope="perMachine"
|
||||
InstallerVersion="500"
|
||||
Compressed="yes">
|
||||
|
||||
<MajorUpgrade
|
||||
AllowSameVersionUpgrades="yes"
|
||||
DowngradeErrorMessage="A newer version of ZoiteChat is already installed." />
|
||||
|
||||
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
|
||||
|
||||
<Icon Id="ZoiteChat.ico" SourceFile="$(sys.SOURCEFILEDIR)..\..\data\icons\zoitechat.ico" />
|
||||
<Property Id="ARPPRODUCTICON" Value="ZoiteChat.ico" />
|
||||
<Property Id="ARPHELPLINK" Value="https://github.com/ZoiteChat/zoitechat/issues" />
|
||||
<Property Id="ARPURLINFOABOUT" Value="https://zoitechat.org/" />
|
||||
<Property Id="ARPURLUPDATEINFO" Value="https://zoitechat.org/downloads.html" />
|
||||
<Property Id="ARPNOMODIFY" Value="1" />
|
||||
|
||||
<!--
|
||||
The Inno installer can bootstrap the VC++ runtime. A plain MSI should not
|
||||
run an EXE prerequisite as an MSI custom action, so require the runtime.
|
||||
Administrative extraction is allowed regardless of prerequisite state.
|
||||
-->
|
||||
<Property Id="VCREDIST_X64_INSTALLED">
|
||||
<RegistrySearch
|
||||
Id="FindVCRedistX64"
|
||||
Root="HKLM"
|
||||
Key="SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64"
|
||||
Name="Installed"
|
||||
Type="raw"
|
||||
Bitness="always64" />
|
||||
</Property>
|
||||
<Launch
|
||||
Condition="Installed OR ACTION = "ADMIN" OR VCREDIST_X64_INSTALLED = "#1""
|
||||
Message="Microsoft Visual C++ 2015-2022 x64 Redistributable is required before installing ZoiteChat." />
|
||||
<Launch
|
||||
Condition="Installed OR ACTION = "ADMIN" OR VersionNT64 > 601 OR (VersionNT64 = 601 AND ServicePackLevel >= 1)"
|
||||
Message="ZoiteChat requires 64-bit Windows 7 SP1 or later." />
|
||||
|
||||
<StandardDirectory Id="ProgramFiles6432Folder">
|
||||
<Directory Id="INSTALLFOLDER" Name="ZoiteChat" />
|
||||
</StandardDirectory>
|
||||
|
||||
<StandardDirectory Id="ProgramMenuFolder">
|
||||
<Directory Id="ApplicationProgramsFolder" Name="ZoiteChat" />
|
||||
</StandardDirectory>
|
||||
|
||||
<Feature Id="MainFeature" Title="ZoiteChat" Level="1">
|
||||
<ComponentGroupRef Id="ApplicationFiles" />
|
||||
<ComponentRef Id="StartMenuShortcuts" />
|
||||
<ComponentRef Id="ProtocolAndThemeAssociations" />
|
||||
</Feature>
|
||||
</Package>
|
||||
|
||||
<Fragment>
|
||||
<ComponentGroup Id="ApplicationFiles">
|
||||
<Files Directory="INSTALLFOLDER" Include="$(SourceDir)\**">
|
||||
<!-- Created by copy.vcxproj for Inno's optional portable task only. -->
|
||||
<Exclude Files="$(SourceDir)\portable-mode" />
|
||||
<!-- Inno custom-only application component. -->
|
||||
<Exclude Files="$(SourceDir)\zoitechat-text.exe" />
|
||||
<!--
|
||||
Keep the bundled utility and scripting plugins, including Python
|
||||
3.14 and 3.8 support, available to non-Inno installations.
|
||||
-->
|
||||
</Files>
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
|
||||
<Fragment>
|
||||
<Component Id="StartMenuShortcuts" Directory="ApplicationProgramsFolder" Guid="*">
|
||||
<Shortcut
|
||||
Id="ZoiteChatShortcut"
|
||||
Name="ZoiteChat"
|
||||
Description="ZoiteChat IRC client"
|
||||
Target="[INSTALLFOLDER]zoitechat.exe"
|
||||
WorkingDirectory="INSTALLFOLDER"
|
||||
Icon="ZoiteChat.ico">
|
||||
<ShortcutProperty Key="System.AppUserModel.ID" Value="ZoiteChat.Desktop.Notify" />
|
||||
</Shortcut>
|
||||
|
||||
<Shortcut
|
||||
Id="ZoiteChatSafeModeShortcut"
|
||||
Name="ZoiteChat Safe Mode"
|
||||
Description="Start ZoiteChat without auto-connect or plugins"
|
||||
Target="[INSTALLFOLDER]zoitechat.exe"
|
||||
Arguments="--no-auto --no-plugins"
|
||||
WorkingDirectory="INSTALLFOLDER"
|
||||
Icon="ZoiteChat.ico" />
|
||||
|
||||
<Shortcut
|
||||
Id="ZoiteChatChangelogShortcut"
|
||||
Name="ZoiteChat ChangeLog"
|
||||
Target="[INSTALLFOLDER]changelog.url" />
|
||||
|
||||
<Shortcut
|
||||
Id="ZoiteChatReadmeShortcut"
|
||||
Name="ZoiteChat ReadMe"
|
||||
Target="[INSTALLFOLDER]readme.url" />
|
||||
|
||||
<Shortcut
|
||||
Id="ZoiteChatUninstallShortcut"
|
||||
Name="Uninstall ZoiteChat"
|
||||
Target="[System64Folder]msiexec.exe"
|
||||
Arguments="/x [ProductCode]" />
|
||||
|
||||
<RemoveFolder Id="RemoveApplicationProgramsFolder" On="uninstall" />
|
||||
<RegistryValue
|
||||
Root="HKLM"
|
||||
Key="Software\ZoiteChat\Installer"
|
||||
Name="StartMenuShortcuts"
|
||||
Type="integer"
|
||||
Value="1"
|
||||
KeyPath="yes" />
|
||||
</Component>
|
||||
</Fragment>
|
||||
|
||||
<Fragment>
|
||||
<Component Id="ProtocolAndThemeAssociations" Directory="INSTALLFOLDER" Guid="*">
|
||||
<RegistryValue
|
||||
Root="HKCR"
|
||||
Key="irc"
|
||||
Type="string"
|
||||
Value="URL:IRC Protocol"
|
||||
KeyPath="yes" />
|
||||
<RegistryValue Root="HKCR" Key="irc" Name="URL Protocol" Type="string" Value="" />
|
||||
<RegistryValue Root="HKCR" Key="irc\DefaultIcon" Type="string" Value="[INSTALLFOLDER]zoitechat.exe,0" />
|
||||
<RegistryValue Root="HKCR" Key="irc\shell" Type="string" Value="open" />
|
||||
<RegistryValue
|
||||
Root="HKCR"
|
||||
Key="irc\shell\open\command"
|
||||
Type="string"
|
||||
Value=""[INSTALLFOLDER]zoitechat.exe" --url="%1"" />
|
||||
|
||||
<RegistryValue Root="HKCR" Key=".zct" Type="string" Value="ZoiteChat.Theme" />
|
||||
<RegistryValue Root="HKCR" Key=".hct" Type="string" Value="ZoiteChat.Theme" />
|
||||
<RegistryValue Root="HKCR" Key="ZoiteChat.Theme" Type="string" Value="ZoiteChat Theme" />
|
||||
<RegistryValue
|
||||
Root="HKCR"
|
||||
Key="ZoiteChat.Theme\DefaultIcon"
|
||||
Type="string"
|
||||
Value="[INSTALLFOLDER]zoitechat.exe,0" />
|
||||
<RegistryValue
|
||||
Root="HKCR"
|
||||
Key="ZoiteChat.Theme\shell\open\command"
|
||||
Type="string"
|
||||
Value=""[INSTALLFOLDER]zoitechat.exe" "%1"" />
|
||||
</Component>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
Reference in New Issue
Block a user