From 62d0fb5d05916f5cb041780369d1ae1bbc6a0faf Mon Sep 17 00:00:00 2001
From: DismissedLight <1686188646@qq.com>
Date: Fri, 28 Oct 2022 14:59:31 +0800
Subject: [PATCH] launch game phase 1
---
src/Snap.Hutao/Snap.Hutao/App.xaml | 30 ++-
src/Snap.Hutao/Snap.Hutao/App.xaml.cs | 6 +-
.../Control/Behavior/AutoHeightBehavior.cs | 2 +-
.../Control/Behavior/AutoWidthBehavior.cs | 58 +++++
.../Snap.Hutao/Core/CommandLineBuilder.cs | 63 ++++++
.../Snap.Hutao/Core/JumpListHelper.cs | 35 +++
.../Snap.Hutao/Core/LifeCycle/Activation.cs | 33 ++-
.../AppActivationArgumentsExtensions.cs | 18 ++
.../Snap.Hutao/Core/ProcessHelper.cs | 2 +-
.../Snap.Hutao/Core/Setting/LocalSetting.cs | 206 ++++++++++++++++-
.../Snap.Hutao/Core/Windowing/Persistence.cs | 4 +-
.../Snap.Hutao/LaunchGameWindow.xaml | 12 +
.../Snap.Hutao/LaunchGameWindow.xaml.cs | 20 ++
.../Model/Binding/Gacha/HistoryWish.cs | 5 +
.../Model/Metadata/Avatar/AvatarIds.cs | 76 -------
.../Model/Metadata/Avatar/CookBonus.cs | 57 +++++
.../Model/Metadata/Avatar/FetterInfo.cs | 5 +
.../Model/Metadata/Avatar/ItemWithCount.cs | 37 +++
.../Snap.Hutao/Model/Metadata/AvatarIds.cs | 78 +++++++
.../Converter/GachaAvatarIconConverter.cs | 31 +++
.../Converter/GachaAvatarImgConverter.cs | 2 +-
.../Metadata/Converter/ItemIconConverter.cs | 30 +++
.../Snap.Hutao/Model/Metadata/GachaEvent.cs | 5 +
.../Snap.Hutao/Package.appxmanifest | 2 +-
src/Snap.Hutao/Snap.Hutao/Program.cs | 3 +-
.../Resource/Icon/UI_GuideIcon_PlayMethod.png | Bin 0 -> 2118 bytes
.../Factory/ReliquaryWeightConfiguration.cs | 2 +-
.../GachaLog/Factory/HistoryWishBuilder.cs | 1 +
.../Snap.Hutao/Service/Game/GameService.cs | 163 +++++++++++---
.../Snap.Hutao/Service/Game/IGameService.cs | 19 ++
.../Service/Game/LaunchConfiguration.cs | 45 ++++
.../Snap.Hutao/Service/Hutao/HutaoCache.cs | 2 +-
src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj | 14 ++
src/Snap.Hutao/Snap.Hutao/View/MainView.xaml | 7 +-
.../Snap.Hutao/View/MainView.xaml.cs | 21 --
.../Snap.Hutao/View/Page/AchievementPage.xaml | 7 -
.../View/Page/AnnouncementPage.xaml | 2 -
.../View/Page/AvatarPropertyPage.xaml | 2 -
.../Snap.Hutao/View/Page/GachaLogPage.xaml | 15 +-
.../View/Page/HutaoDatabasePage.xaml | 14 +-
.../Snap.Hutao/View/Page/LaunchGamePage.xaml | 132 +++++++++++
.../View/Page/LaunchGamePage.xaml.cs | 22 ++
.../Snap.Hutao/View/Page/SettingPage.xaml | 11 +
.../Snap.Hutao/View/Page/WikiAvatarPage.xaml | 210 +++++++++++++++---
src/Snap.Hutao/Snap.Hutao/View/UserView.xaml | 18 +-
.../ViewModel/HutaoDatabaseViewModel.cs | 8 +
.../ViewModel/LaunchGameViewModel.cs | 17 ++
.../Snap.Hutao/ViewModel/SettingViewModel.cs | 45 +++-
48 files changed, 1380 insertions(+), 217 deletions(-)
create mode 100644 src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoWidthBehavior.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/CommandLineBuilder.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/JumpListHelper.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml
create mode 100644 src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs
delete mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/AvatarIds.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/CookBonus.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ItemWithCount.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Metadata/AvatarIds.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/GachaAvatarIconConverter.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/ItemIconConverter.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_GuideIcon_PlayMethod.png
create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchConfiguration.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml
create mode 100644 src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs
diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml b/src/Snap.Hutao/Snap.Hutao/App.xaml
index a578a1ea..3b6babf6 100644
--- a/src/Snap.Hutao/Snap.Hutao/App.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/App.xaml
@@ -2,7 +2,10 @@
x:Class="Snap.Hutao.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:muxc="using:Microsoft.UI.Xaml.Controls">
+ xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Converters"
+ xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
+ xmlns:shmmc="using:Snap.Hutao.Model.Metadata.Converter"
+ xmlns:shvc="using:Snap.Hutao.View.Converter">
@@ -13,21 +16,36 @@
-
+
-
+
ms-appx:///Resource/Font/Segoe Fluent Icons.ttf#Segoe Fluent Icons
-
+
6,16,16,16
16,0,0,0
-
+
16
-
+
6
6,6,0,0
0,6,6,0
0,0,6,6
2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs
index fa75ecd0..47df058f 100644
--- a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs
@@ -54,13 +54,17 @@ public partial class App : Application
logger.LogInformation(EventIds.CommonLog, "Snap Hutao : {version}", CoreEnvironment.Version);
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", ApplicationData.Current.TemporaryFolder.Path);
+ JumpListHelper.ConfigAsync().SafeForget(logger);
+
Ioc.Default
.GetRequiredService()
.ImplictAs()?
.InitializeInternalAsync()
.SafeForget(logger);
- Ioc.Default.GetRequiredService().Initialize();
+ Ioc.Default
+ .GetRequiredService()
+ .Initialize();
}
else
{
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoHeightBehavior.cs b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoHeightBehavior.cs
index 11d8d246..43a8b2a3 100644
--- a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoHeightBehavior.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoHeightBehavior.cs
@@ -55,4 +55,4 @@ internal class AutoHeightBehavior : BehaviorBase
{
AssociatedObject.Height = (double)AssociatedObject.ActualWidth * (TargetHeight / TargetWidth);
}
-}
\ No newline at end of file
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoWidthBehavior.cs b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoWidthBehavior.cs
new file mode 100644
index 00000000..b033564d
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoWidthBehavior.cs
@@ -0,0 +1,58 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using CommunityToolkit.WinUI.UI.Behaviors;
+using Microsoft.UI.Xaml;
+using Snap.Hutao.Core;
+
+namespace Snap.Hutao.Control.Behavior;
+
+///
+/// 按给定比例自动调整高度的行为
+///
+internal class AutoWidthBehavior : BehaviorBase
+{
+ private static readonly DependencyProperty TargetWidthProperty = Property.Depend(nameof(TargetWidth), 320D);
+ private static readonly DependencyProperty TargetHeightProperty = Property.Depend(nameof(TargetHeight), 1024D);
+
+ ///
+ /// 目标宽度
+ ///
+ public double TargetWidth
+ {
+ get => (double)GetValue(TargetWidthProperty);
+ set => SetValue(TargetWidthProperty, value);
+ }
+
+ ///
+ /// 目标高度
+ ///
+ public double TargetHeight
+ {
+ get => (double)GetValue(TargetHeightProperty);
+ set => SetValue(TargetHeightProperty, value);
+ }
+
+ ///
+ protected override void OnAssociatedObjectLoaded()
+ {
+ UpdateElementWidth();
+ AssociatedObject.SizeChanged += OnSizeChanged;
+ }
+
+ ///
+ protected override void OnDetaching()
+ {
+ AssociatedObject.SizeChanged -= OnSizeChanged;
+ }
+
+ private void OnSizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ UpdateElementWidth();
+ }
+
+ private void UpdateElementWidth()
+ {
+ AssociatedObject.Width = (double)AssociatedObject.Height * (TargetWidth / TargetHeight);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/CommandLineBuilder.cs b/src/Snap.Hutao/Snap.Hutao/Core/CommandLineBuilder.cs
new file mode 100644
index 00000000..0cab1bf0
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/CommandLineBuilder.cs
@@ -0,0 +1,63 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Text;
+
+namespace Snap.Hutao.Core;
+
+///
+/// 命令行建造器
+///
+public class CommandLineBuilder
+{
+ private const char WhiteSpace = ' ';
+ private readonly Dictionary options = new();
+
+ ///
+ /// 当符合条件时添加参数
+ ///
+ /// 参数名称
+ /// 条件
+ /// 值
+ /// 命令行建造器
+ public CommandLineBuilder AppendIf(string name, bool condition, object? value = null)
+ {
+ return condition ? Append(name, value) : this;
+ }
+
+ ///
+ /// 添加参数
+ ///
+ /// 参数名称
+ /// 值
+ /// 命令行建造器
+ public CommandLineBuilder Append(string name, object? value = null)
+ {
+ options.Add(name, value?.ToString());
+ return this;
+ }
+
+ ///
+ public string Build()
+ {
+ return ToString();
+ }
+
+ ///
+ public override string ToString()
+ {
+ StringBuilder s = new();
+ foreach ((string key, string? value) in options)
+ {
+ s.Append(WhiteSpace);
+ s.Append(key);
+ if (!string.IsNullOrEmpty(value))
+ {
+ s.Append(WhiteSpace);
+ s.Append(value);
+ }
+ }
+
+ return s.ToString();
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/JumpListHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/JumpListHelper.cs
new file mode 100644
index 00000000..612ac475
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/JumpListHelper.cs
@@ -0,0 +1,35 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Core.LifeCycle;
+using Windows.UI.StartScreen;
+
+namespace Snap.Hutao.Core;
+
+///
+/// 跳转列表帮助类
+///
+public static class JumpListHelper
+{
+ ///
+ /// 异步配置跳转列表
+ ///
+ /// 任务
+ public static async Task ConfigAsync()
+ {
+ if (JumpList.IsSupported())
+ {
+ JumpList list = await JumpList.LoadCurrentAsync();
+
+ list.Items.Clear();
+
+ JumpListItem launchGameItem = JumpListItem.CreateWithArguments(Activation.LaunchGame, "启动游戏");
+ launchGameItem.GroupName = "快捷操作";
+ launchGameItem.Logo = new("ms-appx:///Resource/Icon/UI_GuideIcon_PlayMethod.png");
+
+ list.Items.Add(launchGameItem);
+
+ await list.SaveAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs
index e48a5bfb..1eda1194 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs
@@ -13,6 +13,11 @@ namespace Snap.Hutao.Core.LifeCycle;
///
internal static class Activation
{
+ ///
+ /// 启动游戏启动参数
+ ///
+ public const string LaunchGame = "LaunchGame";
+
private static readonly SemaphoreSlim ActivateSemaphore = new(1);
///
@@ -44,16 +49,36 @@ internal static class Activation
private static async Task HandleActivationCoreAsync(AppActivationArguments args)
{
- _ = Ioc.Default.GetRequiredService();
+ string argument = string.Empty;
- IInfoBarService infoBarService = Ioc.Default.GetRequiredService();
- await infoBarService.WaitInitializationAsync().ConfigureAwait(false);
+ if (args.Kind == ExtendedActivationKind.Launch)
+ {
+ if (args.TryGetLaunchActivatedArgument(out string? arguments))
+ {
+ argument = arguments;
+ }
+ }
+
+ switch (argument)
+ {
+ case "":
+ {
+ _ = Ioc.Default.GetRequiredService();
+ await Ioc.Default.GetRequiredService().WaitInitializationAsync().ConfigureAwait(false);
+ break;
+ }
+
+ case LaunchGame:
+ {
+ break;
+ }
+ }
if (args.Kind == ExtendedActivationKind.Protocol)
{
if (args.TryGetProtocolActivatedUri(out Uri? uri))
{
- infoBarService.Information(uri.ToString());
+ Ioc.Default.GetRequiredService().Information(uri.ToString());
await HandleUrlActivationAsync(uri).ConfigureAwait(false);
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/AppActivationArgumentsExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/AppActivationArgumentsExtensions.cs
index cc4e416d..9ea64b70 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/AppActivationArgumentsExtensions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/AppActivationArgumentsExtensions.cs
@@ -28,4 +28,22 @@ public static class AppActivationArgumentsExtensions
return false;
}
+
+ ///
+ /// 尝试获取启动的参数
+ ///
+ /// 应用程序激活参数
+ /// 参数
+ /// 是否存在参数
+ public static bool TryGetLaunchActivatedArgument(this AppActivationArguments activatedEventArgs, [NotNullWhen(true)] out string? arguments)
+ {
+ arguments = null;
+ if (activatedEventArgs.Data is ILaunchActivatedEventArgs launchArgs)
+ {
+ arguments = launchArgs.Arguments;
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ProcessHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/ProcessHelper.cs
index 87f390b5..080423a0 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/ProcessHelper.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/ProcessHelper.cs
@@ -41,4 +41,4 @@ public static class ProcessHelper
};
return Process.Start(processInfo);
}
-}
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Setting/LocalSetting.cs b/src/Snap.Hutao/Snap.Hutao/Core/Setting/LocalSetting.cs
index f536d772..7a8f9640 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Setting/LocalSetting.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Setting/LocalSetting.cs
@@ -10,10 +10,6 @@ namespace Snap.Hutao.Core.Setting;
///
internal static class LocalSetting
{
- ///
- /// 由于 没有 nullable context,
- /// 在处理引用类型时需要格外小心
- ///
private static readonly ApplicationDataContainer Container;
static LocalSetting()
@@ -21,6 +17,198 @@ internal static class LocalSetting
Container = ApplicationData.Current.LocalSettings;
}
+ ///
+ public static byte Get(string key, byte defaultValue)
+ {
+ return Get(key, defaultValue);
+ }
+
+ ///
+ public static short Get(string key, short defaultValue)
+ {
+ return Get(key, defaultValue);
+ }
+
+ ///
+ public static ushort Get(string key, ushort defaultValue)
+ {
+ return Get(key, defaultValue);
+ }
+
+ ///
+ public static int Get(string key, int defaultValue)
+ {
+ return Get(key, defaultValue);
+ }
+
+ ///
+ public static uint Get(string key, uint defaultValue)
+ {
+ return Get(key, defaultValue);
+ }
+
+ ///
+ public static ulong Get(string key, ulong defaultValue)
+ {
+ return Get(key, defaultValue);
+ }
+
+ ///
+ public static float Get(string key, float defaultValue)
+ {
+ return Get(key, defaultValue);
+ }
+
+ ///
+ public static double Get(string key, double defaultValue)
+ {
+ return Get(key, defaultValue);
+ }
+
+ ///
+ public static bool Get(string key, bool defaultValue)
+ {
+ return Get(key, defaultValue);
+ }
+
+ ///
+ public static char Get(string key, char defaultValue)
+ {
+ return Get(key, defaultValue);
+ }
+
+ ///
+ public static DateTimeOffset Get(string key, DateTimeOffset defaultValue)
+ {
+ return Get(key, defaultValue);
+ }
+
+ ///
+ public static TimeSpan Get(string key, TimeSpan defaultValue)
+ {
+ return Get(key, defaultValue);
+ }
+
+ ///
+ public static Guid Get(string key, Guid defaultValue)
+ {
+ return Get(key, defaultValue);
+ }
+
+ ///
+ public static Windows.Foundation.Point Get(string key, Windows.Foundation.Point defaultValue)
+ {
+ return Get(key, defaultValue);
+ }
+
+ ///
+ public static Windows.Foundation.Size Get(string key, Windows.Foundation.Size defaultValue)
+ {
+ return Get(key, defaultValue);
+ }
+
+ ///
+ public static Windows.Foundation.Rect Get(string key, Windows.Foundation.Rect defaultValue)
+ {
+ return Get(key, defaultValue);
+ }
+
+ ///
+ public static void Set(string key, byte value)
+ {
+ Set(key, value);
+ }
+
+ ///
+ public static void Set(string key, short value)
+ {
+ Set(key, value);
+ }
+
+ ///
+ public static void Set(string key, ushort value)
+ {
+ Set(key, value);
+ }
+
+ ///
+ public static void Set(string key, int value)
+ {
+ Set(key, value);
+ }
+
+ ///
+ public static void Set(string key, uint value)
+ {
+ Set(key, value);
+ }
+
+ ///
+ public static void Set(string key, ulong value)
+ {
+ Set(key, value);
+ }
+
+ ///
+ public static void Set(string key, float value)
+ {
+ Set(key, value);
+ }
+
+ ///
+ public static void Set(string key, double value)
+ {
+ Set(key, value);
+ }
+
+ ///
+ public static void Set(string key, bool value)
+ {
+ Set(key, value);
+ }
+
+ ///
+ public static void Set(string key, char value)
+ {
+ Set(key, value);
+ }
+
+ ///
+ public static void Set(string key, DateTimeOffset value)
+ {
+ Set(key, value);
+ }
+
+ ///
+ public static void Set(string key, TimeSpan value)
+ {
+ Set(key, value);
+ }
+
+ ///
+ public static void Set(string key, Guid value)
+ {
+ Set(key, value);
+ }
+
+ ///
+ public static void Set(string key, Windows.Foundation.Point value)
+ {
+ Set(key, value);
+ }
+
+ ///
+ public static void Set(string key, Windows.Foundation.Size value)
+ {
+ Set(key, value);
+ }
+
+ ///
+ public static void Set(string key, Windows.Foundation.Rect value)
+ {
+ Set(key, value);
+ }
+
///
/// 获取设置项的值
///
@@ -28,8 +216,8 @@ internal static class LocalSetting
/// 键
/// 默认值
/// 获取的值
- [return: MaybeNull]
- public static T Get(string key, [AllowNull] T defaultValue = default)
+ private static T Get(string key, T defaultValue = default)
+ where T : struct
{
if (Container.Values.TryGetValue(key, out object? value))
{
@@ -49,9 +237,9 @@ internal static class LocalSetting
/// 设置项的类型
/// 键
/// 值
- /// 设置的值
- public static object? Set(string key, T value)
+ private static void Set(string key, T value)
+ where T : struct
{
- return Container.Values[key] = value;
+ Container.Values[key] = value;
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Persistence.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Persistence.cs
index 2a882ff4..431f6964 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Persistence.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Persistence.cs
@@ -44,7 +44,7 @@ internal static class Persistence
/// 应用窗体
public static void Save(AppWindow appWindow)
{
- LocalSetting.Set(SettingKeys.WindowRect, (ulong)(CompactRect)appWindow.GetRect());
+ LocalSetting.Set(SettingKeys.WindowRect, (CompactRect)appWindow.GetRect());
}
///
@@ -124,7 +124,7 @@ internal static class Persistence
return new(rect.X, rect.Y, rect.Width, rect.Height);
}
- public static explicit operator ulong(CompactRect rect)
+ public static implicit operator ulong(CompactRect rect)
{
return rect.Value;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml b/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml
new file mode 100644
index 00000000..56cbbfea
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs
new file mode 100644
index 00000000..84f88421
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs
@@ -0,0 +1,20 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.UI.Xaml;
+
+namespace Snap.Hutao;
+
+///
+/// 启动游戏窗口
+///
+public sealed partial class LaunchGameWindow : Window
+{
+ ///
+ /// 构造一个新的启动游戏窗口
+ ///
+ public LaunchGameWindow()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Gacha/HistoryWish.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Gacha/HistoryWish.cs
index e807f505..ec094ad6 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Gacha/HistoryWish.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Gacha/HistoryWish.cs
@@ -10,6 +10,11 @@ namespace Snap.Hutao.Model.Binding.Gacha;
///
public class HistoryWish : WishBase
{
+ ///
+ /// 版本
+ ///
+ public string Version { get; set; }
+
///
/// 五星Up
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/AvatarIds.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/AvatarIds.cs
deleted file mode 100644
index d7701353..00000000
--- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/AvatarIds.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-namespace Snap.Hutao.Model.Metadata.Avatar;
-
-///
-/// 角色ID
-///
-[SuppressMessage("", "SA1600")]
-public static class AvatarIds
-{
- public const int Ayaka = 10000002;
- public const int Qin = 10000003;
-
- public const int Lisa = 10000006;
-
- public const int Barbara = 10000014;
- public const int Kaeya = 10000015;
- public const int Diluc = 10000016;
-
- public const int Razor = 10000020;
- public const int Ambor = 10000021;
- public const int Venti = 10000022;
- public const int Xiangling = 10000023;
- public const int Beidou = 10000024;
- public const int Xingqiu = 10000025;
- public const int Xiao = 10000026;
- public const int Ningguang = 10000027;
-
- public const int Klee = 10000029;
- public const int Zhongli = 10000030;
- public const int Fischl = 10000031;
- public const int Bennett = 10000032;
- public const int Tartaglia = 10000033;
- public const int Noel = 10000034;
- public const int Qiqi = 10000035;
- public const int Chongyun = 10000036;
- public const int Ganyu = 10000037;
- public const int Albedo = 10000038;
- public const int Diona = 10000039;
-
- public const int Mona = 10000041;
- public const int Keqing = 10000042;
- public const int Sucrose = 10000043;
- public const int Xinyan = 10000044;
- public const int Rosaria = 10000045;
- public const int Hutao = 10000046;
- public const int Kazuha = 10000047;
- public const int Feiyan = 10000048;
- public const int Yoimiya = 10000049;
- public const int Tohma = 10000050;
- public const int Eula = 10000051;
- public const int Shougun = 10000052;
- public const int Sayu = 10000053;
- public const int Kokomi = 10000054;
- public const int Gorou = 10000055;
- public const int Sara = 10000056;
- public const int Itto = 10000057;
- public const int Yae = 10000058;
- public const int Heizou = 10000059;
- public const int Yelan = 10000060;
-
- public const int Aloy = 10000062;
- public const int Shenhe = 10000063;
- public const int Yunjin = 10000064;
- public const int Shinobu = 10000065;
- public const int Ayato = 10000066;
- public const int Collei = 10000067;
- public const int Dori = 10000068;
- public const int Tighnari = 10000069;
- public const int Nilou = 10000070;
- public const int Cyno = 10000071;
- public const int Candace = 10000072;
- public const int Nahida = 10000073;
- public const int Layla = 10000074;
-}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/CookBonus.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/CookBonus.cs
new file mode 100644
index 00000000..9fbdb57b
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/CookBonus.cs
@@ -0,0 +1,57 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Model.Intrinsic;
+
+namespace Snap.Hutao.Model.Metadata.Avatar;
+
+///
+/// 料理奖励
+///
+public class CookBonus
+{
+ ///
+ /// 原型名称
+ ///
+ public string OriginName { get; set; } = default!;
+
+ ///
+ /// 原型描述
+ ///
+ public string OriginDescription { get; set; } = default!;
+
+ ///
+ /// 原型图标
+ ///
+ public string OriginIcon { get; set; } = default!;
+
+ ///
+ /// 名称
+ ///
+ public string Name { get; set; } = default!;
+
+ ///
+ /// 描述
+ ///
+ public string Description { get; set; } = default!;
+
+ ///
+ /// 效果描述
+ ///
+ public string EffectDescription { get; set; } = default!;
+
+ ///
+ /// 图标
+ ///
+ public string Icon { get; set; } = default!;
+
+ ///
+ /// 物品等级
+ ///
+ public ItemQuality RankLevel { get; set; }
+
+ ///
+ /// 材料列表
+ ///
+ public List InputList { get; set; } = default!;
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/FetterInfo.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/FetterInfo.cs
index 0656f765..d1dc472e 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/FetterInfo.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/FetterInfo.cs
@@ -76,6 +76,11 @@ public class FetterInfo
///
public string CvKorean { get; set; } = default!;
+ ///
+ /// 料理
+ ///
+ public CookBonus? CookBonus { get; set; }
+
///
/// 好感语音
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ItemWithCount.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ItemWithCount.cs
new file mode 100644
index 00000000..45b572f2
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ItemWithCount.cs
@@ -0,0 +1,37 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Model.Intrinsic;
+
+namespace Snap.Hutao.Model.Metadata.Avatar;
+
+///
+/// 带有个数的物品
+///
+public class ItemWithCount
+{
+ ///
+ /// 物品Id
+ ///
+ public int Id { get; set; }
+
+ ///
+ /// 名称
+ ///
+ public string Name { get; set; } = default!;
+
+ ///
+ /// 图标
+ ///
+ public string Icon { get; set; } = default!;
+
+ ///
+ /// 物品等级
+ ///
+ public ItemQuality RankLevel { get; set; }
+
+ ///
+ /// 数量
+ ///
+ public int Count { get; set; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/AvatarIds.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/AvatarIds.cs
new file mode 100644
index 00000000..aa0c305d
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/AvatarIds.cs
@@ -0,0 +1,78 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Model.Primitive;
+
+namespace Snap.Hutao.Model.Metadata;
+
+///
+/// 角色ID
+///
+[SuppressMessage("", "SA1600")]
+public static class AvatarIds
+{
+ public static readonly AvatarId Ayaka = 10000002;
+ public static readonly AvatarId Qin = 10000003;
+
+ public static readonly AvatarId Lisa = 10000006;
+
+ public static readonly AvatarId Barbara = 10000014;
+ public static readonly AvatarId Kaeya = 10000015;
+ public static readonly AvatarId Diluc = 10000016;
+
+ public static readonly AvatarId Razor = 10000020;
+ public static readonly AvatarId Ambor = 10000021;
+ public static readonly AvatarId Venti = 10000022;
+ public static readonly AvatarId Xiangling = 10000023;
+ public static readonly AvatarId Beidou = 10000024;
+ public static readonly AvatarId Xingqiu = 10000025;
+ public static readonly AvatarId Xiao = 10000026;
+ public static readonly AvatarId Ningguang = 10000027;
+
+ public static readonly AvatarId Klee = 10000029;
+ public static readonly AvatarId Zhongli = 10000030;
+ public static readonly AvatarId Fischl = 10000031;
+ public static readonly AvatarId Bennett = 10000032;
+ public static readonly AvatarId Tartaglia = 10000033;
+ public static readonly AvatarId Noel = 10000034;
+ public static readonly AvatarId Qiqi = 10000035;
+ public static readonly AvatarId Chongyun = 10000036;
+ public static readonly AvatarId Ganyu = 10000037;
+ public static readonly AvatarId Albedo = 10000038;
+ public static readonly AvatarId Diona = 10000039;
+
+ public static readonly AvatarId Mona = 10000041;
+ public static readonly AvatarId Keqing = 10000042;
+ public static readonly AvatarId Sucrose = 10000043;
+ public static readonly AvatarId Xinyan = 10000044;
+ public static readonly AvatarId Rosaria = 10000045;
+ public static readonly AvatarId Hutao = 10000046;
+ public static readonly AvatarId Kazuha = 10000047;
+ public static readonly AvatarId Feiyan = 10000048;
+ public static readonly AvatarId Yoimiya = 10000049;
+ public static readonly AvatarId Tohma = 10000050;
+ public static readonly AvatarId Eula = 10000051;
+ public static readonly AvatarId Shougun = 10000052;
+ public static readonly AvatarId Sayu = 10000053;
+ public static readonly AvatarId Kokomi = 10000054;
+ public static readonly AvatarId Gorou = 10000055;
+ public static readonly AvatarId Sara = 10000056;
+ public static readonly AvatarId Itto = 10000057;
+ public static readonly AvatarId Yae = 10000058;
+ public static readonly AvatarId Heizou = 10000059;
+ public static readonly AvatarId Yelan = 10000060;
+
+ public static readonly AvatarId Aloy = 10000062;
+ public static readonly AvatarId Shenhe = 10000063;
+ public static readonly AvatarId Yunjin = 10000064;
+ public static readonly AvatarId Shinobu = 10000065;
+ public static readonly AvatarId Ayato = 10000066;
+ public static readonly AvatarId Collei = 10000067;
+ public static readonly AvatarId Dori = 10000068;
+ public static readonly AvatarId Tighnari = 10000069;
+ public static readonly AvatarId Nilou = 10000070;
+ public static readonly AvatarId Cyno = 10000071;
+ public static readonly AvatarId Candace = 10000072;
+ public static readonly AvatarId Nahida = 10000073;
+ public static readonly AvatarId Layla = 10000074;
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/GachaAvatarIconConverter.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/GachaAvatarIconConverter.cs
new file mode 100644
index 00000000..cc31af11
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/GachaAvatarIconConverter.cs
@@ -0,0 +1,31 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Control;
+
+namespace Snap.Hutao.Model.Metadata.Converter;
+
+///
+/// 立绘图标转换器
+///
+internal class GachaAvatarIconConverter : ValueConverterBase
+{
+ private const string BaseUrl = "https://static.snapgenshin.com/GachaAvatarIcon/UI_Gacha_AvatarIcon_{0}.png";
+
+ ///
+ /// 名称转Uri
+ ///
+ /// 名称
+ /// 链接
+ public static Uri IconNameToUri(string name)
+ {
+ name = name["UI_AvatarIcon_".Length..];
+ return new Uri(string.Format(BaseUrl, name));
+ }
+
+ ///
+ public override Uri Convert(string from)
+ {
+ return IconNameToUri(from);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/GachaAvatarImgConverter.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/GachaAvatarImgConverter.cs
index b0b192c5..5062adf2 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/GachaAvatarImgConverter.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/GachaAvatarImgConverter.cs
@@ -6,7 +6,7 @@ using Snap.Hutao.Control;
namespace Snap.Hutao.Model.Metadata.Converter;
///
-/// 角色头像转换器
+/// 立绘转换器
///
internal class GachaAvatarImgConverter : ValueConverterBase
{
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/ItemIconConverter.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/ItemIconConverter.cs
new file mode 100644
index 00000000..dd306fc7
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/ItemIconConverter.cs
@@ -0,0 +1,30 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Control;
+
+namespace Snap.Hutao.Model.Metadata.Converter;
+
+///
+/// 物品图片转换器
+///
+internal class ItemIconConverter : ValueConverterBase
+{
+ private const string BaseUrl = "https://static.snapgenshin.com/ItemIcon/{0}.png";
+
+ ///
+ /// 名称转Uri
+ ///
+ /// 名称
+ /// 链接
+ public static Uri IconNameToUri(string name)
+ {
+ return new Uri(string.Format(BaseUrl, name));
+ }
+
+ ///
+ public override Uri Convert(string from)
+ {
+ return IconNameToUri(from);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/GachaEvent.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/GachaEvent.cs
index 9ca299b2..345cb15d 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/GachaEvent.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/GachaEvent.cs
@@ -15,6 +15,11 @@ public class GachaEvent
///
public string Name { get; set; } = default!;
+ ///
+ /// 版本
+ ///
+ public string Version { get; set; } = default!;
+
///
/// 开始时间
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
index 1a04cd71..dae27fae 100644
--- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
+++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
@@ -9,7 +9,7 @@
+ Version="1.1.14.0" />
胡桃
diff --git a/src/Snap.Hutao/Snap.Hutao/Program.cs b/src/Snap.Hutao/Snap.Hutao/Program.cs
index aa64a0a5..546e537e 100644
--- a/src/Snap.Hutao/Snap.Hutao/Program.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Program.cs
@@ -7,7 +7,6 @@ using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Logging;
using System.Runtime.InteropServices;
-using Windows.UI.ViewManagement;
using WinRT;
namespace Snap.Hutao;
@@ -32,6 +31,7 @@ public static partial class Program
private static void Main(string[] args)
{
_ = args;
+
XamlCheckProcessRequirements();
ComWrappersSupport.InitializeComWrappers();
@@ -75,7 +75,6 @@ public static partial class Program
// Discrete services
.AddSingleton(WeakReferenceMessenger.Default)
- .AddSingleton(new UISettings())
.BuildServiceProvider();
diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_GuideIcon_PlayMethod.png b/src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_GuideIcon_PlayMethod.png
new file mode 100644
index 0000000000000000000000000000000000000000..71b3db69079fced0492b106226bfc50c31081a09
GIT binary patch
literal 2118
zcmV-M2)Xx(P)fFFuIkf)Ny#sBwwW?1`DoOdRV+pJSg}(_K~FeJ2S_{&48Nb-Vla
z`JbguEqC4W^0KUDtdlj4YXry|$7&ETG4TG&nv^c52W)WXbME{fJZg&_ll
zeJI#D#FIZDc2O!4Iup@{fC+smk{iiuy^O{wFaL9pFZGDROFdt3NuLK1Sbc+x<_Fqu
z3UV^Yd2R3X8+`~s`Fn#ruIK&-vBoD-S=}t&*FF7x5P`cd1lg>=i}I6t9V8H5=e4ak
zH*_U{>#@8kZ$Q~!f_xO@tklYn6U+Ks+?_hYy2>e)TY~%{Rcp
zpV4=_LEF)XaRycB?@|$zL@5CjH@ozFL65lFIFvV7TWL5L34{YmZ_{>i14)rO<4o=$
zIRbE37~UPVUkLKCbWe6|${YHSfQuiMiUh*bx@3|yy^$Y7tUwb1zP2Z(C~>#zgRKt#
zRH`ngqzY?`d9Y89uer8397?)T3;_wdq)v)k^u3QsVu+pd0Nlw+TuXXXcO;nwDMy>~
zZ@AAX5=wAJ1*MGy66A47
zskWPP9yb{6NAH$s`rY
z$NZZ?UJUY*6G+O(^_{I?!wcvkPB-1qMgT9gHEQ$2&J=QPA1m*FlkZ-V{^78!??^Wd0qBP(
zQz&&JK%{06VEvPlM`_HrVOp;lniJ>^cDT>i$*4E~j}!r9PwV$_dmE0%=o9lf49N*L
zWyX6mVnyj$X<4sG5nyg=tes0-+coBM7@8Bjr6FNs%XQgqkOvV%SiWtD**E5M7?Km{
z4*h(^`nDe#Iw?9d=5rXD6S#j;5@;j9=I<`a%0&r}Kj<2lT`rPd2#nZ52+2jKn;fo*n$
zG*kCL1KhQ_>sm7&|Bwj-$h%qEe>oC
zuuVpKBAS5mT@
z;U`p51n?h$=VX)s3K{kwQFWp?-R)WZ&f
/// 游戏服务
///
-[Injection(InjectAs.Transient, typeof(IGameService))]
+[Injection(InjectAs.Singleton, typeof(IGameService))]
internal class GameService : IGameService
{
- private readonly AppDbContext appDbContext;
+ private const string GamePathKey = $"{nameof(GameService)}.Cache.{SettingEntry.GamePath}";
+
+ private readonly IServiceScopeFactory scopeFactory;
private readonly IMemoryCache memoryCache;
- private readonly IEnumerable gameLocators;
+ private readonly SemaphoreSlim gameSemaphore = new(1);
///
/// 构造一个新的游戏服务
///
- /// 数据库上下文
+ /// 范围工厂
/// 内存缓存
/// 游戏定位器集合
- public GameService(AppDbContext appDbContext, IMemoryCache memoryCache, IEnumerable gameLocators)
+ public GameService(IServiceScopeFactory scopeFactory, IMemoryCache memoryCache)
{
- this.appDbContext = appDbContext;
+ this.scopeFactory = scopeFactory;
this.memoryCache = memoryCache;
- this.gameLocators = gameLocators;
}
///
public async ValueTask> GetGamePathAsync()
{
- string key = $"{nameof(GameService)}.Cache.{SettingEntry.GamePath}";
-
- if (memoryCache.TryGetValue(key, out object? value))
+ if (memoryCache.TryGetValue(GamePathKey, out object? value))
{
return new(true, Must.NotNull((value as string)!));
}
else
{
- SettingEntry entry = appDbContext.Settings.SingleOrAdd(e => e.Key == SettingEntry.GamePath, () => new(SettingEntry.GamePath, null), out bool added);
-
- // Cannot find in setting
- if (added)
+ using (IServiceScope scope = scopeFactory.CreateScope())
{
- // Try locate by registry
- IGameLocator locator = gameLocators.Single(l => l.Name == nameof(RegistryLauncherLocator));
- ValueResult result = await locator.LocateGamePathAsync().ConfigureAwait(false);
+ AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
- if (!result.IsOk)
+ SettingEntry entry = appDbContext.Settings.SingleOrAdd(e => e.Key == SettingEntry.GamePath, () => new(SettingEntry.GamePath, null), out bool added);
+
+ // Cannot find in setting
+ if (added)
{
- // Try locate manually
- locator = gameLocators.Single(l => l.Name == nameof(ManualGameLocator));
- result = await locator.LocateGamePathAsync().ConfigureAwait(false);
+ IEnumerable gameLocators = scope.ServiceProvider.GetRequiredService>();
+
+ // Try locate by registry
+ IGameLocator locator = gameLocators.Single(l => l.Name == nameof(RegistryLauncherLocator));
+ ValueResult result = await locator.LocateGamePathAsync().ConfigureAwait(false);
+
+ if (!result.IsOk)
+ {
+ // Try locate manually
+ locator = gameLocators.Single(l => l.Name == nameof(ManualGameLocator));
+ result = await locator.LocateGamePathAsync().ConfigureAwait(false);
+ }
+
+ if (result.IsOk)
+ {
+ // Save result.
+ entry.Value = result.Value;
+ appDbContext.Settings.UpdateAndSave(entry);
+ }
+ else
+ {
+ return new(false, null!);
+ }
}
- if (result.IsOk)
+ // Set cache and return.
+ string path = memoryCache.Set(GamePathKey, Must.NotNull(entry.Value!));
+ return new(true, path);
+ }
+ }
+ }
+
+ ///
+ public string GetGamePathSkipLocator()
+ {
+ if (memoryCache.TryGetValue(GamePathKey, out object? value))
+ {
+ return (value as string)!;
+ }
+ else
+ {
+ using (IServiceScope scope = scopeFactory.CreateScope())
+ {
+ AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
+ SettingEntry entry = appDbContext.Settings.SingleOrAdd(e => e.Key == SettingEntry.GamePath, () => new(SettingEntry.GamePath, null), out bool added);
+
+ entry.Value ??= string.Empty;
+ appDbContext.Settings.UpdateAndSave(entry);
+
+ // Set cache and return.
+ return memoryCache.Set(GamePathKey, entry.Value);
+ }
+ }
+ }
+
+ ///
+ public void OverwriteGamePath(string path)
+ {
+ // sync cache
+ memoryCache.Set(GamePathKey, path);
+
+ using (IServiceScope scope = scopeFactory.CreateScope())
+ {
+ AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
+
+ SettingEntry entry = appDbContext.Settings.SingleOrAdd(e => e.Key == SettingEntry.GamePath, () => new(SettingEntry.GamePath, null), out _);
+ entry.Value = path;
+ appDbContext.Settings.UpdateAndSave(entry);
+ }
+ }
+
+ ///
+ public async ValueTask LaunchAsync(LaunchConfiguration configuration)
+ {
+ (bool isOk, string gamePath) = await GetGamePathAsync().ConfigureAwait(false);
+
+ if (isOk)
+ {
+ if (gameSemaphore.CurrentCount == 0)
+ {
+ return;
+ }
+
+ string commandLine = new CommandLineBuilder()
+ .Append("-window-mode", configuration.WindowMode)
+ .Append("-screen-fullscreen", configuration.IsFullScreen ? 1 : 0)
+ .Append("-screen-width", configuration.ScreenWidth)
+ .Append("-screen-height", configuration.ScreenHeight)
+ .Append("-monitor", configuration.Monitor)
+ .Build();
+
+ Process game = new()
+ {
+ StartInfo = new()
{
- // Save result.
- entry.Value = result.Value;
- appDbContext.Settings.Update(entry);
- await appDbContext.SaveChangesAsync().ConfigureAwait(false);
+ Arguments = commandLine,
+ FileName = gamePath,
+ UseShellExecute = true,
+ Verb = "runas",
+ WorkingDirectory = Path.GetDirectoryName(gamePath),
+ },
+ };
+
+ using (await gameSemaphore.EnterAsync().ConfigureAwait(false))
+ {
+ if (configuration.UnlockFPS)
+ {
+ IGameFpsUnlocker unlocker = new GameFpsUnlocker(game, configuration.TargetFPS);
+
+ await unlocker.UnlockAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(10000), TimeSpan.FromMilliseconds(2000)).ConfigureAwait(false);
}
else
{
- return new(false, null!);
+ if (game.Start())
+ {
+ await game.WaitForExitAsync().ConfigureAwait(false);
+ }
}
}
-
- // Set cache and return.
- string path = memoryCache.Set(key, Must.NotNull(entry.Value!));
- return new(true, path);
}
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs
index fb9aef99..966aed6c 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs
@@ -15,4 +15,23 @@ internal interface IGameService
///
/// 结果
ValueTask> GetGamePathAsync();
+
+ ///
+ /// 获取游戏路径,跳过异步定位器
+ ///
+ /// 游戏路径,当路径无效时会设置并返回
+ string GetGamePathSkipLocator();
+
+ ///
+ /// 异步启动
+ ///
+ /// 启动配置
+ /// 任务
+ ValueTask LaunchAsync(LaunchConfiguration configuration);
+
+ ///
+ /// 重写游戏路径
+ ///
+ /// 路径
+ void OverwriteGamePath(string path);
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchConfiguration.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchConfiguration.cs
new file mode 100644
index 00000000..d28923b1
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchConfiguration.cs
@@ -0,0 +1,45 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Service.Game;
+
+///
+/// 启动游戏配置
+///
+internal struct LaunchConfiguration
+{
+ ///
+ /// 是否全屏,全屏时无边框设置将被覆盖
+ ///
+ public bool IsFullScreen { get; set; }
+
+ ///
+ /// Override fullscreen windowed mode. Accepted values are exclusive or borderless.
+ ///
+ public string WindowMode { get; private set; }
+
+ ///
+ /// 是否启用解锁帧率
+ ///
+ public bool UnlockFPS { get; private set; }
+
+ ///
+ /// 目标帧率
+ ///
+ public int TargetFPS { get; private set; }
+
+ ///
+ /// 窗口宽度
+ ///
+ public int ScreenWidth { get; private set; }
+
+ ///
+ /// 窗口高度
+ ///
+ public int ScreenHeight { get; private set; }
+
+ ///
+ /// 显示器编号
+ ///
+ public int Monitor { get; private set; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs
index 66541172..4884b33e 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs
@@ -158,4 +158,4 @@ internal class HutaoCache : IHutaoCache
{
Overview = await hutaoService.GetOverviewAsync().ConfigureAwait(false);
}
-}
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
index 7de19a0f..4558210a 100644
--- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
+++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
@@ -34,6 +34,7 @@
+
@@ -41,6 +42,7 @@
+
@@ -67,6 +69,7 @@
+
@@ -93,6 +96,7 @@
+
@@ -144,6 +148,16 @@
+
+
+ MSBuild:Compile
+
+
+
+
+ MSBuild:Compile
+
+
MSBuild:Compile
diff --git a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml
index 302cefe5..9366fbd1 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml
@@ -27,7 +27,12 @@
Icon="{shcm:BitmapIcon Source=ms-appx:///Resource/Icon/UI_BtnIcon_ActivityEntry.png}"/>
-
+
+
+
(INavigationAwaiter.Default, true);
}
-
- private async Task UpdateThemeAsync()
- {
- // It seems that UISettings.ColorValuesChanged
- // event can raise up on a background thread.
- await ThreadHelper.SwitchToMainThreadAsync();
-
- App current = Ioc.Default.GetRequiredService();
-
- if (!ThemeHelper.Equals(current.RequestedTheme, RequestedTheme))
- {
- ILogger logger = Ioc.Default.GetRequiredService>();
- logger.LogInformation(EventIds.CommonLog, "Element Theme [{element}] | App Theme [{app}]", RequestedTheme, current.RequestedTheme);
-
- // Update controls' theme which presents in the PopupRoot
- RequestedTheme = ThemeHelper.ApplicationToElement(current.RequestedTheme);
- }
- }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml
index b6cfd7ee..beb0f4bb 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml
@@ -4,24 +4,17 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Converters"
xmlns:mxic="using:Microsoft.Xaml.Interactions.Core"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:shc="using:Snap.Hutao.Control"
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
- xmlns:shmmc="using:Snap.Hutao.Model.Metadata.Converter"
xmlns:shv="using:Snap.Hutao.ViewModel"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
d:DataContext="{d:DesignInstance shv:AchievementViewModel}">
-
-
-
-
-
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml
index cbcf9f46..6da2956c 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml
@@ -8,7 +8,6 @@
xmlns:cwua="using:CommunityToolkit.WinUI.UI.Animations"
xmlns:cwub="using:CommunityToolkit.WinUI.UI.Behaviors"
xmlns:cwucont="using:CommunityToolkit.WinUI.UI.Controls"
- xmlns:cwuconv="using:CommunityToolkit.WinUI.UI.Converters"
xmlns:mxic="using:Microsoft.Xaml.Interactions.Core"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:shc="using:Snap.Hutao.Control"
@@ -21,7 +20,6 @@
-
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AvatarPropertyPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AvatarPropertyPage.xaml
index 45890b33..25028275 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/AvatarPropertyPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AvatarPropertyPage.xaml
@@ -36,8 +36,6 @@
0.5
-
-
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml
index 582e8578..548c31d2 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml
@@ -140,10 +140,17 @@
-
+
+
+
+
+
@@ -26,11 +28,15 @@
-
-
+
+
-
-
+
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml
new file mode 100644
index 00000000..2d400f9e
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml.cs
new file mode 100644
index 00000000..2ae704f8
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml.cs
@@ -0,0 +1,22 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Control;
+using Snap.Hutao.ViewModel;
+
+namespace Snap.Hutao.View.Page;
+
+///
+/// 启动游戏页面
+///
+public sealed partial class LaunchGamePage : ScopedPage
+{
+ ///
+ /// 构造一个新的启动游戏页面
+ ///
+ public LaunchGamePage()
+ {
+ InitializeWith();
+ InitializeComponent();
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
index 2b3d1419..9b1e5798 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
@@ -95,6 +95,17 @@
+
+
+
+
+
+
+
+
@@ -20,16 +20,6 @@
-
-
-
-
-
-
-
-
-
-
@@ -92,10 +82,7 @@
-
-
-
-
+
@@ -465,6 +452,176 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Margin="16,16,0,0"
+ HorizontalAlignment="Stretch"
+ HorizontalContentAlignment="Stretch"
+ Header="故事">
@@ -538,17 +695,6 @@
-
-
-
-
diff --git a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
index 0bcfe77c..ec5bce4f 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
@@ -8,8 +8,8 @@
xmlns:mxim="using:Microsoft.Xaml.Interactions.Media"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:shc="using:Snap.Hutao.Control"
- xmlns:shvm="using:Snap.Hutao.ViewModel"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
+ xmlns:shvm="using:Snap.Hutao.ViewModel"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance shvm:UserViewModel}">
@@ -120,11 +120,19 @@
HorizontalAlignment="Left"
Margin="0,0"
Height="32"/>
-
+ Margin="12,0,0,0"
+ VerticalAlignment="Center">
+
+
+
+
? avatarAppearanceRanks;
private List? avatarConstellationInfos;
private List? teamAppearances;
+ private Overview? overview;
///
/// 构造一个新的胡桃数据库视图模型
@@ -58,6 +60,11 @@ internal class HutaoDatabaseViewModel : ObservableObject, ISupportCancellation
///
public List? TeamAppearances { get => teamAppearances; set => SetProperty(ref teamAppearances, value); }
+ ///
+ /// 总览数据
+ ///
+ public Overview? Overview { get => overview; set => SetProperty(ref overview, value); }
+
///
/// 打开界面命令
///
@@ -71,6 +78,7 @@ internal class HutaoDatabaseViewModel : ObservableObject, ISupportCancellation
AvatarUsageRanks = hutaoCache.AvatarUsageRanks;
AvatarConstellationInfos = hutaoCache.AvatarConstellationInfos;
TeamAppearances = hutaoCache.TeamAppearances;
+ Overview = hutaoCache.Overview;
}
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs
new file mode 100644
index 00000000..54b5cd55
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs
@@ -0,0 +1,17 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using CommunityToolkit.Mvvm.ComponentModel;
+using Snap.Hutao.Control;
+
+namespace Snap.Hutao.ViewModel;
+
+///
+/// 启动游戏视图模型
+///
+[Injection(InjectAs.Scoped)]
+internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
+{
+ ///
+ public CancellationToken CancellationToken { get; set; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs
index b0945ec7..6781fa30 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs
@@ -4,7 +4,11 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Context.Database;
using Snap.Hutao.Core.Database;
+using Snap.Hutao.Core.Threading;
+using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Model.Entity;
+using Snap.Hutao.Service.Game;
+using Snap.Hutao.Service.Game.Locator;
namespace Snap.Hutao.ViewModel;
@@ -15,23 +19,33 @@ namespace Snap.Hutao.ViewModel;
internal class SettingViewModel : ObservableObject
{
private readonly AppDbContext appDbContext;
+ private readonly IGameService gameService;
private readonly SettingEntry isEmptyHistoryWishVisibleEntry;
private bool isEmptyHistoryWishVisible;
+ private string gamePath;
///
/// 构造一个新的测试视图模型
///
/// 数据库上下文
+ /// 游戏服务
+ /// 异步命令工厂
/// 实验性功能
- public SettingViewModel(AppDbContext appDbContext, ExperimentalFeaturesViewModel experimental)
+ public SettingViewModel(AppDbContext appDbContext, IGameService gameService, IAsyncRelayCommandFactory asyncRelayCommandFactory, ExperimentalFeaturesViewModel experimental)
{
this.appDbContext = appDbContext;
+ this.gameService = gameService;
+
Experimental = experimental;
isEmptyHistoryWishVisibleEntry = appDbContext.Settings
.SingleOrAdd(e => e.Key == SettingEntry.IsEmptyHistoryWishVisible, () => new(SettingEntry.IsEmptyHistoryWishVisible, true.ToString()), out _);
IsEmptyHistoryWishVisible = bool.Parse(isEmptyHistoryWishVisibleEntry.Value!);
+
+ GamePath = gameService.GetGamePathSkipLocator();
+
+ SetGamePathCommand = asyncRelayCommandFactory.Create(SetGamePathAsync);
}
///
@@ -56,8 +70,37 @@ internal class SettingViewModel : ObservableObject
}
}
+ ///
+ /// 游戏路径
+ ///
+ public string GamePath
+ {
+ get => gamePath;
+ [MemberNotNull(nameof(gamePath))]
+ set => SetProperty(ref gamePath, value);
+ }
+
///
/// 实验性功能
///
public ExperimentalFeaturesViewModel Experimental { get; }
+
+ ///
+ /// 设置游戏路径命令
+ ///
+ public ICommand SetGamePathCommand { get; }
+
+ private async Task SetGamePathAsync()
+ {
+ IGameLocator locator = Ioc.Default.GetRequiredService>()
+ .Single(l => l.Name == nameof(ManualGameLocator));
+
+ (bool isOk, string path) = await locator.LocateGamePathAsync().ConfigureAwait(false);
+ if (isOk)
+ {
+ gameService.OverwriteGamePath(path);
+ await ThreadHelper.SwitchToMainThreadAsync();
+ GamePath = path;
+ }
+ }
}
\ No newline at end of file