diff --git a/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.json b/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.json index 1dfaaafb..bcd1d020 100644 --- a/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.json +++ b/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.json @@ -1,6 +1,5 @@ { "$schema": "https://raw.githubusercontent.com/microsoft/CsWin32/main/src/Microsoft.Windows.CsWin32/settings.schema.json", "allowMarshaling": true, - "useSafeHandles": false, - "emitSingleFile": true + "useSafeHandles": false } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogHideToken.cs b/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogHideToken.cs index 0e938e27..c0178f3f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogHideToken.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogHideToken.cs @@ -10,8 +10,8 @@ internal struct ContentDialogHideToken : IDisposable, IAsyncDisposable private readonly ContentDialog contentDialog; private readonly ITaskContext taskContext; - private bool disposed = false; private bool disposing = false; + private bool disposed = false; public ContentDialogHideToken(ContentDialog contentDialog, ITaskContext taskContext) { diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs index c91d536b..790590b6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using CommunityToolkit.Mvvm.Messaging; +using Microsoft.Windows.ApplicationModel.Resources; using Snap.Hutao.Core.Logging; using Snap.Hutao.Service; using System.Globalization; diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ExceptionRecorder.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ExceptionRecorder.cs index 358f83b8..8cf344d2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ExceptionRecorder.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ExceptionRecorder.cs @@ -29,11 +29,14 @@ internal sealed partial class ExceptionRecorder private void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) { - serviceProvider + ValueTask task = serviceProvider .GetRequiredService() - .UploadLogAsync(e.Exception) - .GetAwaiter() - .GetResult(); + .UploadLogAsync(e.Exception); + + if (!task.IsCompleted) + { + task.GetAwaiter().GetResult(); + } logger.LogError("未经处理的全局异常:\r\n{Detail}", ExceptionFormat.Format(e.Exception)); } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/RuntimeEnvironmentException.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/RuntimeEnvironmentException.cs index 272d6cdb..14f65023 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/RuntimeEnvironmentException.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/RuntimeEnvironmentException.cs @@ -15,7 +15,7 @@ internal sealed class RuntimeEnvironmentException : Exception /// /// 消息 /// 内部错误 - public RuntimeEnvironmentException(string message, Exception innerException) + public RuntimeEnvironmentException(string message, Exception? innerException) : base($"{message}\n{innerException.Message}", innerException) { } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs index ec995823..c10f7373 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs @@ -14,27 +14,27 @@ namespace Snap.Hutao.Core.ExceptionService; [System.Diagnostics.StackTraceHidden] internal static class ThrowHelper { - /// - /// 操作取消 - /// - /// 消息 - /// 内部错误 - /// nothing - /// 操作取消异常 [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] - public static OperationCanceledException OperationCanceled(string message, Exception? inner = default) + public static ArgumentException Argument(string message, string? paramName) { - throw new OperationCanceledException(message, inner); + throw new ArgumentException(message, paramName); + } + + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + public static DatabaseCorruptedException DatabaseCorrupted(string message, Exception? inner) + { + throw new DatabaseCorruptedException(message, inner); + } + + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + public static GameFileOperationException GameFileOperation(string message, Exception? inner) + { + throw new GameFileOperationException(message, inner); } - /// - /// 无效操作 - /// - /// 消息 - /// 内部错误 - /// nothing - /// 无效操作异常 [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] public static InvalidOperationException InvalidOperation(string message, Exception? inner = default) @@ -42,71 +42,38 @@ internal static class ThrowHelper throw new InvalidOperationException(message, inner); } - /// - /// 游戏文件操作失败 - /// - /// 消息 - /// 内部错误 - /// nothing - /// 文件操作失败 - public static GameFileOperationException GameFileOperation(string message, Exception inner) - { - throw new GameFileOperationException(message, inner); - } - - /// - /// 包转换错误 - /// - /// 消息 - /// 内部错误 - /// nothing - /// 包转换错误异常 - [DoesNotReturn] - [MethodImpl(MethodImplOptions.NoInlining)] - public static PackageConvertException PackageConvert(string message, Exception inner) - { - throw new PackageConvertException(message, inner); - } - - /// - /// 用户数据损坏 - /// - /// 消息 - /// 内部错误 - /// nothing - /// 数据损坏 - [DoesNotReturn] - [MethodImpl(MethodImplOptions.NoInlining)] - public static UserdataCorruptedException UserdataCorrupted(string message, Exception inner) - { - throw new UserdataCorruptedException(message, inner); - } - - [DoesNotReturn] - [MethodImpl(MethodImplOptions.NoInlining)] - public static DatabaseCorruptedException DatabaseCorrupted(string message, Exception inner) - { - throw new DatabaseCorruptedException(message, inner); - } - - /// - /// 运行环境异常 - /// - /// 消息 - /// 内部错误 - /// nothing - /// 环境异常 - [DoesNotReturn] - [MethodImpl(MethodImplOptions.NoInlining)] - public static RuntimeEnvironmentException RuntimeEnvironment(string message, Exception inner) - { - throw new RuntimeEnvironmentException(message, inner); - } - [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] public static NotSupportedException NotSupported() { throw new NotSupportedException(); } + + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + public static OperationCanceledException OperationCanceled(string message, Exception? inner = default) + { + throw new OperationCanceledException(message, inner); + } + + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + public static PackageConvertException PackageConvert(string message, Exception? inner) + { + throw new PackageConvertException(message, inner); + } + + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + public static RuntimeEnvironmentException RuntimeEnvironment(string message, Exception? inner) + { + throw new RuntimeEnvironmentException(message, inner); + } + + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + public static UserdataCorruptedException UserdataCorrupted(string message, Exception? inner) + { + throw new UserdataCorruptedException(message, inner); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Shell/ShellLinkInterop.cs b/src/Snap.Hutao/Snap.Hutao/Core/Shell/ShellLinkInterop.cs index 257354c0..051aed33 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Shell/ShellLinkInterop.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Shell/ShellLinkInterop.cs @@ -49,7 +49,14 @@ internal sealed partial class ShellLinkInterop : IShellLinkInterop string target = Path.Combine(desktop, $"{SH.AppNameAndVersion.Format(runtimeOptions.Version)}.lnk"); IPersistFile persistFile = (IPersistFile)shellLink; - persistFile.Save(target, false); + try + { + persistFile.Save(target, false); + } + catch (UnauthorizedAccessException) + { + return false; + } return true; } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueExtension.cs index a353ad31..e4365bc9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueExtension.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.UI.Dispatching; +using System.Runtime.ExceptionServices; namespace Snap.Hutao.Core.Threading; @@ -18,15 +19,35 @@ internal static class DispatcherQueueExtension /// 执行的回调 public static void Invoke(this DispatcherQueue dispatcherQueue, Action action) { - using (ManualResetEventSlim blockEvent = new()) + if (dispatcherQueue.HasThreadAccess) + { + action(); + return; + } + + ExceptionDispatchInfo? exceptionDispatchInfo = null; + using (ManualResetEventSlim blockEvent = new(false)) { dispatcherQueue.TryEnqueue(() => { - action(); - blockEvent.Set(); + try + { + action(); + } + catch (Exception ex) + { + ExceptionDispatchInfo.Capture(ex); + } + finally + { + blockEvent.Set(); + } }); blockEvent.Wait(); +#pragma warning disable CA1508 + exceptionDispatchInfo?.Throw(); +#pragma warning restore CA1508 } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueSynchronizationContextSendSupport.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueSynchronizationContextSendSupport.cs new file mode 100644 index 00000000..fe45d9b1 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueSynchronizationContextSendSupport.cs @@ -0,0 +1,33 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.UI.Dispatching; + +namespace Snap.Hutao.Core.Threading; + +internal sealed class DispatcherQueueSynchronizationContextSendSupport : SynchronizationContext +{ + private readonly DispatcherQueue dispatcherQueue; + + public DispatcherQueueSynchronizationContextSendSupport(DispatcherQueue dispatcherQueue) + { + this.dispatcherQueue = dispatcherQueue; + } + + public override void Post(SendOrPostCallback d, object? state) + { + ArgumentNullException.ThrowIfNull(d); + dispatcherQueue.TryEnqueue(() => d(state)); + } + + public override void Send(SendOrPostCallback d, object? state) + { + ArgumentNullException.ThrowIfNull(d); + dispatcherQueue.Invoke(() => d(state)); + } + + public override SynchronizationContext CreateCopy() + { + return new DispatcherQueueSynchronizationContextSendSupport(dispatcherQueue); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskContext.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskContext.cs index 5462d81f..d8aee9cd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskContext.cs @@ -11,7 +11,7 @@ namespace Snap.Hutao.Core.Threading; [Injection(InjectAs.Singleton, typeof(ITaskContext))] internal sealed class TaskContext : ITaskContext { - private readonly DispatcherQueueSynchronizationContext dispatcherQueueSynchronizationContext; + private readonly DispatcherQueueSynchronizationContextSendSupport synchronizationContext; private readonly DispatcherQueue dispatcherQueue; /// @@ -20,8 +20,8 @@ internal sealed class TaskContext : ITaskContext public TaskContext() { dispatcherQueue = DispatcherQueue.GetForCurrentThread(); - dispatcherQueueSynchronizationContext = new(dispatcherQueue); - SynchronizationContext.SetSynchronizationContext(dispatcherQueueSynchronizationContext); + synchronizationContext = new(dispatcherQueue); + SynchronizationContext.SetSynchronizationContext(synchronizationContext); } /// @@ -39,18 +39,11 @@ internal sealed class TaskContext : ITaskContext /// public void InvokeOnMainThread(Action action) { - if (dispatcherQueue.HasThreadAccess) - { - action(); - } - else - { - dispatcherQueue.Invoke(action); - } + dispatcherQueue.Invoke(action); } public IProgress CreateProgressForMainThread(Action handler) { - return new DispatcherQueueProgress(handler, dispatcherQueueSynchronizationContext); + return new DispatcherQueueProgress(handler, synchronizationContext); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/PickerFactory.cs b/src/Snap.Hutao/Snap.Hutao/Factory/PickerFactory.cs index 79c37cbc..bd177eb6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Factory/PickerFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Factory/PickerFactory.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. using Snap.Hutao.Core; +using Snap.Hutao.Core.LifeCycle; +using Snap.Hutao.Core.Windowing; using Snap.Hutao.Factory.Abstraction; using Windows.Storage.Pickers; using WinRT.Interop; @@ -16,7 +18,7 @@ internal sealed partial class PickerFactory : IPickerFactory { private const string AnyType = "*"; - private readonly MainWindow mainWindow; + private readonly ICurrentWindowReference currentWindow; /// public FileOpenPicker GetFileOpenPicker(PickerLocationId location, string commitButton, params string[] fileTypes) @@ -78,7 +80,10 @@ internal sealed partial class PickerFactory : IPickerFactory { // Create a folder picker. T picker = new(); - InitializeWithWindow.Initialize(picker, mainWindow.WindowOptions.Hwnd); + if (currentWindow.Window is IWindowOptionsSource optionsSource) + { + InitializeWithWindow.Initialize(picker, optionsSource.WindowOptions.Hwnd); + } return picker; } diff --git a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGF.cs b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGF.cs index 4b3f8cdb..4ded9b4b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGF.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGF.cs @@ -1,6 +1,8 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using System.Runtime.InteropServices; + namespace Snap.Hutao.Model.InterChange.GachaLog; /// @@ -49,20 +51,35 @@ internal sealed class UIGF /// /// 列表物品是否正常 /// - /// 首个出错的Id + /// 首个出错的Id /// 是否正常 - public bool IsMajor2Minor2OrLowerListValid([NotNullWhen(false)] out long itemId) + public bool IsMajor2Minor2OrLowerListValid([NotNullWhen(false)] out long id) { - foreach (UIGFItem item in List) + foreach (ref readonly UIGFItem item in CollectionsMarshal.AsSpan(List)) { if (item.ItemType != SH.ModelInterchangeUIGFItemTypeAvatar && item.ItemType != SH.ModelInterchangeUIGFItemTypeWeapon) { - itemId = item.Id; + id = item.Id; return false; } } - itemId = 0; + id = 0; + return true; + } + + public bool IsMajor2Minor3OrHigherListValid([NotNullWhen(false)] out long id) + { + foreach (ref readonly UIGFItem item in CollectionsMarshal.AsSpan(List)) + { + if (string.IsNullOrEmpty(item.ItemId)) + { + id = item.Id; + return false; + } + } + + id = 0; return true; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/AvatarIds.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/AvatarIds.cs index 2fa22d7f..279ca10e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/AvatarIds.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/AvatarIds.cs @@ -91,6 +91,8 @@ internal static class AvatarIds public static readonly AvatarId Freminet = 10000085; public static readonly AvatarId Wriothesley = 10000086; public static readonly AvatarId Neuvillette = 10000087; + public static readonly AvatarId Charlotte = 10000088; + public static readonly AvatarId Furina = 10000089; /// /// 检查该角色是否为主角 diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs index 4078022e..10a70e1b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs @@ -2418,6 +2418,15 @@ namespace Snap.Hutao.Resource.Localization { } } + /// + /// 查找类似 当前 WebView2 版本不支持管理配置,继续使用可能会导致异常,请尽快升级 的本地化字符串。 + /// + internal static string ViewControlWebViewerCoreWebView2ProfileQueryInterfaceFailed { + get { + return ResourceManager.GetString("ViewControlWebViewerCoreWebView2ProfileQueryInterfaceFailed", resourceCulture); + } + } + /// /// 查找类似 养成计划 的本地化字符串。 /// @@ -3777,6 +3786,15 @@ namespace Snap.Hutao.Resource.Localization { } } + /// + /// 查找类似 保存游戏路径失败 的本地化字符串。 + /// + internal static string ViewModelSettingSetGamePathDatabaseFailedTitle { + get { + return ResourceManager.GetString("ViewModelSettingSetGamePathDatabaseFailedTitle", resourceCulture); + } + } + /// /// 查找类似 用户 [{0}] 添加成功 的本地化字符串。 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx index 3a3246ec..fad44442 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx @@ -959,6 +959,9 @@ Statistics + + The current version of WebView2 does not support management configuration, continue to use may cause abnormalities, please upgrade as soon as possible + Dev Plan @@ -1412,6 +1415,9 @@ Set data directory successfully. Restart to apply changes. + + Failed to save game path + User [{0}] added successfully @@ -1938,7 +1944,7 @@ Server - 版本更新前需要提前转换至与启动器匹配的服务器 + You need to convert to a server that matches the launcher before updating the version Please turn off V-Sync in the game settings. You may need a high-performance graphic card to support a high frame rate limit. diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ja.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ja.resx index 2ab09211..a6011a11 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ja.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ja.resx @@ -169,10 +169,10 @@ 権限不足のため、一時ファイルを作成できませんでした - ゲームスタート + ゲームランチャー - 胡桃がリアルタイムノートを更新しています | 編集や削除をしないでください + 胡桃がリアルタイムノートを更新するために使用するタスクです。編集や削除をしないでください! セマフォが解放され、操作がキャンセルされました @@ -211,10 +211,10 @@ {0:f2} 回目 - 最も引けなかった回数: {0} 回 + 最も遅い回数: {0} 回 - 最も早く引けた回数: {0} 回 + 最も早い回数: {0} 回 {0} 回目 @@ -295,7 +295,7 @@ Need EXACT same string in game - 少年 + ショタ 少女 @@ -426,58 +426,58 @@ この階層のボス - A组:不同的组同时在场,各自分波独立 + グループA: 異なるグループが同時に存在し、それぞれ独立したウェーブがある - A组第一波:不同的组同时在场,各自分波独立 + グループA ウェーブ1: 異なるグループが同時に存在し、それぞれ独立したウェーブがある - A组第二波:不同的组同时在场,各自分波独立 + グループA ウェーブ2: 異なるグループが同時に存在し、それぞれ独立したウェーブがある - A组第三波:不同的组同时在场,各自分波独立 + グループA ウェーブ3: 異なるグループが同時に存在し、それぞれ独立したウェーブがある - B组:不同的组同时在场,各自分波独立 + グループB: 異なるグループが同時に存在し、それぞれ独立したウェーブがある - B组第一波:不同的组同时在场,各自分波独立 + グループB ウェーブ1: 異なるグループが同時に存在し、それぞれ独立したウェーブがある - B组第二波:不同的组同时在场,各自分波独立 + グループB ウェーブ2: 異なるグループが同時に存在し、それぞれ独立したウェーブがある - B组第三波:不同的组同时在场,各自分波独立 + グループB ウェーブ3: 異なるグループが同時に存在し、それぞれ独立したウェーブがある - C组:不同的组同时在场,各自分波独立 + グループC: 異なるグループが同時に存在し、それぞれ独立したウェーブがある - C组第一波:不同的组同时在场,各自分波独立 + グループC ウェーブ1: 異なるグループが同時に存在し、それぞれ独立したウェーブがある - C组第二波:不同的组同时在场,各自分波独立 + グループC ウェーブ2: 異なるグループが同時に存在し、それぞれ独立したウェーブがある - C组第三波:不同的组同时在场,各自分波独立 + グループC ウェーブ3: 異なるグループが同時に存在し、それぞれ独立したウェーブがある - D组:不同的组同时在场,各自分波独立 + グループD: 異なるグループが同時に存在し、それぞれ独立したウェーブがある - D组第一波:不同的组同时在场,各自分波独立 + グループD ウェーブ1: 異なるグループが同時に存在し、それぞれ独立したウェーブがある - D组第二波:不同的组同时在场,各自分波独立 + グループD ウェーブ2: 異なるグループが同時に存在し、それぞれ独立したウェーブがある - D组第三波:不同的组同时在场,各自分波独立 + グループD ウェーブ3: 異なるグループが同時に存在し、それぞれ独立したウェーブがある - 与其他怪物独立 + 他の敵とは独立 - 暂时没有分波信息 + ウェーブ情報がありません ウェーブ 1: すべての敵を倒すと、次のウェーブの敵が出現する。 @@ -498,13 +498,13 @@ キャラクターラインナップを更新する - データありません + データがありません 精錬ランク{0} - 必须先选择一个用户与角色 + ユーザーとUIDを選択する必要があります {0} つのアチーブメントを追加 | {1} つのアチーブメントを更新 |{2} つのアチーブメントを削除 @@ -645,10 +645,10 @@ 育成計画を複数選択しています - スタート + ゲームを起動 - わかりました + 了解 リクエストエラー @@ -675,7 +675,7 @@ 現在の洞天宝銭:{0} - 多个提醒项达到设定值 + 複数の通知項目が設定値に達しました 天然樹脂 @@ -690,7 +690,7 @@ 参量物質変化器 - まもなく完成 + 準備完了 参量物質変化器は使用可能 @@ -720,7 +720,7 @@ 無効なアイテムが含まれてます、Id:{0} - 请求验证密钥失败 + 認証キーのリクエストに失敗しました。 原神のパスの書き方に誤りがあるか、またはパスを正しく設定されていません。 @@ -744,7 +744,7 @@ Item Id:{0} はサポートしていません - UIGF ファイルの言語:{0} と胡桃の設定言語:{1} とマッチングしません。言語を切り替えて再度試してください + UIGF ファイルの言語:{0} と胡桃の設定言語:{1} がマッチングしません。言語を切り替えて再度試してください 一致するアカウントが複数見つかりました。重複しているアカウントを削除してください。 @@ -753,25 +753,25 @@ ゲームのリソース情報を確認 - 游戏文件操作失败:{0} + ゲームファイルの操作に失敗しました。: {0} - ゲームプロセスが終了した + ゲームプロセスが終了しました - ゲームプロセスが初期化している + ゲームプロセスを初期化しています - ゲームプロセスが生成された + ゲームプロセスが生成されました - FPS上限解除失敗、プロセスが終了させる + FPS上限の解除に失敗しました、プロセスを終了します FPS上限解除成功 - FPS上限解除を試みる + FPSの上限解除を試みています プロセスが終了するまで待機中 @@ -783,16 +783,16 @@ Unity ログファイルが見つかりません - Unity ログファイル内にはゲームパスが見つかりません + Unity ログファイル内にゲームパスが見つかりません バックアップ:{0} - リネーム:{0} を:{1} に + リネーム:{0} を:{1} へ - 復元する:{0} + 置換:{0} データフォルダの名前を変更できませんでした @@ -804,13 +804,13 @@ Package Versionを取得できません - 下载客户端文件失败:{0} + クライアントファイルのダウンロードに失敗しました。: {0} - ゲームパスが見つかりません、設定にて変更してください + ゲームパスが見つかりません、設定で変更してください - 未开启长路径功能,无法设置注册表键值 + 長いパスのサポートがオフになっているため、レジストリキーを編集できません。 PowerShellのインストールディレクトリが見つかりません @@ -822,22 +822,22 @@ 設定ファイルを読み取れない、または保存できませんでした。管理者モードで再試行してください。 - 在查找必要的模块时遇到问题:无法读取任何模块,可能是保护驱动已经加载完成,请重试 + 必要なモジュールの読み込みに失敗しました: モジュールの読み込みが出来ません。保護ドライバが読み込まれている可能性があります。もう一度お試しください。 - 在查找必要的模块时遇到问题:查找模块超时,请重试 + 必要なモジュールの検索時にエラーが発生しました: タイムアウトしました。もう一度お試しください。 - 在匹配内存时遇到问题:无法匹配到期望的内容 + メモリパターンマッチングエラー: 予期しない値です。 - 在读取必要的模块内存时遇到问题:无法将模块内存复制到指定位置 + メモリへのモジュールの読み込みに失敗しました: 指定されたメモリ位置にモジュールをコピーできません。 - 在读取游戏进程内存时遇到问题:无法读取到指定地址的有效值 + ゲームプロセスのメモリの読み取りに失敗しました: 指定されたアドレスで有効な値を読み取れませんでした。 - 祈願履歴のアップロード期限は\n{0:yyyy.MM.dd HH:mm:ss} + 祈願履歴のアップロード期限は\n{0:yyyy.MM.dd HH:mm:ss}です。 キャッシュされたメタデータファイルが見つかりませんでした @@ -846,34 +846,34 @@ メタデータサービスが初期化されていないか、または初期化できませんでした - メタデータの検証ファイルを解析できなかった + メタデータの検証ファイルを解析できませんでした。 - メタデータの検証ファイルをダウンロードできなかった + メタデータの検証ファイルのダウンロードに失敗しました。 胡桃のバージョンが古すぎるため、アップデートを推奨します。 - ログインボーナスを獲得できなかった、{0} + ログインボーナスを獲得できませんでした。 {0} - 获取签到次数失败 + サインイン日数の取得に失敗しました。 - 获取奖励列表失败 + ログイン報酬リストの取得に失敗しました。 - 認証に失敗した、MiYouSheの原神コーナーでログインボーナスを獲得してください + 認証に失敗しました、MiHoYo BBSの原神コーナーでログインボーナスを獲得してください - ログインボーナスを獲得した、{0}×{1} + ログインボーナスを獲得しました。{0}×{1} サポートされていないUIGFバージョン - ユーザー情報が複数セレクトしています + ユーザー情報を複数選択しています。 ユーザー {0} のステータスを保存できません @@ -959,6 +959,9 @@ 統計 + + 現在インストールされているWebView2のバージョンは構成管理をサポートしていないため、エラーが発生する可能性があります。WebView2 コンポーネントを更新してください。 + 育成計画 @@ -1044,7 +1047,7 @@ 祈願記録をインポート - 祈願履歴Urlは無効、再度取得してください + 祈願履歴Urlが無効です、再度取得してください {0} を取得中 @@ -1059,22 +1062,22 @@ 祈願履歴のURLを直接入力 - 请输入请求接口的 Url 复合模板 + パラメーターを使用したリクエストURLの入力 - 接口需要返回形如上方所示的 Json 数据,多余的数据会被忽略 + 認証サービスは、上記のようなJsonデータを返す必要があります。この例以外のデータは無視されます。 "code" は 0 の場合のみ認証成功です。他の戻り値は認証失敗となります。 - 結果を返す + 戻り値 - {0} はリクエストの際、gtに切り替えます + {0} はリクエストの際、gtに置き換えられます - {1} はリクエストの際、challengeに切り替えます + {1} はリクエストの際、challengeに置き換えられます GETメソッドでリクエストの処理を行います。 @@ -1083,7 +1086,7 @@ - 配置无感验证接口 + Geetest/CAPTCHA 認証APIの設定 Appをエクスポート @@ -1101,7 +1104,7 @@ UIAFバージョン - 记录条数 + レコード数 UID @@ -1128,16 +1131,16 @@ ユーザーデータを完全に削除しますか - 立即前往 + すぐに移動 - 进入文档页面并按指示操作 + ドキュメントのページへ移動し、指示に従って操作してください ドキュメント - STokenが含めているCookieを入力してください + STokenが含まれたCookieを入力してください クッキーを設定 @@ -1167,16 +1170,16 @@ 実行環境 - 安装完成后重启胡桃以查看是否正常生效 + インストール完了後に胡桃を再起動し、動作を確認してください - アイコンが文字化けが起きた場合は、ここに + 上のアイコンが読み込めなかったり文字化けしている場合はこちら フォントを自動的にダウンロード、インストールします - WebView2 ランタイムが検出されませんのアラートが出た場合は、ここに + WebView2 ランタイムが検出されませんとアラートが出た場合はこちら ランタイムを自動的にダウンロード、インストールします @@ -1197,13 +1200,13 @@ ゲームランチャー - アーカイブ [{0}] を作成されました + アーカイブ [{0}] を作成しました アーカイブ [{0}] はすでに使用されています。別の名前で作成してください - 無効な文字が含むアーカイブを作成できません + 無効な文字を含むアーカイブは作成できません UIAF ファイル @@ -1224,7 +1227,7 @@ 素材リスト取得中、しばらくお待ちください - 当前角色无法计算,请同步信息后再试 + 現在のキャラクターを計算できません。データの同期後に再試行してください。 キャラクターデータサービス [Enka API] は現在利用できません。 @@ -1236,7 +1239,7 @@ データを取得中 - クリップボードを読み込みできません + クリップボードから読み込みできません キャラクターラインナップが未配置か非表示です。ゲーム内のプロフィール編集で設定してください @@ -1248,10 +1251,10 @@ 育成計画の追加に失敗しました - 完了した:追加/更新:{0}、スキップ{1} + 完了しました:追加/更新:{0}、スキップ{1} - 一部完了した:追加/更新:{0}、スキップ{1} + 操作の一部に失敗しました:追加/更新:{0}、スキップ{1} 選択中の育成計画に正常に追加されました @@ -1260,7 +1263,7 @@ 育成計画で新規作成及びセットしてから続けてください - 追加完了 + 正常に追加されました。 育成計画は同じ名前を使えません。他の名前を使用してください。 @@ -1296,7 +1299,7 @@ 指定された位置に保存しました - エクスポート成功しました + エクスポートに成功しました 書き込み処理でエラーが発生しました @@ -1317,7 +1320,7 @@ UIGF バージョンが古いため、インポートできません - インポートデータにサポートされないアイテムが含まれている + インポートデータにサポートされていないアイテムが含まれています インポート失敗 @@ -1341,7 +1344,7 @@ {0} を削除してよろしいでしょうか? - 胡桃クラウドから祈願履歴を同期します + 胡桃クラウドで祈願履歴を同期します 胡桃クラウドにアップロード中 @@ -1359,7 +1362,7 @@ アセットをダウンロード中、しばらくお待ちください - メールが正しい形式ではありません + メールアドレスが正しい形式ではありません クリップボードのテキスト形式が正しくありません @@ -1374,7 +1377,7 @@ インポート失敗 - サーバー切り替えできませんでした + サーバーの切り替えができませんでした ゲーム設定ファイル {0} の読み込みに失敗しました。ファイルが無いか、権限が不足している可能性があります。 @@ -1389,7 +1392,7 @@ アカウントの切り替えができませんでした - 完了した + 操作は完了しました 削除に失敗しました。ディレクトリのアクセス許可がありません。「管理者として実行」で再度試してください @@ -1404,13 +1407,16 @@ コピーしました - 创建桌面快捷方式失败 + デスクトップへのショートカット作成に失敗しました - 无感验证复合 Url 配置成功 + 非探知認証複合URLの構成に成功しました - データディレクトリセット完了、再起動して適用する + データディレクトリのリセットが完了しました、再起動して変更を適用します + + + ゲームパスの保存に失敗しました ユーザー [{0}] を正常に追加しました @@ -1425,7 +1431,7 @@ このCookieが無効のため、操作できません - ユーザー [{0}] は削除されました + ユーザー [{0}] を削除しました ユーザー [{0}] の Cookie が更新されました。 @@ -1473,7 +1479,7 @@ アチーブメント、キャプション、バージョンまたは番号で検索 - 优先未完成 + 未達成順にソート イベント @@ -1494,7 +1500,7 @@ 会心スコア - 旅人の仲間の情報まだ入手していない + 旅人の仲間の情報がまだ無いぞ! 画像をエクスポート @@ -1515,13 +1521,13 @@ キャラクターラインナップの情報を同期 - MiYouShe育成ツールから同期 + MiHoYo BBS育成ツールから同期 キャラクターの天賦情報を同期 - MiYouSheから所持キャラを同期する + MiHoYo BBSから所持キャラを同期 キャラ天賦以外の情報を概ね同期 @@ -1530,7 +1536,7 @@ 更新日時 - 評価する + 評価スコア 強化後付与のサブOP @@ -1548,10 +1554,10 @@ 続けるには、まず「育成計画」を立てよう - 稍后可以前往其他页面添加养成计划项 + 育成計画の項目は後から他のページでも追加できます。 - 添加我的角色与武器到养成计划 + マイ キャラクターと武器を育成計画に追加します 育成素材 @@ -1611,16 +1617,16 @@ カードを削除 - 今週の消費半減回数は消化済 + 今週の消費半減は消化済 自動更新 - 指定間隔でリアルタイムノートの更新を設定する + 指定間隔でリアルタイムノートを更新します - 这些选项仅允许在非管理员模式下更改 + これらの設定は管理者モードでない時のみ変更できます。 更新 @@ -1632,7 +1638,7 @@ おやすみモード - 验证当前用户与角色 + 現在のユーザーとUIDを確認する すべて更新 @@ -1656,16 +1662,16 @@ このUidのクラウドバックアップを削除する - デベロッパーアカウントは利用期限ありません + デベロッパーアカウントは利用期限がありません - 胡桃クラウドのサービス有効期限が切れました。 + 胡桃クラウドのサービス期限が切れました。 このUidのクラウドバックアップをダウンロードする - 每期深渊首次上传可免费获得 3 天时长 + 各シーズンの深境螺旋の記録を初めてアップロードすると3日間のフリーライセンスが付与されます。 螺旋の記録をアップロード @@ -1704,13 +1710,13 @@ 現在のユーザーのCookieで祈願履歴を表示する - キャッシュの再読み込み + ゲーム内ブラウザキャッシュで更新 ゲーム内ブラウザのキャッシュで祈願履歴を更新 - このアーカイブを削除する + 現在のアーカイブを削除する キャラクター @@ -1734,13 +1740,13 @@ 胡桃を {0} 回起動しました - テイワットへようこそ + テイワットへようこそ! あなたの言う通りだが、しかし「胡桃」はDGP Studioが開発した... - ねぇ、旅人、今日はテイワットでの{0} 日目の冒険だよ + なあ、旅人、今日はテイワットで{0} 日目の冒険だぞ! 設定 @@ -1821,7 +1827,7 @@ パスワードを入力してください - 最低8文字以上 + 8文字以上必要です アカウント作成 @@ -1845,10 +1851,10 @@ 上級者向け設定 - 将窗口创建为弹出窗口,不带框架 + 枠の無いポップアップウィンドウとして作成します。 - フレームレス + ボーダーレス ゲーム内ブラウザには対応していません。ウィンドウの切り替え操作などによりゲームが強制終了する可能性があります。 @@ -1890,7 +1896,7 @@ モニター - 同时运行多个游戏客户端 + 複数のゲームプロセスを同時に実行します。 マルチクライアント @@ -1914,7 +1920,7 @@ このアカウントは UID と紐付けしていません - 绑定当前用户的角色 + 現在のユーザのUIDを連携する ゲーム内でアカウントを切り替えたり、インターネット環境を変更した場合は再検出が必要です。 @@ -1938,7 +1944,7 @@ サーバー - 版本更新前需要提前转换至与启动器匹配的服务器 + アップデートをする前に、ランチャーと同じサーバーに変更する必要があります。 ゲーム内のグラフィック設定で垂直同期を無効にしてください。フレームレートを上げるにはもっと高性能のグラフィックボードが必要となります。 @@ -1953,13 +1959,13 @@ 有効 - HoYo Lab UIDを入力してください + HoYoLab UIDを入力してください ログインしました - Hoyoverse通行証をログイン + MiHoYo BBS通行証でログイン スクリーンショットフォルダを開く @@ -1983,7 +1989,7 @@ テーマ - イメージキャッシュはここに格納 + イメージキャッシュはここに格納されます イメージキャッシュフォルダを開く @@ -1995,7 +2001,7 @@ 新規作成 - デスクトップで常に「管理者として実行」のエイリアスを作成する + 管理者として実行できるショートカットを作成します ショートカットを作成する @@ -2061,7 +2067,7 @@ 構成 - 配置当请求触发人机验证时使用的验证接口 + ボット確認に使用される認証APIの設定 Geetest認証サービスの設定 @@ -2100,7 +2106,7 @@ 胡桃アカウント - 原神およびSnap Hutaoの利用規約を全て熟読し、その後で『ゲームランチャー - 上級者向け設定』を有効にします。 + 原神およびSnap Hutaoの利用規約を全て熟読し、その後に『ゲームランチャー - 上級者向け設定』を有効にします。 上級者向け設定を有効にする @@ -2112,7 +2118,7 @@ リセット - 全ての画像リソースを削除し、すぐに再度ダウンロードする + 全ての画像リソースを削除し、次の起動時に再度ダウンロードします 画像リソースをリセット @@ -2292,13 +2298,13 @@ 編成 - 攻击地脉镇石 + 地脈石へ攻撃 データを再読み込み - MiYouSheから深境螺旋の記録を同期 + HoYoLABから深境螺旋の記録を同期 出撃回数 @@ -2388,16 +2394,16 @@ 武器一覧 - (?:〓活动时间〓|〓任务开放时间〓).*?\d\.\d版本更新(?:完成|)后永久开放 + (?:〓イベント期間〓|〓任務開始時間〓).*?\d\.\dバージョンアップ(?:完了|)後常設オープン - 〓活动时间〓.*?\d\.\d版本期间持续开放 + 〓イベント期間〓.*?\d\.\d当バージョン期間オープン - (?:〓活动时间〓|祈愿时间|【上架时间】).*?(\d\.\d版本更新后).*?~.*?<t class="t_(?:gl|lc)".*?>(.*?)</t> + (?:〓イベント期間〓|祈願期間|【開始日時】).*?(\d\.\dバージョンアップ完了後).*?~.*?<t class="t_(?:gl|lc)".*?>(.*?)</t> - 〓更新时间〓.+?<t class=\"t_(?:gl|lc)\".*?>(.*?)</t> + 〓更新日時〓.+?<t class=\"t_(?:gl|lc)\".*?>(.*?)</t> Ver.\d\.\d 更新内容 @@ -2424,16 +2430,16 @@ すべての依頼を完了していません - 「デイリー依頼」の報酬はまだ受け取っていません + デイリー依頼の報酬はまだ受け取っていません - 「デイリー依頼」の報酬は受け取り済みです + デイリー依頼の報酬は受け取り済みです - 上限到達まで{0}{1:HH:mm} + 上限到達まで{0} {1:HH:mm} - 洞天形態未開放 + 塵歌壺が未解放です 今日 @@ -2451,7 +2457,7 @@ 天然樹脂は完全に回復しました - {0} {1:HH:mm} 後ですべて回復 + {0} {1:HH:mm} 後にすべて回復 後に再び使用することができます @@ -2469,7 +2475,7 @@ 未獲得 - 参量物質変化器未獲得 + 参量物質変化器を持っていません クールタイム中 @@ -2484,7 +2490,7 @@ {0} 秒 - 認証に失敗しました。「MiYouShe - 戦績ツール - リアルタイムノート」で確認し、認証を行ってください + 認証に失敗しました。「MiHoYo BBS - 戦績ツール - リアルタイムノート」で確認し、認証を行ってください UIDは正しくありません diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ko.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ko.resx index cb23e195..51fffaf0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ko.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ko.resx @@ -959,6 +959,9 @@ 统计 + + 当前 WebView2 版本不支持管理配置,继续使用可能会导致异常,请尽快升级 + 육성 계획 @@ -1412,6 +1415,9 @@ 데이터 경로를 설정했습니다. 변경 사항을 적용하기 위해 재시작합니다 + + 保存游戏路径失败 + 사용자 [{0}]가 정상적으로 추가되었습니다 diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index 1ea07b40..4537ef07 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -959,6 +959,9 @@ 统计 + + 当前 WebView2 版本不支持管理配置,继续使用可能会导致异常,请尽快升级 + 养成计划 @@ -1412,6 +1415,9 @@ 设置数据目录成功,重启以应用更改 + + 保存游戏路径失败 + 用户 [{0}] 添加成功 diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.zh-Hant.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.zh-Hant.resx index 4fd2f008..2475fe15 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.zh-Hant.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.zh-Hant.resx @@ -959,6 +959,9 @@ 統計 + + 当前 WebView2 版本不支持管理配置,继续使用可能会导致异常,请尽快升级 + 養成計劃 @@ -1412,6 +1415,9 @@ 設置數據目錄成功,重啓以應用更改 + + 保存游戏路径失败 + 用戶 [{0}] 新增成功 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryHelper.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryHelper.cs index b8d79982..0c5ab37e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryHelper.cs @@ -105,8 +105,12 @@ internal static class SummaryHelper /// 分数 public static float GetPercentSubAffixScore(in ReliquarySubAffixId appendId) { - // 圣遗物相同类型副词条强化档位一共为 4 档 - // 恰好为 70% 80% 90% 100% + // 圣遗物相同类型副词条强化档位一共为 4/3/2 档 + // 五星 为 70% 80% 90% 100% + // 四星 为 70% 80% 90% 100% + // 三星 为 70% 80% 90% 100% + // 二星 为 70% 85% 100% + // 二星 为 80% 100% // 通过计算与最大属性的 Id 差来决定当前副词条的强化档位 uint maxId = GetAffixMaxId(appendId); uint delta = maxId - appendId; @@ -119,7 +123,11 @@ internal static class SummaryHelper (5 or 4 or 3, 3) => 70F, (2, 0) => 100F, - (2, 1) => 80F, + (2, 1) => 85F, + (2, 2) => 70F, + + (1, 0) => 100F, + (1, 1) => 80F, _ => throw Must.NeverHappen($"Unexpected AppendId: {appendId.Value} Delta: {delta}"), }; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFImportService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFImportService.cs index 806ed3e4..45186002 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFImportService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UIGFImportService.cs @@ -35,10 +35,28 @@ internal sealed partial class UIGFImportService : IUIGFImportService // v2.3+ support any locale // v2.2 only support matched locale // v2.1 only support CHS - if (version is UIGFVersion.Major2Minor2OrLower && !metadataOptions.IsCurrentLocale(uigf.Info.Language)) + if (version is UIGFVersion.Major2Minor2OrLower) { - string message = SH.ServiceGachaUIGFImportLanguageNotMatch.Format(uigf.Info.Language, metadataOptions.LanguageCode); - ThrowHelper.InvalidOperation(message, null); + if (!metadataOptions.IsCurrentLocale(uigf.Info.Language)) + { + string message = SH.ServiceGachaUIGFImportLanguageNotMatch.Format(uigf.Info.Language, metadataOptions.LanguageCode); + ThrowHelper.InvalidOperation(message); + } + + if (!uigf.IsMajor2Minor2OrLowerListValid(out long id)) + { + string message = SH.ServiceGachaLogUIGFImportItemInvalidFormat.Format(id); + ThrowHelper.InvalidOperation(message); + } + } + + if (version is UIGFVersion.Major2Minor3OrHigher) + { + if (!uigf.IsMajor2Minor3OrHigherListValid(out long id)) + { + string message = SH.ServiceGachaLogUIGFImportItemInvalidFormat.Format(id); + ThrowHelper.InvalidOperation(message); + } } GachaArchiveOperation.GetOrAdd(gachaLogDbService, taskContext, uigf.Info.Uid, archives, out GachaArchive? archive); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/ChannelOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/ChannelOptions.cs index e8255f7f..8ebb4027 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/ChannelOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/ChannelOptions.cs @@ -49,6 +49,13 @@ internal readonly struct ChannelOptions ConfigFilePath = configFilePath; } + public ChannelOptions(ChannelType channel, SubChannelType subChannel, bool isOversea) + { + Channel = channel; + SubChannel = subChannel; + IsOversea = isOversea; + } + /// /// 配置文件未找到 /// @@ -65,4 +72,9 @@ internal readonly struct ChannelOptions { return $"[ChannelType:{Channel}] [SubChannel:{SubChannel}] [IsOversea: {IsOversea}]"; } + + public override int GetHashCode() + { + return HashCode.Combine(Channel, SubChannel, IsOversea); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileOperationException.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileOperationException.cs index 59d3a000..d15a618e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileOperationException.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileOperationException.cs @@ -14,7 +14,7 @@ internal sealed class GameFileOperationException : Exception /// /// 消息 /// 内部错误 - public GameFileOperationException(string message, Exception innerException) + public GameFileOperationException(string message, Exception? innerException) : base(SH.ServiceGameFileOperationExceptionMessage.Format(message), innerException) { } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs index 66317d61..377c7a34 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs @@ -281,7 +281,7 @@ internal sealed partial class GameService : IGameService } /// - public async ValueTask DetectGameAccountAsync() + public async ValueTask DetectGameAccountAsync() { ArgumentNullException.ThrowIfNull(gameAccounts); @@ -318,7 +318,11 @@ internal sealed partial class GameService : IGameService gameAccounts.Add(account); } } + + return account; } + + return default; } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs index 6afc58eb..19ad9808 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs @@ -26,11 +26,7 @@ internal interface IGameService /// uid void AttachGameAccountToUid(GameAccount gameAccount, string uid); - /// - /// 检测并尝试添加游戏内账户 - /// - /// 任务 - ValueTask DetectGameAccountAsync(); + ValueTask DetectGameAccountAsync(); /// /// 异步获取游戏路径 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/IgnoredInvalidChannelOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/IgnoredInvalidChannelOptions.cs new file mode 100644 index 00000000..eac6d975 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/IgnoredInvalidChannelOptions.cs @@ -0,0 +1,20 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Intrinsic; +using System.Collections.Immutable; + +namespace Snap.Hutao.Service.Game; + +internal static class IgnoredInvalidChannelOptions +{ + private static readonly ImmutableHashSet InvalidOptions = new HashSet() + { + new(ChannelType.Bili, SubChannelType.Official, true), + }.ToImmutableHashSet(); + + public static bool Contains(in ChannelOptions options) + { + return InvalidOptions.Contains(options); + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConvertException.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConvertException.cs index 843789e9..709824a3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConvertException.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConvertException.cs @@ -10,7 +10,7 @@ namespace Snap.Hutao.Service.Game.Package; internal sealed class PackageConvertException : Exception { /// - public PackageConvertException(string message, Exception innerException) + public PackageConvertException(string message, Exception? innerException) : base(message, innerException) { } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/ProcessInterop.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/ProcessInterop.cs index 5b7e8122..e4aca5c7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/ProcessInterop.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/ProcessInterop.cs @@ -26,7 +26,10 @@ internal static class ProcessInterop /// 初始化后的游戏进程 public static Process InitializeGameProcess(LaunchOptions options, string gamePath) { + Must.Argument(options.IsBorderless ^ options.IsExclusive, "无边框与全屏选项无法同时生效"); + // https://docs.unity.cn/cn/current/Manual/PlayerCommandLineArguments.html + // https://docs.unity3d.com/2017.4/Documentation/Manual/CommandLineArguments.html string commandLine = new CommandLineBuilder() .AppendIf("-popupwindow", options.IsBorderless) .AppendIf("-window-mode", options.IsExclusive, "exclusive") diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs index 89de9538..0da882d0 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs @@ -3,6 +3,7 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Markup; using Microsoft.Web.WebView2.Core; using Snap.Hutao.Control.Theme; using Snap.Hutao.Web.Bridge; @@ -131,9 +132,10 @@ internal sealed partial class AnnouncementContentViewer : UserControl private void OnUnloaded(object sender, RoutedEventArgs e) { - WebView.CoreWebView2.WebMessageReceived -= webMessageReceivedHandler; - Loaded -= loadEventHandler; - Unloaded -= unloadEventHandler; + if (WebView is { CoreWebView2: CoreWebView2 coreWebView2 }) + { + coreWebView2.WebMessageReceived -= webMessageReceivedHandler; + } } private async ValueTask LoadAnnouncementAsync() @@ -141,7 +143,7 @@ internal sealed partial class AnnouncementContentViewer : UserControl try { await WebView.EnsureCoreWebView2Async(); - WebView.CoreWebView2.DisableDevToolsOnReleaseBuild(); + WebView.CoreWebView2.DisableDevToolsForReleaseBuild(); WebView.CoreWebView2.WebMessageReceived += webMessageReceivedHandler; await WebView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(MihoyoSDKDefinition); diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/WebViewer.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/WebViewer.xaml.cs index e9326fa2..3029486b 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/WebViewer.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/WebViewer.xaml.cs @@ -2,16 +2,16 @@ // Licensed under the MIT license. using CommunityToolkit.Mvvm.Messaging; -using Microsoft.UI.Content; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Hosting; using Microsoft.Web.WebView2.Core; using Snap.Hutao.Message; using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.User; using Snap.Hutao.ViewModel.User; using Snap.Hutao.Web.Bridge; +using WinRT; +using WinRT.Interop; namespace Snap.Hutao.View.Control; @@ -19,22 +19,22 @@ namespace Snap.Hutao.View.Control; internal partial class WebViewer : UserControl, IRecipient { private readonly IServiceProvider serviceProvider; + private readonly IInfoBarService infoBarService; private readonly RoutedEventHandler loadEventHandler; - private readonly RoutedEventHandler unloadEventHandler; private MiHoYoJSInterface? jsInterface; + private bool isInitializingOrInitialized; public WebViewer() { InitializeComponent(); serviceProvider = Ioc.Default; + infoBarService = serviceProvider.GetRequiredService(); serviceProvider.GetRequiredService().Register(this); loadEventHandler = OnLoaded; - unloadEventHandler = OnUnloaded; Loaded += loadEventHandler; - Unloaded += unloadEventHandler; } public void Receive(UserChangedMessage message) @@ -48,16 +48,17 @@ internal partial class WebViewer : UserControl, IRecipient InitializeAsync().SafeForget(); } - private void OnUnloaded(object sender, RoutedEventArgs e) - { - Loaded -= loadEventHandler; - Unloaded -= unloadEventHandler; - } - private async ValueTask InitializeAsync() { + if (isInitializingOrInitialized) + { + return; + } + + isInitializingOrInitialized = true; + await WebView.EnsureCoreWebView2Async(); - WebView.CoreWebView2.DisableDevToolsOnReleaseBuild(); + WebView.CoreWebView2.DisableDevToolsForReleaseBuild(); RefreshWebview2Content(); } @@ -86,13 +87,22 @@ internal partial class WebViewer : UserControl, IRecipient string source = SourceProvider.GetSource(userAndUid); if (!string.IsNullOrEmpty(source)) { - await coreWebView2.Profile.ClearBrowsingDataAsync(); + try + { + await coreWebView2.Profile.ClearBrowsingDataAsync(); + } + catch (InvalidCastException) + { + infoBarService.Warning(SH.ViewControlWebViewerCoreWebView2ProfileQueryInterfaceFailed); + await coreWebView2.DeleteCookiesAsync(userAndUid.IsOversea).ConfigureAwait(true); + } CoreWebView2Navigator navigator = new(coreWebView2); await navigator.NavigateAsync("about:blank").ConfigureAwait(true); - coreWebView2.SetCookie(user.CookieToken, user.LToken, user.SToken); - _ = userAndUid.User.IsOversea ? coreWebView2.SetMobileOverseaUserAgent() : coreWebView2.SetMobileUserAgent(); + coreWebView2 + .SetCookie(user.CookieToken, user.LToken, userAndUid.IsOversea) + .SetMobileUserAgent(userAndUid.IsOversea); jsInterface?.Detach(); jsInterface = SourceProvider.CreateJsInterface(serviceProvider, coreWebView2, userAndUid); @@ -101,7 +111,7 @@ internal partial class WebViewer : UserControl, IRecipient } else { - serviceProvider.GetRequiredService().Warning(SH.MustSelectUserAndUid); + infoBarService.Warning(SH.MustSelectUserAndUid); } } } diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/ISupportLoginByWebView.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/ISupportLoginByWebView.cs index 34046301..203ca5a1 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/ISupportLoginByWebView.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/ISupportLoginByWebView.cs @@ -18,7 +18,7 @@ internal interface ISupportLoginByWebView { await webView2.EnsureCoreWebView2Async(); await webView2.CoreWebView2.DeleteCookiesAsync(cookie).ConfigureAwait(true); - webView2.CoreWebView2.DisableDevToolsOnReleaseBuild(); + webView2.CoreWebView2.DisableDevToolsForReleaseBuild(); webView2.CoreWebView2.DisableAutoCompletion(); webView2.CoreWebView2.Navigate(navigate); @@ -33,7 +33,7 @@ internal interface ISupportLoginByWebView { (UserOptionResult result, string nickname) = await serviceProvider .GetRequiredService() - .ProcessInputCookieAsync(cookie, false) + .ProcessInputCookieAsync(cookie, isOversea) .ConfigureAwait(false); serviceProvider.GetRequiredService().GoBack(); diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LoginHoyoverseUserPage.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/LoginHoyoverseUserPage.xaml.cs index fd2e3452..0ec71bf1 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/LoginHoyoverseUserPage.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LoginHoyoverseUserPage.xaml.cs @@ -148,4 +148,4 @@ internal sealed partial class LoginHoyoverseUserPage : Microsoft.UI.Xaml.Control [JsonPropertyName("weblogin_token")] public string WebLoginToken { get; set; } = default!; } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml index 23a69bd9..1472301e 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml @@ -22,10 +22,13 @@ - + + + + @@ -74,29 +77,31 @@ Margin="0,4" Text="Copyright © 2022 - 2023 DGP Studio. All Rights Reserved." TextWrapping="Wrap"/> + - + HeaderIcon="{shcm:FontIcon Glyph=}" + IsExpanded="True"> + + + + + - - + + HeaderIcon="{shcm:FontIcon Glyph=}" + IsExpanded="True"> ().ConfigureAwait(false); - ContentDialogHideToken hideToken = await dialog.BlockAsync(taskContext).ConfigureAwait(false); + + ContentDialogHideToken hideToken; + try + { + hideToken = await dialog.BlockAsync(taskContext).ConfigureAwait(false); + } + catch (COMException ex) + { + if (ex.HResult == unchecked((int)0x80000019)) + { + infoBarService.Error(ex); + return; + } + + throw; + } + IProgress progress = taskContext.CreateProgressForMainThread(dialog.OnReport); bool authkeyValid; @@ -221,7 +223,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel } else { - await contentDialogFactory.CreateForConfirmAsync(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage).ConfigureAwait(false); + infoBarService.Error(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage); } } } @@ -349,50 +351,42 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel private async ValueTask TryImportUIGFInternalAsync(UIGF uigf) { - if (uigf.IsCurrentVersionSupported(out UIGFVersion version)) - { - GachaLogImportDialog importDialog = await contentDialogFactory.CreateInstanceAsync(uigf).ConfigureAwait(false); - if (await importDialog.GetShouldImportAsync().ConfigureAwait(false)) - { - if (CanImport(version, uigf, out long itemId)) - { - await taskContext.SwitchToMainThreadAsync(); - ContentDialog dialog = await contentDialogFactory.CreateForIndeterminateProgressAsync(SH.ViewModelGachaLogImportProgress).ConfigureAwait(true); - try - { - using (await dialog.BlockAsync(taskContext).ConfigureAwait(false)) - { - await gachaLogService.ImportFromUIGFAsync(uigf).ConfigureAwait(false); - } - } - catch (InvalidOperationException ex) - { - // 导入物品中存在无效的项 - infoBarService.Error(ex); - return false; - } - catch (FormatException ex) - { - infoBarService.Error(ex); - return false; - } - - infoBarService.Success(SH.ViewModelGachaLogImportComplete); - await taskContext.SwitchToMainThreadAsync(); - await SetSelectedArchiveAndUpdateStatisticsAsync(gachaLogService.CurrentArchive, true).ConfigureAwait(false); - return true; - } - else - { - infoBarService.Warning(SH.ViewModelGachaLogImportWarningTitle, SH.ServiceGachaLogUIGFImportItemInvalidFormat.Format(itemId)); - } - } - } - else + if (!uigf.IsCurrentVersionSupported(out UIGFVersion version)) { infoBarService.Warning(SH.ViewModelGachaLogImportWarningTitle, SH.ViewModelGachaLogImportWarningMessage); + return false; } - return false; + GachaLogImportDialog importDialog = await contentDialogFactory.CreateInstanceAsync(uigf).ConfigureAwait(false); + if (!await importDialog.GetShouldImportAsync().ConfigureAwait(false)) + { + return false; + } + + await taskContext.SwitchToMainThreadAsync(); + ContentDialog dialog = await contentDialogFactory.CreateForIndeterminateProgressAsync(SH.ViewModelGachaLogImportProgress).ConfigureAwait(true); + try + { + using (await dialog.BlockAsync(taskContext).ConfigureAwait(false)) + { + await gachaLogService.ImportFromUIGFAsync(uigf).ConfigureAwait(false); + } + } + catch (InvalidOperationException ex) + { + // 语言不匹配/导入物品中存在无效的项 + infoBarService.Error(ex); + return false; + } + catch (FormatException ex) + { + infoBarService.Error(ex); + return false; + } + + infoBarService.Success(SH.ViewModelGachaLogImportComplete); + await taskContext.SwitchToMainThreadAsync(); + await SetSelectedArchiveAndUpdateStatisticsAsync(gachaLogService.CurrentArchive, true).ConfigureAwait(false); + return true; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs index 4bc472b2..4badbf36 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs @@ -34,12 +34,12 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel public const string DesiredUid = nameof(DesiredUid); private readonly IContentDialogFactory contentDialogFactory; + private readonly LaunchStatusOptions launchStatusOptions; private readonly INavigationService navigationService; private readonly IInfoBarService infoBarService; - private readonly LaunchOptions launchOptions; - private readonly LaunchStatusOptions launchStatusOptions; - private readonly RuntimeOptions hutaoOptions; private readonly ResourceClient resourceClient; + private readonly LaunchOptions launchOptions; + private readonly RuntimeOptions hutaoOptions; private readonly IUserService userService; private readonly ITaskContext taskContext; private readonly IGameService gameService; @@ -125,8 +125,11 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel } catch (InvalidOperationException) { - // 后台收集 - throw new NotSupportedException($"不支持的 MultiChannel: {options}"); + if (!IgnoredInvalidChannelOptions.Contains(options)) + { + // 后台收集 + throw new NotSupportedException($"不支持的 MultiChannel: {options}"); + } } } else @@ -233,7 +236,15 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel { try { - await gameService.DetectGameAccountAsync().ConfigureAwait(false); + GameAccount? account = await gameService.DetectGameAccountAsync().ConfigureAwait(false); + + // If user canceled the operation, the return is null, + // and thus we should not set SelectedAccount + if (account is not null) + { + await taskContext.SwitchToMainThreadAsync(); + SelectedGameAccount = account; + } } catch (UserdataCorruptedException ex) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs index cdf42e65..f026d392 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.UI.Xaml.Controls; using Microsoft.Windows.AppLifecycle; @@ -126,7 +127,15 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel if (isOk) { await taskContext.SwitchToMainThreadAsync(); - Options.GamePath = path; + try + { + Options.GamePath = path; + } + catch (SqliteException ex) + { + // 文件夹权限不足,无法写入数据库 + infoBarService.Error(ex, SH.ViewModelSettingSetGamePathDatabaseFailedTitle); + } } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssView.cs index 5b65af15..d898d8f6 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssView.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssView.cs @@ -18,11 +18,6 @@ internal sealed class SpiralAbyssView : IEntityOnly, { private readonly SpiralAbyssEntry? entity; - /// - /// 构造一个新的深渊视图 - /// - /// 实体 - /// Id角色映射 private SpiralAbyssView(SpiralAbyssEntry entity, SpiralAbyssMetadataContext context) : this(context.IdScheduleMap[entity.ScheduleId], context) { @@ -32,12 +27,12 @@ internal sealed class SpiralAbyssView : IEntityOnly, TotalBattleTimes = spiralAbyss.TotalBattleTimes; TotalStar = spiralAbyss.TotalStar; MaxFloor = spiralAbyss.MaxFloor; - Reveals = spiralAbyss.RevealRank.SelectList(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])); - Defeat = spiralAbyss.DefeatRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault(); - Damage = spiralAbyss.DamageRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault(); - TakeDamage = spiralAbyss.TakeDamageRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault(); - NormalSkill = spiralAbyss.NormalSkillRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault(); - EnergySkill = spiralAbyss.EnergySkillRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault(); + Reveals = ToRankAvatars(spiralAbyss.RevealRank, context); + Defeat = ToRankAvatar(spiralAbyss.DefeatRank, context); + Damage = ToRankAvatar(spiralAbyss.DamageRank, context); + TakeDamage = ToRankAvatar(spiralAbyss.TakeDamageRank, context); + NormalSkill = ToRankAvatar(spiralAbyss.NormalSkillRank, context); + EnergySkill = ToRankAvatar(spiralAbyss.EnergySkillRank, context); Engaged = true; foreach (Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.Floor webFloor in spiralAbyss.Floors) @@ -143,4 +138,15 @@ internal sealed class SpiralAbyssView : IEntityOnly, return new(meta, context); } } + + private static List ToRankAvatars(List ranks, SpiralAbyssMetadataContext context) + { + return ranks.Where(r => r.AvatarId != 0U).Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).ToList(); + } + + private static RankAvatar? ToRankAvatar(List ranks, SpiralAbyssMetadataContext context) + { + return ranks.Where(r => r.AvatarId != 0U).Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault(); + } + } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/CoreWebView2Extension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/CoreWebView2Extension.cs index fd763312..52e972d2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/CoreWebView2Extension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/CoreWebView2Extension.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using Microsoft.Web.WebView2.Core; -using Snap.Hutao.Web.Hoyolab; using System.Diagnostics; namespace Snap.Hutao.Web.Bridge; @@ -14,7 +13,7 @@ namespace Snap.Hutao.Web.Bridge; internal static class CoreWebView2Extension { [Conditional("RELEASE")] - public static void DisableDevToolsOnReleaseBuild(this CoreWebView2 webView) + public static void DisableDevToolsForReleaseBuild(this CoreWebView2 webView) { CoreWebView2Settings settings = webView.Settings; settings.AreBrowserAcceleratorKeysEnabled = false; @@ -38,71 +37,4 @@ internal static class CoreWebView2Extension manager.DeleteCookie(item); } } - - /// - /// 设置 移动端UA - /// - /// webView2 - /// 链式调用的WebView2 - public static CoreWebView2 SetMobileUserAgent(this CoreWebView2 webView) - { - webView.Settings.UserAgent = HoyolabOptions.MobileUserAgent; - return webView; - } - - /// - /// 设置 移动端OsUA - /// - /// webView2 - /// 链式调用的WebView2 - public static CoreWebView2 SetMobileOverseaUserAgent(this CoreWebView2 webView) - { - webView.Settings.UserAgent = HoyolabOptions.MobileUserAgentOversea; - return webView; - } - - /// - /// 设置WebView2的Cookie - /// - /// webView2 - /// CookieToken - /// LToken - /// SToken - /// 是否为国际服,用于改变 cookie domain - /// 链式调用的WebView2 - public static CoreWebView2 SetCookie(this CoreWebView2 webView, Cookie? cookieToken = null, Cookie? lToken = null, Cookie? sToken = null, bool isOversea = false) - { - CoreWebView2CookieManager cookieManager = webView.CookieManager; - - if (cookieToken is not null) - { - cookieManager - .AddMihoyoCookie(Cookie.ACCOUNT_ID, cookieToken, isOversea) - .AddMihoyoCookie(Cookie.COOKIE_TOKEN, cookieToken, isOversea); - } - - if (lToken is not null) - { - cookieManager - .AddMihoyoCookie(Cookie.LTUID, lToken, isOversea) - .AddMihoyoCookie(Cookie.LTOKEN, lToken, isOversea); - } - - if (sToken is not null) - { - cookieManager - .AddMihoyoCookie(Cookie.MID, sToken, isOversea) - .AddMihoyoCookie(Cookie.STUID, sToken, isOversea) - .AddMihoyoCookie(Cookie.STOKEN, sToken, isOversea); - } - - return webView; - } - - private static CoreWebView2CookieManager AddMihoyoCookie(this CoreWebView2CookieManager manager, string name, Cookie cookie, bool isOversea = false) - { - string domain = isOversea ? ".hoyolab.com" : ".mihoyo.com"; - manager.AddOrUpdateCookie(manager.CreateCookie(name, cookie[name], domain, "/")); - return manager; - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/HoyolabCoreWebView2Extension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/HoyolabCoreWebView2Extension.cs new file mode 100644 index 00000000..7131fdcc --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/HoyolabCoreWebView2Extension.cs @@ -0,0 +1,80 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.Web.WebView2.Core; +using Snap.Hutao.Web.Hoyolab; + +namespace Snap.Hutao.Web.Bridge; + +internal static class HoyolabCoreWebView2Extension +{ + public static ValueTask DeleteCookiesAsync(this CoreWebView2 webView, bool isOversea) + { + return webView.DeleteCookiesAsync(isOversea ? ".hoyolab.com" : ".mihoyo.com"); + } + + public static CoreWebView2 SetMobileUserAgent(this CoreWebView2 webView, bool isOversea) + { + return isOversea + ? webView.SetMobileUserAgentOversea() + : webView.SetMobileUserAgentChinese(); + } + + /// + /// 设置 移动端UA + /// + /// webView2 + /// 链式调用的WebView2 + public static CoreWebView2 SetMobileUserAgentChinese(this CoreWebView2 webView) + { + webView.Settings.UserAgent = HoyolabOptions.MobileUserAgent; + return webView; + } + + /// + /// 设置 移动端OsUA + /// + /// webView2 + /// 链式调用的WebView2 + public static CoreWebView2 SetMobileUserAgentOversea(this CoreWebView2 webView) + { + webView.Settings.UserAgent = HoyolabOptions.MobileUserAgentOversea; + return webView; + } + + /// + /// 设置WebView2的Cookie + /// + /// webView2 + /// CookieToken + /// LToken + /// 是否为国际服,用于改变 cookie domain + /// 链式调用的WebView2 + public static CoreWebView2 SetCookie(this CoreWebView2 webView, Cookie? cookieToken = null, Cookie? lToken = null, bool isOversea = false) + { + CoreWebView2CookieManager cookieManager = webView.CookieManager; + + if (cookieToken is not null) + { + cookieManager + .AddMihoyoCookie(Cookie.ACCOUNT_ID, cookieToken, isOversea) + .AddMihoyoCookie(Cookie.COOKIE_TOKEN, cookieToken, isOversea); + } + + if (lToken is not null) + { + cookieManager + .AddMihoyoCookie(Cookie.LTUID, lToken, isOversea) + .AddMihoyoCookie(Cookie.LTOKEN, lToken, isOversea); + } + + return webView; + } + + private static CoreWebView2CookieManager AddMihoyoCookie(this CoreWebView2CookieManager manager, string name, Cookie cookie, bool isOversea = false) + { + string domain = isOversea ? ".hoyolab.com" : ".mihoyo.com"; + manager.AddOrUpdateCookie(manager.CreateCookie(name, cookie[name], domain, "/")); + return manager; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs index fb4911ec..a9067aa1 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs @@ -39,19 +39,20 @@ internal class MiHoYoJSInterface document.querySelector('body').appendChild(st); """; + private readonly SemaphoreSlim webMessageSemaphore = new(1); private readonly Guid interfaceId = Guid.NewGuid(); - private readonly IServiceProvider serviceProvider; private readonly UserAndUid userAndUid; - private CoreWebView2 webView; + private readonly IServiceProvider serviceProvider; private readonly ITaskContext taskContext; private readonly ILogger logger; - private readonly SemaphoreSlim webMessageSemaphore = new(1); private readonly TypedEventHandler webMessageReceivedEventHandler; private readonly TypedEventHandler domContentLoadedEventHandler; private readonly TypedEventHandler navigationStartingEventHandler; + private CoreWebView2 webView; + public MiHoYoJSInterface(CoreWebView2 webView, UserAndUid userAndUid) { // 由于Webview2 的作用域特殊性,我们在此处直接使用根服务 @@ -374,14 +375,18 @@ internal class MiHoYoJSInterface await taskContext.SwitchToMainThreadAsync(); try { - return await webView.ExecuteScriptAsync(js); + if (webView is not null) + { + return await webView.ExecuteScriptAsync(js); + } } catch (COMException) { // COMException (0x8007139F): 组或资源的状态不是执行请求操作的正确状态。 (0x8007139F) // webview is disposing or disposed - return string.Empty; } + + return string.Empty; } private async void OnWebMessageReceived(CoreWebView2 webView2, CoreWebView2WebMessageReceivedEventArgs args) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInClientOversea.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInClientOversea.cs index af23c16c..9e5c30be 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInClientOversea.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/BbsSignReward/SignInClientOversea.cs @@ -57,7 +57,7 @@ internal sealed partial class SignInClientOversea : ISignInClient HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create() .SetRequestUri(ApiOsEndpoints.SignInRewardSign) .SetUserCookie(userAndUid, CookieType.CookieToken) - .PostJson(new SignInData(userAndUid.Uid, false)); + .PostJson(new SignInData(userAndUid.Uid, true)); Response? resp = await builder .TryCatchSendAsync>(httpClient, logger, token) @@ -73,7 +73,7 @@ internal sealed partial class SignInClientOversea : ISignInClient .SetRequestUri(ApiOsEndpoints.SignInRewardSign) .SetUserCookie(userAndUid, CookieType.CookieToken) .SetXrpcChallenge(challenge, validate) - .PostJson(new SignInData(userAndUid.Uid, false)); + .PostJson(new SignInData(userAndUid.Uid, true)); resp = await verifiedBuilder .TryCatchSendAsync>(httpClient, logger, token) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/HomaGeetestClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/HomaGeetestClient.cs index 5ea1550b..a36802d3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/HomaGeetestClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/HomaGeetestClient.cs @@ -22,13 +22,23 @@ internal sealed partial class HomaGeetestClient { string template = appOptions.GeetestCustomCompositeUrl; - if (string.IsNullOrEmpty(template)) + string url; + try + { + url = template.Format(gt, challenge); + } + catch (FormatException) + { + return GeetestResponse.InternalFailure; + } + + if (string.IsNullOrEmpty(template) || !Uri.TryCreate(url, UriKind.Absolute, out Uri? uri)) { return GeetestResponse.InternalFailure; } HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create() - .SetRequestUri(template.Format(gt, challenge)) + .SetRequestUri(uri) .Get(); GeetestResponse? resp = await builder diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Request/Builder/HttpContentSerializer.cs b/src/Snap.Hutao/Snap.Hutao/Web/Request/Builder/HttpContentSerializer.cs index 0b03278d..ee1487cc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Request/Builder/HttpContentSerializer.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Request/Builder/HttpContentSerializer.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Web.Request.Builder.Abstraction; using System.Net.Http; using System.Text; @@ -66,7 +67,7 @@ internal abstract class HttpContentSerializer : IHttpContentSerializer, IHttpCon The content to be serialized does not match the specified type. Expected an instance of the class "{contentType.FullName}", but got "{actualContentType.FullName}". """; - throw new ArgumentException(message, nameof(contentType)); + ThrowHelper.Argument(message, nameof(contentType)); } // The contentType is optional. In that case, try to get the type on our own. diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Request/Builder/HttpRequestMessageBuilderExtension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Request/Builder/HttpRequestMessageBuilderExtension.cs index cd84fae8..263d9825 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Request/Builder/HttpRequestMessageBuilderExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Request/Builder/HttpRequestMessageBuilderExtension.cs @@ -33,6 +33,11 @@ internal static class HttpRequestMessageBuilderExtension logger.LogWarning(ex, RequestErrorMessage); return default; } + catch (HttpContentSerializationException ex) + { + logger.LogWarning(ex, RequestErrorMessage); + return default; + } catch (SocketException ex) { logger.LogWarning(ex, RequestErrorMessage);