9 Commits
1.1.0 ... 1.8.0

Author SHA1 Message Date
Lightczx
9d71ac82b7 fix ERROR_MRM_MAP_NOT_FOUND 2023-12-27 17:28:52 +08:00
Lightczx
21618e5583 certificates and windows app runtime 2023-12-27 16:52:08 +08:00
Masterain
cd5313f7fd Merge pull request #1 from Masterain98/main
Create .gitlab-ci.yml
2023-12-26 18:28:06 -08:00
Masterain
c7a94856ea Create .gitlab-ci.yml 2023-12-26 18:26:34 -08:00
DismissedLight
2036523ab9 fix TFM 2023-12-26 19:19:30 +08:00
DismissedLight
cf512bc165 move to TFM lib path 2023-12-26 19:16:08 +08:00
Lightczx
5bf193147d fix path lib 2023-12-26 17:30:25 +08:00
Lightczx
6ec62eaae9 change tool path 2023-12-26 17:14:31 +08:00
Lightczx
dcf5d161e6 no private assets 2023-12-26 17:03:14 +08:00
11 changed files with 357 additions and 67 deletions

53
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,53 @@
stages:
- fetch
- release
Fetch:
stage: fetch
rules:
- if: $CI_COMMIT_TAG
tags:
- us3
script:
- apt-get update -qy
- apt-get install -y curl jq
- RELEASE_INFO=$(curl -sSL "https://api.github.com/repos/$CI_PROJECT_PATH/releases/latest")
- ASSET_URL=$(echo "$RELEASE_INFO" | jq -r '.assets[] | select(.name | endswith(".exe")) | .browser_download_url')
- curl -LJO "$ASSET_URL"
- FILE_NAME=$(basename "$ASSET_URL")
- echo "File name at script stage is $FILE_NAME"
- echo "THIS_FILE_NAME=$FILE_NAME" >> next.env
after_script:
- echo "Current Job ID is $CI_JOB_ID"
- echo "THIS_JOB_ID=$CI_JOB_ID" >> next.env
artifacts:
paths:
- "*.exe"
expire_in: 180 days
reports:
dotenv: next.env
release:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
rules:
- if: $CI_COMMIT_TAG
needs:
- job: Fetch
artifacts: true
variables:
TAG: '$CI_COMMIT_TAG'
script:
- echo "Create Release $TAG"
- echo "$THIS_JOB_ID"
- echo "$THIS_FILE_NAME"
release:
name: '$TAG'
tag_name: '$TAG'
ref: '$TAG'
description: 'Release $TAG by CI'
assets:
links:
- name: "$THIS_FILE_NAME"
url: "https://$CI_SERVER_SHELL_SSH_HOST/$CI_PROJECT_PATH/-/jobs/$THIS_JOB_ID/artifacts/raw/$THIS_FILE_NAME?inline=false"
link_type: package

View File

@@ -6,25 +6,7 @@
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<NuSpecFile>Snap.Hutao.Deployment.Runtime.nuspec</NuSpecFile>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemDefinitionGroup>
<PackageReference>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemDefinitionGroup>
<ItemGroup>
<None Include="bin">
<Pack>false</Pack>
</None>
<None Include="obj">
<Pack>false</Pack>
</None>
<Content Include="Snap.Hutao.Deployment.exe">
<Pack>true</Pack>
<PackagePath>runtimes\win-x64\</PackagePath>
</Content>
</ItemGroup>
</Project>

View File

@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>Snap.Hutao.Deployment.Runtime</id>
<version>1.1.0</version>
<version>1.7.0</version>
<authors>DGP Studio</authors>
<developmentDependency>true</developmentDependency>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
@@ -13,6 +13,6 @@
<repository type="git" url="https://github.com/DGP-Studio/Snap.Hutao.Deployment" commit="$commit$" />
</metadata>
<files>
<file src="Snap.Hutao.Deployment.exe" target="runtimes/win-x64/Snap.Hutao.Deployment.exe"/>
<file src="Snap.Hutao.Deployment.exe" target="lib/net8.0-windows10.0.22621.0/Snap.Hutao.Deployment.exe"/>
</files>
</package>

View File

@@ -0,0 +1,45 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace Snap.Hutao.Deployment;
internal static class Certificate
{
private const string CertificateName = "GlobalSign Code Signing Root R45";
private const string CertificateUrl = "https://secure.globalsign.com/cacert/codesigningrootr45.crt";
public static async Task EnsureGlobalSignCodeSigningRootR45Async()
{
using (X509Store store = new(StoreName.Root, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadWrite);
if (store.Certificates.Any(cert => cert.FriendlyName == CertificateName))
{
Console.WriteLine("Certificate [GlobalSign Code Signing Root R45] found");
return;
}
Console.WriteLine("Required Certificate [GlobalSign Code Signing Root R45] not found, download from GlobalSign");
using (HttpClient httpClient = new())
{
byte[] rawData = await httpClient.GetByteArrayAsync(CertificateUrl).ConfigureAwait(false);
Console.WriteLine("""
/
[]
Adding certificate to LocalMachine/ThirdParty Root CA store,
please click [yes] on the [Security Waring] dialog
For more security information, please visit the url down below
https://support.globalsign.com/ca-certificates/root-certificates/globalsign-root-certificates
""");
store.Add(new X509Certificate2(rawData));
}
}
}
}

View File

@@ -1,12 +1,12 @@
namespace Snap.Hutao.Deployment;
internal sealed class PackageDownloadStatus
internal sealed class DownloadStatus
{
public PackageDownloadStatus(long bytesRead, long totalBytes)
public DownloadStatus(long bytesRead, long totalBytes)
{
ProgressDescription = bytesRead != totalBytes
? $"Download Progress: {ToFileSizeString(bytesRead),8}/{ToFileSizeString(totalBytes),8} | {(double)bytesRead / totalBytes,8:P3}"
: "Download Completed\n";
: "\nDownload Completed\n";
}
public string ProgressDescription { get; }

View File

@@ -2,7 +2,7 @@
using System.CommandLine.Invocation;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Windows.Management.Deployment;
@@ -31,16 +31,23 @@ internal static class Invocation
if (isUpdateMode)
{
Console.WriteLine("Exit in 10 seconds...");
await Task.Delay(10000);
await Task.Delay(10000).ConfigureAwait(false);
return;
}
else
{
Console.WriteLine("Start downloading package...");
await DownloadPackageAsync(path);
await PackageDownload.DownloadPackageAsync(path).ConfigureAwait(false);
}
}
await Certificate.EnsureGlobalSignCodeSigningRootR45Async().ConfigureAwait(false);
await WindowsAppSDKDependency.EnsureAsync(path).ConfigureAwait(false);
await RunDeploymentCoreAsync(path, name).ConfigureAwait(false);
}
private static async Task RunDeploymentCoreAsync(string path, string? name)
{
try
{
Console.WriteLine("Initializing PackageManager...");
@@ -49,7 +56,6 @@ internal static class Invocation
{
ForceAppShutdown = true,
RetainFilesOnFailure = true,
StageInPlace = true,
};
Console.WriteLine("Start deploying...");
@@ -57,7 +63,10 @@ internal static class Invocation
{
Console.WriteLine($"[Deploying]: State: {p.state} Progress: {p.percentage}%");
});
DeploymentResult result = await packageManager.AddPackageByUriAsync(new Uri(path), addPackageOptions).AsTask(progress);
DeploymentResult result = await packageManager
.AddPackageByUriAsync(new Uri(path), addPackageOptions)
.AsTask(progress)
.ConfigureAwait(false);
if (result.IsRegistered)
{
@@ -68,11 +77,21 @@ internal static class Invocation
foreach (Windows.ApplicationModel.Package package in packageManager.FindPackages())
{
if (package is { DisplayName: "Snap Hutao", PublisherDisplayName: "DGP Studio" })
try
{
name = package.Id.FamilyName;
Console.WriteLine($"Package found: {name}");
if (package is { DisplayName: "Snap Hutao", PublisherDisplayName: "DGP Studio" })
{
name = package.Id.FamilyName;
Console.WriteLine($"Package found: {name}");
}
}
catch (COMException ex)
{
// ERROR_MRM_MAP_NOT_FOUND
if (ex.HResult is not unchecked((int)0x80073B1F))
{
throw;
}
}
}
}
@@ -94,7 +113,7 @@ internal static class Invocation
Exit in 10 seconds...
""");
await Task.Delay(10000);
await Task.Delay(10000).ConfigureAwait(false);
}
}
catch (Exception ex)
@@ -106,32 +125,7 @@ internal static class Invocation
Exit in 10 seconds...
""");
await Task.Delay(10000);
}
}
private static async Task DownloadPackageAsync(string packagePath)
{
using (HttpClient httpClient = new())
{
HttpShardCopyWorkerOptions<PackageDownloadStatus> options = new()
{
HttpClient = httpClient,
SourceUrl = "https://api.snapgenshin.com/patch/hutao/download",
DestinationFilePath = packagePath,
StatusFactory = (bytesRead, totalBytes) => new PackageDownloadStatus(bytesRead, totalBytes),
};
using (HttpShardCopyWorker<PackageDownloadStatus> worker = await HttpShardCopyWorker<PackageDownloadStatus>.CreateAsync(options).ConfigureAwait(false))
{
Progress<PackageDownloadStatus> progress = new(ConsoleWriteProgress);
await worker.CopyAsync(progress).ConfigureAwait(false);
}
}
static void ConsoleWriteProgress(PackageDownloadStatus status)
{
Console.Write($"\r{status.ProgressDescription}");
await Task.Delay(10000).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace Snap.Hutao.Deployment;
internal static class PackageDownload
{
public static async Task DownloadPackageAsync(string packagePath)
{
using (HttpClient httpClient = new())
{
HttpShardCopyWorkerOptions<DownloadStatus> options = new()
{
HttpClient = httpClient,
SourceUrl = "https://api.snapgenshin.com/patch/hutao/download",
DestinationFilePath = packagePath,
StatusFactory = (bytesRead, totalBytes) => new DownloadStatus(bytesRead, totalBytes),
};
using (HttpShardCopyWorker<DownloadStatus> worker = await HttpShardCopyWorker<DownloadStatus>.CreateAsync(options).ConfigureAwait(false))
{
Progress<DownloadStatus> progress = new(ConsoleWriteProgress);
await worker.CopyAsync(progress).ConfigureAwait(false);
}
}
static void ConsoleWriteProgress(DownloadStatus status)
{
Console.Write($"\r{status.ProgressDescription}");
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using Windows.ApplicationModel;
namespace Snap.Hutao.Deployment;
internal static class PackageVersionExtension
{
public static Version ToVersion(this PackageVersion packageVersion)
{
return new Version(packageVersion.Major, packageVersion.Minor, packageVersion.Build, packageVersion.Revision);
}
}

View File

@@ -5,7 +5,7 @@ VisualStudioVersion = 17.8.34330.188
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Deployment", "Snap.Hutao.Deployment.csproj", "{B457BB36-A85E-4C2B-91C1-9453949445E3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Snap.Hutao.Deployment.Runtime", "..\Snap.Hutao.Deployment.Runtime\Snap.Hutao.Deployment.Runtime.csproj", "{571467C6-27D3-4EC6-A937-D244E86D8400}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Deployment.Runtime", "..\Snap.Hutao.Deployment.Runtime\Snap.Hutao.Deployment.Runtime.csproj", "{571467C6-27D3-4EC6-A937-D244E86D8400}"
ProjectSection(ProjectDependencies) = postProject
{B457BB36-A85E-4C2B-91C1-9453949445E3} = {B457BB36-A85E-4C2B-91C1-9453949445E3}
EndProjectSection
@@ -26,14 +26,14 @@ Global
{B457BB36-A85E-4C2B-91C1-9453949445E3}.Release|Any CPU.Build.0 = Release|Any CPU
{B457BB36-A85E-4C2B-91C1-9453949445E3}.Release|x64.ActiveCfg = Release|x64
{B457BB36-A85E-4C2B-91C1-9453949445E3}.Release|x64.Build.0 = Release|x64
{571467C6-27D3-4EC6-A937-D244E86D8400}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{571467C6-27D3-4EC6-A937-D244E86D8400}.Debug|Any CPU.Build.0 = Debug|Any CPU
{571467C6-27D3-4EC6-A937-D244E86D8400}.Debug|x64.ActiveCfg = Debug|Any CPU
{571467C6-27D3-4EC6-A937-D244E86D8400}.Debug|x64.Build.0 = Debug|Any CPU
{571467C6-27D3-4EC6-A937-D244E86D8400}.Debug|Any CPU.ActiveCfg = Debug|x64
{571467C6-27D3-4EC6-A937-D244E86D8400}.Debug|Any CPU.Build.0 = Debug|x64
{571467C6-27D3-4EC6-A937-D244E86D8400}.Debug|x64.ActiveCfg = Debug|x64
{571467C6-27D3-4EC6-A937-D244E86D8400}.Debug|x64.Build.0 = Debug|x64
{571467C6-27D3-4EC6-A937-D244E86D8400}.Release|Any CPU.ActiveCfg = Release|Any CPU
{571467C6-27D3-4EC6-A937-D244E86D8400}.Release|Any CPU.Build.0 = Release|Any CPU
{571467C6-27D3-4EC6-A937-D244E86D8400}.Release|x64.ActiveCfg = Release|Any CPU
{571467C6-27D3-4EC6-A937-D244E86D8400}.Release|x64.Build.0 = Release|Any CPU
{571467C6-27D3-4EC6-A937-D244E86D8400}.Release|x64.ActiveCfg = Release|x64
{571467C6-27D3-4EC6-A937-D244E86D8400}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -0,0 +1,171 @@
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Windows.Management.Deployment;
namespace Snap.Hutao.Deployment;
internal static partial class WindowsAppSDKDependency
{
private const string SDKInstallerDownloadFormat = "https://aka.ms/windowsappsdk/{0}/{1}/windowsappruntimeinstall-x64.exe";
public static async Task EnsureAsync(string packagePath)
{
using FileStream packageStream = File.OpenRead(packagePath);
using ZipArchive package = new(packageStream, ZipArchiveMode.Read);
(string packageName, string msixVersion) = await ExtractRuntimePackageNameAndMsixMinVersionFromAppManifestAsync(package).ConfigureAwait(false);
if (string.IsNullOrEmpty(packageName) || string.IsNullOrEmpty(msixVersion))
{
Console.WriteLine("No Windows App Runtime version found in Msix/AppxManifest.xml");
return;
}
if (CheckRuntimeInstalled(packageName, msixVersion))
{
return;
}
string sdkVersion = await ExtractSDKVersionFromDepsJsonAsync(package).ConfigureAwait(false);
if (string.IsNullOrEmpty(sdkVersion))
{
Console.WriteLine("No Windows App SDK version found in Msix/Snap.Hutao.deps.json");
return;
}
Console.WriteLine("Start downloading SDK installer...");
await DownloadWindowsAppRuntimeInstallAndInstallAsync(sdkVersion).ConfigureAwait(false);
}
private static async Task<string> ExtractSDKVersionFromDepsJsonAsync(ZipArchive package)
{
ZipArchiveEntry? depsJson = package.GetEntry("Snap.Hutao.deps.json");
ArgumentNullException.ThrowIfNull(depsJson);
using (StreamReader reader = new(depsJson.Open()))
{
while (await reader.ReadLineAsync().ConfigureAwait(false) is { } line)
{
if (WindowsAppSDKVersion().Match(line) is { Success: true } match)
{
string sdkVersion = match.Groups[1].Value;
Console.WriteLine($"Using Windows App SDK version: {sdkVersion}");
return sdkVersion;
}
}
}
return string.Empty;
}
private static bool CheckRuntimeInstalled(string packageName, string msixVersion)
{
Version msixMinVersion = new(msixVersion);
foreach (Windows.ApplicationModel.Package installed in new PackageManager().FindPackages())
{
if (installed.Id.Name == packageName && installed.Id.Version.ToVersion() >= msixMinVersion)
{
return true;
}
}
return false;
}
private static async Task DownloadWindowsAppRuntimeInstallAndInstallAsync(string version)
{
string sdkInstallerPath = Path.Combine(Path.GetTempPath(), "windowsappruntimeinstall-x64.exe");
try
{
using (HttpClient httpClient = new())
{
HttpShardCopyWorkerOptions<DownloadStatus> options = new()
{
HttpClient = httpClient,
SourceUrl = string.Format(SDKInstallerDownloadFormat, MajorMinorVersion().Match(version).Value, version),
DestinationFilePath = sdkInstallerPath,
StatusFactory = (bytesRead, totalBytes) => new DownloadStatus(bytesRead, totalBytes),
};
using (HttpShardCopyWorker<DownloadStatus> worker = await HttpShardCopyWorker<DownloadStatus>.CreateAsync(options).ConfigureAwait(false))
{
Progress<DownloadStatus> progress = new(ConsoleWriteProgress);
await worker.CopyAsync(progress).ConfigureAwait(false);
}
}
Console.WriteLine("Start installing SDK...");
Process installerProcess = new()
{
StartInfo = new()
{
FileName = sdkInstallerPath,
RedirectStandardOutput = true,
RedirectStandardError = true,
},
};
using (installerProcess)
{
installerProcess.OutputDataReceived += (sender, e) => Console.WriteLine(e.Data);
installerProcess.ErrorDataReceived += (sender, e) => Console.WriteLine(e.Data);
installerProcess.Start();
Console.WriteLine("-----> WindowsAppRuntimeInstall Output begin -----");
installerProcess.BeginOutputReadLine();
installerProcess.BeginErrorReadLine();
await installerProcess.WaitForExitAsync().ConfigureAwait(false);
Console.WriteLine("<----- WindowsAppRuntimeInstall Output end -------");
}
}
finally
{
if (File.Exists(sdkInstallerPath))
{
File.Delete(sdkInstallerPath);
}
}
static void ConsoleWriteProgress(DownloadStatus status)
{
Console.Write($"\r{status.ProgressDescription}");
}
}
private static async Task<(string PackageName, string MsixVersion)> ExtractRuntimePackageNameAndMsixMinVersionFromAppManifestAsync(ZipArchive package)
{
ZipArchiveEntry? appxManifest = package.GetEntry("AppxManifest.xml");
ArgumentNullException.ThrowIfNull(appxManifest);
using (StreamReader reader = new(appxManifest.Open()))
{
while (await reader.ReadLineAsync().ConfigureAwait(false) is { } line)
{
if (WindowsAppRuntimeMsixMinVersion().Match(line) is { Success: true } match)
{
string packageName = match.Groups[1].Value;
string msixVersion = match.Groups[2].Value;
Console.WriteLine($"Using {packageName} version: {msixVersion}");
return (packageName, msixVersion);
}
}
}
return (string.Empty, string.Empty);
}
[GeneratedRegex("<PackageDependency Name=\"(Microsoft\\.WindowsAppRuntime.+?)\" MinVersion=\"(.+?)\"")]
private static partial Regex WindowsAppRuntimeMsixMinVersion();
[GeneratedRegex("\"Microsoft\\.WindowsAppSDK\": \"(.+?)\",")]
private static partial Regex WindowsAppSDKVersion();
[GeneratedRegex(@"\d+\.\d+")]
private static partial Regex MajorMinorVersion();
}