diff --git a/src/Snap.Hutao.Deployment.Runtime/Snap.Hutao.Deployment.Runtime.nuspec b/src/Snap.Hutao.Deployment.Runtime/Snap.Hutao.Deployment.Runtime.nuspec index fd16f8d..ca42298 100644 --- a/src/Snap.Hutao.Deployment.Runtime/Snap.Hutao.Deployment.Runtime.nuspec +++ b/src/Snap.Hutao.Deployment.Runtime/Snap.Hutao.Deployment.Runtime.nuspec @@ -2,7 +2,7 @@ Snap.Hutao.Deployment.Runtime - 1.5.0 + 1.6.0 DGP Studio true false diff --git a/src/Snap.Hutao.Deployment.Runtime/Snap.Hutao.Deployment.exe b/src/Snap.Hutao.Deployment.Runtime/Snap.Hutao.Deployment.exe index dd1256e..182a756 100644 Binary files a/src/Snap.Hutao.Deployment.Runtime/Snap.Hutao.Deployment.exe and b/src/Snap.Hutao.Deployment.Runtime/Snap.Hutao.Deployment.exe differ diff --git a/src/Snap.Hutao.Deployment/Certificate.cs b/src/Snap.Hutao.Deployment/Certificate.cs new file mode 100644 index 0000000..c7bc18c --- /dev/null +++ b/src/Snap.Hutao.Deployment/Certificate.cs @@ -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)); + } + } + } +} \ No newline at end of file diff --git a/src/Snap.Hutao.Deployment/PackageDownloadStatus.cs b/src/Snap.Hutao.Deployment/DownloadStatus.cs similarity index 88% rename from src/Snap.Hutao.Deployment/PackageDownloadStatus.cs rename to src/Snap.Hutao.Deployment/DownloadStatus.cs index 215c470..2a337a4 100644 --- a/src/Snap.Hutao.Deployment/PackageDownloadStatus.cs +++ b/src/Snap.Hutao.Deployment/DownloadStatus.cs @@ -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; } diff --git a/src/Snap.Hutao.Deployment/Invocation.cs b/src/Snap.Hutao.Deployment/Invocation.cs index fbd3ceb..9d7bb83 100644 --- a/src/Snap.Hutao.Deployment/Invocation.cs +++ b/src/Snap.Hutao.Deployment/Invocation.cs @@ -2,7 +2,6 @@ using System.CommandLine.Invocation; using System.Diagnostics; using System.IO; -using System.Net.Http; using System.Threading.Tasks; using Windows.Management.Deployment; @@ -31,16 +30,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 +55,6 @@ internal static class Invocation { ForceAppShutdown = true, RetainFilesOnFailure = true, - StageInPlace = true, }; Console.WriteLine("Start deploying..."); @@ -57,7 +62,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,7 +76,6 @@ internal static class Invocation foreach (Windows.ApplicationModel.Package package in packageManager.FindPackages()) { - if (package is { DisplayName: "Snap Hutao", PublisherDisplayName: "DGP Studio" }) { name = package.Id.FamilyName; @@ -94,7 +101,7 @@ internal static class Invocation Exit in 10 seconds... """); - await Task.Delay(10000); + await Task.Delay(10000).ConfigureAwait(false); } } catch (Exception ex) @@ -106,32 +113,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 options = new() - { - HttpClient = httpClient, - SourceUrl = "https://api.snapgenshin.com/patch/hutao/download", - DestinationFilePath = packagePath, - StatusFactory = (bytesRead, totalBytes) => new PackageDownloadStatus(bytesRead, totalBytes), - }; - - using (HttpShardCopyWorker worker = await HttpShardCopyWorker.CreateAsync(options).ConfigureAwait(false)) - { - Progress 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); } } } \ No newline at end of file diff --git a/src/Snap.Hutao.Deployment/PackageDownload.cs b/src/Snap.Hutao.Deployment/PackageDownload.cs new file mode 100644 index 0000000..7a43ba3 --- /dev/null +++ b/src/Snap.Hutao.Deployment/PackageDownload.cs @@ -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 options = new() + { + HttpClient = httpClient, + SourceUrl = "https://api.snapgenshin.com/patch/hutao/download", + DestinationFilePath = packagePath, + StatusFactory = (bytesRead, totalBytes) => new DownloadStatus(bytesRead, totalBytes), + }; + + using (HttpShardCopyWorker worker = await HttpShardCopyWorker.CreateAsync(options).ConfigureAwait(false)) + { + Progress progress = new(ConsoleWriteProgress); + await worker.CopyAsync(progress).ConfigureAwait(false); + } + } + + static void ConsoleWriteProgress(DownloadStatus status) + { + Console.Write($"\r{status.ProgressDescription}"); + } + } +} \ No newline at end of file diff --git a/src/Snap.Hutao.Deployment/PackageVersionExtension.cs b/src/Snap.Hutao.Deployment/PackageVersionExtension.cs new file mode 100644 index 0000000..8e51bbc --- /dev/null +++ b/src/Snap.Hutao.Deployment/PackageVersionExtension.cs @@ -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); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao.Deployment/WindowsAppSDKDependency.cs b/src/Snap.Hutao.Deployment/WindowsAppSDKDependency.cs new file mode 100644 index 0000000..a3cb88c --- /dev/null +++ b/src/Snap.Hutao.Deployment/WindowsAppSDKDependency.cs @@ -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 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 options = new() + { + HttpClient = httpClient, + SourceUrl = string.Format(SDKInstallerDownloadFormat, MajorMinorVersion().Match(version).Value, version), + DestinationFilePath = sdkInstallerPath, + StatusFactory = (bytesRead, totalBytes) => new DownloadStatus(bytesRead, totalBytes), + }; + + using (HttpShardCopyWorker worker = await HttpShardCopyWorker.CreateAsync(options).ConfigureAwait(false)) + { + Progress 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("