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);
}
}