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