From bb01f3a3cb74a4a06a44d59cf808174be196f9e3 Mon Sep 17 00:00:00 2001
From: DismissedLight <1686188646@qq.com>
Date: Mon, 30 Jan 2023 10:43:05 +0800
Subject: [PATCH] fix package convert issue
---
.../Core/Database/DbSetExtension.cs | 4 +-
.../Core/Database/QueryableExtension.cs | 26 ++++
src/Snap.Hutao/Snap.Hutao/Core/IO/Digest.cs | 42 ++++++
.../Snap.Hutao/Core/IO/FileDigest.cs | 31 -----
.../Snap.Hutao/Core/IO/FileOperation.cs | 41 ++++++
src/Snap.Hutao/Snap.Hutao/Core/IO/TempFile.cs | 8 +-
.../Snap.Hutao/Core/LifeCycle/Activation.cs | 6 +
.../Snap.Hutao/Package.appxmanifest | 4 +-
.../Service/Achievement/AchievementService.cs | 3 +-
.../Factory/SummaryAvatarFactory.cs | 2 +-
.../Service/Cultivation/CultivationService.cs | 5 +-
.../Service/DailyNote/DailyNoteService.cs | 6 +-
.../Service/DailyNote/IDailyNoteService.cs | 3 +-
.../Service/GachaLog/GachaLogService.cs | 9 +-
.../GachaLogUrlWebCacheProvider.cs | 4 +-
.../Game/GameFileOperationException.cs | 19 +--
.../Snap.Hutao/Service/Game/GameService.cs | 40 ++++--
.../Game/Package/PackageConvertException.cs | 25 ++++
.../Service/Game/Package/PackageConverter.cs | 125 +++++++++++++-----
.../Game/Package/PackageReplaceStatus.cs | 34 +++++
.../Game/Unlocker/GameFpsUnlockerException.cs | 28 ++++
.../Service/Metadata/MetadataService.cs | 2 +-
.../Snap.Hutao/Service/User/UserService.cs | 9 +-
.../Dialog/GachaLogRefreshProgressDialog.xaml | 1 -
.../GachaLogRefreshProgressDialog.xaml.cs | 6 +-
.../LaunchGamePackageConvertDialog.xaml | 9 +-
.../LaunchGamePackageConvertDialog.xaml.cs | 9 +-
.../Snap.Hutao/View/Page/CultivationPage.xaml | 84 ++++++------
.../Snap.Hutao/View/Page/LaunchGamePage.xaml | 4 +-
.../View/Page/LoginMihoyoUserPage.xaml.cs | 7 +
.../ViewModel/AchievementViewModel.cs | 19 ++-
.../ViewModel/DailyNoteViewModel.cs | 6 +-
.../ViewModel/LaunchGameViewModel.cs | 19 ++-
.../Snap.Hutao/ViewModel/WelcomeViewModel.cs | 6 +-
.../ViewModel/WikiAvatarViewModel.cs | 9 +-
.../ViewModel/WikiWeaponViewModel.cs | 9 +-
.../Web/Bridge/MiHoYoJSInterface.cs | 12 +-
.../Snap.Hutao/Web/Enka/Model/TypeValue.cs | 13 ++
.../Snap.Hutao/Web/Geetest/GeetestClient.cs | 11 +-
39 files changed, 519 insertions(+), 181 deletions(-)
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Database/QueryableExtension.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/IO/Digest.cs
delete mode 100644 src/Snap.Hutao/Snap.Hutao/Core/IO/FileDigest.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/IO/FileOperation.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConvertException.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlockerException.cs
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs
index 707c50dc..0ad3729b 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs
@@ -7,7 +7,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Snap.Hutao.Core.Database;
///
-/// 数据库集合上下文
+/// 数据库集合扩展
///
public static class DbSetExtension
{
@@ -134,4 +134,4 @@ public static class DbSetExtension
dbSet.Update(entity);
return await dbSet.Context().SaveChangesAsync().ConfigureAwait(false);
}
-}
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/QueryableExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/QueryableExtension.cs
new file mode 100644
index 00000000..88826a69
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/QueryableExtension.cs
@@ -0,0 +1,26 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.EntityFrameworkCore;
+using System.Linq.Expressions;
+
+namespace Snap.Hutao.Core.Database;
+
+///
+/// 可查询扩展
+///
+public static class QueryableExtension
+{
+ ///
+ /// source.Where(predicate).ExecuteDeleteAsync(token)
+ ///
+ /// 源类型
+ /// 源
+ /// 条件
+ /// 取消令牌
+ /// SQL返回个数
+ public static Task ExecuteDeleteWhereAsync(this IQueryable source, Expression> predicate, CancellationToken token = default)
+ {
+ return source.Where(predicate).ExecuteDeleteAsync(token);
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/Digest.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/Digest.cs
new file mode 100644
index 00000000..28bbd29c
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/Digest.cs
@@ -0,0 +1,42 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.IO;
+using System.Security.Cryptography;
+
+namespace Snap.Hutao.Core.IO;
+
+///
+/// 摘要
+///
+internal static class Digest
+{
+ ///
+ /// 异步获取文件 Md5 摘要
+ ///
+ /// 文件路径
+ /// 取消令牌
+ /// 文件 Md5 摘要
+ public static async Task GetFileMd5Async(string filePath, CancellationToken token = default)
+ {
+ using (FileStream stream = File.OpenRead(filePath))
+ {
+ return await GetStreamMd5Async(stream, token).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// 获取流的 Md5 摘要
+ ///
+ /// 流
+ /// 取消令牌
+ /// 流 Md5 摘要
+ public static async Task GetStreamMd5Async(Stream stream, CancellationToken token = default)
+ {
+ using (MD5 md5 = MD5.Create())
+ {
+ byte[] bytes = await md5.ComputeHashAsync(stream, token).ConfigureAwait(false);
+ return System.Convert.ToHexString(bytes);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/FileDigest.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/FileDigest.cs
deleted file mode 100644
index a59a9407..00000000
--- a/src/Snap.Hutao/Snap.Hutao/Core/IO/FileDigest.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using System.IO;
-using System.Security.Cryptography;
-
-namespace Snap.Hutao.Core.IO;
-
-///
-/// 文件摘要
-///
-internal static class FileDigest
-{
- ///
- /// 异步获取文件 Md5 摘要
- ///
- /// 文件路径
- /// 取消令牌
- /// 文件 Md5 摘要
- public static async Task GetMd5Async(string filePath, CancellationToken token)
- {
- using (FileStream stream = File.OpenRead(filePath))
- {
- using (MD5 md5 = MD5.Create())
- {
- byte[] bytes = await md5.ComputeHashAsync(stream, token).ConfigureAwait(false);
- return System.Convert.ToHexString(bytes);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/FileOperation.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/FileOperation.cs
new file mode 100644
index 00000000..bc8c261f
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/FileOperation.cs
@@ -0,0 +1,41 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.IO;
+
+namespace Snap.Hutao.Core.IO;
+
+///
+/// 文件操作
+///
+internal static class FileOperation
+{
+ ///
+ /// 将指定文件移动到新位置,提供指定新文件名和覆盖目标文件(如果它已存在)的选项。
+ ///
+ /// 要移动的文件的名称。 可以包括相对或绝对路径。
+ /// 文件的新路径和名称。
+ /// 如果要覆盖目标文件
+ /// 是否发生了移动操作
+ public static bool Move(string sourceFileName, string destFileName, bool overwrite)
+ {
+ if (File.Exists(sourceFileName))
+ {
+ if (overwrite)
+ {
+ File.Move(sourceFileName, destFileName, overwrite);
+ return true;
+ }
+ else
+ {
+ if (!File.Exists(destFileName))
+ {
+ File.Move(sourceFileName, destFileName, overwrite);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/TempFile.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/TempFile.cs
index 5e3f2fb9..0d32b5e3 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/IO/TempFile.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/TempFile.cs
@@ -53,6 +53,12 @@ internal sealed class TempFile : IDisposable
///
public void Dispose()
{
- File.Delete(Path);
+ try
+ {
+ File.Delete(Path);
+ }
+ catch (IOException)
+ {
+ }
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs
index 172de3c2..64116454 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs
@@ -10,7 +10,9 @@ using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.DailyNote;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Navigation;
+#if RELEASE
using System.Security.Principal;
+#endif
namespace Snap.Hutao.Core.LifeCycle;
@@ -37,11 +39,15 @@ internal static class Activation
/// 是否提升了权限
public static bool GetElevated()
{
+#if RELEASE
using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
{
WindowsPrincipal principal = new(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
+#else
+ return true;
+#endif
}
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
index 90aa2aab..babe8694 100644
--- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
+++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
@@ -11,7 +11,7 @@
@@ -24,7 +24,7 @@
-
+
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs
index 08653279..b4119679 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs
@@ -72,8 +72,7 @@ internal class AchievementService : IAchievementService
// Cascade deleted the achievements.
await appDbContext.AchievementArchives
- .Where(a => a.InnerId == archive.InnerId)
- .ExecuteDeleteAsync()
+ .ExecuteDeleteWhereAsync(a => a.InnerId == archive.InnerId)
.ConfigureAwait(false);
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs
index 31373e94..b6296ff0 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs
@@ -60,7 +60,7 @@ internal class SummaryAvatarFactory
FetterLevel = avatarInfo.FetterInfo?.ExpLevel ?? 0,
Properties = SummaryHelper.CreateAvatarProperties(avatarInfo.FightPropMap),
CritScore = $"{SummaryHelper.ScoreCrit(avatarInfo.FightPropMap):F2}",
- LevelNumber = int.Parse(avatarInfo.PropMap?[PlayerProperty.PROP_LEVEL].Value ?? string.Empty),
+ LevelNumber = avatarInfo.PropMap?[PlayerProperty.PROP_LEVEL].ValueInt32 ?? 0,
// processed webinfo part
Weapon = reliquaryAndWeapon.Weapon,
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs
index c70d1963..d85bf180 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs
@@ -105,7 +105,10 @@ internal class CultivationService : ICultivationService
await ThreadHelper.SwitchToBackgroundAsync();
using (IServiceScope scope = scopeFactory.CreateScope())
{
- await scope.ServiceProvider.GetRequiredService().CultivateProjects.RemoveAndSaveAsync(project).ConfigureAwait(false);
+ AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
+ await appDbContext.CultivateProjects
+ .ExecuteDeleteWhereAsync(p => p.InnerId == project.InnerId)
+ .ConfigureAwait(false);
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs
index 5b937e43..08877585 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs
@@ -157,14 +157,14 @@ internal class DailyNoteService : IDailyNoteService, IRecipient
- public void RemoveDailyNote(DailyNoteEntry entry)
+ public async Task RemoveDailyNoteAsync(DailyNoteEntry entry)
{
entries!.Remove(entry);
using (IServiceScope scope = scopeFactory.CreateScope())
{
- // DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s)
- scope.ServiceProvider.GetRequiredService().DailyNotes.RemoveAndSave(entry);
+ AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
+ await appDbContext.DailyNotes.ExecuteDeleteWhereAsync(d => d.InnerId == entry.InnerId).ConfigureAwait(false);
}
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/IDailyNoteService.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/IDailyNoteService.cs
index 0a9bea3a..bfa358f1 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/IDailyNoteService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/IDailyNoteService.cs
@@ -36,5 +36,6 @@ public interface IDailyNoteService
/// 移除指定的实时便笺
///
/// 指定的实时便笺
- void RemoveDailyNote(DailyNoteEntry entry);
+ /// 任务
+ Task RemoveDailyNoteAsync(DailyNoteEntry entry);
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs
index f83897b3..acd09fe9 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs
@@ -117,7 +117,14 @@ internal class GachaLogService : IGachaLogService
public async Task> GetArchiveCollectionAsync()
{
await ThreadHelper.SwitchToMainThreadAsync();
- return archiveCollection ??= appDbContext.GachaArchives.AsNoTracking().ToObservableCollection();
+ try
+ {
+ return archiveCollection ??= appDbContext.GachaArchives.AsNoTracking().ToObservableCollection();
+ }
+ catch (SqliteException ex)
+ {
+ throw new Core.ExceptionService.UserdataCorruptedException($"无法获取祈愿记录: {ex.Message}", ex);
+ }
}
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs
index f47a26c3..1ea05d84 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs
@@ -64,8 +64,8 @@ internal class GachaLogUrlWebCacheProvider : IGachaLogUrlProvider
using (MemoryStream memoryStream = new())
{
await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
- string? result = Match(memoryStream, !tempFile.Path.Contains("GenshinImpact_Data"));
- return new(!string.IsNullOrEmpty(result), result!);
+ string? result = Match(memoryStream, cacheFile.Contains(GameConstants.GenshinImpactData));
+ return new(!string.IsNullOrEmpty(result), result ?? "未找到可用的 Url");
}
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileOperationException.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileOperationException.cs
index f388e6bb..b0f152f2 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileOperationException.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileOperationException.cs
@@ -1,23 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Caching.Memory;
-using Microsoft.Extensions.DependencyInjection;
-using Snap.Hutao.Core;
-using Snap.Hutao.Core.Database;
-using Snap.Hutao.Core.IO.Ini;
-using Snap.Hutao.Extension;
-using Snap.Hutao.Model.Binding.LaunchGame;
-using Snap.Hutao.Model.Entity;
-using Snap.Hutao.Model.Entity.Database;
-using Snap.Hutao.Service.Game.Locator;
-using Snap.Hutao.Service.Game.Unlocker;
-using Snap.Hutao.View.Dialog;
-using System.Collections.ObjectModel;
-using System.Diagnostics;
-using System.IO;
-
namespace Snap.Hutao.Service.Game;
///
@@ -34,4 +17,4 @@ internal class GameFileOperationException : Exception
: base($"游戏文件操作失败: {message}", innerException)
{
}
-}
+}
\ 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 9f80d8cc..da7a3bbc 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs
@@ -179,13 +179,17 @@ internal class GameService : IGameService
elements = IniSerializer.Deserialize(readStream).ToList();
}
}
- catch (DirectoryNotFoundException dnfEx)
+ catch (FileNotFoundException ex)
{
- throw new GameFileOperationException($"找不到游戏配置文件 {configPath}", dnfEx);
+ throw new GameFileOperationException($"找不到游戏配置文件 {configPath}", ex);
}
- catch (UnauthorizedAccessException uaEx)
+ catch (DirectoryNotFoundException ex)
{
- throw new GameFileOperationException($"无法读取或保存配置文件,请以管理员模式重试。", uaEx);
+ throw new GameFileOperationException($"找不到游戏配置文件 {configPath}", ex);
+ }
+ catch (UnauthorizedAccessException ex)
+ {
+ throw new GameFileOperationException($"无法读取或保存配置文件,请以管理员模式重试。", ex);
}
bool changed = false;
@@ -244,10 +248,22 @@ internal class GameService : IGameService
if (!LaunchSchemeMatchesExecutable(launchScheme, gameFileName))
{
- await packageConverter.EnsureGameResourceAsync(launchScheme, resource, gameFolder, progress).ConfigureAwait(false);
+ bool replaced = 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 (replaced)
+ {
+ // We need to change the gamePath if we switched.
+ string exeName = launchScheme.IsOversea ? GenshinImpactFileName : YuanShenFileName;
+ OverwriteGamePath(Path.Combine(gameFolder, exeName));
+ }
+ else
+ {
+ // We can't start the game
+ // when we failed to convert game
+ return false;
+ }
}
if (!launchScheme.IsOversea)
@@ -364,7 +380,15 @@ internal class GameService : IGameService
string? registrySdk = GameAccountRegistryInterop.Get();
if (!string.IsNullOrEmpty(registrySdk))
{
- GameAccount? account = gameAccounts.SingleOrDefault(a => a.MihoyoSDK == registrySdk);
+ GameAccount? account;
+ try
+ {
+ account = gameAccounts.SingleOrDefault(a => a.MihoyoSDK == registrySdk);
+ }
+ catch (InvalidOperationException ex)
+ {
+ throw new Core.ExceptionService.UserdataCorruptedException("已存在多个匹配账号,请先删除重复的账号", ex);
+ }
if (account == null)
{
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConvertException.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConvertException.cs
new file mode 100644
index 00000000..747c7937
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConvertException.cs
@@ -0,0 +1,25 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
+using Snap.Hutao.Core.IO;
+using Snap.Hutao.Model.Binding.LaunchGame;
+using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
+using System.IO;
+using System.IO.Compression;
+using System.Net.Http;
+using static Snap.Hutao.Service.Game.GameConstants;
+
+namespace Snap.Hutao.Service.Game.Package;
+
+///
+/// 包转换异常
+///
+public class PackageConvertException : Exception
+{
+ ///
+ public PackageConvertException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+}
\ No newline at end of file
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 5e651223..eeb6c25e 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs
@@ -3,10 +3,8 @@
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;
@@ -20,7 +18,6 @@ namespace Snap.Hutao.Service.Game.Package;
[HttpClient(HttpClientConfigration.Default)]
internal class PackageConverter
{
- private readonly ResourceClient resourceClient;
private readonly JsonSerializerOptions options;
private readonly HttpClient httpClient;
@@ -30,9 +27,8 @@ internal class PackageConverter
/// 资源客户端
/// Json序列化选项
/// http客户端
- public PackageConverter(ResourceClient resourceClient, JsonSerializerOptions options, HttpClient httpClient)
+ public PackageConverter(JsonSerializerOptions options, HttpClient httpClient)
{
- this.resourceClient = resourceClient;
this.options = options;
this.httpClient = httpClient;
}
@@ -46,18 +42,25 @@ internal class PackageConverter
/// 游戏目录
/// 进度
/// 替换结果与资源
- public async Task EnsureGameResourceAsync(LaunchScheme targetScheme, GameResource gameResouce, string gameFolder, IProgress progress)
+ public async Task EnsureGameResourceAsync(LaunchScheme targetScheme, GameResource gameResouce, string gameFolder, IProgress progress)
{
await ThreadHelper.SwitchToBackgroundAsync();
string scatteredFilesUrl = gameResouce.Game.Latest.DecompressedPath;
Uri pkgVersionUri = new($"{scatteredFilesUrl}/pkg_version");
ConvertDirection direction = targetScheme.IsOversea ? ConvertDirection.ChineseToOversea : ConvertDirection.OverseaToChinese;
- progress.Report(new("下载包版本信息"));
+ progress.Report(new("获取 Package Version"));
Dictionary remoteItems;
- using (Stream remoteSteam = await httpClient.GetStreamAsync(pkgVersionUri).ConfigureAwait(false))
+ try
{
- remoteItems = await GetVersionItemsAsync(remoteSteam).ConfigureAwait(false);
+ using (Stream remoteSteam = await httpClient.GetStreamAsync(pkgVersionUri).ConfigureAwait(false))
+ {
+ remoteItems = await GetVersionItemsAsync(remoteSteam).ConfigureAwait(false);
+ }
+ }
+ catch (IOException ex)
+ {
+ throw new PackageConvertException("下载 Package Version 失败", ex);
}
Dictionary localItems;
@@ -67,7 +70,7 @@ internal class PackageConverter
}
IEnumerable diffOperations = GetItemOperationInfos(remoteItems, localItems);
- await ReplaceGameResourceAsync(diffOperations, gameFolder, scatteredFilesUrl, direction, progress).ConfigureAwait(false);
+ return await ReplaceGameResourceAsync(diffOperations, gameFolder, scatteredFilesUrl, direction, progress).ConfigureAwait(false);
}
///
@@ -100,8 +103,8 @@ internal class PackageConverter
{
if (File.Exists(sdkDllBackup) && File.Exists(sdkVersionBackup))
{
- File.Move(sdkDllBackup, sdkDll, false);
- File.Move(sdkVersionBackup, sdkVersion, false);
+ FileOperation.Move(sdkDllBackup, sdkDll, false);
+ FileOperation.Move(sdkVersionBackup, sdkVersion, false);
}
else
{
@@ -124,15 +127,9 @@ internal class PackageConverter
}
else
{
- if (File.Exists(sdkDll))
- {
- File.Move(sdkDll, sdkDllBackup, true);
- }
-
- if (File.Exists(sdkVersion))
- {
- File.Move(sdkVersion, sdkVersionBackup, true);
- }
+ // backup
+ FileOperation.Move(sdkDll, sdkDllBackup, true);
+ FileOperation.Move(sdkVersion, sdkVersionBackup, true);
}
}
@@ -190,11 +187,17 @@ internal class PackageConverter
// so we assume the data folder is present
if (direction == ConvertDirection.ChineseToOversea)
{
- Directory.Move(yuanShenData, genshinImpactData);
+ if (Directory.Exists(yuanShenData))
+ {
+ Directory.Move(yuanShenData, genshinImpactData);
+ }
}
else
{
- Directory.Move(genshinImpactData, yuanShenData);
+ if (Directory.Exists(genshinImpactData))
+ {
+ Directory.Move(genshinImpactData, yuanShenData);
+ }
}
}
@@ -204,10 +207,42 @@ internal class PackageConverter
File.Move(targetFullPath, cacheFilePath, true);
}
- private async Task ReplaceGameResourceAsync(IEnumerable operations, string gameFolder, string scatteredFilesUrl, ConvertDirection direction, IProgress progress)
+ private static async Task CopyToWithProgressAsync(Stream source, Stream target, string name, long totalBytes, IProgress progress)
+ {
+ const int bufferSize = 81920;
+
+ long totalBytesRead = 0;
+ int bytesRead;
+ Memory buffer = new byte[bufferSize];
+ do
+ {
+ bytesRead = await source.ReadAsync(buffer).ConfigureAwait(false);
+ await target.WriteAsync(buffer[..bytesRead]).ConfigureAwait(false);
+
+ totalBytesRead += bytesRead;
+ progress.Report(new(name, totalBytesRead, totalBytes));
+
+ if (bytesRead <= 0)
+ {
+ break;
+ }
+ }
+ while (bytesRead > 0);
+ }
+
+ private async Task ReplaceGameResourceAsync(IEnumerable operations, string gameFolder, string scatteredFilesUrl, ConvertDirection direction, IProgress progress)
{
// 重命名 _Data 目录
- RenameDataFolder(gameFolder, direction);
+ try
+ {
+ RenameDataFolder(gameFolder, direction);
+ }
+ catch (IOException)
+ {
+ // Access to the path is denied.
+ // When user install the game in special folder like 'Program Files'
+ return false;
+ }
// Ensure cache folder
string cacheFolder = Path.Combine(gameFolder, "Screenshot", "HutaoCache");
@@ -224,13 +259,12 @@ internal class PackageConverter
switch (info.Type)
{
case ItemOperationType.Add:
- await ReplaceFromCacheOrWebAsync(cacheFilePath, targetFilePath, scatteredFilesUrl, info).ConfigureAwait(false);
+ await ReplaceFromCacheOrWebAsync(cacheFilePath, targetFilePath, scatteredFilesUrl, info, progress).ConfigureAwait(false);
break;
case ItemOperationType.Replace:
{
MoveToCache(moveToFilePath, targetFilePath);
- await ReplaceFromCacheOrWebAsync(cacheFilePath, targetFilePath, scatteredFilesUrl, info).ConfigureAwait(false);
-
+ await ReplaceFromCacheOrWebAsync(cacheFilePath, targetFilePath, scatteredFilesUrl, info, progress).ConfigureAwait(false);
break;
}
@@ -245,17 +279,18 @@ internal class PackageConverter
// 重新下载所有 *pkg_version 文件
await ReplacePackageVersionsAsync(scatteredFilesUrl, gameFolder).ConfigureAwait(false);
+ return true;
}
- private async Task ReplaceFromCacheOrWebAsync(string cacheFilePath, string targetFilePath, string scatteredFilesUrl, ItemOperationInfo info)
+ private async Task ReplaceFromCacheOrWebAsync(string cacheFilePath, string targetFilePath, string scatteredFilesUrl, ItemOperationInfo info, IProgress progress)
{
if (File.Exists(cacheFilePath))
{
- string remoteMd5 = await FileDigest.GetMd5Async(cacheFilePath, CancellationToken.None).ConfigureAwait(false);
+ string remoteMd5 = await Digest.GetFileMd5Async(cacheFilePath).ConfigureAwait(false);
if (info.Md5 == remoteMd5.ToLowerInvariant() && new FileInfo(cacheFilePath).Length == info.TotalBytes)
{
// Valid, move it to target path
- // There shouldn't be any file in the same name
+ // There shouldn't be any file in the path/name
File.Move(cacheFilePath, targetFilePath, false);
return;
}
@@ -269,9 +304,32 @@ internal class PackageConverter
// Cache no item, download it anyway.
using (FileStream fileStream = File.Create(targetFilePath))
{
- using (Stream webStream = await httpClient.GetStreamAsync($"{scatteredFilesUrl}/{info.Target}").ConfigureAwait(false))
+ while (true)
{
- await webStream.CopyToAsync(fileStream).ConfigureAwait(false);
+ using (HttpResponseMessage response = await httpClient.GetAsync($"{scatteredFilesUrl}/{info.Target}", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
+ {
+ long totalBytes = response.Content.Headers.ContentLength ?? 0;
+ using (Stream webStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
+ {
+ try
+ {
+ await CopyToWithProgressAsync(webStream, fileStream, info.Target, totalBytes, progress).ConfigureAwait(false);
+ fileStream.Seek(0, SeekOrigin.Begin);
+ string remoteMd5 = await Digest.GetStreamMd5Async(fileStream).ConfigureAwait(false);
+ if (info.Md5 == remoteMd5.ToLowerInvariant())
+ {
+ return;
+ }
+ }
+ catch
+ {
+ // System.IO.IOException: The response ended prematurely.
+ // System.IO.IOException: Received an unexpected EOF or 0 bytes from the transport stream.
+
+ // We want to retry forever.
+ }
+ }
+ }
}
}
}
@@ -320,6 +378,7 @@ internal class PackageConverter
private async Task> GetVersionItemsAsync(Stream stream, ConvertDirection direction, Func nameConverter)
{
Dictionary results = new();
+
using (StreamReader reader = new(stream))
{
while (await reader.ReadLineAsync().ConfigureAwait(false) is string raw)
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageReplaceStatus.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageReplaceStatus.cs
index 6398abbe..6286cdb1 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageReplaceStatus.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageReplaceStatus.cs
@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using CommunityToolkit.Common;
+
namespace Snap.Hutao.Service.Game.Package;
///
@@ -17,8 +19,40 @@ public class PackageReplaceStatus
Description = description;
}
+ ///
+ /// 构造一个新的包更新状态
+ ///
+ /// 名称
+ /// 读取的字节数
+ /// 总字节数
+ public PackageReplaceStatus(string name, long bytesRead, long totalBytes)
+ {
+ Percent = (double)bytesRead / totalBytes;
+ Description = $"{name}\n{Converters.ToFileSizeString(bytesRead)}/{Converters.ToFileSizeString(totalBytes)}";
+ }
+
///
/// 描述
///
public string Description { get; set; }
+
+ ///
+ /// 是否有进度
+ ///
+ public bool IsIndeterminate { get => Percent < 0; }
+
+ ///
+ /// 进度
+ ///
+ public double Percent { get; set; } = -1;
+
+ ///
+ /// 克隆
+ ///
+ /// 克隆的实例
+ public PackageReplaceStatus Clone()
+ {
+ // 进度需要在主线程上创建
+ return new(Description) { Percent = Percent };
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlockerException.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlockerException.cs
new file mode 100644
index 00000000..1978a284
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlockerException.cs
@@ -0,0 +1,28 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Core.Diagnostics;
+using Snap.Hutao.Win32;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Windows.Win32.Foundation;
+using Windows.Win32.System.Diagnostics.ToolHelp;
+using static Windows.Win32.PInvoke;
+
+namespace Snap.Hutao.Service.Game.Unlocker;
+
+///
+/// 游戏帧率解锁器异常
+///
+internal class GameFpsUnlockerException : Exception
+{
+ ///
+ /// 构造一个新的用户数据损坏异常
+ ///
+ /// 消息
+ /// 内部错误
+ public GameFpsUnlockerException(Exception innerException)
+ : base($"解锁帧率失败: {innerException.Message}", innerException)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs
index fc630bf5..abd90fc1 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs
@@ -135,7 +135,7 @@ internal partial class MetadataService : IMetadataService, IMetadataServiceIniti
string fileFullPath = Path.Combine(metadataFolderPath, fileFullName);
if (File.Exists(fileFullPath))
{
- skip = md5 == await FileDigest.GetMd5Async(fileFullPath, token).ConfigureAwait(false);
+ skip = md5 == await Digest.GetFileMd5Async(fileFullPath, token).ConfigureAwait(false);
}
if (!skip)
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
index 2f51b46a..fb8f89b0 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
@@ -76,7 +76,14 @@ internal class UserService : IUserService
if (currentUser != null)
{
currentUser.IsSelected = true;
- appDbContext.Users.UpdateAndSave(currentUser.Entity);
+ try
+ {
+ appDbContext.Users.UpdateAndSave(currentUser.Entity);
+ }
+ catch (InvalidOperationException ex)
+ {
+ throw new Core.ExceptionService.UserdataCorruptedException($"用户 {currentUser.UserInfo?.Uid} 状态保存失败", ex);
+ }
}
messenger.Send(message);
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogRefreshProgressDialog.xaml b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogRefreshProgressDialog.xaml
index 8016b197..9ac5488a 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogRefreshProgressDialog.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GachaLogRefreshProgressDialog.xaml
@@ -22,7 +22,6 @@
-
-
+ Text="{Binding State.Description}"/>
+
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 a62a4281..7948711c 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml.cs
@@ -4,6 +4,7 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control;
+using Snap.Hutao.Service.Game.Package;
namespace Snap.Hutao.View.Dialog;
@@ -12,7 +13,7 @@ namespace Snap.Hutao.View.Dialog;
///
public sealed partial class LaunchGamePackageConvertDialog : ContentDialog
{
- private static readonly DependencyProperty DescriptionProperty = Property.Depend(nameof(Description), "请稍候");
+ private static readonly DependencyProperty StateProperty = Property.Depend(nameof(State));
///
/// 构造一个新的启动游戏客户端转换对话框
@@ -27,9 +28,9 @@ public sealed partial class LaunchGamePackageConvertDialog : ContentDialog
///
/// 描述
///
- public string Description
+ public PackageReplaceStatus State
{
- get { return (string)GetValue(DescriptionProperty); }
- set { SetValue(DescriptionProperty, value); }
+ get { return (PackageReplaceStatus)GetValue(StateProperty); }
+ set { SetValue(StateProperty, value); }
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml
index 4b708439..0ed6a0d9 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml
@@ -81,48 +81,6 @@
-
-
-
- 0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Message="切换国际服功能会在游戏截图文件创建缓存文件夹"
+ Severity="Informational"/>
();
+ if (vm.Users!.Count == 1)
+ {
+ await ThreadHelper.SwitchToMainThreadAsync();
+ vm.SelectedUser = vm.Users.Single();
+ }
+
infoBarService.Success($"用户 [{nickname}] 添加成功");
break;
case UserOptionResult.Incomplete:
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs
index db81f843..a0d4cb99 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs
@@ -313,11 +313,22 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
if (result == ContentDialogResult.Primary)
{
- await achievementService.RemoveArchiveAsync(SelectedArchive).ConfigureAwait(false);
+ try
+ {
+ ThrowIfViewDisposed();
+ using (await DisposeLock.EnterAsync(CancellationToken).ConfigureAwait(false))
+ {
+ ThrowIfViewDisposed();
+ await achievementService.RemoveArchiveAsync(SelectedArchive).ConfigureAwait(false);
+ }
- // Re-select first archive
- await ThreadHelper.SwitchToMainThreadAsync();
- SelectedArchive = Archives.FirstOrDefault();
+ // Re-select first archive
+ await ThreadHelper.SwitchToMainThreadAsync();
+ SelectedArchive = Archives.FirstOrDefault();
+ }
+ catch (OperationCanceledException)
+ {
+ }
}
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs
index 7c8d8d49..e279ab25 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs
@@ -63,7 +63,7 @@ internal class DailyNoteViewModel : Abstraction.ViewModel
OpenUICommand = new AsyncRelayCommand(OpenUIAsync);
TrackRoleCommand = new AsyncRelayCommand(TrackRoleAsync);
RefreshCommand = new AsyncRelayCommand(RefreshAsync);
- RemoveDailyNoteCommand = new RelayCommand(RemoveDailyNote);
+ RemoveDailyNoteCommand = new AsyncRelayCommand(RemoveDailyNoteAsync);
ModifyNotificationCommand = new AsyncRelayCommand(ModifyDailyNoteNotificationAsync);
DailyNoteVerificationCommand = new AsyncRelayCommand(VerifyDailyNoteVerificationAsync);
}
@@ -229,11 +229,11 @@ internal class DailyNoteViewModel : Abstraction.ViewModel
await dailyNoteService.RefreshDailyNotesAsync(false).ConfigureAwait(false);
}
- private void RemoveDailyNote(DailyNoteEntry? entry)
+ private async Task RemoveDailyNoteAsync(DailyNoteEntry? entry)
{
if (entry != null)
{
- dailyNoteService.RemoveDailyNote(entry);
+ await dailyNoteService.RemoveDailyNoteAsync(entry).ConfigureAwait(false);
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs
index c40dd326..87107173 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs
@@ -12,6 +12,7 @@ using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Game;
+using Snap.Hutao.Service.Game.Unlocker;
using Snap.Hutao.Service.Navigation;
using Snap.Hutao.Service.User;
using Snap.Hutao.View.Dialog;
@@ -305,10 +306,13 @@ internal class LaunchGameViewModel : Abstraction.ViewModel
// access level is already high enough.
await ThreadHelper.SwitchToMainThreadAsync();
LaunchGamePackageConvertDialog dialog = new();
+ Progress progress = new(state => dialog.State = state.Clone());
await using (await dialog.BlockAsync().ConfigureAwait(false))
{
- Progress progress = new(s => dialog.Description = s.Description);
- await gameService.EnsureGameResourceAsync(SelectedScheme, progress).ConfigureAwait(false);
+ if (!await gameService.EnsureGameResourceAsync(SelectedScheme, progress).ConfigureAwait(false))
+ {
+ infoBarService.Warning("切换服务器失败");
+ }
}
}
@@ -325,7 +329,7 @@ internal class LaunchGameViewModel : Abstraction.ViewModel
LaunchConfiguration configuration = new(IsExclusive, IsFullScreen, IsBorderless, ScreenWidth, ScreenHeight, IsElevated && UnlockFps, TargetFps);
await gameService.LaunchAsync(configuration).ConfigureAwait(false);
}
- catch (GameFileOperationException ex)
+ catch (Exception ex)
{
infoBarService.Error(ex);
}
@@ -334,7 +338,14 @@ internal class LaunchGameViewModel : Abstraction.ViewModel
private async Task DetectGameAccountAsync()
{
- await gameService.DetectGameAccountAsync().ConfigureAwait(false);
+ try
+ {
+ await gameService.DetectGameAccountAsync().ConfigureAwait(false);
+ }
+ catch (Core.ExceptionService.UserdataCorruptedException ex)
+ {
+ Ioc.Default.GetRequiredService().Error(ex);
+ }
}
private void AttachGameAccountToCurrentUserGameRole(GameAccount? gameAccount)
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs
index a1abdcd1..e309f15c 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/WelcomeViewModel.cs
@@ -57,11 +57,11 @@ internal class WelcomeViewModel : ObservableObject
// Cancel all previous created jobs
serviceProvider.GetRequiredService().CancelAllJobs();
- await Task.WhenAll(downloadSummaries.Select(async d =>
+ await Task.WhenAll(downloadSummaries.Select(async downloadTask =>
{
- await d.DownloadAndExtractAsync().ConfigureAwait(false);
+ await downloadTask.DownloadAndExtractAsync().ConfigureAwait(false);
await ThreadHelper.SwitchToMainThreadAsync();
- DownloadSummaries.Remove(d);
+ DownloadSummaries.Remove(downloadTask);
})).ConfigureAwait(true);
serviceProvider.GetRequiredService().Send(new Message.WelcomeStateCompleteMessage());
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs
index c74329fd..6a4d4f00 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs
@@ -19,6 +19,7 @@ using Snap.Hutao.Service.User;
using Snap.Hutao.View.Dialog;
using Snap.Hutao.Web.Response;
using System.Collections.Immutable;
+using System.Runtime.InteropServices;
using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
using CalcConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
@@ -178,7 +179,13 @@ internal class WikiAvatarViewModel : Abstraction.ViewModel
if (!Avatars.Contains(Selected))
{
- Avatars.MoveCurrentToFirst();
+ try
+ {
+ Avatars.MoveCurrentToFirst();
+ }
+ catch (COMException)
+ {
+ }
}
}
else
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiWeaponViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiWeaponViewModel.cs
index 0b503f9b..ac36e97d 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiWeaponViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiWeaponViewModel.cs
@@ -18,6 +18,7 @@ using Snap.Hutao.Service.User;
using Snap.Hutao.View.Dialog;
using Snap.Hutao.Web.Response;
using System.Collections.Immutable;
+using System.Runtime.InteropServices;
using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
using CalcConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
@@ -176,7 +177,13 @@ internal class WikiWeaponViewModel : Abstraction.ViewModel
if (!Weapons.Contains(Selected))
{
- Weapons.MoveCurrentToFirst();
+ try
+ {
+ Weapons.MoveCurrentToFirst();
+ }
+ catch (COMException)
+ {
+ }
}
}
else
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs
index 9dec040d..70e35cf1 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs
@@ -14,6 +14,7 @@ using Snap.Hutao.Web.Hoyolab.Bbs.User;
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using Snap.Hutao.Web.Hoyolab.Passport;
using Snap.Hutao.Web.Hoyolab.Takumi.Auth;
+using System.Runtime.InteropServices;
using System.Text;
namespace Snap.Hutao.Web.Bridge;
@@ -338,7 +339,16 @@ public class MiHoYoJSInterface
logger?.LogInformation("[ExecuteScript: {callback}]\n{payload}", callback, payload);
await ThreadHelper.SwitchToMainThreadAsync();
- return await webView.ExecuteScriptAsync(js);
+ try
+ {
+ return await webView.ExecuteScriptAsync(js);
+ }
+ catch (COMException)
+ {
+ // COMException (0x8007139F): 组或资源的状态不是执行请求操作的正确状态。 (0x8007139F)
+ // webview is disposing or disposed
+ return string.Empty;
+ }
}
[SuppressMessage("", "VSTHRD100")]
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Enka/Model/TypeValue.cs b/src/Snap.Hutao/Snap.Hutao/Web/Enka/Model/TypeValue.cs
index 8e6378f3..de5e9e2c 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Enka/Model/TypeValue.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Enka/Model/TypeValue.cs
@@ -21,4 +21,17 @@ public class TypeValue
///
[JsonPropertyName("val")]
public string? Value { get; set; }
+
+ ///
+ /// 值 Int32
+ ///
+ [JsonIgnore]
+ public int ValueInt32
+ {
+ get
+ {
+ _ = int.TryParse(Value, out int result);
+ return result;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Geetest/GeetestClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Geetest/GeetestClient.cs
index 0b067dd1..fa1c34c0 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Geetest/GeetestClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Geetest/GeetestClient.cs
@@ -59,8 +59,15 @@ internal class GeetestClient
///
/// 验证注册
/// 验证方式
- public Task?> GetAjaxAsync(VerificationRegistration registration)
+ public async Task?> GetAjaxAsync(VerificationRegistration registration)
{
- return GetAjaxAsync(registration.Gt, registration.Challenge);
+ try
+ {
+ return await GetAjaxAsync(registration.Gt, registration.Challenge).ConfigureAwait(false);
+ }
+ catch (HttpRequestException)
+ {
+ return null;
+ }
}
}