diff --git a/src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/JsonSerializeTest.cs b/src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/JsonSerializeTest.cs index 42b0ca1d..309ef2bb 100644 --- a/src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/JsonSerializeTest.cs +++ b/src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/JsonSerializeTest.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs index bb79f0d1..6f874831 100644 --- a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs @@ -20,7 +20,24 @@ namespace Snap.Hutao; [SuppressMessage("", "SH001")] public sealed partial class App : Application { + private const string ConsoleBanner = """ + ---------------------------------------------------------------- + _____ _ _ _ + / ____| | | | | | | + | (___ _ __ __ _ _ __ | |__| | _ _ | |_ __ _ ___ + \___ \ | '_ \ / _` || '_ \ | __ || | | || __|/ _` | / _ \ + ____) || | | || (_| || |_) |_ | | | || |_| || |_| (_| || (_) | + |_____/ |_| |_| \__,_|| .__/(_)|_| |_| \__,_| \__|\__,_| \___/ + | | + |_| + + Snap.Hutao is a open source software developed by DGP Studio. + Copyright (C) 2022 - 2024 DGP Studio, All Rights Reserved. + ---------------------------------------------------------------- + """; + private const string AppInstanceKey = "main"; + private readonly IServiceProvider serviceProvider; private readonly IActivation activation; private readonly ILogger logger; @@ -51,6 +68,8 @@ public sealed partial class App : Application if (firstInstance.IsCurrent) { + logger.LogInformation(ConsoleBanner); + // manually invoke activation.NonRedirectToActivate(firstInstance, activatedEventArgs); activation.InitializeWith(firstInstance); diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Theme/Card.xaml b/src/Snap.Hutao/Snap.Hutao/Control/Theme/Card.xaml index d42eb68e..381d9610 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Theme/Card.xaml +++ b/src/Snap.Hutao/Snap.Hutao/Control/Theme/Card.xaml @@ -2,6 +2,22 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:cwm="using:CommunityToolkit.WinUI.Media"> + + + + + + + + - diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Theme/Uri.xaml b/src/Snap.Hutao/Snap.Hutao/Control/Theme/Uri.xaml index 3e50461f..4237308b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Theme/Uri.xaml +++ b/src/Snap.Hutao/Snap.Hutao/Control/Theme/Uri.xaml @@ -10,25 +10,25 @@ https://afdian.net/a/DismissedLight - https://static.snapgenshin.com/AvatarCard/UI_AvatarIcon_Costume_Card.png + https://api.snapgenshin.com/static/raw/AvatarCard/UI_AvatarIcon_Costume_Card.png - https://static.snapgenshin.com/Bg/UI_Icon_Intee_Explore_1.png - https://static.snapgenshin.com/Bg/UI_ImgSign_ItemIcon.png - https://static.snapgenshin.com/Bg/UI_ItemIcon_None.png - https://static.snapgenshin.com/Bg/UI_MarkQuest_Events_Proce.png - https://static.snapgenshin.com/Bg/UI_MarkTower.png + https://api.snapgenshin.com/static/raw/Bg/UI_Icon_Intee_Explore_1.png + https://api.snapgenshin.com/static/raw/Bg/UI_ImgSign_ItemIcon.png + https://api.snapgenshin.com/static/raw/Bg/UI_ItemIcon_None.png + https://api.snapgenshin.com/static/raw/Bg/UI_MarkQuest_Events_Proce.png + https://api.snapgenshin.com/static/raw/Bg/UI_MarkTower.png - https://static.snapgenshin.com/ItemIcon/UI_ItemIcon_201.png - https://static.snapgenshin.com/ItemIcon/UI_ItemIcon_204.png - https://static.snapgenshin.com/ItemIcon/UI_ItemIcon_210.png - https://static.snapgenshin.com/ItemIcon/UI_ItemIcon_220021.png + https://api.snapgenshin.com/static/raw/ItemIcon/UI_ItemIcon_201.png + https://api.snapgenshin.com/static/raw/ItemIcon/UI_ItemIcon_204.png + https://api.snapgenshin.com/static/raw/ItemIcon/UI_ItemIcon_210.png + https://api.snapgenshin.com/static/raw/ItemIcon/UI_ItemIcon_220021.png - https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon25.png - https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon71.png - https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon250.png - https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon272.png - https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon293.png + https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon25.png + https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon71.png + https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon250.png + https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon272.png + https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon293.png diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Abstraction/IDeconstruct.cs b/src/Snap.Hutao/Snap.Hutao/Core/Abstraction/IDeconstruct.cs index be6f3181..a0052c17 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Abstraction/IDeconstruct.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Abstraction/IDeconstruct.cs @@ -3,18 +3,12 @@ namespace Snap.Hutao.Core.Abstraction; -/// -/// 指示该类可以解构为元组 -/// -/// 元组的第一个类型 -/// 元组的第二个类型 -[HighQuality] internal interface IDeconstruct { - /// - /// 解构 - /// - /// 第一个元素 - /// 第二个元素 void Deconstruct(out T1 t1, out T2 t2); +} + +internal interface IDeconstruct +{ + void Deconstruct(out T1 t1, out T2 t2, out T3 t3); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs index 1f515abd..7799061a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs @@ -24,7 +24,7 @@ internal static class DependencyInjection ServiceProvider serviceProvider = new ServiceCollection() // Microsoft extension - .AddLogging(builder => builder.AddConsoleWindow()) + .AddLogging(builder => builder.AddDebug().AddConsoleWindow()) .AddMemoryCache() // Hutao extensions diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Dictionary.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Dictionary.cs index c6897d7f..bfd640f5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Dictionary.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.Dictionary.cs @@ -23,13 +23,6 @@ internal static partial class EnumerableExtension return true; } - /// - /// 增加计数 - /// - /// 键类型 - /// 值类型 - /// 字典 - /// 键 public static void IncreaseOne(this Dictionary dict, TKey key) where TKey : notnull where TValue : struct, IIncrementOperators @@ -37,14 +30,6 @@ internal static partial class EnumerableExtension ++CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _); } - /// - /// 增加计数 - /// - /// 键类型 - /// 值类型 - /// 字典 - /// 键 - /// 增加的值 public static void IncreaseValue(this Dictionary dict, TKey key, TValue value) where TKey : notnull where TValue : struct, IAdditionOperators @@ -54,14 +39,6 @@ internal static partial class EnumerableExtension current += value; } - /// - /// 增加计数 - /// - /// 键类型 - /// 值类型 - /// 字典 - /// 键 - /// 是否存在键值 public static bool TryIncreaseOne(this Dictionary dict, TKey key) where TKey : notnull where TValue : struct, IIncrementOperators @@ -76,7 +53,6 @@ internal static partial class EnumerableExtension return false; } - /// public static Dictionary ToDictionaryIgnoringDuplicateKeys(this IEnumerable source, Func keySelector) where TKey : notnull { @@ -90,7 +66,6 @@ internal static partial class EnumerableExtension return dictionary; } - /// public static Dictionary ToDictionaryIgnoringDuplicateKeys(this IEnumerable source, Func keySelector, Func elementSelector) where TKey : notnull { diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs index 3ff7c070..a9a99041 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs @@ -3,6 +3,7 @@ using System.Collections.ObjectModel; using System.Runtime.CompilerServices; +using System.Text; namespace Snap.Hutao.Extension; @@ -43,6 +44,63 @@ internal static partial class EnumerableExtension return first; } + public static string JoinToString(this IEnumerable source, char separator, Action selector) + { + StringBuilder resultBuilder = new(); + + IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return string.Empty; + } + + T first = enumerator.Current; + selector(resultBuilder, first); + + if (!enumerator.MoveNext()) + { + return resultBuilder.ToString(); + } + + do + { + resultBuilder.Append(separator); + selector(resultBuilder, enumerator.Current); + } + while (enumerator.MoveNext()); + + return resultBuilder.ToString(); + } + + public static string JoinToString(this IEnumerable> source, char separator, Action selector) + { + StringBuilder resultBuilder = new(); + + IEnumerator> enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return string.Empty; + } + + KeyValuePair first = enumerator.Current; + selector(resultBuilder, first.Key, first.Value); + + if (!enumerator.MoveNext()) + { + return resultBuilder.ToString(); + } + + do + { + resultBuilder.Append(separator); + KeyValuePair current = enumerator.Current; + selector(resultBuilder, current.Key, current.Value); + } + while (enumerator.MoveNext()); + + return resultBuilder.ToString(); + } + /// /// 转换到 /// @@ -64,7 +122,6 @@ internal static partial class EnumerableExtension /// Converted collection into string. public static string ToString(this IEnumerable collection, char separator) { - string result = string.Join(separator, collection); - return result.Length > 0 ? result : string.Empty; + return string.Join(separator, collection); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Extension/UserExtension.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Extension/UserExtension.cs new file mode 100644 index 00000000..66f2df90 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Extension/UserExtension.cs @@ -0,0 +1,19 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Model.Entity.Extension; + +internal static class UserExtension +{ + public static bool TryUpdateFingerprint(this User user, string? deviceFp) + { + if (string.IsNullOrEmpty(deviceFp)) + { + return false; + } + + user.Fingerprint = deviceFp; + user.FingerprintLastUpdateTime = DateTimeOffset.UtcNow; + return true; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Primitive/SchemeType.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Primitive/SchemeType.cs index 12971805..80696756 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Primitive/SchemeType.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Primitive/SchemeType.cs @@ -12,7 +12,7 @@ internal enum SchemeType /// /// 国际服 /// - Mihoyo, + Hoyoverse, /// /// 国服官服 diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.Constant.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.Constant.cs index 97474eb7..b18f0ef8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.Constant.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.Constant.cs @@ -13,6 +13,8 @@ internal sealed partial class SettingEntry /// public const string GamePath = "GamePath"; + public const string GamePathEntries = "GamePathEntries"; + /// /// PowerShell 路径 /// @@ -104,6 +106,8 @@ internal sealed partial class SettingEntry public const string LaunchIsMonitorEnabled = "Launch.IsMonitorEnabled"; + public const string LaunchIsUseCloudThirdPartyMobile = "Launch.IsUseCloudThirdPartyMobile"; + public const string LaunchUseStarwardPlayTimeStatistics = "Launch.UseStarwardPlayTimeStatistics"; public const string LaunchSetDiscordActivityWhenPlaying = "Launch.SetDiscordActivityWhenPlaying"; diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index ab31fad0..4a0d4ea4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -2045,6 +2045,9 @@ 无边框 + + 启用内置触摸布局,不会响应键鼠输入 + 与游戏内浏览器不兼容,切屏等操作也能使游戏闪退 @@ -2189,6 +2192,9 @@ 打开截图文件夹 + + 选择游戏路径 + 关于 胡桃 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs index e02352d9..aeb46ccc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs @@ -15,20 +15,12 @@ namespace Snap.Hutao.Service; [Injection(InjectAs.Singleton)] internal sealed partial class AppOptions : DbStoreOptions { - private string? gamePath; private string? powerShellPath; private bool? isEmptyHistoryWishVisible; private BackdropType? backdropType; private CultureInfo? currentCulture; - private bool? isAdvancedLaunchOptionsEnabled; private string? geetestCustomCompositeUrl; - public string GamePath - { - get => GetOption(ref gamePath, SettingEntry.GamePath); - set => SetOption(ref gamePath, SettingEntry.GamePath, value); - } - public string PowerShellPath { get @@ -80,14 +72,6 @@ internal sealed partial class AppOptions : DbStoreOptions set => SetOption(ref currentCulture, SettingEntry.Culture, value, value => value.Name); } - public bool IsAdvancedLaunchOptionsEnabled - { - // DO NOT MOVE TO OTHER CLASS - // We use this property in SettingPage binding - get => GetOption(ref isAdvancedLaunchOptionsEnabled, SettingEntry.IsAdvancedLaunchOptionsEnabled); - set => SetOption(ref isAdvancedLaunchOptionsEnabled, SettingEntry.IsAdvancedLaunchOptionsEnabled, value); - } - public string GeetestCustomCompositeUrl { get => GetOption(ref geetestCustomCompositeUrl, SettingEntry.GeetestCustomCompositeUrl); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppOptionsExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppOptionsExtension.cs index 0359ed1d..d05ac848 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AppOptionsExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppOptionsExtension.cs @@ -3,45 +3,11 @@ using Snap.Hutao.Model; using System.Globalization; -using System.IO; namespace Snap.Hutao.Service; internal static class AppOptionsExtension { - public static bool TryGetGameFolderAndFileName(this AppOptions appOptions, [NotNullWhen(true)] out string? gameFolder, [NotNullWhen(true)] out string? gameFileName) - { - string gamePath = appOptions.GamePath; - - gameFolder = Path.GetDirectoryName(gamePath); - if (string.IsNullOrEmpty(gameFolder)) - { - gameFileName = default; - return false; - } - - gameFileName = Path.GetFileName(gamePath); - if (string.IsNullOrEmpty(gameFileName)) - { - return false; - } - - return true; - } - - public static bool TryGetGamePathAndGameFileName(this AppOptions appOptions, out string gamePath, [NotNullWhen(true)] out string? gameFileName) - { - gamePath = appOptions.GamePath; - - gameFileName = Path.GetFileName(gamePath); - if (string.IsNullOrEmpty(gameFileName)) - { - return false; - } - - return true; - } - public static NameValue? GetCurrentCultureForSelectionOrDefault(this AppOptions appOptions) { return appOptions.Cultures.SingleOrDefault(c => c.Value == appOptions.CurrentCulture); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/RegistryInterop.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/RegistryInterop.cs index 513fb4a2..bc145fe9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/RegistryInterop.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/RegistryInterop.cs @@ -41,8 +41,7 @@ internal static class RegistryInterop string base64 = Convert.ToBase64String(target); string path = $"HKCU:{GenshinPath}"; string command = $""" - $value = [Convert]::FromBase64String('{base64}'); - Set-ItemProperty -Path '{path}' -Name '{SdkChineseKey}' -Value $value -Force; + -Command "$value = [Convert]::FromBase64String('{base64}'); Set-ItemProperty -Path '{path}' -Name '{SdkChineseKey}' -Value $value -Force;" """; ProcessStartInfo startInfo = new() diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs index 80fd68d3..2b63c89a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs @@ -13,11 +13,11 @@ namespace Snap.Hutao.Service.Game.Configuration; [Injection(InjectAs.Singleton, typeof(IGameChannelOptionsService))] internal sealed partial class GameChannelOptionsService : IGameChannelOptionsService { - private readonly AppOptions appOptions; + private readonly LaunchOptions launchOptions; public ChannelOptions GetChannelOptions() { - string gamePath = appOptions.GamePath; + string gamePath = launchOptions.GamePath; string configPath = Path.Combine(Path.GetDirectoryName(gamePath) ?? string.Empty, ConfigFileName); bool isOversea = string.Equals(Path.GetFileName(gamePath), GenshinImpactFileName, StringComparison.OrdinalIgnoreCase); @@ -38,7 +38,7 @@ internal sealed partial class GameChannelOptionsService : IGameChannelOptionsSer public bool SetChannelOptions(LaunchScheme scheme) { - string gamePath = appOptions.GamePath; + string gamePath = launchOptions.GamePath; string? directory = Path.GetDirectoryName(gamePath); ArgumentException.ThrowIfNullOrEmpty(directory); string configPath = Path.Combine(directory, ConfigFileName); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameServiceFacade.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameServiceFacade.cs index cb20776b..115d1ee7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameServiceFacade.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameServiceFacade.cs @@ -5,6 +5,7 @@ using Snap.Hutao.Model.Entity; using Snap.Hutao.Service.Game.Account; using Snap.Hutao.Service.Game.Configuration; using Snap.Hutao.Service.Game.Package; +using Snap.Hutao.Service.Game.PathAbstraction; using Snap.Hutao.Service.Game.Process; using Snap.Hutao.Service.Game.Scheme; using System.Collections.ObjectModel; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs index 4241bba3..322a788f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs @@ -6,6 +6,8 @@ using Microsoft.UI.Windowing; using Snap.Hutao.Model; using Snap.Hutao.Model.Entity; using Snap.Hutao.Service.Abstraction; +using Snap.Hutao.Service.Game.PathAbstraction; +using System.Collections.Immutable; using System.Globalization; using Windows.Graphics; using Windows.Win32.Foundation; @@ -24,7 +26,10 @@ internal sealed class LaunchOptions : DbStoreOptions private readonly int primaryScreenHeight; private readonly int primaryScreenFps; + private string? gamePath; + private ImmutableList? gamePathEntries; private bool? isEnabled; + private bool? isAdvancedLaunchOptionsEnabled; private bool? isFullScreen; private bool? isBorderless; private bool? isExclusive; @@ -36,6 +41,7 @@ internal sealed class LaunchOptions : DbStoreOptions private int? targetFps; private NameValue? monitor; private bool? isMonitorEnabled; + private bool? isUseCloudThirdPartyMobile; private AspectRatio? selectedAspectRatio; private bool? useStarwardPlayTimeStatistics; private bool? setDiscordActivityWhenPlaying; @@ -85,12 +91,32 @@ internal sealed class LaunchOptions : DbStoreOptions } } + public string GamePath + { + get => GetOption(ref gamePath, SettingEntry.GamePath); + set => SetOption(ref gamePath, SettingEntry.GamePath, value); + } + + public ImmutableList GamePathEntries + { + // Because DbStoreOptions can't detect collection change, We use + // ImmutableList to imply that the whole list needs to be replaced + get => GetOption(ref gamePathEntries, SettingEntry.GamePathEntries, raw => JsonSerializer.Deserialize>(raw), []); + set => SetOption(ref gamePathEntries, SettingEntry.GamePathEntries, value, value => JsonSerializer.Serialize(value)); + } + public bool IsEnabled { get => GetOption(ref isEnabled, SettingEntry.LaunchIsLaunchOptionsEnabled, true); set => SetOption(ref isEnabled, SettingEntry.LaunchIsLaunchOptionsEnabled, value); } + public bool IsAdvancedLaunchOptionsEnabled + { + get => GetOption(ref isAdvancedLaunchOptionsEnabled, SettingEntry.IsAdvancedLaunchOptionsEnabled); + set => SetOption(ref isAdvancedLaunchOptionsEnabled, SettingEntry.IsAdvancedLaunchOptionsEnabled, value); + } + public bool IsFullScreen { get => GetOption(ref isFullScreen, SettingEntry.LaunchIsFullScreen); @@ -166,6 +192,12 @@ internal sealed class LaunchOptions : DbStoreOptions set => SetOption(ref isMonitorEnabled, SettingEntry.LaunchIsMonitorEnabled, value); } + public bool IsUseCloudThirdPartyMobile + { + get => GetOption(ref isUseCloudThirdPartyMobile, SettingEntry.LaunchIsUseCloudThirdPartyMobile, false); + set => SetOption(ref isUseCloudThirdPartyMobile, SettingEntry.LaunchIsUseCloudThirdPartyMobile, value); + } + public List AspectRatios { get; } = [ new(2560, 1440), diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptionsExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptionsExtension.cs new file mode 100644 index 00000000..5913e00e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptionsExtension.cs @@ -0,0 +1,87 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Service.Game.PathAbstraction; +using System.Collections.Immutable; +using System.IO; + +namespace Snap.Hutao.Service.Game; + +internal static class LaunchOptionsExtension +{ + public static bool TryGetGameFolderAndFileName(this LaunchOptions options, [NotNullWhen(true)] out string? gameFolder, [NotNullWhen(true)] out string? gameFileName) + { + string gamePath = options.GamePath; + + gameFolder = Path.GetDirectoryName(gamePath); + if (string.IsNullOrEmpty(gameFolder)) + { + gameFileName = default; + return false; + } + + gameFileName = Path.GetFileName(gamePath); + if (string.IsNullOrEmpty(gameFileName)) + { + return false; + } + + return true; + } + + public static bool TryGetGamePathAndGameFileName(this LaunchOptions options, out string gamePath, [NotNullWhen(true)] out string? gameFileName) + { + gamePath = options.GamePath; + + gameFileName = Path.GetFileName(gamePath); + if (string.IsNullOrEmpty(gameFileName)) + { + return false; + } + + return true; + } + + public static ImmutableList GetGamePathEntries(this LaunchOptions options, out GamePathEntry? entry) + { + string gamePath = options.GamePath; + + if (string.IsNullOrEmpty(gamePath)) + { + entry = default; + return options.GamePathEntries; + } + + if (options.GamePathEntries.SingleOrDefault(entry => string.Equals(entry.Path, options.GamePath, StringComparison.OrdinalIgnoreCase)) is { } existed) + { + entry = existed; + return options.GamePathEntries; + } + + entry = GamePathEntry.Create(options.GamePath); + return [.. options.GamePathEntries, entry]; + } + + public static ImmutableList RemoveGamePathEntry(this LaunchOptions options, GamePathEntry? entry, out GamePathEntry? selected) + { + if (entry is not null) + { + if (string.Equals(options.GamePath, entry.Path, StringComparison.OrdinalIgnoreCase)) + { + options.GamePath = string.Empty; + } + + options.GamePathEntries = options.GamePathEntries.Remove(entry); + } + + return options.GetGamePathEntries(out selected); + } + + public static ImmutableList UpdateGamePathAndRefreshEntries(this LaunchOptions options, string gamePath) + { + options.GamePath = gamePath; + ImmutableList entries = options.GetGamePathEntries(out _); + options.GamePathEntries = entries; + return entries; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/GamePackageService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/GamePackageService.cs index f230c984..09be982f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/GamePackageService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/GamePackageService.cs @@ -16,12 +16,12 @@ internal sealed partial class GamePackageService : IGamePackageService { private readonly PackageConverter packageConverter; private readonly IServiceProvider serviceProvider; + private readonly LaunchOptions launchOptions; private readonly ITaskContext taskContext; - private readonly AppOptions appOptions; public async ValueTask EnsureGameResourceAsync(LaunchScheme launchScheme, IProgress progress) { - if (!appOptions.TryGetGameFolderAndFileName(out string? gameFolder, out string? gameFileName)) + if (!launchOptions.TryGetGameFolderAndFileName(out string? gameFolder, out string? gameFileName)) { return false; } @@ -58,7 +58,7 @@ internal sealed partial class GamePackageService : IGamePackageService string exeName = launchScheme.IsOversea ? GenshinImpactFileName : YuanShenFileName; await taskContext.SwitchToMainThreadAsync(); - appOptions.GamePath = Path.Combine(gameFolder, exeName); + launchOptions.UpdateGamePathAndRefreshEntries(Path.Combine(gameFolder, exeName)); } await packageConverter.EnsureDeprecatedFilesAndSdkAsync(resource, gameFolder).ConfigureAwait(false); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathEntry.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathEntry.cs new file mode 100644 index 00000000..8d8dfd2e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathEntry.cs @@ -0,0 +1,26 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.Game.PathAbstraction; + +internal sealed class GamePathEntry +{ + [JsonPropertyName("Path")] + public string Path { get; set; } = default!; + + [JsonIgnore] + public GamePathKind Kind { get => GetKind(Path); } + + public static GamePathEntry Create(string path) + { + return new() + { + Path = path, + }; + } + + private static GamePathKind GetKind(string path) + { + return GamePathKind.None; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathKind.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathKind.cs new file mode 100644 index 00000000..3271c606 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathKind.cs @@ -0,0 +1,13 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.Game.PathAbstraction; + +internal enum GamePathKind +{ + None, + ChineseClient, + OverseaClient, + ChineseCloud, + OverseaCloud, +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GamePathService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathService.cs similarity index 80% rename from src/Snap.Hutao/Snap.Hutao/Service/Game/GamePathService.cs rename to src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathService.cs index 7f773c77..a9294f1d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GamePathService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathService.cs @@ -3,19 +3,19 @@ using Snap.Hutao.Service.Game.Locator; -namespace Snap.Hutao.Service.Game; +namespace Snap.Hutao.Service.Game.PathAbstraction; [ConstructorGenerated] [Injection(InjectAs.Singleton, typeof(IGamePathService))] internal sealed partial class GamePathService : IGamePathService { private readonly IServiceProvider serviceProvider; - private readonly AppOptions appOptions; + private readonly LaunchOptions launchOptions; public async ValueTask> SilentGetGamePathAsync() { // Cannot find in setting - if (string.IsNullOrEmpty(appOptions.GamePath)) + if (string.IsNullOrEmpty(launchOptions.GamePath)) { IGameLocatorFactory locatorFactory = serviceProvider.GetRequiredService(); @@ -40,7 +40,7 @@ internal sealed partial class GamePathService : IGamePathService if (isOk) { // Save result. - appOptions.GamePath = path; + launchOptions.UpdateGamePathAndRefreshEntries(path); } else { @@ -48,9 +48,9 @@ internal sealed partial class GamePathService : IGamePathService } } - if (!string.IsNullOrEmpty(appOptions.GamePath)) + if (!string.IsNullOrEmpty(launchOptions.GamePath)) { - return new(true, appOptions.GamePath); + return new(true, launchOptions.GamePath); } else { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGamePathService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/IGamePathService.cs similarity index 79% rename from src/Snap.Hutao/Snap.Hutao/Service/Game/IGamePathService.cs rename to src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/IGamePathService.cs index c0b09ccc..3c01c01c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGamePathService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/IGamePathService.cs @@ -1,7 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -namespace Snap.Hutao.Service.Game; +namespace Snap.Hutao.Service.Game.PathAbstraction; internal interface IGamePathService { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Process/GameProcessService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Process/GameProcessService.cs index e9d6e28a..c2191176 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Process/GameProcessService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Process/GameProcessService.cs @@ -21,7 +21,6 @@ internal sealed partial class GameProcessService : IGameProcessService private readonly IDiscordService discordService; private readonly RuntimeOptions runtimeOptions; private readonly LaunchOptions launchOptions; - private readonly AppOptions appOptions; private volatile bool isGameRunning; @@ -52,7 +51,7 @@ internal sealed partial class GameProcessService : IGameProcessService return; } - if (!appOptions.TryGetGamePathAndGameFileName(out string gamePath, out string? gameFileName)) + if (!launchOptions.TryGetGamePathAndGameFileName(out string gamePath, out string? gameFileName)) { ArgumentException.ThrowIfNullOrEmpty(gamePath); return; // null check passing, actually never reach. @@ -73,7 +72,7 @@ internal sealed partial class GameProcessService : IGameProcessService await Starward.LaunchForPlayTimeStatisticsAsync(isOversea).ConfigureAwait(false); } - if (runtimeOptions.IsElevated && appOptions.IsAdvancedLaunchOptionsEnabled && launchOptions.UnlockFps) + if (runtimeOptions.IsElevated && launchOptions.IsAdvancedLaunchOptionsEnabled && launchOptions.UnlockFps) { progress.Report(new(LaunchPhase.UnlockingFps, SH.ServiceGameLaunchPhaseUnlockingFps)); try @@ -116,6 +115,7 @@ internal sealed partial class GameProcessService : IGameProcessService .AppendIf("-screen-width", launchOptions.IsScreenWidthEnabled, launchOptions.ScreenWidth) .AppendIf("-screen-height", launchOptions.IsScreenHeightEnabled, launchOptions.ScreenHeight) .AppendIf("-monitor", launchOptions.IsMonitorEnabled, launchOptions.Monitor.Value) + .AppendIf("-platform_type CLOUD_THIRD_PARTY_MOBILE", launchOptions.IsUseCloudThirdPartyMobile) .ToString(); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserCollectionService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserCollectionService.cs index 8db6973f..b34c8e67 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserCollectionService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserCollectionService.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using Snap.Hutao.ViewModel.User; -using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using System.Collections.ObjectModel; using BindingUser = Snap.Hutao.ViewModel.User.User; @@ -20,6 +19,8 @@ internal interface IUserCollectionService UserGameRole? GetUserGameRoleByUid(string uid); ValueTask RemoveUserAsync(BindingUser user); - ValueTask> TryCreateAndAddUserFromCookieAsync(Cookie cookie, bool isOversea); + + ValueTask> TryCreateAndAddUserFromInputCookieAsync(InputCookie inputCookie); + bool TryGetUserByMid(string mid, [NotNullWhen(true)] out BindingUser? user); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs index 03f62a30..442bd68b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs @@ -1,13 +1,11 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Web.Hoyolab; - namespace Snap.Hutao.Service.User; internal interface IUserInitializationService { - ValueTask CreateUserFromCookieOrDefaultAsync(Cookie cookie, bool isOversea, CancellationToken token = default(CancellationToken)); + ValueTask CreateUserFromInputCookieOrDefaultAsync(InputCookie inputCookie, CancellationToken token = default(CancellationToken)); ValueTask ResumeUserAsync(Model.Entity.User inner, CancellationToken token = default(CancellationToken)); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs index e89d0494..9174bb80 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using Snap.Hutao.ViewModel.User; -using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using System.Collections.ObjectModel; using BindingUser = Snap.Hutao.ViewModel.User.User; @@ -42,13 +41,7 @@ internal interface IUserService /// 对应的角色信息 UserGameRole? GetUserGameRoleByUid(string uid); - /// - /// 尝试异步处理输入的Cookie - /// - /// Cookie - /// 是否为国际服 - /// 处理的结果 - ValueTask> ProcessInputCookieAsync(Cookie cookie, bool isOversea); + ValueTask> ProcessInputCookieAsync(InputCookie inputCookie); ValueTask RefreshCookieTokenAsync(Model.Entity.User user); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/InputCookie.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/InputCookie.cs new file mode 100644 index 00000000..c09ba478 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/InputCookie.cs @@ -0,0 +1,48 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.Abstraction; +using Snap.Hutao.Web.Hoyolab; + +namespace Snap.Hutao.Service.User; + +internal sealed class InputCookie : IDeconstruct +{ + private InputCookie(Cookie cookie, bool isOversea) + { + Cookie = cookie; + IsOversea = isOversea; + cookie.TryGetDeviceFp(out string? deviceFp); + DeviceFp = deviceFp; + } + + private InputCookie(Cookie cookie, bool isOversea, string? deviceFp) + { + Cookie = cookie; + IsOversea = isOversea; + DeviceFp = deviceFp; + } + + public Cookie Cookie { get; } + + public bool IsOversea { get; } + + public string? DeviceFp { get; } + + public static InputCookie Create(Cookie cookie, bool isOversea, string? deviceFp) + { + return new InputCookie(cookie, isOversea, deviceFp); + } + + public static InputCookie CreateWithDeviceFpInference(Cookie cookie, bool isOversea) + { + return new InputCookie(cookie, isOversea); + } + + public void Deconstruct(out Cookie cookie, out bool isOversea, out string? deviceFp) + { + cookie = Cookie; + isOversea = IsOversea; + deviceFp = DeviceFp; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserCollectionService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserCollectionService.cs index d0f689cb..6ea7d8ff 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserCollectionService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserCollectionService.cs @@ -6,7 +6,6 @@ using Snap.Hutao.Core.Database; using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Message; using Snap.Hutao.ViewModel.User; -using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using System.Collections.ObjectModel; using BindingUser = Snap.Hutao.ViewModel.User.User; @@ -113,6 +112,11 @@ internal sealed partial class UserCollectionService : IUserCollectionService midUserMap?.Remove(user.Entity.Mid); } + foreach (UserGameRole role in user.UserGameRoles) + { + uidUserGameRoleMap?.Remove(role.GameUid); + } + // Sync database await taskContext.SwitchToBackgroundAsync(); await userDbService.DeleteUserByIdAsync(user.Entity.InnerId).ConfigureAwait(false); @@ -146,14 +150,14 @@ internal sealed partial class UserCollectionService : IUserCollectionService return midUserMap.TryGetValue(mid, out user); } - public async ValueTask> TryCreateAndAddUserFromCookieAsync(Cookie cookie, bool isOversea) + public async ValueTask> TryCreateAndAddUserFromInputCookieAsync(InputCookie inputCookie) { await taskContext.SwitchToBackgroundAsync(); - BindingUser? newUser = await userInitializationService.CreateUserFromCookieOrDefaultAsync(cookie, isOversea).ConfigureAwait(false); + BindingUser? newUser = await userInitializationService.CreateUserFromInputCookieOrDefaultAsync(inputCookie).ConfigureAwait(false); if (newUser is null) { - return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieRequestUserInfoFailed); + return new(UserOptionResult.CookieInvalid, SH.ServiceUserProcessCookieRequestUserInfoFailed); } await GetUserCollectionAsync().ConfigureAwait(false); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserFingerprintService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserFingerprintService.cs index a8a1f59e..4833570b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserFingerprintService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserFingerprintService.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.ViewModel.User; using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.PublicData.DeviceFp; using Snap.Hutao.Web.Response; @@ -21,7 +22,7 @@ internal sealed partial class UserFingerprintService : IUserFingerprintService return; } - if (user.Entity.FingerprintLastUpdateTime >= DateTimeOffset.UtcNow - TimeSpan.FromDays(3)) + if (user.Entity.FingerprintLastUpdateTime >= DateTimeOffset.UtcNow - TimeSpan.FromDays(7)) { if (!string.IsNullOrEmpty(user.Fingerprint)) { @@ -29,40 +30,62 @@ internal sealed partial class UserFingerprintService : IUserFingerprintService } } - string model = Core.Random.GetUpperAndNumberString(6); - Dictionary extendProperties = new() + string device = Core.Random.GetUpperAndNumberString(12); + string product = Core.Random.GetUpperAndNumberString(6); + Dictionary extendProperties = new() { - { "cpuType", "arm64-v8a" }, + { "proxyStatus", 0 }, + { "isRoot", 0 }, { "romCapacity", "512" }, - { "productName", model }, - { "romRemain", "256" }, - { "manufacturer", "XiaoMi" }, - { "appMemory", "512" }, + { "deviceName", device }, + { "productName", product }, + { "romRemain", "512" }, { "hostname", "dg02-pool03-kvm87" }, - { "screenSize", "1080x1920" }, - { "osVersion", "13" }, + { "screenSize", "1440x2905" }, + { "isTablet", 0 }, { "aaid", string.Empty }, - { "vendor", "中国移动" }, - { "accelerometer", "1.4883357x7.1712894x6.2847486" }, - { "buildTags", "release-keys" }, - { "model", model }, + { "model", device }, { "brand", "XiaoMi" }, - { "oaid", string.Empty }, { "hardware", "qcom" }, { "deviceType", "OP5913L1" }, { "devId", "REL" }, { "serialNumber", "unknown" }, - { "buildTime", "1687848011000" }, - { "buildUser", "root" }, - { "ramCapacity", "469679" }, - { "magnetometer", "20.081251x-27.487501x2.1937501" }, - { "display", $"{model}_13.1.0.181(CN01)" }, - { "ramRemain", "215344" }, - { "deviceInfo", $@"XiaoMi/{model}/OP5913L1:13/SKQ1.221119.001/T.118e6c7-5aa23-73911:user/release-keys" }, - { "gyroscope", "0.030226856x0.014647375x0.010652636" }, + { "sdCapacity", 512215 }, + { "buildTime", "1693626947000" }, + { "buildUser", "android-build" }, + { "simState", 5 }, + { "ramRemain", "239814" }, + { "appUpdateTimeDiff", 1702604034482 }, + { "deviceInfo", $@"XiaoMi\/{product}\/OP5913L1:13\/SKQ1.221119.001\/T.118e6c7-5aa23-73911:user\/release-keys" }, { "vaid", string.Empty }, { "buildType", "user" }, - { "sdkVersion", "33" }, + { "sdkVersion", "34" }, + { "ui_mode", "UI_MODE_TYPE_NORMAL" }, + { "isMockLocation", 0 }, + { "cpuType", "arm64-v8a" }, + { "isAirMode", 0 }, + { "ringMode", 2 }, + { "chargeStatus", 1 }, + { "manufacturer", "XiaoMi" }, + { "emulatorStatus", 0 }, + { "appMemory", "512" }, + { "osVersion", "14" }, + { "vendor", "unknown" }, + { "accelerometer", "1.4883357x7.1712894x6.2847486" }, + { "sdRemain", 239600 }, + { "buildTags", "release-keys" }, + { "packageName", "com.mihoyo.hyperion" }, + { "networkType", "WiFi" }, + { "oaid", string.Empty }, + { "debugStatus", 1 }, + { "ramCapacity", "469679" }, + { "magnetometer", "20.081251x-27.487501x2.1937501" }, + { "display", $"{product}_13.1.0.181(CN01)" }, + { "appInstallTimeDiff", 1688455751496 }, + { "packageVersion", "2.20.1" }, + { "gyroscope", "0.030226856x0.014647375x0.010652636" }, + { "batteryStatus", 100 }, + { "hasKeyboard", 0 }, { "board", "taro" }, }; @@ -79,9 +102,8 @@ internal sealed partial class UserFingerprintService : IUserFingerprintService }; Response response = await deviceFpClient.GetFingerprintAsync(data, token).ConfigureAwait(false); - user.Fingerprint = response.IsOk() ? response.Data.DeviceFp : string.Empty; + user.TryUpdateFingerprint(response.IsOk() ? response.Data.DeviceFp : string.Empty); - user.Entity.FingerprintLastUpdateTime = DateTimeOffset.UtcNow; user.NeedDbUpdateAfterResume = true; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs index 08fc3785..02fce7b6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Core.DependencyInjection.Abstraction; +using Snap.Hutao.Model.Entity.Extension; using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Bbs.User; using Snap.Hutao.Web.Hoyolab.Passport; @@ -30,14 +31,16 @@ internal sealed partial class UserInitializationService : IUserInitializationSer return user; } - public async ValueTask CreateUserFromCookieOrDefaultAsync(Cookie cookie, bool isOversea, CancellationToken token = default) + public async ValueTask CreateUserFromInputCookieOrDefaultAsync(InputCookie inputCookie, CancellationToken token = default) { // 这里只负责创建实体用户,稍后在用户服务中保存到数据库 + (Cookie cookie, bool isOversea, string? deviceFp) = inputCookie; Model.Entity.User entity = Model.Entity.User.From(cookie, isOversea); entity.Aid = cookie.GetValueOrDefault(Cookie.STUID); entity.Mid = isOversea ? entity.Aid : cookie.GetValueOrDefault(Cookie.MID); entity.IsOversea = isOversea; + entity.TryUpdateFingerprint(deviceFp); if (entity.Aid is not null && entity.Mid is not null) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserOptionResult.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserOptionResult.cs index 028c0b2d..ddf9f86f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserOptionResult.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserOptionResult.cs @@ -17,15 +17,15 @@ internal enum UserOptionResult /// /// Cookie不完整 /// - Incomplete, + CookieIncomplete, /// /// Cookie信息已经失效 /// - Invalid, + CookieInvalid, /// /// 用户的Cookie成功更新 /// - Updated, + CookieUpdated, } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs index ae2dc00d..b42f23ab 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs @@ -56,33 +56,36 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe return userCollectionService.GetUserGameRoleByUid(uid); } - public async ValueTask> ProcessInputCookieAsync(Cookie cookie, bool isOversea) + public async ValueTask> ProcessInputCookieAsync(InputCookie inputCookie) { await taskContext.SwitchToBackgroundAsync(); - string? mid = cookie.GetValueOrDefault(isOversea ? Cookie.STUID : Cookie.MID); + (Cookie cookie, bool isOversea, string? deviceFp) = inputCookie; - if (string.IsNullOrEmpty(mid)) + string? midOrAid = cookie.GetValueOrDefault(isOversea ? Cookie.STUID : Cookie.MID); + + if (string.IsNullOrEmpty(midOrAid)) { - return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieNoMid); + return new(UserOptionResult.CookieInvalid, SH.ServiceUserProcessCookieNoMid); } // 检查 mid 对应用户是否存在 - if (!userCollectionService.TryGetUserByMid(mid, out BindingUser? user)) + if (!userCollectionService.TryGetUserByMid(midOrAid, out BindingUser? user)) { - return await userCollectionService.TryCreateAndAddUserFromCookieAsync(cookie, isOversea).ConfigureAwait(false); + return await userCollectionService.TryCreateAndAddUserFromInputCookieAsync(inputCookie).ConfigureAwait(false); } if (!cookie.TryGetSToken(isOversea, out Cookie? stoken)) { - return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieNoSToken); + return new(UserOptionResult.CookieInvalid, SH.ServiceUserProcessCookieNoSToken); } user.SToken = stoken; user.LToken = cookie.TryGetLToken(out Cookie? ltoken) ? ltoken : user.LToken; user.CookieToken = cookie.TryGetCookieToken(out Cookie? cookieToken) ? cookieToken : user.CookieToken; + user.TryUpdateFingerprint(deviceFp); await userDbService.UpdateUserAsync(user.Entity).ConfigureAwait(false); - return new(UserOptionResult.Updated, mid); + return new(UserOptionResult.CookieUpdated, midOrAid); } public async ValueTask RefreshCookieTokenAsync(Model.Entity.User user) diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index 29bea2fe..36d2ffad 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -299,13 +299,14 @@ + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserQRCodeDialog.xaml b/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserQRCodeDialog.xaml index 62e88581..7dc77cd4 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserQRCodeDialog.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserQRCodeDialog.xaml @@ -16,6 +16,6 @@ + Source="{x:Bind QRCodeSource, Mode=OneWay}"/> - + \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserQRCodeDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserQRCodeDialog.xaml.cs index dcbaa0fc..32be4c21 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserQRCodeDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserQRCodeDialog.xaml.cs @@ -71,7 +71,7 @@ internal sealed partial class UserQRCodeDialog : ContentDialog, IDisposable private async ValueTask> GetUidGameTokenCoreAsync() { await taskContext.SwitchToMainThreadAsync(); - await ShowAsync(); + _ = ShowAsync(); while (!userManualCancellationTokenSource.IsCancellationRequested) { diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml index 1ef6d6b7..989ca3ff 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml @@ -183,7 +183,7 @@ - + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/ISupportLoginByWebView.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/ISupportLoginByWebView.cs index a6101a73..0a560cbd 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/ISupportLoginByWebView.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/ISupportLoginByWebView.cs @@ -7,7 +7,6 @@ using Snap.Hutao.Service.Navigation; using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.User; using Snap.Hutao.Web.Bridge; -using Snap.Hutao.Web.Hoyolab; namespace Snap.Hutao.View.Page; @@ -30,11 +29,11 @@ internal interface ISupportLoginByWebView } } - static async ValueTask PostHandleCurrentCookieAsync(IServiceProvider serviceProvider, Cookie cookie, bool isOversea) + static async ValueTask PostHandleCurrentCookieAsync(IServiceProvider serviceProvider, InputCookie inputCookie) { (UserOptionResult result, string nickname) = await serviceProvider .GetRequiredService() - .ProcessInputCookieAsync(cookie, isOversea) + .ProcessInputCookieAsync(inputCookie) .ConfigureAwait(false); serviceProvider.GetRequiredService().GoBack(); diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml index 9fd729b1..bb0170b4 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml @@ -38,7 +38,12 @@ - + + + + + + + Orientation="Horizontal">