mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Add cross-compiling support
This commit is contained in:
104
.github/workflows/build.yml
vendored
104
.github/workflows/build.yml
vendored
@@ -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
|
||||
|
||||
12
Velopack.sln
12
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
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<BeforeTargetFrameworkInferenceTargets>$(MSBuildThisFileDirectory)SelfContained.targets</BeforeTargetFrameworkInferenceTargets>
|
||||
<NoWarn>$(NoWarn);NETSDK1188</NoWarn>
|
||||
<NoWarn>$(NoWarn);NETSDK1188;NU5100</NoWarn>
|
||||
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<PathMap>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)'))=./</PathMap>
|
||||
|
||||
@@ -35,10 +35,7 @@
|
||||
|
||||
<Target Name="AddNugetVendorLibs" BeforeTargets="Build" Condition=" '$(VelopackPackageVendorLibs)' == 'true' ">
|
||||
<ItemGroup>
|
||||
<None Include="..\..\vendor\rcedit.exe" Pack="true" PackagePath="vendor" />
|
||||
<None Include="..\..\vendor\zstd.exe" Pack="true" PackagePath="vendor" />
|
||||
<None Include="..\..\vendor\signtool.exe" Pack="true" PackagePath="vendor" />
|
||||
<None Include="..\..\vendor\appimagetool-x86_64.AppImage" Pack="true" PackagePath="vendor" />
|
||||
<None Include="..\..\vendor\**" Pack="true" PackagePath="vendor" />
|
||||
<None Include="..\..\Velopack.entitlements" Pack="true" PackagePath="vendor" />
|
||||
<None Include="..\..\artwork\DefaultApp.icns" Pack="true" PackagePath="vendor" />
|
||||
<None Include="..\..\artwork\DefaultApp.png" Pack="true" PackagePath="vendor" />
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.9.5" />
|
||||
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.10.4" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="3.5.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -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, )"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public class GitHubRepository : SourceRepository<GitHubDownloadOptions, GithubSo
|
||||
public async Task UploadMissingAssetsAsync(GitHubUploadOptions options)
|
||||
{
|
||||
var (repoOwner, repoName) = GetOwnerAndRepo(options.RepoUrl);
|
||||
var helper = new ReleaseEntryHelper(options.ReleaseDir.FullName, options.Channel, Log);
|
||||
var helper = new ReleaseEntryHelper(options.ReleaseDir.FullName, options.Channel, Log, options.TargetOs);
|
||||
var build = BuildAssets.Read(options.ReleaseDir.FullName, options.Channel);
|
||||
var latest = helper.GetLatestFullRelease();
|
||||
var latestPath = Path.Combine(options.ReleaseDir.FullName, latest.FileName);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AWSSDK.S3" Version="3.7.308" />
|
||||
<PackageReference Include="AWSSDK.S3" Version="3.7.308.8" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.20.0" />
|
||||
<PackageReference Include="Octokit" Version="11.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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, )"
|
||||
|
||||
61
src/Velopack.IcoLib/Binary/BitReader.cs
Normal file
61
src/Velopack.IcoLib/Binary/BitReader.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/Velopack.IcoLib/Binary/BitWriter.cs
Normal file
63
src/Velopack.IcoLib/Binary/BitWriter.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/Velopack.IcoLib/Binary/ByteOrderConverter.cs
Normal file
39
src/Velopack.IcoLib/Binary/ByteOrderConverter.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
53
src/Velopack.IcoLib/Binary/ByteReader.cs
Normal file
53
src/Velopack.IcoLib/Binary/ByteReader.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
|
||||
namespace Ico.Binary
|
||||
{
|
||||
public class ByteReader
|
||||
{
|
||||
public Memory<byte> Data { get; }
|
||||
public ByteOrder Endianness { get; }
|
||||
public int SeekOffset { get; set; }
|
||||
|
||||
public ByteReader(Memory<byte> 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;
|
||||
}
|
||||
}
|
||||
70
src/Velopack.IcoLib/Binary/ByteWriter.cs
Normal file
70
src/Velopack.IcoLib/Binary/ByteWriter.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ico.Binary
|
||||
{
|
||||
internal class ByteWriter
|
||||
{
|
||||
public List<byte> Data { get; } = new List<byte>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
302
src/Velopack.IcoLib/Codecs/BmpDecoder.cs
Normal file
302
src/Velopack.IcoLib/Codecs/BmpDecoder.cs
Normal file
@@ -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<Rgba32>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
328
src/Velopack.IcoLib/Codecs/BmpEncoder.cs
Normal file
328
src/Velopack.IcoLib/Codecs/BmpEncoder.cs
Normal file
@@ -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<Rgba32, int>();
|
||||
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<Rgba32, uint>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
236
src/Velopack.IcoLib/Codecs/BmpUtil.cs
Normal file
236
src/Velopack.IcoLib/Codecs/BmpUtil.cs
Normal file
@@ -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<Rgba32> image)
|
||||
{
|
||||
return IsAnyPixel(image, (x, y, pixel)
|
||||
=> !IsCompletelyOpaque(pixel) && !IsCompletelyTransparent(pixel));
|
||||
}
|
||||
|
||||
public static bool IsAnyAlphaChannel(Image<Rgba32> image)
|
||||
{
|
||||
return IsAnyPixel(image, (x, y, pixel)
|
||||
=> !IsCompletelyOpaque(pixel));
|
||||
}
|
||||
|
||||
public static ulong GetNumberOfDistinctColors(Image<Rgba32> image, bool includeAlpha)
|
||||
{
|
||||
var colors = new HashSet<uint>();
|
||||
|
||||
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<Rgba32> 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<Rgba32> 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<Rgba32> 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<Rgba32> image, Action<int, int, Rgba32> 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<Rgba32> image, Func<int, int, Rgba32, bool> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/Velopack.IcoLib/Codecs/EncodingOptions.cs
Normal file
26
src/Velopack.IcoLib/Codecs/EncodingOptions.cs
Normal file
@@ -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,
|
||||
}
|
||||
}
|
||||
20
src/Velopack.IcoLib/Codecs/FileFormatConstants.cs
Normal file
20
src/Velopack.IcoLib/Codecs/FileFormatConstants.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
110
src/Velopack.IcoLib/Codecs/IcoDecoder.cs
Normal file
110
src/Velopack.IcoLib/Codecs/IcoDecoder.cs
Normal file
@@ -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<IcoFrame> 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
53
src/Velopack.IcoLib/Codecs/IcoEncoder.cs
Normal file
53
src/Velopack.IcoLib/Codecs/IcoEncoder.cs
Normal file
@@ -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<uint>();
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
157
src/Velopack.IcoLib/Codecs/PngDecoder.cs
Normal file
157
src/Velopack.IcoLib/Codecs/PngDecoder.cs
Normal file
@@ -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<Rgba32>(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<byte> 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<Rgba32> 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
40
src/Velopack.IcoLib/Host/ExceptionWrapper.cs
Normal file
40
src/Velopack.IcoLib/Host/ExceptionWrapper.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/Velopack.IcoLib/Host/IErrorReporter.cs
Normal file
23
src/Velopack.IcoLib/Host/IErrorReporter.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
18
src/Velopack.IcoLib/Model/IcoFrame.cs
Normal file
18
src/Velopack.IcoLib/Model/IcoFrame.cs
Normal file
@@ -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<Rgba32> CookedData { get; set; }
|
||||
|
||||
public bool[,] Mask { get; set; }
|
||||
|
||||
public uint TotalDiskUsage { get; set; }
|
||||
}
|
||||
}
|
||||
45
src/Velopack.IcoLib/Model/IcoFrameEncoding.cs
Normal file
45
src/Velopack.IcoLib/Model/IcoFrameEncoding.cs
Normal file
@@ -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; }
|
||||
}
|
||||
}
|
||||
29
src/Velopack.IcoLib/Model/ParseContext.cs
Normal file
29
src/Velopack.IcoLib/Model/ParseContext.cs
Normal file
@@ -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<IcoFrame> 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; }
|
||||
}
|
||||
}
|
||||
23
src/Velopack.IcoLib/Model/PngFileEncoding.cs
Normal file
23
src/Velopack.IcoLib/Model/PngFileEncoding.cs
Normal file
@@ -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; }
|
||||
}
|
||||
}
|
||||
4
src/Velopack.IcoLib/README.md
Normal file
4
src/Velopack.IcoLib/README.md
Normal file
@@ -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
|
||||
60
src/Velopack.IcoLib/Validation/IcoErrorCode.cs
Normal file
60
src/Velopack.IcoLib/Validation/IcoErrorCode.cs
Normal file
@@ -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,
|
||||
}
|
||||
}
|
||||
56
src/Velopack.IcoLib/Validation/InvalidIcoFileException.cs
Normal file
56
src/Velopack.IcoLib/Validation/InvalidIcoFileException.cs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/Velopack.IcoLib/Validation/InvalidPngFileException.cs
Normal file
32
src/Velopack.IcoLib/Validation/InvalidPngFileException.cs
Normal file
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
17
src/Velopack.IcoLib/Velopack.IcoLib.csproj
Normal file
17
src/Velopack.IcoLib/Velopack.IcoLib.csproj
Normal file
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<RootNamespace>Ico</RootNamespace>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<Version>1.1.1</Version>
|
||||
<Authors>Jeffrey Tippet</Authors>
|
||||
<Product>IcoTools</Product>
|
||||
<Copyright>Copyright 2019, Jeffrey Tippet. All rights reserved.</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.8" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
93
src/Velopack.IcoLib/packages.lock.json
Normal file
93
src/Velopack.IcoLib/packages.lock.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// An instance of this exception is thrown when an AppHost binary update
|
||||
/// fails due to known user errors.
|
||||
/// </summary>
|
||||
public class AppHostUpdateException : Exception
|
||||
{
|
||||
internal AppHostUpdateException(string message = null)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The application host executable cannot be customized because adding resources requires
|
||||
/// that the build be performed on Windows (excluding Nano Server).
|
||||
/// </summary>
|
||||
public sealed class AppHostCustomizationUnsupportedOSException : AppHostUpdateException
|
||||
{
|
||||
internal AppHostCustomizationUnsupportedOSException()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The MachO application host executable cannot be customized because
|
||||
/// it was not in the expected format
|
||||
/// </summary>
|
||||
public sealed class AppHostMachOFormatException : AppHostUpdateException
|
||||
{
|
||||
public readonly MachOFormatError Error;
|
||||
|
||||
internal AppHostMachOFormatException(MachOFormatError error)
|
||||
{
|
||||
Error = error;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unable to use the input file as application host executable because it's not a
|
||||
/// Windows executable for the CUI (Console) subsystem.
|
||||
/// </summary>
|
||||
public sealed class AppHostNotCUIException : AppHostUpdateException
|
||||
{
|
||||
internal AppHostNotCUIException()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unable to use the input file as an application host executable
|
||||
/// because it's not a Windows PE file
|
||||
/// </summary>
|
||||
public sealed class AppHostNotPEFileException : AppHostUpdateException
|
||||
{
|
||||
internal AppHostNotPEFileException()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unable to sign the apphost binary.
|
||||
/// </summary>
|
||||
public sealed class AppHostSigningException : AppHostUpdateException
|
||||
{
|
||||
public readonly int ExitCode;
|
||||
|
||||
internal AppHostSigningException(int exitCode, string signingErrorMessage)
|
||||
: base(signingErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given app file name is longer than 1024 bytes
|
||||
/// </summary>
|
||||
public sealed class AppNameTooLongException : AppHostUpdateException
|
||||
{
|
||||
public string LongName { get; }
|
||||
|
||||
internal AppNameTooLongException(string name)
|
||||
{
|
||||
LongName = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an exception thrown because of a Win32 error
|
||||
/// </summary>
|
||||
public class HResultException : Exception
|
||||
{
|
||||
public readonly int Win32HResult;
|
||||
public HResultException(int hResult) : base(hResult.ToString("X4"))
|
||||
{
|
||||
Win32HResult = hResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Embeds the App Name into the AppHost.exe
|
||||
/// If an apphost is a single-file bundle, updates the location of the bundle headers.
|
||||
/// </summary>
|
||||
public static class HostWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// hash value embedded in default apphost executable in a place where the path to the app binary should be stored.
|
||||
/// </summary>
|
||||
private const string AppBinaryPathPlaceholder = "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2";
|
||||
private static readonly byte[] AppBinaryPathPlaceholderSearchValue = Encoding.UTF8.GetBytes(AppBinaryPathPlaceholder);
|
||||
|
||||
/// <summary>
|
||||
/// Create an AppHost with embedded configuration of app binary location
|
||||
/// </summary>
|
||||
/// <param name="appHostSourceFilePath">The path of Apphost template, which has the place holder</param>
|
||||
/// <param name="appHostDestinationFilePath">The destination path for desired location to place, including the file name</param>
|
||||
/// <param name="appBinaryFilePath">Full path to app binary or relative path to the result apphost file</param>
|
||||
/// <param name="windowsGraphicalUserInterface">Specify whether to set the subsystem to GUI. Only valid for PE apphosts.</param>
|
||||
/// <param name="assemblyToCopyResorcesFrom">Path to the intermediate assembly, used for copying resources to PE apphosts.</param>
|
||||
/// <param name="enableMacOSCodeSign">Sign the app binary using codesign with an anonymous certificate.</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the current AppHost as a single-file bundle.
|
||||
/// </summary>
|
||||
/// <param name="appHostPath">The path of Apphost template, which has the place holder</param>
|
||||
/// <param name="bundleHeaderOffset">The offset to the location of bundle header</param>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the an AppHost is a single-file bundle
|
||||
/// </summary>
|
||||
/// <param name="appHostFilePath">The path of Apphost to check</param>
|
||||
/// <returns>True if the AppHost is a single-file bundle, false otherwise</returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Additional details about the failure with caused an AppHostMachOFormatException
|
||||
/// </summary>
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream containing the AppHost</param>
|
||||
/// <returns>
|
||||
/// True if
|
||||
/// - The input is a MachO binary, and
|
||||
/// - It is a signed binary, and
|
||||
/// - The signature was successfully removed
|
||||
/// False otherwise
|
||||
/// </returns>
|
||||
/// <exception cref="AppHostMachOFormatException">
|
||||
/// The input is a MachO file, but doesn't match the expect format of the AppHost.
|
||||
/// </exception>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the AppHost</param>
|
||||
/// <returns>
|
||||
/// True if
|
||||
/// - The input is a MachO binary, and
|
||||
/// - The additional bytes were successfully accomodated within the MachO segments.
|
||||
/// False otherwise
|
||||
/// </returns>
|
||||
/// <exception cref="AppHostMachOFormatException">
|
||||
/// The input is a MachO file, but doesn't match the expect format of the AppHost.
|
||||
/// </exception>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// The first two bytes of a PE file are a constant signature.
|
||||
/// </summary>
|
||||
private const ushort PEFileSignature = 0x5A4D;
|
||||
|
||||
/// <summary>
|
||||
/// The offset of the PE header pointer in the DOS header.
|
||||
/// </summary>
|
||||
private const int PEHeaderPointerOffset = 0x3C;
|
||||
|
||||
/// <summary>
|
||||
/// The offset of the Subsystem field in the PE header.
|
||||
/// </summary>
|
||||
private const int SubsystemOffset = 0x5C;
|
||||
|
||||
/// <summary>
|
||||
/// The value of the sybsystem field which indicates Windows GUI (Graphical UI)
|
||||
/// </summary>
|
||||
private const ushort WindowsGUISubsystem = 0x2;
|
||||
|
||||
/// <summary>
|
||||
/// The value of the subsystem field which indicates Windows CUI (Console)
|
||||
/// </summary>
|
||||
private const ushort WindowsCUISubsystem = 0x3;
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the apphost file is a windows PE image by looking at the first few bytes.
|
||||
/// </summary>
|
||||
/// <param name="accessor">The memory accessor which has the apphost file opened.</param>
|
||||
/// <returns>true if the accessor represents a PE image, false otherwise.</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method will attempt to set the subsystem to GUI. The apphost file should be a windows PE file.
|
||||
/// </summary>
|
||||
/// <param name="accessor">The memory accessor which has the apphost file opened.</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method will return the subsystem CUI/GUI value. The apphost file should be a windows PE file.
|
||||
/// </summary>
|
||||
/// <param name="accessor">The memory accessor which has the apphost file opened.</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Unable to use input file as a valid application host executable, as it does not contain
|
||||
/// the expected placeholder byte sequence.
|
||||
/// </summary>
|
||||
public class PlaceHolderNotFoundInAppHostException : AppHostUpdateException
|
||||
{
|
||||
public byte[] MissingPattern { get; }
|
||||
public PlaceHolderNotFoundInAppHostException(byte[] pattern)
|
||||
{
|
||||
MissingPattern = pattern;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// BundleOptions: Optional settings for configuring the type of files
|
||||
/// included in the single file bundle.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum BundleOptions
|
||||
{
|
||||
None = 0,
|
||||
BundleNativeBinaries = 1,
|
||||
BundleOtherFiles = 2,
|
||||
BundleSymbolFiles = 4,
|
||||
BundleAllContent = BundleNativeBinaries | BundleOtherFiles,
|
||||
EnableCompression = 8,
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Bundler: Functionality to embed the managed app and its dependencies
|
||||
/// into the host native binary.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Embed 'file' into 'bundle'
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// startOffset: offset of the start 'file' within 'bundle'
|
||||
/// compressedSize: size of the compressed data, if entry was compressed, otherwise 0
|
||||
/// </returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a bundle, given the specification of embedded files
|
||||
/// </summary>
|
||||
/// <param name="fileSpecs">
|
||||
/// 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.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The full path the the generated bundle file
|
||||
/// </returns>
|
||||
/// <exceptions>
|
||||
/// ArgumentException if input is invalid
|
||||
/// IOExceptions and ArgumentExceptions from callees flow to the caller.
|
||||
/// </exceptions>
|
||||
public string GenerateBundle(IReadOnlyList<FileSpec> 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<string, FileSpec>(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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
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}";
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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]" : "")}";
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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]
|
||||
///
|
||||
///
|
||||
///
|
||||
/// _________________________________________________
|
||||
/// </summary>
|
||||
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<FileEntry> Files;
|
||||
public string BundleVersion => $"{BundleMajorVersion}.{BundleMinorVersion}";
|
||||
|
||||
public Manifest(uint bundleMajorVersion, bool netcoreapp3CompatMode = false)
|
||||
{
|
||||
BundleMajorVersion = bundleMajorVersion;
|
||||
Files = new List<FileEntry>();
|
||||
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<byte>(), 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
|
||||
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";
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Tracing utilities for diagnostic output
|
||||
/// </summary>
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for modifying the embedded native resources
|
||||
/// in a PE image. It currently only works on Windows, because it
|
||||
/// requires various kernel32 APIs.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the native handle for the resource update.
|
||||
/// </summary>
|
||||
private readonly SafeUpdateHandle hUpdate;
|
||||
|
||||
///<summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public ResourceUpdater(string peFile)
|
||||
{
|
||||
hUpdate = Kernel32.BeginUpdateResource(peFile, false);
|
||||
if (hUpdate.IsInvalid) {
|
||||
ThrowExceptionForLastWin32Error();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net472;net6.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<NoWarn>$(NoWarn);CA2007;CS8002;IDE0161</NoWarn>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<RootNamespace>Microsoft.NET.HostModel</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Reflection.Metadata" Version="8.0.0" Condition="'$(TargetFramework)' == 'net472'" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<string, string>() {
|
||||
{ "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<string> 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<string> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
46
src/Velopack.Packaging.Unix/BinDetect.cs
Normal file
46
src/Velopack.Packaging.Unix/BinDetect.cs
Normal file
@@ -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<byte> 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];
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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<LinuxPackOptions>
|
||||
{
|
||||
protected string PortablePackagePath { get; set; }
|
||||
@@ -21,36 +19,34 @@ public class LinuxPackCommandRunner : PackageBuilder<LinuxPackOptions>
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public class OsxBundleCommandRunner : ICommand<OsxBundleOptions>
|
||||
|
||||
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<OsxBundleOptions>
|
||||
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.");
|
||||
}
|
||||
|
||||
|
||||
@@ -32,10 +32,10 @@ public class OsxPackCommandRunner : PackageBuilder<OsxPackOptions>
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ELFSharp" Version="2.17.3" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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, )"
|
||||
|
||||
@@ -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 +
|
||||
|
||||
@@ -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<WindowsPackOptions>
|
||||
{
|
||||
public WindowsPackCommandRunner(ILogger logger, IFancyConsole console)
|
||||
@@ -48,7 +46,7 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
|
||||
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<WindowsPackOptions>
|
||||
}
|
||||
|
||||
// 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<WindowsPackOptions>
|
||||
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<WindowsPackOptions>
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
111
src/Velopack.Packaging.Windows/IcoExtract.cs
Normal file
111
src/Velopack.Packaging.Windows/IcoExtract.cs
Normal file
@@ -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<IcoFrame> _frames = new();
|
||||
|
||||
public IcoExtract(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public List<IcoFrame> ExtractFrames(FileInfo file)
|
||||
{
|
||||
var reporter = new IcoILoggerReporter(_logger);
|
||||
var context = new ParseContext {
|
||||
DisplayedPath = file.Name,
|
||||
FullPath = file.FullName,
|
||||
GeneratedFrames = new List<IcoFrame>(),
|
||||
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
|
||||
}
|
||||
@@ -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<string> args = new List<string>() {
|
||||
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));
|
||||
}
|
||||
}
|
||||
194
src/Velopack.Packaging.Windows/ResourceEdit.cs
Normal file
194
src/Velopack.Packaging.Windows/ResourceEdit.cs
Normal file
@@ -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<uint, IconGroupDirectory> { { 1, group } });
|
||||
}
|
||||
|
||||
private void WriteToDirectory(IResourceDirectory rootDirectory, Dictionary<uint, IconGroupDirectory> _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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,11 @@
|
||||
<TargetFrameworks>net472;net6.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<NoWarn>$(NoWarn);CA2007;CS8002</NoWarn>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Velopack.Packaging.HostModel\Velopack.Packaging.HostModel.csproj" />
|
||||
<ProjectReference Include="..\Velopack.IcoLib\Velopack.IcoLib.csproj" />
|
||||
<ProjectReference Include="..\Velopack.Packaging\Velopack.Packaging.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -15,5 +16,5 @@
|
||||
<PackageReference Include="AsmResolver.DotNet" Version="5.5.1" />
|
||||
<PackageReference Include="AsmResolver.PE.Win32Resources" Version="5.5.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string> args, string workingDir, IDictionary<string, string> envVar = null)
|
||||
{
|
||||
var result = InvokeProcess(exePath, args, workingDir, CancellationToken.None, envVar);
|
||||
|
||||
@@ -19,7 +19,7 @@ public interface IVelopackFlowServiceClient
|
||||
|
||||
Task<Profile?> 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<Profile>(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<string> 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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Velopack.Packaging;
|
||||
public abstract class PackageBuilder<T> : ICommand<T>
|
||||
where T : class, IPackOptions
|
||||
{
|
||||
protected RuntimeOs SupportedTargetOs { get; }
|
||||
protected RuntimeOs TargetOs { get; }
|
||||
|
||||
protected ILogger Log { get; }
|
||||
|
||||
@@ -36,25 +36,28 @@ public abstract class PackageBuilder<T> : ICommand<T>
|
||||
|
||||
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<T> : ICommand<T>
|
||||
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<T> : ICommand<T>
|
||||
|
||||
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) => {
|
||||
|
||||
@@ -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<string, List<VelopackAsset>> _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";
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageReference Include="Markdig" Version="0.37.0" />
|
||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.61.0" />
|
||||
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.61.0" />
|
||||
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.61.0" />
|
||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.61.2" />
|
||||
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.61.2" />
|
||||
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.61.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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, )"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace Velopack.Vpk.Commands;
|
||||
|
||||
public class BaseCommand : CliCommand
|
||||
{
|
||||
public RuntimeOs TargetOs { get; private set; }
|
||||
|
||||
private readonly Dictionary<CliOption, Action<ParseResult, IConfiguration>> _setters = new();
|
||||
|
||||
private readonly Dictionary<CliOption, string> _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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,6 @@ public abstract class PlatformCommand : OutputCommand
|
||||
TargetRuntimeOption = AddOption<string>((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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
namespace Velopack.Vpk.Logging;
|
||||
|
||||
public record DefaultPromptValueFactory(bool DefaultPromptValue)
|
||||
{
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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<int> 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<Microsoft.Extensions.Logging.ILogger>();
|
||||
|
||||
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<WindowsPackCommand, WindowsPackCommandRunner, WindowsPackOptions>(provider);
|
||||
} else if (VelopackRuntimeInfo.IsOSX) {
|
||||
rootCommand.AddCommand<OsxBundleCommand, OsxBundleCommandRunner, OsxBundleOptions>(provider);
|
||||
rootCommand.AddCommand<OsxPackCommand, OsxPackCommandRunner, OsxPackOptions>(provider);
|
||||
} else if (VelopackRuntimeInfo.IsLinux) {
|
||||
break;
|
||||
case RuntimeOs.Linux:
|
||||
rootCommand.AddCommand<LinuxPackCommand, LinuxPackCommandRunner, LinuxPackOptions>(provider);
|
||||
} else {
|
||||
break;
|
||||
case RuntimeOs.OSX:
|
||||
if (VelopackRuntimeInfo.IsOSX) {
|
||||
rootCommand.AddCommand<OsxBundleCommand, OsxBundleCommandRunner, OsxBundleOptions>(provider);
|
||||
rootCommand.AddCommand<OsxPackCommand, OsxPackCommandRunner, OsxPackOptions>(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<ILoggerFactory>().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<IFancyConsole, BasicConsole>();
|
||||
@@ -195,12 +240,15 @@ public static class ProgramCommandExtensions
|
||||
var command = new TCli();
|
||||
command.SetAction(async (ctx, token) => {
|
||||
var logger = provider.GetRequiredService<Microsoft.Extensions.Logging.ILogger>();
|
||||
var console = provider.GetRequiredService<IFancyConsole>();
|
||||
var config = provider.GetRequiredService<IConfiguration>();
|
||||
var defaults = provider.GetRequiredService<VelopackDefaults>();
|
||||
|
||||
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<TOpt>(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;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Expressions" Version="4.0.0" />
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.23407.1" />
|
||||
<PackageReference Include="NuGet.Protocol" Version="6.9.1" />
|
||||
<PackageReference Include="NuGet.Protocol" Version="6.10.0" />
|
||||
<PackageReference Include="Spectre.Console" Version="0.49.1" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="3.5.1" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||
|
||||
18
src/Velopack.Vpk/VelopackDefaults.cs
Normal file
18
src/Velopack.Vpk/VelopackDefaults.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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, )"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<int> progress, CancellationToken cancelToken)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NuGet.Versioning" Version="6.9.1" />
|
||||
<PackageReference Include="NuGet.Versioning" Version="6.10.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" $(TargetFramework.StartsWith('net4')) ">
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -38,10 +38,10 @@
|
||||
|
||||
<ItemGroup Condition=" $(MSBuildProjectName.EndsWith('Tests')) ">
|
||||
<ProjectReference Include="..\Divergic.Logging.Xunit\Divergic.Logging.Xunit.csproj" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="xunit" Version="2.8.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1" />
|
||||
<PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
|
||||
<PackageReference Include="coverlet.msbuild" Version="6.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Update="GitHubActionsTestLogger" Version="2.4.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user