From 5e734ac689835803f2806090779f58767b008e22 Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Tue, 7 Nov 2023 15:37:53 +0800 Subject: [PATCH 01/15] impl #961 --- .../Snap.Hutao/Core/LifeCycle/Activation.cs | 4 +- .../CurrentWindowReferenceExtension.cs | 10 + .../Core/LifeCycle/ICurrentWindowReference.cs | 3 + .../Snap.Hutao/Core/Setting/SettingKeys.cs | 2 + .../Windowing/HotKey/HotKeyCombination.cs | 304 ++++++++++++++++++ .../Core/Windowing/HotKey/HotKeyController.cs | 76 ++--- .../Core/Windowing/HotKey/HotKeyOptions.cs | 27 +- .../Core/Windowing/HotKey/HotKeyParameter.cs | 36 ++- .../Windowing/HotKey/IHotKeyController.cs | 6 +- .../Core/Windowing/HotKey/VirtualKeys.cs | 17 + .../Core/Windowing/WindowController.cs | 12 +- .../Core/Windowing/WindowOptions.cs | 9 +- .../Core/Windowing/WindowSubclass.cs | 6 +- .../Factory/Picker/PickerFactory.cs | 5 +- .../Snap.Hutao/Model/CollectionsNameValue.cs | 13 + src/Snap.Hutao/Snap.Hutao/Model/NameValue.cs | 2 + .../Snap.Hutao/Resource/Localization/SH.resx | 9 + .../Snap.Hutao/Service/AppOptions.cs | 7 +- .../Service/Game/Scheme/LaunchScheme.cs | 7 +- .../Service/Metadata/MetadataService.cs | 5 + .../Snap.Hutao/View/Page/SettingPage.xaml | 42 ++- src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml | 9 +- .../ViewModel/Game/LaunchGameViewModel.cs | 2 +- .../{ => Setting}/HomeCardOptions.cs | 2 +- .../ViewModel/Setting/SettingViewModel.cs | 10 +- 25 files changed, 530 insertions(+), 95 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyCombination.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/VirtualKeys.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/CollectionsNameValue.cs rename src/Snap.Hutao/Snap.Hutao/ViewModel/{ => Setting}/HomeCardOptions.cs (96%) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs index d9884b1d..186f6917 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs @@ -163,7 +163,7 @@ internal sealed partial class Activation : IActivation { await taskContext.SwitchToMainThreadAsync(); - currentWindowReference.Window = serviceProvider.GetRequiredService(); + serviceProvider.GetRequiredService(); serviceProvider .GetRequiredService() @@ -270,7 +270,7 @@ internal sealed partial class Activation : IActivation if (currentWindowReference.Window is null) { - currentWindowReference.Window = serviceProvider.GetRequiredService(); + serviceProvider.GetRequiredService(); } else { diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/CurrentWindowReferenceExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/CurrentWindowReferenceExtension.cs index b2db3d7e..47d674cf 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/CurrentWindowReferenceExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/CurrentWindowReferenceExtension.cs @@ -2,6 +2,9 @@ // Licensed under the MIT license. using Microsoft.UI.Xaml; +using Snap.Hutao.Core.Windowing; +using Windows.Win32.Foundation; +using WinRT.Interop; namespace Snap.Hutao.Core.LifeCycle; @@ -11,4 +14,11 @@ internal static class CurrentWindowReferenceExtension { return reference.Window.Content.XamlRoot; } + + public static HWND GetWindowHandle(this ICurrentWindowReference reference) + { + return reference.Window is IWindowOptionsSource optionsSource + ? optionsSource.WindowOptions.Hwnd + : (HWND)WindowNative.GetWindowHandle(reference.Window); + } } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/ICurrentWindowReference.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/ICurrentWindowReference.cs index 72a79f6d..2258c93b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/ICurrentWindowReference.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/ICurrentWindowReference.cs @@ -7,5 +7,8 @@ namespace Snap.Hutao.Core.LifeCycle; internal interface ICurrentWindowReference { + /// + /// Only set in WindowController + /// public Window Window { get; set; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs index 6873e1fc..d67cc641 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs @@ -81,4 +81,6 @@ internal static class SettingKeys public const string IsHomeCardGachaStatisticsPresented = "IsHomeCardGachaStatisticsPresented"; public const string IsHomeCardAchievementPresented = "IsHomeCardAchievementPresented"; public const string IsHomeCardDailyNotePresented = "IsHomeCardDailyNotePresented"; + + public const string HotKeyMouseClickRepeatForever = "HotKeyMouseClickRepeatForever"; } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyCombination.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyCombination.cs new file mode 100644 index 00000000..ac5947e8 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyCombination.cs @@ -0,0 +1,304 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using CommunityToolkit.Mvvm.ComponentModel; +using Snap.Hutao.Core.LifeCycle; +using Snap.Hutao.Core.Setting; +using Snap.Hutao.Model; +using System.Text; +using Windows.System; +using Windows.Win32.Foundation; +using Windows.Win32.UI.Input.KeyboardAndMouse; +using static Windows.Win32.PInvoke; + +namespace Snap.Hutao.Core.Windowing.HotKey; + +[SuppressMessage("", "SA1124")] +internal sealed class HotKeyCombination : ObservableObject +{ + private readonly ICurrentWindowReference currentWindowReference; + private readonly RuntimeOptions runtimeOptions; + + private readonly string settingKey; + private readonly int hotKeyId; + private readonly HotKeyParameter defaultHotKeyParameter; + + private bool registered; + + private bool modifierHasWindows; + private bool modifierHasControl; + private bool modifierHasShift; + private bool modifierHasAlt; + private NameValue keyNameValue; + private HOT_KEY_MODIFIERS modifiers; + private VirtualKey key; + private bool isEnabled; + + public HotKeyCombination(IServiceProvider serviceProvider, string settingKey, int hotKeyId, HOT_KEY_MODIFIERS defaultModifiers, VirtualKey defaultKey) + { + currentWindowReference = serviceProvider.GetRequiredService(); + runtimeOptions = serviceProvider.GetRequiredService(); + + this.settingKey = settingKey; + this.hotKeyId = hotKeyId; + defaultHotKeyParameter = new(defaultModifiers, defaultKey); + + // Initialize Property backing fields + { + // Retrieve from LocalSetting + isEnabled = LocalSetting.Get($"{settingKey}.IsEnabled", true); + + HotKeyParameter actual = LocalSettingGetHotKeyParameter(); + modifiers = actual.Modifiers; + InitializeModifiersComposeFields(); + key = actual.Key; + + keyNameValue = VirtualKeys.GetList().Single(v => v.Value == key); + } + } + + #region Binding Property + public bool ModifierHasWindows + { + get => modifierHasWindows; + set + { + if (SetProperty(ref modifierHasWindows, value)) + { + UpdateModifiers(); + } + } + } + + public bool ModifierHasControl + { + get => modifierHasControl; + set + { + if (SetProperty(ref modifierHasControl, value)) + { + UpdateModifiers(); + } + } + } + + public bool ModifierHasShift + { + get => modifierHasShift; + set + { + if (SetProperty(ref modifierHasShift, value)) + { + UpdateModifiers(); + } + } + } + + public bool ModifierHasAlt + { + get => modifierHasAlt; + set + { + if (SetProperty(ref modifierHasAlt, value)) + { + UpdateModifiers(); + } + } + } + + public NameValue KeyNameValue + { + get => keyNameValue; + set + { + if (value is null) + { + return; + } + + if (SetProperty(ref keyNameValue, value)) + { + Key = value.Value; + } + } + } + #endregion + + public HOT_KEY_MODIFIERS Modifiers + { + get => modifiers; + private set + { + if (SetProperty(ref modifiers, value)) + { + OnPropertyChanged(nameof(DisplayName)); + LocalSettingSetHotKeyParameterAndRefresh(); + } + } + } + + public VirtualKey Key + { + get => key; + private set + { + if (SetProperty(ref key, value)) + { + OnPropertyChanged(nameof(DisplayName)); + LocalSettingSetHotKeyParameterAndRefresh(); + } + } + } + + public bool IsEnabled + { + get => isEnabled; + set + { + if (SetProperty(ref isEnabled, value)) + { + LocalSetting.Set($"{settingKey}.IsEnabled", value); + + _ = (value, registered) switch + { + (true, false) => RegisterForCurrentWindow(), + (false, true) => UnregisterForCurrentWindow(), + _ => false, + }; + } + } + } + + public string DisplayName { get => ToString(); } + + public bool RegisterForCurrentWindow() + { + if (!runtimeOptions.IsElevated || !IsEnabled) + { + return false; + } + + if (registered) + { + return true; + } + + HWND hwnd = currentWindowReference.GetWindowHandle(); + registered = RegisterHotKey(hwnd, hotKeyId, Modifiers, (uint)Key); + return registered; + } + + public bool UnregisterForCurrentWindow() + { + if (!runtimeOptions.IsElevated) + { + return false; + } + + if (!registered) + { + return true; + } + + HWND hwnd = currentWindowReference.GetWindowHandle(); + registered = UnregisterHotKey(hwnd, hotKeyId); + return registered; + } + + public override string ToString() + { + StringBuilder stringBuilder = new(); + + if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_WIN)) + { + stringBuilder.Append("Win").Append(" + "); + } + + if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_CONTROL)) + { + stringBuilder.Append("Ctrl").Append(" + "); + } + + if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_SHIFT)) + { + stringBuilder.Append("Shift").Append(" + "); + } + + if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_ALT)) + { + stringBuilder.Append("Alt").Append(" + "); + } + + stringBuilder.Append(Key); + + return stringBuilder.ToString(); + } + + private void UpdateModifiers() + { + HOT_KEY_MODIFIERS modifiers = default; + + if (ModifierHasWindows) + { + modifiers |= HOT_KEY_MODIFIERS.MOD_WIN; + } + + if (ModifierHasControl) + { + modifiers |= HOT_KEY_MODIFIERS.MOD_CONTROL; + } + + if (ModifierHasShift) + { + modifiers |= HOT_KEY_MODIFIERS.MOD_SHIFT; + } + + if (ModifierHasAlt) + { + modifiers |= HOT_KEY_MODIFIERS.MOD_ALT; + } + + Modifiers = modifiers; + } + + private void InitializeModifiersComposeFields() + { + if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_WIN)) + { + modifierHasWindows = true; + } + + if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_CONTROL)) + { + modifierHasControl = true; + } + + if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_SHIFT)) + { + modifierHasShift = true; + } + + if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_ALT)) + { + modifierHasAlt = true; + } + } + + private unsafe HotKeyParameter LocalSettingGetHotKeyParameter() + { + fixed (HotKeyParameter* pDefaultHotKey = &defaultHotKeyParameter) + { + int value = LocalSetting.Get(settingKey, *(int*)pDefaultHotKey); + return *(HotKeyParameter*)&value; + } + } + + private unsafe void LocalSettingSetHotKeyParameterAndRefresh() + { + HotKeyParameter current = new(Modifiers, Key); + LocalSetting.Set(settingKey, *(int*)¤t); + + UnregisterForCurrentWindow(); + RegisterForCurrentWindow(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyController.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyController.cs index a2b4c497..4741e8a9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyController.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyController.cs @@ -2,69 +2,38 @@ // Licensed under the MIT license. using System.Runtime.InteropServices; -using Windows.Win32.Foundation; using Windows.Win32.UI.Input.KeyboardAndMouse; using static Windows.Win32.PInvoke; namespace Snap.Hutao.Core.Windowing.HotKey; [SuppressMessage("", "CA1001")] -internal sealed class HotKeyController : IHotKeyController +[ConstructorGenerated] +internal sealed partial class HotKeyController : IHotKeyController { - private const int DefaultId = 100000; + private static readonly WaitCallback RunMouseClickRepeatForever = MouseClickRepeatForever; private readonly object locker = new(); - private readonly WaitCallback runMouseClickRepeatForever; + private readonly HotKeyOptions hotKeyOptions; - private readonly RuntimeOptions runtimeOptions; + private volatile CancellationTokenSource? cancellationTokenSource; - public HotKeyController(IServiceProvider serviceProvider) + public void RegisterAll() { - hotKeyOptions = serviceProvider.GetRequiredService(); - runtimeOptions = serviceProvider.GetRequiredService(); - runMouseClickRepeatForever = MouseClickRepeatForever; + hotKeyOptions.MouseClickRepeatForeverKeyCombination.RegisterForCurrentWindow(); } - public bool Register(in HWND hwnd) + public void UnregisterAll() { - if (runtimeOptions.IsElevated) - { - return RegisterHotKey(hwnd, DefaultId, default, (uint)VIRTUAL_KEY.VK_F8); - } - - return false; - } - - public bool Unregister(in HWND hwnd) - { - if (runtimeOptions.IsElevated) - { - return UnregisterHotKey(hwnd, DefaultId); - } - - return false; + hotKeyOptions.MouseClickRepeatForeverKeyCombination.UnregisterForCurrentWindow(); } public void OnHotKeyPressed(in HotKeyParameter parameter) { - if (parameter is { Key: VIRTUAL_KEY.VK_F8, NativeModifier: 0 }) + if (parameter.Equals(hotKeyOptions.MouseClickRepeatForeverKeyCombination)) { - lock (locker) - { - if (hotKeyOptions.IsMouseClickRepeatForeverOn) - { - cancellationTokenSource?.Cancel(); - cancellationTokenSource = default; - hotKeyOptions.IsMouseClickRepeatForeverOn = false; - } - else - { - cancellationTokenSource = new(); - ThreadPool.QueueUserWorkItem(runMouseClickRepeatForever, cancellationTokenSource.Token); - hotKeyOptions.IsMouseClickRepeatForeverOn = true; - } - } + ToggleMouseClickRepeatForever(); } } @@ -76,7 +45,7 @@ internal sealed class HotKeyController : IHotKeyController } [SuppressMessage("", "SH007")] - private unsafe void MouseClickRepeatForever(object? state) + private static unsafe void MouseClickRepeatForever(object? state) { CancellationToken token = (CancellationToken)state!; @@ -102,4 +71,25 @@ internal sealed class HotKeyController : IHotKeyController Thread.Sleep(Random.Shared.Next(100, 150)); } } + + private void ToggleMouseClickRepeatForever() + { + lock (locker) + { + if (hotKeyOptions.IsMouseClickRepeatForeverOn) + { + // Turn off + cancellationTokenSource?.Cancel(); + cancellationTokenSource = default; + hotKeyOptions.IsMouseClickRepeatForeverOn = false; + } + else + { + // Turn on + cancellationTokenSource = new(); + ThreadPool.QueueUserWorkItem(RunMouseClickRepeatForever, cancellationTokenSource.Token); + hotKeyOptions.IsMouseClickRepeatForeverOn = true; + } + } + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyOptions.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyOptions.cs index c2741dcb..fe49fbaa 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyOptions.cs @@ -2,13 +2,34 @@ // Licensed under the MIT license. using CommunityToolkit.Mvvm.ComponentModel; +using Snap.Hutao.Core.Setting; +using Snap.Hutao.Model; +using Windows.System; namespace Snap.Hutao.Core.Windowing.HotKey; [Injection(InjectAs.Singleton)] -internal sealed class HotKeyOptions : ObservableObject +internal sealed partial class HotKeyOptions : ObservableObject { - private bool isVirtualKeyF8Pressed; + private bool isMouseClickRepeatForeverOn; + private HotKeyCombination mouseClickRepeatForeverKeyCombination; - public bool IsMouseClickRepeatForeverOn { get => isVirtualKeyF8Pressed; set => SetProperty(ref isVirtualKeyF8Pressed, value); } + public HotKeyOptions(IServiceProvider serviceProvider) + { + mouseClickRepeatForeverKeyCombination = new(serviceProvider, SettingKeys.HotKeyMouseClickRepeatForever, 100000, default, VirtualKey.F8); + } + + public List> VirtualKeys { get; } = HotKey.VirtualKeys.GetList(); + + public bool IsMouseClickRepeatForeverOn + { + get => isMouseClickRepeatForeverOn; + set => SetProperty(ref isMouseClickRepeatForeverOn, value); + } + + public HotKeyCombination MouseClickRepeatForeverKeyCombination + { + get => mouseClickRepeatForeverKeyCombination; + set => SetProperty(ref mouseClickRepeatForeverKeyCombination, value); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyParameter.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyParameter.cs index 8a8cf3e2..69a6b7d4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyParameter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyParameter.cs @@ -1,17 +1,43 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Windows.System; using Windows.Win32.UI.Input.KeyboardAndMouse; namespace Snap.Hutao.Core.Windowing.HotKey; -internal readonly struct HotKeyParameter +/// +/// HotKeyParameter +/// The size of this struct must be sizeof(LPARAM) or 4 +/// +internal readonly struct HotKeyParameter : IEquatable { - public readonly ushort NativeModifier; - public readonly VIRTUAL_KEY Key; + public readonly ushort NativeModifiers; + public readonly VIRTUAL_KEY NativeKey; - public readonly HOT_KEY_MODIFIERS Modifier + public HotKeyParameter(HOT_KEY_MODIFIERS modifiers, VirtualKey key) { - get => (HOT_KEY_MODIFIERS)NativeModifier; + NativeModifiers = (ushort)modifiers; + NativeKey = (VIRTUAL_KEY)key; + } + + public readonly HOT_KEY_MODIFIERS Modifiers + { + get => (HOT_KEY_MODIFIERS)NativeModifiers; + } + + public readonly VirtualKey Key + { + get => (VirtualKey)NativeKey; + } + + public bool Equals(HotKeyCombination? other) + { + if (other is null) + { + return false; + } + + return Modifiers == other.Modifiers && Key == other.Key; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/IHotKeyController.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/IHotKeyController.cs index 5e140c06..c78fa472 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/IHotKeyController.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/IHotKeyController.cs @@ -1,15 +1,13 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Windows.Win32.Foundation; - namespace Snap.Hutao.Core.Windowing.HotKey; internal interface IHotKeyController { void OnHotKeyPressed(in HotKeyParameter parameter); - bool Register(in HWND hwnd); + void RegisterAll(); - bool Unregister(in HWND hwnd); + void UnregisterAll(); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/VirtualKeys.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/VirtualKeys.cs new file mode 100644 index 00000000..1cea2893 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/VirtualKeys.cs @@ -0,0 +1,17 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model; +using Windows.System; + +namespace Snap.Hutao.Core.Windowing.HotKey; + +internal static class VirtualKeys +{ + private static readonly List> Values = CollectionsNameValue.ListFromEnum(); + + public static List> GetList() + { + return Values; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs index 2e54c6f2..706e7547 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs @@ -6,6 +6,7 @@ using Microsoft.UI.Composition.SystemBackdrops; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; +using Snap.Hutao.Core.LifeCycle; using Snap.Hutao.Core.Setting; using Snap.Hutao.Service; using System.IO; @@ -32,8 +33,9 @@ internal sealed class WindowController this.options = options; this.serviceProvider = serviceProvider; + // Window reference must be set before Window Subclass created + serviceProvider.GetRequiredService().Window = window; subclass = new(window, options, serviceProvider); - InitializeCore(); } @@ -78,7 +80,7 @@ internal sealed class WindowController private void RecoverOrInitWindowSize() { // Set first launch size - double scale = options.GetWindowScale(); + double scale = options.GetRasterizationScale(); SizeInt32 scaledSize = options.InitSize.Scale(scale); RectInt32 rect = StructMarshal.RectInt32(scaledSize); @@ -108,14 +110,14 @@ internal sealed class WindowController // prevent save value when we are maximized. if (!windowPlacement.showCmd.HasFlag(SHOW_WINDOW_CMD.SW_SHOWMAXIMIZED)) { - double scale = 1 / options.GetWindowScale(); + double scale = 1.0 / options.GetRasterizationScale(); LocalSetting.Set(SettingKeys.WindowRect, (CompactRect)window.AppWindow.GetRect().Scale(scale)); } } private void OnOptionsPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(AppOptions.BackdropType)) + if (e.PropertyName is nameof(AppOptions.BackdropType)) { if (sender is AppOptions options) { @@ -198,7 +200,7 @@ internal sealed class WindowController { AppWindowTitleBar appTitleBar = window.AppWindow.TitleBar; - double scale = options.GetWindowScale(); + double scale = options.GetRasterizationScale(); // 48 is the navigation button leftInset RectInt32 dragRect = StructMarshal.RectInt32(48, 0, options.TitleBar.ActualSize).Scale(scale); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowOptions.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowOptions.cs index 008a7b09..698bedf7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowOptions.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Microsoft.UI.Input; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Windows.Graphics; @@ -20,6 +21,11 @@ internal readonly struct WindowOptions /// public readonly HWND Hwnd; + /// + /// 非客户端区域指针源 + /// + public readonly InputNonClientPointerSource InputNonClientPointerSource; + /// /// 标题栏元素 /// @@ -50,6 +56,7 @@ internal readonly struct WindowOptions public WindowOptions(Window window, FrameworkElement titleBar, SizeInt32 initSize, bool persistSize = false) { Hwnd = (HWND)WindowNative.GetWindowHandle(window); + InputNonClientPointerSource = InputNonClientPointerSource.GetForWindowId(window.AppWindow.Id); TitleBar = titleBar; InitSize = initSize; PersistSize = persistSize; @@ -59,7 +66,7 @@ internal readonly struct WindowOptions /// 获取窗体当前的DPI缩放比 /// /// 缩放比 - public double GetWindowScale() + public double GetRasterizationScale() { uint dpi = GetDpiForWindow(Hwnd); return Math.Round(dpi / 96D, 2, MidpointRounding.AwayFromZero); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs index ab7efea0..46921f03 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs @@ -45,7 +45,7 @@ internal sealed class WindowSubclass : IDisposable { windowProc = OnSubclassProcedure; bool windowHooked = SetWindowSubclass(options.Hwnd, windowProc, WindowSubclassId, 0); - hotKeyController.Register(options.Hwnd); + hotKeyController.RegisterAll(); bool titleBarHooked = true; @@ -72,7 +72,7 @@ internal sealed class WindowSubclass : IDisposable /// public void Dispose() { - hotKeyController.Unregister(options.Hwnd); + hotKeyController.UnregisterAll(); RemoveWindowSubclass(options.Hwnd, windowProc, WindowSubclassId); windowProc = null; @@ -93,7 +93,7 @@ internal sealed class WindowSubclass : IDisposable { if (window is IMinMaxInfoHandler handler) { - handler.HandleMinMaxInfo(ref *(MINMAXINFO*)lParam.Value, options.GetWindowScale()); + handler.HandleMinMaxInfo(ref *(MINMAXINFO*)lParam.Value, options.GetRasterizationScale()); } break; diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/Picker/PickerFactory.cs b/src/Snap.Hutao/Snap.Hutao/Factory/Picker/PickerFactory.cs index 4000235b..45874140 100644 --- a/src/Snap.Hutao/Snap.Hutao/Factory/Picker/PickerFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Factory/Picker/PickerFactory.cs @@ -5,6 +5,7 @@ using Snap.Hutao.Core; using Snap.Hutao.Core.LifeCycle; using Snap.Hutao.Core.Windowing; using Windows.Storage.Pickers; +using Windows.Win32.Foundation; using WinRT.Interop; namespace Snap.Hutao.Factory.Picker; @@ -79,10 +80,8 @@ internal sealed partial class PickerFactory : IPickerFactory { // Create a folder picker. T picker = new(); - nint hwnd = currentWindowReference.Window is IWindowOptionsSource optionsSource - ? (nint)optionsSource.WindowOptions.Hwnd - : WindowNative.GetWindowHandle(currentWindowReference.Window); + HWND hwnd = currentWindowReference.GetWindowHandle(); InitializeWithWindow.Initialize(picker, hwnd); return picker; diff --git a/src/Snap.Hutao/Snap.Hutao/Model/CollectionsNameValue.cs b/src/Snap.Hutao/Snap.Hutao/Model/CollectionsNameValue.cs new file mode 100644 index 00000000..6501702c --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/CollectionsNameValue.cs @@ -0,0 +1,13 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Model; + +internal static class CollectionsNameValue +{ + public static List> ListFromEnum() + where T : struct, Enum + { + return Enum.GetValues().Select(x => new NameValue(x.ToString(), x)).ToList(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/NameValue.cs b/src/Snap.Hutao/Snap.Hutao/Model/NameValue.cs index 9781a893..824eb514 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/NameValue.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/NameValue.cs @@ -6,6 +6,8 @@ namespace Snap.Hutao.Model; /// /// 封装带有名称描述的值 /// 在绑定枚举变量时非常有用 +/// https://github.com/microsoft/microsoft-ui-xaml/issues/4266 +/// 直接绑定枚举变量会显示 Windows.Foundation.IReference{T} /// /// 包含值的类型 [HighQuality] diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index 9a8c6ef5..e42ab378 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -2300,6 +2300,15 @@ 启动高级功能 + + 更改自动连点功能的快捷键 + + + 自动连点 + + + 快捷键 + 前往官网 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs index 3e9834e3..96121b9a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs @@ -19,12 +19,7 @@ namespace Snap.Hutao.Service; [Injection(InjectAs.Singleton)] internal sealed partial class AppOptions : DbStoreOptions { - private readonly List> supportedBackdropTypesInner = new() - { - new("Acrylic", BackdropType.Acrylic), - new("Mica", BackdropType.Mica), - new("MicaAlt", BackdropType.MicaAlt), - }; + private readonly List> supportedBackdropTypesInner = CollectionsNameValue.ListFromEnum(); private readonly List> supportedCulturesInner = new() { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs index f2bf764f..6b9ad778 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs @@ -10,7 +10,7 @@ namespace Snap.Hutao.Service.Game.Scheme; /// 启动方案 /// [HighQuality] -internal class LaunchScheme +internal class LaunchScheme : IEquatable { /// /// 显示名称 @@ -72,9 +72,10 @@ internal class LaunchScheme /// /// 多通道 /// 是否相等 - public bool MultiChannelEqual(in ChannelOptions multiChannel) + [SuppressMessage("", "SH002")] + public bool Equals(ChannelOptions other) { - return Channel == multiChannel.Channel && SubChannel == multiChannel.SubChannel; + return Channel == other.Channel && SubChannel == other.SubChannel; } public bool ExecutableMatches(string gameFileName) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs index 7abf2eb1..851db7a0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs @@ -83,6 +83,11 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi return false; } } + catch (JsonException ex) + { + infoBarService.Error(ex, SH.ServiceMetadataRequestFailed); + return false; + } catch (HttpRequestException ex) { if (ex.StatusCode is HttpStatusCode.Forbidden or HttpStatusCode.NotFound) diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml index 269841f3..f7098c72 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml @@ -195,6 +195,45 @@ SelectedItem="{Binding SelectedBackdropType, Mode=TwoWay}"/> + + + + + + + + + + + + + - - - + - - + + diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs index 4fd85dcf..02a5e442 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs @@ -125,7 +125,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel { SelectedScheme = KnownSchemes .Where(scheme => scheme.IsOversea == options.IsOversea) - .Single(scheme => scheme.MultiChannelEqual(options)); + .Single(scheme => scheme.Equals(options)); } catch (InvalidOperationException) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/HomeCardOptions.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/HomeCardOptions.cs similarity index 96% rename from src/Snap.Hutao/Snap.Hutao/ViewModel/HomeCardOptions.cs rename to src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/HomeCardOptions.cs index e5807644..6316ac4e 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/HomeCardOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/HomeCardOptions.cs @@ -3,7 +3,7 @@ using Snap.Hutao.Core.Setting; -namespace Snap.Hutao.ViewModel; +namespace Snap.Hutao.ViewModel.Setting; internal sealed class HomeCardOptions { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs index 131190c7..b57ee8d9 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs @@ -11,6 +11,7 @@ using Snap.Hutao.Core.IO.DataTransfer; using Snap.Hutao.Core.Setting; using Snap.Hutao.Core.Shell; using Snap.Hutao.Core.Windowing; +using Snap.Hutao.Core.Windowing.HotKey; using Snap.Hutao.Factory.ContentDialog; using Snap.Hutao.Factory.Picker; using Snap.Hutao.Model; @@ -51,6 +52,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel private readonly IInfoBarService infoBarService; private readonly RuntimeOptions runtimeOptions; private readonly IPickerFactory pickerFactory; + private readonly HotKeyOptions hotKeyOptions; private readonly IUserService userService; private readonly ITaskContext taskContext; private readonly AppOptions appOptions; @@ -63,18 +65,14 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel /// public AppOptions Options { get => appOptions; } - /// - /// 胡桃选项 - /// public RuntimeOptions HutaoOptions { get => runtimeOptions; } - /// - /// 胡桃用户选项 - /// public HutaoUserOptions UserOptions { get => hutaoUserOptions; } public HomeCardOptions HomeCardOptions { get => homeCardOptions; } + public HotKeyOptions HotKeyOptions { get => hotKeyOptions; } + public HutaoPassportViewModel Passport { get => hutaoPassportViewModel; } /// From ec007d5d818ed2a01c043d27442dacf2e1fae63d Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Tue, 7 Nov 2023 19:08:48 +0800 Subject: [PATCH 02/15] add fp to jsbridge --- .../Web/Bridge/MiHoYoJSInterface.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs index 644a0f7e..360e7a6a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs @@ -237,14 +237,21 @@ internal class MiHoYoJSInterface /// Http请求头 public virtual JsResult> GetHttpRequestHeader(JsParam param) { + Dictionary headers = new() + { + { "x-rpc-client_type", "5" }, + { "x-rpc-device_id", HoyolabOptions.DeviceId }, + { "x-rpc-app_version", SaltConstants.CNVersion }, + }; + + if (!userAndUid.IsOversea) + { + headers.Add("x-rpc-device_fp", userAndUid.User.Fingerprint ?? string.Empty); + } + return new() { - Data = new Dictionary() - { - { "x-rpc-client_type", "5" }, - { "x-rpc-device_id", HoyolabOptions.DeviceId }, - { "x-rpc-app_version", SaltConstants.CNVersion }, - }, + Data = headers, }; } From acdf2baa9aca109902d77a5753cdddcbe0fabe60 Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Tue, 7 Nov 2023 21:02:25 +0800 Subject: [PATCH 03/15] improve webviewer & hotkey --- .../Snap.Hutao/Control/Theme/FlyoutStyle.xaml | 1 - .../IocHttpClientConfiguration.cs | 10 +-- .../Windowing/HotKey/HotKeyCombination.cs | 10 ++- .../Snap.Hutao/Resource/Localization/SH.resx | 2 +- .../Snap.Hutao/View/Control/WebViewer.xaml | 40 +++++++++- .../Snap.Hutao/View/Control/WebViewer.xaml.cs | 25 ++++++ src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml | 36 +++++---- src/Snap.Hutao/Snap.Hutao/View/UserView.xaml | 7 +- .../Web/Bridge/MiHoYoJSInterface.cs | 76 ++++++++++++------- .../Web/Bridge/SignInJSInterface.cs | 13 +--- .../Web/Bridge/SignInJSInterfaceOversea.cs | 20 ++--- 11 files changed, 152 insertions(+), 88 deletions(-) diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Theme/FlyoutStyle.xaml b/src/Snap.Hutao/Snap.Hutao/Control/Theme/FlyoutStyle.xaml index b2da9a2e..955fffba 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Theme/FlyoutStyle.xaml +++ b/src/Snap.Hutao/Snap.Hutao/Control/Theme/FlyoutStyle.xaml @@ -4,7 +4,6 @@ BasedOn="{StaticResource DefaultFlyoutPresenterStyle}" TargetType="FlyoutPresenter"> - - diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs index 360e7a6a..e4123d16 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs @@ -74,12 +74,20 @@ internal class MiHoYoJSInterface public event Action? ClosePageRequested; + public void Detach() + { + coreWebView2.WebMessageReceived -= webMessageReceivedEventHandler; + coreWebView2.DOMContentLoaded -= domContentLoadedEventHandler; + coreWebView2.NavigationStarting -= navigationStartingEventHandler; + coreWebView2 = default!; + } + /// /// 关闭 /// /// 参数 /// 响应 - public virtual async ValueTask ClosePageAsync(JsParam param) + protected virtual async ValueTask ClosePageAsync(JsParam param) { await taskContext.SwitchToMainThreadAsync(); if (coreWebView2.CanGoBack) @@ -99,7 +107,7 @@ internal class MiHoYoJSInterface /// /// 参数 /// 响应 - public virtual IJsResult? ConfigureShare(JsParam param) + protected virtual IJsResult? ConfigureShare(JsParam param) { return null; } @@ -109,7 +117,7 @@ internal class MiHoYoJSInterface /// /// 参数 /// 响应 - public virtual async ValueTask GetActionTicketAsync(JsParam jsParam) + protected virtual async ValueTask GetActionTicketAsync(JsParam jsParam) { return await serviceProvider .GetRequiredService() @@ -122,7 +130,7 @@ internal class MiHoYoJSInterface /// /// 参数 /// 响应 - public virtual JsResult> GetCookieInfo(JsParam param) + protected virtual JsResult> GetCookieInfo(JsParam param) { ArgumentNullException.ThrowIfNull(userAndUid.User.LToken); @@ -142,7 +150,7 @@ internal class MiHoYoJSInterface /// /// 参数 /// 响应 - public virtual async ValueTask>> GetCookieTokenAsync(JsParam param) + protected virtual async ValueTask>> GetCookieTokenAsync(JsParam param) { IUserService userService = serviceProvider.GetRequiredService(); if (param.Payload.ForceRefresh) @@ -162,7 +170,7 @@ internal class MiHoYoJSInterface /// /// param /// 语言与时区 - public virtual JsResult> GetCurrentLocale(JsParam param) + protected virtual JsResult> GetCurrentLocale(JsParam param) { MetadataOptions metadataOptions = serviceProvider.GetRequiredService(); @@ -181,7 +189,7 @@ internal class MiHoYoJSInterface /// /// 参数 /// 响应 - public virtual JsResult> GetDynamicSecrectV1(JsParam param) + protected virtual JsResult> GetDynamicSecrectV1(JsParam param) { string salt = HoyolabOptions.Salts[SaltType.LK2]; long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); @@ -212,7 +220,7 @@ internal class MiHoYoJSInterface /// /// 参数 /// 响应 - public virtual JsResult> GetDynamicSecrectV2(JsParam param) + protected virtual JsResult> GetDynamicSecrectV2(JsParam param) { string salt = HoyolabOptions.Salts[SaltType.X4]; long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); @@ -235,13 +243,20 @@ internal class MiHoYoJSInterface /// /// 参数 /// Http请求头 - public virtual JsResult> GetHttpRequestHeader(JsParam param) + protected virtual JsResult> GetHttpRequestHeader(JsParam param) { Dictionary headers = new() { + // Skip x-rpc-device_name + // Skip x-rpc-device_model + // Skip x-rpc-sys_version + // Skip x-rpc-game_biz + // Skip x-rpc-lifecycle_id + { "x-rpc-app_id", "bll8iq97cem8" }, { "x-rpc-client_type", "5" }, - { "x-rpc-device_id", HoyolabOptions.DeviceId }, - { "x-rpc-app_version", SaltConstants.CNVersion }, + { "x-rpc-device_id", HoyolabOptions.DeviceId }, + { "x-rpc-app_version", userAndUid.IsOversea ? SaltConstants.OSVersion : SaltConstants.CNVersion }, + { "x-rpc-sdk_version", "2.16.0" }, }; if (!userAndUid.IsOversea) @@ -249,18 +264,24 @@ internal class MiHoYoJSInterface headers.Add("x-rpc-device_fp", userAndUid.User.Fingerprint ?? string.Empty); } + GetHttpRequestHeaderCore(headers); + return new() { Data = headers, }; } + protected virtual void GetHttpRequestHeaderCore(Dictionary headers) + { + } + /// /// 获取状态栏高度 /// /// 参数 /// 结果 - public virtual JsResult> GetStatusBarHeight(JsParam param) + protected virtual JsResult> GetStatusBarHeight(JsParam param) { return new() { Data = new() { ["statusBarHeight"] = 0 } }; } @@ -270,7 +291,7 @@ internal class MiHoYoJSInterface /// /// 参数 /// 响应 - public virtual async ValueTask>> GetUserInfoAsync(JsParam param) + protected virtual async ValueTask>> GetUserInfoAsync(JsParam param) { Response response = await serviceProvider .GetRequiredService>() @@ -299,7 +320,7 @@ internal class MiHoYoJSInterface } } - public virtual async ValueTask PushPageAsync(JsParam param) + protected virtual async ValueTask PushPageAsync(JsParam param) { const string bbsSchema = "mihoyobbs://"; string pageUrl = param.Payload.Page; @@ -323,7 +344,7 @@ internal class MiHoYoJSInterface return null; } - public virtual IJsResult? Share(JsParam param) + protected virtual IJsResult? Share(JsParam param) { return new JsResult>() { @@ -334,57 +355,53 @@ internal class MiHoYoJSInterface }; } - public virtual ValueTask ShowAlertDialogAsync(JsParam param) + protected virtual ValueTask ShowAlertDialogAsync(JsParam param) { return ValueTask.FromException(new NotSupportedException()); } - public virtual IJsResult? StartRealPersonValidation(JsParam param) + protected virtual IJsResult? StartRealPersonValidation(JsParam param) { throw new NotImplementedException(); } - public virtual IJsResult? StartRealnameAuth(JsParam param) + protected virtual IJsResult? StartRealnameAuth(JsParam param) { throw new NotImplementedException(); } - public virtual IJsResult? GenAuthKey(JsParam param) + protected virtual IJsResult? GenAuthKey(JsParam param) { throw new NotImplementedException(); } - public virtual IJsResult? GenAppAuthKey(JsParam param) + protected virtual IJsResult? GenAppAuthKey(JsParam param) { throw new NotImplementedException(); } - public virtual IJsResult? OpenSystemBrowser(JsParam param) + protected virtual IJsResult? OpenSystemBrowser(JsParam param) { throw new NotImplementedException(); } - public virtual IJsResult? SaveLoginTicket(JsParam param) + protected virtual IJsResult? SaveLoginTicket(JsParam param) { throw new NotImplementedException(); } - public virtual ValueTask GetNotificationSettingsAsync(JsParam param) + protected virtual ValueTask GetNotificationSettingsAsync(JsParam param) { throw new NotImplementedException(); } - public virtual IJsResult? ShowToast(JsParam param) + protected virtual IJsResult? ShowToast(JsParam param) { throw new NotImplementedException(); } - public void Detach() + protected virtual void DOMContentLoaded(CoreWebView2 coreWebView2) { - coreWebView2.WebMessageReceived -= webMessageReceivedEventHandler; - coreWebView2.DOMContentLoaded -= domContentLoadedEventHandler; - coreWebView2.NavigationStarting -= navigationStartingEventHandler; - coreWebView2 = default!; } private async ValueTask ExecuteCallbackScriptAsync(string callback, string? payload = null) @@ -485,6 +502,7 @@ internal class MiHoYoJSInterface private void OnDOMContentLoaded(CoreWebView2 coreWebView2, CoreWebView2DOMContentLoadedEventArgs args) { + DOMContentLoaded(coreWebView2); coreWebView2.ExecuteScriptAsync(HideScrollBarScript).AsTask().SafeForget(logger); } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterface.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterface.cs index 52c03502..513f2f5e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterface.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterface.cs @@ -20,17 +20,8 @@ internal sealed class SignInJSInterface : MiHoYoJSInterface { } - /// - public override JsResult> GetHttpRequestHeader(JsParam param) + protected override void GetHttpRequestHeaderCore(Dictionary headers) { - return new() - { - Data = new Dictionary() - { - { "x-rpc-client_type", "2" }, - { "x-rpc-device_id", HoyolabOptions.DeviceId }, - { "x-rpc-app_version", SaltConstants.CNVersion }, - }, - }; + headers["x-rpc-client_type"] = "2"; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterfaceOversea.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterfaceOversea.cs index 4321bec2..90692280 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterfaceOversea.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterfaceOversea.cs @@ -14,6 +14,7 @@ namespace Snap.Hutao.Web.Bridge; [HighQuality] internal sealed class SignInJSInterfaceOversea : MiHoYoJSInterface { + // 移除 请旋转手机 提示所在的HTML元素 private const string RemoveRotationWarningScript = """ let landscape = document.getElementById('mihoyo_landscape'); landscape.remove(); @@ -26,26 +27,15 @@ internal sealed class SignInJSInterfaceOversea : MiHoYoJSInterface : base(webView, userAndUid) { logger = serviceProvider.GetRequiredService>(); - webView.DOMContentLoaded += OnDOMContentLoaded; } - /// - public override JsResult> GetHttpRequestHeader(JsParam param) + protected override void GetHttpRequestHeaderCore(Dictionary headers) { - return new() - { - Data = new Dictionary() - { - { "x-rpc-client_type", "2" }, - { "x-rpc-device_id", HoyolabOptions.DeviceId }, - { "x-rpc-app_version", SaltConstants.OSVersion }, - }, - }; + headers["x-rpc-client_type"] = "2"; } - private void OnDOMContentLoaded(CoreWebView2 coreWebView2, CoreWebView2DOMContentLoadedEventArgs args) + protected override void DOMContentLoaded(CoreWebView2 coreWebView2) { - // 移除“请旋转手机”提示所在的HTML元素 coreWebView2.ExecuteScriptAsync(RemoveRotationWarningScript).AsTask().SafeForget(logger); } -} +} \ No newline at end of file From 1c0ce62885ae96a42dea58eda6ea6bcafd358aee Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Wed, 8 Nov 2023 13:34:55 +0800 Subject: [PATCH 04/15] fix gacha item corner radius --- src/Snap.Hutao/Snap.Hutao/Control/Theme/CornerRadius.xaml | 1 + src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Theme/CornerRadius.xaml b/src/Snap.Hutao/Snap.Hutao/Control/Theme/CornerRadius.xaml index f2ebb9f5..4dcfd7d4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Theme/CornerRadius.xaml +++ b/src/Snap.Hutao/Snap.Hutao/Control/Theme/CornerRadius.xaml @@ -1,4 +1,5 @@  4,4,0,0 0,0,4,4 + 0,4,0,4 diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml index 4b720db2..d0178afa 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml @@ -212,7 +212,7 @@ HorizontalAlignment="Right" VerticalAlignment="Top" Background="#80000000" - CornerRadius="0,6,0,6"> + CornerRadius="{ThemeResource ControlCornerRadiusTopRightAndBottomLeft}"> Date: Thu, 9 Nov 2023 11:38:30 +0800 Subject: [PATCH 05/15] fix #1079 --- .../Control/Layout/UniformStaggeredLayout.cs | 7 ++- .../Snap.Hutao/View/Page/CultivationPage.xaml | 59 +++++++++---------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Layout/UniformStaggeredLayout.cs b/src/Snap.Hutao/Snap.Hutao/Control/Layout/UniformStaggeredLayout.cs index 43d82481..ce1b24f9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Layout/UniformStaggeredLayout.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Layout/UniformStaggeredLayout.cs @@ -128,8 +128,13 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout UniformStaggeredItem item = state.GetItemAt(i); if (item.Height == 0) { + // https://github.com/DGP-Studio/Snap.Hutao/issues/1079 + // The first element must be force refreshed otherwise + // it will use the old one realized + ElementRealizationOptions options = i == 0 ? ElementRealizationOptions.ForceCreate : ElementRealizationOptions.None; + // Item has not been measured yet. Get the element and store the values - UIElement element = context.GetOrCreateElementAt(i); + UIElement element = context.GetOrCreateElementAt(i, options); element.Measure(new Size(state.ColumnWidth, availableHeight)); item.Height = element.DesiredSize.Height; item.Element = element; diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml index 92887710..4a835472 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml @@ -272,39 +272,36 @@ - - - - - - - - + + + + + + + - - - - - + + + + + + + Date: Thu, 9 Nov 2023 11:51:56 +0800 Subject: [PATCH 06/15] rename jsbridge --- .../Snap.Hutao/View/Control/IWebViewerSource.cs | 2 +- .../View/Control/StaticWebview2ViewerSource.cs | 4 ++-- .../Snap.Hutao/View/Control/WebViewer.xaml.cs | 6 +++--- .../ViewModel/DailyNote/DailyNoteWebViewerSource.cs | 4 ++-- .../Snap.Hutao/ViewModel/User/SignInWebViewerSouce.cs | 6 +++--- .../Bridge/{MiHoYoJSInterface.cs => MiHoYoJSBridge.cs} | 8 ++++---- .../Bridge/{SignInJSInterface.cs => SignInJSBridge.cs} | 5 ++--- ...nJSInterfaceOversea.cs => SignInJSBridgeOversea.cs} | 10 +++------- 8 files changed, 20 insertions(+), 25 deletions(-) rename src/Snap.Hutao/Snap.Hutao/Web/Bridge/{MiHoYoJSInterface.cs => MiHoYoJSBridge.cs} (98%) rename src/Snap.Hutao/Snap.Hutao/Web/Bridge/{SignInJSInterface.cs => SignInJSBridge.cs} (67%) rename src/Snap.Hutao/Snap.Hutao/Web/Bridge/{SignInJSInterfaceOversea.cs => SignInJSBridgeOversea.cs} (67%) diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/IWebViewerSource.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/IWebViewerSource.cs index 7a2394f4..35d624c1 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/IWebViewerSource.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/IWebViewerSource.cs @@ -9,7 +9,7 @@ namespace Snap.Hutao.View.Control; internal interface IWebViewerSource { - MiHoYoJSInterface CreateJsInterface(IServiceProvider serviceProvider, CoreWebView2 coreWebView2, UserAndUid userAndUid); + MiHoYoJSBridge CreateJSBridge(IServiceProvider serviceProvider, CoreWebView2 coreWebView2, UserAndUid userAndUid); string GetSource(UserAndUid userAndUid); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/StaticWebview2ViewerSource.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/StaticWebview2ViewerSource.cs index 44c197fd..2fddb13f 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/StaticWebview2ViewerSource.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/StaticWebview2ViewerSource.cs @@ -12,9 +12,9 @@ namespace Snap.Hutao.View.Control; [DependencyProperty("OverseaSource", typeof(string))] internal sealed partial class StaticWebview2ViewerSource : DependencyObject, IWebViewerSource { - public MiHoYoJSInterface CreateJsInterface(IServiceProvider serviceProvider, CoreWebView2 coreWebView2, UserAndUid userAndUid) + public MiHoYoJSBridge CreateJSBridge(IServiceProvider serviceProvider, CoreWebView2 coreWebView2, UserAndUid userAndUid) { - return serviceProvider.CreateInstance(coreWebView2, userAndUid); + return serviceProvider.CreateInstance(coreWebView2, userAndUid); } public string GetSource(UserAndUid userAndUid) 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 8f50f1b1..085556d5 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/WebViewer.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/WebViewer.xaml.cs @@ -25,7 +25,7 @@ internal partial class WebViewer : UserControl, IRecipient private readonly RoutedEventHandler loadEventHandler; private readonly TypedEventHandler documentTitleChangedEventHander; - private MiHoYoJSInterface? jsInterface; + private MiHoYoJSBridge? jsBridge; private bool isInitializingOrInitialized; public WebViewer() @@ -129,8 +129,8 @@ internal partial class WebViewer : UserControl, IRecipient coreWebView2 .SetCookie(user.CookieToken, user.LToken, userAndUid.IsOversea) .SetMobileUserAgent(userAndUid.IsOversea); - jsInterface?.Detach(); - jsInterface = SourceProvider.CreateJsInterface(serviceProvider, coreWebView2, userAndUid); + jsBridge?.Detach(); + jsBridge = SourceProvider.CreateJSBridge(serviceProvider, coreWebView2, userAndUid); await navigator.NavigateAsync(source).ConfigureAwait(true); } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteWebViewerSource.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteWebViewerSource.cs index c8e648da..64767b36 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteWebViewerSource.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteWebViewerSource.cs @@ -11,9 +11,9 @@ namespace Snap.Hutao.ViewModel.DailyNote; internal sealed class DailyNoteWebViewerSource : IWebViewerSource { - public MiHoYoJSInterface CreateJsInterface(IServiceProvider serviceProvider, CoreWebView2 coreWebView2, UserAndUid userAndUid) + public MiHoYoJSBridge CreateJSBridge(IServiceProvider serviceProvider, CoreWebView2 coreWebView2, UserAndUid userAndUid) { - return serviceProvider.CreateInstance(coreWebView2, userAndUid); + return serviceProvider.CreateInstance(coreWebView2, userAndUid); } public string GetSource(UserAndUid userAndUid) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/SignInWebViewerSouce.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/SignInWebViewerSouce.cs index 7fd64d23..d656bece 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/SignInWebViewerSouce.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/SignInWebViewerSouce.cs @@ -10,11 +10,11 @@ namespace Snap.Hutao.ViewModel.User; internal sealed class SignInWebViewerSouce : DependencyObject, IWebViewerSource { - public MiHoYoJSInterface CreateJsInterface(IServiceProvider serviceProvider, CoreWebView2 coreWebView2, UserAndUid userAndUid) + public MiHoYoJSBridge CreateJSBridge(IServiceProvider serviceProvider, CoreWebView2 coreWebView2, UserAndUid userAndUid) { return userAndUid.User.IsOversea - ? serviceProvider.CreateInstance(coreWebView2, userAndUid) - : serviceProvider.CreateInstance(coreWebView2, userAndUid); + ? serviceProvider.CreateInstance(coreWebView2, userAndUid) + : serviceProvider.CreateInstance(coreWebView2, userAndUid); } public string GetSource(UserAndUid userAndUid) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSBridge.cs similarity index 98% rename from src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs rename to src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSBridge.cs index e4123d16..2630976f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSBridge.cs @@ -24,7 +24,7 @@ namespace Snap.Hutao.Web.Bridge; [HighQuality] [SuppressMessage("", "CA1001")] [SuppressMessage("", "CA1308")] -internal class MiHoYoJSInterface +internal class MiHoYoJSBridge { private const string InitializeJsInterfaceScript2 = """ window.MiHoYoJSInterface = { @@ -45,7 +45,7 @@ internal class MiHoYoJSInterface private readonly IServiceProvider serviceProvider; private readonly ITaskContext taskContext; - private readonly ILogger logger; + private readonly ILogger logger; private readonly TypedEventHandler webMessageReceivedEventHandler; private readonly TypedEventHandler domContentLoadedEventHandler; @@ -53,7 +53,7 @@ internal class MiHoYoJSInterface private CoreWebView2 coreWebView2; - public MiHoYoJSInterface(CoreWebView2 coreWebView2, UserAndUid userAndUid) + public MiHoYoJSBridge(CoreWebView2 coreWebView2, UserAndUid userAndUid) { // 由于Webview2 的作用域特殊性,我们在此处直接使用根服务 serviceProvider = Ioc.Default; @@ -61,7 +61,7 @@ internal class MiHoYoJSInterface this.userAndUid = userAndUid; taskContext = serviceProvider.GetRequiredService(); - logger = serviceProvider.GetRequiredService>(); + logger = serviceProvider.GetRequiredService>(); webMessageReceivedEventHandler = OnWebMessageReceived; domContentLoadedEventHandler = OnDOMContentLoaded; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterface.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSBridge.cs similarity index 67% rename from src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterface.cs rename to src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSBridge.cs index 513f2f5e..a96061ca 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterface.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSBridge.cs @@ -12,10 +12,9 @@ namespace Snap.Hutao.Web.Bridge; /// 签到页面JS桥 /// [HighQuality] -internal sealed class SignInJSInterface : MiHoYoJSInterface +internal sealed class SignInJSBridge : MiHoYoJSBridge { - /// - public SignInJSInterface(CoreWebView2 webView, IServiceProvider serviceProvider, UserAndUid userAndUid) + public SignInJSBridge(CoreWebView2 webView, UserAndUid userAndUid) : base(webView, userAndUid) { } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterfaceOversea.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSBridgeOversea.cs similarity index 67% rename from src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterfaceOversea.cs rename to src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSBridgeOversea.cs index 90692280..bf172a7f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterfaceOversea.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSBridgeOversea.cs @@ -12,7 +12,7 @@ namespace Snap.Hutao.Web.Bridge; /// HoYoLAB 签到页面JS桥 /// [HighQuality] -internal sealed class SignInJSInterfaceOversea : MiHoYoJSInterface +internal sealed class SignInJSBridgeOversea : MiHoYoJSBridge { // 移除 请旋转手机 提示所在的HTML元素 private const string RemoveRotationWarningScript = """ @@ -20,13 +20,9 @@ internal sealed class SignInJSInterfaceOversea : MiHoYoJSInterface landscape.remove(); """; - private readonly ILogger logger; - - /// - public SignInJSInterfaceOversea(CoreWebView2 webView, IServiceProvider serviceProvider, UserAndUid userAndUid) + public SignInJSBridgeOversea(CoreWebView2 webView, UserAndUid userAndUid) : base(webView, userAndUid) { - logger = serviceProvider.GetRequiredService>(); } protected override void GetHttpRequestHeaderCore(Dictionary headers) @@ -36,6 +32,6 @@ internal sealed class SignInJSInterfaceOversea : MiHoYoJSInterface protected override void DOMContentLoaded(CoreWebView2 coreWebView2) { - coreWebView2.ExecuteScriptAsync(RemoveRotationWarningScript).AsTask().SafeForget(logger); + coreWebView2.ExecuteScriptAsync(RemoveRotationWarningScript).AsTask().SafeForget(); } } \ No newline at end of file From 71363f4d8dd0493358f67b171a665cd03b228ae3 Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Thu, 9 Nov 2023 15:23:51 +0800 Subject: [PATCH 07/15] fix #1081 --- .../GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs index 635c336c..3b37e5f9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQueryWebCacheProvider.cs @@ -104,7 +104,7 @@ internal sealed partial class GachaLogQueryWebCacheProvider : IGachaLogQueryProv { ReadOnlySpan span = stream.ToArray(); ReadOnlySpan match = isOversea - ? "https://webstatic-sea.hoyoverse.com/genshin/event/e20190909gacha-v2/index.html"u8 + ? "https://gs.hoyoverse.com/genshin/event/e20190909gacha-v2/index.html"u8 : "https://webstatic.mihoyo.com/hk4e/event/e20190909gacha-v2/index.html"u8; int index = span.LastIndexOf(match); From 3005031b39666ff97fa9f211c9a91bf3ac76b31a Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Thu, 9 Nov 2023 17:18:56 +0800 Subject: [PATCH 08/15] add basic timezone support for gachaitem --- .../Json/Converter/DateTimeOffsetConverter.cs | 2 +- .../Snap.Hutao/View/Page/SettingPage.xaml | 3 ++- .../Snap.Hutao/Web/Hoyolab/PlayerUid.cs | 14 ++++++++++++++ .../Web/Hoyolab/ServerTimeZoneInfo.cs | 17 +++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerTimeZoneInfo.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/DateTimeOffsetConverter.cs b/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/DateTimeOffsetConverter.cs index be8eaef3..d9585277 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/DateTimeOffsetConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/DateTimeOffsetConverter.cs @@ -27,6 +27,6 @@ internal class DateTimeOffsetConverter : JsonConverter /// public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) { - writer.WriteStringValue(value.ToString(Format, CultureInfo.CurrentCulture)); + writer.WriteStringValue(value.ToLocalTime().ToString(Format, CultureInfo.CurrentCulture)); } } \ 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 f7098c72..04ad3645 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml @@ -225,7 +225,8 @@ VerticalAlignment="Center" DisplayMemberPath="Name" ItemsSource="{Binding HotKeyOptions.VirtualKeys}" - SelectedItem="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.KeyNameValue, Mode=TwoWay}"/> + SelectedItem="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.KeyNameValue, Mode=TwoWay}" + Style="{StaticResource DefaultComboBoxStyle}"/> ServerTimeZoneInfo.UsaTimeZone, + "os_euro" => ServerTimeZoneInfo.EuroTimeZone, + _ => ServerTimeZoneInfo.CommonTimeZone, + }; + } + /// public override string ToString() { diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerTimeZoneInfo.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerTimeZoneInfo.cs new file mode 100644 index 00000000..03d07838 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerTimeZoneInfo.cs @@ -0,0 +1,17 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Web.Hoyolab; + +internal static class ServerTimeZoneInfo +{ + private static readonly TimeZoneInfo UsaTimeZoneValue = TimeZoneInfo.CreateCustomTimeZone("Server:UTC-05", new TimeSpan(-05, 0, 0), "UTC-05", "UTC-05"); + private static readonly TimeZoneInfo EuroTimeZoneValue = TimeZoneInfo.CreateCustomTimeZone("Server:UTC+02", new TimeSpan(+02, 0, 0), "UTC+02", "UTC+02"); + private static readonly TimeZoneInfo CommonTimeZoneValue = TimeZoneInfo.CreateCustomTimeZone("Server:UTC+08", new TimeSpan(+08, 0, 0), "UTC+08", "UTC+08"); + + public static TimeZoneInfo UsaTimeZone { get => UsaTimeZoneValue; } + + public static TimeZoneInfo EuroTimeZone { get => EuroTimeZoneValue; } + + public static TimeZoneInfo CommonTimeZone { get => CommonTimeZoneValue; } +} \ No newline at end of file From cfff6f39fc0603d1850cce131c606480425c8e99 Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Thu, 9 Nov 2023 23:15:08 +0800 Subject: [PATCH 09/15] adjust server timezone --- src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs | 6 +++--- .../Snap.Hutao/Web/Hoyolab/ServerTimeZoneInfo.cs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs index cb9814bc..e525cdea 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs @@ -58,12 +58,12 @@ internal readonly struct PlayerUid public TimeZoneInfo GetTimeZoneInfo() { // 美服 UTC-05 - // 欧服 UTC+02 + // 欧服 UTC+01 // 其他 UTC+08 return Region switch { - "os_usa" => ServerTimeZoneInfo.UsaTimeZone, - "os_euro" => ServerTimeZoneInfo.EuroTimeZone, + "os_usa" => ServerTimeZoneInfo.AmericaTimeZone, + "os_euro" => ServerTimeZoneInfo.EuropeTimeZone, _ => ServerTimeZoneInfo.CommonTimeZone, }; } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerTimeZoneInfo.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerTimeZoneInfo.cs index 03d07838..a0ff4023 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerTimeZoneInfo.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerTimeZoneInfo.cs @@ -5,13 +5,13 @@ namespace Snap.Hutao.Web.Hoyolab; internal static class ServerTimeZoneInfo { - private static readonly TimeZoneInfo UsaTimeZoneValue = TimeZoneInfo.CreateCustomTimeZone("Server:UTC-05", new TimeSpan(-05, 0, 0), "UTC-05", "UTC-05"); - private static readonly TimeZoneInfo EuroTimeZoneValue = TimeZoneInfo.CreateCustomTimeZone("Server:UTC+02", new TimeSpan(+02, 0, 0), "UTC+02", "UTC+02"); + private static readonly TimeZoneInfo AmericaTimeZoneValue = TimeZoneInfo.CreateCustomTimeZone("Server:UTC-05", new TimeSpan(-05, 0, 0), "UTC-05", "UTC-05"); + private static readonly TimeZoneInfo EuropeTimeZoneValue = TimeZoneInfo.CreateCustomTimeZone("Server:UTC+01", new TimeSpan(+01, 0, 0), "UTC+01", "UTC+01"); private static readonly TimeZoneInfo CommonTimeZoneValue = TimeZoneInfo.CreateCustomTimeZone("Server:UTC+08", new TimeSpan(+08, 0, 0), "UTC+08", "UTC+08"); - public static TimeZoneInfo UsaTimeZone { get => UsaTimeZoneValue; } + public static TimeZoneInfo AmericaTimeZone { get => AmericaTimeZoneValue; } - public static TimeZoneInfo EuroTimeZone { get => EuroTimeZoneValue; } + public static TimeZoneInfo EuropeTimeZone { get => EuropeTimeZoneValue; } public static TimeZoneInfo CommonTimeZone { get => CommonTimeZoneValue; } } \ No newline at end of file From 3eb255639304474d6dd92ba9ccea0c82645e48cc Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Thu, 9 Nov 2023 23:39:51 +0800 Subject: [PATCH 10/15] update gacha info endpoints --- src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs | 29 ++++++++++--------- .../Snap.Hutao/Web/ApiOsEndpoints.cs | 2 +- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs b/src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs index 2f1f87c4..97637dd0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs @@ -321,19 +321,6 @@ internal static class ApiEndpoints public const string AnnContent = $"{Hk4eApiAnnouncementApi}/getAnnContent?{AnnouncementQuery}"; #endregion - #region Hk4eApiGachaInfoApi - - /// - /// 获取祈愿记录 - /// - /// query string - /// 祈愿记录信息Url - public static string GachaInfoGetGachaLog(string query) - { - return $"{Hk4eApiGachaInfoApi}/getGachaLog?{query}"; - } - #endregion - #region PassportApi | PassportApiV4 /// @@ -380,6 +367,19 @@ internal static class ApiEndpoints } #endregion + #region PublicOperationHk4eGachaInfoApi + + /// + /// 获取祈愿记录 + /// + /// query string + /// 祈愿记录信息Url + public static string GachaInfoGetGachaLog(string query) + { + return $"{PublicOperationHk4eGachaInfoApi}/getGachaLog?{query}"; + } + #endregion + #region SdkStaticLauncherApi /// @@ -434,6 +434,9 @@ internal static class ApiEndpoints private const string PublicDataApi = "https://public-data-api.mihoyo.com"; private const string PublicDataApiDeviceFpApi = $"{PublicDataApi}/device-fp/api"; + private const string PublicOperationHk4e = "https://public-operation-hk4e.mihoyo.com"; + private const string PublicOperationHk4eGachaInfoApi = $"{PublicOperationHk4e}/gacha_info/api"; + private const string SdkStatic = "https://sdk-static.mihoyo.com"; private const string SdkStaticLauncherApi = $"{SdkStatic}/hk4e_cn/mdk/launcher/api"; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/ApiOsEndpoints.cs b/src/Snap.Hutao/Snap.Hutao/Web/ApiOsEndpoints.cs index 676fb754..26cbc006 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/ApiOsEndpoints.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/ApiOsEndpoints.cs @@ -307,7 +307,7 @@ internal static class ApiOsEndpoints private const string BbsApiOsGameRecordAppApi = $"{BbsApiOs}/game_record/app/genshin/api"; private const string Hk4eApiOs = "https://hk4e-api-os.hoyoverse.com"; - private const string Hk4eApiOsGachaInfoApi = $"{Hk4eApiOs}/event/gacha_info/api"; + private const string Hk4eApiOsGachaInfoApi = $"{Hk4eApiOs}/gacha_info/api"; private const string SdkOsStatic = "https://sdk-os-static.mihoyo.com"; private const string SdkOsStaticLauncherApi = $"{SdkOsStatic}/hk4e_global/mdk/launcher/api"; From 7442f7f1ec5ca1adc298aa136f28989856b6a375 Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Fri, 10 Nov 2023 11:37:45 +0800 Subject: [PATCH 11/15] support UIGF v2.4-preview --- .../Json/Converter/DateTimeOffsetConverter.cs | 9 ++- .../Extension/DateTimeOffsetExtension.cs | 14 +++++ .../Model/InterChange/GachaLog/UIGF.cs | 35 +++++++++++- .../Model/InterChange/GachaLog/UIGFInfo.cs | 8 +++ .../Snap.Hutao/Resource/Localization/SH.resx | 3 + .../DailyNote/DailyNoteWebViewerSource.cs | 1 + .../ViewModel/GachaLog/SummaryItem.cs | 2 +- .../Snap.Hutao/Web/Hoyolab/PlayerUid.cs | 56 ++++++++++--------- .../Web/Hoyolab/PlayerUidExtension.cs | 18 ++++++ .../Web/Hoyolab/ServerTimeZoneInfo.cs | 9 +++ 10 files changed, 125 insertions(+), 30 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUidExtension.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/DateTimeOffsetConverter.cs b/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/DateTimeOffsetConverter.cs index d9585277..1d67f9ab 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/DateTimeOffsetConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/DateTimeOffsetConverter.cs @@ -7,6 +7,8 @@ namespace Snap.Hutao.Core.Json.Converter; /// /// 实现日期的转换 +/// 此转换器无法实现无损往返 +/// 必须在反序列化后调整 Offset /// [HighQuality] internal class DateTimeOffsetConverter : JsonConverter @@ -18,7 +20,10 @@ internal class DateTimeOffsetConverter : JsonConverter { if (reader.GetString() is { } dataTimeString) { - return DateTimeOffset.ParseExact(dataTimeString, Format, CultureInfo.CurrentCulture); + // By doing so, the DateTimeOffset parsed out will be a + // no offset datetime, and need to be adjusted later + DateTime dateTime = DateTime.ParseExact(dataTimeString, Format, CultureInfo.InvariantCulture); + return new DateTimeOffset(dateTime, default); } return default; @@ -27,6 +32,6 @@ internal class DateTimeOffsetConverter : JsonConverter /// public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) { - writer.WriteStringValue(value.ToLocalTime().ToString(Format, CultureInfo.CurrentCulture)); + writer.WriteStringValue(value.DateTime.ToString(Format, CultureInfo.InvariantCulture)); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/DateTimeOffsetExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/DateTimeOffsetExtension.cs index 867ef104..7c5ab181 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/DateTimeOffsetExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/DateTimeOffsetExtension.cs @@ -42,4 +42,18 @@ internal static class DateTimeOffsetExtension return defaultValue; } } + + [SuppressMessage("", "SH002")] + public static unsafe DateTimeOffset UnsafeAdjustOffsetOnly(this DateTimeOffset dateTimeOffset, in TimeSpan offset) + { + UnsafeWritableDateTimeOffset* pUnsafe = (UnsafeWritableDateTimeOffset*)&dateTimeOffset; + pUnsafe->OffsetMinutes = (short)(offset.Ticks / TimeSpan.TicksPerMinute); + return dateTimeOffset; + } + + private struct UnsafeWritableDateTimeOffset + { + public DateTime DateTime; + public short OffsetMinutes; + } } \ No newline at end of file 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 4ded9b4b..db48065e 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,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Web.Hoyolab; using System.Runtime.InteropServices; namespace Snap.Hutao.Model.InterChange.GachaLog; @@ -10,12 +11,12 @@ namespace Snap.Hutao.Model.InterChange.GachaLog; /// https://uigf.org/standards/UIGF.html /// [HighQuality] -internal sealed class UIGF +internal sealed class UIGF : IJsonOnSerializing, IJsonOnDeserialized { /// /// 当前版本 /// - public const string CurrentVersion = "v2.3"; + public const string CurrentVersion = "v2.4"; /// /// 信息 @@ -30,6 +31,25 @@ internal sealed class UIGF [JsonPropertyName("list")] public List List { get; set; } = default!; + public void OnSerializing() + { + TimeSpan offset = GetRegionTimeZoneUtcOffset(); + foreach (UIGFItem item in List) + { + item.Time = item.Time.ToOffset(offset); + } + } + + public void OnDeserialized() + { + // Adjust items timezone + TimeSpan offset = GetRegionTimeZoneUtcOffset(); + foreach (UIGFItem item in List) + { + item.Time = item.Time.UnsafeAdjustOffsetOnly(offset); + } + } + /// /// 确认当前UIGF对象的版本是否受支持 /// @@ -42,6 +62,7 @@ internal sealed class UIGF "v2.1" => UIGFVersion.Major2Minor2OrLower, "v2.2" => UIGFVersion.Major2Minor2OrLower, "v2.3" => UIGFVersion.Major2Minor3OrHigher, + "v2.4" => UIGFVersion.Major2Minor3OrHigher, _ => UIGFVersion.NotSupported, }; @@ -82,4 +103,14 @@ internal sealed class UIGF id = 0; return true; } + + private TimeSpan GetRegionTimeZoneUtcOffset() + { + if (Info.RegionTimeZone is int offsetHours) + { + return new TimeSpan(offsetHours, 0, 0); + } + + return PlayerUid.GetRegionTimeZoneUtcOffset(Info.Uid); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFInfo.cs b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFInfo.cs index 0b548800..f6a0a9cc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFInfo.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFInfo.cs @@ -4,6 +4,7 @@ using Snap.Hutao.Core; using Snap.Hutao.Core.Abstraction; using Snap.Hutao.Service.Metadata; +using Snap.Hutao.Web.Hoyolab; namespace Snap.Hutao.Model.InterChange.GachaLog; @@ -58,6 +59,12 @@ internal sealed class UIGFInfo : IMappingFrom + /// 时区偏移 + /// + [JsonPropertyName("region_time_zone")] + public int? RegionTimeZone { get; set; } = default!; + public static UIGFInfo From(RuntimeOptions runtimeOptions, MetadataOptions metadataOptions, string uid) { return new() @@ -68,6 +75,7 @@ internal sealed class UIGFInfo : IMappingFrom 下载链接复制成功 + + 无效的 UID + 验证失败,请手动验证或前往「米游社-我的角色」页面查看 diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteWebViewerSource.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteWebViewerSource.cs index 64767b36..830c060b 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteWebViewerSource.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteWebViewerSource.cs @@ -5,6 +5,7 @@ using Microsoft.Web.WebView2.Core; using Snap.Hutao.View.Control; using Snap.Hutao.ViewModel.User; using Snap.Hutao.Web.Bridge; +using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Request.QueryString; namespace Snap.Hutao.ViewModel.DailyNote; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/SummaryItem.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/SummaryItem.cs index e25c02f0..21f31527 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/SummaryItem.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/SummaryItem.cs @@ -36,7 +36,7 @@ internal sealed class SummaryItem : Item /// public string TimeFormatted { - get => $"{Time:yyy.MM.dd HH:mm:ss}"; + get => $"{Time.ToLocalTime():yyy.MM.dd HH:mm:ss}"; } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs index e525cdea..aa632bf2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs @@ -1,7 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Web.Request.QueryString; +using System.Text.RegularExpressions; using Windows.ApplicationModel.Store.Preview.InstallControl; namespace Snap.Hutao.Web.Hoyolab; @@ -10,7 +10,7 @@ namespace Snap.Hutao.Web.Hoyolab; /// 玩家 Uid /// [HighQuality] -internal readonly struct PlayerUid +internal readonly partial struct PlayerUid { /// /// UID 的实际值 @@ -29,65 +29,68 @@ internal readonly struct PlayerUid /// 服务器,当提供该参数时会无条件信任 public PlayerUid(string value, string? region = default) { - Must.Argument(value.Length == 9, "uid 应为9位数字"); + Must.Argument(UidRegex().IsMatch(value), SH.WebHoyolabInvalidUid); Value = value; Region = region ?? EvaluateRegion(value.AsSpan()[0]); } public static implicit operator PlayerUid(string source) { - return new(source); + return FromUidString(source); + } + + public static PlayerUid FromUidString(string uid) + { + return new(uid); } /// /// 判断是否为国际服 - /// We make this a static method rather than property, - /// to avoid unnecessary memory allocation. /// /// uid /// 是否为国际服 public static bool IsOversea(string uid) { - return uid[0] switch + // We make this a static method rather than property, + // to avoid unnecessary memory allocation (Region field). + Must.Argument(UidRegex().IsMatch(uid), SH.WebHoyolabInvalidUid); + + return uid.AsSpan()[0] switch { >= '1' and <= '5' => false, _ => true, }; } - public TimeZoneInfo GetTimeZoneInfo() + public static TimeZoneInfo GetTimeZoneInfo(string uid) { + // We make this a static method rather than property, + // to avoid unnecessary memory allocation (Region field). + Must.Argument(UidRegex().IsMatch(uid), SH.WebHoyolabInvalidUid); + // 美服 UTC-05 // 欧服 UTC+01 // 其他 UTC+08 - return Region switch + return uid.AsSpan()[0] switch { - "os_usa" => ServerTimeZoneInfo.AmericaTimeZone, - "os_euro" => ServerTimeZoneInfo.EuropeTimeZone, + '6' => ServerTimeZoneInfo.AmericaTimeZone, + '7' => ServerTimeZoneInfo.EuropeTimeZone, _ => ServerTimeZoneInfo.CommonTimeZone, }; } + public static TimeSpan GetRegionTimeZoneUtcOffset(string uid) + { + return GetTimeZoneInfo(uid).BaseUtcOffset; + } + /// public override string ToString() { return Value; } - /// - /// 转换到查询字符串 - /// - /// 查询字符串 - public QueryString ToQueryString() - { - QueryString queryString = new(); - queryString.Set("role_id", Value); - queryString.Set("server", Region); - - return queryString; - } - - private static string EvaluateRegion(char first) + private static string EvaluateRegion(in char first) { return first switch { @@ -103,4 +106,7 @@ internal readonly struct PlayerUid _ => throw Must.NeverHappen(), }; } + + [GeneratedRegex("[1-9][0-9]{8}")] + private static partial Regex UidRegex(); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUidExtension.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUidExtension.cs new file mode 100644 index 00000000..4dcb9eda --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUidExtension.cs @@ -0,0 +1,18 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Web.Request.QueryString; + +namespace Snap.Hutao.Web.Hoyolab; + +internal static class PlayerUidExtension +{ + public static QueryString ToQueryString(this in PlayerUid playerUid) + { + QueryString queryString = new(); + queryString.Set("role_id", playerUid.Value); + queryString.Set("server", playerUid.Region); + + return queryString; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerTimeZoneInfo.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerTimeZoneInfo.cs index a0ff4023..f05d395c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerTimeZoneInfo.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerTimeZoneInfo.cs @@ -9,9 +9,18 @@ internal static class ServerTimeZoneInfo private static readonly TimeZoneInfo EuropeTimeZoneValue = TimeZoneInfo.CreateCustomTimeZone("Server:UTC+01", new TimeSpan(+01, 0, 0), "UTC+01", "UTC+01"); private static readonly TimeZoneInfo CommonTimeZoneValue = TimeZoneInfo.CreateCustomTimeZone("Server:UTC+08", new TimeSpan(+08, 0, 0), "UTC+08", "UTC+08"); + /// + /// UTC-05 + /// public static TimeZoneInfo AmericaTimeZone { get => AmericaTimeZoneValue; } + /// + /// UTC+01 + /// public static TimeZoneInfo EuropeTimeZone { get => EuropeTimeZoneValue; } + /// + /// UTC+08 + /// public static TimeZoneInfo CommonTimeZone { get => CommonTimeZoneValue; } } \ No newline at end of file From 26d23fec7fc7e4610a2d4bf0ea240749ffab1a5f Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Fri, 10 Nov 2023 11:39:53 +0800 Subject: [PATCH 12/15] impl #830 in previous commit --- .../Snap.Hutao/Model/InterChange/GachaLog/UIGF.cs | 10 ---------- 1 file changed, 10 deletions(-) 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 db48065e..927930cf 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGF.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGF.cs @@ -50,11 +50,6 @@ internal sealed class UIGF : IJsonOnSerializing, IJsonOnDeserialized } } - /// - /// 确认当前UIGF对象的版本是否受支持 - /// - /// 版本 - /// 当前UIAF对象是否受支持 public bool IsCurrentVersionSupported(out UIGFVersion version) { version = Info.UIGFVersion switch @@ -69,11 +64,6 @@ internal sealed class UIGF : IJsonOnSerializing, IJsonOnDeserialized return version != UIGFVersion.NotSupported; } - /// - /// 列表物品是否正常 - /// - /// 首个出错的Id - /// 是否正常 public bool IsMajor2Minor2OrLowerListValid([NotNullWhen(false)] out long id) { foreach (ref readonly UIGFItem item in CollectionsMarshal.AsSpan(List)) From f97bc344d0ec39036227641b92f73e0683e2751f Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Fri, 10 Nov 2023 14:20:44 +0800 Subject: [PATCH 13/15] fix announcement time incorrectness --- .../Extension/DateTimeOffsetExtension.cs | 14 ---------- .../Extension/UnsafeDateTimeOffset.cs | 18 +++++++++++++ .../Model/InterChange/GachaLog/UIGF.cs | 4 ++- .../Hk4e/Common/Announcement/Announcement.cs | 2 ++ .../Announcement/AnnouncementWrapper.cs | 16 +++++++++++- .../Snap.Hutao/Web/Hoyolab/PlayerUid.cs | 16 ++++-------- .../Web/Hoyolab/ServerRegionTimeZone.cs | 26 +++++++++++++++++++ .../Web/Hoyolab/ServerTimeZoneInfo.cs | 26 ------------------- 8 files changed, 69 insertions(+), 53 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Extension/UnsafeDateTimeOffset.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerRegionTimeZone.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerTimeZoneInfo.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/DateTimeOffsetExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/DateTimeOffsetExtension.cs index 7c5ab181..867ef104 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/DateTimeOffsetExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/DateTimeOffsetExtension.cs @@ -42,18 +42,4 @@ internal static class DateTimeOffsetExtension return defaultValue; } } - - [SuppressMessage("", "SH002")] - public static unsafe DateTimeOffset UnsafeAdjustOffsetOnly(this DateTimeOffset dateTimeOffset, in TimeSpan offset) - { - UnsafeWritableDateTimeOffset* pUnsafe = (UnsafeWritableDateTimeOffset*)&dateTimeOffset; - pUnsafe->OffsetMinutes = (short)(offset.Ticks / TimeSpan.TicksPerMinute); - return dateTimeOffset; - } - - private struct UnsafeWritableDateTimeOffset - { - public DateTime DateTime; - public short OffsetMinutes; - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/UnsafeDateTimeOffset.cs b/src/Snap.Hutao/Snap.Hutao/Extension/UnsafeDateTimeOffset.cs new file mode 100644 index 00000000..15e57cc4 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Extension/UnsafeDateTimeOffset.cs @@ -0,0 +1,18 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Extension; + +internal struct UnsafeDateTimeOffset +{ + private DateTime dateTime; + private short offsetMinutes; + + [SuppressMessage("", "SH002")] + public static unsafe DateTimeOffset AdjustOffsetOnly(DateTimeOffset dateTimeOffset, in TimeSpan offset) + { + UnsafeDateTimeOffset* pUnsafe = (UnsafeDateTimeOffset*)&dateTimeOffset; + pUnsafe->offsetMinutes = (short)(offset.Ticks / TimeSpan.TicksPerMinute); + return dateTimeOffset; + } +} \ No newline at end of file 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 927930cf..a29b8471 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGF.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGF.cs @@ -34,6 +34,7 @@ internal sealed class UIGF : IJsonOnSerializing, IJsonOnDeserialized public void OnSerializing() { TimeSpan offset = GetRegionTimeZoneUtcOffset(); + foreach (UIGFItem item in List) { item.Time = item.Time.ToOffset(offset); @@ -44,9 +45,10 @@ internal sealed class UIGF : IJsonOnSerializing, IJsonOnDeserialized { // Adjust items timezone TimeSpan offset = GetRegionTimeZoneUtcOffset(); + foreach (UIGFItem item in List) { - item.Time = item.Time.UnsafeAdjustOffsetOnly(offset); + item.Time = UnsafeDateTimeOffset.AdjustOffsetOnly(item.Time, offset); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs index 68b20263..42947ed9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/Announcement.cs @@ -9,6 +9,7 @@ namespace Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement; [HighQuality] internal sealed class Announcement : AnnouncementContent { + #region Binding /// /// 是否应展示时间 /// @@ -81,6 +82,7 @@ internal sealed class Announcement : AnnouncementContent { get => $"{StartTime:yyyy.MM.dd HH:mm} - {EndTime:yyyy.MM.dd HH:mm}"; } + #endregion /// /// 类型标签 diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementWrapper.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementWrapper.cs index e27a29da..ce675358 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementWrapper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Common/Announcement/AnnouncementWrapper.cs @@ -9,7 +9,7 @@ namespace Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement; /// 公告包装器 /// [HighQuality] -internal sealed class AnnouncementWrapper : ListWrapper +internal sealed class AnnouncementWrapper : ListWrapper, IJsonOnDeserialized { /// /// 总数 @@ -46,4 +46,18 @@ internal sealed class AnnouncementWrapper : ListWrapper /// [JsonPropertyName("t")] public string TimeStamp { get; set; } = default!; + + public void OnDeserialized() + { + TimeSpan offset = new(TimeZone, 0, 0); + + foreach (AnnouncementListWrapper wrapper in List) + { + foreach (Announcement item in wrapper.List) + { + item.StartTime = UnsafeDateTimeOffset.AdjustOffsetOnly(item.StartTime, offset); + item.EndTime = UnsafeDateTimeOffset.AdjustOffsetOnly(item.EndTime, offset); + } + } + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs index aa632bf2..9dbe74ee 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/PlayerUid.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using System.Text.RegularExpressions; -using Windows.ApplicationModel.Store.Preview.InstallControl; namespace Snap.Hutao.Web.Hoyolab; @@ -62,7 +61,7 @@ internal readonly partial struct PlayerUid }; } - public static TimeZoneInfo GetTimeZoneInfo(string uid) + public static TimeSpan GetRegionTimeZoneUtcOffset(string uid) { // We make this a static method rather than property, // to avoid unnecessary memory allocation (Region field). @@ -73,17 +72,12 @@ internal readonly partial struct PlayerUid // 其他 UTC+08 return uid.AsSpan()[0] switch { - '6' => ServerTimeZoneInfo.AmericaTimeZone, - '7' => ServerTimeZoneInfo.EuropeTimeZone, - _ => ServerTimeZoneInfo.CommonTimeZone, + '6' => ServerRegionTimeZone.AmericaServerOffset, + '7' => ServerRegionTimeZone.EuropeServerOffset, + _ => ServerRegionTimeZone.CommonOffset, }; } - public static TimeSpan GetRegionTimeZoneUtcOffset(string uid) - { - return GetTimeZoneInfo(uid).BaseUtcOffset; - } - /// public override string ToString() { @@ -107,6 +101,6 @@ internal readonly partial struct PlayerUid }; } - [GeneratedRegex("[1-9][0-9]{8}")] + [GeneratedRegex("^[1-9][0-9]{8}$")] private static partial Regex UidRegex(); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerRegionTimeZone.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerRegionTimeZone.cs new file mode 100644 index 00000000..39bed842 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerRegionTimeZone.cs @@ -0,0 +1,26 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Web.Hoyolab; + +internal static class ServerRegionTimeZone +{ + private static readonly TimeSpan AmericaOffsetValue = new(-05, 0, 0); + private static readonly TimeSpan EuropeOffsetValue = new(+01, 0, 0); + private static readonly TimeSpan CommonOffsetValue = new(+08, 0, 0); + + /// + /// UTC-05 + /// + public static TimeSpan AmericaServerOffset { get => AmericaOffsetValue; } + + /// + /// UTC+01 + /// + public static TimeSpan EuropeServerOffset { get => EuropeOffsetValue; } + + /// + /// UTC+08 + /// + public static TimeSpan CommonOffset { get => CommonOffsetValue; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerTimeZoneInfo.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerTimeZoneInfo.cs deleted file mode 100644 index f05d395c..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ServerTimeZoneInfo.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -namespace Snap.Hutao.Web.Hoyolab; - -internal static class ServerTimeZoneInfo -{ - private static readonly TimeZoneInfo AmericaTimeZoneValue = TimeZoneInfo.CreateCustomTimeZone("Server:UTC-05", new TimeSpan(-05, 0, 0), "UTC-05", "UTC-05"); - private static readonly TimeZoneInfo EuropeTimeZoneValue = TimeZoneInfo.CreateCustomTimeZone("Server:UTC+01", new TimeSpan(+01, 0, 0), "UTC+01", "UTC+01"); - private static readonly TimeZoneInfo CommonTimeZoneValue = TimeZoneInfo.CreateCustomTimeZone("Server:UTC+08", new TimeSpan(+08, 0, 0), "UTC+08", "UTC+08"); - - /// - /// UTC-05 - /// - public static TimeZoneInfo AmericaTimeZone { get => AmericaTimeZoneValue; } - - /// - /// UTC+01 - /// - public static TimeZoneInfo EuropeTimeZone { get => EuropeTimeZoneValue; } - - /// - /// UTC+08 - /// - public static TimeZoneInfo CommonTimeZone { get => CommonTimeZoneValue; } -} \ No newline at end of file From d4549581c1cc111012bb2365412038f26269b5e8 Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Fri, 10 Nov 2023 15:33:11 +0800 Subject: [PATCH 14/15] fix exception capture --- ...rQueueSynchronizationContextSendSupport.cs | 33 ------------------- .../Snap.Hutao/Core/Threading/TaskContext.cs | 2 +- .../Extension/UnsafeDateTimeOffset.cs | 2 ++ .../Factory/Progress/IProgressFactory.cs | 5 ++- .../Factory/Progress/ProgressFactory.cs | 5 ++- .../Service/Game/Scheme/LaunchScheme.cs | 5 --- .../ViewModel/SpiralAbyss/MonsterView.cs | 12 +++---- src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs | 1 - .../Hk4e/Common/Announcement/Announcement.cs | 2 ++ 9 files changed, 19 insertions(+), 48 deletions(-) delete mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueSynchronizationContextSendSupport.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueSynchronizationContextSendSupport.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueSynchronizationContextSendSupport.cs deleted file mode 100644 index fe45d9b1..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/Threading/DispatcherQueueSynchronizationContextSendSupport.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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 44d97e0e..c0d5e145 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 DispatcherQueueSynchronizationContextSendSupport synchronizationContext; + private readonly DispatcherQueueSynchronizationContext synchronizationContext; private readonly DispatcherQueue dispatcherQueue; /// diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/UnsafeDateTimeOffset.cs b/src/Snap.Hutao/Snap.Hutao/Extension/UnsafeDateTimeOffset.cs index 15e57cc4..854b29eb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/UnsafeDateTimeOffset.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/UnsafeDateTimeOffset.cs @@ -8,6 +8,8 @@ internal struct UnsafeDateTimeOffset private DateTime dateTime; private short offsetMinutes; + public DateTime DateTime { readonly get => dateTime; set => dateTime = value; } + [SuppressMessage("", "SH002")] public static unsafe DateTimeOffset AdjustOffsetOnly(DateTimeOffset dateTimeOffset, in TimeSpan offset) { diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/Progress/IProgressFactory.cs b/src/Snap.Hutao/Snap.Hutao/Factory/Progress/IProgressFactory.cs index 016c2235..a084afb4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Factory/Progress/IProgressFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Factory/Progress/IProgressFactory.cs @@ -1,4 +1,7 @@ -namespace Snap.Hutao.Factory.Progress; +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Factory.Progress; internal interface IProgressFactory { diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/Progress/ProgressFactory.cs b/src/Snap.Hutao/Snap.Hutao/Factory/Progress/ProgressFactory.cs index 197845e9..fe39a0c7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Factory/Progress/ProgressFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Factory/Progress/ProgressFactory.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs index 6b9ad778..244c31fb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs @@ -67,11 +67,6 @@ internal class LaunchScheme : IEquatable }; } - /// - /// 多通道相等 - /// - /// 多通道 - /// 是否相等 [SuppressMessage("", "SH002")] public bool Equals(ChannelOptions other) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/MonsterView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/MonsterView.cs index b7f6bf8a..d9a47f8c 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/MonsterView.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/MonsterView.cs @@ -11,7 +11,7 @@ namespace Snap.Hutao.ViewModel.SpiralAbyss; internal sealed class MonsterView : INameIcon, IMappingFrom { - private MonsterView(MonsterRelationshipId id) + private MonsterView(in MonsterRelationshipId id) { Name = $"Unknown {id}"; Icon = Web.HutaoEndpoints.UIIconNone; @@ -27,11 +27,6 @@ internal sealed class MonsterView : INameIcon, IMappingFrom [HighQuality] +[SuppressMessage("", "SA1124")] internal sealed class Announcement : AnnouncementContent { #region Binding + /// /// 是否应展示时间 /// From 3479a1916457f8d51bae24b008f861afcd8fd4ad Mon Sep 17 00:00:00 2001 From: Masterain Date: Thu, 9 Nov 2023 23:39:00 -0800 Subject: [PATCH 15/15] New Crowdin updates (#1078) Co-authored-by: DismissedLight <1686188646@qq.com> --- .../Resource/Localization/SH.en.resx | 17 +++++++++++++- .../Resource/Localization/SH.ja.resx | 23 +++++++++++++++---- .../Resource/Localization/SH.ko.resx | 17 +++++++++++++- .../Resource/Localization/SH.zh-Hant.resx | 17 +++++++++++++- 4 files changed, 67 insertions(+), 7 deletions(-) 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 36342172..3dae5ac9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx @@ -2300,6 +2300,15 @@ Enable Advanced Features + + Change Auto Click Shortcut + + + Auto Click + + + Shortcut Keys + Official Website @@ -2526,7 +2535,7 @@ Current user - My Characters + Official Tools Web Login @@ -2741,6 +2750,12 @@ Weapon Event Wish + + Copy Link Successful + + + Invalid UID + Verification failed. Please verify manually or check MiHoYo BBS - My Characters page 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 26444c37..317e4241 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ja.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ja.resx @@ -2022,7 +2022,7 @@ ファイル - 进程间 + インタープロセス 指定したディスプレイで実行 @@ -2040,10 +2040,10 @@ ゲームオプション - 在游戏启动后尝试启动并使用 Starward 进行游戏时长统计 + ゲームの開始後にStarward ランチャーを起動し、プレイ時間の統計を確認してみてください。 - 时长统计 + プレイ時間 プロセス @@ -2300,6 +2300,15 @@ 上級者向け設定を有効にする + + オートクリック機能のショートカットキーを変更します + + + オートクリック + + + ショートカットキー + 公式サイト @@ -2526,7 +2535,7 @@ 現在のユーザー - マイ キャラクター + 旅行ツール ウェブ上でログイン @@ -2741,6 +2750,12 @@ イベント祈願・武器 + + ダウンロードリンクのコピーに成功しました + + + 无效的 UID + 認証に失敗しました。 手動で認証するか、MiHoYo BBS - 戦績 を確認してください。 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 ba406663..fdfce283 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ko.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.ko.resx @@ -2300,6 +2300,15 @@ 고급 기능 활성화 + + 更改自动连点功能的快捷键 + + + 自动连点 + + + 快捷键 + 공식 홈페이지로 이동 @@ -2526,7 +2535,7 @@ 当前用户 - 我的角色 + 旅行工具 웹 로그인 @@ -2741,6 +2750,12 @@ 무기 이벤트 기원 + + 下载链接复制成功 + + + 无效的 UID + 验证失败,请手动验证或前往「米游社-我的角色」页面查看 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 9690e7b9..28d4fe28 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 @@ -2300,6 +2300,15 @@ 啟動高級功能 + + 更改自动连点功能的快捷键 + + + 自动连点 + + + 快捷键 + 前往官網 @@ -2526,7 +2535,7 @@ 當前用戶 - 我的角色 + 旅行工具 網頁登陸 @@ -2741,6 +2750,12 @@ 武器活動祈願 + + 下载链接复制成功 + + + 无效的 UID + 验证失败,请手动验证或前往「米游社-我的角色」页面查看