From 01b7e58b3e3d5c29de4f9f03a93d32795439bcd4 Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Fri, 27 Jan 2023 16:51:43 +0800 Subject: [PATCH] fix convert cache --- .../Snap.Hutao/Package.appxmanifest | 2 +- .../Snap.Hutao/Service/Game/GameConstants.cs | 35 ++++ .../Snap.Hutao/Service/Game/GameService.cs | 55 ++++--- .../Snap.Hutao/Service/Game/IGameService.cs | 2 +- .../Service/Game/Package/ItemOperationInfo.cs | 4 +- .../Service/Game/Package/PackageConverter.cs | 151 +++++++++++++----- src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj | 2 +- .../LaunchGamePackageConvertDialog.xaml | 10 +- .../LaunchGamePackageConvertDialog.xaml.cs | 10 +- .../Snap.Hutao/View/Page/LaunchGamePage.xaml | 23 ++- .../ViewModel/LaunchGameViewModel.cs | 2 +- 11 files changed, 209 insertions(+), 87 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Game/GameConstants.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest index fb83f62e..90aa2aab 100644 --- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest +++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest @@ -12,7 +12,7 @@ + Version="1.4.0.0" /> 胡桃 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameConstants.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameConstants.cs new file mode 100644 index 00000000..606ecb13 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameConstants.cs @@ -0,0 +1,35 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.Game; + +/// +/// 游戏常量 +/// +internal static class GameConstants +{ + /// + /// 设置文件 + /// + public const string ConfigFileName = "config.ini"; + + /// + /// 国服文件名 + /// + public const string YuanShenFileName = "YuanShen.exe"; + + /// + /// 外服文件名 + /// + public const string GenshinImpactFileName = "GenshinImpact.exe"; + + /// + /// 国服数据文件夹 + /// + public const string YuanShenData = "YuanShen_Data"; + + /// + /// 国际服数据文件夹 + /// + public const string GenshinImpactData = "GenshinImpact_Data"; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs index 0dc89243..9f80d8cc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs @@ -20,6 +20,7 @@ using Snap.Hutao.Web.Response; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; +using static Snap.Hutao.Service.Game.GameConstants; namespace Snap.Hutao.Service.Game; @@ -31,7 +32,6 @@ namespace Snap.Hutao.Service.Game; internal class GameService : IGameService { private const string GamePathKey = $"{nameof(GameService)}.Cache.{SettingEntry.GamePath}"; - private const string ConfigFile = "config.ini"; private readonly IServiceScopeFactory scopeFactory; private readonly IMemoryCache memoryCache; @@ -148,7 +148,7 @@ internal class GameService : IGameService public MultiChannel GetMultiChannel() { string gamePath = GetGamePathSkipLocator(); - string configPath = Path.Combine(Path.GetDirectoryName(gamePath) ?? string.Empty, ConfigFile); + string configPath = Path.Combine(Path.GetDirectoryName(gamePath) ?? string.Empty, ConfigFileName); if (!File.Exists(configPath)) { @@ -169,7 +169,7 @@ internal class GameService : IGameService public bool SetMultiChannel(LaunchScheme scheme) { string gamePath = GetGamePathSkipLocator(); - string configPath = Path.Combine(Path.GetDirectoryName(gamePath)!, ConfigFile); + string configPath = Path.Combine(Path.GetDirectoryName(gamePath)!, ConfigFileName); List elements; try @@ -226,28 +226,39 @@ internal class GameService : IGameService } /// - public async Task ReplaceGameResourceAsync(LaunchScheme launchScheme, IProgress progress) + public async Task EnsureGameResourceAsync(LaunchScheme launchScheme, IProgress progress) { string gamePath = GetGamePathSkipLocator(); string gameFolder = Path.GetDirectoryName(gamePath)!; string gameFileName = Path.GetFileName(gamePath); - if (launchScheme.IsOversea && gameFileName == "GenshinImpact.exe") + progress.Report(new("查询游戏资源信息")); + Response response = await Ioc.Default + .GetRequiredService() + .GetResourceAsync(launchScheme) + .ConfigureAwait(false); + + if (response.IsOk()) { - // Already that scheme, no need to replace files - return true; - } - else if (!launchScheme.IsOversea && gameFileName == "YuanShen.exe") - { - // Already that scheme, no need to replace files + GameResource resource = response.Data; + + if (!LaunchSchemeMatchesExecutable(launchScheme, gameFileName)) + { + await packageConverter.EnsureGameResourceAsync(launchScheme, resource, gameFolder, progress).ConfigureAwait(false); + + // We need to change the gamePath if we switched. + OverwriteGamePath(Path.Combine(gameFolder, launchScheme.IsOversea ? GenshinImpactFileName : YuanShenFileName)); + } + + if (!launchScheme.IsOversea) + { + await packageConverter.EnsureDeprecatedFilesAndSdkAsync(resource, gameFolder).ConfigureAwait(false); + } + return true; } - // TODO: we still need to handle the Bilibili scheme. - await packageConverter.ReplaceGameResourceAsync(launchScheme, gameFolder, progress).ConfigureAwait(false); - // We need to change the gamePath if we switch. - - return true; + return false; } /// @@ -258,19 +269,19 @@ internal class GameService : IGameService return true; } - return Process.GetProcessesByName("YuanShen.exe").Any() - || Process.GetProcessesByName("GenshinImpact.exe").Any(); + return Process.GetProcessesByName(YuanShenFileName).Any() + || Process.GetProcessesByName(GenshinImpactFileName).Any(); } /// public async Task> GetGameAccountCollectionAsync() { - await ThreadHelper.SwitchToMainThreadAsync(); if (gameAccounts == null) { using (IServiceScope scope = scopeFactory.CreateScope()) { AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + await ThreadHelper.SwitchToMainThreadAsync(); gameAccounts = appDbContext.GameAccounts.AsNoTracking().ToObservableCollection(); } } @@ -431,4 +442,10 @@ internal class GameService : IGameService await scope.ServiceProvider.GetRequiredService().GameAccounts.RemoveAndSaveAsync(gameAccount).ConfigureAwait(false); } } + + private static bool LaunchSchemeMatchesExecutable(LaunchScheme launchScheme, string gameFileName) + { + return (launchScheme.IsOversea && gameFileName == GenshinImpactFileName) + || (!launchScheme.IsOversea && gameFileName == YuanShenFileName); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs index 13c01785..0fbb0df1 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs @@ -90,7 +90,7 @@ internal interface IGameService /// 目标启动方案 /// 进度 /// 是否替换成功 - Task ReplaceGameResourceAsync(LaunchScheme launchScheme, IProgress progress); + Task EnsureGameResourceAsync(LaunchScheme launchScheme, IProgress progress); /// /// 修改注册表中的账号信息 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/ItemOperationInfo.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/ItemOperationInfo.cs index 63305b2c..d353c820 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/ItemOperationInfo.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/ItemOperationInfo.cs @@ -21,7 +21,7 @@ internal class ItemOperationInfo { Type = type; Target = target.RemoteName; - Cache = cache.RemoteName; + MoveTo = cache.RemoteName; Md5 = target.Md5; TotalBytes = target.FileSize; } @@ -39,7 +39,7 @@ internal class ItemOperationInfo /// /// 移动至中时的名称 /// - public string Cache { get; set; } + public string MoveTo { get; set; } /// /// 文件的目标Md5 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs index 9940ed2c..5e651223 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs @@ -3,11 +3,14 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Core.IO; +using Snap.Hutao.Extension; using Snap.Hutao.Model.Binding.LaunchGame; using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher; using Snap.Hutao.Web.Response; using System.IO; +using System.IO.Compression; using System.Net.Http; +using static Snap.Hutao.Service.Game.GameConstants; namespace Snap.Hutao.Service.Game.Package; @@ -17,9 +20,6 @@ namespace Snap.Hutao.Service.Game.Package; [HttpClient(HttpClientConfigration.Default)] internal class PackageConverter { - private const string GenshinImpactData = "GenshinImpact_Data"; - private const string YuanShenData = "YuanShen_Data"; - private readonly ResourceClient resourceClient; private readonly JsonSerializerOptions options; private readonly HttpClient httpClient; @@ -38,47 +38,102 @@ internal class PackageConverter } /// - /// 异步替换游戏资源 + /// 异步检查替换游戏资源 /// 调用前需要确认本地文件与服务器上的不同 /// /// 目标启动方案 + /// 游戏资源 /// 游戏目录 /// 进度 - /// 任务 - public async Task ReplaceGameResourceAsync(LaunchScheme targetScheme, string gameFolder, IProgress progress) + /// 替换结果与资源 + public async Task EnsureGameResourceAsync(LaunchScheme targetScheme, GameResource gameResouce, string gameFolder, IProgress progress) { await ThreadHelper.SwitchToBackgroundAsync(); - progress.Report(new("查询游戏资源信息")); - Response response = await resourceClient.GetResourceAsync(targetScheme).ConfigureAwait(false); + string scatteredFilesUrl = gameResouce.Game.Latest.DecompressedPath; + Uri pkgVersionUri = new($"{scatteredFilesUrl}/pkg_version"); + ConvertDirection direction = targetScheme.IsOversea ? ConvertDirection.ChineseToOversea : ConvertDirection.OverseaToChinese; - if (response.IsOk()) + progress.Report(new("下载包版本信息")); + Dictionary remoteItems; + using (Stream remoteSteam = await httpClient.GetStreamAsync(pkgVersionUri).ConfigureAwait(false)) { - GameResource remoteGameResouce = response.Data; - - string scatteredFilesUrl = remoteGameResouce.Game.Latest.DecompressedPath; - Uri pkgVersionUri = new($"{scatteredFilesUrl}/pkg_version"); - ConvertDirection direction = targetScheme.IsOversea ? ConvertDirection.ChineseToOversea : ConvertDirection.OverseaToChinese; - - progress.Report(new("下载包版本信息")); - Dictionary remoteItems; - using (Stream remoteSteam = await httpClient.GetStreamAsync(pkgVersionUri).ConfigureAwait(false)) - { - remoteItems = await GetVersionItemsAsync(remoteSteam).ConfigureAwait(false); - } - - Dictionary localItems; - using (FileStream localSteam = File.OpenRead(Path.Combine(gameFolder, "pkg_version"))) - { - localItems = await GetVersionItemsAsync(localSteam, direction, ConvertRemoteName).ConfigureAwait(false); - } - - IEnumerable diffOperations = GetItemOperationInfos(remoteItems, localItems); - var a = diffOperations.ToList(); - await ReplaceGameResourceCoreAsync(diffOperations, gameFolder, scatteredFilesUrl, direction, progress).ConfigureAwait(false); - return true; + remoteItems = await GetVersionItemsAsync(remoteSteam).ConfigureAwait(false); } - return false; + Dictionary localItems; + using (FileStream localSteam = File.OpenRead(Path.Combine(gameFolder, "pkg_version"))) + { + localItems = await GetVersionItemsAsync(localSteam, direction, ConvertRemoteName).ConfigureAwait(false); + } + + IEnumerable diffOperations = GetItemOperationInfos(remoteItems, localItems); + await ReplaceGameResourceAsync(diffOperations, gameFolder, scatteredFilesUrl, direction, progress).ConfigureAwait(false); + } + + /// + /// 检查过时文件与Sdk + /// + /// 游戏资源 + /// 游戏文件夹 + /// 任务 + public async Task EnsureDeprecatedFilesAndSdkAsync(GameResource resource, string gameFolder) + { + if (resource.DeprecatedFiles != null) + { + foreach (NameMd5 file in resource.DeprecatedFiles) + { + string filePath = Path.Combine(gameFolder, file.Name); + if (File.Exists(filePath)) + { + File.Move(filePath, $"{filePath}.backup"); + } + } + } + + string sdkDllBackup = Path.Combine(gameFolder, YuanShenData, "Plugins\\PCGameSDK.dll.backup"); + string sdkDll = Path.Combine(gameFolder, YuanShenData, "Plugins\\PCGameSDK.dll"); + string sdkVersionBackup = Path.Combine(gameFolder, YuanShenData, "sdk_pkg_version.backup"); + string sdkVersion = Path.Combine(gameFolder, YuanShenData, "sdk_pkg_version"); + + // Only bilibili's sdk is not null + if (resource.Sdk != null) + { + if (File.Exists(sdkDllBackup) && File.Exists(sdkVersionBackup)) + { + File.Move(sdkDllBackup, sdkDll, false); + File.Move(sdkVersionBackup, sdkVersion, false); + } + else + { + using (Stream sdkWebStream = await httpClient.GetStreamAsync(resource.Sdk.Path).ConfigureAwait(false)) + { + using (ZipArchive zip = new(sdkWebStream)) + { + foreach (ZipArchiveEntry entry in zip.Entries) + { + if (entry.CompressedLength != 0) + { + string targetPath = Path.Combine(gameFolder, entry.FullName); + Directory.CreateDirectory(Path.GetDirectoryName(targetPath)!); + entry.ExtractToFile(targetPath, true); + } + } + } + } + } + } + else + { + if (File.Exists(sdkDll)) + { + File.Move(sdkDll, sdkDllBackup, true); + } + + if (File.Exists(sdkVersion)) + { + File.Move(sdkVersion, sdkVersionBackup, true); + } + } } private static string ConvertRemoteName(string remoteName, ConvertDirection direction) @@ -143,14 +198,13 @@ internal class PackageConverter } } - private static void MoveToCache(string cacheFolder, string cacheName, string targetFullPath) + private static void MoveToCache(string cacheFilePath, string targetFullPath) { - string cacheFilePath = Path.Combine(cacheFolder, cacheName); Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)!); File.Move(targetFullPath, cacheFilePath, true); } - private async Task ReplaceGameResourceCoreAsync(IEnumerable operations, string gameFolder, string scatteredFilesUrl, ConvertDirection direction, IProgress progress) + private async Task ReplaceGameResourceAsync(IEnumerable operations, string gameFolder, string scatteredFilesUrl, ConvertDirection direction, IProgress progress) { // 重命名 _Data 目录 RenameDataFolder(gameFolder, direction); @@ -164,7 +218,8 @@ internal class PackageConverter progress.Report(new($"{info.Target}")); string targetFilePath = Path.Combine(gameFolder, info.Target); - string cacheFilePath = Path.Combine(cacheFolder, info.Cache); + string cacheFilePath = Path.Combine(cacheFolder, info.Target); + string moveToFilePath = Path.Combine(cacheFolder, info.MoveTo); switch (info.Type) { @@ -173,14 +228,14 @@ internal class PackageConverter break; case ItemOperationType.Replace: { - MoveToCache(cacheFolder, info.Cache, targetFilePath); + MoveToCache(moveToFilePath, targetFilePath); await ReplaceFromCacheOrWebAsync(cacheFilePath, targetFilePath, scatteredFilesUrl, info).ConfigureAwait(false); break; } case ItemOperationType.Remove: - MoveToCache(cacheFolder, info.Cache, targetFilePath); + MoveToCache(moveToFilePath, targetFilePath); break; default: @@ -223,14 +278,22 @@ internal class PackageConverter private async Task ReplacePackageVersionsAsync(string scatteredFilesUrl, string gameFolder) { - foreach (string audioPkgVersionFilePath in Directory.EnumerateFiles(gameFolder, "*pkg_version")) + foreach (string versionFilePath in Directory.EnumerateFiles(gameFolder, "*pkg_version")) { - string audioPkgVersionFileName = Path.GetFileName(audioPkgVersionFilePath); - using (FileStream audioPkgVersionFileStream = File.Create(audioPkgVersionFilePath)) + string versionFileName = Path.GetFileName(versionFilePath); + + if (versionFileName == "sdk_pkg_version") { - using (Stream webStream = await httpClient.GetStreamAsync($"{scatteredFilesUrl}/{audioPkgVersionFileName}").ConfigureAwait(false)) + // Skiping the sdk_pkg_version file, + // it can't be claimed from remote. + continue; + } + + using (FileStream versionFileStream = File.Create(versionFilePath)) + { + using (Stream webStream = await httpClient.GetStreamAsync($"{scatteredFilesUrl}/{versionFileName}").ConfigureAwait(false)) { - await webStream.CopyToAsync(audioPkgVersionFileStream).ConfigureAwait(false); + await webStream.CopyToAsync(versionFileStream).ConfigureAwait(false); } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index f6582f59..0f6f2e3e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -22,7 +22,7 @@ True F8C2255969BEA4A681CED102771BF807856AEC02 SHA256 - True + False True True Never diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml b/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml index 800d16ae..c350ad7d 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml @@ -1,4 +1,4 @@ - + - + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml.cs index e3d8bc20..a62a4281 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml.cs @@ -1,4 +1,4 @@ -// Copyright (c) DGP Studio. All rights reserved. +// Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. using Microsoft.UI.Xaml; @@ -8,14 +8,14 @@ using Snap.Hutao.Control; namespace Snap.Hutao.View.Dialog; /// -/// ϷͻתԻ +/// 启动游戏客户端转换对话框 /// public sealed partial class LaunchGamePackageConvertDialog : ContentDialog { - private static readonly DependencyProperty DescriptionProperty = Property.Depend(nameof(Description), "Ժ"); + private static readonly DependencyProperty DescriptionProperty = Property.Depend(nameof(Description), "请稍候"); /// - /// һµϷͻתԻ + /// 构造一个新的启动游戏客户端转换对话框 /// public LaunchGamePackageConvertDialog() { @@ -25,7 +25,7 @@ public sealed partial class LaunchGamePackageConvertDialog : ContentDialog } /// - /// + /// 描述 /// public string Description { diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml index 0c016f7f..38d50463 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml @@ -52,11 +52,23 @@ IsOpen="True" Message="所有选项仅会在启动游戏成功后保存" Severity="Informational"/> + + + Icon="" + IsEnabled="{Binding IsElevated}"> - progress = new(s => dialog.Description = s.Description); - await gameService.ReplaceGameResourceAsync(SelectedScheme, progress).ConfigureAwait(false); + await gameService.EnsureGameResourceAsync(SelectedScheme, progress).ConfigureAwait(false); } }