diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c20f8c7a..7434884e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -134,6 +134,9 @@ jobs:
lipo -create -output macos-universal/UpdateMac macos-x64/UpdateMac macos-arm64/UpdateMac
file macos-universal/UpdateMac
lipo -archs macos-universal/UpdateMac
+ - uses: geekyeggo/delete-artifact@v5
+ with:
+ name: rust-macos-*
- name: Upload Universal Binary
uses: actions/upload-artifact@v4
with:
@@ -172,52 +175,75 @@ jobs:
sudo add-apt-repository universe
sudo apt install libfuse2
if: ${{ matrix.os == 'ubuntu-latest' }}
+ - name: Install squashfs-tools
+ run: brew install squashfs
+ if: ${{ matrix.os == 'macos-latest' }}
- name: Install dotnet-coverage
run: dotnet tool install -g dotnet-coverage
- name: Build .NET
run: dotnet build -c Release
- - name: Wait for artifacts
- shell: pwsh
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # for gh cli
- run: |
- # Wait 15 minutes for the artifact to become available
- $artifactName = "rust-${{ matrix.os }}"
- $maxAttempts = 90
- $sleepSeconds = 10
- $attempt = 0
- $workflowRunId = $env:GITHUB_RUN_ID
-
- Write-Host "Waiting for artifact '$artifactName' to become available in workflow run $workflowRunId..."
- while ($attempt -lt $maxAttempts) {
- $artifactsJson = gh api repos/$env:GITHUB_REPOSITORY/actions/runs/$workflowRunId/artifacts
- $artifacts = $artifactsJson | ConvertFrom-Json
- if ($artifacts.artifacts | Where-Object { $_.name -eq $artifactName }) {
- Write-Host "Artifact '$artifactName' is available, continuing..."
- break
- }
- Write-Host "Artifact not available yet. Attempt $($attempt + 1)/$maxAttempts"
- Start-Sleep -Seconds $sleepSeconds
- $attempt++
- }
-
- if ($attempt -ge $maxAttempts) {
- Write-Host "Error: Artifact '$artifactName' did not become available in time."
- exit 1
- }
+ - uses: caesay/wait-artifact-action@494939e840383463b1686ce3624a8aab059c2c8b
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ max_wait_seconds: 900
+ artifacts: rust-macos-latest,rust-windows-latest,rust-ubuntu-latest
+ verbose: true
- name: Download Rust Artifacts
uses: actions/download-artifact@v4
with:
- name: rust-${{ matrix.os }}
path: src/Rust/target/release
+ pattern: rust-*
+ merge-multiple: true
- name: Test .NET
run: dotnet test --no-build -c Release -l "console;verbosity=detailed;consoleLoggerParameters=ErrorsOnly" -l GithubActions -- RunConfiguration.CollectSourceInformation=true
+ - name: Upload Cross-Compile Artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: cross-${{ matrix.os }}
+ path: test/artifacts/*
- name: Upload Coverage Artifacts
uses: actions/upload-artifact@v4
with:
name: coverage-dotnet-${{ matrix.os }}
path: ./test/*.xml
+ test-cross:
+ strategy:
+ matrix:
+ os: [windows-latest, ubuntu-latest]
+ needs: [test-dotnet]
+ runs-on: ${{ matrix.os }}
+ env:
+ NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - uses: actions/cache@v4
+ with:
+ path: ${{ github.workspace }}/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-nuget-
+ - name: Install FUSE
+ run: |
+ sudo add-apt-repository universe
+ sudo apt install libfuse2
+ if: ${{ matrix.os == 'ubuntu-latest' }}
+ - name: Download Cross Artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: test/artifacts
+ pattern: cross-*
+ merge-multiple: true
+ - name: Test Cross-Compiled Apps
+ env:
+ VELOPACK_CROSS_ARTIFACTS: true
+ run: dotnet test -c Release test/Velopack.Packaging.Tests/Velopack.Packaging.Tests.csproj --filter "FullyQualifiedName~RunCrossApp" -l "console;verbosity=detailed;consoleLoggerParameters=ErrorsOnly" -l GithubActions -- RunConfiguration.CollectSourceInformation=true
+ - uses: geekyeggo/delete-artifact@v5
+ with:
+ name: cross-*
+
package:
runs-on: ubuntu-latest
needs: [build-rust-windows, build-rust-linux, build-mac-universal]
@@ -227,21 +253,12 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- - name: Download Rust OSX
+ - name: Download Rust Artifacts
uses: actions/download-artifact@v4
with:
- name: rust-macos-latest
- path: src/Rust/target/release
- - name: Download Rust Windows
- uses: actions/download-artifact@v4
- with:
- name: rust-windows-latest
- path: src/Rust/target/release
- - name: Download Rust Linux
- uses: actions/download-artifact@v4
- with:
- name: rust-ubuntu-latest
path: src/Rust/target/release
+ pattern: rust-*
+ merge-multiple: true
- name: Build .NET
run: dotnet build -c Release /p:PackRustAssets=true /p:ContinuousIntegrationBuild=true
- name: Upload Package Artifacts
@@ -267,6 +284,9 @@ jobs:
with:
directory: ./test
fail_ci_if_error: true
+ - uses: geekyeggo/delete-artifact@v5
+ with:
+ name: coverage-*
# - name: Publish to GitHub Packages
diff --git a/Velopack.sln b/Velopack.sln
index f1558f2c..1a2b468a 100644
--- a/Velopack.sln
+++ b/Velopack.sln
@@ -54,10 +54,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VeloWpfSample", "samples\Ve
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Divergic.Logging.Xunit", "test\Divergic.Logging.Xunit\Divergic.Logging.Xunit.csproj", "{5ED2E9AF-101D-4D2D-B0B5-90A920EF692D}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Velopack.Packaging.HostModel", "src\Velopack.Packaging.HostModel\Velopack.Packaging.HostModel.csproj", "{E9A2620C-C638-446C-BA30-F62C05709365}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Velopack.Build", "src\Velopack.Build\Velopack.Build.csproj", "{97C9B2CF-877F-4C98-A513-058784A23697}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Velopack.IcoLib", "src\Velopack.IcoLib\Velopack.IcoLib.csproj", "{8A0A980A-D51C-458E-8942-00BC900FD2D0}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -120,14 +120,14 @@ Global
{5ED2E9AF-101D-4D2D-B0B5-90A920EF692D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5ED2E9AF-101D-4D2D-B0B5-90A920EF692D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5ED2E9AF-101D-4D2D-B0B5-90A920EF692D}.Release|Any CPU.Build.0 = Release|Any CPU
- {E9A2620C-C638-446C-BA30-F62C05709365}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {E9A2620C-C638-446C-BA30-F62C05709365}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {E9A2620C-C638-446C-BA30-F62C05709365}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {E9A2620C-C638-446C-BA30-F62C05709365}.Release|Any CPU.Build.0 = Release|Any CPU
{97C9B2CF-877F-4C98-A513-058784A23697}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{97C9B2CF-877F-4C98-A513-058784A23697}.Debug|Any CPU.Build.0 = Debug|Any CPU
{97C9B2CF-877F-4C98-A513-058784A23697}.Release|Any CPU.ActiveCfg = Release|Any CPU
{97C9B2CF-877F-4C98-A513-058784A23697}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8A0A980A-D51C-458E-8942-00BC900FD2D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8A0A980A-D51C-458E-8942-00BC900FD2D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8A0A980A-D51C-458E-8942-00BC900FD2D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8A0A980A-D51C-458E-8942-00BC900FD2D0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index ab86c120..b251166e 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -10,7 +10,7 @@
true
false
$(MSBuildThisFileDirectory)SelfContained.targets
- $(NoWarn);NETSDK1188
+ $(NoWarn);NETSDK1188;NU5100
en
$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)'))=./
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
index 57f2441c..832cb3cf 100644
--- a/src/Directory.Build.targets
+++ b/src/Directory.Build.targets
@@ -35,10 +35,7 @@
-
-
-
-
+
diff --git a/src/Velopack.Build/PublishTask.cs b/src/Velopack.Build/PublishTask.cs
index a4bf0dc3..0055b939 100644
--- a/src/Velopack.Build/PublishTask.cs
+++ b/src/Velopack.Build/PublishTask.cs
@@ -39,7 +39,10 @@ public class PublishTask : MSBuildAsyncTask
return true;
}
- await client.UploadLatestReleaseAssetsAsync(Channel, ReleaseDirectory, ServiceUrl, cancellationToken)
+ // todo: currently it's not possible to cross-compile for different OSes using Velopack.Build
+ var targetOs = VelopackRuntimeInfo.SystemOs;
+
+ await client.UploadLatestReleaseAssetsAsync(Channel, ReleaseDirectory, ServiceUrl, targetOs, cancellationToken)
.ConfigureAwait(false);
return true;
diff --git a/src/Velopack.Build/Velopack.Build.csproj b/src/Velopack.Build/Velopack.Build.csproj
index c8ce0537..a48e5b33 100644
--- a/src/Velopack.Build/Velopack.Build.csproj
+++ b/src/Velopack.Build/Velopack.Build.csproj
@@ -21,7 +21,7 @@
-
+
diff --git a/src/Velopack.Build/packages.lock.json b/src/Velopack.Build/packages.lock.json
index 2888339f..a7949b56 100644
--- a/src/Velopack.Build/packages.lock.json
+++ b/src/Velopack.Build/packages.lock.json
@@ -4,13 +4,13 @@
".NETFramework,Version=v4.7.2": {
"Microsoft.Build.Utilities.Core": {
"type": "Direct",
- "requested": "[17.9.5, )",
- "resolved": "17.9.5",
- "contentHash": "H2hpVdm7cX/uGJD1HOfab3RKgD5tlnvzQkFqvsrAqGHRi0sqb2w1+hRwERFm23witCjmERnqNgiQjYks6/ds8A==",
+ "requested": "[17.10.4, )",
+ "resolved": "17.10.4",
+ "contentHash": "eEB/tcXkSV+nQgvoa/l53UPtn+KVtKZ8zBceDZsXVTrfE4fA+4+/olrx9W8n2tq4XiESsL9UuGJgCKzqBwQCoQ==",
"dependencies": {
- "Microsoft.Build.Framework": "17.9.5",
+ "Microsoft.Build.Framework": "17.10.4",
"Microsoft.IO.Redist": "6.0.0",
- "Microsoft.NET.StringTools": "17.9.5",
+ "Microsoft.NET.StringTools": "17.10.4",
"System.Collections.Immutable": "8.0.0",
"System.Configuration.ConfigurationManager": "8.0.0",
"System.Memory": "4.5.5",
@@ -30,8 +30,8 @@
"Nerdbank.GitVersioning": {
"type": "Direct",
"requested": "[3.6.*, )",
- "resolved": "3.6.133",
- "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw=="
+ "resolved": "3.6.139",
+ "contentHash": "rq0Ub/Jik7PtMtZtLn0tHuJ01Yt36RQ+eeBe+S7qnJ/EFOX6D4T9zuYD3vQPYKGI6Ro4t2iWgFm3fGDgjBrMfg=="
},
"Riok.Mapperly": {
"type": "Direct",
@@ -101,8 +101,8 @@
},
"Microsoft.Build.Framework": {
"type": "Transitive",
- "resolved": "17.9.5",
- "contentHash": "CjRmqu9Wv2fyC1d7NKOuBDXcNMI8+GiXGM6izygB+skGGu4Vf0cBcoPq7AFqZCcMpn5DtZ+y7RpaLpB2qrzanQ==",
+ "resolved": "17.10.4",
+ "contentHash": "4qXCwNOXBR1dyCzuks9SwTwFJQO/xmf2wcMislotDWJu7MN/r3xDNoU8Ae5QmKIHPaLG1xmfDkYS7qBVzxmeKw==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
@@ -119,8 +119,8 @@
},
"Microsoft.Identity.Client": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "3TDPEie+9t/NgBhoNifLFwM6nkypYUa8GUHfMnkYtovDDTQM8bsS2KEIP5tAh28BgfJZgof/KlCEKvGxz0H3Eg==",
+ "resolved": "4.61.2",
+ "contentHash": "zXPDXjTju1yOZQzi5PeNmQfBzlxv+VlCfgfZ70VqyFuhaPtFXwvbLSlwCOMoVALR/MzOR6xkiadoyVDmWDmqKw==",
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "6.35.0",
"System.Diagnostics.DiagnosticSource": "6.0.1"
@@ -128,27 +128,27 @@
},
"Microsoft.Identity.Client.Broker": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "D5M7plyrXrQwhj/PILHLS7G3TqDg8rUO9lR+5awUSXueGxsdA2qHpeKiSR0sVfX6wxziWszOLDUR2vjFFKHPSg==",
+ "resolved": "4.61.2",
+ "contentHash": "+13pImNYfPGl04qZh3Rtq8wPfo55FScAUEio4ymPzeWJ/PE04jCsKH6ZIlrYbPCqe6t9u0lru1eZ+ho9Dsnvpw==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
- "Microsoft.Identity.Client.NativeInterop": "0.16.0"
+ "Microsoft.Identity.Client": "4.61.2",
+ "Microsoft.Identity.Client.NativeInterop": "0.16.1"
}
},
"Microsoft.Identity.Client.Extensions.Msal": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "tCX3c1bSkwH89AKjtB/oAOLlv661tOsEA8ULOCavOc6te/CKlqmMmFs8zMI58Qlv3XkbLOrswYr50IhDntiiPA==",
+ "resolved": "4.61.2",
+ "contentHash": "GCSBhzkr7/7DZDk2kYS9dfihB5ieM2CqwGAhtjuE6T1GtAqzuh62UJhMgO0dSx/PLgIYMzNHnh+pfHK8aWJQJQ==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
+ "Microsoft.Identity.Client": "4.61.2",
"System.IO.FileSystem.AccessControl": "5.0.0",
"System.Security.Cryptography.ProtectedData": "4.5.0"
}
},
"Microsoft.Identity.Client.NativeInterop": {
"type": "Transitive",
- "resolved": "0.16.0",
- "contentHash": "riaIH9tT4kRG4xKFxx+wpuo+VAZVOr1ElbNubcvpFXb82gjEmZtJRzpdiS4tdRYTVh6CUKNz9AINAptActTtBQ=="
+ "resolved": "0.16.1",
+ "contentHash": "OvY/+/AHESi24f5zOCf9kL4HXwPxXVQ3A+tMQsJvFk2DmP+sc88FYWL49zlku5q0bvx5yFvBLNpHQeRT9a6A5g=="
},
"Microsoft.IdentityModel.Abstractions": {
"type": "Transitive",
@@ -166,8 +166,8 @@
},
"Microsoft.NET.StringTools": {
"type": "Transitive",
- "resolved": "17.9.5",
- "contentHash": "C/oPRnjcIZBRzcpl1V06R1eEMCxOGt6mIm+8ioyblELgJEXLM8XjUPuCwljMO52VetsHw54xMcYwU8UEeHEIEg==",
+ "resolved": "17.10.4",
+ "contentHash": "wyABaqY+IHCMMSTQmcc3Ca6vbmg5BaEPgicnEgpll+4xyWZWlkQqUwafweUd9VAhBb4jqplMl6voUHQ6yfdUcg==",
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
@@ -185,8 +185,29 @@
},
"NuGet.Versioning": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "ypnSvEtpNGo48bAWn95J1oHChycCXcevFSbn53fqzLxlXFSZP7dawu8p/7mHAfGufZQSV2sBpW80XQGIfXO8kQ=="
+ "resolved": "6.10.0",
+ "contentHash": "ytTA08tgZWo/Pbk333hnzQfiMyyynkkwMt4GyC5T0bzExYYSRNrkwv4jT8jmWr5LUOYsumEZvXdoven2SA7YZw=="
+ },
+ "SharpZipLib": {
+ "type": "Transitive",
+ "resolved": "1.4.2",
+ "contentHash": "yjj+3zgz8zgXpiiC3ZdF/iyTBbz2fFvMxZFEBPUcwZjIvXOf37Ylm+K58hqMfIBt5JgU/Z2uoUS67JmTLe973A==",
+ "dependencies": {
+ "System.Memory": "4.5.4",
+ "System.Threading.Tasks.Extensions": "4.5.2"
+ }
+ },
+ "SixLabors.ImageSharp": {
+ "type": "Transitive",
+ "resolved": "2.1.8",
+ "contentHash": "ML1++vactR5xMW36wHA2nAX3vc6VpR62qYnpopQdLNGbP8BAJ/ckv1IrGIIFRUEQ0JS9EgWr1y1gEa/81f+HaA==",
+ "dependencies": {
+ "System.Buffers": "4.5.1",
+ "System.Memory": "4.5.4",
+ "System.Numerics.Vectors": "4.5.0",
+ "System.Runtime.CompilerServices.Unsafe": "5.0.0",
+ "System.Text.Encoding.CodePages": "5.0.0"
+ }
},
"System.Buffers": {
"type": "Transitive",
@@ -261,15 +282,6 @@
"resolved": "4.5.0",
"contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ=="
},
- "System.Reflection.Metadata": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==",
- "dependencies": {
- "System.Collections.Immutable": "8.0.0",
- "System.Memory": "4.5.5"
- }
- },
"System.Runtime": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -328,6 +340,14 @@
"resolved": "5.0.0",
"contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA=="
},
+ "System.Text.Encoding.CodePages": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==",
+ "dependencies": {
+ "System.Runtime.CompilerServices.Unsafe": "5.0.0"
+ }
+ },
"System.Text.Encodings.Web": {
"type": "Transitive",
"resolved": "6.0.0",
@@ -371,31 +391,32 @@
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Newtonsoft.Json": "[13.0.1, )",
- "NuGet.Versioning": "[6.9.1, )"
+ "NuGet.Versioning": "[6.10.0, )"
+ }
+ },
+ "velopack.icolib": {
+ "type": "Project",
+ "dependencies": {
+ "SixLabors.ImageSharp": "[2.1.8, )"
}
},
"velopack.packaging": {
"type": "Project",
"dependencies": {
"Markdig": "[0.37.0, )",
- "Microsoft.Identity.Client": "[4.61.0, )",
- "Microsoft.Identity.Client.Broker": "[4.61.0, )",
- "Microsoft.Identity.Client.Extensions.Msal": "[4.61.0, )",
+ "Microsoft.Identity.Client": "[4.61.2, )",
+ "Microsoft.Identity.Client.Broker": "[4.61.2, )",
+ "Microsoft.Identity.Client.Extensions.Msal": "[4.61.2, )",
"System.Linq.Async": "[6.0.1, )",
"System.Net.Http": "[4.3.4, )",
"Velopack": "[1.0.0, )"
}
},
- "velopack.packaging.hostmodel": {
- "type": "Project",
- "dependencies": {
- "System.Reflection.Metadata": "[8.0.0, )"
- }
- },
"velopack.packaging.unix": {
"type": "Project",
"dependencies": {
"ELFSharp": "[2.17.3, )",
+ "SharpZipLib": "[1.4.2, )",
"Velopack.Packaging": "[1.0.0, )"
}
},
@@ -404,20 +425,20 @@
"dependencies": {
"AsmResolver.DotNet": "[5.5.1, )",
"AsmResolver.PE.Win32Resources": "[5.5.1, )",
- "Velopack.Packaging": "[1.0.0, )",
- "Velopack.Packaging.HostModel": "[1.0.0, )"
+ "Velopack.IcoLib": "[1.1.1, )",
+ "Velopack.Packaging": "[1.0.0, )"
}
}
},
"net6.0": {
"Microsoft.Build.Utilities.Core": {
"type": "Direct",
- "requested": "[17.9.5, )",
- "resolved": "17.9.5",
- "contentHash": "H2hpVdm7cX/uGJD1HOfab3RKgD5tlnvzQkFqvsrAqGHRi0sqb2w1+hRwERFm23witCjmERnqNgiQjYks6/ds8A==",
+ "requested": "[17.10.4, )",
+ "resolved": "17.10.4",
+ "contentHash": "eEB/tcXkSV+nQgvoa/l53UPtn+KVtKZ8zBceDZsXVTrfE4fA+4+/olrx9W8n2tq4XiESsL9UuGJgCKzqBwQCoQ==",
"dependencies": {
- "Microsoft.Build.Framework": "17.9.5",
- "Microsoft.NET.StringTools": "17.9.5",
+ "Microsoft.Build.Framework": "17.10.4",
+ "Microsoft.NET.StringTools": "17.10.4",
"Microsoft.Win32.Registry": "5.0.0",
"System.Collections.Immutable": "8.0.0",
"System.Configuration.ConfigurationManager": "8.0.0",
@@ -440,8 +461,8 @@
"Nerdbank.GitVersioning": {
"type": "Direct",
"requested": "[3.6.*, )",
- "resolved": "3.6.133",
- "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw=="
+ "resolved": "3.6.139",
+ "contentHash": "rq0Ub/Jik7PtMtZtLn0tHuJ01Yt36RQ+eeBe+S7qnJ/EFOX6D4T9zuYD3vQPYKGI6Ro4t2iWgFm3fGDgjBrMfg=="
},
"Riok.Mapperly": {
"type": "Direct",
@@ -505,8 +526,8 @@
},
"Microsoft.Build.Framework": {
"type": "Transitive",
- "resolved": "17.9.5",
- "contentHash": "CjRmqu9Wv2fyC1d7NKOuBDXcNMI8+GiXGM6izygB+skGGu4Vf0cBcoPq7AFqZCcMpn5DtZ+y7RpaLpB2qrzanQ==",
+ "resolved": "17.10.4",
+ "contentHash": "4qXCwNOXBR1dyCzuks9SwTwFJQO/xmf2wcMislotDWJu7MN/r3xDNoU8Ae5QmKIHPaLG1xmfDkYS7qBVzxmeKw==",
"dependencies": {
"Microsoft.Win32.Registry": "5.0.0",
"System.Memory": "4.5.5",
@@ -526,8 +547,8 @@
},
"Microsoft.Identity.Client": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "3TDPEie+9t/NgBhoNifLFwM6nkypYUa8GUHfMnkYtovDDTQM8bsS2KEIP5tAh28BgfJZgof/KlCEKvGxz0H3Eg==",
+ "resolved": "4.61.2",
+ "contentHash": "zXPDXjTju1yOZQzi5PeNmQfBzlxv+VlCfgfZ70VqyFuhaPtFXwvbLSlwCOMoVALR/MzOR6xkiadoyVDmWDmqKw==",
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "6.35.0",
"System.Diagnostics.DiagnosticSource": "6.0.1"
@@ -535,26 +556,26 @@
},
"Microsoft.Identity.Client.Broker": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "D5M7plyrXrQwhj/PILHLS7G3TqDg8rUO9lR+5awUSXueGxsdA2qHpeKiSR0sVfX6wxziWszOLDUR2vjFFKHPSg==",
+ "resolved": "4.61.2",
+ "contentHash": "+13pImNYfPGl04qZh3Rtq8wPfo55FScAUEio4ymPzeWJ/PE04jCsKH6ZIlrYbPCqe6t9u0lru1eZ+ho9Dsnvpw==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
- "Microsoft.Identity.Client.NativeInterop": "0.16.0"
+ "Microsoft.Identity.Client": "4.61.2",
+ "Microsoft.Identity.Client.NativeInterop": "0.16.1"
}
},
"Microsoft.Identity.Client.Extensions.Msal": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "tCX3c1bSkwH89AKjtB/oAOLlv661tOsEA8ULOCavOc6te/CKlqmMmFs8zMI58Qlv3XkbLOrswYr50IhDntiiPA==",
+ "resolved": "4.61.2",
+ "contentHash": "GCSBhzkr7/7DZDk2kYS9dfihB5ieM2CqwGAhtjuE6T1GtAqzuh62UJhMgO0dSx/PLgIYMzNHnh+pfHK8aWJQJQ==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
+ "Microsoft.Identity.Client": "4.61.2",
"System.Security.Cryptography.ProtectedData": "4.5.0"
}
},
"Microsoft.Identity.Client.NativeInterop": {
"type": "Transitive",
- "resolved": "0.16.0",
- "contentHash": "riaIH9tT4kRG4xKFxx+wpuo+VAZVOr1ElbNubcvpFXb82gjEmZtJRzpdiS4tdRYTVh6CUKNz9AINAptActTtBQ=="
+ "resolved": "0.16.1",
+ "contentHash": "OvY/+/AHESi24f5zOCf9kL4HXwPxXVQ3A+tMQsJvFk2DmP+sc88FYWL49zlku5q0bvx5yFvBLNpHQeRT9a6A5g=="
},
"Microsoft.IdentityModel.Abstractions": {
"type": "Transitive",
@@ -563,8 +584,8 @@
},
"Microsoft.NET.StringTools": {
"type": "Transitive",
- "resolved": "17.9.5",
- "contentHash": "C/oPRnjcIZBRzcpl1V06R1eEMCxOGt6mIm+8ioyblELgJEXLM8XjUPuCwljMO52VetsHw54xMcYwU8UEeHEIEg==",
+ "resolved": "17.10.4",
+ "contentHash": "wyABaqY+IHCMMSTQmcc3Ca6vbmg5BaEPgicnEgpll+4xyWZWlkQqUwafweUd9VAhBb4jqplMl6voUHQ6yfdUcg==",
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
@@ -596,8 +617,8 @@
},
"NuGet.Versioning": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "ypnSvEtpNGo48bAWn95J1oHChycCXcevFSbn53fqzLxlXFSZP7dawu8p/7mHAfGufZQSV2sBpW80XQGIfXO8kQ=="
+ "resolved": "6.10.0",
+ "contentHash": "ytTA08tgZWo/Pbk333hnzQfiMyyynkkwMt4GyC5T0bzExYYSRNrkwv4jT8jmWr5LUOYsumEZvXdoven2SA7YZw=="
},
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
@@ -697,6 +718,20 @@
"resolved": "4.3.2",
"contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg=="
},
+ "SharpZipLib": {
+ "type": "Transitive",
+ "resolved": "1.4.2",
+ "contentHash": "yjj+3zgz8zgXpiiC3ZdF/iyTBbz2fFvMxZFEBPUcwZjIvXOf37Ylm+K58hqMfIBt5JgU/Z2uoUS67JmTLe973A=="
+ },
+ "SixLabors.ImageSharp": {
+ "type": "Transitive",
+ "resolved": "2.1.8",
+ "contentHash": "ML1++vactR5xMW36wHA2nAX3vc6VpR62qYnpopQdLNGbP8BAJ/ckv1IrGIIFRUEQ0JS9EgWr1y1gEa/81f+HaA==",
+ "dependencies": {
+ "System.Runtime.CompilerServices.Unsafe": "5.0.0",
+ "System.Text.Encoding.CodePages": "5.0.0"
+ }
+ },
"System.Collections": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -1219,28 +1254,32 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[6.0.0, )",
- "NuGet.Versioning": "[6.9.1, )"
+ "NuGet.Versioning": "[6.10.0, )"
+ }
+ },
+ "velopack.icolib": {
+ "type": "Project",
+ "dependencies": {
+ "SixLabors.ImageSharp": "[2.1.8, )"
}
},
"velopack.packaging": {
"type": "Project",
"dependencies": {
"Markdig": "[0.37.0, )",
- "Microsoft.Identity.Client": "[4.61.0, )",
- "Microsoft.Identity.Client.Broker": "[4.61.0, )",
- "Microsoft.Identity.Client.Extensions.Msal": "[4.61.0, )",
+ "Microsoft.Identity.Client": "[4.61.2, )",
+ "Microsoft.Identity.Client.Broker": "[4.61.2, )",
+ "Microsoft.Identity.Client.Extensions.Msal": "[4.61.2, )",
"System.Linq.Async": "[6.0.1, )",
"System.Net.Http": "[4.3.4, )",
"Velopack": "[1.0.0, )"
}
},
- "velopack.packaging.hostmodel": {
- "type": "Project"
- },
"velopack.packaging.unix": {
"type": "Project",
"dependencies": {
"ELFSharp": "[2.17.3, )",
+ "SharpZipLib": "[1.4.2, )",
"Velopack.Packaging": "[1.0.0, )"
}
},
@@ -1249,8 +1288,8 @@
"dependencies": {
"AsmResolver.DotNet": "[5.5.1, )",
"AsmResolver.PE.Win32Resources": "[5.5.1, )",
- "Velopack.Packaging": "[1.0.0, )",
- "Velopack.Packaging.HostModel": "[1.0.0, )"
+ "Velopack.IcoLib": "[1.1.1, )",
+ "Velopack.Packaging": "[1.0.0, )"
}
}
}
diff --git a/src/Velopack.Deployment/GitHubRepository.cs b/src/Velopack.Deployment/GitHubRepository.cs
index d7dad373..ca8cdf78 100644
--- a/src/Velopack.Deployment/GitHubRepository.cs
+++ b/src/Velopack.Deployment/GitHubRepository.cs
@@ -56,7 +56,7 @@ public class GitHubRepository : SourceRepository
-
+
diff --git a/src/Velopack.Deployment/_Repository.cs b/src/Velopack.Deployment/_Repository.cs
index 5fcc5931..f9f2c609 100644
--- a/src/Velopack.Deployment/_Repository.cs
+++ b/src/Velopack.Deployment/_Repository.cs
@@ -7,7 +7,14 @@ namespace Velopack.Deployment;
public class RepositoryOptions : IOutputOptions
{
- public string Channel { get; set; } = ReleaseEntryHelper.GetDefaultChannel();
+ private string _channel;
+
+ public RuntimeOs TargetOs { get; set; }
+
+ public string Channel {
+ get => _channel ?? ReleaseEntryHelper.GetDefaultChannel(TargetOs);
+ set => _channel = value;
+ }
public DirectoryInfo ReleaseDir { get; set; }
}
diff --git a/src/Velopack.Deployment/packages.lock.json b/src/Velopack.Deployment/packages.lock.json
index d0c58052..1875d9b3 100644
--- a/src/Velopack.Deployment/packages.lock.json
+++ b/src/Velopack.Deployment/packages.lock.json
@@ -4,11 +4,11 @@
"net6.0": {
"AWSSDK.S3": {
"type": "Direct",
- "requested": "[3.7.308, )",
- "resolved": "3.7.308",
- "contentHash": "U4LWi1yTKVK6IFHWQc4anKPJKXj9dPN+dT1oXto4ndXXlr+DVzm9dULkquyXBjAHlr6jf339ojuYlE6t9Mk6pQ==",
+ "requested": "[3.7.308.8, )",
+ "resolved": "3.7.308.8",
+ "contentHash": "lUQgJsj9n/sJo1CRQFsiB2Gqh3rC21I4e5CWR1b+oYBnc9m8LIsGewctX6YQRzGzuv7Im7iIZo7XoirmYhhqEA==",
"dependencies": {
- "AWSSDK.Core": "[3.7.304.1, 4.0.0)"
+ "AWSSDK.Core": "[3.7.304.10, 4.0.0)"
}
},
"Azure.Storage.Blobs": {
@@ -34,8 +34,8 @@
"Nerdbank.GitVersioning": {
"type": "Direct",
"requested": "[3.6.*, )",
- "resolved": "3.6.133",
- "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw=="
+ "resolved": "3.6.139",
+ "contentHash": "rq0Ub/Jik7PtMtZtLn0tHuJ01Yt36RQ+eeBe+S7qnJ/EFOX6D4T9zuYD3vQPYKGI6Ro4t2iWgFm3fGDgjBrMfg=="
},
"Octokit": {
"type": "Direct",
@@ -45,8 +45,8 @@
},
"AWSSDK.Core": {
"type": "Transitive",
- "resolved": "3.7.304.1",
- "contentHash": "O/gyE5ptF5zEc3QDk3JI3+AOgBfRfmg6eLH5z3x7hUPmV1Wxu0V4Fm86FSyT6czRviMmQGY0q0SVMbWRbP4vDA=="
+ "resolved": "3.7.304.10",
+ "contentHash": "0CXnPzoM+KYXODm2bvRW8eYs7ic2VLop45sphL8FBfvxHfBK/3OJgpxEE2InSdWS1Iby+6KXpz3NplJJY5+Y1A=="
},
"Azure.Core": {
"type": "Transitive",
@@ -94,8 +94,8 @@
},
"Microsoft.Identity.Client": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "3TDPEie+9t/NgBhoNifLFwM6nkypYUa8GUHfMnkYtovDDTQM8bsS2KEIP5tAh28BgfJZgof/KlCEKvGxz0H3Eg==",
+ "resolved": "4.61.2",
+ "contentHash": "zXPDXjTju1yOZQzi5PeNmQfBzlxv+VlCfgfZ70VqyFuhaPtFXwvbLSlwCOMoVALR/MzOR6xkiadoyVDmWDmqKw==",
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "6.35.0",
"System.Diagnostics.DiagnosticSource": "6.0.1"
@@ -103,26 +103,26 @@
},
"Microsoft.Identity.Client.Broker": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "D5M7plyrXrQwhj/PILHLS7G3TqDg8rUO9lR+5awUSXueGxsdA2qHpeKiSR0sVfX6wxziWszOLDUR2vjFFKHPSg==",
+ "resolved": "4.61.2",
+ "contentHash": "+13pImNYfPGl04qZh3Rtq8wPfo55FScAUEio4ymPzeWJ/PE04jCsKH6ZIlrYbPCqe6t9u0lru1eZ+ho9Dsnvpw==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
- "Microsoft.Identity.Client.NativeInterop": "0.16.0"
+ "Microsoft.Identity.Client": "4.61.2",
+ "Microsoft.Identity.Client.NativeInterop": "0.16.1"
}
},
"Microsoft.Identity.Client.Extensions.Msal": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "tCX3c1bSkwH89AKjtB/oAOLlv661tOsEA8ULOCavOc6te/CKlqmMmFs8zMI58Qlv3XkbLOrswYr50IhDntiiPA==",
+ "resolved": "4.61.2",
+ "contentHash": "GCSBhzkr7/7DZDk2kYS9dfihB5ieM2CqwGAhtjuE6T1GtAqzuh62UJhMgO0dSx/PLgIYMzNHnh+pfHK8aWJQJQ==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
+ "Microsoft.Identity.Client": "4.61.2",
"System.Security.Cryptography.ProtectedData": "4.5.0"
}
},
"Microsoft.Identity.Client.NativeInterop": {
"type": "Transitive",
- "resolved": "0.16.0",
- "contentHash": "riaIH9tT4kRG4xKFxx+wpuo+VAZVOr1ElbNubcvpFXb82gjEmZtJRzpdiS4tdRYTVh6CUKNz9AINAptActTtBQ=="
+ "resolved": "0.16.1",
+ "contentHash": "OvY/+/AHESi24f5zOCf9kL4HXwPxXVQ3A+tMQsJvFk2DmP+sc88FYWL49zlku5q0bvx5yFvBLNpHQeRT9a6A5g=="
},
"Microsoft.IdentityModel.Abstractions": {
"type": "Transitive",
@@ -146,8 +146,8 @@
},
"NuGet.Versioning": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "ypnSvEtpNGo48bAWn95J1oHChycCXcevFSbn53fqzLxlXFSZP7dawu8p/7mHAfGufZQSV2sBpW80XQGIfXO8kQ=="
+ "resolved": "6.10.0",
+ "contentHash": "ytTA08tgZWo/Pbk333hnzQfiMyyynkkwMt4GyC5T0bzExYYSRNrkwv4jT8jmWr5LUOYsumEZvXdoven2SA7YZw=="
},
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
@@ -752,16 +752,16 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[6.0.0, )",
- "NuGet.Versioning": "[6.9.1, )"
+ "NuGet.Versioning": "[6.10.0, )"
}
},
"velopack.packaging": {
"type": "Project",
"dependencies": {
"Markdig": "[0.37.0, )",
- "Microsoft.Identity.Client": "[4.61.0, )",
- "Microsoft.Identity.Client.Broker": "[4.61.0, )",
- "Microsoft.Identity.Client.Extensions.Msal": "[4.61.0, )",
+ "Microsoft.Identity.Client": "[4.61.2, )",
+ "Microsoft.Identity.Client.Broker": "[4.61.2, )",
+ "Microsoft.Identity.Client.Extensions.Msal": "[4.61.2, )",
"System.Linq.Async": "[6.0.1, )",
"System.Net.Http": "[4.3.4, )",
"Velopack": "[1.0.0, )"
diff --git a/src/Velopack.IcoLib/Binary/BitReader.cs b/src/Velopack.IcoLib/Binary/BitReader.cs
new file mode 100644
index 00000000..90fcb677
--- /dev/null
+++ b/src/Velopack.IcoLib/Binary/BitReader.cs
@@ -0,0 +1,61 @@
+using System;
+
+namespace Ico.Binary
+{
+ internal ref struct BitReader
+ {
+ private ByteReader reader;
+ private byte currentByte;
+ private uint currentBitsRemaining;
+
+ public BitReader(ByteReader reader)
+ {
+ this.reader = reader;
+ this.currentByte = 0;
+ this.currentBitsRemaining = 0;
+ }
+
+ public uint NextBit1()
+ {
+ return NextBit(1);
+ }
+
+ public uint NextBit2()
+ {
+ return NextBit(2);
+ }
+
+ public uint NextBit4()
+ {
+ return NextBit(4);
+ }
+
+ public uint NextBit(uint n)
+ {
+ EnsureBits(n);
+
+ var shift = (byte)(currentBitsRemaining - n);
+ var mask = (1u << (int)n) - 1;
+ var result = (uint)(currentByte >> shift) & mask;
+
+ currentBitsRemaining -= n;
+ return result;
+ }
+
+ private void EnsureBits(uint numBitsNeeded)
+ {
+ if (0 != (currentBitsRemaining % numBitsNeeded))
+ {
+ throw new Exception("Cannot read unaligned value");
+ }
+
+ if (currentBitsRemaining >= numBitsNeeded)
+ {
+ return;
+ }
+
+ currentByte = reader.NextUint8();
+ currentBitsRemaining = 8;
+ }
+ }
+}
diff --git a/src/Velopack.IcoLib/Binary/BitWriter.cs b/src/Velopack.IcoLib/Binary/BitWriter.cs
new file mode 100644
index 00000000..79b94f6f
--- /dev/null
+++ b/src/Velopack.IcoLib/Binary/BitWriter.cs
@@ -0,0 +1,63 @@
+using System;
+
+namespace Ico.Binary
+{
+ internal class BitWriter
+ {
+ private ByteWriter writer;
+ private uint currentBitsRemaining;
+
+ public BitWriter(ByteWriter writer)
+ {
+ this.writer = writer;
+ this.currentBitsRemaining = 0;
+ }
+
+ public void AddBit1(byte value)
+ {
+ AddBits(1, value);
+ }
+
+ public void AddBit2(byte value)
+ {
+ AddBits(2, value);
+ }
+
+ public void AddBit4(byte value)
+ {
+ AddBits(4, value);
+ }
+
+ public void AddBits(uint numBits, byte value)
+ {
+ EnsureBits(numBits);
+
+ if (value != 0)
+ {
+ var shift = (int)(currentBitsRemaining - numBits);
+
+ var b = writer.Data[writer.Data.Count - 1];
+ b = (byte)(b | (value << shift));
+ writer.Data[writer.Data.Count - 1] = b;
+ }
+
+ currentBitsRemaining -= numBits;
+ }
+
+ private void EnsureBits(uint numBitsNeeded)
+ {
+ if (0 != (currentBitsRemaining % numBitsNeeded))
+ {
+ throw new Exception("Cannot write unaligned value");
+ }
+
+ if (currentBitsRemaining >= numBitsNeeded)
+ {
+ return;
+ }
+
+ writer.AddUint8(0);
+ currentBitsRemaining = 8;
+ }
+ }
+}
diff --git a/src/Velopack.IcoLib/Binary/ByteOrderConverter.cs b/src/Velopack.IcoLib/Binary/ByteOrderConverter.cs
new file mode 100644
index 00000000..e973e496
--- /dev/null
+++ b/src/Velopack.IcoLib/Binary/ByteOrderConverter.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Ico.Binary
+{
+ public enum ByteOrder
+ {
+ LittleEndian,
+ BigEndian,
+ NetworkEndian = BigEndian,
+ }
+
+ public static class ByteOrderConverter
+ {
+ private static bool NeedsSwap(ByteOrder endian) => BitConverter.IsLittleEndian != (endian == ByteOrder.LittleEndian);
+
+ public static ushort To(ByteOrder endian, ushort value) => NeedsSwap(endian) ? Swap(value) : value;
+
+ public static uint To(ByteOrder endian, uint value) => NeedsSwap(endian) ? Swap(value) : value;
+
+ public static ulong To(ByteOrder endian, ulong value) => NeedsSwap(endian) ? Swap(value) : value;
+
+ public static int To(ByteOrder endian, int value) => NeedsSwap(endian) ? (int)Swap((uint)value) : value;
+
+ public static ushort Swap(ushort value) =>
+ (ushort)((value & 0xffu) << 8 | (value & 0xff00u) >> 8);
+
+ private static uint Swap(uint value) =>
+ (value & 0x000000FFU) << 24 | (value & 0x0000FF00U) << 8 |
+ (value & 0x00FF0000U) >> 8 | (value & 0xFF000000U) >> 24;
+
+ private static ulong Swap(ulong value) =>
+ (value & 0x00000000000000FFUL) << 56 | (value & 0x000000000000FF00UL) << 40 |
+ (value & 0x0000000000FF0000UL) << 24 | (value & 0x00000000FF000000UL) << 8 |
+ (value & 0x000000FF00000000UL) >> 8 | (value & 0x0000FF0000000000UL) >> 24 |
+ (value & 0x00FF000000000000UL) >> 40 | (value & 0xFF00000000000000UL) >> 56;
+ }
+}
diff --git a/src/Velopack.IcoLib/Binary/ByteReader.cs b/src/Velopack.IcoLib/Binary/ByteReader.cs
new file mode 100644
index 00000000..10602695
--- /dev/null
+++ b/src/Velopack.IcoLib/Binary/ByteReader.cs
@@ -0,0 +1,53 @@
+using System;
+
+namespace Ico.Binary
+{
+ public class ByteReader
+ {
+ public Memory Data { get; }
+ public ByteOrder Endianness { get; }
+ public int SeekOffset { get; set; }
+
+ public ByteReader(Memory data, ByteOrder endianness)
+ {
+ Data = data;
+ Endianness = endianness;
+ SeekOffset = 0;
+ }
+
+ public byte NextUint8()
+ {
+ return Data.Span[SeekOffset++];
+ }
+
+ public ushort NextUint16()
+ {
+ var result = BitConverter.ToUInt16(Data.Span.Slice(SeekOffset, 2).ToArray(), 0);
+ SeekOffset += 2;
+ return ByteOrderConverter.To(Endianness, result);
+ }
+
+ public uint NextUint32()
+ {
+ var result = BitConverter.ToUInt32(Data.Span.Slice(SeekOffset, 4).ToArray(), 0);
+ SeekOffset += 4;
+ return ByteOrderConverter.To(Endianness, result);
+ }
+
+ public int NextInt32()
+ {
+ var result = BitConverter.ToInt32(Data.Span.Slice(SeekOffset, 4).ToArray(), 0);
+ SeekOffset += 4;
+ return ByteOrderConverter.To(Endianness, result);
+ }
+
+ public ulong NextUint64()
+ {
+ var result = BitConverter.ToUInt64(Data.Span.Slice(SeekOffset, 8).ToArray(), 0);
+ SeekOffset += 8;
+ return ByteOrderConverter.To(Endianness, result);
+ }
+
+ public bool IsEof => SeekOffset == Data.Length;
+ }
+}
diff --git a/src/Velopack.IcoLib/Binary/ByteWriter.cs b/src/Velopack.IcoLib/Binary/ByteWriter.cs
new file mode 100644
index 00000000..ed4ad892
--- /dev/null
+++ b/src/Velopack.IcoLib/Binary/ByteWriter.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+
+namespace Ico.Binary
+{
+ internal class ByteWriter
+ {
+ public List Data { get; } = new List();
+ public ByteOrder Endianness { get; }
+ public int SeekOffset { get; set; } = 0;
+
+ public ByteWriter(ByteOrder endianness)
+ {
+ Endianness = endianness;
+ }
+
+ public void AddUint8(byte value)
+ {
+ EnsureCapacity(1);
+ Data[SeekOffset++] = value;
+ }
+
+ public void AddUint16(ushort value)
+ {
+ EnsureCapacity(2);
+ value = ByteOrderConverter.To(Endianness, value);
+ foreach (var b in BitConverter.GetBytes(value))
+ {
+ Data[SeekOffset++] = b;
+ }
+ }
+
+ public void AddUint32(uint value)
+ {
+ EnsureCapacity(4);
+ value = ByteOrderConverter.To(Endianness, value);
+ foreach (var b in BitConverter.GetBytes(value))
+ {
+ Data[SeekOffset++] = b;
+ }
+ }
+
+ public void AddInt32(int value)
+ {
+ EnsureCapacity(4);
+ value = ByteOrderConverter.To(Endianness, value);
+ foreach (var b in BitConverter.GetBytes(value))
+ {
+ Data[SeekOffset++] = b;
+ }
+ }
+
+ private void EnsureCapacity(int additionalBytesNeeded)
+ {
+ while (Data.Count < SeekOffset + additionalBytesNeeded)
+ {
+ Data.Add(0);
+ }
+ }
+
+ internal void AddBlob(byte[] blob)
+ {
+ EnsureCapacity(blob.Length);
+ foreach (var b in blob)
+ {
+ Data[SeekOffset++] = b;
+ }
+ }
+ }
+}
diff --git a/src/Velopack.IcoLib/Codecs/BmpDecoder.cs b/src/Velopack.IcoLib/Codecs/BmpDecoder.cs
new file mode 100644
index 00000000..c681523e
--- /dev/null
+++ b/src/Velopack.IcoLib/Codecs/BmpDecoder.cs
@@ -0,0 +1,302 @@
+using Ico.Binary;
+using Ico.Model;
+using Ico.Validation;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace Ico.Codecs
+{
+ public static class BmpDecoder
+ {
+ public static void DoBitmapEntry(ByteReader reader, ParseContext context, IcoFrame source)
+ {
+ var biSize = reader.NextUint32();
+ var biWidth = reader.NextInt32();
+ var biHeight = reader.NextInt32();
+ var biPlanes = reader.NextUint16();
+ var biBitCount = reader.NextUint16();
+ var biCompression = reader.NextUint32();
+ var biSizeImage = reader.NextUint32();
+ var biXPelsPerMeter = reader.NextInt32();
+ var biYPelsPerMeter = reader.NextInt32();
+ var biClrUsed = reader.NextUint32();
+ var biClrImportant = reader.NextUint32();
+
+ if (biSize != FileFormatConstants._bitmapInfoHeaderSize)
+ {
+ throw new InvalidIcoFileException(IcoErrorCode.InvalidBitapInfoHeader_ciSize, $"BITMAPINFOHEADER.ciSize should be {FileFormatConstants._bitmapInfoHeaderSize}, was {biSize}.", context);
+ }
+
+ if (biXPelsPerMeter != 0)
+ {
+ context.Reporter.WarnLine(IcoErrorCode.InvalidBitapInfoHeader_biXPelsPerMeter, $"BITMAPINFOHEADER.biXPelsPerMeter should be 0, was {biXPelsPerMeter}.", context.DisplayedPath, context.ImageDirectoryIndex.Value);
+ }
+
+ if (biYPelsPerMeter != 0)
+ {
+ context.Reporter.WarnLine(IcoErrorCode.InvalidBitapInfoHeader_biYPelsPerMeter, $"BITMAPINFOHEADER.biYPelsPerMeter should be 0, was {biYPelsPerMeter}.", context.DisplayedPath, context.ImageDirectoryIndex.Value);
+ }
+
+ if (biCompression == FileFormatConstants.BI_BITFIELDS)
+ {
+ throw new InvalidIcoFileException(IcoErrorCode.BitfieldCompressionNotSupported, $"This tool does not implement icon bitmaps that use BI_BITFIELDS compression. (The .ICO file may be okay, although it is certainly unusual.)", context);
+ }
+
+ if (biCompression != FileFormatConstants.BI_RGB)
+ {
+ throw new InvalidIcoFileException(IcoErrorCode.BitmapCompressionNotSupported, $"BITMAPINFOHEADER.biCompression is unknown value ({biCompression}).", context);
+ }
+
+ if (biHeight != source.Encoding.ClaimedHeight * 2)
+ {
+ context.Reporter.WarnLine(IcoErrorCode.MismatchedHeight, $"BITMAPINFOHEADER.biHeight is not exactly double ICONDIRECTORY.bHeight ({biHeight} != 2 * {source.Encoding.ClaimedHeight}).", context.DisplayedPath, context.ImageDirectoryIndex.Value);
+ }
+
+ if (biWidth != source.Encoding.ClaimedWidth)
+ {
+ context.Reporter.WarnLine(IcoErrorCode.MismatchedWidth, $"BITMAPINFOHEADER.biWidth is not exactly equal to ICONDIRECTORY.bWidth ({biWidth} != 2 * {source.Encoding.ClaimedWidth}).", context.DisplayedPath, context.ImageDirectoryIndex.Value);
+ }
+
+ var height = biHeight / 2;
+ var width = biWidth;
+
+ source.Encoding.ActualHeight = (uint)height;
+ source.Encoding.ActualWidth = (uint)width;
+ source.Encoding.ActualBitDepth = biBitCount;
+ source.Encoding.Type = IcoEncodingType.Bitmap;
+ source.CookedData = new Image(width, height);
+
+ switch (biBitCount)
+ {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ ReadIndexedBitmap(reader, context, biBitCount, biClrUsed, height, width, source);
+ break;
+ case 16:
+ ReadBitmap16(reader, context, height, width, source);
+ break;
+ case 24:
+ ReadBitmap24(reader, context, biClrUsed, height, width, source);
+ break;
+ case 32:
+ ReadBitmap32(reader, context, height, width, source);
+ break;
+ default:
+ throw new InvalidIcoFileException(IcoErrorCode.InvalidBitapInfoHeader_biBitCount, $"BITMAPINFOHEADER.biBitCount is unknown value ({biBitCount}); expected 1, 4, 8, 16, or 32 bit depth.", context);
+ }
+ }
+
+
+ private static void ReadIndexedBitmap(ByteReader reader, ParseContext context, uint bitDepth, uint colorTableSize, int height, int width, IcoFrame source)
+ {
+ var anyReservedChannel = false;
+ var anyIndexOutOfBounds = false;
+
+ if (colorTableSize == 0)
+ {
+ colorTableSize = 1u << (int)bitDepth;
+ }
+
+ source.Encoding.PaletteSize = colorTableSize;
+
+ if (colorTableSize > 1u << (int)bitDepth)
+ {
+ throw new InvalidIcoFileException(IcoErrorCode.InvalidBitapInfoHeader_biClrUsed, $"BITMAPINFOHEADER.biClrUsed is greater than 2^biBitCount (biClrUsed == {colorTableSize}, biBitCount = {bitDepth}).", context);
+ }
+ else if (colorTableSize < 1u << (int)bitDepth)
+ {
+ context.Reporter.WarnLine(IcoErrorCode.UndersizedColorTable, $"This bitmap uses a color table that is smaller than the bit depth ({colorTableSize} < 2^{bitDepth})", context.DisplayedPath, context.ImageDirectoryIndex.Value);
+ }
+
+ var colorTable = new Rgba32[colorTableSize];
+ for (var i = 0; i < colorTableSize; i++)
+ {
+ var c = new Bgra32
+ {
+ PackedValue = reader.NextUint32()
+ };
+
+ if (c.A != 0)
+ {
+ anyReservedChannel = true;
+ }
+
+ c.A = 255;
+ c.ToRgba32(ref colorTable[i]);
+ }
+
+ var padding = reader.SeekOffset % 4;
+
+ for (var y = height - 1; y >= 0; y--)
+ {
+ var bits = new BitReader(reader);
+
+ for (var x = 0; x < width; x++)
+ {
+ var colorIndex = bits.NextBit(bitDepth);
+
+ if (colorIndex >= colorTableSize)
+ {
+ anyIndexOutOfBounds = true;
+ source.CookedData[x, y] = Color.Black;
+ }
+ else
+ {
+ source.CookedData[x, y] = colorTable[colorIndex];
+ }
+ }
+
+ while ((reader.SeekOffset % 4) != padding)
+ {
+ reader.SeekOffset += 1;
+ }
+ }
+
+ switch (bitDepth)
+ {
+ case 1:
+ source.Encoding.PixelFormat = BitmapEncoding.Pixel_indexed1;
+ break;
+ case 2:
+ source.Encoding.PixelFormat = BitmapEncoding.Pixel_indexed2;
+ break;
+ case 4:
+ source.Encoding.PixelFormat = BitmapEncoding.Pixel_indexed4;
+ break;
+ case 8:
+ source.Encoding.PixelFormat = BitmapEncoding.Pixel_indexed8;
+ break;
+ }
+
+ ReadBitmapMask(reader, context, height, width, source);
+
+ if (anyReservedChannel)
+ {
+ context.Reporter.WarnLine(IcoErrorCode.NonzeroAlpha, $"Reserved Alpha channel used in color table.", context.DisplayedPath, context.ImageDirectoryIndex.Value);
+ }
+
+ if (anyIndexOutOfBounds)
+ {
+ context.Reporter.WarnLine(IcoErrorCode.IndexedColorOutOfBounds, $"Bitmap uses color at illegal index; pixel filled with Black color.", context.DisplayedPath, context.ImageDirectoryIndex.Value);
+ }
+ }
+
+ private static void ReadBitmap16(ByteReader reader, ParseContext context, int height, int width, IcoFrame source)
+ {
+ for (var y = height - 1; y >= 0; y--)
+ {
+ for (var x = 0; x < width; x++)
+ {
+ var colorValue = reader.NextUint16();
+ source.CookedData[x, y] = new Rgba32(
+ _5To8[colorValue >> 10],
+ _5To8[(colorValue >> 5) & 0x1f],
+ _5To8[colorValue & 0x1f],
+ 255);
+ }
+ }
+
+ source.Encoding.PixelFormat = BitmapEncoding.Pixel_rgb15;
+ ReadBitmapMask(reader, context, height, width, source);
+ }
+
+ private static readonly byte[] _5To8 = new byte[]
+ {
+ 0, 8, 16, 25, 33, 41, 49, 58, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, 148, 156, 165, 173, 181, 189, 197, 206, 214, 222, 230, 239, 247, 255,
+ };
+
+ private static void ReadBitmap24(ByteReader reader, ParseContext context, uint colorTableSize, int height, int width, IcoFrame source)
+ {
+ reader.SeekOffset += (int)colorTableSize * 4;
+
+ for (var y = height - 1; y >= 0; y--)
+ {
+ for (var x = 0; x < width; x++)
+ {
+ var b = reader.NextUint8();
+ var g = reader.NextUint8();
+ var r = reader.NextUint8();
+
+ source.CookedData[x, y] = new Rgba32(r, g, b, 255);
+ }
+ }
+
+ source.Encoding.PixelFormat = BitmapEncoding.Pixel_rgb24;
+ ReadBitmapMask(reader, context, height, width, source);
+ }
+
+ private static void ReadBitmap32(ByteReader reader, ParseContext context, int height, int width, IcoFrame source)
+ {
+ for (var y = height - 1; y >= 0; y--)
+ {
+ for (var x = 0; x < width; x++)
+ {
+ var colorValue = new Bgra32 { PackedValue = reader.NextUint32() };
+ Rgba32 rgba32Value = source.CookedData[x, y]; // the ref keyword cannot be used on this indexer, so we need a temporary
+ colorValue.ToRgba32(ref rgba32Value);
+ source.CookedData[x, y] = rgba32Value;
+ }
+ }
+
+ source.Encoding.PixelFormat = BmpUtil.IsAnyAlphaChannel(source.CookedData)
+ ? BitmapEncoding.Pixel_argb32
+ : BitmapEncoding.Pixel_0rgb32;
+
+ ReadBitmapMask(reader, context, height, width, source);
+ }
+
+ private static void ReadBitmapMask(ByteReader reader, ParseContext context, int height, int width, IcoFrame source)
+ {
+ source.Mask = new bool[width, height];
+
+ var anyMask = false;
+ var anyMaskedColors = false;
+
+ var padding = reader.SeekOffset % 4;
+
+ for (var y = height - 1; y >= 0; y--)
+ {
+ var bits = new BitReader(reader);
+
+ for (var x = 0; x < width; x++)
+ {
+ var mask = bits.NextBit1();
+
+ if (mask == 0)
+ {
+ continue;
+ }
+
+ source.Mask[x, y] = true;
+
+ anyMask = true;
+
+ if (source.CookedData[x, y].R != 0 || source.CookedData[x, y].G != 0 || source.CookedData[x, y].B != 0)
+ {
+ anyMaskedColors = true;
+ }
+
+ //source.CookedData[x, y] = new Rgba32(0, 0, 0, 0);
+ }
+
+ while ((reader.SeekOffset % 4) != padding)
+ {
+ reader.SeekOffset += 1;
+ }
+ }
+
+ if (!anyMask)
+ {
+ context.Reporter.WarnLine(IcoErrorCode.NoMaskedPixels, $"No bitmap mask.", context.DisplayedPath, context.ImageDirectoryIndex.Value);
+ }
+
+ if (anyMaskedColors)
+ {
+ context.Reporter.WarnLine(IcoErrorCode.MaskedPixelWithColor, $"Non-black image pixels masked out.", context.DisplayedPath, context.ImageDirectoryIndex.Value);
+ }
+ }
+ }
+}
+
diff --git a/src/Velopack.IcoLib/Codecs/BmpEncoder.cs b/src/Velopack.IcoLib/Codecs/BmpEncoder.cs
new file mode 100644
index 00000000..4751e812
--- /dev/null
+++ b/src/Velopack.IcoLib/Codecs/BmpEncoder.cs
@@ -0,0 +1,328 @@
+using Ico.Binary;
+using Ico.Model;
+using Ico.Validation;
+using SixLabors.ImageSharp.PixelFormats;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Ico.Codecs
+{
+ public static class BmpEncoder
+ {
+ public enum Dialect
+ {
+ Ico,
+ Bmp,
+ }
+
+ public static byte[] EncodeBitmap(ParseContext context, BitmapEncoding encoding, Dialect dialect, IcoFrame source)
+ {
+ context.LastEncodeError = IcoErrorCode.NoError;
+
+ return (BmpUtil.GetBitDepthForPixelFormat(encoding) < 16)
+ ? EncodeIndexedBitmap(context, encoding, dialect, source)
+ : EncodeRgbBitmap(source, context, encoding, dialect);
+ }
+
+ private static byte[] EncodeIndexedBitmap(ParseContext context, BitmapEncoding encoding, Dialect dialect, IcoFrame source)
+ {
+ var numBits = BmpUtil.GetBitDepthForPixelFormat(encoding);
+
+ var colorTable = BuildColorTable(1u << numBits, context, source);
+ if (colorTable == null)
+ {
+ context.LastEncodeError = IcoErrorCode.TooManyColorsForBitDepth;
+ return null;
+ }
+
+ var writer = new ByteWriter(ByteOrder.LittleEndian);
+
+ EncodeBitmapHeader(source, dialect, encoding, colorTable, writer, out var offsetToImageSize);
+
+ var reverseTable = new Dictionary();
+ for (var i = 0; i < colorTable.Length; i++)
+ {
+ if (!reverseTable.ContainsKey(colorTable[i]))
+ {
+ reverseTable.Add(colorTable[i], i);
+ }
+ }
+
+ var offsetToData = (uint)writer.Data.Count;
+ var padding = writer.Data.Count % 4;
+
+ for (var y = source.CookedData.Height - 1; y >= 0; y--)
+ {
+ var bits = new BitWriter(writer);
+
+ for (var x = 0; x < source.CookedData.Width; x++)
+ {
+ var color = source.CookedData[x, y];
+
+ if (source.Mask[x, y])
+ {
+ switch (context.MaskedImagePixelEmitOptions)
+ {
+ case StrictnessPolicy.Compliant:
+ color = new Rgba32(0, 0, 0, 255);
+ break;
+ case StrictnessPolicy.PreserveSource:
+ // Pass through whatever the original pixel was.
+ break;
+ case StrictnessPolicy.Loose:
+ color = colorTable.First();
+ break;
+ }
+ }
+
+ color.A = 255;
+
+ var index = reverseTable[color];
+ bits.AddBits((uint)numBits, (byte)index);
+ }
+
+ while ((writer.Data.Count % 4) != padding)
+ {
+ writer.AddUint8(0);
+ }
+ }
+
+ return FinalizeBitmap(source, encoding, dialect, writer, offsetToData, offsetToImageSize);
+ }
+
+ private static byte[] EncodeRgbBitmap(IcoFrame source, ParseContext context, BitmapEncoding encoding, Dialect dialect)
+ {
+ var writer = new ByteWriter(ByteOrder.LittleEndian);
+ EncodeBitmapHeader(source, dialect, encoding, null, writer, out var offsetToImageSize);
+
+ var offsetToData = (uint)writer.Data.Count;
+ var padding = writer.Data.Count % 4;
+
+ for (var y = source.CookedData.Height - 1; y >= 0; y--)
+ {
+ var bits = new BitWriter(writer);
+
+ for (var x = 0; x < source.CookedData.Width; x++)
+ {
+ var color = source.CookedData[x, y];
+
+ if (source.Mask[x, y])
+ {
+ switch (context.MaskedImagePixelEmitOptions)
+ {
+ case StrictnessPolicy.Compliant:
+ case StrictnessPolicy.Loose:
+ color = new Rgba32(0, 0, 0, 0);
+ break;
+ case StrictnessPolicy.PreserveSource:
+ // Pass through whatever the original pixel was.
+ break;
+ }
+ }
+
+ switch (encoding)
+ {
+ case BitmapEncoding.Pixel_rgb15:
+ var value = X8To5(color.R) << 10 | X8To5(color.G) << 5 | X8To5(color.B);
+ writer.AddUint16((ushort)value);
+ break;
+ case BitmapEncoding.Pixel_rgb24:
+ writer.AddUint8(color.B);
+ writer.AddUint8(color.G);
+ writer.AddUint8(color.R);
+ break;
+ case BitmapEncoding.Pixel_0rgb32:
+ writer.AddUint8(color.B);
+ writer.AddUint8(color.G);
+ writer.AddUint8(color.R);
+ writer.AddUint8(0);
+ break;
+ case BitmapEncoding.Pixel_argb32:
+ writer.AddUint8(color.B);
+ writer.AddUint8(color.G);
+ writer.AddUint8(color.R);
+ writer.AddUint8(color.A);
+ break;
+ }
+ }
+
+ while ((writer.Data.Count % 4) != padding)
+ {
+ writer.AddUint8(0);
+ }
+ }
+
+ return FinalizeBitmap(source, encoding, dialect, writer, offsetToData, offsetToImageSize);
+ }
+
+ private static byte X8To5(uint b)
+ {
+ return (byte)(b * 32 / 256);
+ }
+
+ private static void EncodeBitmapHeader(IcoFrame source, Dialect dialect, BitmapEncoding encoding, Rgba32[] colorTable, ByteWriter writer, out uint offsetToImageSize)
+ {
+ if (dialect != Dialect.Ico)
+ {
+ writer.AddUint16(FileFormatConstants._bitmapFileMagic);
+ writer.AddUint32(0); // Size will be filled in later
+ writer.AddUint32(0); // Reserved
+ writer.AddUint32(0); // Offset will be filled in later
+ }
+
+ writer.AddUint32(FileFormatConstants._bitmapInfoHeaderSize);
+ writer.AddUint32((uint)source.CookedData.Width);
+ writer.AddUint32((uint)source.CookedData.Height * ((dialect == Dialect.Ico) ? 2u : 1u));
+ writer.AddUint16(1); // biPlanes
+ writer.AddUint16((ushort)BmpUtil.GetBitDepthForPixelFormat(encoding)); // biBitCount
+ writer.AddUint32(FileFormatConstants.BI_RGB); // biCompression
+ offsetToImageSize = (uint)writer.SeekOffset;
+ writer.AddUint32(0); // biSizeImage
+ writer.AddUint32((dialect == Dialect.Ico) ? 0u : FileFormatConstants._72dpiInPixelsPerMeter); // biXPelsPerMeter
+ writer.AddUint32((dialect == Dialect.Ico) ? 0u : FileFormatConstants._72dpiInPixelsPerMeter); // biYPelsPerMeter
+ writer.AddUint32((uint)(colorTable?.Length ?? 0)); // biClrUsed
+ writer.AddUint32(0); // biClrImportant
+
+ if (colorTable != null)
+ {
+ foreach (var color in colorTable)
+ {
+ writer.AddUint8(color.B);
+ writer.AddUint8(color.G);
+ writer.AddUint8(color.R);
+ writer.AddUint8(0);
+ }
+ }
+
+ if (dialect != Dialect.Ico)
+ {
+ while (writer.Data.Count % 4 != 0)
+ {
+ writer.AddUint8(0);
+ }
+ }
+ }
+
+ private static byte[] FinalizeBitmap(IcoFrame source, BitmapEncoding encoding, Dialect dialect, ByteWriter writer, uint offsetToData, uint offsetToImageSize)
+ {
+ var offsetToEndOfData = writer.SeekOffset;
+
+ if (dialect == Dialect.Ico)
+ {
+ var padding = writer.Data.Count % 4;
+
+ var inferMaskFromAlpha = (source.Encoding.PixelFormat == BitmapEncoding.Pixel_argb32 && encoding != BitmapEncoding.Pixel_argb32);
+
+ for (var y = source.CookedData.Height - 1; y >= 0; y--)
+ {
+ var bits = new BitWriter(writer);
+
+ for (var x = 0; x < source.CookedData.Width; x++)
+ {
+ var mask = inferMaskFromAlpha
+ ? (source.CookedData[x, y].A == 0)
+ : source.Mask[x, y];
+
+ bits.AddBit1((byte)(mask ? 1 : 0));
+ }
+
+ while ((writer.Data.Count % 4) != padding)
+ {
+ writer.AddUint8(0);
+ }
+ }
+ }
+
+ if (dialect != Dialect.Ico)
+ {
+ writer.SeekOffset = 2;
+ writer.AddUint32((uint)writer.Data.Count);
+
+ writer.SeekOffset = 10;
+ writer.AddUint32(offsetToData);
+ }
+
+ writer.SeekOffset = (int)offsetToImageSize;
+ writer.AddUint32((uint)(offsetToEndOfData - offsetToData)); // biSizeImage
+
+ return writer.Data.ToArray();
+ }
+
+ private static Rgba32[] BuildColorTable(uint maxColorTableSize, ParseContext context, IcoFrame source)
+ {
+ var colorTable = new Dictionary();
+
+ for (var y = source.CookedData.Height - 1; y >= 0; y--)
+ {
+ for (var x = 0; x < source.CookedData.Width; x++)
+ {
+ var color = source.CookedData[x, y];
+
+ if (source.Mask[x, y])
+ {
+ switch (context.MaskedImagePixelEmitOptions)
+ {
+ case StrictnessPolicy.Compliant:
+ // Ensure an entry is added for black.
+ color = new Rgba32(0, 0, 0, 0);
+ break;
+ case StrictnessPolicy.PreserveSource:
+ // Pass through whatever the original pixel was.
+ break;
+ case StrictnessPolicy.Loose:
+ // Don't create a palette entry for this pixel.
+ continue;
+ }
+ }
+
+ color.A = 255;
+
+ if (colorTable.ContainsKey(color))
+ {
+ colorTable[color] += 1;
+ }
+ else if (colorTable.Count == maxColorTableSize)
+ {
+ return null;
+ }
+ else
+ {
+ colorTable.Add(color, 1);
+ }
+ }
+ }
+
+ if (colorTable.Count == 0)
+ {
+ colorTable.Add(new Rgba32(0, 0, 0, 255), 1);
+ }
+
+ var table = (from c in colorTable
+ orderby c.Value descending
+ select c.Key).ToList();
+
+ var targetPaletteSize = 0u;
+
+ switch (context.AllowPaletteTruncation)
+ {
+ case StrictnessPolicy.Compliant:
+ targetPaletteSize = maxColorTableSize;
+ break;
+ case StrictnessPolicy.PreserveSource:
+ targetPaletteSize = source.Encoding.PaletteSize;
+ break;
+ case StrictnessPolicy.Loose:
+ targetPaletteSize = (uint)table.Count;
+ break;
+ }
+
+ while (table.Count < targetPaletteSize)
+ {
+ table.Add(new Rgba32(0, 0, 0, 255));
+ }
+
+ return table.ToArray();
+ }
+ }
+}
diff --git a/src/Velopack.IcoLib/Codecs/BmpUtil.cs b/src/Velopack.IcoLib/Codecs/BmpUtil.cs
new file mode 100644
index 00000000..f1bc05f2
--- /dev/null
+++ b/src/Velopack.IcoLib/Codecs/BmpUtil.cs
@@ -0,0 +1,236 @@
+using Ico.Model;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
+using System;
+using System.Collections.Generic;
+
+namespace Ico.Codecs
+{
+ public static class BmpUtil
+ {
+ public static bool IsAlphaSignificant(IcoFrame source)
+ {
+ if (IsAnyPartialTransparency(source.CookedData))
+ {
+ return true;
+ }
+
+ return IsAnyPixel(source.CookedData, (x, y, pixel)
+ => IsCompletelyTransparent(pixel) != source.Mask[x, y]);
+ }
+
+ public static bool IsAnyPartialTransparency(Image image)
+ {
+ return IsAnyPixel(image, (x, y, pixel)
+ => !IsCompletelyOpaque(pixel) && !IsCompletelyTransparent(pixel));
+ }
+
+ public static bool IsAnyAlphaChannel(Image image)
+ {
+ return IsAnyPixel(image, (x, y, pixel)
+ => !IsCompletelyOpaque(pixel));
+ }
+
+ public static ulong GetNumberOfDistinctColors(Image image, bool includeAlpha)
+ {
+ var colors = new HashSet();
+
+ if (includeAlpha)
+ {
+ ForeachPixel(image, (x, y, pixel)
+ => colors.Add(pixel.PackedValue));
+ }
+ else
+ {
+ ForeachPixel(image, (x, y, pixel)
+ => colors.Add((uint)((pixel.R << 16) | (pixel.G << 8) | pixel.B)));
+ }
+
+ return (uint)colors.Count;
+ }
+
+ private class NumBitsPerChannel
+ {
+ public int R { get; set; } = 1;
+ public int G { get; set; } = 1;
+ public int B { get; set; } = 1;
+ public int A { get; set; } = 1;
+
+ public bool RgbLessThan(int depth)
+ {
+ return R < depth && G < depth && B < depth;
+ }
+ }
+
+ public static int GetMinimumColorDepthForDisplay(Image image)
+ {
+ if (IsAnyPartialTransparency(image))
+ {
+ return 32;
+ }
+
+ if (!IsAnyPixel(image, (x, y, pixel) => !IsBlack(pixel) && !IsWhite(pixel)))
+ {
+ return 1;
+ }
+
+ var bpc = new NumBitsPerChannel();
+ ForeachPixel(image, (x, y, pixel) => UpdateNumBitsPerChannel(pixel, bpc));
+
+ if (bpc.RgbLessThan(2))
+ return 3 * 1;
+ if (bpc.RgbLessThan(3))
+ return 3 * 2;
+ if (bpc.RgbLessThan(5))
+ return 3 * 3;
+
+ return 3 * 4;
+ }
+
+ public static BitmapEncoding GetIdealBitmapEncoding(Image image, bool hasIcoMask)
+ {
+ if (IsAnyPartialTransparency(image))
+ {
+ return BitmapEncoding.Pixel_argb32;
+ }
+
+ var numColors = GetNumberOfDistinctColors(image, !hasIcoMask);
+ if (numColors <= 2)
+ return BitmapEncoding.Pixel_indexed1;
+ if (numColors <= 4)
+ return BitmapEncoding.Pixel_indexed2;
+ if (numColors <= 16)
+ return BitmapEncoding.Pixel_indexed4;
+ if (numColors <= 256)
+ return BitmapEncoding.Pixel_indexed8;
+
+ var bpc = new NumBitsPerChannel();
+ ForeachPixel(image, (x, y, pixel) => UpdateNumBitsPerChannel(pixel, bpc));
+
+ if (hasIcoMask || !IsAnyAlphaChannel(image))
+ {
+ if (bpc.RgbLessThan(6))
+ return BitmapEncoding.Pixel_rgb15;
+ return BitmapEncoding.Pixel_rgb24;
+ }
+
+ return BitmapEncoding.Pixel_argb32;
+ }
+
+ private static void UpdateNumBitsPerChannel(Rgba32 pixel, NumBitsPerChannel bpc)
+ {
+ bpc.R = Math.Max(bpc.R, GetMinimumColorDepth(pixel.R));
+ bpc.G = Math.Max(bpc.G, GetMinimumColorDepth(pixel.G));
+ bpc.B = Math.Max(bpc.B, GetMinimumColorDepth(pixel.B));
+ bpc.A = Math.Max(bpc.A, GetMinimumColorDepth(pixel.A));
+ }
+
+ private static int GetMinimumColorDepth(byte channel)
+ {
+ if (channel == 255)
+ return 1;
+
+ var mask = 255;
+ int depth;
+
+ for (depth = 1; depth < 8; depth++)
+ {
+ if ((channel & mask) == 0)
+ break;
+
+ mask >>= 1;
+ }
+
+ return depth;
+ }
+
+ public static bool[,] CreateMaskFromImage(Image image, bool blackIsTransparent)
+ {
+ var mask = new bool[image.Width, image.Height];
+
+ ForeachPixel(image, (x, y, pixel) =>
+ {
+ if (IsCompletelyTransparent(pixel) ||
+ (blackIsTransparent && IsBlack(pixel)))
+ {
+ mask[x, y] = true;
+ }
+ });
+
+ return mask;
+ }
+
+ public static int GetBitDepthForPixelFormat(BitmapEncoding pixelFormat)
+ {
+ switch (pixelFormat)
+ {
+ case BitmapEncoding.Pixel_indexed1:
+ return 1;
+ case BitmapEncoding.Pixel_indexed2:
+ return 2;
+ case BitmapEncoding.Pixel_indexed4:
+ return 4;
+ case BitmapEncoding.Pixel_indexed8:
+ return 8;
+ case BitmapEncoding.Pixel_rgb15:
+ return 16;
+ case BitmapEncoding.Pixel_rgb24:
+ return 24;
+ case BitmapEncoding.Pixel_0rgb32:
+ return 32;
+ case BitmapEncoding.Pixel_argb32:
+ return 32;
+ }
+ throw new ArgumentException(nameof(pixelFormat));
+ }
+
+ private static void ForeachPixel(Image image, Action action)
+ {
+ var mask = new bool[image.Width, image.Height];
+
+ for (int x = 0; x < image.Width; x++)
+ {
+ for (int y = 0; y < image.Height; y++)
+ {
+ action(x, y, image[x,y]);
+ }
+ }
+ }
+
+ private static bool IsAnyPixel(Image image, Func predicate)
+ {
+ var mask = new bool[image.Width, image.Height];
+
+ for (int x = 0; x < image.Width; x++)
+ {
+ for (int y = 0; y < image.Height; y++)
+ {
+ if (predicate(x, y, image[x, y]))
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static bool IsCompletelyTransparent(Rgba32 pixel)
+ {
+ return pixel.A == 0;
+ }
+
+ private static bool IsCompletelyOpaque(Rgba32 pixel)
+ {
+ return pixel.A == 255;
+ }
+
+ private static bool IsBlack(Rgba32 pixel)
+ {
+ return pixel.R == 0 && pixel.G == 0 && pixel.B == 0;
+ }
+
+ private static bool IsWhite(Rgba32 pixel)
+ {
+ return pixel.R == 255 && pixel.G == 255 && pixel.B == 255;
+ }
+ }
+}
diff --git a/src/Velopack.IcoLib/Codecs/EncodingOptions.cs b/src/Velopack.IcoLib/Codecs/EncodingOptions.cs
new file mode 100644
index 00000000..df3d93f3
--- /dev/null
+++ b/src/Velopack.IcoLib/Codecs/EncodingOptions.cs
@@ -0,0 +1,26 @@
+namespace Ico.Codecs
+{
+ public enum MaskedImagePixelEmitOptions
+ {
+ Compliant,
+ PreserveSource,
+ Loose,
+ }
+
+ public enum StrictnessPolicy
+ {
+ Compliant,
+ PreserveSource,
+ Loose,
+ }
+
+ public enum BestFormatPolicy
+ {
+ PreserveSource,
+ MinimizeStorage,
+ PngLargeImages,
+ AlwaysPng,
+ AlwaysBmp,
+ Inherited,
+ }
+}
diff --git a/src/Velopack.IcoLib/Codecs/FileFormatConstants.cs b/src/Velopack.IcoLib/Codecs/FileFormatConstants.cs
new file mode 100644
index 00000000..d6865bc3
--- /dev/null
+++ b/src/Velopack.IcoLib/Codecs/FileFormatConstants.cs
@@ -0,0 +1,20 @@
+namespace Ico.Codecs
+{
+ public static class FileFormatConstants
+ {
+ public static int MaxIcoFileSize = 20 * 1024 * 1024;
+ internal static ushort _iconMagicHeader = 0;
+ internal static ushort _iconMagicType = 1;
+ internal static ushort _iconMaxEntries = 256;
+ internal static byte _iconEntryReserved = 0;
+ internal static ulong _pngHeader = 0x89504e470d0a1a0a;
+
+ internal static uint _bitmapInfoHeaderSize = 40;
+ internal static uint BI_RGB = 0;
+ internal static uint BI_BITFIELDS = 3;
+
+ internal static ushort _bitmapFileMagic = 0x4d42; // 'BM'
+ internal static ushort _bitmapFileHeaderSize = 14;
+ internal static uint _72dpiInPixelsPerMeter = 2835u;
+ }
+}
diff --git a/src/Velopack.IcoLib/Codecs/IcoDecoder.cs b/src/Velopack.IcoLib/Codecs/IcoDecoder.cs
new file mode 100644
index 00000000..d60bb739
--- /dev/null
+++ b/src/Velopack.IcoLib/Codecs/IcoDecoder.cs
@@ -0,0 +1,110 @@
+using Ico.Binary;
+using Ico.Model;
+using Ico.Validation;
+using System;
+
+namespace Ico.Codecs
+{
+ public static class IcoDecoder
+ {
+ public static void DoFile(byte[] data, ParseContext context, Action processFrame)
+ {
+ var reader = new ByteReader(data, ByteOrder.LittleEndian);
+
+ var idReserved = reader.NextUint16();
+ var idType = reader.NextUint16();
+ var idCount = reader.NextUint16();
+
+ if (idReserved != FileFormatConstants._iconMagicHeader)
+ {
+ throw new InvalidIcoFileException(IcoErrorCode.InvalidIcoHeader_idReserved, $"ICONDIR.idReserved should be {FileFormatConstants._iconMagicHeader}, was {idReserved}.", context);
+ }
+
+ if (idType != FileFormatConstants._iconMagicType)
+ {
+ throw new InvalidIcoFileException(IcoErrorCode.InvalidIconHeader_idType, $"ICONDIR.idType should be {FileFormatConstants._iconMagicType}, was {idType}.", context);
+ }
+
+ if (idCount == 0 || idCount > FileFormatConstants._iconMaxEntries)
+ {
+ throw new InvalidIcoFileException(IcoErrorCode.TooManyFrames, $"ICONDIR.idCount is {idCount}, an implausible value for an ICO file.", context);
+ }
+
+ for (var i = 0u; i < idCount; i++)
+ {
+ context.ImageDirectoryIndex = i;
+ var source = ProcessIcoFrame(reader, context);
+ processFrame(source);
+ }
+
+ context.ImageDirectoryIndex = null;
+ }
+
+ private static IcoFrame ProcessIcoFrame(ByteReader reader, ParseContext context)
+ {
+ var bWidth = reader.NextUint8();
+ var bHeight = reader.NextUint8();
+ var bColorCount = reader.NextUint8();
+ var bReserved = reader.NextUint8();
+ var wPlanes = reader.NextUint16();
+ var wBitCount = reader.NextUint16();
+ var dwBytesInRes = reader.NextUint32();
+ var dwImageOffset = reader.NextUint32();
+
+ if (bWidth != bHeight)
+ {
+ context.Reporter.WarnLine(IcoErrorCode.NotSquare, $"Icon is not square ({bWidth}x{bHeight}).", context.DisplayedPath, context.ImageDirectoryIndex.Value);
+ }
+
+ if (bReserved != FileFormatConstants._iconEntryReserved)
+ {
+ throw new InvalidIcoFileException(IcoErrorCode.InvalidFrameHeader_bReserved, $"ICONDIRECTORY.bReserved should be {FileFormatConstants._iconEntryReserved}, was {bReserved}.", context);
+ }
+
+ if (wPlanes > 1)
+ {
+ throw new InvalidIcoFileException(IcoErrorCode.InvalidFrameHeader_wPlanes, $"ICONDIRECTORY.wPlanes is {wPlanes}. Only single-plane bitmaps are supported.", context);
+ }
+
+ if (dwBytesInRes > int.MaxValue)
+ {
+ throw new InvalidIcoFileException(IcoErrorCode.InvalidFrameHeader_dwBytesInRes, $"ICONDIRECTORY.dwBytesInRes == {dwBytesInRes}, which is unreasonably large.", context);
+ }
+
+ if (dwImageOffset > int.MaxValue)
+ {
+ throw new InvalidIcoFileException(IcoErrorCode.InvalidFrameHeader_dwImageOffset, $"ICONDIRECTORY.dwImageOffset == {dwImageOffset}, which is unreasonably large.", context);
+ }
+
+ var source = new IcoFrame
+ {
+ TotalDiskUsage = dwBytesInRes + /* sizeof(ICONDIRENTRY) */ 16,
+
+ Encoding = new IcoFrameEncoding
+ {
+ ClaimedBitDepth = wBitCount,
+ ClaimedHeight = bHeight > 0 ? bHeight : 256u,
+ ClaimedWidth = bWidth > 0 ? bWidth : 256u,
+ },
+ };
+
+ source.RawData = reader.Data.Slice((int)dwImageOffset, (int)dwBytesInRes).ToArray();
+ var bitmapHeader = new ByteReader(source.RawData, ByteOrder.LittleEndian);
+
+ var signature = bitmapHeader.NextUint64();
+ bitmapHeader.SeekOffset = 0;
+
+ if (PngDecoder.IsProbablyPngFile(ByteOrderConverter.To(ByteOrder.NetworkEndian, signature)))
+ {
+ PngDecoder.DoPngEntry(bitmapHeader, context, source);
+ }
+ else
+ {
+ BmpDecoder.DoBitmapEntry(bitmapHeader, context, source);
+ }
+
+ return source;
+ }
+
+ }
+}
diff --git a/src/Velopack.IcoLib/Codecs/IcoEncoder.cs b/src/Velopack.IcoLib/Codecs/IcoEncoder.cs
new file mode 100644
index 00000000..0256dc9f
--- /dev/null
+++ b/src/Velopack.IcoLib/Codecs/IcoEncoder.cs
@@ -0,0 +1,53 @@
+using Ico.Binary;
+using Ico.Model;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ico.Codecs
+{
+ public static class IcoEncoder
+ {
+ public static void EmitIco(string outputPath, ParseContext context)
+ {
+ var writer = new ByteWriter(ByteOrder.LittleEndian);
+
+ writer.AddUint16(FileFormatConstants._iconMagicHeader);
+ writer.AddUint16(FileFormatConstants._iconMagicType);
+ writer.AddUint16((ushort)context.GeneratedFrames.Count);
+
+ var offsets = new Queue();
+
+ foreach (var frame in context.GeneratedFrames)
+ {
+ var width = frame.Encoding.ClaimedWidth;
+ var height = frame.Encoding.ClaimedHeight;
+ var bitDepth = frame.Encoding.ClaimedBitDepth;
+
+ writer.AddUint8((byte)(width >= 256 ? 0 : width)); // bWidth
+ writer.AddUint8((byte)(height >= 256 ? 0 : height)); // bHeight
+ writer.AddUint8((byte)(bitDepth < 8 ? 1u << (int)bitDepth : 0)); // bColorCount
+ writer.AddUint8(0); // bReserved
+ writer.AddUint16(1); // wPlanes
+ writer.AddUint16((ushort)bitDepth); // wBitCount
+ writer.AddUint32((uint)frame.RawData.Length); // dwBytesInRes
+
+ offsets.Enqueue((uint)writer.SeekOffset);
+ writer.AddUint32(0); // dwImageOffset (will fix later)
+ }
+
+ foreach (var frame in context.GeneratedFrames)
+ {
+ var currentOffset = writer.SeekOffset;
+ writer.SeekOffset = (int)offsets.Dequeue();
+ writer.AddUint32((uint)currentOffset); // dwImageOffset
+ writer.SeekOffset = currentOffset;
+
+ writer.AddBlob(frame.RawData);
+
+ frame.TotalDiskUsage = (uint)frame.RawData.Length + /* sizeof(ICONDIRENTRY) */ 16;
+ }
+
+ File.WriteAllBytes(outputPath, writer.Data.ToArray());
+ }
+ }
+}
diff --git a/src/Velopack.IcoLib/Codecs/PngDecoder.cs b/src/Velopack.IcoLib/Codecs/PngDecoder.cs
new file mode 100644
index 00000000..9cc95bce
--- /dev/null
+++ b/src/Velopack.IcoLib/Codecs/PngDecoder.cs
@@ -0,0 +1,157 @@
+using Ico.Binary;
+using Ico.Model;
+using Ico.Validation;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
+using System;
+using System.IO;
+using System.Threading;
+
+namespace Ico.Codecs
+{
+ public static class PngDecoder
+ {
+ private const int _ihdrChunkName = 0x49484452; // "IHDR"
+
+ public static bool IsProbablyPngFile(ulong first8Bytes)
+ {
+ return FileFormatConstants._pngHeader == first8Bytes;
+ }
+
+ public static void DoPngEntry(ByteReader bitmapHeader, ParseContext context, IcoFrame source)
+ {
+ if (source.Encoding.ClaimedBitDepth != 32)
+ {
+ context.Reporter.WarnLine(IcoErrorCode.PngNot32Bit, $"PNG-encoded image with bit depth {source.Encoding.ClaimedBitDepth} (expected 32).", context.DisplayedPath, context.ImageDirectoryIndex.Value);
+ }
+
+ using (var stream = new MemoryStream(bitmapHeader.Data.ToArray()))
+ {
+ var decoder = new SixLabors.ImageSharp.Formats.Png.PngDecoder();
+ source.CookedData = decoder.Decode(new Configuration(), stream, CancellationToken.None);
+ }
+
+ source.Encoding.Type = IcoEncodingType.Png;
+ source.Encoding.PixelFormat = BmpUtil.IsAnyPartialTransparency(source.CookedData) ? BitmapEncoding.Pixel_argb32 : BitmapEncoding.Pixel_0rgb32;
+ source.Mask = GenerateMaskFromAlpha(source.CookedData);
+
+ // Conservatively assume that the output wouldn't have used palette trimming, if it had been a bmp frame.
+ if (source.Encoding.ClaimedBitDepth < 16)
+ {
+ source.Encoding.PaletteSize = 1u << (int)source.Encoding.ClaimedBitDepth;
+ }
+
+ var encoding = GetPngFileEncoding(bitmapHeader.Data);
+ if (encoding.ColorType != PngColorType.RGBA)
+ {
+ context.Reporter.WarnLine(IcoErrorCode.PngNotRGBA32, $"ICO files require the embedded PNG image to be encoded in RGBA32 format; this is {encoding.ColorType}", context.DisplayedPath, context.ImageDirectoryIndex.Value);
+ }
+ else if (encoding.BitsPerChannel != 8)
+ {
+ context.Reporter.WarnLine(IcoErrorCode.PngNotRGBA32, $"ICO files require the embedded PNG image to be encoded in RGBA32 format; this is RGBA{encoding.BitsPerChannel * 4}", context.DisplayedPath, context.ImageDirectoryIndex.Value);
+ }
+
+ uint numChannels = 0;
+ switch (encoding.ColorType)
+ {
+ case PngColorType.Grayscale:
+ numChannels = 1;
+ break;
+ case PngColorType.RGB:
+ numChannels = 3;
+ break;
+ case PngColorType.GrayscaleAlpha:
+ numChannels = 2;
+ break;
+ case PngColorType.RGBA:
+ numChannels = 4;
+ break;
+ case PngColorType.Palette:
+ default:
+ break;
+ }
+
+ source.Encoding.ActualHeight = encoding.Height;
+ source.Encoding.ActualWidth = encoding.Width;
+ source.Encoding.ActualBitDepth = encoding.BitsPerChannel * numChannels;
+ }
+
+ public static PngFileEncoding GetPngFileEncoding(Memory data)
+ {
+ var reader = new ByteReader(data, Ico.Binary.ByteOrder.NetworkEndian);
+ if (FileFormatConstants._pngHeader != reader.NextUint64())
+ {
+ throw new InvalidPngFileException(IcoErrorCode.NotPng, $"Data stream does not begin with the PNG magic constant");
+ }
+
+ var chunkLength = reader.NextUint32();
+ var chunkType = reader.NextUint32();
+
+ if (chunkType != _ihdrChunkName)
+ {
+ throw new InvalidPngFileException(IcoErrorCode.PngBadIHDR, $"PNG file should begin with IHDR chunk; found {chunkType} instead");
+ }
+
+ if (chunkLength < 13)
+ {
+ throw new InvalidPngFileException(IcoErrorCode.PngBadIHDR, $"IHDR chunk is invalid length {chunkLength}; expected at least 13 bytes");
+ }
+
+ var result = new PngFileEncoding
+ {
+ Width = reader.NextUint32(),
+ Height = reader.NextUint32(),
+ BitsPerChannel = reader.NextUint8(),
+ ColorType = (PngColorType)reader.NextUint8(),
+ };
+
+ if (result.Width == 0 || result.Height == 0)
+ {
+ throw new InvalidPngFileException(IcoErrorCode.PngIllegalInputDimensions, $"Illegal Width x Height of {result.Width} x {result.Height}");
+ }
+
+ switch (result.BitsPerChannel)
+ {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ case 16:
+ break;
+ default:
+ throw new InvalidPngFileException(IcoErrorCode.PngIllegalInputDepth, $"Illegal bits per color channel / palette entry of {result.BitsPerChannel}");
+ }
+
+ switch (result.ColorType)
+ {
+ case PngColorType.Grayscale:
+ case PngColorType.RGB:
+ case PngColorType.Palette:
+ case PngColorType.GrayscaleAlpha:
+ case PngColorType.RGBA:
+ break;
+ default:
+ throw new InvalidPngFileException(IcoErrorCode.PngIllegalColorType, $"Illegal color type {result.ColorType}");
+ }
+
+ return result;
+ }
+
+ private static bool[,] GenerateMaskFromAlpha(Image image)
+ {
+ var mask = new bool[image.Width, image.Height];
+
+ for (var x = 0; x < image.Width; x++)
+ {
+ for (var y = 0; y < image.Height; y++)
+ {
+ var alpha = image[x, y].A;
+ mask[x, y] = alpha == 0;
+ }
+ }
+
+ return mask;
+ }
+
+ }
+}
diff --git a/src/Velopack.IcoLib/Host/ExceptionWrapper.cs b/src/Velopack.IcoLib/Host/ExceptionWrapper.cs
new file mode 100644
index 00000000..715bbd2c
--- /dev/null
+++ b/src/Velopack.IcoLib/Host/ExceptionWrapper.cs
@@ -0,0 +1,40 @@
+using Ico.Model;
+using Ico.Validation;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Ico.Host
+{
+ public static class ExceptionWrapper
+ {
+ public static void Try(Action action, ParseContext context, IErrorReporter reporter)
+ {
+ try
+ {
+ action();
+ }
+ catch (InvalidIcoFileException e)
+ {
+ var frame = (e.Context?.ImageDirectoryIndex);
+
+ if (frame != null)
+ {
+ reporter.ErrorLine(e.ErrorCode, e.Message, context.DisplayedPath, context.ImageDirectoryIndex.Value);
+ }
+ else
+ {
+ reporter.ErrorLine(e.ErrorCode, e.Message, context.DisplayedPath);
+ }
+ }
+ catch (InvalidPngFileException e) when (e.ErrorCode != IcoErrorCode.NoError)
+ {
+ reporter.ErrorLine(e.ErrorCode, e.Message);
+ }
+ catch (Exception e)
+ {
+ reporter.ErrorLine(IcoErrorCode.NoError, e.ToString(), context.DisplayedPath);
+ }
+ }
+ }
+}
diff --git a/src/Velopack.IcoLib/Host/IErrorReporter.cs b/src/Velopack.IcoLib/Host/IErrorReporter.cs
new file mode 100644
index 00000000..f333aeb6
--- /dev/null
+++ b/src/Velopack.IcoLib/Host/IErrorReporter.cs
@@ -0,0 +1,23 @@
+using Ico.Validation;
+
+namespace Ico.Host
+{
+ public interface IErrorReporter
+ {
+ void ErrorLine(IcoErrorCode errorCode, string message);
+
+ void ErrorLine(IcoErrorCode errorCode, string message, string fileName);
+
+ void ErrorLine(IcoErrorCode errorCode, string message, string fileName, uint frameNumber);
+
+ void WarnLine(IcoErrorCode errorCode, string message);
+
+ void WarnLine(IcoErrorCode errorCode, string message, string fileName);
+
+ void WarnLine(IcoErrorCode errorCode, string message, string fileName, uint frameNumber);
+
+ void InfoLine(string message);
+
+ void VerboseLine(string message);
+ }
+}
diff --git a/src/Velopack.IcoLib/Model/IcoFrame.cs b/src/Velopack.IcoLib/Model/IcoFrame.cs
new file mode 100644
index 00000000..6d59f93d
--- /dev/null
+++ b/src/Velopack.IcoLib/Model/IcoFrame.cs
@@ -0,0 +1,18 @@
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace Ico.Model
+{
+ public class IcoFrame
+ {
+ public IcoFrameEncoding Encoding { get; set; }
+
+ public byte[] RawData { get; set; }
+
+ public Image CookedData { get; set; }
+
+ public bool[,] Mask { get; set; }
+
+ public uint TotalDiskUsage { get; set; }
+ }
+}
diff --git a/src/Velopack.IcoLib/Model/IcoFrameEncoding.cs b/src/Velopack.IcoLib/Model/IcoFrameEncoding.cs
new file mode 100644
index 00000000..7136f9ae
--- /dev/null
+++ b/src/Velopack.IcoLib/Model/IcoFrameEncoding.cs
@@ -0,0 +1,45 @@
+namespace Ico.Model
+{
+ public enum IcoEncodingType
+ {
+ Bitmap,
+ Png,
+ }
+
+ public enum BitmapEncoding
+ {
+ Pixel_indexed1,
+ Pixel_indexed2,
+ Pixel_indexed4,
+ Pixel_indexed8,
+ Pixel_rgb15,
+ Pixel_rgb24,
+ Pixel_0rgb32,
+ Pixel_argb32,
+ }
+
+ public class IcoFrameEncoding
+ {
+ public IcoEncodingType Type { get; set; }
+
+ // In the ICO header
+
+ public uint ClaimedBitDepth { get; set; }
+
+ public uint ClaimedWidth { get; set; }
+
+ public uint ClaimedHeight { get; set; }
+
+ public uint ActualWidth { get; set; }
+
+ public uint ActualHeight { get; set; }
+
+ // Bitmap only
+
+ public BitmapEncoding PixelFormat { get; set; }
+
+ public uint ActualBitDepth { get; set; }
+
+ public uint PaletteSize { get; set; }
+ }
+}
diff --git a/src/Velopack.IcoLib/Model/ParseContext.cs b/src/Velopack.IcoLib/Model/ParseContext.cs
new file mode 100644
index 00000000..80acece8
--- /dev/null
+++ b/src/Velopack.IcoLib/Model/ParseContext.cs
@@ -0,0 +1,29 @@
+using Ico.Codecs;
+using Ico.Host;
+using Ico.Validation;
+using SixLabors.ImageSharp.Formats.Png;
+using System.Collections.Generic;
+
+namespace Ico.Model
+{
+ public class ParseContext
+ {
+ public string FullPath { get; set; }
+
+ public string DisplayedPath { get; set; }
+
+ public uint? ImageDirectoryIndex { get; set; }
+
+ public List GeneratedFrames { get; set; }
+
+ public PngEncoder PngEncoder { get; set; }
+
+ public StrictnessPolicy MaskedImagePixelEmitOptions { get; set; }
+
+ public StrictnessPolicy AllowPaletteTruncation { get; set; }
+
+ public IErrorReporter Reporter { get; set; }
+
+ public IcoErrorCode LastEncodeError { get; set; }
+ }
+}
diff --git a/src/Velopack.IcoLib/Model/PngFileEncoding.cs b/src/Velopack.IcoLib/Model/PngFileEncoding.cs
new file mode 100644
index 00000000..ddd1e243
--- /dev/null
+++ b/src/Velopack.IcoLib/Model/PngFileEncoding.cs
@@ -0,0 +1,23 @@
+namespace Ico.Model
+{
+ public enum PngColorType
+ {
+ Grayscale = 0,
+ RGB = 2,
+ Palette = 3,
+ GrayscaleAlpha = 4,
+ RGBA = 6,
+ }
+
+ public class PngFileEncoding
+ {
+ // 1, 2, 4, 8, or 16
+ public uint BitsPerChannel { get; set; }
+
+ public PngColorType ColorType { get; set; }
+
+ public uint Width { get; set; }
+
+ public uint Height { get; set; }
+ }
+}
diff --git a/src/Velopack.IcoLib/README.md b/src/Velopack.IcoLib/README.md
new file mode 100644
index 00000000..492fe8b1
--- /dev/null
+++ b/src/Velopack.IcoLib/README.md
@@ -0,0 +1,4 @@
+# IcoLib
+IcoLib is a library for creating, extracting, and managing ICO files. It is written in C# and is compatible with .NET Standard 2.0.
+
+Find the original project here https://github.com/jtippet/IcoTools
\ No newline at end of file
diff --git a/src/Velopack.IcoLib/Validation/IcoErrorCode.cs b/src/Velopack.IcoLib/Validation/IcoErrorCode.cs
new file mode 100644
index 00000000..7defc29d
--- /dev/null
+++ b/src/Velopack.IcoLib/Validation/IcoErrorCode.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Ico.Validation
+{
+ public enum IcoErrorCode
+ {
+ NoError = 0,
+
+ // 100 - 199: invalid ICO file format
+ InvalidIcoHeader_idReserved = 100,
+ InvalidIconHeader_idType = 101,
+ InvalidFrameHeader_bReserved = 110,
+ InvalidFrameHeader_wPlanes = 111,
+ InvalidFrameHeader_dwBytesInRes = 112,
+ InvalidFrameHeader_dwImageOffset = 113,
+ TooManyFrames = 120,
+ InvalidBitapInfoHeader_ciSize = 130,
+ InvalidBitapInfoHeader_biXPelsPerMeter = 131,
+ InvalidBitapInfoHeader_biYPelsPerMeter = 132,
+ InvalidBitapInfoHeader_biBitCount = 133,
+ InvalidBitapInfoHeader_biClrUsed = 134,
+
+ // 200 - 299: nonstandard or nonportable ICO file format
+ ZeroFrames = 200,
+ DuplicateFrameTypes = 201,
+ MismatchedHeight = 210,
+ MismatchedWidth = 211,
+ NonzeroAlpha = 220,
+ MaskedPixelWithColor = 221,
+ NoMaskedPixels = 222,
+ IndexedColorOutOfBounds = 230,
+ UndersizedColorTable = 231,
+ PngNot32Bit = 240,
+ PngNotRGBA32 = 241,
+ NotSquare = 256,
+
+ // 300 - 399: tool limitations
+ FileTooLarge = 300,
+ BitfieldCompressionNotSupported = 310,
+ BitmapCompressionNotSupported = 311,
+
+ // 400 - 499: usage or environmental error
+ FileExists = 403,
+ FileNotFound = 404,
+ UnsupportedCodec = 410,
+ UnsupportedBitmapEncoding = 411,
+ OnlySupportedOnBitmaps = 412,
+ BitmapMaskWrongDimensions = 420,
+ BitampMaskWrongColors = 421,
+ InvalidFrameIndex = 430,
+ TooManyColorsForBitDepth = 431,
+ NotPng = 440,
+ PngBadIHDR = 441,
+ PngIllegalInputDimensions = 445,
+ PngIllegalInputDepth = 446,
+ PngIllegalColorType = 447,
+ }
+}
diff --git a/src/Velopack.IcoLib/Validation/InvalidIcoFileException.cs b/src/Velopack.IcoLib/Validation/InvalidIcoFileException.cs
new file mode 100644
index 00000000..6029b185
--- /dev/null
+++ b/src/Velopack.IcoLib/Validation/InvalidIcoFileException.cs
@@ -0,0 +1,56 @@
+using Ico.Model;
+using System;
+using System.Runtime.Serialization;
+
+namespace Ico.Validation
+{
+ [Serializable]
+ public class InvalidIcoFileException : Exception
+ {
+ public ParseContext Context { get; private set; }
+
+ public IcoErrorCode ErrorCode { get; private set; }
+
+ public InvalidIcoFileException()
+ {
+ }
+
+ public InvalidIcoFileException(string message) : base(message)
+ {
+ }
+
+ public InvalidIcoFileException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ public InvalidIcoFileException(IcoErrorCode errorCode, string message, ParseContext context) : this(message)
+ {
+ ErrorCode = errorCode;
+ Context = context;
+ }
+
+ protected InvalidIcoFileException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+
+ public override string ToString()
+ {
+ if (Context != null)
+ {
+ if (Context.DisplayedPath != null && Context.ImageDirectoryIndex == null)
+ {
+ return base.ToString() + $"\nFile: \"{Context.DisplayedPath}\"";
+ }
+ else if (Context.DisplayedPath != null && Context.ImageDirectoryIndex == null)
+ {
+ return base.ToString() + $"\nFile: \"{Context.DisplayedPath}\"";
+ }
+ else if (Context.DisplayedPath != null && Context.ImageDirectoryIndex.HasValue)
+ {
+ return base.ToString() + $"\nFile: \"{Context.DisplayedPath}\"\nImage directory index: #{Context.ImageDirectoryIndex.Value}";
+ }
+ }
+ return base.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Velopack.IcoLib/Validation/InvalidPngFileException.cs b/src/Velopack.IcoLib/Validation/InvalidPngFileException.cs
new file mode 100644
index 00000000..74320e9b
--- /dev/null
+++ b/src/Velopack.IcoLib/Validation/InvalidPngFileException.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace Ico.Validation
+{
+ public class InvalidPngFileException : Exception
+ {
+ public IcoErrorCode ErrorCode { get; private set; }
+
+ public InvalidPngFileException()
+ {
+ }
+
+ public InvalidPngFileException(string message) : base(message)
+ {
+ }
+
+ public InvalidPngFileException(IcoErrorCode errorCode, string message) : base(message)
+ {
+ ErrorCode = errorCode;
+ }
+
+ public InvalidPngFileException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected InvalidPngFileException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+
+ }
+}
diff --git a/src/Velopack.IcoLib/Velopack.IcoLib.csproj b/src/Velopack.IcoLib/Velopack.IcoLib.csproj
new file mode 100644
index 00000000..87a42e91
--- /dev/null
+++ b/src/Velopack.IcoLib/Velopack.IcoLib.csproj
@@ -0,0 +1,17 @@
+
+
+
+ netstandard2.0
+ Ico
+ 7.3
+ 1.1.1
+ Jeffrey Tippet
+ IcoTools
+ Copyright 2019, Jeffrey Tippet. All rights reserved.
+
+
+
+
+
+
+
diff --git a/src/Velopack.IcoLib/packages.lock.json b/src/Velopack.IcoLib/packages.lock.json
new file mode 100644
index 00000000..e2d3df53
--- /dev/null
+++ b/src/Velopack.IcoLib/packages.lock.json
@@ -0,0 +1,93 @@
+{
+ "version": 1,
+ "dependencies": {
+ ".NETStandard,Version=v2.0": {
+ "Microsoft.SourceLink.GitHub": {
+ "type": "Direct",
+ "requested": "[8.0.0, )",
+ "resolved": "8.0.0",
+ "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
+ "dependencies": {
+ "Microsoft.Build.Tasks.Git": "8.0.0",
+ "Microsoft.SourceLink.Common": "8.0.0"
+ }
+ },
+ "Nerdbank.GitVersioning": {
+ "type": "Direct",
+ "requested": "[3.6.*, )",
+ "resolved": "3.6.139",
+ "contentHash": "rq0Ub/Jik7PtMtZtLn0tHuJ01Yt36RQ+eeBe+S7qnJ/EFOX6D4T9zuYD3vQPYKGI6Ro4t2iWgFm3fGDgjBrMfg=="
+ },
+ "NETStandard.Library": {
+ "type": "Direct",
+ "requested": "[2.0.3, )",
+ "resolved": "2.0.3",
+ "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0"
+ }
+ },
+ "SixLabors.ImageSharp": {
+ "type": "Direct",
+ "requested": "[2.1.8, )",
+ "resolved": "2.1.8",
+ "contentHash": "ML1++vactR5xMW36wHA2nAX3vc6VpR62qYnpopQdLNGbP8BAJ/ckv1IrGIIFRUEQ0JS9EgWr1y1gEa/81f+HaA==",
+ "dependencies": {
+ "System.Buffers": "4.5.1",
+ "System.Memory": "4.5.4",
+ "System.Numerics.Vectors": "4.5.0",
+ "System.Runtime.CompilerServices.Unsafe": "5.0.0",
+ "System.Text.Encoding.CodePages": "5.0.0"
+ }
+ },
+ "Microsoft.Build.Tasks.Git": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
+ },
+ "Microsoft.NETCore.Platforms": {
+ "type": "Transitive",
+ "resolved": "1.1.0",
+ "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
+ },
+ "Microsoft.SourceLink.Common": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
+ },
+ "System.Buffers": {
+ "type": "Transitive",
+ "resolved": "4.5.1",
+ "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
+ },
+ "System.Memory": {
+ "type": "Transitive",
+ "resolved": "4.5.4",
+ "contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==",
+ "dependencies": {
+ "System.Buffers": "4.5.1",
+ "System.Numerics.Vectors": "4.4.0",
+ "System.Runtime.CompilerServices.Unsafe": "4.5.3"
+ }
+ },
+ "System.Numerics.Vectors": {
+ "type": "Transitive",
+ "resolved": "4.5.0",
+ "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ=="
+ },
+ "System.Runtime.CompilerServices.Unsafe": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "ZD9TMpsmYJLrxbbmdvhwt9YEgG5WntEnZ/d1eH8JBX9LBp+Ju8BSBhUGbZMNVHHomWo2KVImJhTDl2hIgw/6MA=="
+ },
+ "System.Text.Encoding.CodePages": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==",
+ "dependencies": {
+ "System.Runtime.CompilerServices.Unsafe": "5.0.0"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Velopack.Packaging.HostModel/AppHost/AppHostExceptions.cs b/src/Velopack.Packaging.HostModel/AppHost/AppHostExceptions.cs
deleted file mode 100644
index 31719d74..00000000
--- a/src/Velopack.Packaging.HostModel/AppHost/AppHostExceptions.cs
+++ /dev/null
@@ -1,90 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.NET.HostModel.AppHost
-{
- ///
- /// An instance of this exception is thrown when an AppHost binary update
- /// fails due to known user errors.
- ///
- public class AppHostUpdateException : Exception
- {
- internal AppHostUpdateException(string message = null)
- : base(message)
- {
- }
- }
-
- ///
- /// The application host executable cannot be customized because adding resources requires
- /// that the build be performed on Windows (excluding Nano Server).
- ///
- public sealed class AppHostCustomizationUnsupportedOSException : AppHostUpdateException
- {
- internal AppHostCustomizationUnsupportedOSException()
- {
- }
- }
-
- ///
- /// The MachO application host executable cannot be customized because
- /// it was not in the expected format
- ///
- public sealed class AppHostMachOFormatException : AppHostUpdateException
- {
- public readonly MachOFormatError Error;
-
- internal AppHostMachOFormatException(MachOFormatError error)
- {
- Error = error;
- }
- }
-
- ///
- /// Unable to use the input file as application host executable because it's not a
- /// Windows executable for the CUI (Console) subsystem.
- ///
- public sealed class AppHostNotCUIException : AppHostUpdateException
- {
- internal AppHostNotCUIException()
- {
- }
- }
-
- ///
- /// Unable to use the input file as an application host executable
- /// because it's not a Windows PE file
- ///
- public sealed class AppHostNotPEFileException : AppHostUpdateException
- {
- internal AppHostNotPEFileException()
- {
- }
- }
-
- ///
- /// Unable to sign the apphost binary.
- ///
- public sealed class AppHostSigningException : AppHostUpdateException
- {
- public readonly int ExitCode;
-
- internal AppHostSigningException(int exitCode, string signingErrorMessage)
- : base(signingErrorMessage)
- {
- }
- }
-
- ///
- /// Given app file name is longer than 1024 bytes
- ///
- public sealed class AppNameTooLongException : AppHostUpdateException
- {
- public string LongName { get; }
-
- internal AppNameTooLongException(string name)
- {
- LongName = name;
- }
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/AppHost/BinaryUtils.cs b/src/Velopack.Packaging.HostModel/AppHost/BinaryUtils.cs
deleted file mode 100644
index 54c3ded6..00000000
--- a/src/Velopack.Packaging.HostModel/AppHost/BinaryUtils.cs
+++ /dev/null
@@ -1,168 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.IO.MemoryMappedFiles;
-
-namespace Microsoft.NET.HostModel.AppHost
-{
- public static class BinaryUtils
- {
- internal static unsafe void SearchAndReplace(
- MemoryMappedViewAccessor accessor,
- byte[] searchPattern,
- byte[] patternToReplace,
- bool pad0s = true)
- {
- byte* pointer = null;
-
- try {
- accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
- byte* bytes = pointer + accessor.PointerOffset;
-
- int position = KMPSearch(searchPattern, bytes, accessor.Capacity);
- if (position < 0) {
- throw new PlaceHolderNotFoundInAppHostException(searchPattern);
- }
-
- accessor.WriteArray(
- position: position,
- array: patternToReplace,
- offset: 0,
- count: patternToReplace.Length);
-
- if (pad0s) {
- Pad0(searchPattern, patternToReplace, bytes, position);
- }
- } finally {
- if (pointer != null) {
- accessor.SafeMemoryMappedViewHandle.ReleasePointer();
- }
- }
- }
-
- private static unsafe void Pad0(byte[] searchPattern, byte[] patternToReplace, byte* bytes, int offset)
- {
- if (patternToReplace.Length < searchPattern.Length) {
- for (int i = patternToReplace.Length; i < searchPattern.Length; i++) {
- bytes[i + offset] = 0x0;
- }
- }
- }
-
- public static unsafe void SearchAndReplace(
- string filePath,
- byte[] searchPattern,
- byte[] patternToReplace,
- bool pad0s = true)
- {
- using (var mappedFile = MemoryMappedFile.CreateFromFile(filePath)) {
- using (var accessor = mappedFile.CreateViewAccessor()) {
- SearchAndReplace(accessor, searchPattern, patternToReplace, pad0s);
- }
- }
- }
-
- public static unsafe int SearchInFile(MemoryMappedViewAccessor accessor, byte[] searchPattern)
- {
- var safeBuffer = accessor.SafeMemoryMappedViewHandle;
- return KMPSearch(searchPattern, (byte*) safeBuffer.DangerousGetHandle(), (int) safeBuffer.ByteLength);
- }
-
- public static unsafe int SearchInFile(string filePath, byte[] searchPattern)
- {
- using (var mappedFile = MemoryMappedFile.CreateFromFile(filePath)) {
- using (var accessor = mappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read)) {
- return SearchInFile(accessor, searchPattern);
- }
- }
- }
-
- // See: https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm
- private static int[] ComputeKMPFailureFunction(byte[] pattern)
- {
- int[] table = new int[pattern.Length];
- if (pattern.Length >= 1) {
- table[0] = -1;
- }
- if (pattern.Length >= 2) {
- table[1] = 0;
- }
-
- int pos = 2;
- int cnd = 0;
- while (pos < pattern.Length) {
- if (pattern[pos - 1] == pattern[cnd]) {
- table[pos] = cnd + 1;
- cnd++;
- pos++;
- } else if (cnd > 0) {
- cnd = table[cnd];
- } else {
- table[pos] = 0;
- pos++;
- }
- }
- return table;
- }
-
- // See: https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm
- private static unsafe int KMPSearch(byte[] pattern, byte* bytes, long bytesLength)
- {
- int m = 0;
- int i = 0;
- int[] table = ComputeKMPFailureFunction(pattern);
-
- while (m + i < bytesLength) {
- if (pattern[i] == bytes[m + i]) {
- if (i == pattern.Length - 1) {
- return m;
- }
- i++;
- } else {
- if (table[i] > -1) {
- m = m + i - table[i];
- i = table[i];
- } else {
- m++;
- i = 0;
- }
- }
- }
-
- return -1;
- }
-
- public static void CopyFile(string sourcePath, string destinationPath)
- {
- var destinationDirectory = new FileInfo(destinationPath).Directory.FullName;
- if (!Directory.Exists(destinationDirectory)) {
- Directory.CreateDirectory(destinationDirectory);
- }
-
- // Copy file to destination path so it inherits the same attributes/permissions.
- File.Copy(sourcePath, destinationPath, overwrite: true);
- }
-
- internal static void WriteToStream(MemoryMappedViewAccessor sourceViewAccessor, FileStream fileStream, long length)
- {
- int pos = 0;
- int bufSize = 16384; //16K
-
- byte[] buf = new byte[bufSize];
- length = Math.Min(length, sourceViewAccessor.Capacity);
- do {
- int bytesRequested = Math.Min((int) length - pos, bufSize);
- if (bytesRequested <= 0) {
- break;
- }
-
- int bytesRead = sourceViewAccessor.ReadArray(pos, buf, 0, bytesRequested);
- if (bytesRead > 0) {
- fileStream.Write(buf, 0, bytesRead);
- pos += bytesRead;
- }
- }
- while (true);
- }
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/AppHost/ElfUtils.cs b/src/Velopack.Packaging.HostModel/AppHost/ElfUtils.cs
deleted file mode 100644
index b2dce3c2..00000000
--- a/src/Velopack.Packaging.HostModel/AppHost/ElfUtils.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.NET.HostModel.AppHost
-{
- internal static class ElfUtils
- {
- // The Linux Headers are copied from elf.h
-
-#pragma warning disable 0649
- private struct ElfHeader
- {
- private byte EI_MAG0;
- private byte EI_MAG1;
- private byte EI_MAG2;
- private byte EI_MAG3;
-
- public bool IsValid()
- {
- return EI_MAG0 == 0x7f &&
- EI_MAG1 == 0x45 &&
- EI_MAG2 == 0x4C &&
- EI_MAG3 == 0x46;
- }
-
-
- }
-#pragma warning restore 0649
-
- public static bool IsElfImage(string filePath)
- {
- using (BinaryReader reader = new BinaryReader(File.OpenRead(filePath)))
- {
- if (reader.BaseStream.Length < 16) // EI_NIDENT = 16
- {
- return false;
- }
-
- byte[] eIdent = reader.ReadBytes(4);
-
- // Check that the first four bytes are 0x7f, 'E', 'L', 'F'
- return eIdent[0] == 0x7f &&
- eIdent[1] == 0x45 &&
- eIdent[2] == 0x4C &&
- eIdent[3] == 0x46;
- }
- }
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/AppHost/HResultException.cs b/src/Velopack.Packaging.HostModel/AppHost/HResultException.cs
deleted file mode 100644
index 1e2db783..00000000
--- a/src/Velopack.Packaging.HostModel/AppHost/HResultException.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.NET.HostModel
-{
- ///
- /// Represents an exception thrown because of a Win32 error
- ///
- public class HResultException : Exception
- {
- public readonly int Win32HResult;
- public HResultException(int hResult) : base(hResult.ToString("X4"))
- {
- Win32HResult = hResult;
- }
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/AppHost/HostWriter.cs b/src/Velopack.Packaging.HostModel/AppHost/HostWriter.cs
deleted file mode 100644
index 7a8183e0..00000000
--- a/src/Velopack.Packaging.HostModel/AppHost/HostWriter.cs
+++ /dev/null
@@ -1,277 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.ComponentModel;
-using System.IO.MemoryMappedFiles;
-using System.Runtime.InteropServices;
-using System.Text;
-
-namespace Microsoft.NET.HostModel.AppHost
-{
- ///
- /// Embeds the App Name into the AppHost.exe
- /// If an apphost is a single-file bundle, updates the location of the bundle headers.
- ///
- public static class HostWriter
- {
- ///
- /// hash value embedded in default apphost executable in a place where the path to the app binary should be stored.
- ///
- private const string AppBinaryPathPlaceholder = "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2";
- private static readonly byte[] AppBinaryPathPlaceholderSearchValue = Encoding.UTF8.GetBytes(AppBinaryPathPlaceholder);
-
- ///
- /// Create an AppHost with embedded configuration of app binary location
- ///
- /// The path of Apphost template, which has the place holder
- /// The destination path for desired location to place, including the file name
- /// Full path to app binary or relative path to the result apphost file
- /// Specify whether to set the subsystem to GUI. Only valid for PE apphosts.
- /// Path to the intermediate assembly, used for copying resources to PE apphosts.
- /// Sign the app binary using codesign with an anonymous certificate.
- public static void CreateAppHost(
- string appHostSourceFilePath,
- string appHostDestinationFilePath,
- string appBinaryFilePath,
- bool windowsGraphicalUserInterface = false,
- string assemblyToCopyResorcesFrom = null,
- bool enableMacOSCodeSign = false)
- {
- var bytesToWrite = Encoding.UTF8.GetBytes(appBinaryFilePath);
- if (bytesToWrite.Length > 1024)
- {
- throw new AppNameTooLongException(appBinaryFilePath);
- }
-
- bool appHostIsPEImage = false;
-
- void RewriteAppHost(MemoryMappedViewAccessor accessor)
- {
- // Re-write the destination apphost with the proper contents.
- BinaryUtils.SearchAndReplace(accessor, AppBinaryPathPlaceholderSearchValue, bytesToWrite);
-
- appHostIsPEImage = PEUtils.IsPEImage(accessor);
-
- if (windowsGraphicalUserInterface)
- {
- if (!appHostIsPEImage)
- {
- throw new AppHostNotPEFileException();
- }
-
- PEUtils.SetWindowsGraphicalUserInterfaceBit(accessor);
- }
- }
-
- void UpdateResources()
- {
- if (assemblyToCopyResorcesFrom != null && appHostIsPEImage)
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && ResourceUpdater.IsSupportedOS())
- {
- // Copy resources from managed dll to the apphost
- new ResourceUpdater(appHostDestinationFilePath)
- .AddResourcesFromPEImage(assemblyToCopyResorcesFrom)
- .Update();
- }
- else
- {
- throw new AppHostCustomizationUnsupportedOSException();
- }
- }
- }
-
- try
- {
- RetryUtil.RetryOnIOError(() =>
- {
- FileStream appHostSourceStream = null;
- MemoryMappedFile memoryMappedFile = null;
- MemoryMappedViewAccessor memoryMappedViewAccessor = null;
- try
- {
- // Open the source host file.
- appHostSourceStream = new FileStream(appHostSourceFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
- memoryMappedFile = MemoryMappedFile.CreateFromFile(appHostSourceStream, null, 0, MemoryMappedFileAccess.Read, HandleInheritability.None, true);
- memoryMappedViewAccessor = memoryMappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.CopyOnWrite);
-
- // Get the size of the source app host to ensure that we don't write extra data to the destination.
- // On Windows, the size of the view accessor is rounded up to the next page boundary.
- long sourceAppHostLength = appHostSourceStream.Length;
-
- // Transform the host file in-memory.
- RewriteAppHost(memoryMappedViewAccessor);
-
- // Save the transformed host.
- using (FileStream fileStream = new FileStream(appHostDestinationFilePath, FileMode.Create))
- {
- BinaryUtils.WriteToStream(memoryMappedViewAccessor, fileStream, sourceAppHostLength);
-
- // Remove the signature from MachO hosts.
- if (!appHostIsPEImage)
- {
- MachOUtils.RemoveSignature(fileStream);
- }
- }
- }
- finally
- {
- memoryMappedViewAccessor?.Dispose();
- memoryMappedFile?.Dispose();
- appHostSourceStream?.Dispose();
- }
- });
-
- RetryUtil.RetryOnWin32Error(UpdateResources);
-
- if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- var filePermissionOctal = Convert.ToInt32("755", 8); // -rwxr-xr-x
- const int EINTR = 4;
- int chmodReturnCode = 0;
-
- do
- {
- chmodReturnCode = chmod(appHostDestinationFilePath, filePermissionOctal);
- }
- while (chmodReturnCode == -1 && Marshal.GetLastWin32Error() == EINTR);
-
- if (chmodReturnCode == -1)
- {
- throw new Win32Exception(Marshal.GetLastWin32Error(), $"Could not set file permission {filePermissionOctal} for {appHostDestinationFilePath}.");
- }
-
- if (enableMacOSCodeSign && RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && HostModelUtils.IsCodesignAvailable())
- {
- (int exitCode, string stdErr) = HostModelUtils.RunCodesign("-s -", appHostDestinationFilePath);
- if (exitCode != 0)
- {
- throw new AppHostSigningException(exitCode, stdErr);
- }
- }
- }
- }
- catch (Exception ex)
- {
- // Delete the destination file so we don't leave an unmodified apphost
- try
- {
- File.Delete(appHostDestinationFilePath);
- }
- catch (Exception failedToDeleteEx)
- {
- throw new AggregateException(ex, failedToDeleteEx);
- }
-
- throw;
- }
- }
-
- ///
- /// Set the current AppHost as a single-file bundle.
- ///
- /// The path of Apphost template, which has the place holder
- /// The offset to the location of bundle header
- public static void SetAsBundle(
- string appHostPath,
- long bundleHeaderOffset)
- {
- byte[] bundleHeaderPlaceholder = {
- // 8 bytes represent the bundle header-offset
- // Zero for non-bundle apphosts (default).
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- // 32 bytes represent the bundle signature: SHA-256 for ".net core bundle"
- 0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38,
- 0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32,
- 0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18,
- 0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae
- };
-
- // Re-write the destination apphost with the proper contents.
- RetryUtil.RetryOnIOError(() =>
- BinaryUtils.SearchAndReplace(appHostPath,
- bundleHeaderPlaceholder,
- BitConverter.GetBytes(bundleHeaderOffset),
- pad0s: false));
-
- RetryUtil.RetryOnIOError(() =>
- MachOUtils.AdjustHeadersForBundle(appHostPath));
-
- // Memory-mapped write does not updating last write time
- RetryUtil.RetryOnIOError(() =>
- File.SetLastWriteTimeUtc(appHostPath, DateTime.UtcNow));
- }
-
- ///
- /// Check if the an AppHost is a single-file bundle
- ///
- /// The path of Apphost to check
- /// True if the AppHost is a single-file bundle, false otherwise
- public static void ResetBundle(string appHostFilePath)
- {
- byte[] bundleSignature = {
- // 32 bytes represent the bundle signature: SHA-256 for ".net core bundle"
- 0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38,
- 0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32,
- 0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18,
- 0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae
- };
-
- void ResetBundleHeader()
- {
- using (var memoryMappedFile = MemoryMappedFile.CreateFromFile(appHostFilePath))
- {
- using (MemoryMappedViewAccessor accessor = memoryMappedFile.CreateViewAccessor())
- {
- int position = BinaryUtils.SearchInFile(accessor, bundleSignature);
- if (position == -1)
- {
- throw new PlaceHolderNotFoundInAppHostException(bundleSignature);
- }
-
- accessor.WriteArray(position - sizeof(long), new byte[sizeof(long)], 0, sizeof(long));
- }
- }
- }
-
- RetryUtil.RetryOnIOError(ResetBundleHeader);
- }
-
- public static bool IsBundle(string appHostFilePath, out long bundleHeaderOffset)
- {
- byte[] bundleSignature = {
- // 32 bytes represent the bundle signature: SHA-256 for ".net core bundle"
- 0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38,
- 0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32,
- 0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18,
- 0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae
- };
-
- long headerOffset = 0;
- void FindBundleHeader()
- {
- using (var memoryMappedFile = MemoryMappedFile.CreateFromFile(appHostFilePath))
- {
- using (MemoryMappedViewAccessor accessor = memoryMappedFile.CreateViewAccessor())
- {
- int position = BinaryUtils.SearchInFile(accessor, bundleSignature);
- if (position == -1)
- {
- throw new PlaceHolderNotFoundInAppHostException(bundleSignature);
- }
-
- headerOffset = accessor.ReadInt64(position - sizeof(long));
- }
- }
- }
-
- RetryUtil.RetryOnIOError(FindBundleHeader);
- bundleHeaderOffset = headerOffset;
-
- return headerOffset != 0;
- }
-
- [DllImport("libc", SetLastError = true)]
- private static extern int chmod(string pathname, int mode);
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/AppHost/MachOFormatError.cs b/src/Velopack.Packaging.HostModel/AppHost/MachOFormatError.cs
deleted file mode 100644
index eae935f1..00000000
--- a/src/Velopack.Packaging.HostModel/AppHost/MachOFormatError.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.NET.HostModel.AppHost
-{
- ///
- /// Additional details about the failure with caused an AppHostMachOFormatException
- ///
- public enum MachOFormatError
- {
- Not64BitExe, // Apphost is expected to be a 64-bit MachO executable
- DuplicateLinkEdit, // Only one __LINKEDIT segment is expected in the apphost
- DuplicateSymtab, // Only one SYMTAB is expected in the apphost
- MissingLinkEdit, // CODE_SIGNATURE command must follow a Segment64 command named __LINKEDIT
- MissingSymtab, // CODE_SIGNATURE command must follow the SYMTAB command
- LinkEditNotLast, // __LINKEDIT must be the last segment in the binary layout
- SymtabNotInLinkEdit, // SYMTAB must within the __LINKEDIT segment!
- SignNotInLinkEdit, // Signature blob must be within the __LINKEDIT segment!
- SignCommandNotLast, // CODE_SIGNATURE command must be the last command
- SignBlobNotLast, // Signature blob must be at the very end of the file
- SignDoesntFollowSymtab, // Signature blob must immediately follow the Symtab
- MemoryMapAccessFault, // Error reading the memory-mapped apphost
- InvalidUTF8, // UTF8 decoding failed
- SignNotRemoved, // Signature not removed from the host (while processing a single-file bundle)
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/AppHost/MachOUtils.cs b/src/Velopack.Packaging.HostModel/AppHost/MachOUtils.cs
deleted file mode 100644
index 30f64476..00000000
--- a/src/Velopack.Packaging.HostModel/AppHost/MachOUtils.cs
+++ /dev/null
@@ -1,444 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.IO.MemoryMappedFiles;
-using System.Runtime.CompilerServices;
-using System.Text;
-
-namespace Microsoft.NET.HostModel.AppHost
-{
- internal static class MachOUtils
- {
- // The MachO Headers are copied from
- // https://opensource.apple.com/source/cctools/cctools-870/include/mach-o/loader.h
- //
- // The data fields and enumerations match the structure definitions in the above file,
- // and hence do not conform to C# CoreFx naming style.
-
- private enum Magic : uint
- {
- MH_MAGIC = 0xfeedface,
- MH_CIGAM = 0xcefaedfe,
- MH_MAGIC_64 = 0xfeedfacf,
- MH_CIGAM_64 = 0xcffaedfe
- }
-
- private enum FileType : uint
- {
- MH_EXECUTE = 0x2
- }
-
-#pragma warning disable 0649
- private struct MachHeader
- {
- public Magic magic;
- public int cputype;
- public int cpusubtype;
- public FileType filetype;
- public uint ncmds;
- public uint sizeofcmds;
- public uint flags;
- public uint reserved;
-
- public bool Is64BitExecutable()
- {
- return magic == Magic.MH_MAGIC_64 && filetype == FileType.MH_EXECUTE;
- }
-
- public bool IsValid()
- {
- switch (magic)
- {
- case Magic.MH_CIGAM:
- case Magic.MH_CIGAM_64:
- case Magic.MH_MAGIC:
- case Magic.MH_MAGIC_64:
- return true;
-
- default:
- return false;
- }
- }
- }
-
- private enum Command : uint
- {
- LC_SYMTAB = 0x2,
- LC_SEGMENT_64 = 0x19,
- LC_CODE_SIGNATURE = 0x1d,
- }
-
- private struct LoadCommand
- {
- public Command cmd;
- public uint cmdsize;
- }
-
- // The linkedit_data_command contains the offsets and sizes of a blob
- // of data in the __LINKEDIT segment (including LC_CODE_SIGNATURE).
- private struct LinkEditDataCommand
- {
- public Command cmd;
- public uint cmdsize;
- public uint dataoff;
- public uint datasize;
- }
-
- private struct SymtabCommand
- {
- public uint cmd;
- public uint cmdsize;
- public uint symoff;
- public uint nsyms;
- public uint stroff;
- public uint strsize;
- };
-
- private unsafe struct SegmentCommand64
- {
- public Command cmd;
- public uint cmdsize;
- public fixed byte segname[16];
- public ulong vmaddr;
- public ulong vmsize;
- public ulong fileoff;
- public ulong filesize;
- public int maxprot;
- public int initprot;
- public uint nsects;
- public uint flags;
-
- public string SegName
- {
- get
- {
- fixed (byte* p = segname)
- {
- int len = 0;
- while (*(p + len) != 0 && len++ < 16) ;
-
- try
- {
- return Encoding.UTF8.GetString(p, len);
- }
- catch (ArgumentException)
- {
- throw new AppHostMachOFormatException(MachOFormatError.InvalidUTF8);
- }
- }
- }
- }
- }
-
-#pragma warning restore 0649
-
- private static void Verify(bool condition, MachOFormatError error)
- {
- if (!condition)
- {
- throw new AppHostMachOFormatException(error);
- }
- }
-
- public static bool IsMachOImage(string filePath)
- {
- using (BinaryReader reader = new BinaryReader(File.OpenRead(filePath)))
- {
- if (reader.BaseStream.Length < 256) // Header size
- {
- return false;
- }
-
- uint magic = reader.ReadUInt32();
- return Enum.IsDefined(typeof(Magic), magic);
- }
- }
-
- ///
- /// This Method is a utility to remove the code-signature (if any)
- /// from a MachO AppHost binary.
- ///
- /// The tool assumes the following layout of the executable:
- ///
- /// * MachoHeader (64-bit, executable, not swapped integers)
- /// * LoadCommands
- /// LC_SEGMENT_64 (__PAGEZERO)
- /// LC_SEGMENT_64 (__TEXT)
- /// LC_SEGMENT_64 (__DATA)
- /// LC_SEGMENT_64 (__LINKEDIT)
- /// ...
- /// LC_SYMTAB
- /// ...
- /// LC_CODE_SIGNATURE (last)
- ///
- /// * ... Different Segments ...
- ///
- /// * The __LINKEDIT Segment (last)
- /// * ... Different sections ...
- /// * SYMTAB
- /// * (Some alignment bytes)
- /// * The Code-signature
- ///
- /// In order to remove the signature, the method:
- /// - Removes (zeros out) the LC_CODE_SIGNATURE command
- /// - Adjusts the size and count of the load commands in the header
- /// - Truncates the size of the __LINKEDIT segment to the end of SYMTAB
- /// - Truncates the apphost file to the end of the __LINKEDIT segment
- ///
- ///
- /// Stream containing the AppHost
- ///
- /// True if
- /// - The input is a MachO binary, and
- /// - It is a signed binary, and
- /// - The signature was successfully removed
- /// False otherwise
- ///
- ///
- /// The input is a MachO file, but doesn't match the expect format of the AppHost.
- ///
- public static unsafe bool RemoveSignature(FileStream stream)
- {
- uint signatureSize = 0;
- using (var mappedFile = MemoryMappedFile.CreateFromFile(stream,
- mapName: null,
- capacity: 0,
- MemoryMappedFileAccess.ReadWrite,
- HandleInheritability.None,
- leaveOpen: true))
- {
- using (var accessor = mappedFile.CreateViewAccessor())
- {
- byte* file = null;
- try
- {
- accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref file);
- Verify(file != null, MachOFormatError.MemoryMapAccessFault);
-
- MachHeader* header = (MachHeader*)file;
-
- if (!header->IsValid())
- {
- // Not a MachO file.
- return false;
- }
-
- Verify(header->Is64BitExecutable(), MachOFormatError.Not64BitExe);
-
- file += sizeof(MachHeader);
- SegmentCommand64* linkEdit = null;
- SymtabCommand* symtab = null;
- LinkEditDataCommand* signature = null;
-
- for (uint i = 0; i < header->ncmds; i++)
- {
- LoadCommand* command = (LoadCommand*)file;
- if (command->cmd == Command.LC_SEGMENT_64)
- {
- SegmentCommand64* segment = (SegmentCommand64*)file;
- if (segment->SegName.Equals("__LINKEDIT"))
- {
- Verify(linkEdit == null, MachOFormatError.DuplicateLinkEdit);
- linkEdit = segment;
- }
- }
- else if (command->cmd == Command.LC_SYMTAB)
- {
- Verify(symtab == null, MachOFormatError.DuplicateSymtab);
- symtab = (SymtabCommand*)command;
- }
- else if (command->cmd == Command.LC_CODE_SIGNATURE)
- {
- Verify(i == header->ncmds - 1, MachOFormatError.SignCommandNotLast);
- signature = (LinkEditDataCommand*)command;
- break;
- }
-
- file += command->cmdsize;
- }
-
- if (signature != null)
- {
- Verify(linkEdit != null, MachOFormatError.MissingLinkEdit);
- Verify(symtab != null, MachOFormatError.MissingSymtab);
-
- var symtabEnd = symtab->stroff + symtab->strsize;
- var linkEditEnd = linkEdit->fileoff + linkEdit->filesize;
- var signatureEnd = signature->dataoff + signature->datasize;
- var fileEnd = (ulong)stream.Length;
-
- Verify(linkEditEnd == fileEnd, MachOFormatError.LinkEditNotLast);
- Verify(signatureEnd == fileEnd, MachOFormatError.SignBlobNotLast);
-
- Verify(symtab->symoff > linkEdit->fileoff, MachOFormatError.SymtabNotInLinkEdit);
- Verify(signature->dataoff > linkEdit->fileoff, MachOFormatError.SignNotInLinkEdit);
-
- // The signature blob immediately follows the symtab blob,
- // except for a few bytes of padding.
- Verify(signature->dataoff >= symtabEnd && signature->dataoff - symtabEnd < 32, MachOFormatError.SignBlobNotLast);
-
- // Remove the signature command
- header->ncmds--;
- header->sizeofcmds -= signature->cmdsize;
- Unsafe.InitBlock(signature, 0, signature->cmdsize);
-
- // Remove the signature blob (note for truncation)
- signatureSize = (uint)(fileEnd - symtabEnd);
-
- // Adjust the __LINKEDIT segment load command
- linkEdit->filesize -= signatureSize;
-
- // codesign --remove-signature doesn't reset the vmsize.
- // Setting the vmsize here makes the output bin-equal with the original
- // unsigned apphost (and not bin-equal with a signed-unsigned-apphost).
- linkEdit->vmsize = linkEdit->filesize;
- }
- }
- finally
- {
- if (file != null)
- {
- accessor.SafeMemoryMappedViewHandle.ReleasePointer();
- }
- }
- }
- }
-
- if (signatureSize != 0)
- {
- // The signature was removed, update the file length
- stream.SetLength(stream.Length - signatureSize);
- return true;
- }
-
- return false;
- }
-
- ///
- /// This Method is a utility to adjust the apphost MachO-header
- /// to include the bytes added by the single-file bundler at the end of the file.
- ///
- /// The tool assumes the following layout of the executable
- ///
- /// * MachoHeader (64-bit, executable, not swapped integers)
- /// * LoadCommands
- /// LC_SEGMENT_64 (__PAGEZERO)
- /// LC_SEGMENT_64 (__TEXT)
- /// LC_SEGMENT_64 (__DATA)
- /// LC_SEGMENT_64 (__LINKEDIT)
- /// ...
- /// LC_SYMTAB
- ///
- /// * ... Different Segments
- ///
- /// * The __LINKEDIT Segment (last)
- /// * ... Different sections ...
- /// * SYMTAB (last)
- ///
- /// The MAC codesign tool places several restrictions on the layout
- /// * The __LINKEDIT segment must be the last one
- /// * The __LINKEDIT segment must cover the end of the file
- /// * All bytes in the __LINKEDIT segment are used by other linkage commands
- /// (ex: symbol/string table, dynamic load information etc)
- ///
- /// In order to circumvent these restrictions, we:
- /// * Extend the __LINKEDIT segment to include the bundle-data
- /// * Extend the string table to include all the bundle-data
- /// (that is, the bundle-data appear as strings to the loader/codesign tool).
- ///
- /// This method has certain limitations:
- /// * The bytes for the bundler may be unnecessarily loaded at startup
- /// * Tools that process the string table may be confused (?)
- /// * The string table size is limited to 4GB. Bundles larger than that size
- /// cannot be accomodated by this utility.
- ///
- ///
- /// Path to the AppHost
- ///
- /// True if
- /// - The input is a MachO binary, and
- /// - The additional bytes were successfully accomodated within the MachO segments.
- /// False otherwise
- ///
- ///
- /// The input is a MachO file, but doesn't match the expect format of the AppHost.
- ///
- public static unsafe bool AdjustHeadersForBundle(string filePath)
- {
- ulong fileLength = (ulong)new FileInfo(filePath).Length;
- using (var mappedFile = MemoryMappedFile.CreateFromFile(filePath))
- {
- using (var accessor = mappedFile.CreateViewAccessor())
- {
- byte* file = null;
- try
- {
- accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref file);
- Verify(file != null, MachOFormatError.MemoryMapAccessFault);
-
- MachHeader* header = (MachHeader*)file;
-
- if (!header->IsValid())
- {
- // Not a MachO file.
- return false;
- }
-
- Verify(header->Is64BitExecutable(), MachOFormatError.Not64BitExe);
-
- file += sizeof(MachHeader);
- SegmentCommand64* linkEdit = null;
- SymtabCommand* symtab = null;
- LinkEditDataCommand* signature = null;
-
- for (uint i = 0; i < header->ncmds; i++)
- {
- LoadCommand* command = (LoadCommand*)file;
- if (command->cmd == Command.LC_SEGMENT_64)
- {
- SegmentCommand64* segment = (SegmentCommand64*)file;
- if (segment->SegName.Equals("__LINKEDIT"))
- {
- Verify(linkEdit == null, MachOFormatError.DuplicateLinkEdit);
- linkEdit = segment;
- }
- }
- else if (command->cmd == Command.LC_SYMTAB)
- {
- Verify(symtab == null, MachOFormatError.DuplicateSymtab);
- symtab = (SymtabCommand*)command;
- }
-
- file += command->cmdsize;
- }
-
- Verify(linkEdit != null, MachOFormatError.MissingLinkEdit);
- Verify(symtab != null, MachOFormatError.MissingSymtab);
-
- // Update the string table to include bundle-data
- ulong newStringTableSize = fileLength - symtab->stroff;
- if (newStringTableSize > uint.MaxValue)
- {
- // Too big, too bad;
- return false;
- }
- symtab->strsize = (uint)newStringTableSize;
-
- // Update the __LINKEDIT segment to include bundle-data
- linkEdit->filesize = fileLength - linkEdit->fileoff;
- linkEdit->vmsize = linkEdit->filesize;
- }
- finally
- {
- if (file != null)
- {
- accessor.SafeMemoryMappedViewHandle.ReleasePointer();
- }
- }
- }
- }
-
- return true;
- }
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/AppHost/PEUtils.cs b/src/Velopack.Packaging.HostModel/AppHost/PEUtils.cs
deleted file mode 100644
index ffc68f8c..00000000
--- a/src/Velopack.Packaging.HostModel/AppHost/PEUtils.cs
+++ /dev/null
@@ -1,178 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.IO.MemoryMappedFiles;
-
-namespace Microsoft.NET.HostModel.AppHost
-{
- public static class PEUtils
- {
- ///
- /// The first two bytes of a PE file are a constant signature.
- ///
- private const ushort PEFileSignature = 0x5A4D;
-
- ///
- /// The offset of the PE header pointer in the DOS header.
- ///
- private const int PEHeaderPointerOffset = 0x3C;
-
- ///
- /// The offset of the Subsystem field in the PE header.
- ///
- private const int SubsystemOffset = 0x5C;
-
- ///
- /// The value of the sybsystem field which indicates Windows GUI (Graphical UI)
- ///
- private const ushort WindowsGUISubsystem = 0x2;
-
- ///
- /// The value of the subsystem field which indicates Windows CUI (Console)
- ///
- private const ushort WindowsCUISubsystem = 0x3;
-
- ///
- /// Check whether the apphost file is a windows PE image by looking at the first few bytes.
- ///
- /// The memory accessor which has the apphost file opened.
- /// true if the accessor represents a PE image, false otherwise.
- internal static unsafe bool IsPEImage(MemoryMappedViewAccessor accessor)
- {
- byte* pointer = null;
-
- try
- {
- accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
- byte* bytes = pointer + accessor.PointerOffset;
-
- // https://en.wikipedia.org/wiki/Portable_Executable
- // Validate that we're looking at Windows PE file
- if (((ushort*)bytes)[0] != PEFileSignature || accessor.Capacity < PEHeaderPointerOffset + sizeof(uint))
- {
- return false;
- }
- return true;
- }
- finally
- {
- if (pointer != null)
- {
- accessor.SafeMemoryMappedViewHandle.ReleasePointer();
- }
- }
- }
-
- public static bool IsPEImage(string filePath)
- {
- using (BinaryReader reader = new BinaryReader(File.OpenRead(filePath)))
- {
- if (reader.BaseStream.Length < PEHeaderPointerOffset + sizeof(uint))
- {
- return false;
- }
-
- ushort signature = reader.ReadUInt16();
- return signature == PEFileSignature;
- }
- }
-
- ///
- /// This method will attempt to set the subsystem to GUI. The apphost file should be a windows PE file.
- ///
- /// The memory accessor which has the apphost file opened.
- internal static unsafe void SetWindowsGraphicalUserInterfaceBit(MemoryMappedViewAccessor accessor)
- {
- byte* pointer = null;
-
- try
- {
- accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
- byte* bytes = pointer + accessor.PointerOffset;
-
- // https://en.wikipedia.org/wiki/Portable_Executable
- uint peHeaderOffset = ((uint*)(bytes + PEHeaderPointerOffset))[0];
-
- if (accessor.Capacity < peHeaderOffset + SubsystemOffset + sizeof(ushort))
- {
- throw new AppHostNotPEFileException();
- }
-
- ushort* subsystem = ((ushort*)(bytes + peHeaderOffset + SubsystemOffset));
-
- // https://docs.microsoft.com/windows/desktop/Debug/pe-format#windows-subsystem
- // The subsystem of the prebuilt apphost should be set to CUI
- if (subsystem[0] != WindowsCUISubsystem)
- {
- throw new AppHostNotCUIException();
- }
-
- // Set the subsystem to GUI
- subsystem[0] = WindowsGUISubsystem;
- }
- finally
- {
- if (pointer != null)
- {
- accessor.SafeMemoryMappedViewHandle.ReleasePointer();
- }
- }
- }
-
- public static unsafe void SetWindowsGraphicalUserInterfaceBit(string filePath)
- {
- using (var mappedFile = MemoryMappedFile.CreateFromFile(filePath))
- {
- using (var accessor = mappedFile.CreateViewAccessor())
- {
- SetWindowsGraphicalUserInterfaceBit(accessor);
- }
- }
- }
-
- ///
- /// This method will return the subsystem CUI/GUI value. The apphost file should be a windows PE file.
- ///
- /// The memory accessor which has the apphost file opened.
- internal static unsafe ushort GetWindowsGraphicalUserInterfaceBit(MemoryMappedViewAccessor accessor)
- {
- byte* pointer = null;
-
- try
- {
- accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
- byte* bytes = pointer + accessor.PointerOffset;
-
- // https://en.wikipedia.org/wiki/Portable_Executable
- uint peHeaderOffset = ((uint*)(bytes + PEHeaderPointerOffset))[0];
-
- if (accessor.Capacity < peHeaderOffset + SubsystemOffset + sizeof(ushort))
- {
- throw new AppHostNotPEFileException();
- }
-
- ushort* subsystem = ((ushort*)(bytes + peHeaderOffset + SubsystemOffset));
-
- return subsystem[0];
- }
- finally
- {
- if (pointer != null)
- {
- accessor.SafeMemoryMappedViewHandle.ReleasePointer();
- }
- }
- }
-
- public static unsafe ushort GetWindowsGraphicalUserInterfaceBit(string filePath)
- {
- using (var mappedFile = MemoryMappedFile.CreateFromFile(filePath))
- {
- using (var accessor = mappedFile.CreateViewAccessor())
- {
- return GetWindowsGraphicalUserInterfaceBit(accessor);
- }
- }
- }
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/AppHost/PlaceHolderNotFoundInAppHostException.cs b/src/Velopack.Packaging.HostModel/AppHost/PlaceHolderNotFoundInAppHostException.cs
deleted file mode 100644
index 8c93866b..00000000
--- a/src/Velopack.Packaging.HostModel/AppHost/PlaceHolderNotFoundInAppHostException.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.NET.HostModel.AppHost
-{
- ///
- /// Unable to use input file as a valid application host executable, as it does not contain
- /// the expected placeholder byte sequence.
- ///
- public class PlaceHolderNotFoundInAppHostException : AppHostUpdateException
- {
- public byte[] MissingPattern { get; }
- public PlaceHolderNotFoundInAppHostException(byte[] pattern)
- {
- MissingPattern = pattern;
- }
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/AppHost/RetryUtil.cs b/src/Velopack.Packaging.HostModel/AppHost/RetryUtil.cs
deleted file mode 100644
index 6bb57049..00000000
--- a/src/Velopack.Packaging.HostModel/AppHost/RetryUtil.cs
+++ /dev/null
@@ -1,92 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.NET.HostModel
-{
- ///
- /// HostModel library implements several services for updating the AppHost DLL.
- /// These updates involve multiple file open/close operations.
- /// An Antivirus scanner may intercept in-between and lock the file,
- /// causing the operations to fail with IO-Error.
- /// So, the operations are retried a few times on failures such as
- /// - IOException
- /// - Failure with Win32 errors indicating file-lock
- ///
- public static class RetryUtil
- {
- public const int NumberOfRetries = 500;
- public const int NumMilliSecondsToWait = 100;
-
- public static void RetryOnIOError(Action func)
- {
- for (int i = 1; i <= NumberOfRetries; i++)
- {
- try
- {
- func();
- break;
- }
- catch (IOException) when (i < NumberOfRetries)
- {
- Thread.Sleep(NumMilliSecondsToWait);
- }
- }
- }
-
- public static void RetryOnWin32Error(Action func)
- {
- static bool IsKnownIrrecoverableError(int hresult)
- {
- // Error codes are defined in winerror.h
- // The error code is stored in the lowest 16 bits of the HResult
-
- switch (hresult & 0xffff)
- {
- case 0x00000001: // ERROR_INVALID_FUNCTION
- case 0x00000002: // ERROR_FILE_NOT_FOUND
- case 0x00000003: // ERROR_PATH_NOT_FOUND
- case 0x00000006: // ERROR_INVALID_HANDLE
- case 0x00000008: // ERROR_NOT_ENOUGH_MEMORY
- case 0x0000000B: // ERROR_BAD_FORMAT
- case 0x0000000E: // ERROR_OUTOFMEMORY
- case 0x0000000F: // ERROR_INVALID_DRIVE
- case 0x00000012: // ERROR_NO_MORE_FILES
- case 0x00000035: // ERROR_BAD_NETPATH
- case 0x00000057: // ERROR_INVALID_PARAMETER
- case 0x00000071: // ERROR_NO_MORE_SEARCH_HANDLES
- case 0x00000072: // ERROR_INVALID_TARGET_HANDLE
- case 0x00000078: // ERROR_CALL_NOT_IMPLEMENTED
- case 0x0000007B: // ERROR_INVALID_NAME
- case 0x0000007C: // ERROR_INVALID_LEVEL
- case 0x0000007D: // ERROR_NO_VOLUME_LABEL
- case 0x0000009A: // ERROR_LABEL_TOO_LONG
- case 0x000000A0: // ERROR_BAD_ARGUMENTS
- case 0x000000A1: // ERROR_BAD_PATHNAME
- case 0x000000CE: // ERROR_FILENAME_EXCED_RANGE
- case 0x000000DF: // ERROR_FILE_TOO_LARGE
- case 0x000003ED: // ERROR_UNRECOGNIZED_VOLUME
- case 0x000003EE: // ERROR_FILE_INVALID
- case 0x00000651: // ERROR_DEVICE_REMOVED
- return true;
-
- default:
- return false;
- }
- }
-
- for (int i = 1; i <= NumberOfRetries; i++)
- {
- try
- {
- func();
- break;
- }
- catch (HResultException hrex)
- when (i < NumberOfRetries && !IsKnownIrrecoverableError(hrex.Win32HResult))
- {
- Thread.Sleep(NumMilliSecondsToWait);
- }
- }
- }
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/Bundle/BundleOptions.cs b/src/Velopack.Packaging.HostModel/Bundle/BundleOptions.cs
deleted file mode 100644
index 8eb34115..00000000
--- a/src/Velopack.Packaging.HostModel/Bundle/BundleOptions.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.NET.HostModel.Bundle
-{
- ///
- /// BundleOptions: Optional settings for configuring the type of files
- /// included in the single file bundle.
- ///
- [Flags]
- public enum BundleOptions
- {
- None = 0,
- BundleNativeBinaries = 1,
- BundleOtherFiles = 2,
- BundleSymbolFiles = 4,
- BundleAllContent = BundleNativeBinaries | BundleOtherFiles,
- EnableCompression = 8,
- };
-}
diff --git a/src/Velopack.Packaging.HostModel/Bundle/Bundler.cs b/src/Velopack.Packaging.HostModel/Bundle/Bundler.cs
deleted file mode 100644
index 5a10e427..00000000
--- a/src/Velopack.Packaging.HostModel/Bundle/Bundler.cs
+++ /dev/null
@@ -1,373 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics;
-using System.IO.Compression;
-using System.Reflection.PortableExecutable;
-using System.Runtime.InteropServices;
-using Microsoft.NET.HostModel.AppHost;
-
-namespace Microsoft.NET.HostModel.Bundle
-{
- ///
- /// Bundler: Functionality to embed the managed app and its dependencies
- /// into the host native binary.
- ///
- public class Bundler
- {
- public const uint BundlerMajorVersion = 6;
- public const uint BundlerMinorVersion = 0;
- public readonly Manifest BundleManifest;
-
- private readonly string _hostName;
- private readonly string _outputDir;
- private readonly string _depsJson;
- private readonly string _runtimeConfigJson;
- private readonly string _runtimeConfigDevJson;
-
- private readonly Trace _tracer;
- private readonly TargetInfo _target;
- private readonly BundleOptions _options;
- private readonly bool _macosCodesign;
-
- public Bundler(string hostName,
- string outputDir,
- BundleOptions options = BundleOptions.None,
- OSPlatform? targetOS = null,
- Architecture? targetArch = null,
- Version targetFrameworkVersion = null,
- bool diagnosticOutput = false,
- string appAssemblyName = null,
- bool macosCodesign = true)
- {
- _tracer = new Trace(diagnosticOutput);
-
- _hostName = hostName;
- _outputDir = Path.GetFullPath(string.IsNullOrEmpty(outputDir) ? Environment.CurrentDirectory : outputDir);
- _target = new TargetInfo(targetOS, targetArch, targetFrameworkVersion);
-
- if (_target.BundleMajorVersion < 6 &&
- (options & BundleOptions.EnableCompression) != 0)
- {
- throw new ArgumentException("Compression requires framework version 6.0 or above", nameof(options));
- }
-
- appAssemblyName ??= _target.GetAssemblyName(hostName);
- _depsJson = appAssemblyName + ".deps.json";
- _runtimeConfigJson = appAssemblyName + ".runtimeconfig.json";
- _runtimeConfigDevJson = appAssemblyName + ".runtimeconfig.dev.json";
-
- BundleManifest = new Manifest(_target.BundleMajorVersion, netcoreapp3CompatMode: options.HasFlag(BundleOptions.BundleAllContent));
- _options = _target.DefaultOptions | options;
- _macosCodesign = macosCodesign;
- }
-
- private bool ShouldCompress(FileType type)
- {
- if (!_options.HasFlag(BundleOptions.EnableCompression))
- {
- return false;
- }
-
- switch (type)
- {
- case FileType.DepsJson:
- case FileType.RuntimeConfigJson:
- return false;
-
- default:
- return true;
- }
- }
-
- ///
- /// Embed 'file' into 'bundle'
- ///
- ///
- /// startOffset: offset of the start 'file' within 'bundle'
- /// compressedSize: size of the compressed data, if entry was compressed, otherwise 0
- ///
- private (long startOffset, long compressedSize) AddToBundle(Stream bundle, Stream file, FileType type)
- {
- long startOffset = bundle.Position;
- if (ShouldCompress(type))
- {
- long fileLength = file.Length;
- file.Position = 0;
-
- // We use DeflateStream here.
- // It uses GZip algorithm, but with a trivial header that does not contain file info.
- using (DeflateStream compressionStream = new DeflateStream(bundle, CompressionLevel.Optimal, leaveOpen: true))
- {
- file.CopyTo(compressionStream);
- }
-
- long compressedSize = bundle.Position - startOffset;
- if (compressedSize < fileLength * 0.75)
- {
- return (startOffset, compressedSize);
- }
-
- // compression rate was not good enough
- // roll back the bundle offset and let the uncompressed code path take care of the entry.
- bundle.Seek(startOffset, SeekOrigin.Begin);
- }
-
- if (type == FileType.Assembly)
- {
- long misalignment = (bundle.Position % _target.AssemblyAlignment);
-
- if (misalignment != 0)
- {
- long padding = _target.AssemblyAlignment - misalignment;
- bundle.Position += padding;
- }
- }
-
- file.Position = 0;
- startOffset = bundle.Position;
- file.CopyTo(bundle);
-
- return (startOffset, 0);
- }
-
- private bool IsHost(string fileRelativePath)
- {
- return fileRelativePath.Equals(_hostName);
- }
-
- private bool ShouldIgnore(string fileRelativePath)
- {
- return fileRelativePath.Equals(_runtimeConfigDevJson);
- }
-
- private bool ShouldExclude(FileType type, string relativePath)
- {
- switch (type)
- {
- case FileType.Assembly:
- case FileType.DepsJson:
- case FileType.RuntimeConfigJson:
- return false;
-
- case FileType.NativeBinary:
- return !_options.HasFlag(BundleOptions.BundleNativeBinaries) || _target.ShouldExclude(relativePath);
-
- case FileType.Symbols:
- return !_options.HasFlag(BundleOptions.BundleSymbolFiles);
-
- case FileType.Unknown:
- return !_options.HasFlag(BundleOptions.BundleOtherFiles);
-
- default:
- Debug.Assert(false);
- return false;
- }
- }
-
- private bool IsAssembly(string path, out bool isPE)
- {
- isPE = false;
-
- using (FileStream file = File.OpenRead(path))
- {
- try
- {
- PEReader peReader = new PEReader(file);
- CorHeader corHeader = peReader.PEHeaders.CorHeader;
-
- isPE = true; // If peReader.PEHeaders doesn't throw, it is a valid PEImage
- return corHeader != null;
- }
- catch (BadImageFormatException)
- {
- }
- }
-
- return false;
- }
-
- private FileType InferType(FileSpec fileSpec)
- {
- if (fileSpec.BundleRelativePath.Equals(_depsJson))
- {
- return FileType.DepsJson;
- }
-
- if (fileSpec.BundleRelativePath.Equals(_runtimeConfigJson))
- {
- return FileType.RuntimeConfigJson;
- }
-
- if (Path.GetExtension(fileSpec.BundleRelativePath).ToLowerInvariant().Equals(".pdb"))
- {
- return FileType.Symbols;
- }
-
- bool isPE;
- if (IsAssembly(fileSpec.SourcePath, out isPE))
- {
- return FileType.Assembly;
- }
-
- bool isNativeBinary = _target.IsWindows ? isPE : _target.IsNativeBinary(fileSpec.SourcePath);
-
- if (isNativeBinary)
- {
- return FileType.NativeBinary;
- }
-
- return FileType.Unknown;
- }
-
- ///
- /// Generate a bundle, given the specification of embedded files
- ///
- ///
- /// An enumeration FileSpecs for the files to be embedded.
- ///
- /// Files in fileSpecs that are not bundled within the single file bundle,
- /// and should be published as separate files are marked as "IsExcluded" by this method.
- /// This doesn't include unbundled files that should be dropped, and not publised as output.
- ///
- ///
- /// The full path the the generated bundle file
- ///
- ///
- /// ArgumentException if input is invalid
- /// IOExceptions and ArgumentExceptions from callees flow to the caller.
- ///
- public string GenerateBundle(IReadOnlyList fileSpecs)
- {
- _tracer.Log($"Bundler Version: {BundlerMajorVersion}.{BundlerMinorVersion}");
- _tracer.Log($"Bundle Version: {BundleManifest.BundleVersion}");
- _tracer.Log($"Target Runtime: {_target}");
- _tracer.Log($"Bundler Options: {_options}");
-
- if (fileSpecs.Any(x => !x.IsValid()))
- {
- throw new ArgumentException("Invalid input specification: Found entry with empty source-path or bundle-relative-path.");
- }
-
- string hostSource;
- try
- {
- hostSource = fileSpecs.Where(x => x.BundleRelativePath.Equals(_hostName)).Single().SourcePath;
- }
- catch (InvalidOperationException)
- {
- throw new ArgumentException("Invalid input specification: Must specify the host binary");
- }
-
- string bundlePath = Path.Combine(_outputDir, _hostName);
- if (File.Exists(bundlePath))
- {
- _tracer.Log($"Ovewriting existing File {bundlePath}");
- }
-
- BinaryUtils.CopyFile(hostSource, bundlePath);
-
- if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && HostModelUtils.IsCodesignAvailable())
- {
- RemoveCodesignIfNecessary(bundlePath);
- }
-
- // Note: We're comparing file paths both on the OS we're running on as well as on the target OS for the app
- // We can't really make assumptions about the file systems (even on Linux there can be case insensitive file systems
- // and vice versa for Windows). So it's safer to do case sensitive comparison everywhere.
- var relativePathToSpec = new Dictionary(StringComparer.Ordinal);
-
- long headerOffset = 0;
- using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(bundlePath)))
- {
- Stream bundle = writer.BaseStream;
- bundle.Position = bundle.Length;
-
- foreach (var fileSpec in fileSpecs)
- {
- string relativePath = fileSpec.BundleRelativePath;
-
- if (IsHost(relativePath))
- {
- continue;
- }
-
- if (ShouldIgnore(relativePath))
- {
- _tracer.Log($"Ignore: {relativePath}");
- continue;
- }
-
- FileType type = InferType(fileSpec);
-
- if (ShouldExclude(type, relativePath))
- {
- _tracer.Log($"Exclude [{type}]: {relativePath}");
- fileSpec.Excluded = true;
- continue;
- }
-
- if (relativePathToSpec.TryGetValue(fileSpec.BundleRelativePath, out var existingFileSpec))
- {
- if (!string.Equals(fileSpec.SourcePath, existingFileSpec.SourcePath, StringComparison.Ordinal))
- {
- throw new ArgumentException($"Invalid input specification: Found entries '{fileSpec.SourcePath}' and '{existingFileSpec.SourcePath}' with the same BundleRelativePath '{fileSpec.BundleRelativePath}'");
- }
-
- // Exact duplicate - intentionally skip and don't include a second copy in the bundle
- continue;
- }
- else
- {
- relativePathToSpec.Add(fileSpec.BundleRelativePath, fileSpec);
- }
-
- using (FileStream file = File.OpenRead(fileSpec.SourcePath))
- {
- FileType targetType = _target.TargetSpecificFileType(type);
- (long startOffset, long compressedSize) = AddToBundle(bundle, file, targetType);
- FileEntry entry = BundleManifest.AddEntry(targetType, file, relativePath, startOffset, compressedSize, _target.BundleMajorVersion);
- _tracer.Log($"Embed: {entry}");
- }
- }
-
- // Write the bundle manifest
- headerOffset = BundleManifest.Write(writer);
- _tracer.Log($"Header Offset={headerOffset}");
- _tracer.Log($"Meta-data Size={writer.BaseStream.Position - headerOffset}");
- _tracer.Log($"Bundle: Path={bundlePath}, Size={bundle.Length}");
- }
-
- HostWriter.SetAsBundle(bundlePath, headerOffset);
-
- // Sign the bundle if requested
- if (_macosCodesign && RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && HostModelUtils.IsCodesignAvailable())
- {
- var (exitCode, stdErr) = HostModelUtils.RunCodesign("-s -", bundlePath);
- if (exitCode != 0)
- {
- throw new InvalidOperationException($"Failed to codesign '{bundlePath}': {stdErr}");
- }
- }
-
- return bundlePath;
-
- // Remove mac code signature if applied before bundling
- static void RemoveCodesignIfNecessary(string bundlePath)
- {
- Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.OSX));
- Debug.Assert(HostModelUtils.IsCodesignAvailable());
-
- // `codesign -v` returns 0 if app is signed
- if (HostModelUtils.RunCodesign("-v", bundlePath).ExitCode == 0)
- {
- var (exitCode, stdErr) = HostModelUtils.RunCodesign("--remove-signature", bundlePath);
- if (exitCode != 0)
- {
- throw new InvalidOperationException($"Removing codesign from '{bundlePath}' failed: {stdErr}");
- }
- }
- }
- }
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/Bundle/FileEntry.cs b/src/Velopack.Packaging.HostModel/Bundle/FileEntry.cs
deleted file mode 100644
index 74053529..00000000
--- a/src/Velopack.Packaging.HostModel/Bundle/FileEntry.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.NET.HostModel.Bundle
-{
- ///
- /// FileEntry: Records information about embedded files.
- ///
- /// The bundle manifest records the following meta-data for each
- /// file embedded in the bundle:
- /// * Type (1 byte)
- /// * NameLength (7-bit extension encoding, typically 1 byte)
- /// * Name ("NameLength" Bytes)
- /// * Offset (Int64)
- /// * Size (Int64)
- /// === present only in bundle version 3+
- /// * CompressedSize (Int64) 0 indicates No Compression
- ///
- public class FileEntry
- {
- public readonly uint BundleMajorVersion;
-
- public readonly long Offset;
- public readonly long Size;
- public readonly long CompressedSize;
- public readonly FileType Type;
- public readonly string RelativePath; // Path of an embedded file, relative to the Bundle source-directory.
-
- public const char DirectorySeparatorChar = '/';
-
- public FileEntry(FileType fileType, string relativePath, long offset, long size, long compressedSize, uint bundleMajorVersion)
- {
- BundleMajorVersion = bundleMajorVersion;
- Type = fileType;
- RelativePath = relativePath.Replace('\\', DirectorySeparatorChar);
- Offset = offset;
- Size = size;
- CompressedSize = compressedSize;
- }
-
- public void Write(BinaryWriter writer)
- {
- writer.Write(Offset);
- writer.Write(Size);
- // compression is used only in version 6.0+
- if (BundleMajorVersion >= 6)
- {
- writer.Write(CompressedSize);
- }
- writer.Write((byte)Type);
- writer.Write(RelativePath);
- }
-
- public override string ToString() => $"{RelativePath} [{Type}] @{Offset} Sz={Size} CompressedSz={CompressedSize}";
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/Bundle/FileSpec.cs b/src/Velopack.Packaging.HostModel/Bundle/FileSpec.cs
deleted file mode 100644
index 14119e9e..00000000
--- a/src/Velopack.Packaging.HostModel/Bundle/FileSpec.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.NET.HostModel.Bundle
-{
- ///
- /// Information about files to embed into the Bundle (input to the Bundler).
- ///
- /// SourcePath: path to the file to be bundled at compile time
- /// BundleRelativePath: path where the file is expected at run time,
- /// relative to the app DLL.
- ///
- public class FileSpec
- {
- public readonly string SourcePath;
- public readonly string BundleRelativePath;
- public bool Excluded;
-
- public FileSpec(string sourcePath, string bundleRelativePath)
- {
- SourcePath = sourcePath;
- BundleRelativePath = bundleRelativePath;
- Excluded = false;
- }
-
- public bool IsValid()
- {
- return !string.IsNullOrWhiteSpace(SourcePath) &&
- !string.IsNullOrWhiteSpace(BundleRelativePath);
- }
-
- public override string ToString() => $"SourcePath: {SourcePath}, RelativePath: {BundleRelativePath} {(Excluded ? "[Excluded]" : "")}";
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/Bundle/FileType.cs b/src/Velopack.Packaging.HostModel/Bundle/FileType.cs
deleted file mode 100644
index 3369d314..00000000
--- a/src/Velopack.Packaging.HostModel/Bundle/FileType.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.NET.HostModel.Bundle
-{
- ///
- /// FileType: Identifies the type of file embedded into the bundle.
- ///
- /// The bundler differentiates a few kinds of files via the manifest,
- /// with respect to the way in which they'll be used by the runtime.
- ///
- public enum FileType : byte
- {
- Unknown, // Type not determined.
- Assembly, // IL and R2R Assemblies
- NativeBinary, // NativeBinaries
- DepsJson, // .deps.json configuration file
- RuntimeConfigJson, // .runtimeconfig.json configuration file
- Symbols // PDB Files
- };
-}
diff --git a/src/Velopack.Packaging.HostModel/Bundle/Manifest.cs b/src/Velopack.Packaging.HostModel/Bundle/Manifest.cs
deleted file mode 100644
index f4f74bf0..00000000
--- a/src/Velopack.Packaging.HostModel/Bundle/Manifest.cs
+++ /dev/null
@@ -1,173 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Security.Cryptography;
-
-namespace Microsoft.NET.HostModel.Bundle
-{
- ///
- /// BundleManifest is a description of the contents of a bundle file.
- /// This class handles creation and consumption of bundle-manifests.
- ///
- /// Here is the description of the Bundle Layout:
- /// _______________________________________________
- /// AppHost
- ///
- ///
- /// ------------Embedded Files ---------------------
- /// The embedded files including the app, its
- /// configuration files, dependencies, and
- /// possibly the runtime.
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- /// ------------ Bundle Header -------------
- /// MajorVersion
- /// MinorVersion
- /// NumEmbeddedFiles
- /// ExtractionID
- /// DepsJson Location [Version 2+]
- /// Offset
- /// Size
- /// RuntimeConfigJson Location [Version 2+]
- /// Offset
- /// Size
- /// Flags [Version 2+]
- /// - - - - - - Manifest Entries - - - - - - - - - - -
- /// Series of FileEntries (for each embedded file)
- /// [File Type, Name, Offset, Size information]
- ///
- ///
- ///
- /// _________________________________________________
- ///
- public class Manifest
- {
- // NetcoreApp3CompatMode flag is set on a .net5 app,
- // which chooses to build single-file apps in .netcore3.x compat mode,
- // by constructing the bundler with BundleAllConent option.
- // This mode is expected to be deprecated in future versions of .NET.
- [Flags]
- private enum HeaderFlags : ulong
- {
- None = 0,
- NetcoreApp3CompatMode = 1
- }
-
- // Bundle ID is a string that is used to uniquely
- // identify this bundle. It is choosen to be compatible
- // with path-names so that the AppHost can use it in
- // extraction path.
- public string BundleID { get; private set; }
- //Same as Path.GetRandomFileName
- private const int BundleIdLength = 12;
- private SHA256 bundleHash = SHA256.Create();
- public readonly uint BundleMajorVersion;
- // The Minor version is currently unused, and is always zero
- public const uint BundleMinorVersion = 0;
- private FileEntry DepsJsonEntry;
- private FileEntry RuntimeConfigJsonEntry;
- private HeaderFlags Flags;
- public List Files;
- public string BundleVersion => $"{BundleMajorVersion}.{BundleMinorVersion}";
-
- public Manifest(uint bundleMajorVersion, bool netcoreapp3CompatMode = false)
- {
- BundleMajorVersion = bundleMajorVersion;
- Files = new List();
- Flags = (netcoreapp3CompatMode) ? HeaderFlags.NetcoreApp3CompatMode : HeaderFlags.None;
- }
-
- public FileEntry AddEntry(FileType type, FileStream fileContent, string relativePath, long offset, long compressedSize, uint bundleMajorVersion)
- {
- if (bundleHash == null)
- {
- throw new InvalidOperationException("It is forbidden to change Manifest state after it was written or BundleId was obtained.");
- }
-
- FileEntry entry = new FileEntry(type, relativePath, offset, fileContent.Length, compressedSize, bundleMajorVersion);
- Files.Add(entry);
-
- fileContent.Position = 0;
- byte[] hashBytes = ComputeSha256Hash(fileContent);
- bundleHash.TransformBlock(hashBytes, 0, hashBytes.Length, hashBytes, 0);
-
- switch (entry.Type)
- {
- case FileType.DepsJson:
- DepsJsonEntry = entry;
- break;
- case FileType.RuntimeConfigJson:
- RuntimeConfigJsonEntry = entry;
- break;
-
- case FileType.Assembly:
- break;
-
- default:
- break;
- }
-
- return entry;
- }
-
- private static byte[] ComputeSha256Hash(Stream stream)
- {
- using (SHA256 sha = SHA256.Create())
- {
- return sha.ComputeHash(stream);
- }
- }
-
- private string GenerateDeterministicId()
- {
- bundleHash.TransformFinalBlock(Array.Empty(), 0, 0);
- byte[] manifestHash = bundleHash.Hash;
- bundleHash.Dispose();
- bundleHash = null;
-
- return Convert.ToBase64String(manifestHash).Substring(BundleIdLength).Replace('/', '_');
- }
-
- public long Write(BinaryWriter writer)
- {
- BundleID = BundleID ?? GenerateDeterministicId();
-
- long startOffset = writer.BaseStream.Position;
-
- // Write the bundle header
- writer.Write(BundleMajorVersion);
- writer.Write(BundleMinorVersion);
- writer.Write(Files.Count);
- writer.Write(BundleID);
-
- if (BundleMajorVersion >= 2)
- {
- writer.Write((DepsJsonEntry != null) ? DepsJsonEntry.Offset : 0);
- writer.Write((DepsJsonEntry != null) ? DepsJsonEntry.Size : 0);
-
- writer.Write((RuntimeConfigJsonEntry != null) ? RuntimeConfigJsonEntry.Offset : 0);
- writer.Write((RuntimeConfigJsonEntry != null) ? RuntimeConfigJsonEntry.Size : 0);
-
- writer.Write((ulong)Flags);
- }
-
- // Write the manifest entries
- foreach (FileEntry entry in Files)
- {
- entry.Write(writer);
- }
-
- return startOffset;
- }
-
- public bool Contains(string relativePath)
- {
- return Files.Any(entry => relativePath.Equals(entry.RelativePath));
- }
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/Bundle/TargetInfo.cs b/src/Velopack.Packaging.HostModel/Bundle/TargetInfo.cs
deleted file mode 100644
index 1342dc4e..00000000
--- a/src/Velopack.Packaging.HostModel/Bundle/TargetInfo.cs
+++ /dev/null
@@ -1,118 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.NET.HostModel.AppHost;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-
-namespace Microsoft.NET.HostModel.Bundle
-{
- ///
- /// TargetInfo: Information about the target for which the single-file bundle is built.
- ///
- /// Currently the TargetInfo only tracks:
- /// - the target operating system
- /// - the target architecture
- /// - the target framework
- /// - the default options for this target
- /// - the assembly alignment for this target
- ///
-
- public class TargetInfo
- {
- public readonly OSPlatform OS;
- public readonly Architecture Arch;
- public readonly Version FrameworkVersion;
- public readonly uint BundleMajorVersion;
- public readonly BundleOptions DefaultOptions;
- public readonly int AssemblyAlignment;
-
- public TargetInfo(OSPlatform? os, Architecture? arch, Version targetFrameworkVersion)
- {
- OS = os ?? HostOS;
- Arch = arch ?? RuntimeInformation.OSArchitecture;
- FrameworkVersion = targetFrameworkVersion ?? net60;
-
- Debug.Assert(IsLinux || IsOSX || IsWindows);
-
- if (FrameworkVersion.CompareTo(net60) >= 0)
- {
- BundleMajorVersion = 6u;
- DefaultOptions = BundleOptions.None;
- }
- else if (FrameworkVersion.CompareTo(net50) >= 0)
- {
- BundleMajorVersion = 2u;
- DefaultOptions = BundleOptions.None;
- }
- else if (FrameworkVersion.Major == 3 && (FrameworkVersion.Minor == 0 || FrameworkVersion.Minor == 1))
- {
- BundleMajorVersion = 1u;
- DefaultOptions = BundleOptions.BundleAllContent;
- }
- else
- {
- throw new ArgumentException($"Invalid input: Unsupported Target Framework Version {targetFrameworkVersion}");
- }
-
- if (IsLinux && Arch == Architecture.Arm64)
- {
- // We align assemblies in the bundle at 4K so that we can use mmap on Linux without changing the page alignment of ARM64 R2R code.
- // This is only necessary for R2R assemblies, but we do it for all assemblies for simplicity.
- // See https://github.com/dotnet/runtime/issues/41832.
- AssemblyAlignment = 4096;
- }
- else
- {
- // Otherwise, assemblies are 16 bytes aligned, so that their sections can be memory-mapped cache aligned.
- AssemblyAlignment = 16;
- }
- }
-
- public bool IsNativeBinary(string filePath)
- {
- return IsLinux ? ElfUtils.IsElfImage(filePath) : IsOSX ? MachOUtils.IsMachOImage(filePath) : PEUtils.IsPEImage(filePath);
- }
-
- public string GetAssemblyName(string hostName)
- {
- // This logic to calculate assembly name from hostName should be removed (and probably moved to test helpers)
- // once the SDK in the correct assembly name.
- return (IsWindows ? Path.GetFileNameWithoutExtension(hostName) : hostName);
- }
-
- public override string ToString()
- {
- string os = IsWindows ? "win" : IsLinux ? "linux" : "osx";
- string arch = Arch.ToString().ToLowerInvariant();
- return $"OS: {os} Arch: {arch} FrameworkVersion: {FrameworkVersion}";
- }
-
- private static OSPlatform HostOS => RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? OSPlatform.Linux :
- RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? OSPlatform.OSX : OSPlatform.Windows;
-
- public bool IsLinux => OS.Equals(OSPlatform.Linux);
- public bool IsOSX => OS.Equals(OSPlatform.OSX);
- public bool IsWindows => OS.Equals(OSPlatform.Windows);
-
- // The .net core 3 apphost doesn't care about semantics of FileType -- all files are extracted at startup.
- // However, the apphost checks that the FileType value is within expected bounds, so set it to the first enumeration.
- public FileType TargetSpecificFileType(FileType fileType) => (BundleMajorVersion == 1) ? FileType.Unknown : fileType;
-
- // In .net core 3.x, bundle processing happens within the AppHost.
- // Therefore HostFxr and HostPolicy can be bundled within the single-file app.
- // In .net 5, bundle processing happens in HostFxr and HostPolicy libraries.
- // Therefore, these libraries themselves cannot be bundled into the single-file app.
- // This problem is mitigated by statically linking these host components with the AppHost.
- // https://github.com/dotnet/runtime/issues/32823
- public bool ShouldExclude(string relativePath) =>
- (FrameworkVersion.Major != 3) && (relativePath.Equals(HostFxr) || relativePath.Equals(HostPolicy));
-
- private readonly Version net60 = new Version(6, 0);
- private readonly Version net50 = new Version(5, 0);
- private string HostFxr => IsWindows ? "hostfxr.dll" : IsLinux ? "libhostfxr.so" : "libhostfxr.dylib";
- private string HostPolicy => IsWindows ? "hostpolicy.dll" : IsLinux ? "libhostpolicy.so" : "libhostpolicy.dylib";
-
-
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/Bundle/Trace.cs b/src/Velopack.Packaging.HostModel/Bundle/Trace.cs
deleted file mode 100644
index a531e702..00000000
--- a/src/Velopack.Packaging.HostModel/Bundle/Trace.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.NET.HostModel.Bundle
-{
- ///
- /// Tracing utilities for diagnostic output
- ///
- public class Trace
- {
- private readonly bool Verbose;
-
- public Trace(bool verbose)
- {
- Verbose = verbose;
- }
-
- public void Log(string fmt, params object[] args)
- {
- if (Verbose)
- {
- Console.WriteLine("LOG: " + fmt, args);
- }
- }
-
- public void Error(string type, string message)
- {
- Console.Error.WriteLine($"ERROR: {message}");
- }
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/HostModelUtils.cs b/src/Velopack.Packaging.HostModel/HostModelUtils.cs
deleted file mode 100644
index a4eafb9a..00000000
--- a/src/Velopack.Packaging.HostModel/HostModelUtils.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-
-namespace Microsoft.NET.HostModel
-{
- internal static class HostModelUtils
- {
- private const string CodesignPath = @"/usr/bin/codesign";
-
- public static bool IsCodesignAvailable() => File.Exists(CodesignPath);
-
- public static (int ExitCode, string StdErr) RunCodesign(string args, string appHostPath)
- {
- Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.OSX));
- Debug.Assert(IsCodesignAvailable());
-
- var psi = new ProcessStartInfo() {
- Arguments = $"{args} \"{appHostPath}\"",
- FileName = CodesignPath,
- RedirectStandardError = true,
- UseShellExecute = false,
- };
-
- using (var p = Process.Start(psi)) {
- p.WaitForExit();
- return (p.ExitCode, p.StandardError.ReadToEnd());
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/Velopack.Packaging.HostModel/README.md b/src/Velopack.Packaging.HostModel/README.md
deleted file mode 100644
index f473c6c8..00000000
--- a/src/Velopack.Packaging.HostModel/README.md
+++ /dev/null
@@ -1,19 +0,0 @@
-Host Model
-===================================
-
-HostModel is a library used by the [SDK](https://github.com/dotnet/sdk) to perform certain transformations on host executables. The main services implemented in HostModel are:
-
-* AppHost rewriter: Embeds the App Name into the AppHost executable. On Windows, also copies resources from App.dll to the AppHost.
-* ComHost rewriter: Creates a ComHost with an embedded CLSIDMap file to map CLSIDs to .NET Classes.
-
-* Single-file bundler: Embeds an application and its dependencies into the AppHost, to publish a single executable, as described [here](https://github.com/dotnet/designs/blob/master/accepted/2020/single-file/design.md).
-
-The HostModel library is in the Runtime repo because:
-
-* The implementations of the host and HostModel are closely related, which facilitates easy development, update, and testing.
-* Separating the HostModel implementation from SDK repo repo aligns with code ownership, and facilitates maintenance.
-
-The build targets/tasks that use the HostModel library are in the SDK repo because:
-
-* This facilitates the MSBuild tasks to be multi-targeted.
-* It helps generate localized error messages, since SDK repo has the localization infrastructure.
diff --git a/src/Velopack.Packaging.HostModel/ResourceUpdater.Squirrel.cs b/src/Velopack.Packaging.HostModel/ResourceUpdater.Squirrel.cs
deleted file mode 100644
index 0b59075f..00000000
--- a/src/Velopack.Packaging.HostModel/ResourceUpdater.Squirrel.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-// If updating HostModel, mark the ResourceUpdater.cs class as partial so these functions can get mixed in
-
-namespace Microsoft.NET.HostModel
-{
- public partial class ResourceUpdater
- {
- public ResourceUpdater(string peFile, bool bDeleteExistingResources)
- {
- hUpdate = Kernel32.BeginUpdateResource(peFile, bDeleteExistingResources);
- if (hUpdate.IsInvalid) {
- ThrowExceptionForLastWin32Error();
- }
- }
-
- public ResourceUpdater AddResource(byte[] data, string lpType, IntPtr lpName, ushort langId)
- {
- if (hUpdate.IsInvalid) {
- ThrowExceptionForInvalidUpdate();
- }
-
- if (!IsIntResource(lpName)) {
- throw new ArgumentException("AddResource can only be used with integer resource names");
- }
-
- if (!Kernel32.UpdateResource(hUpdate, lpType, lpName, langId, data, (uint) data.Length)) {
- ThrowExceptionForLastWin32Error();
- }
-
- return this;
- }
-
- //public ResourceUpdater ClearResource(string lpType, IntPtr lpName, ushort langId)
- //{
- // if (hUpdate.IsInvalid) {
- // ThrowExceptionForInvalidUpdate();
- // }
-
- // if (!IsIntResource(lpName)) {
- // throw new ArgumentException("AddResource can only be used with integer resource names");
- // }
-
- // if (!Kernel32.UpdateResource(hUpdate, lpType, lpName, langId, null, 0)) {
- // ThrowExceptionForLastWin32Error();
- // }
-
- // return this;
- //}
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/ResourceUpdater.cs b/src/Velopack.Packaging.HostModel/ResourceUpdater.cs
deleted file mode 100644
index 1b8a7387..00000000
--- a/src/Velopack.Packaging.HostModel/ResourceUpdater.cs
+++ /dev/null
@@ -1,458 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-
-namespace Microsoft.NET.HostModel
-{
- ///
- /// Provides methods for modifying the embedded native resources
- /// in a PE image. It currently only works on Windows, because it
- /// requires various kernel32 APIs.
- ///
- public partial class ResourceUpdater : IDisposable
- {
- private sealed class Kernel32
- {
- //
- // Native methods for updating resources
- //
-
- [DllImport(nameof(Kernel32), CharSet = CharSet.Unicode, SetLastError = true)]
- public static extern SafeUpdateHandle BeginUpdateResource(string pFileName,
- [MarshalAs(UnmanagedType.Bool)] bool bDeleteExistingResources);
-
- // Update a resource with data from an IntPtr
- [DllImport(nameof(Kernel32), SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool UpdateResource(SafeUpdateHandle hUpdate,
- IntPtr lpType,
- IntPtr lpName,
- ushort wLanguage,
- IntPtr lpData,
- uint cbData);
-
- // Update a resource with data from a managed byte[]
- [DllImport(nameof(Kernel32), SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool UpdateResource(SafeUpdateHandle hUpdate,
- IntPtr lpType,
- IntPtr lpName,
- ushort wLanguage,
- [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 5)] byte[] lpData,
- uint cbData);
-
- // Update a resource with data from a managed byte[]
- [DllImport(nameof(Kernel32), SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool UpdateResource(SafeUpdateHandle hUpdate,
- string lpType,
- IntPtr lpName,
- ushort wLanguage,
- [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 5)] byte[] lpData,
- uint cbData);
-
- [DllImport(nameof(Kernel32), SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool EndUpdateResource(SafeUpdateHandle hUpdate,
- bool fDiscard);
-
- // The IntPtr version of this dllimport is used in the
- // SafeHandle implementation
- [DllImport(nameof(Kernel32), SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool EndUpdateResource(IntPtr hUpdate,
- bool fDiscard);
-
- public const ushort LangID_LangNeutral_SublangNeutral = 0;
-
- //
- // Native methods used to read resources from a PE file
- //
-
- // Loading and freeing PE files
-
- public enum LoadLibraryFlags : uint
- {
- LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040,
- LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020
- }
-
- [DllImport(nameof(Kernel32), CharSet = CharSet.Unicode, SetLastError = true)]
- public static extern IntPtr LoadLibraryEx(string lpFileName,
- IntPtr hReservedNull,
- LoadLibraryFlags dwFlags);
-
- [DllImport(nameof(Kernel32), SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool FreeLibrary(IntPtr hModule);
-
- // Enumerating resources
-
- public delegate bool EnumResTypeProc(IntPtr hModule,
- IntPtr lpType,
- IntPtr lParam);
-
- public delegate bool EnumResNameProc(IntPtr hModule,
- IntPtr lpType,
- IntPtr lpName,
- IntPtr lParam);
-
- public delegate bool EnumResLangProc(IntPtr hModule,
- IntPtr lpType,
- IntPtr lpName,
- ushort wLang,
- IntPtr lParam);
-
- [DllImport(nameof(Kernel32), SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool EnumResourceTypes(IntPtr hModule,
- EnumResTypeProc lpEnumFunc,
- IntPtr lParam);
-
- [DllImport(nameof(Kernel32), SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool EnumResourceNames(IntPtr hModule,
- IntPtr lpType,
- EnumResNameProc lpEnumFunc,
- IntPtr lParam);
-
- [DllImport(nameof(Kernel32), SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool EnumResourceLanguages(IntPtr hModule,
- IntPtr lpType,
- IntPtr lpName,
- EnumResLangProc lpEnumFunc,
- IntPtr lParam);
-
- public const int UserStoppedResourceEnumerationHRESULT = unchecked((int) 0x80073B02);
- public const int ResourceDataNotFoundHRESULT = unchecked((int) 0x80070714);
-
- // Querying and loading resources
-
- [DllImport(nameof(Kernel32), SetLastError = true)]
- public static extern IntPtr FindResourceEx(IntPtr hModule,
- IntPtr lpType,
- IntPtr lpName,
- ushort wLanguage);
-
- [DllImport(nameof(Kernel32), SetLastError = true)]
- public static extern IntPtr LoadResource(IntPtr hModule,
- IntPtr hResInfo);
-
- [DllImport(nameof(Kernel32))] // does not call SetLastError
- public static extern IntPtr LockResource(IntPtr hResData);
-
- [DllImport(nameof(Kernel32), SetLastError = true)]
- public static extern uint SizeofResource(IntPtr hModule,
- IntPtr hResInfo);
-
- public const int ERROR_CALL_NOT_IMPLEMENTED = 0x78;
- }
-
- ///
- /// Holds the update handle returned by BeginUpdateResource.
- /// Normally, native resources for the update handle are
- /// released by a call to ResourceUpdater.Update(). In case
- /// this doesn't happen, the SafeUpdateHandle will release the
- /// native resources for the update handle without updating
- /// the target file.
- ///
- private sealed class SafeUpdateHandle : SafeHandle
- {
- public SafeUpdateHandle() : base(IntPtr.Zero, true)
- {
- }
-
- public override bool IsInvalid => handle == IntPtr.Zero;
-
- protected override bool ReleaseHandle()
- {
- // discard pending updates without writing them
- return Kernel32.EndUpdateResource(handle, true);
- }
- }
-
- ///
- /// Holds the native handle for the resource update.
- ///
- private readonly SafeUpdateHandle hUpdate;
-
- ///
- /// Determines if the ResourceUpdater is supported by the current operating system.
- /// Some versions of Windows, such as Nano Server, do not support the needed APIs.
- ///
- public static bool IsSupportedOS()
- {
-#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER
- if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
- return false;
- }
-#endif
-
- try {
- // On Nano Server 1709+, `BeginUpdateResource` is exported but returns a null handle with a zero error
- // Try to call `BeginUpdateResource` with an invalid parameter; the error should be non-zero if supported
- // On Nano Server 20213, `BeginUpdateResource` fails with ERROR_CALL_NOT_IMPLEMENTED
- using (var handle = Kernel32.BeginUpdateResource("", false)) {
- int lastWin32Error = Marshal.GetLastWin32Error();
-
- if (handle.IsInvalid && (lastWin32Error == 0 || lastWin32Error == Kernel32.ERROR_CALL_NOT_IMPLEMENTED)) {
- return false;
- }
- }
- } catch (EntryPointNotFoundException) {
- // BeginUpdateResource isn't exported from Kernel32
- return false;
- }
-
- return true;
- }
-
- ///
- /// Create a resource updater for the given PE file. This will
- /// acquire a native resource update handle for the file,
- /// preparing it for updates. Resources can be added to this
- /// updater, which will queue them for update. The target PE
- /// file will not be modified until Update() is called, after
- /// which the ResourceUpdater can not be used for further
- /// updates.
- ///
- public ResourceUpdater(string peFile)
- {
- hUpdate = Kernel32.BeginUpdateResource(peFile, false);
- if (hUpdate.IsInvalid) {
- ThrowExceptionForLastWin32Error();
- }
- }
-
- ///
- /// Add all resources from a source PE file. It is assumed
- /// that the input is a valid PE file. If it is not, an
- /// exception will be thrown. This will not modify the target
- /// until Update() is called.
- /// Throws an InvalidOperationException if Update() was already called.
- ///
- public ResourceUpdater AddResourcesFromPEImage(string peFile)
- {
- if (hUpdate.IsInvalid) {
- ThrowExceptionForInvalidUpdate();
- }
-
- // Using both flags lets the OS loader decide how to load
- // it most efficiently. Either mode will prevent other
- // processes from modifying the module while it is loaded.
- IntPtr hModule = Kernel32.LoadLibraryEx(peFile, IntPtr.Zero,
- Kernel32.LoadLibraryFlags.LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE |
- Kernel32.LoadLibraryFlags.LOAD_LIBRARY_AS_IMAGE_RESOURCE);
- if (hModule == IntPtr.Zero) {
- ThrowExceptionForLastWin32Error();
- }
-
- var enumTypesCallback = new Kernel32.EnumResTypeProc(EnumAndUpdateTypesCallback);
- var errorInfo = new EnumResourcesErrorInfo();
- GCHandle errorInfoHandle = GCHandle.Alloc(errorInfo);
- var errorInfoPtr = GCHandle.ToIntPtr(errorInfoHandle);
-
- try {
- if (!Kernel32.EnumResourceTypes(hModule, enumTypesCallback, errorInfoPtr)) {
- if (Marshal.GetHRForLastWin32Error() != Kernel32.ResourceDataNotFoundHRESULT) {
- CaptureEnumResourcesErrorInfo(errorInfoPtr);
- errorInfo.ThrowException();
- }
- }
- } finally {
- errorInfoHandle.Free();
-
- if (!Kernel32.FreeLibrary(hModule)) {
- ThrowExceptionForLastWin32Error();
- }
- }
-
- return this;
- }
-
- internal static bool IsIntResource(IntPtr lpType)
- {
- return ((uint) lpType >> 16) == 0;
- }
-
- ///
- /// Add a language-neutral integer resource from a byte[] with
- /// a particular type and name. This will not modify the
- /// target until Update() is called.
- /// Throws an InvalidOperationException if Update() was already called.
- ///
- public ResourceUpdater AddResource(byte[] data, IntPtr lpType, IntPtr lpName)
- {
- if (hUpdate.IsInvalid) {
- ThrowExceptionForInvalidUpdate();
- }
-
- if (!IsIntResource(lpType) || !IsIntResource(lpName)) {
- throw new ArgumentException("AddResource can only be used with integer resource types");
- }
-
- if (!Kernel32.UpdateResource(hUpdate, lpType, lpName, Kernel32.LangID_LangNeutral_SublangNeutral, data, (uint) data.Length)) {
- ThrowExceptionForLastWin32Error();
- }
-
- return this;
- }
-
- ///
- /// Add a language-neutral integer resource from a byte[] with
- /// a particular type and name. This will not modify the
- /// target until Update() is called.
- /// Throws an InvalidOperationException if Update() was already called.
- ///
- public ResourceUpdater AddResource(byte[] data, string lpType, IntPtr lpName)
- {
- if (hUpdate.IsInvalid) {
- ThrowExceptionForInvalidUpdate();
- }
-
- if (!IsIntResource(lpName)) {
- throw new ArgumentException("AddResource can only be used with integer resource names");
- }
-
- if (!Kernel32.UpdateResource(hUpdate, lpType, lpName, Kernel32.LangID_LangNeutral_SublangNeutral, data, (uint) data.Length)) {
- ThrowExceptionForLastWin32Error();
- }
-
- return this;
- }
-
- ///
- /// Write the pending resource updates to the target PE
- /// file. After this, the ResourceUpdater no longer maintains
- /// an update handle, and can not be used for further updates.
- /// Throws an InvalidOperationException if Update() was already called.
- ///
- public void Update()
- {
- if (hUpdate.IsInvalid) {
- ThrowExceptionForInvalidUpdate();
- }
-
- try {
- if (!Kernel32.EndUpdateResource(hUpdate, false)) {
- ThrowExceptionForLastWin32Error();
- }
- } finally {
- hUpdate.SetHandleAsInvalid();
- }
- }
-
- private bool EnumAndUpdateTypesCallback(IntPtr hModule, IntPtr lpType, IntPtr lParam)
- {
- var enumNamesCallback = new Kernel32.EnumResNameProc(EnumAndUpdateNamesCallback);
- if (!Kernel32.EnumResourceNames(hModule, lpType, enumNamesCallback, lParam)) {
- CaptureEnumResourcesErrorInfo(lParam);
- return false;
- }
- return true;
- }
-
- private bool EnumAndUpdateNamesCallback(IntPtr hModule, IntPtr lpType, IntPtr lpName, IntPtr lParam)
- {
- var enumLanguagesCallback = new Kernel32.EnumResLangProc(EnumAndUpdateLanguagesCallback);
- if (!Kernel32.EnumResourceLanguages(hModule, lpType, lpName, enumLanguagesCallback, lParam)) {
- CaptureEnumResourcesErrorInfo(lParam);
- return false;
- }
- return true;
- }
-
- private bool EnumAndUpdateLanguagesCallback(IntPtr hModule, IntPtr lpType, IntPtr lpName, ushort wLang, IntPtr lParam)
- {
- IntPtr hResource = Kernel32.FindResourceEx(hModule, lpType, lpName, wLang);
- if (hResource == IntPtr.Zero) {
- CaptureEnumResourcesErrorInfo(lParam);
- return false;
- }
-
- // hResourceLoaded is just a handle to the resource, which
- // can be used to get the resource data
- IntPtr hResourceLoaded = Kernel32.LoadResource(hModule, hResource);
- if (hResourceLoaded == IntPtr.Zero) {
- CaptureEnumResourcesErrorInfo(lParam);
- return false;
- }
-
- // This doesn't actually lock memory. It just retrieves a
- // pointer to the resource data. The pointer is valid
- // until the module is unloaded.
- IntPtr lpResourceData = Kernel32.LockResource(hResourceLoaded);
- if (lpResourceData == IntPtr.Zero) {
- ((EnumResourcesErrorInfo) GCHandle.FromIntPtr(lParam).Target).failedToLockResource = true;
- }
-
- if (!Kernel32.UpdateResource(hUpdate, lpType, lpName, wLang, lpResourceData, Kernel32.SizeofResource(hModule, hResource))) {
- CaptureEnumResourcesErrorInfo(lParam);
- return false;
- }
-
- return true;
- }
-
- private class EnumResourcesErrorInfo
- {
- public int hResult;
- public bool failedToLockResource;
-
- public void ThrowException()
- {
- if (failedToLockResource) {
- Debug.Assert(hResult == 0);
- throw new ResourceNotAvailableException("Failed to lock resource");
- }
-
- Debug.Assert(hResult != 0);
- throw new Win32Exception(hResult);
- }
- }
-
- private static void CaptureEnumResourcesErrorInfo(IntPtr errorInfoPtr)
- {
- int hResult = Marshal.GetHRForLastWin32Error();
- if (hResult != Kernel32.UserStoppedResourceEnumerationHRESULT) {
- GCHandle errorInfoHandle = GCHandle.FromIntPtr(errorInfoPtr);
- var errorInfo = (EnumResourcesErrorInfo) errorInfoHandle.Target;
- errorInfo.hResult = hResult;
- }
- }
-
- private class ResourceNotAvailableException : Exception
- {
- public ResourceNotAvailableException(string message) : base(message)
- {
- }
- }
-
- private static void ThrowExceptionForLastWin32Error()
- {
- throw new Win32Exception(Marshal.GetHRForLastWin32Error());
- }
-
- private static void ThrowExceptionForInvalidUpdate()
- {
- throw new InvalidOperationException("Update handle is invalid. This instance may not be used for further updates");
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- public void Dispose(bool disposing)
- {
- if (disposing) {
- hUpdate.Dispose();
- }
- }
- }
-}
diff --git a/src/Velopack.Packaging.HostModel/Velopack.Packaging.HostModel.csproj b/src/Velopack.Packaging.HostModel/Velopack.Packaging.HostModel.csproj
deleted file mode 100644
index 054b9c4b..00000000
--- a/src/Velopack.Packaging.HostModel/Velopack.Packaging.HostModel.csproj
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
- net472;net6.0
- enable
- $(NoWarn);CA2007;CS8002;IDE0161
- true
- Microsoft.NET.HostModel
-
-
-
-
-
-
-
diff --git a/src/Velopack.Packaging.HostModel/packages.lock.json b/src/Velopack.Packaging.HostModel/packages.lock.json
deleted file mode 100644
index 4210d9a2..00000000
--- a/src/Velopack.Packaging.HostModel/packages.lock.json
+++ /dev/null
@@ -1,105 +0,0 @@
-{
- "version": 1,
- "dependencies": {
- ".NETFramework,Version=v4.7.2": {
- "Microsoft.SourceLink.GitHub": {
- "type": "Direct",
- "requested": "[8.0.0, )",
- "resolved": "8.0.0",
- "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
- "dependencies": {
- "Microsoft.Build.Tasks.Git": "8.0.0",
- "Microsoft.SourceLink.Common": "8.0.0"
- }
- },
- "Nerdbank.GitVersioning": {
- "type": "Direct",
- "requested": "[3.6.*, )",
- "resolved": "3.6.133",
- "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw=="
- },
- "System.Reflection.Metadata": {
- "type": "Direct",
- "requested": "[8.0.0, )",
- "resolved": "8.0.0",
- "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==",
- "dependencies": {
- "System.Collections.Immutable": "8.0.0",
- "System.Memory": "4.5.5"
- }
- },
- "Microsoft.Build.Tasks.Git": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
- },
- "Microsoft.SourceLink.Common": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
- },
- "System.Buffers": {
- "type": "Transitive",
- "resolved": "4.5.1",
- "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
- },
- "System.Collections.Immutable": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==",
- "dependencies": {
- "System.Memory": "4.5.5",
- "System.Runtime.CompilerServices.Unsafe": "6.0.0"
- }
- },
- "System.Memory": {
- "type": "Transitive",
- "resolved": "4.5.5",
- "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
- "dependencies": {
- "System.Buffers": "4.5.1",
- "System.Numerics.Vectors": "4.5.0",
- "System.Runtime.CompilerServices.Unsafe": "4.5.3"
- }
- },
- "System.Numerics.Vectors": {
- "type": "Transitive",
- "resolved": "4.5.0",
- "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ=="
- },
- "System.Runtime.CompilerServices.Unsafe": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
- }
- },
- "net6.0": {
- "Microsoft.SourceLink.GitHub": {
- "type": "Direct",
- "requested": "[8.0.0, )",
- "resolved": "8.0.0",
- "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
- "dependencies": {
- "Microsoft.Build.Tasks.Git": "8.0.0",
- "Microsoft.SourceLink.Common": "8.0.0"
- }
- },
- "Nerdbank.GitVersioning": {
- "type": "Direct",
- "requested": "[3.6.*, )",
- "resolved": "3.6.133",
- "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw=="
- },
- "Microsoft.Build.Tasks.Git": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
- },
- "Microsoft.SourceLink.Common": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/Velopack.Packaging.Unix/AppImageTool.cs b/src/Velopack.Packaging.Unix/AppImageTool.cs
index 38053df6..b18bd107 100644
--- a/src/Velopack.Packaging.Unix/AppImageTool.cs
+++ b/src/Velopack.Packaging.Unix/AppImageTool.cs
@@ -1,30 +1,138 @@
-using System.Runtime.Versioning;
+using ICSharpCode.SharpZipLib.Tar;
using Microsoft.Extensions.Logging;
+using Velopack.Compression;
namespace Velopack.Packaging.Unix;
public class AppImageTool
{
- [SupportedOSPlatform("linux")]
public static void CreateLinuxAppImage(string appDir, string outputFile, RuntimeCpu machine, ILogger logger)
{
- var tool = HelperFile.AppImageToolX64;
-
- string arch = machine switch {
- RuntimeCpu.x86 => "i386",
- RuntimeCpu.x64 => "x86_64",
- RuntimeCpu.arm64 => "arm_aarch64",
+ string runtime = machine switch {
+ RuntimeCpu.x86 => HelperFile.AppImageRuntimeX86,
+ RuntimeCpu.x64 => HelperFile.AppImageRuntimeX64,
+ RuntimeCpu.arm64 => HelperFile.AppImageRuntimeArm64,
_ => throw new ArgumentOutOfRangeException(nameof(machine), machine, null)
};
- var envVar = new Dictionary() {
- { "ARCH", arch }
- };
-
- logger.Info("About to create .AppImage for architecture: " + arch);
+ string tmpSquashFile = outputFile + ".tmpfs";
+ string tmpTarFile = outputFile + ".tmptar";
- Chmod.ChmodFileAsExecutable(tool);
- Exe.InvokeAndThrowIfNonZero(tool, new[] { appDir, outputFile }, null, envVar);
- Chmod.ChmodFileAsExecutable(outputFile);
+ try {
+ if (VelopackRuntimeInfo.IsWindows) {
+ // to workaround a permissions limitation of gensquashfs.exe
+ // we need to create a tar archive of the AppDir, setting Linux permissions in the tar header
+ // and then use tar2sqfs.exe to create the squashfs filesystem
+ logger.Info("Compressing AppDir into tar and setting file permissions");
+ using (var outStream = File.Create(tmpTarFile))
+ using (var tarArchive = TarArchive.CreateOutputTarArchive(outStream)) {
+ tarArchive.RootPath = Path.GetFullPath(appDir);
+ void AddDirectoryToTar(TarArchive tarArchive, DirectoryInfo dir)
+ {
+ var directories = dir.GetDirectories();
+ foreach (var directory in directories) {
+ AddDirectoryToTar(tarArchive, directory);
+ }
+
+ var filenames = dir.GetFiles();
+ foreach (var filename in filenames) {
+ var tarEntry = TarEntry.CreateEntryFromFile(filename.FullName);
+ tarEntry.TarHeader.Magic = "ustar";
+ tarEntry.TarHeader.Version = "00";
+ tarEntry.TarHeader.ModTime = EasyZip.ZipFormatMinDate;
+ tarEntry.TarHeader.Mode = Convert.ToInt32("755", 8);
+ tarArchive.WriteEntry(tarEntry, true);
+ }
+ }
+ AddDirectoryToTar(tarArchive, new DirectoryInfo(appDir));
+ }
+
+ logger.Info("Converting tar into squashfs filesystem");
+ var tool = HelperFile.FindHelperFile("squashfs-tools\\tar2sqfs.exe");
+ logger.Debug(Exe.RunHostedCommand($"\"{tool}\" \"{tmpSquashFile}\" < \"{tmpTarFile}\""));
+ } else {
+ Exe.AssertSystemBinaryExists("mksquashfs", "sudo apt install squashfs-tools", "brew install squashfs");
+ var tool = "mksquashfs";
+ List args =
+ [
+ appDir,
+ tmpSquashFile,
+ "-comp",
+ "xz",
+ "-root-owned",
+ "-noappend",
+ "-Xdict-size",
+ "100%",
+ "-b",
+ "16384",
+ "-mkfs-time",
+ "0",
+ ];
+ logger.Info("Compressing AppDir into squashfs filesystem");
+ logger.Debug(Exe.InvokeAndThrowIfNonZero(tool, args, null));
+ }
+
+ logger.Info($"Creating AppImage with {Path.GetFileName(runtime)} runtime");
+ File.Copy(runtime, outputFile, true);
+
+ using var outputfs = File.Open(outputFile, FileMode.Append);
+ using var squashfs = File.OpenRead(tmpSquashFile);
+ squashfs.CopyTo(outputfs);
+
+ Chmod.ChmodFileAsExecutable(outputFile);
+ } finally {
+ Utility.DeleteFileOrDirectoryHard(tmpSquashFile);
+ Utility.DeleteFileOrDirectoryHard(tmpTarFile);
+ }
+ }
+
+ public static void CreateLinuxAppImageOld(string appDir, string outputFile, RuntimeCpu machine, ILogger logger)
+ {
+ string runtime = machine switch {
+ RuntimeCpu.x86 => HelperFile.AppImageRuntimeX86,
+ RuntimeCpu.x64 => HelperFile.AppImageRuntimeX64,
+ RuntimeCpu.arm64 => HelperFile.AppImageRuntimeArm64,
+ _ => throw new ArgumentOutOfRangeException(nameof(machine), machine, null)
+ };
+
+ string tool = HelperFile.GetMkSquashFsPath();
+ List args = new();
+
+ string tmpPath = outputFile + ".tmpfs";
+ if (VelopackRuntimeInfo.IsWindows) {
+ args.Add("--all-root");
+ args.Add("--pack-dir");
+ args.Add(appDir);
+ args.Add(tmpPath);
+ } else {
+ args.Add(appDir);
+ args.Add(tmpPath);
+ args.Add("-comp");
+ args.Add("xz");
+ args.Add("-root-owned");
+ args.Add("-noappend");
+ args.Add("-Xdict-size");
+ args.Add("100%");
+ args.Add("-b");
+ args.Add("16384");
+ args.Add("-mkfs-time");
+ args.Add("0");
+ }
+
+ try {
+ logger.Info("Compressing AppDir into squashfs filesystem");
+ Exe.InvokeAndThrowIfNonZero(tool, args, null);
+
+ logger.Info($"Creating AppImage with {Path.GetFileName(runtime)} runtime");
+ File.Copy(runtime, outputFile, true);
+
+ using var outputfs = File.Open(outputFile, FileMode.Append);
+ using var squashfs = File.OpenRead(tmpPath);
+ squashfs.CopyTo(outputfs);
+
+ Chmod.ChmodFileAsExecutable(outputFile);
+ } finally {
+ Utility.DeleteFileOrDirectoryHard(tmpPath);
+ }
}
}
diff --git a/src/Velopack.Packaging.Unix/BinDetect.cs b/src/Velopack.Packaging.Unix/BinDetect.cs
new file mode 100644
index 00000000..369e3c71
--- /dev/null
+++ b/src/Velopack.Packaging.Unix/BinDetect.cs
@@ -0,0 +1,46 @@
+namespace Velopack.Packaging;
+
+public class BinDetect
+{
+ private enum MagicMachO : uint
+ {
+ MH_MAGIC = 0xfeedface,
+ MH_CIGAM = 0xcefaedfe,
+ MH_MAGIC_64 = 0xfeedfacf,
+ MH_CIGAM_64 = 0xcffaedfe
+ }
+
+ public static bool IsMachOImage(string filePath)
+ {
+ using (BinaryReader reader = new BinaryReader(File.OpenRead(filePath))) {
+ if (reader.BaseStream.Length < 256) // Header size
+ return false;
+
+ uint magic = reader.ReadUInt32();
+ return Enum.IsDefined(typeof(MagicMachO), magic);
+ }
+ }
+
+ // First four bytes of valid ELF, as defined in https://github.com/torvalds/linux/blob/aae703b/include/uapi/linux/elf.h
+ // 0x7f (DEL), 'E', 'L', 'F'
+ private static ReadOnlySpan ElfMagic => "\u007f"u8 + "ELF"u8;
+
+ public static bool IsElfImage(string filePath)
+ {
+ using FileStream fileStream = File.OpenRead(filePath);
+ using BinaryReader reader = new(fileStream);
+
+ if (reader.BaseStream.Length < 16) // EI_NIDENT = 16
+ {
+ return false;
+ }
+
+ byte[] eIdent = reader.ReadBytes(4);
+
+ return
+ eIdent[0] == ElfMagic[0] &&
+ eIdent[1] == ElfMagic[1] &&
+ eIdent[2] == ElfMagic[2] &&
+ eIdent[3] == ElfMagic[3];
+ }
+}
\ No newline at end of file
diff --git a/src/Velopack.Packaging.Unix/Chmod.cs b/src/Velopack.Packaging.Unix/Chmod.cs
index 79f91461..6ec899da 100644
--- a/src/Velopack.Packaging.Unix/Chmod.cs
+++ b/src/Velopack.Packaging.Unix/Chmod.cs
@@ -4,8 +4,6 @@ using System.Runtime.Versioning;
namespace Velopack.Packaging.Unix;
-[SupportedOSPlatform("linux")]
-[SupportedOSPlatform("macos")]
public class Chmod
{
private const string OSX_CSTD_LIB = "libSystem.dylib";
diff --git a/src/Velopack.Packaging.Unix/Commands/LinuxPackCommandRunner.cs b/src/Velopack.Packaging.Unix/Commands/LinuxPackCommandRunner.cs
index e827add3..f8ab224c 100644
--- a/src/Velopack.Packaging.Unix/Commands/LinuxPackCommandRunner.cs
+++ b/src/Velopack.Packaging.Unix/Commands/LinuxPackCommandRunner.cs
@@ -1,11 +1,9 @@
-using System.Runtime.Versioning;
-using ELFSharp.ELF;
+using ELFSharp.ELF;
using Microsoft.Extensions.Logging;
using Velopack.Packaging.Abstractions;
namespace Velopack.Packaging.Unix.Commands;
-[SupportedOSPlatform("linux")]
public class LinuxPackCommandRunner : PackageBuilder
{
protected string PortablePackagePath { get; set; }
@@ -21,36 +19,34 @@ public class LinuxPackCommandRunner : PackageBuilder
var bin = dir.CreateSubdirectory("usr").CreateSubdirectory("bin");
if (Options.PackIsAppDir) {
- Log.Info("Using provided .AppDir, will skip building new one.");
+ Log.Info("Using provided AppDir, will skip building new one.");
CopyFiles(new DirectoryInfo(Options.PackDirectory), dir, progress, true);
} else {
- Log.Info("Building new .AppDir");
+ Log.Info("Building automatic AppDir from pack directory");
var appRunPath = Path.Combine(dir.FullName, "AppRun");
// app icon
- var icon = Options.Icon ?? HelperFile.GetDefaultAppIcon();
+ var icon = Options.Icon ?? HelperFile.GetDefaultAppIcon(RuntimeOs.Linux);
var iconFilename = Options.PackId + Path.GetExtension(icon);
File.Copy(icon, Path.Combine(dir.FullName, iconFilename), true);
- var categories = String.IsNullOrWhiteSpace(Options.Categories)
- ? "Utility"
+ var categories = String.IsNullOrWhiteSpace(Options.Categories)
+ ? "Utility"
: Options.Categories.TrimEnd(';');
File.WriteAllText(appRunPath, $$"""
#!/bin/sh
-
if [ ! -z "$APPIMAGE" ] && [ ! -z "$APPDIR" ]; then
MD5=$(echo -n "file://$APPIMAGE" | md5sum | cut -d' ' -f1)
- cp "$APPDIR/{{iconFilename}}" "$HOME/.cache/thumbnails/normal/$MD5.png"
- cp "$APPDIR/{{iconFilename}}" "$HOME/.cache/thumbnails/large/$MD5.png"
- xdg-icon-resource forceupdate
+ cp "$APPDIR/{{iconFilename}}" "$HOME/.cache/thumbnails/normal/$MD5.png" >/dev/null 2>&1
+ cp "$APPDIR/{{iconFilename}}" "$HOME/.cache/thumbnails/large/$MD5.png" >/dev/null 2>&1
+ xdg-icon-resource forceupdate >/dev/null 2>&1
fi
-
HERE="$(dirname "$(readlink -f "${0}")")"
export PATH="${HERE}"/usr/bin/:"${PATH}"
EXEC=$(grep -e '^Exec=.*' "${HERE}"/*.desktop | head -n 1 | cut -d "=" -f 2 | cut -d " " -f 1 | sed 's/\\s/ /g')
exec "${EXEC}" "$@"
-""");
+""".Replace("\r", ""));
Chmod.ChmodFileAsExecutable(appRunPath);
var mainExeName = Options.EntryExecutableName ?? Options.PackId;
@@ -71,7 +67,7 @@ Icon={Options.PackId}
Exec={mainExeName}
StartupWMClass={Options.PackId}
Categories={categories};
-""");
+""".Replace("\r", ""));
// copy existing app files
CopyFiles(new DirectoryInfo(packDir), bin, progress, true);
@@ -79,7 +75,7 @@ Categories={categories};
// velopack required files
File.WriteAllText(Path.Combine(bin.FullName, "sq.version"), GenerateNuspecContent());
- File.Copy(HelperFile.GetUpdatePath(), Path.Combine(bin.FullName, "UpdateNix"), true);
+ File.Copy(HelperFile.GetUpdatePath(RuntimeOs.Linux), Path.Combine(bin.FullName, "UpdateNix"), true);
progress(100);
return Task.FromResult(dir.FullName);
}
diff --git a/src/Velopack.Packaging.Unix/Commands/OsxBundleCommandRunner.cs b/src/Velopack.Packaging.Unix/Commands/OsxBundleCommandRunner.cs
index 900c53f2..3fba6abc 100644
--- a/src/Velopack.Packaging.Unix/Commands/OsxBundleCommandRunner.cs
+++ b/src/Velopack.Packaging.Unix/Commands/OsxBundleCommandRunner.cs
@@ -25,7 +25,7 @@ public class OsxBundleCommandRunner : ICommand
public string Bundle(OsxBundleOptions options)
{
- var icon = options.Icon ?? HelperFile.GetDefaultAppIcon();
+ var icon = options.Icon ?? HelperFile.GetDefaultAppIcon(RuntimeOs.OSX);
var packId = options.PackId;
var packDirectory = options.PackDirectory;
var packVersion = options.PackVersion;
@@ -41,7 +41,7 @@ public class OsxBundleCommandRunner : ICommand
throw new UserInfoException($"--exeName '{mainExePath}' does not exist.");
}
- if (!MachO.IsMachOImage(mainExePath)) {
+ if (!BinDetect.IsMachOImage(mainExePath)) {
throw new UserInfoException($"--exeName '{mainExePath}' is not a mach-o executable.");
}
diff --git a/src/Velopack.Packaging.Unix/Commands/OsxPackCommandRunner.cs b/src/Velopack.Packaging.Unix/Commands/OsxPackCommandRunner.cs
index 4dbdca77..aee11b5e 100644
--- a/src/Velopack.Packaging.Unix/Commands/OsxPackCommandRunner.cs
+++ b/src/Velopack.Packaging.Unix/Commands/OsxPackCommandRunner.cs
@@ -32,10 +32,10 @@ public class OsxPackCommandRunner : PackageBuilder
var structure = new OsxStructureBuilder(dir.FullName);
var macosdir = structure.MacosDirectory;
File.WriteAllText(Path.Combine(macosdir, "sq.version"), GenerateNuspecContent());
- File.Copy(HelperFile.GetUpdatePath(), Path.Combine(macosdir, "UpdateMac"), true);
+ File.Copy(HelperFile.GetUpdatePath(RuntimeOs.OSX), Path.Combine(macosdir, "UpdateMac"), true);
foreach (var f in Directory.GetFiles(macosdir, "*", SearchOption.AllDirectories)) {
- if (MachO.IsMachOImage(f)) {
+ if (BinDetect.IsMachOImage(f)) {
Log.Debug(f + " is a mach-o binary, chmod as executable.");
Chmod.ChmodFileAsExecutable(f);
}
diff --git a/src/Velopack.Packaging.Unix/Velopack.Packaging.Unix.csproj b/src/Velopack.Packaging.Unix/Velopack.Packaging.Unix.csproj
index c5b9686b..6fd5560c 100644
--- a/src/Velopack.Packaging.Unix/Velopack.Packaging.Unix.csproj
+++ b/src/Velopack.Packaging.Unix/Velopack.Packaging.Unix.csproj
@@ -8,6 +8,7 @@
+
diff --git a/src/Velopack.Packaging.Unix/packages.lock.json b/src/Velopack.Packaging.Unix/packages.lock.json
index a5cf7e0f..9eeca3a2 100644
--- a/src/Velopack.Packaging.Unix/packages.lock.json
+++ b/src/Velopack.Packaging.Unix/packages.lock.json
@@ -21,8 +21,18 @@
"Nerdbank.GitVersioning": {
"type": "Direct",
"requested": "[3.6.*, )",
- "resolved": "3.6.133",
- "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw=="
+ "resolved": "3.6.139",
+ "contentHash": "rq0Ub/Jik7PtMtZtLn0tHuJ01Yt36RQ+eeBe+S7qnJ/EFOX6D4T9zuYD3vQPYKGI6Ro4t2iWgFm3fGDgjBrMfg=="
+ },
+ "SharpZipLib": {
+ "type": "Direct",
+ "requested": "[1.4.2, )",
+ "resolved": "1.4.2",
+ "contentHash": "yjj+3zgz8zgXpiiC3ZdF/iyTBbz2fFvMxZFEBPUcwZjIvXOf37Ylm+K58hqMfIBt5JgU/Z2uoUS67JmTLe973A==",
+ "dependencies": {
+ "System.Memory": "4.5.4",
+ "System.Threading.Tasks.Extensions": "4.5.2"
+ }
},
"Markdig": {
"type": "Transitive",
@@ -52,8 +62,8 @@
},
"Microsoft.Identity.Client": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "3TDPEie+9t/NgBhoNifLFwM6nkypYUa8GUHfMnkYtovDDTQM8bsS2KEIP5tAh28BgfJZgof/KlCEKvGxz0H3Eg==",
+ "resolved": "4.61.2",
+ "contentHash": "zXPDXjTju1yOZQzi5PeNmQfBzlxv+VlCfgfZ70VqyFuhaPtFXwvbLSlwCOMoVALR/MzOR6xkiadoyVDmWDmqKw==",
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "6.35.0",
"System.Diagnostics.DiagnosticSource": "6.0.1"
@@ -61,27 +71,27 @@
},
"Microsoft.Identity.Client.Broker": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "D5M7plyrXrQwhj/PILHLS7G3TqDg8rUO9lR+5awUSXueGxsdA2qHpeKiSR0sVfX6wxziWszOLDUR2vjFFKHPSg==",
+ "resolved": "4.61.2",
+ "contentHash": "+13pImNYfPGl04qZh3Rtq8wPfo55FScAUEio4ymPzeWJ/PE04jCsKH6ZIlrYbPCqe6t9u0lru1eZ+ho9Dsnvpw==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
- "Microsoft.Identity.Client.NativeInterop": "0.16.0"
+ "Microsoft.Identity.Client": "4.61.2",
+ "Microsoft.Identity.Client.NativeInterop": "0.16.1"
}
},
"Microsoft.Identity.Client.Extensions.Msal": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "tCX3c1bSkwH89AKjtB/oAOLlv661tOsEA8ULOCavOc6te/CKlqmMmFs8zMI58Qlv3XkbLOrswYr50IhDntiiPA==",
+ "resolved": "4.61.2",
+ "contentHash": "GCSBhzkr7/7DZDk2kYS9dfihB5ieM2CqwGAhtjuE6T1GtAqzuh62UJhMgO0dSx/PLgIYMzNHnh+pfHK8aWJQJQ==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
+ "Microsoft.Identity.Client": "4.61.2",
"System.IO.FileSystem.AccessControl": "5.0.0",
"System.Security.Cryptography.ProtectedData": "4.5.0"
}
},
"Microsoft.Identity.Client.NativeInterop": {
"type": "Transitive",
- "resolved": "0.16.0",
- "contentHash": "riaIH9tT4kRG4xKFxx+wpuo+VAZVOr1ElbNubcvpFXb82gjEmZtJRzpdiS4tdRYTVh6CUKNz9AINAptActTtBQ=="
+ "resolved": "0.16.1",
+ "contentHash": "OvY/+/AHESi24f5zOCf9kL4HXwPxXVQ3A+tMQsJvFk2DmP+sc88FYWL49zlku5q0bvx5yFvBLNpHQeRT9a6A5g=="
},
"Microsoft.IdentityModel.Abstractions": {
"type": "Transitive",
@@ -100,8 +110,8 @@
},
"NuGet.Versioning": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "ypnSvEtpNGo48bAWn95J1oHChycCXcevFSbn53fqzLxlXFSZP7dawu8p/7mHAfGufZQSV2sBpW80XQGIfXO8kQ=="
+ "resolved": "6.10.0",
+ "contentHash": "ytTA08tgZWo/Pbk333hnzQfiMyyynkkwMt4GyC5T0bzExYYSRNrkwv4jT8jmWr5LUOYsumEZvXdoven2SA7YZw=="
},
"System.Buffers": {
"type": "Transitive",
@@ -233,16 +243,16 @@
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Newtonsoft.Json": "[13.0.1, )",
- "NuGet.Versioning": "[6.9.1, )"
+ "NuGet.Versioning": "[6.10.0, )"
}
},
"velopack.packaging": {
"type": "Project",
"dependencies": {
"Markdig": "[0.37.0, )",
- "Microsoft.Identity.Client": "[4.61.0, )",
- "Microsoft.Identity.Client.Broker": "[4.61.0, )",
- "Microsoft.Identity.Client.Extensions.Msal": "[4.61.0, )",
+ "Microsoft.Identity.Client": "[4.61.2, )",
+ "Microsoft.Identity.Client.Broker": "[4.61.2, )",
+ "Microsoft.Identity.Client.Extensions.Msal": "[4.61.2, )",
"System.Linq.Async": "[6.0.1, )",
"System.Net.Http": "[4.3.4, )",
"Velopack": "[1.0.0, )"
@@ -269,8 +279,14 @@
"Nerdbank.GitVersioning": {
"type": "Direct",
"requested": "[3.6.*, )",
- "resolved": "3.6.133",
- "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw=="
+ "resolved": "3.6.139",
+ "contentHash": "rq0Ub/Jik7PtMtZtLn0tHuJ01Yt36RQ+eeBe+S7qnJ/EFOX6D4T9zuYD3vQPYKGI6Ro4t2iWgFm3fGDgjBrMfg=="
+ },
+ "SharpZipLib": {
+ "type": "Direct",
+ "requested": "[1.4.2, )",
+ "resolved": "1.4.2",
+ "contentHash": "yjj+3zgz8zgXpiiC3ZdF/iyTBbz2fFvMxZFEBPUcwZjIvXOf37Ylm+K58hqMfIBt5JgU/Z2uoUS67JmTLe973A=="
},
"Markdig": {
"type": "Transitive",
@@ -294,8 +310,8 @@
},
"Microsoft.Identity.Client": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "3TDPEie+9t/NgBhoNifLFwM6nkypYUa8GUHfMnkYtovDDTQM8bsS2KEIP5tAh28BgfJZgof/KlCEKvGxz0H3Eg==",
+ "resolved": "4.61.2",
+ "contentHash": "zXPDXjTju1yOZQzi5PeNmQfBzlxv+VlCfgfZ70VqyFuhaPtFXwvbLSlwCOMoVALR/MzOR6xkiadoyVDmWDmqKw==",
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "6.35.0",
"System.Diagnostics.DiagnosticSource": "6.0.1"
@@ -303,26 +319,26 @@
},
"Microsoft.Identity.Client.Broker": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "D5M7plyrXrQwhj/PILHLS7G3TqDg8rUO9lR+5awUSXueGxsdA2qHpeKiSR0sVfX6wxziWszOLDUR2vjFFKHPSg==",
+ "resolved": "4.61.2",
+ "contentHash": "+13pImNYfPGl04qZh3Rtq8wPfo55FScAUEio4ymPzeWJ/PE04jCsKH6ZIlrYbPCqe6t9u0lru1eZ+ho9Dsnvpw==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
- "Microsoft.Identity.Client.NativeInterop": "0.16.0"
+ "Microsoft.Identity.Client": "4.61.2",
+ "Microsoft.Identity.Client.NativeInterop": "0.16.1"
}
},
"Microsoft.Identity.Client.Extensions.Msal": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "tCX3c1bSkwH89AKjtB/oAOLlv661tOsEA8ULOCavOc6te/CKlqmMmFs8zMI58Qlv3XkbLOrswYr50IhDntiiPA==",
+ "resolved": "4.61.2",
+ "contentHash": "GCSBhzkr7/7DZDk2kYS9dfihB5ieM2CqwGAhtjuE6T1GtAqzuh62UJhMgO0dSx/PLgIYMzNHnh+pfHK8aWJQJQ==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
+ "Microsoft.Identity.Client": "4.61.2",
"System.Security.Cryptography.ProtectedData": "4.5.0"
}
},
"Microsoft.Identity.Client.NativeInterop": {
"type": "Transitive",
- "resolved": "0.16.0",
- "contentHash": "riaIH9tT4kRG4xKFxx+wpuo+VAZVOr1ElbNubcvpFXb82gjEmZtJRzpdiS4tdRYTVh6CUKNz9AINAptActTtBQ=="
+ "resolved": "0.16.1",
+ "contentHash": "OvY/+/AHESi24f5zOCf9kL4HXwPxXVQ3A+tMQsJvFk2DmP+sc88FYWL49zlku5q0bvx5yFvBLNpHQeRT9a6A5g=="
},
"Microsoft.IdentityModel.Abstractions": {
"type": "Transitive",
@@ -346,8 +362,8 @@
},
"NuGet.Versioning": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "ypnSvEtpNGo48bAWn95J1oHChycCXcevFSbn53fqzLxlXFSZP7dawu8p/7mHAfGufZQSV2sBpW80XQGIfXO8kQ=="
+ "resolved": "6.10.0",
+ "contentHash": "ytTA08tgZWo/Pbk333hnzQfiMyyynkkwMt4GyC5T0bzExYYSRNrkwv4jT8jmWr5LUOYsumEZvXdoven2SA7YZw=="
},
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
@@ -909,16 +925,16 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[6.0.0, )",
- "NuGet.Versioning": "[6.9.1, )"
+ "NuGet.Versioning": "[6.10.0, )"
}
},
"velopack.packaging": {
"type": "Project",
"dependencies": {
"Markdig": "[0.37.0, )",
- "Microsoft.Identity.Client": "[4.61.0, )",
- "Microsoft.Identity.Client.Broker": "[4.61.0, )",
- "Microsoft.Identity.Client.Extensions.Msal": "[4.61.0, )",
+ "Microsoft.Identity.Client": "[4.61.2, )",
+ "Microsoft.Identity.Client.Broker": "[4.61.2, )",
+ "Microsoft.Identity.Client.Extensions.Msal": "[4.61.2, )",
"System.Linq.Async": "[6.0.1, )",
"System.Net.Http": "[4.3.4, )",
"Velopack": "[1.0.0, )"
diff --git a/src/Velopack.Packaging.Windows/CodeSign.cs b/src/Velopack.Packaging.Windows/CodeSign.cs
index a794d133..515c8b2d 100644
--- a/src/Velopack.Packaging.Windows/CodeSign.cs
+++ b/src/Velopack.Packaging.Windows/CodeSign.cs
@@ -1,12 +1,10 @@
using System.Diagnostics;
-using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
using Velopack.Packaging.Exceptions;
namespace Velopack.Packaging.Windows;
-[SupportedOSPlatform("windows")]
public class CodeSign
{
public ILogger Log { get; }
@@ -88,7 +86,11 @@ public class CodeSign
if (signAsTemplate) {
command = signArguments.Replace("{{file}}", filesToSignStr);
} else {
- command = $"\"{HelperFile.SignToolPath}\" sign {signArguments} {filesToSignStr}";
+ if (VelopackRuntimeInfo.IsWindows) {
+ command = $"\"{HelperFile.SignToolPath}\" sign {signArguments} {filesToSignStr}";
+ } else {
+ throw new PlatformNotSupportedException("signtool.exe does not work on non-Windows platforms.");
+ }
}
RunSigningCommand(command, rootDir, signLogFile);
@@ -107,22 +109,29 @@ public class CodeSign
// about how the dotnet tool host works prevents signtool from being able to open a token password
// prompt, meaning signing fails for those with an HSM.
- string args = $"/S /C \"{command} >> \"{signLogFile}\" 2>&1\"";
+ var fileName = "cmd.exe";
+ var args = $"/S /C \"{command} >> \"{signLogFile}\" 2>&1\"";
+
+ if (!VelopackRuntimeInfo.IsWindows) {
+ fileName = "/bin/bash";
+ string escapedCommand = command.Replace("'", "'\\''");
+ args = $"-c '{escapedCommand} >> \"{signLogFile}\" 2>&1'";
+ }
var psi = new ProcessStartInfo {
- FileName = "cmd.exe",
+ FileName = fileName,
Arguments = args,
UseShellExecute = false,
WorkingDirectory = workDir,
CreateNoWindow = true,
};
- var process = Process.Start(psi);
+ using var process = Process.Start(psi);
process.WaitForExit();
if (process.ExitCode != 0) {
- var cmdWithPasswordHidden = "cmd.exe " + new Regex(@"\/p\s+?[^\s]+").Replace(command, "/p ********");
- Log.Debug($"Signing command failed: {cmdWithPasswordHidden}");
+ var cmdWithPasswordHidden = fileName + " " + new Regex(@"\/p\s+?[^\s]+").Replace(args, "/p ********");
+ Log.Debug($"Signing command failed - {Environment.NewLine} {cmdWithPasswordHidden}");
var output = File.Exists(signLogFile) ? File.ReadAllText(signLogFile).Trim() : "No output file was created.";
throw new UserInfoException(
$"Signing command failed. Specify --verbose argument to print signing command." + Environment.NewLine +
diff --git a/src/Velopack.Packaging.Windows/Commands/WindowsPackCommandRunner.cs b/src/Velopack.Packaging.Windows/Commands/WindowsPackCommandRunner.cs
index 059594dc..830dec8d 100644
--- a/src/Velopack.Packaging.Windows/Commands/WindowsPackCommandRunner.cs
+++ b/src/Velopack.Packaging.Windows/Commands/WindowsPackCommandRunner.cs
@@ -1,5 +1,4 @@
-using System.Runtime.Versioning;
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging;
using Velopack.Compression;
using Velopack.NuGet;
using Velopack.Packaging.Abstractions;
@@ -8,7 +7,6 @@ using Velopack.Windows;
namespace Velopack.Packaging.Windows.Commands;
-[SupportedOSPlatform("windows")]
public class WindowsPackCommandRunner : PackageBuilder
{
public WindowsPackCommandRunner(ILogger logger, IFancyConsole console)
@@ -48,7 +46,7 @@ public class WindowsPackCommandRunner : PackageBuilder
packDir = dir.FullName;
var updatePath = Path.Combine(TempDir.FullName, "Update.exe");
- File.Copy(HelperFile.GetUpdatePath(), updatePath, true);
+ File.Copy(HelperFile.GetUpdatePath(RuntimeOs.Windows), updatePath, true);
// check for and delete clickonce manifest
var clickonceManifests = Directory.EnumerateFiles(packDir, "*.application")
@@ -65,10 +63,10 @@ public class WindowsPackCommandRunner : PackageBuilder
}
// update icon for Update.exe if requested
- if (Options.Icon != null && VelopackRuntimeInfo.IsWindows) {
- Rcedit.SetExeIcon(updatePath, Options.Icon);
- } else if (Options.Icon != null) {
- Log.Warn("Unable to set icon for Update.exe (only supported on windows).");
+ if (Options.Icon != null) {
+ var editor = new ResourceEdit(updatePath, Log);
+ editor.SetExeIcon(Options.Icon);
+ editor.Commit();
}
File.Copy(updatePath, Path.Combine(packDir, "Squirrel.exe"), true);
@@ -176,11 +174,14 @@ public class WindowsPackCommandRunner : PackageBuilder
var bundledZip = new ZipPackage(releasePkg);
Utility.Retry(() => File.Copy(HelperFile.SetupPath, targetSetupExe, true));
progress(10);
- if (VelopackRuntimeInfo.IsWindows) {
- Rcedit.SetPEVersionBlockFromPackageInfo(targetSetupExe, bundledZip, Options.Icon);
- } else {
- Log.Warn("Unable to set PE Version on Setup.exe (only supported on windows)");
+
+ var editor = new ResourceEdit(targetSetupExe, Log);
+ editor.SetVersionInfo(bundledZip);
+ if (Options.Icon != null) {
+ editor.SetExeIcon(Options.Icon);
}
+ editor.Commit();
+
progress(25);
Log.Debug($"Creating Setup bundle");
SetupBundle.CreatePackageBundle(targetSetupExe, releasePkg);
@@ -231,15 +232,9 @@ public class WindowsPackCommandRunner : PackageBuilder
try {
Utility.Retry(() => File.Copy(HelperFile.StubExecutablePath, targetStubPath, true));
- Utility.Retry(() => {
- if (VelopackRuntimeInfo.IsWindows) {
- using var writer = new Microsoft.NET.HostModel.ResourceUpdater(targetStubPath, true);
- writer.AddResourcesFromPEImage(exeToCopy);
- writer.Update();
- } else {
- Log.Warn($"Cannot set resources/icon for {targetStubPath} (only supported on windows).");
- }
- });
+ var edit = new ResourceEdit(targetStubPath, Log);
+ edit.CopyResourcesFrom(exeToCopy);
+ edit.Commit();
} catch (Exception ex) {
Log.Error(ex, $"Error creating StubExecutable and copying resources for '{exeToCopy}'. This stub may or may not work properly.");
}
diff --git a/src/Velopack.Packaging.Windows/IcoExtract.cs b/src/Velopack.Packaging.Windows/IcoExtract.cs
new file mode 100644
index 00000000..3e9baf99
--- /dev/null
+++ b/src/Velopack.Packaging.Windows/IcoExtract.cs
@@ -0,0 +1,111 @@
+using System.Diagnostics.CodeAnalysis;
+using Ico.Codecs;
+using Ico.Host;
+using Ico.Model;
+using Ico.Validation;
+using Microsoft.Extensions.Logging;
+
+namespace Velopack.Packaging.Windows;
+
+public class IcoExtract
+{
+ private readonly ILogger _logger;
+ private readonly List _frames = new();
+
+ public IcoExtract(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public List ExtractFrames(FileInfo file)
+ {
+ var reporter = new IcoILoggerReporter(_logger);
+ var context = new ParseContext {
+ DisplayedPath = file.Name,
+ FullPath = file.FullName,
+ GeneratedFrames = new List(),
+ Reporter = reporter,
+ PngEncoder = new SixLabors.ImageSharp.Formats.Png.PngEncoder {
+ CompressionLevel = SixLabors.ImageSharp.Formats.Png.PngCompressionLevel.Level9,
+ ColorType = SixLabors.ImageSharp.Formats.Png.PngColorType.RgbWithAlpha,
+ },
+ AllowPaletteTruncation = StrictnessPolicy.Loose,
+ MaskedImagePixelEmitOptions = StrictnessPolicy.Loose,
+ };
+
+ _frames.Clear();
+ ExceptionWrapper.Try(() => DoExtractFile(context), context, reporter);
+ return _frames;
+ }
+
+ private void DoExtractFile(ParseContext context)
+ {
+ var length = new FileInfo(context.FullPath).Length;
+ var data = File.ReadAllBytes(context.FullPath);
+ IcoDecoder.DoFile(data, context, _frames.Add);
+ }
+
+ #region IErrorReporter
+
+ [ExcludeFromCodeCoverage]
+ private class IcoILoggerReporter : IErrorReporter
+ {
+ private readonly ILogger _logger;
+
+ public IcoILoggerReporter(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ void IErrorReporter.ErrorLine(IcoErrorCode errorCode, string message)
+ {
+ _logger.LogDebug($"Error{GenerateCode(errorCode)}: {message}");
+ }
+
+ void IErrorReporter.ErrorLine(IcoErrorCode errorCode, string message, string fileName)
+ {
+ _logger.LogDebug($"{fileName}: Error{GenerateCode(errorCode)}: {message}");
+ }
+
+ void IErrorReporter.ErrorLine(IcoErrorCode errorCode, string message, string fileName, uint frameNumber)
+ {
+ _logger.LogDebug($"{fileName}({frameNumber + 1}): Error{GenerateCode(errorCode)}: {message}");
+ }
+
+ void IErrorReporter.InfoLine(string message)
+ {
+ _logger.LogDebug(message);
+ }
+
+ void IErrorReporter.VerboseLine(string message)
+ {
+ _logger.LogDebug(message);
+ }
+
+ void IErrorReporter.WarnLine(IcoErrorCode errorCode, string message)
+ {
+ _logger.LogDebug($"Warning{GenerateCode(errorCode)}: {message}");
+ }
+
+ void IErrorReporter.WarnLine(IcoErrorCode errorCode, string message, string fileName)
+ {
+ _logger.LogDebug($"{fileName}: Warning{GenerateCode(errorCode)}: {message}");
+ }
+
+ void IErrorReporter.WarnLine(IcoErrorCode errorCode, string message, string fileName, uint frameNumber)
+ {
+ _logger.LogDebug($"{fileName}({frameNumber + 1}): Warning{GenerateCode(errorCode)}: {message}");
+ }
+
+ private string GenerateCode(IcoErrorCode code)
+ {
+ if (code == IcoErrorCode.NoError) {
+ return "";
+ } else {
+ return $" ICO{(uint) code}";
+ }
+ }
+ }
+
+ #endregion
+}
diff --git a/src/Velopack.Packaging.Windows/Rcedit.cs b/src/Velopack.Packaging.Windows/Rcedit.cs
deleted file mode 100644
index 2137fee6..00000000
--- a/src/Velopack.Packaging.Windows/Rcedit.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System.Runtime.Versioning;
-using Velopack.NuGet;
-
-namespace Velopack.Packaging.Windows;
-
-[SupportedOSPlatform("windows")]
-public class Rcedit
-{
- public static void SetExeIcon(string exePath, string iconPath)
- {
- var args = new[] { Path.GetFullPath(exePath), "--set-icon", iconPath };
- Utility.Retry(() => Exe.InvokeAndThrowIfNonZero(HelperFile.RceditPath, args, null));
- }
-
- [SupportedOSPlatform("windows")]
- public static void SetPEVersionBlockFromPackageInfo(string exePath, PackageManifest package, string iconPath = null)
- {
- var realExePath = Path.GetFullPath(exePath);
-
- List args = new List() {
- realExePath,
- "--set-version-string", "CompanyName", package.ProductCompany,
- "--set-version-string", "LegalCopyright", package.ProductCopyright,
- "--set-version-string", "FileDescription", package.ProductDescription,
- "--set-version-string", "ProductName", package.ProductName,
- "--set-file-version", package.Version.ToString(),
- "--set-product-version", package.Version.ToString(),
- };
-
- if (iconPath != null) {
- args.Add("--set-icon");
- args.Add(Path.GetFullPath(iconPath));
- }
-
- Utility.Retry(() => Exe.InvokeAndThrowIfNonZero(HelperFile.RceditPath, args, null));
- }
-}
diff --git a/src/Velopack.Packaging.Windows/ResourceEdit.cs b/src/Velopack.Packaging.Windows/ResourceEdit.cs
new file mode 100644
index 00000000..b5fb1be0
--- /dev/null
+++ b/src/Velopack.Packaging.Windows/ResourceEdit.cs
@@ -0,0 +1,194 @@
+using AsmResolver.PE;
+using AsmResolver.PE.File;
+using AsmResolver.PE.File.Headers;
+using AsmResolver.PE.Win32Resources;
+using AsmResolver.PE.Win32Resources.Builder;
+using AsmResolver.PE.Win32Resources.Icon;
+using AsmResolver.PE.Win32Resources.Version;
+using Microsoft.Extensions.Logging;
+using Velopack.NuGet;
+
+namespace Velopack.Packaging.Windows;
+
+public class ResourceEdit
+{
+ const ushort kLangNeutral = 0;
+ const ushort kCodePageUtf16 = 1200;
+
+ private readonly string _exePath;
+ private readonly ILogger _logger;
+ private readonly PEFile _file;
+ private readonly ushort _langId = kLangNeutral;
+
+ IResourceDirectory _resources;
+ private bool _disposed;
+
+ public ResourceEdit(string exeFile, ILogger logger)
+ {
+ _exePath = exeFile;
+ _logger = logger;
+ _file = PEFile.FromBytes(File.ReadAllBytes(exeFile));
+ var image = PEImage.FromFile(_file);
+ _resources = image.Resources ?? new ResourceDirectory((uint) 0);
+
+ // if there is already a manifest, we want to keep the existing language ID
+ try {
+ var existingInfo = VersionInfoResource.FromDirectory(_resources);
+ if (existingInfo != null) {
+ _langId = (ushort) existingInfo.Lcid;
+ }
+ } catch { }
+ }
+
+ public void SetExeIcon(string iconPath)
+ {
+ ThrowIfDisposed();
+
+ // should use this commented code once these issues are fixed in AsmResolver
+ // https://github.com/Washi1337/AsmResolver/issues/532
+ // https://github.com/Washi1337/AsmResolver/issues/533
+ // var iconResource = new IconResource();
+ // var group = new IconGroupDirectory();
+ // iconResource.AddEntry(1, group);
+ // iconResource.WriteToDirectory(_resources);
+
+ var group = new IconGroupDirectory() {
+ Type = 1,
+ };
+
+ var extractor = new IcoExtract(_logger);
+ var frames = extractor.ExtractFrames(new FileInfo(iconPath));
+
+ for (var p = 0; p < frames.Count; p++) {
+ var f = frames[p];
+
+ var dictEntry = new IconGroupDirectoryEntry() {
+ BytesInRes = (uint) f.RawData.Length,
+ Height = (byte) f.CookedData.Height,
+ Width = (byte) f.CookedData.Width,
+ Id = (ushort) (p + 1),
+ Reserved = 0,
+ PixelBitCount = (ushort) f.CookedData.PixelType.BitsPerPixel,
+ ColorCount = (byte) f.Encoding.PaletteSize,
+ ColorPlanes = 1,
+ };
+
+ var iconEntry = new IconEntry() {
+ RawIcon = f.RawData,
+ };
+
+ group.AddEntry(dictEntry, iconEntry);
+ group.Count++;
+ }
+
+ WriteToDirectory(_resources, new Dictionary { { 1, group } });
+ }
+
+ private void WriteToDirectory(IResourceDirectory rootDirectory, Dictionary _entries)
+ {
+ ThrowIfDisposed();
+
+ // this function can be removed once these issues are fixed in AsmResolver
+ // https://github.com/Washi1337/AsmResolver/issues/532
+ // https://github.com/Washi1337/AsmResolver/issues/533
+
+ var newIconDirectory = new ResourceDirectory(ResourceType.Icon);
+ foreach (var entry in _entries) {
+ foreach (var (groupEntry, iconEntry) in entry.Value.GetIconEntries()) {
+ newIconDirectory.Entries.Add(new ResourceDirectory(groupEntry.Id) { Entries = { new ResourceData(_langId, iconEntry) } });
+ }
+ }
+
+ var newGroupIconDirectory = new ResourceDirectory(ResourceType.GroupIcon);
+ foreach (var entry in _entries) {
+ newGroupIconDirectory.Entries.Add(new ResourceDirectory(entry.Key) { Entries = { new ResourceData(_langId, entry.Value) } });
+ }
+
+ rootDirectory.AddOrReplaceEntry(newIconDirectory);
+ rootDirectory.AddOrReplaceEntry(newGroupIconDirectory);
+ }
+
+ public void SetVersionInfo(PackageManifest package)
+ {
+ ThrowIfDisposed();
+
+ // We just replace the entire VersionInfo section, so we know that the
+ // VarFileInfo languages will be correct.
+ var fileVersion = new Version(package.Version.Major, package.Version.Minor, package.Version.Patch, 0);
+
+ var versionInfo = new VersionInfoResource(_langId);
+ versionInfo.FixedVersionInfo.FileOS = FileOS.NT;
+ versionInfo.FixedVersionInfo.FileType = FileType.App;
+ versionInfo.FixedVersionInfo.FileVersion = fileVersion;
+ versionInfo.FixedVersionInfo.ProductVersion = fileVersion;
+
+ StringFileInfo stringInfo = new StringFileInfo();
+ versionInfo.AddEntry(stringInfo);
+
+ VarFileInfo varInfo = new VarFileInfo();
+ versionInfo.AddEntry(varInfo);
+
+ var stringTable = new StringTable(_langId, kCodePageUtf16);
+ stringTable[StringTable.CompanyNameKey] = package.ProductCompany;
+ stringTable[StringTable.FileDescriptionKey] = package.ProductDescription;
+ stringTable[StringTable.FileVersionKey] = package.Version.ToFullString();
+ stringTable[StringTable.LegalCopyrightKey] = package.ProductCopyright;
+ stringTable[StringTable.ProductNameKey] = package.ProductName;
+ stringTable[StringTable.ProductVersionKey] = package.Version.ToFullString();
+ stringTable[StringTable.CommentsKey] = $"Generated by Velopack {VelopackRuntimeInfo.VelopackNugetVersion}";
+ stringInfo.Tables.Add(stringTable);
+
+ var varTable = new VarTable();
+ varTable.Values.Add(((uint) kCodePageUtf16 << 16) | _langId);
+ varInfo.Tables.Add(varTable);
+
+ versionInfo.WriteToDirectory(_resources);
+ }
+
+ public void CopyResourcesFrom(string otherExeFile)
+ {
+ ThrowIfDisposed();
+
+ var file = PEFile.FromBytes(File.ReadAllBytes(otherExeFile));
+ var image = PEImage.FromFile(file);
+ _resources = image.Resources;
+ }
+
+ public void Commit()
+ {
+ ThrowIfDisposed();
+ _disposed = true;
+
+ var sortedResources = new ResourceDirectory((uint) 0);
+ foreach (var entry in _resources.Entries.OrderBy(e => e.Id).ToArray()) {
+ _resources.RemoveEntry(entry.Id);
+ sortedResources.Entries.Add(entry);
+ }
+
+ var resourceBuffer = new ResourceDirectoryBuffer();
+ resourceBuffer.AddDirectory(sortedResources);
+
+ var resourceDirectory = _file.OptionalHeader.GetDataDirectory(DataDirectoryIndex.ResourceDirectory);
+
+ if (resourceDirectory.IsPresentInPE) {
+ var section = _file.GetSectionContainingRva(resourceDirectory.VirtualAddress);
+ section.Contents = resourceBuffer;
+ } else {
+ _file.Sections.Add(new PESection(".rsrc", SectionFlags.MemoryRead | SectionFlags.ContentInitializedData, resourceBuffer));
+ }
+
+ _file.UpdateHeaders();
+ _file.OptionalHeader.SetDataDirectory(DataDirectoryIndex.ResourceDirectory,
+ new DataDirectory(resourceBuffer.Rva, resourceBuffer.GetPhysicalSize()));
+
+ Utility.Retry(() => {
+ using var fs = File.Create(_exePath);
+ _file.Write(fs);
+ });
+ }
+
+ private void ThrowIfDisposed()
+ {
+ if (_disposed) throw new ObjectDisposedException(nameof(ResourceEdit));
+ }
+}
diff --git a/src/Velopack.Packaging.Windows/SetupBundle.cs b/src/Velopack.Packaging.Windows/SetupBundle.cs
index eed496b8..3f15dde4 100644
--- a/src/Velopack.Packaging.Windows/SetupBundle.cs
+++ b/src/Velopack.Packaging.Windows/SetupBundle.cs
@@ -1,6 +1,4 @@
using System.IO.MemoryMappedFiles;
-using Microsoft.NET.HostModel;
-using Microsoft.NET.HostModel.AppHost;
namespace Velopack.Packaging.Windows;
@@ -24,9 +22,9 @@ public static class SetupBundle
using var memoryMappedFile = MemoryMappedFile.CreateFromFile(setupPath, FileMode.Open, null, 0, MemoryMappedFileAccess.Read);
using MemoryMappedViewAccessor accessor = memoryMappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read);
- int position = BinaryUtils.SearchInFile(accessor, bundleSignature);
+ int position = SearchInFile(accessor, bundleSignature);
if (position == -1) {
- throw new PlaceHolderNotFoundInAppHostException(bundleSignature);
+ throw new Exception("PlaceHolderNotFoundInAppHostException");
}
offset = accessor.ReadInt64(position - 16);
@@ -74,11 +72,11 @@ public static class SetupBundle
Array.Copy(BitConverter.GetBytes(bundleLength), 0, data, 8, 8);
// replace the beginning of the placeholder with the bytes from 'data'
- RetryUtil.RetryOnIOError(() =>
- BinaryUtils.SearchAndReplace(setupPath, placeholder, data, pad0s: false));
+ RetryOnIOError(() =>
+ SearchAndReplace(setupPath, placeholder, data, pad0s: false));
// memory-mapped write does not updating last write time
- RetryUtil.RetryOnIOError(() =>
+ RetryOnIOError(() =>
File.SetLastWriteTimeUtc(setupPath, DateTime.UtcNow));
if (!IsBundle(setupPath, out var offset, out var length))
@@ -86,4 +84,237 @@ public static class SetupBundle
return bundleOffset;
}
+
+ internal static unsafe void SearchAndReplace(
+ MemoryMappedViewAccessor accessor,
+ byte[] searchPattern,
+ byte[] patternToReplace,
+ bool pad0s = true)
+ {
+ byte* pointer = null;
+
+ try {
+ accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
+ byte* bytes = pointer + accessor.PointerOffset;
+
+ int position = KMPSearch(searchPattern, bytes, accessor.Capacity);
+ if (position < 0) {
+ throw new Exception("PlaceHolderNotFoundInAppHostException");
+ }
+
+ accessor.WriteArray(
+ position: position,
+ array: patternToReplace,
+ offset: 0,
+ count: patternToReplace.Length);
+
+ if (pad0s) {
+ Pad0(searchPattern, patternToReplace, bytes, position);
+ }
+ } finally {
+ if (pointer != null) {
+ accessor.SafeMemoryMappedViewHandle.ReleasePointer();
+ }
+ }
+ }
+
+ private static unsafe void Pad0(byte[] searchPattern, byte[] patternToReplace, byte* bytes, int offset)
+ {
+ if (patternToReplace.Length < searchPattern.Length) {
+ for (int i = patternToReplace.Length; i < searchPattern.Length; i++) {
+ bytes[i + offset] = 0x0;
+ }
+ }
+ }
+
+ public static unsafe void SearchAndReplace(
+ string filePath,
+ byte[] searchPattern,
+ byte[] patternToReplace,
+ bool pad0s = true)
+ {
+ using (var mappedFile = MemoryMappedFile.CreateFromFile(filePath)) {
+ using (var accessor = mappedFile.CreateViewAccessor()) {
+ SearchAndReplace(accessor, searchPattern, patternToReplace, pad0s);
+ }
+ }
+ }
+
+ public static unsafe int SearchInFile(MemoryMappedViewAccessor accessor, byte[] searchPattern)
+ {
+ var safeBuffer = accessor.SafeMemoryMappedViewHandle;
+ return KMPSearch(searchPattern, (byte*) safeBuffer.DangerousGetHandle(), (int) safeBuffer.ByteLength);
+ }
+
+ public static unsafe int SearchInFile(string filePath, byte[] searchPattern)
+ {
+ using (var mappedFile = MemoryMappedFile.CreateFromFile(filePath, FileMode.Open, null, 0, MemoryMappedFileAccess.Read)) {
+ using (var accessor = mappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read)) {
+ return SearchInFile(accessor, searchPattern);
+ }
+ }
+ }
+
+ // See: https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm
+ private static int[] ComputeKMPFailureFunction(byte[] pattern)
+ {
+ int[] table = new int[pattern.Length];
+ if (pattern.Length >= 1) {
+ table[0] = -1;
+ }
+ if (pattern.Length >= 2) {
+ table[1] = 0;
+ }
+
+ int pos = 2;
+ int cnd = 0;
+ while (pos < pattern.Length) {
+ if (pattern[pos - 1] == pattern[cnd]) {
+ table[pos] = cnd + 1;
+ cnd++;
+ pos++;
+ } else if (cnd > 0) {
+ cnd = table[cnd];
+ } else {
+ table[pos] = 0;
+ pos++;
+ }
+ }
+ return table;
+ }
+
+ // See: https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm
+ private static unsafe int KMPSearch(byte[] pattern, byte* bytes, long bytesLength)
+ {
+ int m = 0;
+ int i = 0;
+ int[] table = ComputeKMPFailureFunction(pattern);
+
+ while (m + i < bytesLength) {
+ if (pattern[i] == bytes[m + i]) {
+ if (i == pattern.Length - 1) {
+ return m;
+ }
+ i++;
+ } else {
+ if (table[i] > -1) {
+ m = m + i - table[i];
+ i = table[i];
+ } else {
+ m++;
+ i = 0;
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ public static void CopyFile(string sourcePath, string destinationPath)
+ {
+ var destinationDirectory = new FileInfo(destinationPath).Directory.FullName;
+ if (!Directory.Exists(destinationDirectory)) {
+ Directory.CreateDirectory(destinationDirectory);
+ }
+
+ // Copy file to destination path so it inherits the same attributes/permissions.
+ File.Copy(sourcePath, destinationPath, overwrite: true);
+ }
+
+ internal static void WriteToStream(MemoryMappedViewAccessor sourceViewAccessor, FileStream fileStream, long length)
+ {
+ int pos = 0;
+ int bufSize = 16384; //16K
+
+ byte[] buf = new byte[bufSize];
+ length = Math.Min(length, sourceViewAccessor.Capacity);
+ do {
+ int bytesRequested = Math.Min((int) length - pos, bufSize);
+ if (bytesRequested <= 0) {
+ break;
+ }
+
+ int bytesRead = sourceViewAccessor.ReadArray(pos, buf, 0, bytesRequested);
+ if (bytesRead > 0) {
+ fileStream.Write(buf, 0, bytesRead);
+ pos += bytesRead;
+ }
+ }
+ while (true);
+ }
+
+ public const int NumberOfRetries = 500;
+ public const int NumMilliSecondsToWait = 100;
+
+ public static void RetryOnIOError(Action func)
+ {
+ for (int i = 1; i <= NumberOfRetries; i++) {
+ try {
+ func();
+ break;
+ } catch (IOException) when (i < NumberOfRetries) {
+ Thread.Sleep(NumMilliSecondsToWait);
+ }
+ }
+ }
+
+ public static void RetryOnWin32Error(Action func)
+ {
+ static bool IsKnownIrrecoverableError(int hresult)
+ {
+ // Error codes are defined in winerror.h
+ // The error code is stored in the lowest 16 bits of the HResult
+
+ switch (hresult & 0xffff) {
+ case 0x00000001: // ERROR_INVALID_FUNCTION
+ case 0x00000002: // ERROR_FILE_NOT_FOUND
+ case 0x00000003: // ERROR_PATH_NOT_FOUND
+ case 0x00000006: // ERROR_INVALID_HANDLE
+ case 0x00000008: // ERROR_NOT_ENOUGH_MEMORY
+ case 0x0000000B: // ERROR_BAD_FORMAT
+ case 0x0000000E: // ERROR_OUTOFMEMORY
+ case 0x0000000F: // ERROR_INVALID_DRIVE
+ case 0x00000012: // ERROR_NO_MORE_FILES
+ case 0x00000035: // ERROR_BAD_NETPATH
+ case 0x00000057: // ERROR_INVALID_PARAMETER
+ case 0x00000071: // ERROR_NO_MORE_SEARCH_HANDLES
+ case 0x00000072: // ERROR_INVALID_TARGET_HANDLE
+ case 0x00000078: // ERROR_CALL_NOT_IMPLEMENTED
+ case 0x0000007B: // ERROR_INVALID_NAME
+ case 0x0000007C: // ERROR_INVALID_LEVEL
+ case 0x0000007D: // ERROR_NO_VOLUME_LABEL
+ case 0x0000009A: // ERROR_LABEL_TOO_LONG
+ case 0x000000A0: // ERROR_BAD_ARGUMENTS
+ case 0x000000A1: // ERROR_BAD_PATHNAME
+ case 0x000000CE: // ERROR_FILENAME_EXCED_RANGE
+ case 0x000000DF: // ERROR_FILE_TOO_LARGE
+ case 0x000003ED: // ERROR_UNRECOGNIZED_VOLUME
+ case 0x000003EE: // ERROR_FILE_INVALID
+ case 0x00000651: // ERROR_DEVICE_REMOVED
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ for (int i = 1; i <= NumberOfRetries; i++) {
+ try {
+ func();
+ break;
+ } catch (HResultException hrex)
+ when (i < NumberOfRetries && !IsKnownIrrecoverableError(hrex.Win32HResult)) {
+ Thread.Sleep(NumMilliSecondsToWait);
+ }
+ }
+ }
+
+ public class HResultException : Exception
+ {
+ public readonly int Win32HResult;
+ public HResultException(int hResult) : base(hResult.ToString("X4"))
+ {
+ Win32HResult = hResult;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Velopack.Packaging.Windows/Velopack.Packaging.Windows.csproj b/src/Velopack.Packaging.Windows/Velopack.Packaging.Windows.csproj
index 3ff44a9f..fc896d17 100644
--- a/src/Velopack.Packaging.Windows/Velopack.Packaging.Windows.csproj
+++ b/src/Velopack.Packaging.Windows/Velopack.Packaging.Windows.csproj
@@ -4,10 +4,11 @@
net472;net6.0
enable
$(NoWarn);CA2007;CS8002
+ true
-
+
@@ -15,5 +16,5 @@
-
+
diff --git a/src/Velopack.Packaging.Windows/packages.lock.json b/src/Velopack.Packaging.Windows/packages.lock.json
index 779de4cd..2c487861 100644
--- a/src/Velopack.Packaging.Windows/packages.lock.json
+++ b/src/Velopack.Packaging.Windows/packages.lock.json
@@ -35,8 +35,8 @@
"Nerdbank.GitVersioning": {
"type": "Direct",
"requested": "[3.6.*, )",
- "resolved": "3.6.133",
- "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw=="
+ "resolved": "3.6.139",
+ "contentHash": "rq0Ub/Jik7PtMtZtLn0tHuJ01Yt36RQ+eeBe+S7qnJ/EFOX6D4T9zuYD3vQPYKGI6Ro4t2iWgFm3fGDgjBrMfg=="
},
"AsmResolver": {
"type": "Transitive",
@@ -87,8 +87,8 @@
},
"Microsoft.Identity.Client": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "3TDPEie+9t/NgBhoNifLFwM6nkypYUa8GUHfMnkYtovDDTQM8bsS2KEIP5tAh28BgfJZgof/KlCEKvGxz0H3Eg==",
+ "resolved": "4.61.2",
+ "contentHash": "zXPDXjTju1yOZQzi5PeNmQfBzlxv+VlCfgfZ70VqyFuhaPtFXwvbLSlwCOMoVALR/MzOR6xkiadoyVDmWDmqKw==",
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "6.35.0",
"System.Diagnostics.DiagnosticSource": "6.0.1"
@@ -96,27 +96,27 @@
},
"Microsoft.Identity.Client.Broker": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "D5M7plyrXrQwhj/PILHLS7G3TqDg8rUO9lR+5awUSXueGxsdA2qHpeKiSR0sVfX6wxziWszOLDUR2vjFFKHPSg==",
+ "resolved": "4.61.2",
+ "contentHash": "+13pImNYfPGl04qZh3Rtq8wPfo55FScAUEio4ymPzeWJ/PE04jCsKH6ZIlrYbPCqe6t9u0lru1eZ+ho9Dsnvpw==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
- "Microsoft.Identity.Client.NativeInterop": "0.16.0"
+ "Microsoft.Identity.Client": "4.61.2",
+ "Microsoft.Identity.Client.NativeInterop": "0.16.1"
}
},
"Microsoft.Identity.Client.Extensions.Msal": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "tCX3c1bSkwH89AKjtB/oAOLlv661tOsEA8ULOCavOc6te/CKlqmMmFs8zMI58Qlv3XkbLOrswYr50IhDntiiPA==",
+ "resolved": "4.61.2",
+ "contentHash": "GCSBhzkr7/7DZDk2kYS9dfihB5ieM2CqwGAhtjuE6T1GtAqzuh62UJhMgO0dSx/PLgIYMzNHnh+pfHK8aWJQJQ==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
+ "Microsoft.Identity.Client": "4.61.2",
"System.IO.FileSystem.AccessControl": "5.0.0",
"System.Security.Cryptography.ProtectedData": "4.5.0"
}
},
"Microsoft.Identity.Client.NativeInterop": {
"type": "Transitive",
- "resolved": "0.16.0",
- "contentHash": "riaIH9tT4kRG4xKFxx+wpuo+VAZVOr1ElbNubcvpFXb82gjEmZtJRzpdiS4tdRYTVh6CUKNz9AINAptActTtBQ=="
+ "resolved": "0.16.1",
+ "contentHash": "OvY/+/AHESi24f5zOCf9kL4HXwPxXVQ3A+tMQsJvFk2DmP+sc88FYWL49zlku5q0bvx5yFvBLNpHQeRT9a6A5g=="
},
"Microsoft.IdentityModel.Abstractions": {
"type": "Transitive",
@@ -135,23 +135,26 @@
},
"NuGet.Versioning": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "ypnSvEtpNGo48bAWn95J1oHChycCXcevFSbn53fqzLxlXFSZP7dawu8p/7mHAfGufZQSV2sBpW80XQGIfXO8kQ=="
+ "resolved": "6.10.0",
+ "contentHash": "ytTA08tgZWo/Pbk333hnzQfiMyyynkkwMt4GyC5T0bzExYYSRNrkwv4jT8jmWr5LUOYsumEZvXdoven2SA7YZw=="
+ },
+ "SixLabors.ImageSharp": {
+ "type": "Transitive",
+ "resolved": "2.1.8",
+ "contentHash": "ML1++vactR5xMW36wHA2nAX3vc6VpR62qYnpopQdLNGbP8BAJ/ckv1IrGIIFRUEQ0JS9EgWr1y1gEa/81f+HaA==",
+ "dependencies": {
+ "System.Buffers": "4.5.1",
+ "System.Memory": "4.5.4",
+ "System.Numerics.Vectors": "4.5.0",
+ "System.Runtime.CompilerServices.Unsafe": "5.0.0",
+ "System.Text.Encoding.CodePages": "5.0.0"
+ }
},
"System.Buffers": {
"type": "Transitive",
"resolved": "4.5.1",
"contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
},
- "System.Collections.Immutable": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==",
- "dependencies": {
- "System.Memory": "4.5.5",
- "System.Runtime.CompilerServices.Unsafe": "6.0.0"
- }
- },
"System.Diagnostics.DiagnosticSource": {
"type": "Transitive",
"resolved": "6.0.1",
@@ -206,15 +209,6 @@
"resolved": "4.5.0",
"contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ=="
},
- "System.Reflection.Metadata": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==",
- "dependencies": {
- "System.Collections.Immutable": "8.0.0",
- "System.Memory": "4.5.5"
- }
- },
"System.Runtime": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -273,6 +267,14 @@
"resolved": "5.0.0",
"contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA=="
},
+ "System.Text.Encoding.CodePages": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==",
+ "dependencies": {
+ "System.Runtime.CompilerServices.Unsafe": "5.0.0"
+ }
+ },
"System.Text.Encodings.Web": {
"type": "Transitive",
"resolved": "6.0.0",
@@ -316,26 +318,26 @@
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Newtonsoft.Json": "[13.0.1, )",
- "NuGet.Versioning": "[6.9.1, )"
+ "NuGet.Versioning": "[6.10.0, )"
+ }
+ },
+ "velopack.icolib": {
+ "type": "Project",
+ "dependencies": {
+ "SixLabors.ImageSharp": "[2.1.8, )"
}
},
"velopack.packaging": {
"type": "Project",
"dependencies": {
"Markdig": "[0.37.0, )",
- "Microsoft.Identity.Client": "[4.61.0, )",
- "Microsoft.Identity.Client.Broker": "[4.61.0, )",
- "Microsoft.Identity.Client.Extensions.Msal": "[4.61.0, )",
+ "Microsoft.Identity.Client": "[4.61.2, )",
+ "Microsoft.Identity.Client.Broker": "[4.61.2, )",
+ "Microsoft.Identity.Client.Extensions.Msal": "[4.61.2, )",
"System.Linq.Async": "[6.0.1, )",
"System.Net.Http": "[4.3.4, )",
"Velopack": "[1.0.0, )"
}
- },
- "velopack.packaging.hostmodel": {
- "type": "Project",
- "dependencies": {
- "System.Reflection.Metadata": "[8.0.0, )"
- }
}
},
"net6.0": {
@@ -372,8 +374,8 @@
"Nerdbank.GitVersioning": {
"type": "Direct",
"requested": "[3.6.*, )",
- "resolved": "3.6.133",
- "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw=="
+ "resolved": "3.6.139",
+ "contentHash": "rq0Ub/Jik7PtMtZtLn0tHuJ01Yt36RQ+eeBe+S7qnJ/EFOX6D4T9zuYD3vQPYKGI6Ro4t2iWgFm3fGDgjBrMfg=="
},
"AsmResolver": {
"type": "Transitive",
@@ -418,8 +420,8 @@
},
"Microsoft.Identity.Client": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "3TDPEie+9t/NgBhoNifLFwM6nkypYUa8GUHfMnkYtovDDTQM8bsS2KEIP5tAh28BgfJZgof/KlCEKvGxz0H3Eg==",
+ "resolved": "4.61.2",
+ "contentHash": "zXPDXjTju1yOZQzi5PeNmQfBzlxv+VlCfgfZ70VqyFuhaPtFXwvbLSlwCOMoVALR/MzOR6xkiadoyVDmWDmqKw==",
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "6.35.0",
"System.Diagnostics.DiagnosticSource": "6.0.1"
@@ -427,26 +429,26 @@
},
"Microsoft.Identity.Client.Broker": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "D5M7plyrXrQwhj/PILHLS7G3TqDg8rUO9lR+5awUSXueGxsdA2qHpeKiSR0sVfX6wxziWszOLDUR2vjFFKHPSg==",
+ "resolved": "4.61.2",
+ "contentHash": "+13pImNYfPGl04qZh3Rtq8wPfo55FScAUEio4ymPzeWJ/PE04jCsKH6ZIlrYbPCqe6t9u0lru1eZ+ho9Dsnvpw==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
- "Microsoft.Identity.Client.NativeInterop": "0.16.0"
+ "Microsoft.Identity.Client": "4.61.2",
+ "Microsoft.Identity.Client.NativeInterop": "0.16.1"
}
},
"Microsoft.Identity.Client.Extensions.Msal": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "tCX3c1bSkwH89AKjtB/oAOLlv661tOsEA8ULOCavOc6te/CKlqmMmFs8zMI58Qlv3XkbLOrswYr50IhDntiiPA==",
+ "resolved": "4.61.2",
+ "contentHash": "GCSBhzkr7/7DZDk2kYS9dfihB5ieM2CqwGAhtjuE6T1GtAqzuh62UJhMgO0dSx/PLgIYMzNHnh+pfHK8aWJQJQ==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
+ "Microsoft.Identity.Client": "4.61.2",
"System.Security.Cryptography.ProtectedData": "4.5.0"
}
},
"Microsoft.Identity.Client.NativeInterop": {
"type": "Transitive",
- "resolved": "0.16.0",
- "contentHash": "riaIH9tT4kRG4xKFxx+wpuo+VAZVOr1ElbNubcvpFXb82gjEmZtJRzpdiS4tdRYTVh6CUKNz9AINAptActTtBQ=="
+ "resolved": "0.16.1",
+ "contentHash": "OvY/+/AHESi24f5zOCf9kL4HXwPxXVQ3A+tMQsJvFk2DmP+sc88FYWL49zlku5q0bvx5yFvBLNpHQeRT9a6A5g=="
},
"Microsoft.IdentityModel.Abstractions": {
"type": "Transitive",
@@ -455,8 +457,8 @@
},
"Microsoft.NETCore.Platforms": {
"type": "Transitive",
- "resolved": "1.1.1",
- "contentHash": "TMBuzAHpTenGbGgk0SMTwyEkyijY/Eae4ZGsFNYJvAr/LDn1ku3Etp3FPxChmDp5HHF3kzJuoaa08N0xjqAJfQ=="
+ "resolved": "5.0.0",
+ "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ=="
},
"Microsoft.NETCore.Targets": {
"type": "Transitive",
@@ -470,8 +472,8 @@
},
"NuGet.Versioning": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "ypnSvEtpNGo48bAWn95J1oHChycCXcevFSbn53fqzLxlXFSZP7dawu8p/7mHAfGufZQSV2sBpW80XQGIfXO8kQ=="
+ "resolved": "6.10.0",
+ "contentHash": "ytTA08tgZWo/Pbk333hnzQfiMyyynkkwMt4GyC5T0bzExYYSRNrkwv4jT8jmWr5LUOYsumEZvXdoven2SA7YZw=="
},
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
@@ -571,6 +573,15 @@
"resolved": "4.3.2",
"contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg=="
},
+ "SixLabors.ImageSharp": {
+ "type": "Transitive",
+ "resolved": "2.1.8",
+ "contentHash": "ML1++vactR5xMW36wHA2nAX3vc6VpR62qYnpopQdLNGbP8BAJ/ckv1IrGIIFRUEQ0JS9EgWr1y1gEa/81f+HaA==",
+ "dependencies": {
+ "System.Runtime.CompilerServices.Unsafe": "5.0.0",
+ "System.Text.Encoding.CodePages": "5.0.0"
+ }
+ },
"System.Collections": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -1010,6 +1021,14 @@
"System.Runtime": "4.3.0"
}
},
+ "System.Text.Encoding.CodePages": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "5.0.0"
+ }
+ },
"System.Text.Encodings.Web": {
"type": "Transitive",
"resolved": "6.0.0",
@@ -1050,23 +1069,26 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[6.0.0, )",
- "NuGet.Versioning": "[6.9.1, )"
+ "NuGet.Versioning": "[6.10.0, )"
+ }
+ },
+ "velopack.icolib": {
+ "type": "Project",
+ "dependencies": {
+ "SixLabors.ImageSharp": "[2.1.8, )"
}
},
"velopack.packaging": {
"type": "Project",
"dependencies": {
"Markdig": "[0.37.0, )",
- "Microsoft.Identity.Client": "[4.61.0, )",
- "Microsoft.Identity.Client.Broker": "[4.61.0, )",
- "Microsoft.Identity.Client.Extensions.Msal": "[4.61.0, )",
+ "Microsoft.Identity.Client": "[4.61.2, )",
+ "Microsoft.Identity.Client.Broker": "[4.61.2, )",
+ "Microsoft.Identity.Client.Extensions.Msal": "[4.61.2, )",
"System.Linq.Async": "[6.0.1, )",
"System.Net.Http": "[4.3.4, )",
"Velopack": "[1.0.0, )"
}
- },
- "velopack.packaging.hostmodel": {
- "type": "Project"
}
}
}
diff --git a/src/Velopack.Packaging/Exe.cs b/src/Velopack.Packaging/Exe.cs
index db5a40cd..e348593d 100644
--- a/src/Velopack.Packaging/Exe.cs
+++ b/src/Velopack.Packaging/Exe.cs
@@ -5,7 +5,7 @@ namespace Velopack.Packaging;
public static class Exe
{
- public static void AssertSystemBinaryExists(string binaryName)
+ public static void AssertSystemBinaryExists(string binaryName, string linuxInstallCmd, string osxInstallCmd)
{
try {
if (VelopackRuntimeInfo.IsWindows) {
@@ -20,10 +20,52 @@ public static class Exe
throw new PlatformNotSupportedException();
}
} catch (ProcessFailedException) {
- throw new Exception($"Could not find '{binaryName}' on the system, ensure it is installed and on the PATH.");
+ string recommendedCmd = null;
+ if (VelopackRuntimeInfo.IsLinux && !String.IsNullOrEmpty(linuxInstallCmd))
+ recommendedCmd = linuxInstallCmd;
+ else if (VelopackRuntimeInfo.IsOSX && !String.IsNullOrEmpty(osxInstallCmd))
+ recommendedCmd = osxInstallCmd;
+
+ string message = $"Could not find '{binaryName}' binary on the system, ensure it is installed and on the PATH.";
+ if (!String.IsNullOrEmpty(recommendedCmd)) {
+ message += $" You might be able to install it by running: '{recommendedCmd}'";
+ }
+
+ throw new UserInfoException(message);
}
}
+ public static string RunHostedCommand(string command, string workDir = null)
+ {
+ using var _1 = Utility.GetTempFileName(out var outputFile);
+ File.Create(outputFile).Close();
+
+ var fileName = "cmd.exe";
+ var args = $"/S /C \"{command} >> \"{outputFile}\" 2>&1\"";
+
+ if (!VelopackRuntimeInfo.IsWindows) {
+ fileName = "/bin/bash";
+ string escapedCommand = command.Replace("'", "'\\''");
+ args = $"-c '{escapedCommand} >> \"{outputFile}\" 2>&1'";
+ }
+
+ var psi = new ProcessStartInfo {
+ FileName = fileName,
+ Arguments = args,
+ UseShellExecute = false,
+ WorkingDirectory = workDir,
+ CreateNoWindow = true,
+ };
+
+ using var process = Process.Start(psi);
+ process.WaitForExit();
+
+ var stdout = Utility.Retry(() => File.ReadAllText(outputFile).Trim(), 10, 1000);
+ var result = (process.ExitCode, stdout, command);
+ ProcessFailedException.ThrowIfNonZero(result);
+ return result.Item2;
+ }
+
public static string InvokeAndThrowIfNonZero(string exePath, IEnumerable args, string workingDir, IDictionary envVar = null)
{
var result = InvokeProcess(exePath, args, workingDir, CancellationToken.None, envVar);
diff --git a/src/Velopack.Packaging/Flow/VelopackFlowServiceClient.cs b/src/Velopack.Packaging/Flow/VelopackFlowServiceClient.cs
index 03ca6c4e..5919b7fb 100644
--- a/src/Velopack.Packaging/Flow/VelopackFlowServiceClient.cs
+++ b/src/Velopack.Packaging/Flow/VelopackFlowServiceClient.cs
@@ -19,7 +19,7 @@ public interface IVelopackFlowServiceClient
Task GetProfileAsync(VelopackServiceOptions? options, CancellationToken cancellationToken);
- Task UploadLatestReleaseAssetsAsync(string? channel, string releaseDirectory, string? serviceUrl, CancellationToken cancellationToken);
+ Task UploadLatestReleaseAssetsAsync(string? channel, string releaseDirectory, string? serviceUrl, RuntimeOs os, CancellationToken cancellationToken);
}
public class VelopackFlowServiceClient(HttpClient HttpClient, ILogger Logger) : IVelopackFlowServiceClient
@@ -91,10 +91,11 @@ public class VelopackFlowServiceClient(HttpClient HttpClient, ILogger Logger) :
return await HttpClient.GetFromJsonAsync(endpoint, cancellationToken);
}
- public async Task UploadLatestReleaseAssetsAsync(string? channel, string releaseDirectory, string? serviceUrl, CancellationToken cancellationToken)
+ public async Task UploadLatestReleaseAssetsAsync(string? channel, string releaseDirectory, string? serviceUrl,
+ RuntimeOs os, CancellationToken cancellationToken)
{
- channel ??= ReleaseEntryHelper.GetDefaultChannel(VelopackRuntimeInfo.SystemOs);
- ReleaseEntryHelper helper = new(releaseDirectory, channel, Logger);
+ channel ??= ReleaseEntryHelper.GetDefaultChannel(os);
+ ReleaseEntryHelper helper = new(releaseDirectory, channel, Logger, os);
var latestAssets = helper.GetLatestAssets().ToList();
List installers = [];
@@ -107,13 +108,13 @@ public class VelopackFlowServiceClient(HttpClient HttpClient, ILogger Logger) :
version = latestAssets[0].Version;
if (VelopackRuntimeInfo.IsWindows || VelopackRuntimeInfo.IsOSX) {
- var setupName = ReleaseEntryHelper.GetSuggestedSetupName(packageId, channel);
+ var setupName = ReleaseEntryHelper.GetSuggestedSetupName(packageId, channel, os);
if (File.Exists(Path.Combine(releaseDirectory, setupName))) {
installers.Add(setupName);
}
}
- var portableName = ReleaseEntryHelper.GetSuggestedPortableName(packageId, channel);
+ var portableName = ReleaseEntryHelper.GetSuggestedPortableName(packageId, channel, os);
if (File.Exists(Path.Combine(releaseDirectory, portableName))) {
installers.Add(portableName);
}
diff --git a/src/Velopack.Packaging/Flow/VelopackServiceOptions.cs b/src/Velopack.Packaging/Flow/VelopackServiceOptions.cs
index fb411bfc..9844d56f 100644
--- a/src/Velopack.Packaging/Flow/VelopackServiceOptions.cs
+++ b/src/Velopack.Packaging/Flow/VelopackServiceOptions.cs
@@ -5,5 +5,6 @@ public class VelopackServiceOptions
public const string DefaultBaseUrl = "https://api.velopack.io/";
public string VelopackBaseUrl { get; set; } = DefaultBaseUrl;
+
public string ApiKey { get; set; } = string.Empty;
}
diff --git a/src/Velopack.Packaging/HelperFile.cs b/src/Velopack.Packaging/HelperFile.cs
index 4de1ea51..8a4b48f4 100644
--- a/src/Velopack.Packaging/HelperFile.cs
+++ b/src/Velopack.Packaging/HelperFile.cs
@@ -4,12 +4,11 @@ namespace Velopack.Packaging;
public static class HelperFile
{
- public static string GetUpdateExeName(RuntimeOs? os = null)
+ public static string GetUpdateExeName(RuntimeOs os)
{
- var _os = os ?? VelopackRuntimeInfo.SystemOs;
- switch (_os) {
+ switch (os) {
case RuntimeOs.Windows:
- return FindHelperFile("Update.exe");
+ return FindHelperFile("update.exe");
#if DEBUG
case RuntimeOs.Linux:
return FindHelperFile("update");
@@ -26,38 +25,43 @@ public static class HelperFile
}
}
- public static string GetUpdatePath(RuntimeOs? os = null) => FindHelperFile(GetUpdateExeName(os));
+ public static string GetUpdatePath(RuntimeOs os) => FindHelperFile(GetUpdateExeName(os));
public static string GetZstdPath()
{
if (VelopackRuntimeInfo.IsWindows)
return FindHelperFile("zstd.exe");
- Exe.AssertSystemBinaryExists("zstd");
+ Exe.AssertSystemBinaryExists("zstd", "sudo apt install zstd", "brew install zstd");
return "zstd";
}
+ public static string GetMkSquashFsPath()
+ {
+ if (VelopackRuntimeInfo.IsWindows)
+ return FindHelperFile("squashfs-tools\\gensquashfs.exe");
+ Exe.AssertSystemBinaryExists("mksquashfs", "sudo apt install squashfs-tools", "brew install squashfs");
+ return "mksquashfs";
+ }
+
[SupportedOSPlatform("macos")]
public static string VelopackEntitlements => FindHelperFile("Velopack.entitlements");
- [SupportedOSPlatform("linux")]
- public static string AppImageToolX64 => FindHelperFile("appimagetool-x86_64.AppImage");
+ public static string AppImageRuntimeArm64 => FindHelperFile("appimagekit-runtime-aarch64");
- [SupportedOSPlatform("windows")]
- public static string SetupPath => FindHelperFile("Setup.exe");
+ public static string AppImageRuntimeX64 => FindHelperFile("appimagekit-runtime-x86_64");
+
+ public static string AppImageRuntimeX86 => FindHelperFile("appimagekit-runtime-i686");
+
+ public static string SetupPath => FindHelperFile("setup.exe");
- [SupportedOSPlatform("windows")]
public static string StubExecutablePath => FindHelperFile("stub.exe");
[SupportedOSPlatform("windows")]
public static string SignToolPath => FindHelperFile("signtool.exe");
- [SupportedOSPlatform("windows")]
- public static string RceditPath => FindHelperFile("rcedit.exe");
-
- public static string GetDefaultAppIcon(RuntimeOs? os = null)
+ public static string GetDefaultAppIcon(RuntimeOs os)
{
- var _os = os ?? VelopackRuntimeInfo.SystemOs;
- switch (_os) {
+ switch (os) {
case RuntimeOs.Windows:
return null;
case RuntimeOs.Linux:
diff --git a/src/Velopack.Packaging/PackageBuilder.cs b/src/Velopack.Packaging/PackageBuilder.cs
index d4ef2c4f..e9745ca9 100644
--- a/src/Velopack.Packaging/PackageBuilder.cs
+++ b/src/Velopack.Packaging/PackageBuilder.cs
@@ -14,7 +14,7 @@ namespace Velopack.Packaging;
public abstract class PackageBuilder : ICommand
where T : class, IPackOptions
{
- protected RuntimeOs SupportedTargetOs { get; }
+ protected RuntimeOs TargetOs { get; }
protected ILogger Log { get; }
@@ -36,25 +36,28 @@ public abstract class PackageBuilder : ICommand
public PackageBuilder(RuntimeOs supportedOs, ILogger logger, IFancyConsole console)
{
- SupportedTargetOs = supportedOs;
+ TargetOs = supportedOs;
Log = logger;
Console = console;
}
public async Task Run(T options)
{
- if (options.TargetRuntime?.BaseRID != SupportedTargetOs)
- throw new UserInfoException($"To build packages for {SupportedTargetOs.GetOsLongName()}, " +
- $"the target rid must be {SupportedTargetOs} (actually was {options.TargetRuntime?.BaseRID}).");
+ if (options.TargetRuntime?.BaseRID != TargetOs) {
+ throw new UserInfoException($"To build packages for {TargetOs.GetOsLongName()}, " +
+ $"the target rid must be {TargetOs} (actually was {options.TargetRuntime?.BaseRID}). " +
+ $"If your real intention was to cross-compile a release for {options.TargetRuntime?.BaseRID} then you " +
+ $"should provide an OS directive: eg. 'vpk [{options.TargetRuntime?.BaseRID.GetOsShortName()}] pack ...'");
+ }
Log.Info($"Beginning to package Velopack release {options.PackVersion}.");
Log.Info("Releases Directory: " + options.ReleaseDir.FullName);
var releaseDir = options.ReleaseDir;
- var channel = options.Channel?.ToLower() ?? ReleaseEntryHelper.GetDefaultChannel(SupportedTargetOs);
+ var channel = options.Channel?.ToLower() ?? ReleaseEntryHelper.GetDefaultChannel(TargetOs);
Channel = channel;
- var entryHelper = new ReleaseEntryHelper(releaseDir.FullName, channel, Log);
+ var entryHelper = new ReleaseEntryHelper(releaseDir.FullName, channel, Log, TargetOs);
if (entryHelper.DoesSimilarVersionExist(SemanticVersion.Parse(options.PackVersion))) {
if (await Console.PromptYesNo("A release in this channel with the same or greater version already exists. Do you want to continue and potentially overwrite files?") != true) {
throw new UserInfoException($"There is a release in channel {channel} which is equal or greater to the current version {options.PackVersion}. Please increase the current package version or remove that release.");
@@ -110,35 +113,35 @@ public abstract class PackageBuilder : ICommand
packDirectory = await PreprocessPackDir(progress, packDirectory);
});
- if (VelopackRuntimeInfo.IsWindows || VelopackRuntimeInfo.IsOSX) {
+ if (TargetOs != RuntimeOs.Linux) {
await ctx.RunTask("Code-sign application", async (progress) => {
await CodeSign(progress, packDirectory);
});
}
Task portableTask = null;
- if (VelopackRuntimeInfo.IsLinux || !Options.NoPortable) {
+ if (TargetOs == RuntimeOs.Linux || !Options.NoPortable) {
portableTask = ctx.RunTask("Building portable package", async (progress) => {
- var suggestedName = ReleaseEntryHelper.GetSuggestedPortableName(packId, channel);
+ var suggestedName = ReleaseEntryHelper.GetSuggestedPortableName(packId, channel, TargetOs);
var path = getIncompletePath(suggestedName);
await CreatePortablePackage(progress, packDirectory, path);
});
}
// TODO: hack, this is a prerequisite for building full package but only on linux
- if (VelopackRuntimeInfo.IsLinux) await portableTask;
+ if (TargetOs == RuntimeOs.Linux) await portableTask;
string releasePath = null;
await ctx.RunTask($"Building release {packVersion}", async (progress) => {
- var suggestedName = ReleaseEntryHelper.GetSuggestedReleaseName(packId, packVersion, channel, false);
+ var suggestedName = ReleaseEntryHelper.GetSuggestedReleaseName(packId, packVersion, channel, false, TargetOs);
releasePath = getIncompletePath(suggestedName);
await CreateReleasePackage(progress, packDirectory, releasePath);
});
Task setupTask = null;
- if (!Options.NoInst && (VelopackRuntimeInfo.IsWindows || VelopackRuntimeInfo.IsOSX)) {
+ if (!Options.NoInst && TargetOs != RuntimeOs.Linux) {
setupTask = ctx.RunTask("Building setup package", async (progress) => {
- var suggestedName = ReleaseEntryHelper.GetSuggestedSetupName(packId, channel);
+ var suggestedName = ReleaseEntryHelper.GetSuggestedSetupName(packId, channel, TargetOs);
var path = getIncompletePath(suggestedName);
await CreateSetupPackage(progress, releasePath, packDirectory, path);
});
@@ -146,12 +149,12 @@ public abstract class PackageBuilder : ICommand
if (prev != null && options.DeltaMode != DeltaMode.None) {
await ctx.RunTask($"Building delta {prev.Version} -> {packVersion}", async (progress) => {
- var suggestedName = ReleaseEntryHelper.GetSuggestedReleaseName(packId, packVersion, channel, true);
+ var suggestedName = ReleaseEntryHelper.GetSuggestedReleaseName(packId, packVersion, channel, true, TargetOs);
var deltaPkg = await CreateDeltaPackage(progress, releasePath, prev.PackageFile, getIncompletePath(suggestedName), options.DeltaMode);
});
}
- if (!VelopackRuntimeInfo.IsLinux && portableTask != null) await portableTask;
+ if (TargetOs != RuntimeOs.Linux && portableTask != null) await portableTask;
if (setupTask != null) await setupTask;
await ctx.RunTask("Post-process steps", (progress) => {
diff --git a/src/Velopack.Packaging/ReleaseEntryHelper.cs b/src/Velopack.Packaging/ReleaseEntryHelper.cs
index b78ca0b8..23e1a65c 100644
--- a/src/Velopack.Packaging/ReleaseEntryHelper.cs
+++ b/src/Velopack.Packaging/ReleaseEntryHelper.cs
@@ -1,4 +1,4 @@
-using System.Text;
+using System.Text;
using Microsoft.Extensions.Logging;
using NuGet.Versioning;
using Velopack.Json;
@@ -13,11 +13,11 @@ public class ReleaseEntryHelper
private readonly string _channel;
private Dictionary> _releases;
- public ReleaseEntryHelper(string outputDir, string channel, ILogger logger)
+ public ReleaseEntryHelper(string outputDir, string channel, ILogger logger, RuntimeOs os)
{
_outputDir = outputDir;
_logger = logger;
- _channel = channel ?? GetDefaultChannel();
+ _channel = channel ?? GetDefaultChannel(os);
_releases = GetReleasesFromDir(outputDir);
}
@@ -153,20 +153,20 @@ public class ReleaseEntryHelper
return Encoding.UTF8.GetString(ms.ToArray());
}
- public static string GetSuggestedReleaseName(string id, string version, string channel, bool delta)
+ public static string GetSuggestedReleaseName(string id, string version, string channel, bool delta, RuntimeOs os)
{
var suffix = GetUniqueAssetSuffix(channel);
version = SemanticVersion.Parse(version).ToNormalizedString();
- if (VelopackRuntimeInfo.IsWindows && channel == GetDefaultChannel(RuntimeOs.Windows)) {
+ if (os == RuntimeOs.Windows && channel == GetDefaultChannel(RuntimeOs.Windows)) {
return $"{id}-{version}{(delta ? "-delta" : "-full")}.nupkg";
}
return $"{id}-{version}{suffix}{(delta ? "-delta" : "-full")}.nupkg";
}
- public static string GetSuggestedPortableName(string id, string channel)
+ public static string GetSuggestedPortableName(string id, string channel, RuntimeOs os)
{
var suffix = GetUniqueAssetSuffix(channel);
- if (VelopackRuntimeInfo.IsLinux) {
+ if (os == RuntimeOs.Linux) {
if (channel == GetDefaultChannel(RuntimeOs.Linux)) {
return $"{id}.AppImage";
} else {
@@ -177,12 +177,12 @@ public class ReleaseEntryHelper
}
}
- public static string GetSuggestedSetupName(string id, string channel)
+ public static string GetSuggestedSetupName(string id, string channel, RuntimeOs os)
{
var suffix = GetUniqueAssetSuffix(channel);
- if (VelopackRuntimeInfo.IsWindows)
+ if (os == RuntimeOs.Windows)
return $"{id}{suffix}-Setup.exe";
- else if (VelopackRuntimeInfo.IsOSX)
+ else if (os == RuntimeOs.OSX)
return $"{id}{suffix}-Setup.pkg";
else
throw new PlatformNotSupportedException("Platform not supported.");
@@ -193,9 +193,8 @@ public class ReleaseEntryHelper
return "-" + channel;
}
- public static string GetDefaultChannel(RuntimeOs? os = null)
+ public static string GetDefaultChannel(RuntimeOs os)
{
- os ??= VelopackRuntimeInfo.SystemOs;
if (os == RuntimeOs.Windows) return "win";
if (os == RuntimeOs.OSX) return "osx";
if (os == RuntimeOs.Linux) return "linux";
diff --git a/src/Velopack.Packaging/Velopack.Packaging.csproj b/src/Velopack.Packaging/Velopack.Packaging.csproj
index e49951cd..0d93bf8c 100644
--- a/src/Velopack.Packaging/Velopack.Packaging.csproj
+++ b/src/Velopack.Packaging/Velopack.Packaging.csproj
@@ -15,9 +15,9 @@
-
-
-
+
+
+
diff --git a/src/Velopack.Packaging/packages.lock.json b/src/Velopack.Packaging/packages.lock.json
index 9962ea2d..8db18076 100644
--- a/src/Velopack.Packaging/packages.lock.json
+++ b/src/Velopack.Packaging/packages.lock.json
@@ -13,9 +13,9 @@
},
"Microsoft.Identity.Client": {
"type": "Direct",
- "requested": "[4.61.0, )",
- "resolved": "4.61.0",
- "contentHash": "3TDPEie+9t/NgBhoNifLFwM6nkypYUa8GUHfMnkYtovDDTQM8bsS2KEIP5tAh28BgfJZgof/KlCEKvGxz0H3Eg==",
+ "requested": "[4.61.2, )",
+ "resolved": "4.61.2",
+ "contentHash": "zXPDXjTju1yOZQzi5PeNmQfBzlxv+VlCfgfZ70VqyFuhaPtFXwvbLSlwCOMoVALR/MzOR6xkiadoyVDmWDmqKw==",
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "6.35.0",
"System.Diagnostics.DiagnosticSource": "6.0.1"
@@ -23,21 +23,21 @@
},
"Microsoft.Identity.Client.Broker": {
"type": "Direct",
- "requested": "[4.61.0, )",
- "resolved": "4.61.0",
- "contentHash": "D5M7plyrXrQwhj/PILHLS7G3TqDg8rUO9lR+5awUSXueGxsdA2qHpeKiSR0sVfX6wxziWszOLDUR2vjFFKHPSg==",
+ "requested": "[4.61.2, )",
+ "resolved": "4.61.2",
+ "contentHash": "+13pImNYfPGl04qZh3Rtq8wPfo55FScAUEio4ymPzeWJ/PE04jCsKH6ZIlrYbPCqe6t9u0lru1eZ+ho9Dsnvpw==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
- "Microsoft.Identity.Client.NativeInterop": "0.16.0"
+ "Microsoft.Identity.Client": "4.61.2",
+ "Microsoft.Identity.Client.NativeInterop": "0.16.1"
}
},
"Microsoft.Identity.Client.Extensions.Msal": {
"type": "Direct",
- "requested": "[4.61.0, )",
- "resolved": "4.61.0",
- "contentHash": "tCX3c1bSkwH89AKjtB/oAOLlv661tOsEA8ULOCavOc6te/CKlqmMmFs8zMI58Qlv3XkbLOrswYr50IhDntiiPA==",
+ "requested": "[4.61.2, )",
+ "resolved": "4.61.2",
+ "contentHash": "GCSBhzkr7/7DZDk2kYS9dfihB5ieM2CqwGAhtjuE6T1GtAqzuh62UJhMgO0dSx/PLgIYMzNHnh+pfHK8aWJQJQ==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
+ "Microsoft.Identity.Client": "4.61.2",
"System.IO.FileSystem.AccessControl": "5.0.0",
"System.Security.Cryptography.ProtectedData": "4.5.0"
}
@@ -55,8 +55,8 @@
"Nerdbank.GitVersioning": {
"type": "Direct",
"requested": "[3.6.*, )",
- "resolved": "3.6.133",
- "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw=="
+ "resolved": "3.6.139",
+ "contentHash": "rq0Ub/Jik7PtMtZtLn0tHuJ01Yt36RQ+eeBe+S7qnJ/EFOX6D4T9zuYD3vQPYKGI6Ro4t2iWgFm3fGDgjBrMfg=="
},
"System.Linq.Async": {
"type": "Direct",
@@ -96,8 +96,8 @@
},
"Microsoft.Identity.Client.NativeInterop": {
"type": "Transitive",
- "resolved": "0.16.0",
- "contentHash": "riaIH9tT4kRG4xKFxx+wpuo+VAZVOr1ElbNubcvpFXb82gjEmZtJRzpdiS4tdRYTVh6CUKNz9AINAptActTtBQ=="
+ "resolved": "0.16.1",
+ "contentHash": "OvY/+/AHESi24f5zOCf9kL4HXwPxXVQ3A+tMQsJvFk2DmP+sc88FYWL49zlku5q0bvx5yFvBLNpHQeRT9a6A5g=="
},
"Microsoft.IdentityModel.Abstractions": {
"type": "Transitive",
@@ -116,8 +116,8 @@
},
"NuGet.Versioning": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "ypnSvEtpNGo48bAWn95J1oHChycCXcevFSbn53fqzLxlXFSZP7dawu8p/7mHAfGufZQSV2sBpW80XQGIfXO8kQ=="
+ "resolved": "6.10.0",
+ "contentHash": "ytTA08tgZWo/Pbk333hnzQfiMyyynkkwMt4GyC5T0bzExYYSRNrkwv4jT8jmWr5LUOYsumEZvXdoven2SA7YZw=="
},
"System.Buffers": {
"type": "Transitive",
@@ -233,7 +233,7 @@
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Newtonsoft.Json": "[13.0.1, )",
- "NuGet.Versioning": "[6.9.1, )"
+ "NuGet.Versioning": "[6.10.0, )"
}
}
},
@@ -246,9 +246,9 @@
},
"Microsoft.Identity.Client": {
"type": "Direct",
- "requested": "[4.61.0, )",
- "resolved": "4.61.0",
- "contentHash": "3TDPEie+9t/NgBhoNifLFwM6nkypYUa8GUHfMnkYtovDDTQM8bsS2KEIP5tAh28BgfJZgof/KlCEKvGxz0H3Eg==",
+ "requested": "[4.61.2, )",
+ "resolved": "4.61.2",
+ "contentHash": "zXPDXjTju1yOZQzi5PeNmQfBzlxv+VlCfgfZ70VqyFuhaPtFXwvbLSlwCOMoVALR/MzOR6xkiadoyVDmWDmqKw==",
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "6.35.0",
"System.Diagnostics.DiagnosticSource": "6.0.1"
@@ -256,21 +256,21 @@
},
"Microsoft.Identity.Client.Broker": {
"type": "Direct",
- "requested": "[4.61.0, )",
- "resolved": "4.61.0",
- "contentHash": "D5M7plyrXrQwhj/PILHLS7G3TqDg8rUO9lR+5awUSXueGxsdA2qHpeKiSR0sVfX6wxziWszOLDUR2vjFFKHPSg==",
+ "requested": "[4.61.2, )",
+ "resolved": "4.61.2",
+ "contentHash": "+13pImNYfPGl04qZh3Rtq8wPfo55FScAUEio4ymPzeWJ/PE04jCsKH6ZIlrYbPCqe6t9u0lru1eZ+ho9Dsnvpw==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
- "Microsoft.Identity.Client.NativeInterop": "0.16.0"
+ "Microsoft.Identity.Client": "4.61.2",
+ "Microsoft.Identity.Client.NativeInterop": "0.16.1"
}
},
"Microsoft.Identity.Client.Extensions.Msal": {
"type": "Direct",
- "requested": "[4.61.0, )",
- "resolved": "4.61.0",
- "contentHash": "tCX3c1bSkwH89AKjtB/oAOLlv661tOsEA8ULOCavOc6te/CKlqmMmFs8zMI58Qlv3XkbLOrswYr50IhDntiiPA==",
+ "requested": "[4.61.2, )",
+ "resolved": "4.61.2",
+ "contentHash": "GCSBhzkr7/7DZDk2kYS9dfihB5ieM2CqwGAhtjuE6T1GtAqzuh62UJhMgO0dSx/PLgIYMzNHnh+pfHK8aWJQJQ==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
+ "Microsoft.Identity.Client": "4.61.2",
"System.Security.Cryptography.ProtectedData": "4.5.0"
}
},
@@ -287,8 +287,8 @@
"Nerdbank.GitVersioning": {
"type": "Direct",
"requested": "[3.6.*, )",
- "resolved": "3.6.133",
- "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw=="
+ "resolved": "3.6.139",
+ "contentHash": "rq0Ub/Jik7PtMtZtLn0tHuJ01Yt36RQ+eeBe+S7qnJ/EFOX6D4T9zuYD3vQPYKGI6Ro4t2iWgFm3fGDgjBrMfg=="
},
"System.Linq.Async": {
"type": "Direct",
@@ -350,8 +350,8 @@
},
"Microsoft.Identity.Client.NativeInterop": {
"type": "Transitive",
- "resolved": "0.16.0",
- "contentHash": "riaIH9tT4kRG4xKFxx+wpuo+VAZVOr1ElbNubcvpFXb82gjEmZtJRzpdiS4tdRYTVh6CUKNz9AINAptActTtBQ=="
+ "resolved": "0.16.1",
+ "contentHash": "OvY/+/AHESi24f5zOCf9kL4HXwPxXVQ3A+tMQsJvFk2DmP+sc88FYWL49zlku5q0bvx5yFvBLNpHQeRT9a6A5g=="
},
"Microsoft.IdentityModel.Abstractions": {
"type": "Transitive",
@@ -375,8 +375,8 @@
},
"NuGet.Versioning": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "ypnSvEtpNGo48bAWn95J1oHChycCXcevFSbn53fqzLxlXFSZP7dawu8p/7mHAfGufZQSV2sBpW80XQGIfXO8kQ=="
+ "resolved": "6.10.0",
+ "contentHash": "ytTA08tgZWo/Pbk333hnzQfiMyyynkkwMt4GyC5T0bzExYYSRNrkwv4jT8jmWr5LUOYsumEZvXdoven2SA7YZw=="
},
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
@@ -897,7 +897,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[6.0.0, )",
- "NuGet.Versioning": "[6.9.1, )"
+ "NuGet.Versioning": "[6.10.0, )"
}
}
}
diff --git a/src/Velopack.Vpk/Commands/Flow/PublishCommandRunner.cs b/src/Velopack.Vpk/Commands/Flow/PublishCommandRunner.cs
index 00a1823c..4004d4ec 100644
--- a/src/Velopack.Vpk/Commands/Flow/PublishCommandRunner.cs
+++ b/src/Velopack.Vpk/Commands/Flow/PublishCommandRunner.cs
@@ -19,6 +19,7 @@ public class PublishCommandRunner(IVelopackFlowServiceClient Client) : ICommand<
return;
}
- await Client.UploadLatestReleaseAssetsAsync(options.Channel, options.ReleaseDirectory, options.VelopackBaseUrl, token);
+ await Client.UploadLatestReleaseAssetsAsync(options.Channel, options.ReleaseDirectory,
+ options.VelopackBaseUrl, options.TargetOs, token);
}
}
diff --git a/src/Velopack.Vpk/Commands/Flow/PublishOptions.cs b/src/Velopack.Vpk/Commands/Flow/PublishOptions.cs
index ce2cbfbd..8e051320 100644
--- a/src/Velopack.Vpk/Commands/Flow/PublishOptions.cs
+++ b/src/Velopack.Vpk/Commands/Flow/PublishOptions.cs
@@ -5,6 +5,8 @@ namespace Velopack.Vpk.Commands.Flow;
#nullable enable
public sealed class PublishOptions : VelopackServiceOptions
{
+ public RuntimeOs TargetOs { get; set; }
+
public string ReleaseDirectory { get; set; } = "";
public string? Channel { get; set; }
diff --git a/src/Velopack.Vpk/Commands/_BaseCommand.cs b/src/Velopack.Vpk/Commands/_BaseCommand.cs
index b09a00b7..03cf5805 100644
--- a/src/Velopack.Vpk/Commands/_BaseCommand.cs
+++ b/src/Velopack.Vpk/Commands/_BaseCommand.cs
@@ -5,6 +5,8 @@ namespace Velopack.Vpk.Commands;
public class BaseCommand : CliCommand
{
+ public RuntimeOs TargetOs { get; private set; }
+
private readonly Dictionary> _setters = new();
private readonly Dictionary _envHelp = new();
@@ -60,17 +62,18 @@ public class BaseCommand : CliCommand
public string GetEnvVariableName(CliOption option) => _envHelp.ContainsKey(option) ? _envHelp[option] : null;
- public virtual void SetProperties(ParseResult context, IConfiguration config)
+ public virtual void SetProperties(ParseResult context, IConfiguration config, RuntimeOs targetOs)
{
+ TargetOs = targetOs;
foreach (var kvp in _setters) {
kvp.Value(context, config);
}
}
- public virtual ParseResult ParseAndApply(string command, IConfiguration config = null)
+ public virtual ParseResult ParseAndApply(string command, IConfiguration config = null, RuntimeOs? targetOs = null)
{
var x = Parse(command);
- SetProperties(x, config ?? new ConfigurationManager());
+ SetProperties(x, config ?? new ConfigurationManager(), targetOs ?? VelopackRuntimeInfo.SystemOs);
return x;
}
}
diff --git a/src/Velopack.Vpk/Commands/_PlatformCommand.cs b/src/Velopack.Vpk/Commands/_PlatformCommand.cs
index 54131708..63862d6c 100644
--- a/src/Velopack.Vpk/Commands/_PlatformCommand.cs
+++ b/src/Velopack.Vpk/Commands/_PlatformCommand.cs
@@ -11,11 +11,6 @@ public abstract class PlatformCommand : OutputCommand
TargetRuntimeOption = AddOption((v) => TargetRuntime = v, "-r", "--runtime")
.SetDescription("The target runtime to build packages for.")
.SetArgumentHelpName("RID")
- .SetDefault(VelopackRuntimeInfo.SystemOs.GetOsShortName())
.MustBeSupportedRid();
}
-
- public RID GetRid() => RID.Parse(TargetRuntime ?? VelopackRuntimeInfo.SystemOs.GetOsShortName());
-
- public RuntimeOs GetRuntimeOs() => GetRid().BaseRID;
}
diff --git a/src/Velopack.Vpk/Logging/BasicConsole.cs b/src/Velopack.Vpk/Logging/BasicConsole.cs
index 027f274a..c1102947 100644
--- a/src/Velopack.Vpk/Logging/BasicConsole.cs
+++ b/src/Velopack.Vpk/Logging/BasicConsole.cs
@@ -5,9 +5,9 @@ namespace Velopack.Vpk.Logging;
public class BasicConsole : IFancyConsole
{
private readonly ILogger logger;
- private readonly DefaultPromptValueFactory defaultFactory;
+ private readonly VelopackDefaults defaultFactory;
- public BasicConsole(ILogger logger, DefaultPromptValueFactory defaultFactory)
+ public BasicConsole(ILogger logger, VelopackDefaults defaultFactory)
{
this.logger = logger;
this.defaultFactory = defaultFactory;
diff --git a/src/Velopack.Vpk/Logging/DefaultPromptValueFactory.cs b/src/Velopack.Vpk/Logging/DefaultPromptValueFactory.cs
deleted file mode 100644
index c4350654..00000000
--- a/src/Velopack.Vpk/Logging/DefaultPromptValueFactory.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace Velopack.Vpk.Logging;
-
-public record DefaultPromptValueFactory(bool DefaultPromptValue)
-{
-}
diff --git a/src/Velopack.Vpk/Logging/SpectreConsole.cs b/src/Velopack.Vpk/Logging/SpectreConsole.cs
index 7b645b27..63a6321e 100644
--- a/src/Velopack.Vpk/Logging/SpectreConsole.cs
+++ b/src/Velopack.Vpk/Logging/SpectreConsole.cs
@@ -7,9 +7,9 @@ namespace Velopack.Vpk.Logging;
public class SpectreConsole : IFancyConsole
{
private readonly ILogger logger;
- private readonly DefaultPromptValueFactory defaultFactory;
+ private readonly VelopackDefaults defaultFactory;
- public SpectreConsole(ILogger logger, DefaultPromptValueFactory defaultFactory)
+ public SpectreConsole(ILogger logger, VelopackDefaults defaultFactory)
{
this.logger = logger;
this.defaultFactory = defaultFactory;
diff --git a/src/Velopack.Vpk/Program.cs b/src/Velopack.Vpk/Program.cs
index fd0a8398..0a39a312 100644
--- a/src/Velopack.Vpk/Program.cs
+++ b/src/Velopack.Vpk/Program.cs
@@ -37,17 +37,32 @@ public class Program
.SetRecursive(true)
.SetDescription("'yes' by instead of 'no' in non-interactive prompts.");
+ public static CliDirective WindowsDirective { get; } = new CliDirective("win") {
+ Description = "Show and run Windows specific commands."
+ };
+
+ public static CliDirective LinuxDirective { get; } = new CliDirective("linux") {
+ Description = "Show and run Linux specific commands."
+ };
+
+ public static CliDirective OsxDirective { get; } = new CliDirective("osx") {
+ Description = "Show and run MacOS specific commands."
+ };
+
public static readonly string INTRO
= $"Velopack CLI {VelopackRuntimeInfo.VelopackDisplayVersion}, for distributing applications.";
public static async Task Main(string[] args)
{
- CliCommand rootCommand = new CliCommand("vpk", INTRO) {
- new LongHelpCommand(),
- LegacyConsoleOption,
- YesOption,
- VerboseOption,
- };
+ CliRootCommand rootCommand = new CliRootCommand(INTRO);
+ rootCommand.Options.Clear(); // remove the default help option
+ rootCommand.Options.Add(new LongHelpCommand());
+ rootCommand.Options.Add(LegacyConsoleOption);
+ rootCommand.Options.Add(YesOption);
+ rootCommand.Options.Add(VerboseOption);
+ rootCommand.Directives.Add(WindowsDirective);
+ rootCommand.Directives.Add(LinuxDirective);
+ rootCommand.Directives.Add(OsxDirective);
rootCommand.TreatUnmatchedTokensAsErrors = false;
ParseResult parseResult = rootCommand.Parse(args);
@@ -56,6 +71,9 @@ public class Program
|| Console.IsOutputRedirected
|| Console.IsErrorRedirected;
bool defaultYes = parseResult.GetValue(YesOption);
+ bool directiveWin = parseResult.GetResult(WindowsDirective) != null;
+ bool directiveLinux = parseResult.GetResult(LinuxDirective) != null;
+ bool directiveOsx = parseResult.GetResult(OsxDirective) != null;
rootCommand.TreatUnmatchedTokensAsErrors = true;
var builder = Host.CreateEmptyApplicationBuilder(new HostApplicationBuilderSettings {
@@ -66,20 +84,49 @@ public class Program
});
SetupConfig(builder);
- SetupLogging(builder, verbose, legacyConsole, defaultYes);
+ SetupLogging(builder, verbose, legacyConsole);
SetupVelopackService(builder.Services);
+ RuntimeOs targetOs = VelopackRuntimeInfo.SystemOs;
+ if (new bool[] { directiveWin, directiveLinux, directiveOsx }.Count(x => x) > 1) {
+ throw new UserInfoException(
+ "Invalid arguments: Only one OS directive can be specified at a time: either [win], [linux], or [osx].");
+ }
+
+ if (directiveWin) {
+ targetOs = RuntimeOs.Windows;
+ } else if (directiveLinux) {
+ targetOs = RuntimeOs.Linux;
+ } else if (directiveOsx) {
+ targetOs = RuntimeOs.OSX;
+ }
+
+ builder.Services.AddSingleton(new VelopackDefaults(defaultYes, targetOs));
+
var host = builder.Build();
var provider = host.Services;
+ var logger = provider.GetRequiredService();
- if (VelopackRuntimeInfo.IsWindows) {
+ if (targetOs != VelopackRuntimeInfo.SystemOs) {
+ logger.LogInformation($"Directive enabled for cross-compiling from {VelopackRuntimeInfo.SystemOs} (current os) to {targetOs}.");
+ }
+
+ switch (targetOs) {
+ case RuntimeOs.Windows:
rootCommand.AddCommand(provider);
- } else if (VelopackRuntimeInfo.IsOSX) {
- rootCommand.AddCommand(provider);
- rootCommand.AddCommand(provider);
- } else if (VelopackRuntimeInfo.IsLinux) {
+ break;
+ case RuntimeOs.Linux:
rootCommand.AddCommand(provider);
- } else {
+ break;
+ case RuntimeOs.OSX:
+ if (VelopackRuntimeInfo.IsOSX) {
+ rootCommand.AddCommand(provider);
+ rootCommand.AddCommand(provider);
+ } else {
+ throw new NotSupportedException($"Cross-compiling from {VelopackRuntimeInfo.SystemOs} to MacOS is not supported.");
+ }
+ break;
+ default:
throw new NotSupportedException("Unsupported OS platform: " + VelopackRuntimeInfo.SystemOs.GetOsLongName());
}
@@ -122,15 +169,13 @@ public class Program
builder.Services.AddTransient(s => s.GetService().CreateLogger("vpk"));
}
- private static void SetupLogging(IHostApplicationBuilder builder, bool verbose, bool legacyConsole, bool defaultPromptValue)
+ private static void SetupLogging(IHostApplicationBuilder builder, bool verbose, bool legacyConsole)
{
var conf = new LoggerConfiguration()
.MinimumLevel.Is(verbose ? LogEventLevel.Debug : LogEventLevel.Information)
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Warning);
- builder.Services.AddSingleton(new DefaultPromptValueFactory(defaultPromptValue));
-
if (legacyConsole) {
// spectre can have issues with redirected output, so we disable it.
builder.Services.AddSingleton();
@@ -195,12 +240,15 @@ public static class ProgramCommandExtensions
var command = new TCli();
command.SetAction(async (ctx, token) => {
var logger = provider.GetRequiredService();
+ var console = provider.GetRequiredService();
var config = provider.GetRequiredService();
+ var defaults = provider.GetRequiredService();
+
logger.LogInformation($"[bold]{Program.INTRO}[/]");
var updateCheck = new UpdateChecker(logger);
await updateCheck.CheckForUpdates();
- command.SetProperties(ctx, config);
+ command.SetProperties(ctx, config, defaults.TargetOs);
var options = OptionMapper.Map(command);
try {
@@ -210,10 +258,10 @@ public static class ProgramCommandExtensions
return 0;
} catch (Exception ex) when (ex is ProcessFailedException or UserInfoException) {
// some exceptions are just user info / user error, so don't need a stack trace.
- logger.Fatal($"[bold orange3]{ex.Message}[/]");
+ logger.Fatal($"[bold orange3]{console.EscapeMarkup(ex.Message)}[/]");
return -1;
} catch (Exception ex) {
- logger.Fatal(ex, $"Command {typeof(TCli).Name} had an exception.");
+ logger.Fatal(ex);
return -1;
}
});
diff --git a/src/Velopack.Vpk/Velopack.Vpk.csproj b/src/Velopack.Vpk/Velopack.Vpk.csproj
index 82675d15..f4d7afee 100644
--- a/src/Velopack.Vpk/Velopack.Vpk.csproj
+++ b/src/Velopack.Vpk/Velopack.Vpk.csproj
@@ -23,7 +23,7 @@
-
+
diff --git a/src/Velopack.Vpk/VelopackDefaults.cs b/src/Velopack.Vpk/VelopackDefaults.cs
new file mode 100644
index 00000000..61ae81b4
--- /dev/null
+++ b/src/Velopack.Vpk/VelopackDefaults.cs
@@ -0,0 +1,18 @@
+namespace Velopack.Vpk;
+
+public record VelopackDefaults
+{
+ public bool DefaultPromptValue { get; }
+ public RuntimeOs TargetOs { get; }
+
+ public VelopackDefaults(bool defaultPromptValue)
+ : this(defaultPromptValue, VelopackRuntimeInfo.SystemOs)
+ {
+ }
+
+ public VelopackDefaults(bool defaultPromptValue, RuntimeOs targetOs)
+ {
+ DefaultPromptValue = defaultPromptValue;
+ TargetOs = targetOs;
+ }
+}
diff --git a/src/Velopack.Vpk/packages.lock.json b/src/Velopack.Vpk/packages.lock.json
index 7bd89d40..b372495d 100644
--- a/src/Velopack.Vpk/packages.lock.json
+++ b/src/Velopack.Vpk/packages.lock.json
@@ -64,16 +64,16 @@
"Nerdbank.GitVersioning": {
"type": "Direct",
"requested": "[3.6.*, )",
- "resolved": "3.6.133",
- "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw=="
+ "resolved": "3.6.139",
+ "contentHash": "rq0Ub/Jik7PtMtZtLn0tHuJ01Yt36RQ+eeBe+S7qnJ/EFOX6D4T9zuYD3vQPYKGI6Ro4t2iWgFm3fGDgjBrMfg=="
},
"NuGet.Protocol": {
"type": "Direct",
- "requested": "[6.9.1, )",
- "resolved": "6.9.1",
- "contentHash": "h3bdjqUY4jvwM02D/7QM4FR8x/bbf4Pyjrc1Etw7an2OrWKPUSx0vJPdapKzioxIw7GXl/aVUM/DCeIc3S9brA==",
+ "requested": "[6.10.0, )",
+ "resolved": "6.10.0",
+ "contentHash": "/3r1avHk5IlqoGlXlb1ezNgtTIQyMTR5DAgh1WBcllivpbADpM9rvsFeemvcnndaFuQkEgc7a2egQZEnOK15ew==",
"dependencies": {
- "NuGet.Packaging": "6.9.1"
+ "NuGet.Packaging": "6.10.0"
}
},
"Riok.Mapperly": {
@@ -169,15 +169,15 @@
},
"AWSSDK.Core": {
"type": "Transitive",
- "resolved": "3.7.304.1",
- "contentHash": "O/gyE5ptF5zEc3QDk3JI3+AOgBfRfmg6eLH5z3x7hUPmV1Wxu0V4Fm86FSyT6czRviMmQGY0q0SVMbWRbP4vDA=="
+ "resolved": "3.7.304.10",
+ "contentHash": "0CXnPzoM+KYXODm2bvRW8eYs7ic2VLop45sphL8FBfvxHfBK/3OJgpxEE2InSdWS1Iby+6KXpz3NplJJY5+Y1A=="
},
"AWSSDK.S3": {
"type": "Transitive",
- "resolved": "3.7.308",
- "contentHash": "U4LWi1yTKVK6IFHWQc4anKPJKXj9dPN+dT1oXto4ndXXlr+DVzm9dULkquyXBjAHlr6jf339ojuYlE6t9Mk6pQ==",
+ "resolved": "3.7.308.8",
+ "contentHash": "lUQgJsj9n/sJo1CRQFsiB2Gqh3rC21I4e5CWR1b+oYBnc9m8LIsGewctX6YQRzGzuv7Im7iIZo7XoirmYhhqEA==",
"dependencies": {
- "AWSSDK.Core": "[3.7.304.1, 4.0.0)"
+ "AWSSDK.Core": "[3.7.304.10, 4.0.0)"
}
},
"Azure.Core": {
@@ -493,8 +493,8 @@
},
"Microsoft.Identity.Client": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "3TDPEie+9t/NgBhoNifLFwM6nkypYUa8GUHfMnkYtovDDTQM8bsS2KEIP5tAh28BgfJZgof/KlCEKvGxz0H3Eg==",
+ "resolved": "4.61.2",
+ "contentHash": "zXPDXjTju1yOZQzi5PeNmQfBzlxv+VlCfgfZ70VqyFuhaPtFXwvbLSlwCOMoVALR/MzOR6xkiadoyVDmWDmqKw==",
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "6.35.0",
"System.Diagnostics.DiagnosticSource": "6.0.1"
@@ -502,26 +502,26 @@
},
"Microsoft.Identity.Client.Broker": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "D5M7plyrXrQwhj/PILHLS7G3TqDg8rUO9lR+5awUSXueGxsdA2qHpeKiSR0sVfX6wxziWszOLDUR2vjFFKHPSg==",
+ "resolved": "4.61.2",
+ "contentHash": "+13pImNYfPGl04qZh3Rtq8wPfo55FScAUEio4ymPzeWJ/PE04jCsKH6ZIlrYbPCqe6t9u0lru1eZ+ho9Dsnvpw==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
- "Microsoft.Identity.Client.NativeInterop": "0.16.0"
+ "Microsoft.Identity.Client": "4.61.2",
+ "Microsoft.Identity.Client.NativeInterop": "0.16.1"
}
},
"Microsoft.Identity.Client.Extensions.Msal": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "tCX3c1bSkwH89AKjtB/oAOLlv661tOsEA8ULOCavOc6te/CKlqmMmFs8zMI58Qlv3XkbLOrswYr50IhDntiiPA==",
+ "resolved": "4.61.2",
+ "contentHash": "GCSBhzkr7/7DZDk2kYS9dfihB5ieM2CqwGAhtjuE6T1GtAqzuh62UJhMgO0dSx/PLgIYMzNHnh+pfHK8aWJQJQ==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
+ "Microsoft.Identity.Client": "4.61.2",
"System.Security.Cryptography.ProtectedData": "4.5.0"
}
},
"Microsoft.Identity.Client.NativeInterop": {
"type": "Transitive",
- "resolved": "0.16.0",
- "contentHash": "riaIH9tT4kRG4xKFxx+wpuo+VAZVOr1ElbNubcvpFXb82gjEmZtJRzpdiS4tdRYTVh6CUKNz9AINAptActTtBQ=="
+ "resolved": "0.16.1",
+ "contentHash": "OvY/+/AHESi24f5zOCf9kL4HXwPxXVQ3A+tMQsJvFk2DmP+sc88FYWL49zlku5q0bvx5yFvBLNpHQeRT9a6A5g=="
},
"Microsoft.IdentityModel.Abstractions": {
"type": "Transitive",
@@ -530,8 +530,8 @@
},
"Microsoft.NETCore.Platforms": {
"type": "Transitive",
- "resolved": "1.1.1",
- "contentHash": "TMBuzAHpTenGbGgk0SMTwyEkyijY/Eae4ZGsFNYJvAr/LDn1ku3Etp3FPxChmDp5HHF3kzJuoaa08N0xjqAJfQ=="
+ "resolved": "5.0.0",
+ "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ=="
},
"Microsoft.NETCore.Targets": {
"type": "Transitive",
@@ -550,41 +550,41 @@
},
"NuGet.Common": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "FbuWZBjQ1NJXBDqCwSddN2yvw3Plq3sTCIh0nc66Hu8jrNr+BOaxlKZv78jvJ+pSy8BvurYOdF9sl9KoORjrtg==",
+ "resolved": "6.10.0",
+ "contentHash": "ujuzfDwUylDALwZmqRi7I5Jx4E9/8vR2c0Hq8zRj8zCkR8KarSp84WPJ2uE/qwAOjpBYGek06wWgGJ3ABQ/WxA==",
"dependencies": {
- "NuGet.Frameworks": "6.9.1"
+ "NuGet.Frameworks": "6.10.0"
}
},
"NuGet.Configuration": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "GM06pcUzWdNsizeGciqCjAhryfI1F/rQPETLDF+8pDRgzVpA+wKAR01/4aFU+IXzugnQ9LqOb5YyCRuR1OVZiQ==",
+ "resolved": "6.10.0",
+ "contentHash": "PbtCCdFC/K3GG3fdstIIMVraiXcxC1IgnzqayowckSlIg2xSxvcqAWd5Z7G1V6st5JzJi9THSnvmy/kchwh80A==",
"dependencies": {
- "NuGet.Common": "6.9.1",
+ "NuGet.Common": "6.10.0",
"System.Security.Cryptography.ProtectedData": "4.4.0"
}
},
"NuGet.Frameworks": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "DaKh3lenPUvzGccPkbI97BIvA27z+/UsL3ankfoZlX/4vBVDK5N1sheFTQ+GuJf+IgSzsJz/A21SPUpQLHwUtA=="
+ "resolved": "6.10.0",
+ "contentHash": "bKaC87Q8rxK7ozFN9Eyo0YVUmd4r2s8pbNxHI7sHRqL16OAP0yEdCU5wpDkG9cKLHpZCahgoQUfAVC0UadVU+A=="
},
"NuGet.Packaging": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "6FyasOxKInCELJ+pGy8f17ub9st6ofFeNcBNTo7CRiPJlxyhJfKGKNpfe3HHYwvnZhc3Vdfr0kSR+f1BVGDuTA==",
+ "resolved": "6.10.0",
+ "contentHash": "QSznYvf+HejToKt9zOB2z9F1C4R01lnrUUcJ+WxL1ihazTBfrXWPCCAQDtQFSlJlibiJBn/GEoq1gMeaeSo2qQ==",
"dependencies": {
"Newtonsoft.Json": "13.0.3",
- "NuGet.Configuration": "6.9.1",
- "NuGet.Versioning": "6.9.1",
+ "NuGet.Configuration": "6.10.0",
+ "NuGet.Versioning": "6.10.0",
"System.Security.Cryptography.Pkcs": "6.0.4"
}
},
"NuGet.Versioning": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "ypnSvEtpNGo48bAWn95J1oHChycCXcevFSbn53fqzLxlXFSZP7dawu8p/7mHAfGufZQSV2sBpW80XQGIfXO8kQ=="
+ "resolved": "6.10.0",
+ "contentHash": "ytTA08tgZWo/Pbk333hnzQfiMyyynkkwMt4GyC5T0bzExYYSRNrkwv4jT8jmWr5LUOYsumEZvXdoven2SA7YZw=="
},
"Octokit": {
"type": "Transitive",
@@ -703,6 +703,20 @@
"Serilog": "3.1.1"
}
},
+ "SharpZipLib": {
+ "type": "Transitive",
+ "resolved": "1.4.2",
+ "contentHash": "yjj+3zgz8zgXpiiC3ZdF/iyTBbz2fFvMxZFEBPUcwZjIvXOf37Ylm+K58hqMfIBt5JgU/Z2uoUS67JmTLe973A=="
+ },
+ "SixLabors.ImageSharp": {
+ "type": "Transitive",
+ "resolved": "2.1.8",
+ "contentHash": "ML1++vactR5xMW36wHA2nAX3vc6VpR62qYnpopQdLNGbP8BAJ/ckv1IrGIIFRUEQ0JS9EgWr1y1gEa/81f+HaA==",
+ "dependencies": {
+ "System.Runtime.CompilerServices.Unsafe": "5.0.0",
+ "System.Text.Encoding.CodePages": "5.0.0"
+ }
+ },
"System.ClientModel": {
"type": "Transitive",
"resolved": "1.0.0",
@@ -1193,6 +1207,14 @@
"System.Runtime": "4.3.0"
}
},
+ "System.Text.Encoding.CodePages": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "5.0.0"
+ }
+ },
"System.Text.Encodings.Web": {
"type": "Transitive",
"resolved": "8.0.0",
@@ -1238,37 +1260,41 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[6.0.0, )",
- "NuGet.Versioning": "[6.9.1, )"
+ "NuGet.Versioning": "[6.10.0, )"
}
},
"velopack.deployment": {
"type": "Project",
"dependencies": {
- "AWSSDK.S3": "[3.7.308, )",
+ "AWSSDK.S3": "[3.7.308.8, )",
"Azure.Storage.Blobs": "[12.20.0, )",
"Octokit": "[11.0.1, )",
"Velopack.Packaging": "[1.0.0, )"
}
},
+ "velopack.icolib": {
+ "type": "Project",
+ "dependencies": {
+ "SixLabors.ImageSharp": "[2.1.8, )"
+ }
+ },
"velopack.packaging": {
"type": "Project",
"dependencies": {
"Markdig": "[0.37.0, )",
- "Microsoft.Identity.Client": "[4.61.0, )",
- "Microsoft.Identity.Client.Broker": "[4.61.0, )",
- "Microsoft.Identity.Client.Extensions.Msal": "[4.61.0, )",
+ "Microsoft.Identity.Client": "[4.61.2, )",
+ "Microsoft.Identity.Client.Broker": "[4.61.2, )",
+ "Microsoft.Identity.Client.Extensions.Msal": "[4.61.2, )",
"System.Linq.Async": "[6.0.1, )",
"System.Net.Http": "[4.3.4, )",
"Velopack": "[1.0.0, )"
}
},
- "velopack.packaging.hostmodel": {
- "type": "Project"
- },
"velopack.packaging.unix": {
"type": "Project",
"dependencies": {
"ELFSharp": "[2.17.3, )",
+ "SharpZipLib": "[1.4.2, )",
"Velopack.Packaging": "[1.0.0, )"
}
},
@@ -1277,8 +1303,8 @@
"dependencies": {
"AsmResolver.DotNet": "[5.5.1, )",
"AsmResolver.PE.Win32Resources": "[5.5.1, )",
- "Velopack.Packaging": "[1.0.0, )",
- "Velopack.Packaging.HostModel": "[1.0.0, )"
+ "Velopack.IcoLib": "[1.1.1, )",
+ "Velopack.Packaging": "[1.0.0, )"
}
}
},
@@ -1346,16 +1372,16 @@
"Nerdbank.GitVersioning": {
"type": "Direct",
"requested": "[3.6.*, )",
- "resolved": "3.6.133",
- "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw=="
+ "resolved": "3.6.139",
+ "contentHash": "rq0Ub/Jik7PtMtZtLn0tHuJ01Yt36RQ+eeBe+S7qnJ/EFOX6D4T9zuYD3vQPYKGI6Ro4t2iWgFm3fGDgjBrMfg=="
},
"NuGet.Protocol": {
"type": "Direct",
- "requested": "[6.9.1, )",
- "resolved": "6.9.1",
- "contentHash": "h3bdjqUY4jvwM02D/7QM4FR8x/bbf4Pyjrc1Etw7an2OrWKPUSx0vJPdapKzioxIw7GXl/aVUM/DCeIc3S9brA==",
+ "requested": "[6.10.0, )",
+ "resolved": "6.10.0",
+ "contentHash": "/3r1avHk5IlqoGlXlb1ezNgtTIQyMTR5DAgh1WBcllivpbADpM9rvsFeemvcnndaFuQkEgc7a2egQZEnOK15ew==",
"dependencies": {
- "NuGet.Packaging": "6.9.1"
+ "NuGet.Packaging": "6.10.0"
}
},
"Riok.Mapperly": {
@@ -1448,15 +1474,15 @@
},
"AWSSDK.Core": {
"type": "Transitive",
- "resolved": "3.7.304.1",
- "contentHash": "O/gyE5ptF5zEc3QDk3JI3+AOgBfRfmg6eLH5z3x7hUPmV1Wxu0V4Fm86FSyT6czRviMmQGY0q0SVMbWRbP4vDA=="
+ "resolved": "3.7.304.10",
+ "contentHash": "0CXnPzoM+KYXODm2bvRW8eYs7ic2VLop45sphL8FBfvxHfBK/3OJgpxEE2InSdWS1Iby+6KXpz3NplJJY5+Y1A=="
},
"AWSSDK.S3": {
"type": "Transitive",
- "resolved": "3.7.308",
- "contentHash": "U4LWi1yTKVK6IFHWQc4anKPJKXj9dPN+dT1oXto4ndXXlr+DVzm9dULkquyXBjAHlr6jf339ojuYlE6t9Mk6pQ==",
+ "resolved": "3.7.308.8",
+ "contentHash": "lUQgJsj9n/sJo1CRQFsiB2Gqh3rC21I4e5CWR1b+oYBnc9m8LIsGewctX6YQRzGzuv7Im7iIZo7XoirmYhhqEA==",
"dependencies": {
- "AWSSDK.Core": "[3.7.304.1, 4.0.0)"
+ "AWSSDK.Core": "[3.7.304.10, 4.0.0)"
}
},
"Azure.Core": {
@@ -1767,8 +1793,8 @@
},
"Microsoft.Identity.Client": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "3TDPEie+9t/NgBhoNifLFwM6nkypYUa8GUHfMnkYtovDDTQM8bsS2KEIP5tAh28BgfJZgof/KlCEKvGxz0H3Eg==",
+ "resolved": "4.61.2",
+ "contentHash": "zXPDXjTju1yOZQzi5PeNmQfBzlxv+VlCfgfZ70VqyFuhaPtFXwvbLSlwCOMoVALR/MzOR6xkiadoyVDmWDmqKw==",
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "6.35.0",
"System.Diagnostics.DiagnosticSource": "6.0.1"
@@ -1776,26 +1802,26 @@
},
"Microsoft.Identity.Client.Broker": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "D5M7plyrXrQwhj/PILHLS7G3TqDg8rUO9lR+5awUSXueGxsdA2qHpeKiSR0sVfX6wxziWszOLDUR2vjFFKHPSg==",
+ "resolved": "4.61.2",
+ "contentHash": "+13pImNYfPGl04qZh3Rtq8wPfo55FScAUEio4ymPzeWJ/PE04jCsKH6ZIlrYbPCqe6t9u0lru1eZ+ho9Dsnvpw==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
- "Microsoft.Identity.Client.NativeInterop": "0.16.0"
+ "Microsoft.Identity.Client": "4.61.2",
+ "Microsoft.Identity.Client.NativeInterop": "0.16.1"
}
},
"Microsoft.Identity.Client.Extensions.Msal": {
"type": "Transitive",
- "resolved": "4.61.0",
- "contentHash": "tCX3c1bSkwH89AKjtB/oAOLlv661tOsEA8ULOCavOc6te/CKlqmMmFs8zMI58Qlv3XkbLOrswYr50IhDntiiPA==",
+ "resolved": "4.61.2",
+ "contentHash": "GCSBhzkr7/7DZDk2kYS9dfihB5ieM2CqwGAhtjuE6T1GtAqzuh62UJhMgO0dSx/PLgIYMzNHnh+pfHK8aWJQJQ==",
"dependencies": {
- "Microsoft.Identity.Client": "4.61.0",
+ "Microsoft.Identity.Client": "4.61.2",
"System.Security.Cryptography.ProtectedData": "4.5.0"
}
},
"Microsoft.Identity.Client.NativeInterop": {
"type": "Transitive",
- "resolved": "0.16.0",
- "contentHash": "riaIH9tT4kRG4xKFxx+wpuo+VAZVOr1ElbNubcvpFXb82gjEmZtJRzpdiS4tdRYTVh6CUKNz9AINAptActTtBQ=="
+ "resolved": "0.16.1",
+ "contentHash": "OvY/+/AHESi24f5zOCf9kL4HXwPxXVQ3A+tMQsJvFk2DmP+sc88FYWL49zlku5q0bvx5yFvBLNpHQeRT9a6A5g=="
},
"Microsoft.IdentityModel.Abstractions": {
"type": "Transitive",
@@ -1804,8 +1830,8 @@
},
"Microsoft.NETCore.Platforms": {
"type": "Transitive",
- "resolved": "1.1.1",
- "contentHash": "TMBuzAHpTenGbGgk0SMTwyEkyijY/Eae4ZGsFNYJvAr/LDn1ku3Etp3FPxChmDp5HHF3kzJuoaa08N0xjqAJfQ=="
+ "resolved": "5.0.0",
+ "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ=="
},
"Microsoft.NETCore.Targets": {
"type": "Transitive",
@@ -1824,41 +1850,41 @@
},
"NuGet.Common": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "FbuWZBjQ1NJXBDqCwSddN2yvw3Plq3sTCIh0nc66Hu8jrNr+BOaxlKZv78jvJ+pSy8BvurYOdF9sl9KoORjrtg==",
+ "resolved": "6.10.0",
+ "contentHash": "ujuzfDwUylDALwZmqRi7I5Jx4E9/8vR2c0Hq8zRj8zCkR8KarSp84WPJ2uE/qwAOjpBYGek06wWgGJ3ABQ/WxA==",
"dependencies": {
- "NuGet.Frameworks": "6.9.1"
+ "NuGet.Frameworks": "6.10.0"
}
},
"NuGet.Configuration": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "GM06pcUzWdNsizeGciqCjAhryfI1F/rQPETLDF+8pDRgzVpA+wKAR01/4aFU+IXzugnQ9LqOb5YyCRuR1OVZiQ==",
+ "resolved": "6.10.0",
+ "contentHash": "PbtCCdFC/K3GG3fdstIIMVraiXcxC1IgnzqayowckSlIg2xSxvcqAWd5Z7G1V6st5JzJi9THSnvmy/kchwh80A==",
"dependencies": {
- "NuGet.Common": "6.9.1",
+ "NuGet.Common": "6.10.0",
"System.Security.Cryptography.ProtectedData": "4.4.0"
}
},
"NuGet.Frameworks": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "DaKh3lenPUvzGccPkbI97BIvA27z+/UsL3ankfoZlX/4vBVDK5N1sheFTQ+GuJf+IgSzsJz/A21SPUpQLHwUtA=="
+ "resolved": "6.10.0",
+ "contentHash": "bKaC87Q8rxK7ozFN9Eyo0YVUmd4r2s8pbNxHI7sHRqL16OAP0yEdCU5wpDkG9cKLHpZCahgoQUfAVC0UadVU+A=="
},
"NuGet.Packaging": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "6FyasOxKInCELJ+pGy8f17ub9st6ofFeNcBNTo7CRiPJlxyhJfKGKNpfe3HHYwvnZhc3Vdfr0kSR+f1BVGDuTA==",
+ "resolved": "6.10.0",
+ "contentHash": "QSznYvf+HejToKt9zOB2z9F1C4R01lnrUUcJ+WxL1ihazTBfrXWPCCAQDtQFSlJlibiJBn/GEoq1gMeaeSo2qQ==",
"dependencies": {
"Newtonsoft.Json": "13.0.3",
- "NuGet.Configuration": "6.9.1",
- "NuGet.Versioning": "6.9.1",
+ "NuGet.Configuration": "6.10.0",
+ "NuGet.Versioning": "6.10.0",
"System.Security.Cryptography.Pkcs": "6.0.4"
}
},
"NuGet.Versioning": {
"type": "Transitive",
- "resolved": "6.9.1",
- "contentHash": "ypnSvEtpNGo48bAWn95J1oHChycCXcevFSbn53fqzLxlXFSZP7dawu8p/7mHAfGufZQSV2sBpW80XQGIfXO8kQ=="
+ "resolved": "6.10.0",
+ "contentHash": "ytTA08tgZWo/Pbk333hnzQfiMyyynkkwMt4GyC5T0bzExYYSRNrkwv4jT8jmWr5LUOYsumEZvXdoven2SA7YZw=="
},
"Octokit": {
"type": "Transitive",
@@ -1977,6 +2003,20 @@
"Serilog": "3.1.1"
}
},
+ "SharpZipLib": {
+ "type": "Transitive",
+ "resolved": "1.4.2",
+ "contentHash": "yjj+3zgz8zgXpiiC3ZdF/iyTBbz2fFvMxZFEBPUcwZjIvXOf37Ylm+K58hqMfIBt5JgU/Z2uoUS67JmTLe973A=="
+ },
+ "SixLabors.ImageSharp": {
+ "type": "Transitive",
+ "resolved": "2.1.8",
+ "contentHash": "ML1++vactR5xMW36wHA2nAX3vc6VpR62qYnpopQdLNGbP8BAJ/ckv1IrGIIFRUEQ0JS9EgWr1y1gEa/81f+HaA==",
+ "dependencies": {
+ "System.Runtime.CompilerServices.Unsafe": "5.0.0",
+ "System.Text.Encoding.CodePages": "5.0.0"
+ }
+ },
"System.ClientModel": {
"type": "Transitive",
"resolved": "1.0.0",
@@ -2243,6 +2283,11 @@
"Microsoft.NETCore.Targets": "1.1.0"
}
},
+ "System.Runtime.CompilerServices.Unsafe": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "ZD9TMpsmYJLrxbbmdvhwt9YEgG5WntEnZ/d1eH8JBX9LBp+Ju8BSBhUGbZMNVHHomWo2KVImJhTDl2hIgw/6MA=="
+ },
"System.Runtime.Extensions": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -2454,6 +2499,14 @@
"System.Runtime": "4.3.0"
}
},
+ "System.Text.Encoding.CodePages": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "5.0.0"
+ }
+ },
"System.Text.Encodings.Web": {
"type": "Transitive",
"resolved": "8.0.0",
@@ -2495,37 +2548,41 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )",
- "NuGet.Versioning": "[6.9.1, )"
+ "NuGet.Versioning": "[6.10.0, )"
}
},
"velopack.deployment": {
"type": "Project",
"dependencies": {
- "AWSSDK.S3": "[3.7.308, )",
+ "AWSSDK.S3": "[3.7.308.8, )",
"Azure.Storage.Blobs": "[12.20.0, )",
"Octokit": "[11.0.1, )",
"Velopack.Packaging": "[1.0.0, )"
}
},
+ "velopack.icolib": {
+ "type": "Project",
+ "dependencies": {
+ "SixLabors.ImageSharp": "[2.1.8, )"
+ }
+ },
"velopack.packaging": {
"type": "Project",
"dependencies": {
"Markdig": "[0.37.0, )",
- "Microsoft.Identity.Client": "[4.61.0, )",
- "Microsoft.Identity.Client.Broker": "[4.61.0, )",
- "Microsoft.Identity.Client.Extensions.Msal": "[4.61.0, )",
+ "Microsoft.Identity.Client": "[4.61.2, )",
+ "Microsoft.Identity.Client.Broker": "[4.61.2, )",
+ "Microsoft.Identity.Client.Extensions.Msal": "[4.61.2, )",
"System.Linq.Async": "[6.0.1, )",
"System.Net.Http": "[4.3.4, )",
"Velopack": "[1.0.0, )"
}
},
- "velopack.packaging.hostmodel": {
- "type": "Project"
- },
"velopack.packaging.unix": {
"type": "Project",
"dependencies": {
"ELFSharp": "[2.17.3, )",
+ "SharpZipLib": "[1.4.2, )",
"Velopack.Packaging": "[1.0.0, )"
}
},
@@ -2534,8 +2591,8 @@
"dependencies": {
"AsmResolver.DotNet": "[5.5.1, )",
"AsmResolver.PE.Win32Resources": "[5.5.1, )",
- "Velopack.Packaging": "[1.0.0, )",
- "Velopack.Packaging.HostModel": "[1.0.0, )"
+ "Velopack.IcoLib": "[1.1.1, )",
+ "Velopack.Packaging": "[1.0.0, )"
}
}
}
diff --git a/src/Velopack/Compression/EasyZip.cs b/src/Velopack/Compression/EasyZip.cs
index ff31dc09..814296d8 100644
--- a/src/Velopack/Compression/EasyZip.cs
+++ b/src/Velopack/Compression/EasyZip.cs
@@ -99,7 +99,7 @@ namespace Velopack.Compression
}
private static char s_pathSeperator = '/';
- private static readonly DateTime ZipFormatMinDate = new DateTime(1980, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+ public static readonly DateTime ZipFormatMinDate = new DateTime(1980, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static async Task DeterministicCreateFromDirectoryAsync(string sourceDirectoryName, string destinationArchiveFileName, CompressionLevel compressionLevel,
Action progress, CancellationToken cancelToken)
diff --git a/src/Velopack/Velopack.csproj b/src/Velopack/Velopack.csproj
index 92f6cab7..ec63f84c 100644
--- a/src/Velopack/Velopack.csproj
+++ b/src/Velopack/Velopack.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/src/Velopack/packages.lock.json b/src/Velopack/packages.lock.json
index a98ae222..f0e61172 100644
--- a/src/Velopack/packages.lock.json
+++ b/src/Velopack/packages.lock.json
@@ -30,8 +30,8 @@
"Nerdbank.GitVersioning": {
"type": "Direct",
"requested": "[3.6.*, )",
- "resolved": "3.6.133",
- "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw=="
+ "resolved": "3.6.139",
+ "contentHash": "rq0Ub/Jik7PtMtZtLn0tHuJ01Yt36RQ+eeBe+S7qnJ/EFOX6D4T9zuYD3vQPYKGI6Ro4t2iWgFm3fGDgjBrMfg=="
},
"Newtonsoft.Json": {
"type": "Direct",
@@ -41,9 +41,9 @@
},
"NuGet.Versioning": {
"type": "Direct",
- "requested": "[6.9.1, )",
- "resolved": "6.9.1",
- "contentHash": "ypnSvEtpNGo48bAWn95J1oHChycCXcevFSbn53fqzLxlXFSZP7dawu8p/7mHAfGufZQSV2sBpW80XQGIfXO8kQ=="
+ "requested": "[6.10.0, )",
+ "resolved": "6.10.0",
+ "contentHash": "ytTA08tgZWo/Pbk333hnzQfiMyyynkkwMt4GyC5T0bzExYYSRNrkwv4jT8jmWr5LUOYsumEZvXdoven2SA7YZw=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
@@ -81,8 +81,8 @@
"Nerdbank.GitVersioning": {
"type": "Direct",
"requested": "[3.6.*, )",
- "resolved": "3.6.133",
- "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw=="
+ "resolved": "3.6.139",
+ "contentHash": "rq0Ub/Jik7PtMtZtLn0tHuJ01Yt36RQ+eeBe+S7qnJ/EFOX6D4T9zuYD3vQPYKGI6Ro4t2iWgFm3fGDgjBrMfg=="
},
"Newtonsoft.Json": {
"type": "Direct",
@@ -92,9 +92,9 @@
},
"NuGet.Versioning": {
"type": "Direct",
- "requested": "[6.9.1, )",
- "resolved": "6.9.1",
- "contentHash": "ypnSvEtpNGo48bAWn95J1oHChycCXcevFSbn53fqzLxlXFSZP7dawu8p/7mHAfGufZQSV2sBpW80XQGIfXO8kQ=="
+ "requested": "[6.10.0, )",
+ "resolved": "6.10.0",
+ "contentHash": "ytTA08tgZWo/Pbk333hnzQfiMyyynkkwMt4GyC5T0bzExYYSRNrkwv4jT8jmWr5LUOYsumEZvXdoven2SA7YZw=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
@@ -127,14 +127,14 @@
"Nerdbank.GitVersioning": {
"type": "Direct",
"requested": "[3.6.*, )",
- "resolved": "3.6.133",
- "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw=="
+ "resolved": "3.6.139",
+ "contentHash": "rq0Ub/Jik7PtMtZtLn0tHuJ01Yt36RQ+eeBe+S7qnJ/EFOX6D4T9zuYD3vQPYKGI6Ro4t2iWgFm3fGDgjBrMfg=="
},
"NuGet.Versioning": {
"type": "Direct",
- "requested": "[6.9.1, )",
- "resolved": "6.9.1",
- "contentHash": "ypnSvEtpNGo48bAWn95J1oHChycCXcevFSbn53fqzLxlXFSZP7dawu8p/7mHAfGufZQSV2sBpW80XQGIfXO8kQ=="
+ "requested": "[6.10.0, )",
+ "resolved": "6.10.0",
+ "contentHash": "ytTA08tgZWo/Pbk333hnzQfiMyyynkkwMt4GyC5T0bzExYYSRNrkwv4jT8jmWr5LUOYsumEZvXdoven2SA7YZw=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
@@ -176,14 +176,14 @@
"Nerdbank.GitVersioning": {
"type": "Direct",
"requested": "[3.6.*, )",
- "resolved": "3.6.133",
- "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw=="
+ "resolved": "3.6.139",
+ "contentHash": "rq0Ub/Jik7PtMtZtLn0tHuJ01Yt36RQ+eeBe+S7qnJ/EFOX6D4T9zuYD3vQPYKGI6Ro4t2iWgFm3fGDgjBrMfg=="
},
"NuGet.Versioning": {
"type": "Direct",
- "requested": "[6.9.1, )",
- "resolved": "6.9.1",
- "contentHash": "ypnSvEtpNGo48bAWn95J1oHChycCXcevFSbn53fqzLxlXFSZP7dawu8p/7mHAfGufZQSV2sBpW80XQGIfXO8kQ=="
+ "requested": "[6.10.0, )",
+ "resolved": "6.10.0",
+ "contentHash": "ytTA08tgZWo/Pbk333hnzQfiMyyynkkwMt4GyC5T0bzExYYSRNrkwv4jT8jmWr5LUOYsumEZvXdoven2SA7YZw=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
diff --git a/test/Directory.Build.props b/test/Directory.Build.props
index da42d76b..6b7f766a 100644
--- a/test/Directory.Build.props
+++ b/test/Directory.Build.props
@@ -38,10 +38,10 @@
-
+
-
-
+
+
all
diff --git a/test/Velopack.CommandLine.Tests/Velopack.CommandLine.Tests.csproj b/test/Velopack.CommandLine.Tests/Velopack.CommandLine.Tests.csproj
index 7b12fd7e..a94e763e 100644
--- a/test/Velopack.CommandLine.Tests/Velopack.CommandLine.Tests.csproj
+++ b/test/Velopack.CommandLine.Tests/Velopack.CommandLine.Tests.csproj
@@ -15,6 +15,10 @@
all
runtime; build; native; contentfiles; analyzers
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
diff --git a/test/Velopack.Packaging.Tests/CompatUtilTests.cs b/test/Velopack.Packaging.Tests/CompatUtilTests.cs
index 9bc557dc..08bcd81f 100644
--- a/test/Velopack.Packaging.Tests/CompatUtilTests.cs
+++ b/test/Velopack.Packaging.Tests/CompatUtilTests.cs
@@ -1,6 +1,7 @@
using Divergic.Logging.Xunit;
using Velopack.Packaging.Exceptions;
using Velopack.Packaging.Windows;
+using Velopack.Vpk;
using Velopack.Vpk.Logging;
namespace Velopack.Packaging.Tests;
@@ -17,7 +18,7 @@ public class CompatUtilTests
private ICacheLogger GetCompat(out CompatUtil compat)
{
var logger = _output.BuildLoggerFor();
- compat = new CompatUtil(logger, new BasicConsole(logger, new DefaultPromptValueFactory(true)));
+ compat = new CompatUtil(logger, new BasicConsole(logger, new VelopackDefaults(true)));
return logger;
}
diff --git a/test/Velopack.Packaging.Tests/CrossCompile.cs b/test/Velopack.Packaging.Tests/CrossCompile.cs
new file mode 100644
index 00000000..ef792c90
--- /dev/null
+++ b/test/Velopack.Packaging.Tests/CrossCompile.cs
@@ -0,0 +1,100 @@
+using Velopack.Packaging.Unix;
+
+namespace Velopack.Packaging.Tests;
+
+public class CrossCompile
+{
+ private readonly ITestOutputHelper _output;
+
+ public CrossCompile(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ [Theory]
+ [InlineData("win-x64")]
+ [InlineData("linux-x64")]
+ public void PackCrossApp(string target)
+ {
+ using var logger = _output.BuildLoggerFor();
+ var rid = RID.Parse(target);
+
+ string id = $"from-{VelopackRuntimeInfo.SystemOs.GetOsShortName()}-targets-{rid.BaseRID.GetOsShortName()}";
+ using var _1 = Utility.GetTempDirectory(out var tempDir);
+ TestApp.PackTestApp(id, "1.0.0", id, tempDir, logger, targetRid: rid);
+
+ var artifactsDir = PathHelper.GetTestRootPath("artifacts");
+ Directory.CreateDirectory(artifactsDir);
+
+ string src, dest;
+ if (rid.BaseRID == RuntimeOs.Windows) {
+ src = Path.Combine(tempDir, id + "-win-Setup.exe");
+ dest = Path.Combine(artifactsDir, id + ".exe");
+ } else {
+ src = Path.Combine(tempDir, id + ".AppImage");
+ dest = Path.Combine(artifactsDir, id + ".AppImage");
+ }
+
+ Assert.True(File.Exists(src), $"Expected {src} to exist");
+ File.Copy(src, dest, overwrite: true);
+ }
+
+ [SkippableTheory]
+ [InlineData("from-win-targets-linux")]
+ [InlineData("from-linux-targets-linux")]
+ [InlineData("from-osx-targets-linux")]
+ public void RunCrossAppLinux(string artifactId)
+ {
+ using var logger = _output.BuildLoggerFor();
+ Skip.If(String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("VELOPACK_CROSS_ARTIFACTS")),
+ "VELOPACK_CROSS_ARTIFACTS not set");
+ Skip.IfNot(VelopackRuntimeInfo.IsLinux, "AppImage's can only run on Linux");
+
+ var artifactsDir = PathHelper.GetTestRootPath("artifacts");
+ var artifactPath = Path.Combine(artifactsDir, artifactId + ".AppImage");
+
+ Assert.True(File.Exists(artifactPath), $"Expected {artifactPath} to exist");
+ Chmod.ChmodFileAsExecutable(artifactPath);
+
+ var output = Exe.InvokeAndThrowIfNonZero(artifactPath, new[] { "test" }, null);
+ logger.LogInformation(output);
+ Assert.EndsWith(artifactId, output.Trim());
+ }
+
+ [SkippableTheory]
+ [InlineData("from-win-targets-win")]
+ [InlineData("from-linux-targets-win")]
+ [InlineData("from-osx-targets-win")]
+ public void RunCrossAppWindows(string artifactId)
+ {
+ using var logger = _output.BuildLoggerFor();
+ Skip.If(String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("VELOPACK_CROSS_ARTIFACTS")),
+ "VELOPACK_CROSS_ARTIFACTS not set");
+ Skip.IfNot(VelopackRuntimeInfo.IsWindows, "PE files can only run on Windows");
+
+ var artifactsDir = PathHelper.GetTestRootPath("artifacts");
+ var artifactPath = Path.Combine(artifactsDir, artifactId + ".exe");
+
+ Assert.True(File.Exists(artifactPath), $"Expected {artifactPath} to exist");
+
+ var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+ var appRoot = Path.Combine(appData, artifactId);
+ var appExe = Path.Combine(appRoot, "current", "TestApp.exe");
+ var appUpdate = Path.Combine(appRoot, "Update.exe");
+
+ Utility.DeleteFileOrDirectoryHard(appRoot);
+
+ Assert.False(File.Exists(appExe));
+ var installOutput = Exe.InvokeAndThrowIfNonZero(artifactPath, new[] { "--silent" }, null);
+ logger.LogInformation(installOutput);
+
+ Assert.True(File.Exists(appExe));
+
+ var output = Exe.InvokeAndThrowIfNonZero(appExe, new[] { "test" }, null);
+ logger.LogInformation(output);
+ Assert.EndsWith(artifactId, output.Trim());
+
+ Exe.RunHostedCommand($"\"{appUpdate}\" --uninstall --silent");
+ Assert.False(File.Exists(appExe));
+ }
+}
diff --git a/test/Velopack.Packaging.Tests/ResourceEditTests.cs b/test/Velopack.Packaging.Tests/ResourceEditTests.cs
new file mode 100644
index 00000000..907fdbfe
--- /dev/null
+++ b/test/Velopack.Packaging.Tests/ResourceEditTests.cs
@@ -0,0 +1,220 @@
+using System.Diagnostics;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+using System.Reflection.PortableExecutable;
+using AsmResolver.PE;
+using AsmResolver.PE.File;
+using AsmResolver.PE.Win32Resources.Icon;
+using AsmResolver.PE.Win32Resources.Version;
+using Velopack.NuGet;
+using Velopack.Packaging.Windows;
+
+namespace Velopack.Packaging.Tests;
+
+public class ResourceEditTests
+{
+ private readonly ITestOutputHelper _output;
+
+ public ResourceEditTests(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ private void CreateTestPEFileWithoutRsrc(string tempFile)
+ {
+ var peBuilder = new ManagedPEBuilder(
+ PEHeaderBuilder.CreateExecutableHeader(),
+ new MetadataRootBuilder(new MetadataBuilder()),
+ ilStream: new BlobBuilder());
+ var peImageBuilder = new BlobBuilder();
+ peBuilder.Serialize(peImageBuilder);
+
+ using var fs = File.OpenWrite(tempFile);
+ fs.Write(peImageBuilder.ToArray());
+ }
+
+ [Fact]
+ public void CommitResourcesInCorrectOrder()
+ {
+ using var logger = _output.BuildLoggerFor();
+ using var _1 = Utility.GetTempFileName(out var tempFile);
+ var exe = PathHelper.GetRustAsset("setup.exe");
+ File.Copy(exe, tempFile);
+
+ var nuspec = PathHelper.GetFixture("FullNuspec.nuspec");
+ var manifest = PackageManifest.ParseFromFile(nuspec);
+ var pkgVersion = manifest.Version!;
+
+ var edit = new ResourceEdit(tempFile, logger);
+ edit.SetExeIcon(PathHelper.GetFixture("clowd.ico"));
+ edit.SetVersionInfo(manifest);
+ edit.Commit();
+
+ var afterRsrc = PEImage.FromFile(PEFile.FromFile(tempFile)).Resources;
+ Assert.NotNull(afterRsrc);
+
+ uint lastId = 0;
+ foreach (var e in afterRsrc.Entries) {
+ Assert.True(e.Id > lastId, "Resource entry ID must be greater than the previous");
+ lastId = e.Id;
+ }
+ }
+
+ [Fact]
+ public void CopyResourcesWithoutRsrc()
+ {
+ using var logger = _output.BuildLoggerFor();
+
+ using var _1 = Utility.GetTempFileName(out var tempFile);
+ CreateTestPEFileWithoutRsrc(tempFile);
+
+ var edit = new ResourceEdit(tempFile, logger);
+ edit.CopyResourcesFrom(PathHelper.GetFixture("Clowd.exe"));
+ edit.Commit();
+
+ AssertVersionInfo(tempFile, "3.4.439.61274", "3.4.439+ef5a83", "Copyright © Caelan Sayler, 2014-2022",
+ "Clowd", "Clowd", "Caelan Sayler");
+ }
+
+ [Fact]
+ public void CopyResourcesWithPreExistingRsrc()
+ {
+ using var logger = _output.BuildLoggerFor();
+
+ using var _1 = Utility.GetTempFileName(out var tempFile);
+ var exe = PathHelper.GetFixture("SquirrelAwareTweakedNetCoreApp.exe");
+ File.Copy(exe, tempFile);
+
+ var edit = new ResourceEdit(tempFile, logger);
+ edit.CopyResourcesFrom(PathHelper.GetFixture("Clowd.exe"));
+ edit.Commit();
+
+ AssertVersionInfo(tempFile, "3.4.439.61274", "3.4.439+ef5a83", "Copyright © Caelan Sayler, 2014-2022",
+ "Clowd", "Clowd", "Caelan Sayler");
+ }
+
+ [Fact]
+ public void SetIconWithPreExistingRsrc()
+ {
+ using var logger = _output.BuildLoggerFor();
+
+ using var _1 = Utility.GetTempFileName(out var tempFile);
+ var exe = PathHelper.GetFixture("atom.exe");
+ File.Copy(exe, tempFile);
+
+ var beforeRsrc = PEImage.FromFile(PEFile.FromFile(tempFile)).Resources;
+ Assert.NotNull(beforeRsrc);
+ var beforeIcon = IconResource.FromDirectory(beforeRsrc);
+ Assert.Single(beforeIcon.GetIconGroups());
+ Assert.Equal(6, beforeIcon.GetIconGroups().ToList()[0].GetIconEntries().Count());
+
+ var edit = new ResourceEdit(tempFile, logger);
+ edit.SetExeIcon(PathHelper.GetFixture("clowd.ico"));
+ edit.Commit();
+
+ var afterRsrc = PEImage.FromFile(PEFile.FromFile(tempFile)).Resources;
+ Assert.NotNull(afterRsrc);
+ var afterIcon = IconResource.FromDirectory(afterRsrc);
+ Assert.Single(afterIcon.GetIconGroups());
+ Assert.Equal(1, afterIcon.GetIconGroups().Single().Type);
+ Assert.Equal(7, afterIcon.GetIconGroups().ToList()[0].GetIconEntries().Count());
+ }
+
+ [Fact]
+ public void SetIconWithoutRsrc()
+ {
+ using var logger = _output.BuildLoggerFor();
+
+ using var _1 = Utility.GetTempFileName(out var tempFile);
+ CreateTestPEFileWithoutRsrc(tempFile);
+
+ var beforeRsrc = PEImage.FromFile(PEFile.FromFile(tempFile)).Resources;
+ Assert.Null(beforeRsrc);
+
+ var edit = new ResourceEdit(tempFile, logger);
+ edit.SetExeIcon(PathHelper.GetFixture("clowd.ico"));
+ edit.Commit();
+
+ var afterRsrc = PEImage.FromFile(PEFile.FromFile(tempFile)).Resources;
+ Assert.NotNull(afterRsrc);
+ var afterIcon = IconResource.FromDirectory(afterRsrc);
+ Assert.Single(afterIcon.GetIconGroups());
+ Assert.Equal(7, afterIcon.GetIconGroups().ToList()[0].GetIconEntries().Count());
+ }
+
+ [Fact]
+ public void SetVersionInfoWithPreExistingRsrc()
+ {
+ using var logger = _output.BuildLoggerFor();
+ using var _1 = Utility.GetTempFileName(out var tempFile);
+ var exe = PathHelper.GetFixture("atom.exe");
+ File.Copy(exe, tempFile);
+
+ var nuspec = PathHelper.GetFixture("FullNuspec.nuspec");
+ var manifest = PackageManifest.ParseFromFile(nuspec);
+ var pkgVersion = manifest.Version!;
+
+ var edit = new ResourceEdit(tempFile, logger);
+ edit.SetVersionInfo(manifest);
+ edit.Commit();
+
+ AssertVersionInfo(tempFile, manifest);
+ }
+
+ [Fact]
+ public void SetVersionInfoWithoutRsrc()
+ {
+ using var logger = _output.BuildLoggerFor();
+ using var _1 = Utility.GetTempFileName(out var tempFile);
+ CreateTestPEFileWithoutRsrc(tempFile);
+
+ var nuspec = PathHelper.GetFixture("FullNuspec.nuspec");
+ var manifest = PackageManifest.ParseFromFile(nuspec);
+ var pkgVersion = manifest.Version!;
+
+ var edit = new ResourceEdit(tempFile, logger);
+ edit.SetVersionInfo(manifest);
+ edit.Commit();
+
+ AssertVersionInfo(tempFile, manifest);
+ }
+
+ private void AssertVersionInfo(string exeFile, PackageManifest manifest)
+ {
+ AssertVersionInfo(exeFile, manifest.Version!.ToFullString(), manifest.Version!.ToFullString(),
+ manifest.ProductCopyright, manifest.ProductName, manifest.ProductDescription, manifest.ProductCompany);
+ }
+
+ private void AssertVersionInfo(string exeFile, string fileVersion, string productVersion,
+ string legalCopyright, string productName, string fileDescription, string companyName)
+ {
+ if (VelopackRuntimeInfo.IsWindows) {
+ // on Windows FileVersionInfo uses win32 methods to retrieve info from the PE resources
+ // on Unix, this function just looks for managed assembly attributes so is not suitable
+ var versionInfo = FileVersionInfo.GetVersionInfo(exeFile);
+ Assert.Equal(fileVersion, versionInfo.FileVersion);
+ Assert.Equal(productVersion, versionInfo.ProductVersion);
+ Assert.Equal(legalCopyright, versionInfo.LegalCopyright);
+ Assert.Equal(productName, versionInfo.ProductName);
+ Assert.Equal(fileDescription, versionInfo.FileDescription);
+ Assert.Equal(companyName, versionInfo.CompanyName);
+ } else {
+ var file = PEFile.FromFile(exeFile);
+ var image = PEImage.FromFile(file);
+ Assert.NotNull(image.Resources);
+ var versionInfo = VersionInfoResource.FromDirectory(image.Resources);
+
+ var stringInfo = versionInfo.GetChild(StringFileInfo.StringFileInfoKey);
+ Assert.NotNull(stringInfo);
+ Assert.Single(stringInfo.Tables);
+
+ var stringTable = stringInfo.Tables[0];
+ Assert.Equal(companyName, stringTable[StringTable.CompanyNameKey]);
+ Assert.Equal(fileDescription, stringTable[StringTable.FileDescriptionKey]);
+ Assert.Equal(fileVersion, stringTable[StringTable.FileVersionKey]);
+ Assert.Equal(legalCopyright, stringTable[StringTable.LegalCopyrightKey]);
+ Assert.Equal(productName, stringTable[StringTable.ProductNameKey]);
+ Assert.Equal(productVersion, stringTable[StringTable.ProductVersionKey]);
+ }
+ }
+}
diff --git a/test/Velopack.Packaging.Tests/TestApp.cs b/test/Velopack.Packaging.Tests/TestApp.cs
index a019b8f6..b7f56b27 100644
--- a/test/Velopack.Packaging.Tests/TestApp.cs
+++ b/test/Velopack.Packaging.Tests/TestApp.cs
@@ -1,6 +1,7 @@
using System.Diagnostics;
using Velopack.Packaging.Unix.Commands;
using Velopack.Packaging.Windows.Commands;
+using Velopack.Vpk;
using Velopack.Vpk.Logging;
namespace Velopack.Packaging.Tests;
@@ -8,8 +9,10 @@ namespace Velopack.Packaging.Tests;
public static class TestApp
{
public static void PackTestApp(string id, string version, string testString, string releaseDir, ILogger logger,
- string releaseNotes = null, string channel = null)
+ string releaseNotes = null, string channel = null, RID targetRid = null)
{
+ targetRid ??= RID.Parse(VelopackRuntimeInfo.SystemRid);
+
var projDir = PathHelper.GetTestRootPath("TestApp");
var testStringFile = Path.Combine(projDir, "Const.cs");
var oldText = File.ReadAllText(testStringFile);
@@ -17,7 +20,7 @@ public static class TestApp
try {
File.WriteAllText(testStringFile, $"class Const {{ public const string TEST_STRING = \"{testString}\"; }}");
- var args = new string[] { "publish", "--no-self-contained", "-c", "Release", "-r", VelopackRuntimeInfo.SystemRid, "-o", "publish" };
+ var args = new string[] { "publish", "--no-self-contained", "-c", "Release", "-r", targetRid.ToString(), "-o", "publish" };
var psi = new ProcessStartInfo("dotnet");
psi.WorkingDirectory = projDir;
@@ -31,14 +34,14 @@ public static class TestApp
if (p.ExitCode != 0)
throw new Exception($"dotnet publish failed with exit code {p.ExitCode}");
- var console = new BasicConsole(logger, new DefaultPromptValueFactory(false));
+ var console = new BasicConsole(logger, new VelopackDefaults(false));
- if (VelopackRuntimeInfo.IsWindows) {
+ if (targetRid.BaseRID == RuntimeOs.Windows) {
var options = new WindowsPackOptions {
EntryExecutableName = "TestApp.exe",
ReleaseDir = new DirectoryInfo(releaseDir),
PackId = id,
- TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
+ TargetRuntime = targetRid,
PackVersion = version,
PackDirectory = Path.Combine(projDir, "publish"),
ReleaseNotes = releaseNotes,
@@ -46,25 +49,29 @@ public static class TestApp
};
var runner = new WindowsPackCommandRunner(logger, console);
runner.Run(options).GetAwaiterResult();
- } else if (VelopackRuntimeInfo.IsOSX) {
+ } else if (targetRid.BaseRID == RuntimeOs.OSX) {
var options = new OsxPackOptions {
EntryExecutableName = "TestApp",
ReleaseDir = new DirectoryInfo(releaseDir),
PackId = id,
- TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
+ TargetRuntime = targetRid,
PackVersion = version,
PackDirectory = Path.Combine(projDir, "publish"),
ReleaseNotes = releaseNotes,
Channel = channel,
};
- var runner = new OsxPackCommandRunner(logger, console);
- runner.Run(options).GetAwaiterResult();
- } else if (VelopackRuntimeInfo.IsLinux) {
+ if (VelopackRuntimeInfo.IsOSX) {
+ var runner = new OsxPackCommandRunner(logger, console);
+ runner.Run(options).GetAwaiterResult();
+ } else {
+ throw new PlatformNotSupportedException();
+ }
+ } else if (targetRid.BaseRID == RuntimeOs.Linux) {
var options = new LinuxPackOptions {
EntryExecutableName = "TestApp",
ReleaseDir = new DirectoryInfo(releaseDir),
PackId = id,
- TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
+ TargetRuntime = targetRid,
PackVersion = version,
PackDirectory = Path.Combine(projDir, "publish"),
ReleaseNotes = releaseNotes,
diff --git a/test/Velopack.Packaging.Tests/Velopack.Packaging.Tests.csproj b/test/Velopack.Packaging.Tests/Velopack.Packaging.Tests.csproj
index d0bed100..313a1b66 100644
--- a/test/Velopack.Packaging.Tests/Velopack.Packaging.Tests.csproj
+++ b/test/Velopack.Packaging.Tests/Velopack.Packaging.Tests.csproj
@@ -10,7 +10,7 @@
-
+
@@ -18,6 +18,10 @@
all
runtime; build; native; contentfiles; analyzers
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
diff --git a/test/Velopack.Packaging.Tests/WindowsPackTests.cs b/test/Velopack.Packaging.Tests/WindowsPackTests.cs
index 66e85234..e44a48ce 100644
--- a/test/Velopack.Packaging.Tests/WindowsPackTests.cs
+++ b/test/Velopack.Packaging.Tests/WindowsPackTests.cs
@@ -8,6 +8,7 @@ using Velopack.Compression;
using Velopack.Packaging.Commands;
using Velopack.Packaging.Exceptions;
using Velopack.Packaging.Windows.Commands;
+using Velopack.Vpk;
using Velopack.Vpk.Logging;
using Velopack.Windows;
@@ -25,7 +26,7 @@ public class WindowsPackTests
private WindowsPackCommandRunner GetPackRunner(ILogger logger)
{
- var console = new BasicConsole(logger, new DefaultPromptValueFactory(false));
+ var console = new BasicConsole(logger, new VelopackDefaults(false));
return new WindowsPackCommandRunner(logger, console);
}
@@ -327,7 +328,7 @@ public class WindowsPackTests
// apply delta and check package
var output = Path.Combine(releaseDir, "delta.patched");
- new DeltaPatchCommandRunner(logger, new BasicConsole(logger, new DefaultPromptValueFactory(false))).Run(new DeltaPatchOptions {
+ new DeltaPatchCommandRunner(logger, new BasicConsole(logger, new VelopackDefaults(false))).Run(new DeltaPatchOptions {
BasePackage = Path.Combine(releaseDir, $"{id}-1.0.0-full.nupkg"),
OutputFile = output,
PatchFiles = new[] { new FileInfo(deltaPath) },
@@ -344,7 +345,7 @@ public class WindowsPackTests
// can apply multiple deltas, and handle add/removing files?
output = Path.Combine(releaseDir, "delta.patched2");
var deltav3 = Path.Combine(releaseDir, $"{id}-3.0.0-delta.nupkg");
- new DeltaPatchCommandRunner(logger, new BasicConsole(logger, new DefaultPromptValueFactory(false))).Run(new DeltaPatchOptions {
+ new DeltaPatchCommandRunner(logger, new BasicConsole(logger, new VelopackDefaults(false))).Run(new DeltaPatchOptions {
BasePackage = Path.Combine(releaseDir, $"{id}-1.0.0-full.nupkg"),
OutputFile = output,
PatchFiles = [new FileInfo(deltaPath), new FileInfo(deltav3)],
diff --git a/test/Velopack.Tests/Velopack.Tests.csproj b/test/Velopack.Tests/Velopack.Tests.csproj
index 41559489..66d3ed99 100644
--- a/test/Velopack.Tests/Velopack.Tests.csproj
+++ b/test/Velopack.Tests/Velopack.Tests.csproj
@@ -39,6 +39,10 @@
all
runtime; build; native; contentfiles; analyzers
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
diff --git a/test/fixtures/Clowd.exe b/test/fixtures/Clowd.exe
new file mode 100644
index 00000000..2c47b7bb
Binary files /dev/null and b/test/fixtures/Clowd.exe differ
diff --git a/test/fixtures/clowd.ico b/test/fixtures/clowd.ico
new file mode 100644
index 00000000..486bf28b
Binary files /dev/null and b/test/fixtures/clowd.ico differ
diff --git a/vendor/7za.exe b/vendor/7za.exe
deleted file mode 100644
index 9544a638..00000000
Binary files a/vendor/7za.exe and /dev/null differ
diff --git a/vendor/THIRD_PARTY_NOTICES.md b/vendor/THIRD_PARTY_NOTICES.md
index 93075955..99339d07 100644
--- a/vendor/THIRD_PARTY_NOTICES.md
+++ b/vendor/THIRD_PARTY_NOTICES.md
@@ -1,27 +1,22 @@
# Vendor Binaries
This folder contains pre-compiled binaries from a variety of sources. These should be updated periodically.
-### rcedit.exe v2.0.0
-- Updates PE resources, like VersionInfo or icons. It is used when generating `Setup.exe` and `Update.exe` to apply the user preferences.
-- Can be found at https://github.com/electron/rcedit/releases
-- MIT License: https://github.com/electron/rcedit/blob/master/LICENSE
-
### signtool.exe v10.0.22621
- Signs application binaries while building packages.
- Can be found in the Windows SDK at "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x86\signtool.exe" or similar, depending on the version of the SDK you have installed.
- License? https://github.com/dotnet/docs/issues/10478
-### 7za.exe / 7zz v21.07
-- Incldued because it is much faster at zipping / unzipping than the available managed algorithms.
-- Can be found at https://www.7-zip.org/
-- License is LGPL & BSD 3: https://www.7-zip.org/license.txt
-
### zstd.exe v1.5.5
- Fast compression and diff/patch
- Can be found at https://github.com/facebook/zstd
- License is GPL-2.0 & BSD 3: https://github.com/facebook/zstd/blob/dev/LICENSE, https://github.com/facebook/zstd/blob/dev/COPYING
-### appimagetool
-- Create .AppImage for Linux
+### appimagekit (continuous Mar 8, 2023)
+- Only include the "runtime" binaries needed to create a .AppImage for Linux
- Can be found at https://github.com/AppImage/AppImageKit
-- License is MIT https://github.com/AppImage/AppImageKit/blob/master/LICENSE
\ No newline at end of file
+- License is MIT https://github.com/AppImage/AppImageKit/blob/master/LICENSE
+
+### squashfs-tools-ng v1.3.0
+- Squashfs utilities for Windows
+- Can be found at https://github.com/AgentD/squashfs-tools-ng
+- License is GPL-3 https://github.com/AgentD/squashfs-tools-ng/blob/master/COPYING.md
\ No newline at end of file
diff --git a/vendor/appimagekit-runtime-aarch64 b/vendor/appimagekit-runtime-aarch64
new file mode 100644
index 00000000..c60ccae0
Binary files /dev/null and b/vendor/appimagekit-runtime-aarch64 differ
diff --git a/vendor/appimagekit-runtime-i686 b/vendor/appimagekit-runtime-i686
new file mode 100644
index 00000000..5319ae61
Binary files /dev/null and b/vendor/appimagekit-runtime-i686 differ
diff --git a/vendor/appimagekit-runtime-x86_64 b/vendor/appimagekit-runtime-x86_64
new file mode 100644
index 00000000..3d73e2ab
Binary files /dev/null and b/vendor/appimagekit-runtime-x86_64 differ
diff --git a/vendor/appimagetool-x86_64.AppImage b/vendor/appimagetool-x86_64.AppImage
deleted file mode 100755
index 89ff93ca..00000000
Binary files a/vendor/appimagetool-x86_64.AppImage and /dev/null differ
diff --git a/vendor/rcedit.exe b/vendor/rcedit.exe
deleted file mode 100644
index 0d817e90..00000000
Binary files a/vendor/rcedit.exe and /dev/null differ
diff --git a/vendor/squashfs-tools/gensquashfs.exe b/vendor/squashfs-tools/gensquashfs.exe
new file mode 100644
index 00000000..ce4b58b5
Binary files /dev/null and b/vendor/squashfs-tools/gensquashfs.exe differ
diff --git a/vendor/squashfs-tools/liblz4-1.dll b/vendor/squashfs-tools/liblz4-1.dll
new file mode 100644
index 00000000..41d26d2f
Binary files /dev/null and b/vendor/squashfs-tools/liblz4-1.dll differ
diff --git a/vendor/squashfs-tools/liblzma-5.dll b/vendor/squashfs-tools/liblzma-5.dll
new file mode 100644
index 00000000..8fa6c428
Binary files /dev/null and b/vendor/squashfs-tools/liblzma-5.dll differ
diff --git a/vendor/squashfs-tools/liblzo2-2.dll b/vendor/squashfs-tools/liblzo2-2.dll
new file mode 100644
index 00000000..32a534f1
Binary files /dev/null and b/vendor/squashfs-tools/liblzo2-2.dll differ
diff --git a/vendor/squashfs-tools/libsquashfs.dll b/vendor/squashfs-tools/libsquashfs.dll
new file mode 100644
index 00000000..9398b5e8
Binary files /dev/null and b/vendor/squashfs-tools/libsquashfs.dll differ
diff --git a/vendor/squashfs-tools/libzstd.dll b/vendor/squashfs-tools/libzstd.dll
new file mode 100644
index 00000000..2c928494
Binary files /dev/null and b/vendor/squashfs-tools/libzstd.dll differ
diff --git a/vendor/squashfs-tools/rdsquashfs.exe b/vendor/squashfs-tools/rdsquashfs.exe
new file mode 100644
index 00000000..241ce118
Binary files /dev/null and b/vendor/squashfs-tools/rdsquashfs.exe differ
diff --git a/vendor/squashfs-tools/sqfs2tar.exe b/vendor/squashfs-tools/sqfs2tar.exe
new file mode 100644
index 00000000..bdfe752e
Binary files /dev/null and b/vendor/squashfs-tools/sqfs2tar.exe differ
diff --git a/vendor/squashfs-tools/sqfsdiff.exe b/vendor/squashfs-tools/sqfsdiff.exe
new file mode 100644
index 00000000..4197b5af
Binary files /dev/null and b/vendor/squashfs-tools/sqfsdiff.exe differ
diff --git a/vendor/squashfs-tools/tar2sqfs.exe b/vendor/squashfs-tools/tar2sqfs.exe
new file mode 100644
index 00000000..70721c95
Binary files /dev/null and b/vendor/squashfs-tools/tar2sqfs.exe differ
diff --git a/vendor/squashfs-tools/zlib1.dll b/vendor/squashfs-tools/zlib1.dll
new file mode 100644
index 00000000..2f12f9e5
Binary files /dev/null and b/vendor/squashfs-tools/zlib1.dll differ