diff --git a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
index 5378799a..4f049ed9 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs
@@ -15,9 +15,6 @@ namespace Snap.Hutao.Core;
///
internal static class CoreEnvironment
{
- private const string CryptographyKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\";
- private const string MachineGuidValue = "MachineGuid";
-
// 计算过程:https://gist.github.com/Lightczx/373c5940b36e24b25362728b52dec4fd
///
@@ -71,6 +68,9 @@ internal static class CoreEnvironment
WriteIndented = true,
};
+ private const string CryptographyKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\";
+ private const string MachineGuidValue = "MachineGuid";
+
static CoreEnvironment()
{
Version = Package.Current.Id.Version.ToVersion();
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs
index bc3add7e..ad22f519 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs
@@ -26,7 +26,6 @@ internal class DbCurrent
///
/// 数据集
/// 消息器
- ///
public DbCurrent(DbSet dbSet, IMessenger messenger)
{
this.dbSet = dbSet;
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs
index e0e87e8f..019fb9e1 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs
@@ -3,6 +3,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Snap.Hutao.Model.Entity;
namespace Snap.Hutao.Core.Database;
@@ -91,4 +92,4 @@ public static class DbSetExtension
dbSet.Update(entity);
return dbSet.Context().SaveChanges();
}
-}
\ No newline at end of file
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/SettingEntryHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/SettingEntryHelper.cs
new file mode 100644
index 00000000..f336d7e6
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/SettingEntryHelper.cs
@@ -0,0 +1,95 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.EntityFrameworkCore;
+using Snap.Hutao.Model.Entity;
+
+namespace Snap.Hutao.Core.Database;
+
+///
+/// 设置帮助类
+///
+public static class SettingEntryHelper
+{
+ ///
+ /// 获取或添加一个对应的设置
+ ///
+ /// 设置集
+ /// 键
+ /// 值
+ /// 设置
+ public static SettingEntry SingleOrAdd(this DbSet dbSet, string key, string value)
+ {
+ SettingEntry? entry = dbSet.SingleOrDefault(entry => key == entry.Key);
+
+ if (entry == null)
+ {
+ entry = new(key, value);
+ dbSet.Add(entry);
+ dbSet.Context().SaveChanges();
+ }
+
+ return entry;
+ }
+
+ ///
+ /// 获取或添加一个对应的设置
+ ///
+ /// 设置集
+ /// 键
+ /// 值工厂
+ /// 设置
+ public static SettingEntry SingleOrAdd(this DbSet dbSet, string key, Func valueFactory)
+ {
+ SettingEntry? entry = dbSet.SingleOrDefault(entry => key == entry.Key);
+
+ if (entry == null)
+ {
+ entry = new(key, valueFactory());
+ dbSet.Add(entry);
+ dbSet.Context().SaveChanges();
+ }
+
+ return entry;
+ }
+
+ ///
+ /// 获取 Boolean 值
+ ///
+ /// 设置
+ /// 值
+ public static bool GetBoolean(this SettingEntry entry)
+ {
+ return bool.Parse(entry.Value!);
+ }
+
+ ///
+ /// 设置 Boolean 值
+ ///
+ /// 设置
+ /// 值
+ public static void SetBoolean(this SettingEntry entry, bool value)
+ {
+ entry.Value = value.ToString();
+ }
+
+ ///
+ /// 获取 Int32 值
+ ///
+ /// 设置
+ /// 值
+ public static int GetInt32(this SettingEntry entry)
+ {
+ return int.Parse(entry.Value!);
+ }
+
+ ///
+ /// 设置 Int32 值
+ ///
+ /// 设置
+ /// 值
+ public static void SetInt32(this SettingEntry entry, int value)
+ {
+ entry.Value = value.ToString();
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSerializer.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSerializer.cs
index 26b6a82d..bcbb8dc5 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSerializer.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSerializer.cs
@@ -11,7 +11,7 @@ namespace Snap.Hutao.Core.IO.Ini;
internal static class IniSerializer
{
///
- /// 异步反序列化
+ /// 反序列化
///
/// 文件流
/// Ini 元素集合
@@ -44,4 +44,20 @@ internal static class IniSerializer
}
}
}
+
+ ///
+ /// 序列化
+ ///
+ /// 写入的流
+ /// 元素
+ public static void Serialize(FileStream fileStream, IEnumerable elements)
+ {
+ using (TextWriter writer = new StreamWriter(fileStream))
+ {
+ foreach (IniElement element in elements)
+ {
+ writer.WriteLine(element.ToString());
+ }
+ }
+ }
}
\ 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 1eda1194..5672c517 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs
@@ -5,6 +5,7 @@ using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core.Threading;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Navigation;
+using System.Security.Principal;
namespace Snap.Hutao.Core.LifeCycle;
@@ -20,6 +21,19 @@ internal static class Activation
private static readonly SemaphoreSlim ActivateSemaphore = new(1);
+ ///
+ /// 获取是否提升了权限
+ ///
+ /// 是否提升了权限
+ public static bool GetElevated()
+ {
+ using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
+ {
+ WindowsPrincipal principal = new(identity);
+ return principal.IsInRole(WindowsBuiltInRole.Administrator);
+ }
+ }
+
///
/// 响应激活事件
/// 激活事件一般不会在UI线程上触发
@@ -70,6 +84,18 @@ internal static class Activation
case LaunchGame:
{
+ await ThreadHelper.SwitchToMainThreadAsync();
+ if (!MainWindow.IsPresent)
+ {
+ _ = Ioc.Default.GetRequiredService();
+ }
+ else
+ {
+ await Ioc.Default
+ .GetRequiredService()
+ .NavigateAsync(INavigationAwaiter.Default, true).ConfigureAwait(false);
+ }
+
break;
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs
index e41aea32..497d1018 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs
@@ -18,16 +18,18 @@ namespace Snap.Hutao.Core.Windowing;
/// 窗口管理器
/// 主要包含了针对窗体的 P/Inoke 逻辑
///
-internal sealed class ExtendedWindow
+/// 窗体类型
+internal sealed class ExtendedWindow
+ where TWindow : Window, IExtendedWindowSource
{
private readonly HWND handle;
private readonly AppWindow appWindow;
- private readonly Window window;
+ private readonly TWindow window;
private readonly FrameworkElement titleBar;
- private readonly ILogger logger;
- private readonly WindowSubclassManager subclassManager;
+ private readonly ILogger> logger;
+ private readonly WindowSubclassManager subclassManager;
private readonly bool useLegacyDragBar;
@@ -36,11 +38,11 @@ internal sealed class ExtendedWindow
///
/// 窗口
/// 充当标题栏的元素
- private ExtendedWindow(Window window, FrameworkElement titleBar)
+ private ExtendedWindow(TWindow window, FrameworkElement titleBar)
{
this.window = window;
this.titleBar = titleBar;
- logger = Ioc.Default.GetRequiredService>();
+ logger = Ioc.Default.GetRequiredService>>();
handle = (HWND)WindowNative.GetWindowHandle(window);
@@ -48,7 +50,7 @@ internal sealed class ExtendedWindow
appWindow = AppWindow.GetFromWindowId(windowId);
useLegacyDragBar = !AppWindowTitleBar.IsCustomizationSupported();
- subclassManager = new(handle, useLegacyDragBar);
+ subclassManager = new(window, handle, useLegacyDragBar);
InitializeWindow();
}
@@ -57,11 +59,10 @@ internal sealed class ExtendedWindow
/// 初始化
///
/// 窗口
- /// 标题栏
/// 实例
- public static ExtendedWindow Initialize(Window window, FrameworkElement titleBar)
+ public static ExtendedWindow Initialize(TWindow window)
{
- return new(window, titleBar);
+ return new(window, window.TitleBar);
}
private static void UpdateTitleButtonColor(AppWindowTitleBar appTitleBar)
@@ -103,7 +104,8 @@ internal sealed class ExtendedWindow
appWindow.Title = "胡桃";
ExtendsContentIntoTitleBar();
- Persistence.RecoverOrInit(appWindow);
+
+ Persistence.RecoverOrInit(appWindow, window.PersistSize, window.InitSize);
// Log basic window state here.
(string pos, string size) = GetPostionAndSize(appWindow);
@@ -115,14 +117,18 @@ internal sealed class ExtendedWindow
logger.LogInformation(EventIds.BackdropState, "Apply {name} : {result}", nameof(SystemBackdrop), micaApplied ? "succeed" : "failed");
bool subClassApplied = subclassManager.TrySetWindowSubclass();
- logger.LogInformation(EventIds.SubClassing, "Apply {name} : {result}", nameof(WindowSubclassManager), subClassApplied ? "succeed" : "failed");
+ logger.LogInformation(EventIds.SubClassing, "Apply {name} : {result}", nameof(WindowSubclassManager), subClassApplied ? "succeed" : "failed");
window.Closed += OnWindowClosed;
}
private void OnWindowClosed(object sender, WindowEventArgs args)
{
- Persistence.Save(appWindow);
+ if (window.PersistSize)
+ {
+ Persistence.Save(appWindow);
+ }
+
subclassManager?.Dispose();
}
@@ -155,4 +161,4 @@ internal sealed class ExtendedWindow
RectInt32 dragRect = new RectInt32(48, 0, (int)titleBar.ActualWidth, (int)titleBar.ActualHeight).Scale(scale);
appTitleBar.SetDragRectangles(dragRect.Enumerate().ToArray());
}
-}
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/IExtendedWindowSource.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/IExtendedWindowSource.cs
new file mode 100644
index 00000000..2c4e6e6d
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/IExtendedWindowSource.cs
@@ -0,0 +1,37 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.UI.Xaml;
+using Windows.Graphics;
+using Windows.Win32.UI.WindowsAndMessaging;
+
+namespace Snap.Hutao.Core.Windowing;
+
+///
+/// 为扩展窗体提供必要的选项
+///
+/// 窗体类型
+internal interface IExtendedWindowSource
+{
+ ///
+ /// 提供的标题栏
+ ///
+ FrameworkElement TitleBar { get; }
+
+ ///
+ /// 是否持久化尺寸
+ ///
+ bool PersistSize { get; }
+
+ ///
+ /// 初始大小
+ ///
+ SizeInt32 InitSize { get; }
+
+ ///
+ /// 处理最大最小信息
+ ///
+ /// 信息指针
+ /// 缩放比
+ unsafe void ProcessMinMaxInfo(MINMAXINFO* pInfo, double scalingFactor);
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Persistence.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Persistence.cs
index 431f6964..2d9ce21f 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Persistence.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Persistence.cs
@@ -21,21 +21,26 @@ internal static class Persistence
/// 设置窗体位置
///
/// 应用窗体
- public static void RecoverOrInit(AppWindow appWindow)
+ /// 持久化尺寸
+ /// 初始尺寸
+ public static void RecoverOrInit(AppWindow appWindow, bool persistSize, SizeInt32 size)
{
// Set first launch size.
HWND hwnd = (HWND)Win32Interop.GetWindowFromWindowId(appWindow.Id);
- SizeInt32 size = TransformSizeForWindow(new(1200, 741), hwnd);
- RectInt32 rect = StructMarshal.RectInt32(size);
+ SizeInt32 transformedSize = TransformSizeForWindow(size, hwnd);
+ RectInt32 rect = StructMarshal.RectInt32(transformedSize);
- RectInt32 target = (CompactRect)LocalSetting.Get(SettingKeys.WindowRect, (ulong)(CompactRect)rect);
- if (target.Width * target.Height < 848 * 524)
+ if (persistSize)
{
- target = rect;
+ RectInt32 persistedSize = (CompactRect)LocalSetting.Get(SettingKeys.WindowRect, (ulong)(CompactRect)rect);
+ if (persistedSize.Width * persistedSize.Height > 848 * 524)
+ {
+ rect = persistedSize;
+ }
}
- TransformToCenterScreen(ref target);
- appWindow.MoveAndResize(target);
+ TransformToCenterScreen(ref rect);
+ appWindow.MoveAndResize(rect);
}
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclassManager.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclassManager.cs
index ae87de4e..c4c91c02 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclassManager.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclassManager.cs
@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Microsoft.UI.Xaml;
using Windows.Win32.Foundation;
using Windows.Win32.UI.Shell;
using Windows.Win32.UI.WindowsAndMessaging;
@@ -11,14 +12,14 @@ namespace Snap.Hutao.Core.Windowing;
///
/// 窗体子类管理器
///
-internal class WindowSubclassManager : IDisposable
+/// 窗体类型
+internal class WindowSubclassManager : IDisposable
+ where TWindow : Window, IExtendedWindowSource
{
private const int WindowSubclassId = 101;
private const int DragBarSubclassId = 102;
- private const int MinWidth = 848;
- private const int MinHeight = 524;
-
+ private readonly TWindow window;
private readonly HWND hwnd;
private readonly bool isLegacyDragBar;
private HWND hwndDragBar;
@@ -30,12 +31,13 @@ internal class WindowSubclassManager : IDisposable
///
/// 构造一个新的窗体子类管理器
///
+ /// 窗体实例
/// 窗体句柄
/// 是否为经典标题栏区域
- public WindowSubclassManager(HWND hwnd, bool isLegacyDragBar)
+ public WindowSubclassManager(TWindow window, HWND hwnd, bool isLegacyDragBar)
{
- Must.NotNull(hwnd);
- this.hwnd = hwnd;
+ this.window = window;
+ this.hwnd = Must.NotNull(hwnd);
this.isLegacyDragBar = isLegacyDragBar;
}
@@ -85,9 +87,7 @@ internal class WindowSubclassManager : IDisposable
case WM_GETMINMAXINFO:
{
double scalingFactor = Persistence.GetScaleForWindow(hwnd);
- MINMAXINFO* info = (MINMAXINFO*)lParam.Value;
- info->ptMinTrackSize.X = (int)Math.Max(MinWidth * scalingFactor, info->ptMinTrackSize.X);
- info->ptMinTrackSize.Y = (int)Math.Max(MinHeight * scalingFactor, info->ptMinTrackSize.Y);
+ window.ProcessMinMaxInfo((MINMAXINFO*)lParam.Value, scalingFactor);
break;
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml b/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml
index 56cbbfea..8d607b0e 100644
--- a/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml
@@ -1,12 +1,63 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs
index 84f88421..25437c8a 100644
--- a/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs
@@ -1,20 +1,63 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
+using Snap.Hutao.Core.Windowing;
+using Snap.Hutao.ViewModel;
+using Windows.Graphics;
+using Windows.Win32.UI.WindowsAndMessaging;
namespace Snap.Hutao;
///
/// 启动游戏窗口
///
-public sealed partial class LaunchGameWindow : Window
+[Injection(InjectAs.Singleton)]
+public sealed partial class LaunchGameWindow : Window, IDisposable, IExtendedWindowSource
{
+ private const int MinWidth = 240;
+ private const int MinHeight = 240;
+
+ private const int MaxWidth = 320;
+ private const int MaxHeight = 320;
+
+ private readonly IServiceScope scope;
+
///
/// 构造一个新的启动游戏窗口
///
- public LaunchGameWindow()
+ /// 范围工厂
+ public LaunchGameWindow(IServiceScopeFactory scopeFactory)
{
InitializeComponent();
+ ExtendedWindow.Initialize(this);
+
+ scope = scopeFactory.CreateScope();
+ RootGrid.DataContext = scope.ServiceProvider.GetRequiredService();
+ }
+
+ ///
+ public FrameworkElement TitleBar { get => DragableGrid; }
+
+ ///
+ public bool PersistSize { get => false; }
+
+ ///
+ public SizeInt32 InitSize { get => new(320, 320); }
+
+ ///
+ public void Dispose()
+ {
+ scope.Dispose();
+ }
+
+ ///
+ public unsafe void ProcessMinMaxInfo(MINMAXINFO* pInfo, double scalingFactor)
+ {
+ pInfo->ptMinTrackSize.X = (int)Math.Max(MinWidth * scalingFactor, pInfo->ptMinTrackSize.X);
+ pInfo->ptMinTrackSize.Y = (int)Math.Max(MinHeight * scalingFactor, pInfo->ptMinTrackSize.Y);
+ pInfo->ptMaxTrackSize.X = (int)Math.Min(MaxWidth * scalingFactor, pInfo->ptMaxTrackSize.X);
+ pInfo->ptMaxTrackSize.Y = (int)Math.Min(MaxHeight * scalingFactor, pInfo->ptMaxTrackSize.Y);
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
index c748caea..421099a8 100644
--- a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
@@ -3,6 +3,8 @@
using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Windowing;
+using Windows.Graphics;
+using Windows.Win32.UI.WindowsAndMessaging;
namespace Snap.Hutao;
@@ -11,14 +13,40 @@ namespace Snap.Hutao;
///
[Injection(InjectAs.Singleton)]
[SuppressMessage("", "CA1001")]
-public sealed partial class MainWindow : Window
+public sealed partial class MainWindow : Window, IExtendedWindowSource
{
+ private const int MinWidth = 848;
+ private const int MinHeight = 524;
+
///
/// 构造一个新的主窗体
///
public MainWindow()
{
InitializeComponent();
- ExtendedWindow.Initialize(this, TitleBarView.DragArea);
+ ExtendedWindow.Initialize(this);
+ IsPresent = true;
+ Closed += (s, e) => IsPresent = false;
+ }
+
+ ///
+ /// 是否打开
+ ///
+ public static bool IsPresent { get; private set; }
+
+ ///
+ public FrameworkElement TitleBar { get => TitleBarView.DragArea; }
+
+ ///
+ public bool PersistSize { get => true; }
+
+ ///
+ public SizeInt32 InitSize { get => new(1200, 741); }
+
+ ///
+ public unsafe void ProcessMinMaxInfo(MINMAXINFO* pInfo, double scalingFactor)
+ {
+ pInfo->ptMinTrackSize.X = (int)Math.Max(MinWidth * scalingFactor, pInfo->ptMinTrackSize.X);
+ pInfo->ptMinTrackSize.Y = (int)Math.Max(MinHeight * scalingFactor, pInfo->ptMinTrackSize.Y);
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/20221031104940_GameAccount.Designer.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/20221031104940_GameAccount.Designer.cs
new file mode 100644
index 00000000..1f33b341
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Migrations/20221031104940_GameAccount.Designer.cs
@@ -0,0 +1,215 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Snap.Hutao.Context.Database;
+
+#nullable disable
+
+namespace Snap.Hutao.Migrations
+{
+ [DbContext(typeof(AppDbContext))]
+ [Migration("20221031104940_GameAccount")]
+ partial class GameAccount
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "6.0.10");
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("ArchiveId")
+ .HasColumnType("TEXT");
+
+ b.Property("Current")
+ .HasColumnType("INTEGER");
+
+ b.Property("Id")
+ .HasColumnType("INTEGER");
+
+ b.Property("Status")
+ .HasColumnType("INTEGER");
+
+ b.Property("Time")
+ .HasColumnType("TEXT");
+
+ b.HasKey("InnerId");
+
+ b.HasIndex("ArchiveId");
+
+ b.ToTable("achievements");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("IsSelected")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("achievement_archives");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Info")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Uid")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("avatar_infos");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("IsSelected")
+ .HasColumnType("INTEGER");
+
+ b.Property("Uid")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("gacha_archives");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("ArchiveId")
+ .HasColumnType("TEXT");
+
+ b.Property("GachaType")
+ .HasColumnType("INTEGER");
+
+ b.Property("Id")
+ .HasColumnType("INTEGER");
+
+ b.Property("ItemId")
+ .HasColumnType("INTEGER");
+
+ b.Property("QueryType")
+ .HasColumnType("INTEGER");
+
+ b.Property("Time")
+ .HasColumnType("TEXT");
+
+ b.HasKey("InnerId");
+
+ b.HasIndex("ArchiveId");
+
+ b.ToTable("gacha_items");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("AttachUid")
+ .HasColumnType("TEXT");
+
+ b.Property("MihoyoSDK")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("game_accounts");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
+ {
+ b.Property("Key")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Key");
+
+ b.ToTable("settings");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Cookie")
+ .HasColumnType("TEXT");
+
+ b.Property("IsSelected")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("users");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
+ {
+ b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
+ .WithMany()
+ .HasForeignKey("ArchiveId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Archive");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
+ {
+ b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
+ .WithMany()
+ .HasForeignKey("ArchiveId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Archive");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/20221031104940_GameAccount.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/20221031104940_GameAccount.cs
new file mode 100644
index 00000000..dff7e721
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Migrations/20221031104940_GameAccount.cs
@@ -0,0 +1,34 @@
+//
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Snap.Hutao.Migrations
+{
+ public partial class GameAccount : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "game_accounts",
+ columns: table => new
+ {
+ InnerId = table.Column(type: "TEXT", nullable: false),
+ AttachUid = table.Column(type: "TEXT", nullable: true),
+ Type = table.Column(type: "INTEGER", nullable: false),
+ Name = table.Column(type: "TEXT", nullable: false),
+ MihoyoSDK = table.Column(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_game_accounts", x => x.InnerId);
+ });
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "game_accounts");
+ }
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs
index 1a7dfe2a..a20c886d 100644
--- a/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs
@@ -15,7 +15,7 @@ namespace Snap.Hutao.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
- modelBuilder.HasAnnotation("ProductVersion", "6.0.9");
+ modelBuilder.HasAnnotation("ProductVersion", "6.0.10");
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
{
@@ -131,6 +131,31 @@ namespace Snap.Hutao.Migrations
b.ToTable("gacha_items");
});
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("AttachUid")
+ .HasColumnType("TEXT");
+
+ b.Property("MihoyoSDK")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("game_accounts");
+ });
+
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
{
b.Property("Key")
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/User.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/User.cs
index 1df4de43..d9423096 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/User.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/User.cs
@@ -56,7 +56,7 @@ public class User : ObservableObject
}
///
- public Cookie Cookie
+ public Cookie? Cookie
{
get => inner.Cookie;
set
@@ -71,7 +71,7 @@ public class User : ObservableObject
///
public bool HasSToken
{
- get => inner.Cookie.ContainsSToken();
+ get => inner.Cookie!.ContainsSToken();
}
///
@@ -84,17 +84,6 @@ public class User : ObservableObject
///
public bool IsInitialized { get => isInitialized; }
- ///
- /// 更新SToken
- ///
- /// uid
- /// cookie
- internal void UpdateSToken(string uid, Cookie cookie)
- {
- Cookie.InsertSToken(uid, cookie);
- OnPropertyChanged(nameof(HasSToken));
- }
-
///
/// 从数据库恢复用户
///
@@ -125,6 +114,17 @@ public class User : ObservableObject
return successful ? user : null;
}
+ ///
+ /// 更新SToken
+ ///
+ /// uid
+ /// cookie
+ internal void UpdateSToken(string uid, Cookie cookie)
+ {
+ Cookie!.InsertSToken(uid, cookie);
+ OnPropertyChanged(nameof(HasSToken));
+ }
+
private async Task InitializeCoreAsync(UserClient userClient, BindingClient userGameRoleClient, CancellationToken token = default)
{
if (isInitialized)
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/GameAccount.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/GameAccount.cs
index 97fe2ed7..b587a07b 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/GameAccount.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/GameAccount.cs
@@ -1,9 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Binding.LaunchGame;
-using Snap.Hutao.Web.Hoyolab;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -13,8 +11,11 @@ namespace Snap.Hutao.Model.Entity;
/// 游戏内账号
///
[Table("game_accounts")]
-public class GameAccount : ISelectable
+public class GameAccount : INotifyPropertyChanged
{
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged;
+
///
/// 内部Id
///
@@ -22,9 +23,6 @@ public class GameAccount : ISelectable
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid InnerId { get; set; }
- ///
- public bool IsSelected { get; set; }
-
///
/// 对应的Uid
///
@@ -41,7 +39,43 @@ public class GameAccount : ISelectable
public string Name { get; set; } = default!;
///
- /// MIHOYOSDK_ADL_PROD_CN_h3123967166
+ /// [MIHOYOSDK_ADL_PROD_CN_h3123967166]
+ /// see
///
public string MihoyoSDK { get; set; } = default!;
+
+ ///
+ /// 构造一个新的游戏内账号
+ ///
+ /// 名称
+ /// sdk
+ /// 游戏内账号
+ public static GameAccount Create(string name, string sdk)
+ {
+ return new()
+ {
+ Name = name,
+ MihoyoSDK = sdk,
+ };
+ }
+
+ ///
+ /// 更新绑定的Uid
+ ///
+ /// uid
+ public void UpdateAttachUid(string? uid)
+ {
+ AttachUid = uid;
+ PropertyChanged?.Invoke(this, new(nameof(AttachUid)));
+ }
+
+ ///
+ /// 更新名称
+ ///
+ /// 新名称
+ public void UpdateName(string name)
+ {
+ Name = name;
+ PropertyChanged?.Invoke(this, new(nameof(Name)));
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs
index 74d06f99..8a947f70 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs
@@ -22,6 +22,36 @@ public class SettingEntry
///
public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";
+ ///
+ /// 启动游戏 全屏
+ ///
+ public const string LaunchIsFullScreen = "Launch.IsFullScreen";
+
+ ///
+ /// 启动游戏 无边框
+ ///
+ public const string LaunchIsBorderless = "Launch.IsBorderless";
+
+ ///
+ /// 启动游戏 宽度
+ ///
+ public const string LaunchScreenWidth = "Launch.ScreenWidth";
+
+ ///
+ /// 启动游戏 高度
+ ///
+ public const string LaunchScreenHeight = "Launch.ScreenHeight";
+
+ ///
+ /// 启动游戏 解锁帧率
+ ///
+ public const string LaunchUnlockFps = "Launch.UnlockFps";
+
+ ///
+ /// 启动游戏 目标帧率
+ ///
+ public const string LaunchTargetFps = "Launch.TargetFps";
+
///
/// 构造一个新的设置入口
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs
index 24cc0955..1712bfda 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs
@@ -29,7 +29,7 @@ public class User : ISelectable
///
/// 用户的Cookie
///
- public Cookie Cookie { get; set; } = default!;
+ public Cookie? Cookie { get; set; }
///
/// 创建一个新的用户
diff --git a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
index dae27fae..ce720832 100644
--- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
+++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
@@ -2,19 +2,22 @@
+ IgnorableNamespaces="uap desktop6 rescap">
+ Version="1.1.18.0" />
胡桃
DGP Studio
Assets\StoreLogo.png
+
+ disabled
@@ -50,6 +53,7 @@
-
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogHelper.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogHelper.cs
index 441cf087..2f281056 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogHelper.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogHelper.cs
@@ -49,7 +49,6 @@ public static class LogHelper
if (frames.Length > 0 && frames[0].HasNativeImage())
{
-
}
return current;
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs
index 58c5dafc..27ee4f64 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs
@@ -54,59 +54,34 @@ internal class GachaLogUrlWebCacheProvider : IGachaLogUrlProvider
{
using (FileStream fileStream = new(tempFile.Path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
- using (BinaryReader reader = new(fileStream))
+ using (MemoryStream memoryStream = new())
{
- string url = string.Empty;
- while (!reader.EndOfStream())
- {
- uint test = reader.ReadUInt32();
-
- if (test == 0x2F302F31)
- {
- byte[] chars = ReadBytesUntilZero(reader);
- string result = Encoding.UTF8.GetString(chars.AsSpan());
-
- if (result.Contains("&auth_appid=webview_gacha"))
- {
- url = result;
- }
-
- // align up
- long offset = reader.BaseStream.Position % 128;
- reader.BaseStream.Position += 128 - offset;
- }
- }
-
- return new(!string.IsNullOrEmpty(url), url);
+ await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
+ string? result = Match(memoryStream);
+ return new(!string.IsNullOrEmpty(result), result!);
}
}
}
}
else
{
- return new(false, $"未正确提供原神路径,或当前设置的路径不正确");
+ return new(false, "未正确提供原神路径,或当前设置的路径不正确");
}
}
- private static byte[] ReadBytesUntilZero(BinaryReader binaryReader)
+ private static string? Match(MemoryStream stream)
{
- return ReadByteEnumerableUntilZero(binaryReader).ToArray();
- }
+ ReadOnlySpan span = stream.ToArray();
+ ReadOnlySpan match = Encoding.UTF8.GetBytes("https://webstatic.mihoyo.com/hk4e/event/e20190909gacha-v2/index.html");
+ ReadOnlySpan zero = Encoding.UTF8.GetBytes("\0");
- private static IEnumerable ReadByteEnumerableUntilZero(BinaryReader binaryReader)
- {
- while (binaryReader.BaseStream.Position < binaryReader.BaseStream.Length)
+ int index = span.LastIndexOf(match);
+ if (index >= 0)
{
- byte b = binaryReader.ReadByte();
-
- if (b == 0x00)
- {
- yield break;
- }
- else
- {
- yield return b;
- }
+ int length = span[index..].IndexOf(zero);
+ return Encoding.UTF8.GetString(span.Slice(index, length));
}
+
+ return null;
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameAccountRegistryInterop.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameAccountRegistryInterop.cs
new file mode 100644
index 00000000..5759a38b
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameAccountRegistryInterop.cs
@@ -0,0 +1,49 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.Win32;
+using Snap.Hutao.Model.Entity;
+using System.Text;
+
+namespace Snap.Hutao.Service.Game;
+
+///
+/// 定义了对注册表的操作
+///
+internal static class GameAccountRegistryInterop
+{
+ private const string GenshinKey = @"HKEY_CURRENT_USER\Software\miHoYo\原神";
+ private const string SdkKey = "MIHOYOSDK_ADL_PROD_CN_h3123967166";
+
+ ///
+ /// 设置键值
+ ///
+ /// 账户
+ /// 账号是否设置
+ public static bool Set(GameAccount? account)
+ {
+ if (account != null)
+ {
+ Registry.SetValue(GenshinKey, SdkKey, Encoding.UTF8.GetBytes(account.MihoyoSDK));
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// 在注册表中获取账号信息
+ ///
+ /// 当前注册表中的信息
+ public static string? Get()
+ {
+ object? sdk = Registry.GetValue(GenshinKey, SdkKey, Array.Empty());
+
+ if (sdk is byte[] bytes)
+ {
+ return Encoding.UTF8.GetString(bytes);
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs
index 722e2eb2..75898956 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs
@@ -8,9 +8,11 @@ using Snap.Hutao.Core;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.IO.Ini;
using Snap.Hutao.Core.Threading;
+using Snap.Hutao.Model.Binding.LaunchGame;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Game.Locator;
using Snap.Hutao.Service.Game.Unlocker;
+using Snap.Hutao.View.Dialog;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
@@ -24,6 +26,7 @@ namespace Snap.Hutao.Service.Game;
internal class GameService : IGameService
{
private const string GamePathKey = $"{nameof(GameService)}.Cache.{SettingEntry.GamePath}";
+ private const string ConfigFile = "config.ini";
private readonly IServiceScopeFactory scopeFactory;
private readonly IMemoryCache memoryCache;
@@ -136,7 +139,7 @@ internal class GameService : IGameService
public MultiChannel GetMultiChannel()
{
string gamePath = GetGamePathSkipLocator();
- string configPath = Path.Combine(gamePath, "config.ini");
+ string configPath = Path.Combine(Path.GetDirectoryName(gamePath)!, ConfigFile);
using (FileStream stream = File.OpenRead(configPath))
{
@@ -148,11 +151,61 @@ internal class GameService : IGameService
}
}
- public async Task> GetGameAccountCollectionAsync()
+ ///
+ public void SetMultiChannel(LaunchScheme scheme)
+ {
+ string gamePath = GetGamePathSkipLocator();
+ string configPath = Path.Combine(Path.GetDirectoryName(gamePath)!, ConfigFile);
+
+ List elements;
+ using (FileStream readStream = File.OpenRead(configPath))
+ {
+ elements = IniSerializer.Deserialize(readStream).ToList();
+ }
+
+ foreach (IniElement element in elements)
+ {
+ if (element is IniParameter parameter)
+ {
+ if (parameter.Key == "channel")
+ {
+ parameter.Value = scheme.Channel;
+ }
+
+ if (parameter.Key == "sub_channel")
+ {
+ parameter.Value = scheme.SubChannel;
+ }
+ }
+ }
+
+ using (FileStream writeStream = File.Create(configPath))
+ {
+ IniSerializer.Serialize(writeStream, elements);
+ }
+ }
+
+ ///
+ public bool IsGameRunning()
+ {
+ if (gameSemaphore.CurrentCount == 0)
+ {
+ return true;
+ }
+
+ return Process.GetProcessesByName("YuanShen.exe").Any();
+ }
+
+ ///
+ public ObservableCollection GetGameAccountCollection()
{
if (gameAccounts == null)
{
-
+ using (IServiceScope scope = scopeFactory.CreateScope())
+ {
+ AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
+ gameAccounts = new(appDbContext.GameAccounts.ToList());
+ }
}
return gameAccounts;
@@ -161,19 +214,19 @@ internal class GameService : IGameService
///
public async ValueTask LaunchAsync(LaunchConfiguration configuration)
{
- if (gameSemaphore.CurrentCount == 0)
+ if (IsGameRunning())
{
return;
}
string gamePath = GetGamePathSkipLocator();
+ // https://docs.unity.cn/cn/current/Manual/PlayerCommandLineArguments.html
string commandLine = new CommandLineBuilder()
.AppendIf("-popupwindow", configuration.IsBorderless)
.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()
@@ -190,22 +243,122 @@ internal class GameService : IGameService
using (await gameSemaphore.EnterAsync().ConfigureAwait(false))
{
- if (configuration.UnlockFPS)
+ try
{
- IGameFpsUnlocker unlocker = new GameFpsUnlocker(game, configuration.TargetFPS);
-
- TimeSpan findModuleDelay = TimeSpan.FromMilliseconds(100);
- TimeSpan findModuleLimit = TimeSpan.FromMilliseconds(10000);
- TimeSpan adjustFpsDelay = TimeSpan.FromMilliseconds(2000);
- await unlocker.UnlockAsync(findModuleDelay, findModuleLimit, adjustFpsDelay).ConfigureAwait(false);
- }
- else
- {
- if (game.Start())
+ if (configuration.UnlockFPS)
{
- await game.WaitForExitAsync().ConfigureAwait(false);
+ IGameFpsUnlocker unlocker = new GameFpsUnlocker(game, configuration.TargetFPS);
+
+ TimeSpan findModuleDelay = TimeSpan.FromMilliseconds(100);
+ TimeSpan findModuleLimit = TimeSpan.FromMilliseconds(10000);
+ TimeSpan adjustFpsDelay = TimeSpan.FromMilliseconds(2000);
+ if (game.Start())
+ {
+ await unlocker.UnlockAsync(findModuleDelay, findModuleLimit, adjustFpsDelay).ConfigureAwait(false);
+ }
+ }
+ else
+ {
+ if (game.Start())
+ {
+ await game.WaitForExitAsync().ConfigureAwait(false);
+ }
+ }
+ }
+ catch (Win32Exception)
+ {
+ }
+ }
+ }
+
+ ///
+ public async ValueTask DetectGameAccountAsync()
+ {
+ Must.NotNull(gameAccounts!);
+
+ string? registrySdk = GameAccountRegistryInterop.Get();
+ if (!string.IsNullOrEmpty(registrySdk))
+ {
+ GameAccount? account = gameAccounts.SingleOrDefault(a => a.MihoyoSDK == registrySdk);
+
+ if (account == null)
+ {
+ MainWindow mainWindow = Ioc.Default.GetRequiredService();
+ (bool isOk, string name) = await new GameAccountNameDialog(mainWindow).GetInputNameAsync().ConfigureAwait(false);
+
+ if (isOk)
+ {
+ account = GameAccount.Create(name, registrySdk);
+
+ // sync cache
+ await ThreadHelper.SwitchToMainThreadAsync();
+ gameAccounts.Add(GameAccount.Create(name, registrySdk));
+
+ // sync database
+ using (IServiceScope scope = scopeFactory.CreateScope())
+ {
+ AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
+ appDbContext.GameAccounts.AddAndSave(account);
+ }
}
}
}
}
+
+ ///
+ public bool SetGameAccount(GameAccount account)
+ {
+ return GameAccountRegistryInterop.Set(account);
+ }
+
+ ///
+ public void AttachGameAccountToUid(GameAccount gameAccount, string uid)
+ {
+ using (IServiceScope scope = scopeFactory.CreateScope())
+ {
+ AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
+ IQueryable oldAccounts = appDbContext.GameAccounts.Where(a => a.AttachUid == uid);
+
+ foreach (GameAccount account in oldAccounts)
+ {
+ account.UpdateAttachUid(null);
+ appDbContext.GameAccounts.UpdateAndSave(account);
+ }
+
+ gameAccount.UpdateAttachUid(uid);
+ appDbContext.GameAccounts.UpdateAndSave(gameAccount);
+ }
+ }
+
+ ///
+ public async ValueTask ModifyGameAccountAsync(GameAccount gameAccount)
+ {
+ MainWindow mainWindow = Ioc.Default.GetRequiredService();
+ (bool isOk, string name) = await new GameAccountNameDialog(mainWindow).GetInputNameAsync().ConfigureAwait(false);
+
+ if (isOk)
+ {
+ gameAccount.UpdateName(name);
+
+ // sync database
+ using (IServiceScope scope = scopeFactory.CreateScope())
+ {
+ AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
+ appDbContext.GameAccounts.UpdateAndSave(gameAccount);
+ }
+ }
+ }
+
+ ///
+ public async ValueTask RemoveGameAccountAsync(GameAccount gameAccount)
+ {
+ Must.NotNull(gameAccounts!).Remove(gameAccount);
+
+ await Task.Yield();
+ using (IServiceScope scope = scopeFactory.CreateScope())
+ {
+ AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
+ appDbContext.GameAccounts.RemoveAndSave(gameAccount);
+ }
+ }
}
\ 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 13020091..7f325ed5 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs
@@ -2,6 +2,9 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.Threading;
+using Snap.Hutao.Model.Binding.LaunchGame;
+using Snap.Hutao.Model.Entity;
+using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.Game;
@@ -10,6 +13,26 @@ namespace Snap.Hutao.Service.Game;
///
internal interface IGameService
{
+ ///
+ /// 将账号绑定到对应的Uid
+ /// 清除老账号的绑定状态
+ ///
+ /// 游戏内账号
+ /// uid
+ void AttachGameAccountToUid(GameAccount gameAccount, string uid);
+
+ ///
+ /// 检测并尝试添加游戏内账户
+ ///
+ /// 任务
+ ValueTask DetectGameAccountAsync();
+
+ ///
+ /// 获取游戏内账号集合
+ ///
+ /// 游戏内账号集合
+ ObservableCollection GetGameAccountCollection();
+
///
/// 异步获取游戏路径
///
@@ -28,6 +51,12 @@ internal interface IGameService
/// 多通道值
MultiChannel GetMultiChannel();
+ ///
+ /// 游戏是否正在运行
+ ///
+ /// 是否正在运行
+ bool IsGameRunning();
+
///
/// 异步启动
///
@@ -35,9 +64,36 @@ internal interface IGameService
/// 任务
ValueTask LaunchAsync(LaunchConfiguration configuration);
+ ///
+ /// 异步修改游戏账号名称
+ ///
+ /// 游戏账号
+ /// 任务
+ ValueTask ModifyGameAccountAsync(GameAccount gameAccount);
+
///
/// 重写游戏路径
///
/// 路径
void OverwriteGamePath(string path);
+
+ ///
+ /// 异步尝试移除账号
+ ///
+ /// 账号
+ /// 任务
+ ValueTask RemoveGameAccountAsync(GameAccount gameAccount);
+
+ ///
+ /// 修改注册表中的账号信息
+ ///
+ /// 账号
+ /// 是否设置成功
+ bool SetGameAccount(GameAccount account);
+
+ ///
+ /// 设置多通道值
+ ///
+ /// 方案
+ void SetMultiChannel(LaunchScheme scheme);
}
\ 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
index 9c62a359..0bf32659 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchConfiguration.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchConfiguration.cs
@@ -6,40 +6,55 @@ namespace Snap.Hutao.Service.Game;
///
/// 启动游戏配置
///
-internal struct LaunchConfiguration
+internal readonly struct LaunchConfiguration
{
///
/// 是否全屏,全屏时无边框设置将被覆盖
///
- public bool IsFullScreen { get; set; }
+ public readonly bool IsFullScreen;
///
/// 是否为无边框窗口
///
- public bool IsBorderless { get; private set; }
-
- ///
- /// 是否启用解锁帧率
- ///
- public bool UnlockFPS { get; private set; }
-
- ///
- /// 目标帧率
- ///
- public int TargetFPS { get; private set; }
+ public readonly bool IsBorderless;
///
/// 窗口宽度
///
- public int ScreenWidth { get; private set; }
+ public readonly int ScreenWidth;
///
/// 窗口高度
///
- public int ScreenHeight { get; private set; }
+ public readonly int ScreenHeight;
///
- /// 显示器编号
+ /// 是否启用解锁帧率
///
- public int Monitor { get; private set; }
+ public readonly bool UnlockFPS;
+
+ ///
+ /// 目标帧率
+ ///
+ public readonly int TargetFPS;
+
+ ///
+ /// 构造一个新的启动配置
+ ///
+ /// 全屏
+ /// 无边框
+ /// 宽度
+ /// 高度
+ /// 解锁帧率
+ /// 目标帧率
+ public LaunchConfiguration(bool isFullScreen, bool isBorderless, int screenWidth, int screenHeight, bool unlockFps, int targetFps)
+ {
+ IsFullScreen = isFullScreen;
+ IsBorderless = isBorderless;
+ ScreenHeight = screenHeight;
+ ScreenWidth = screenWidth;
+ ScreenHeight = screenHeight;
+ UnlockFPS = unlockFps;
+ TargetFPS = targetFps;
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Navigation/NavigationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Navigation/NavigationService.cs
index 576a43c5..864cfecb 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Navigation/NavigationService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Navigation/NavigationService.cs
@@ -181,13 +181,16 @@ internal class NavigationService : INavigationService
///
public void GoBack()
{
- bool canGoBack = Frame?.CanGoBack ?? false;
-
- if (canGoBack)
+ Program.DispatcherQueue!.TryEnqueue(() =>
{
- Frame!.GoBack();
- SyncSelectedNavigationViewItemWith(Frame.Content.GetType());
- }
+ bool canGoBack = Frame?.CanGoBack ?? false;
+
+ if (canGoBack)
+ {
+ Frame!.GoBack();
+ SyncSelectedNavigationViewItemWith(Frame.Content.GetType());
+ }
+ });
}
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
index ac245f89..c6b52f4f 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
@@ -95,7 +95,6 @@ internal class UserService : IUserService
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
-
appDbContext.Users.RemoveAndSave(user.Entity);
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
index 05b1ee58..55ae9b44 100644
--- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
+++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
@@ -60,7 +60,7 @@
-
+
@@ -150,6 +150,11 @@
+
+
+ MSBuild:Compile
+
+
MSBuild:Compile
@@ -190,11 +195,6 @@
MSBuild:Compile
-
-
- MSBuild:Compile
-
-
MSBuild:Compile
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/GameAccountNameDialog.xaml b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GameAccountNameDialog.xaml
new file mode 100644
index 00000000..206bb031
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GameAccountNameDialog.xaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/GameAccountNameDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GameAccountNameDialog.xaml.cs
new file mode 100644
index 00000000..81c103a7
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GameAccountNameDialog.xaml.cs
@@ -0,0 +1,35 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Snap.Hutao.Core.Threading;
+
+namespace Snap.Hutao.View.Dialog;
+
+///
+/// 游戏账号命名对话框
+///
+public sealed partial class GameAccountNameDialog : ContentDialog
+{
+ ///
+ /// 构造一个新的游戏账号命名对话框
+ ///
+ /// 窗体
+ public GameAccountNameDialog(Window window)
+ {
+ InitializeComponent();
+ XamlRoot = window.Content.XamlRoot;
+ }
+
+ ///
+ /// 获取输入的Cookie
+ ///
+ /// 输入的结果
+ public async Task> GetInputNameAsync()
+ {
+ ContentDialogResult result = await ShowAsync();
+ string text = InputText.Text;
+ return new(result == ContentDialogResult.Primary && (!string.IsNullOrEmpty(text)), text);
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserAutoCookieDialog.xaml b/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserAutoCookieDialog.xaml
deleted file mode 100644
index d27860cc..00000000
--- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserAutoCookieDialog.xaml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
- 1600
- 200
- 1200
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserAutoCookieDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserAutoCookieDialog.xaml.cs
deleted file mode 100644
index 17f6434c..00000000
--- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserAutoCookieDialog.xaml.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.Web.WebView2.Core;
-using Snap.Hutao.Core.Threading;
-using Snap.Hutao.Web.Hoyolab;
-
-namespace Snap.Hutao.View.Dialog;
-
-///
-/// 用户自动Cookie对话框
-///
-public sealed partial class UserAutoCookieDialog : ContentDialog
-{
- private Cookie? cookie;
-
- ///
- /// 构造一个新的用户自动Cookie对话框
- ///
- /// 依赖窗口
- public UserAutoCookieDialog(Window window)
- {
- InitializeComponent();
- XamlRoot = window.Content.XamlRoot;
- }
-
- ///
- /// 获取输入的Cookie
- ///
- /// 输入的结果
- public async Task> GetInputCookieAsync()
- {
- ContentDialogResult result = await ShowAsync();
- return new(result == ContentDialogResult.Primary && cookie != null, cookie!);
- }
-
- [SuppressMessage("", "VSTHRD100")]
- private async void OnRootLoaded(object sender, RoutedEventArgs e)
- {
- await WebView.EnsureCoreWebView2Async();
- WebView.CoreWebView2.SourceChanged += OnCoreWebView2SourceChanged;
-
- CoreWebView2CookieManager manager = WebView.CoreWebView2.CookieManager;
- IReadOnlyList cookies = await manager.GetCookiesAsync("https://user.mihoyo.com");
- foreach (CoreWebView2Cookie item in cookies)
- {
- manager.DeleteCookie(item);
- }
-
- WebView.CoreWebView2.Navigate("https://user.mihoyo.com/#/login/password");
- }
-
- [SuppressMessage("", "VSTHRD100")]
- private async void OnCoreWebView2SourceChanged(CoreWebView2 sender, CoreWebView2SourceChangedEventArgs args)
- {
- if (sender != null)
- {
- if (sender.Source.ToString() == "https://user.mihoyo.com/#/account/home")
- {
- CoreWebView2CookieManager manager = WebView.CoreWebView2.CookieManager;
- IReadOnlyList cookies = await manager.GetCookiesAsync("https://user.mihoyo.com");
- cookie = Cookie.FromCoreWebView2Cookies(cookies);
- WebView.CoreWebView2.SourceChanged -= OnCoreWebView2SourceChanged;
- }
- }
- }
-}
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserDialog.xaml b/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserDialog.xaml
index 1f751da7..237ff4d5 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserDialog.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/UserDialog.xaml
@@ -7,7 +7,7 @@
xmlns:settings="using:SettingsUI.Controls"
mc:Ignorable="d"
IsPrimaryButtonEnabled="False"
- Title="设置米游社Cookie"
+ Title="设置 Cookie"
DefaultButton="Primary"
PrimaryButtonText="请输入Cookie"
CloseButtonText="取消"
@@ -20,17 +20,19 @@
TextChanged="InputTextChanged"
PlaceholderText="在此处输入"
VerticalAlignment="Top"/>
+
+
+ NavigateUri="https://hut.ao/features/mhy-account-switch.html#%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96-cookie"/>
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml
index de97920b..4d654179 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml
@@ -1,19 +1,32 @@
-
+
+
+
+
+
+
+
@@ -34,10 +47,14 @@
Header="服务器"
Description="切换游戏服务器,B服用户需要自备额外的 PCGameSDK.dll 文件">
-
+
-
+
@@ -45,7 +62,6 @@
-
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Visible
+
+
+
+
+
+
+
+
+
+ Collapsed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -75,8 +175,9 @@
Description="覆盖默认的全屏状态">
+ IsOn="{Binding IsFullScreen,Mode=TwoWay}"
+ Style="{StaticResource ToggleSwitchSettingStyle}"
+ Width="120"/>
+ IsOn="{Binding IsBorderless,Mode=TwoWay}"
+ Style="{StaticResource ToggleSwitchSettingStyle}"
+ Width="120"/>
@@ -97,7 +199,8 @@
Description="覆盖默认屏幕宽度">
+ Value="{Binding ScreenWidth,Mode=TwoWay}"
+ Width="160"/>
+ Value="{Binding ScreenHeight,Mode=TwoWay}"
+ Width="160"/>
-
+
+ Description="Requires administrator privilege. Otherwise the option will be disabled.">
+ IsOn="{Binding UnlockFps,Mode=TwoWay}"
+ OnContent="Enable"
+ OffContent="Disable"
+ Style="{StaticResource ToggleSwitchSettingStyle}"
+ Width="120"/>
+ Description="{Binding TargetFps}">
@@ -143,6 +249,7 @@
VerticalAlignment="Bottom"
Background="{StaticResource SystemControlAcrylicElementMediumHighBrush}">
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoBBSPage.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoBBSPage.xaml.cs
index 6f8d6538..9e47675a 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoBBSPage.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoBBSPage.xaml.cs
@@ -63,7 +63,7 @@ public sealed partial class LoginMihoyoBBSPage : Microsoft.UI.Xaml.Controls.Page
infoBarService.Information($"此 Cookie 不完整,操作失败");
break;
case UserOptionResult.Invalid:
- infoBarService.Information($"此 Cookie 无法,操作失败");
+ infoBarService.Information($"此 Cookie 无效,操作失败");
break;
case UserOptionResult.Updated:
infoBarService.Success($"用户 [{nickname}] 更新成功");
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml.cs
index 8b089698..523a1d1f 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml.cs
@@ -28,7 +28,6 @@ public sealed partial class LoginMihoyoUserPage : Microsoft.UI.Xaml.Controls.Pag
private async void OnRootLoaded(object sender, RoutedEventArgs e)
{
await WebView.EnsureCoreWebView2Async();
- WebView.CoreWebView2.SourceChanged += OnCoreWebView2SourceChanged;
CoreWebView2CookieManager manager = WebView.CoreWebView2.CookieManager;
IReadOnlyList cookies = await manager.GetCookiesAsync("https://user.mihoyo.com");
@@ -40,23 +39,10 @@ public sealed partial class LoginMihoyoUserPage : Microsoft.UI.Xaml.Controls.Pag
WebView.CoreWebView2.Navigate("https://user.mihoyo.com/#/login/password");
}
- [SuppressMessage("", "VSTHRD100")]
- private async void OnCoreWebView2SourceChanged(CoreWebView2 sender, CoreWebView2SourceChangedEventArgs args)
- {
- if (sender != null)
- {
- if (sender.Source.ToString() == "https://user.mihoyo.com/#/account/home")
- {
- await HandleCurrentCookieAsync().ConfigureAwait(false);
- }
- }
- }
-
private async Task HandleCurrentCookieAsync()
{
CoreWebView2CookieManager manager = WebView.CoreWebView2.CookieManager;
IReadOnlyList cookies = await manager.GetCookiesAsync("https://user.mihoyo.com");
- WebView.CoreWebView2.SourceChanged -= OnCoreWebView2SourceChanged;
Cookie cookie = Cookie.FromCoreWebView2Cookies(cookies);
IUserService userService = Ioc.Default.GetRequiredService();
diff --git a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
index 60c309c5..be84a280 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
@@ -22,7 +22,7 @@
Background="Transparent"
BorderBrush="{x:Null}"
Grid.Column="2"
- Margin="4">
+ Margin="4,4,4,6">
-
-
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs
index 53309b2a..88518429 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs
@@ -2,11 +2,19 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Microsoft.EntityFrameworkCore;
+using Snap.Hutao.Context.Database;
using Snap.Hutao.Control;
+using Snap.Hutao.Core.Database;
+using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Model.Binding.LaunchGame;
using Snap.Hutao.Model.Entity;
+using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Game;
+using Snap.Hutao.Service.User;
+using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using System.Collections.ObjectModel;
namespace Snap.Hutao.ViewModel;
@@ -17,7 +25,11 @@ namespace Snap.Hutao.ViewModel;
[Injection(InjectAs.Scoped)]
internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
{
+ private static readonly string TrueString = true.ToString();
+ private static readonly string FalseString = false.ToString();
+
private readonly IGameService gameService;
+ private readonly AppDbContext appDbContext;
private readonly List knownSchemes = new()
{
@@ -41,16 +53,19 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
/// 构造一个新的启动游戏视图模型
///
/// 游戏服务
+ /// 数据库上下文
/// 异步命令工厂
- public LaunchGameViewModel(IGameService gameService, IAsyncRelayCommandFactory asyncRelayCommandFactory)
+ public LaunchGameViewModel(IGameService gameService, AppDbContext appDbContext, IAsyncRelayCommandFactory asyncRelayCommandFactory)
{
this.gameService = gameService;
+ this.appDbContext = appDbContext;
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
LaunchCommand = asyncRelayCommandFactory.Create(LaunchAsync);
DetectGameAccountCommand = asyncRelayCommandFactory.Create(DetectGameAccountAsync);
- ModifyGameAccountCommand = asyncRelayCommandFactory.Create(ModifyGameAccountAsync);
- RemoveGameAccountCommand = asyncRelayCommandFactory.Create(RemoveGameAccountAsync);
+ ModifyGameAccountCommand = asyncRelayCommandFactory.Create(ModifyGameAccountAsync);
+ RemoveGameAccountCommand = asyncRelayCommandFactory.Create(RemoveGameAccountAsync);
+ AttachGameAccountCommand = new RelayCommand(AttachGameAccountToCurrentUserGameRole);
}
///
@@ -106,6 +121,12 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
///
public int TargetFps { get => targetFps; set => SetProperty(ref targetFps, value); }
+ ///
+ /// 是否提权
+ ///
+ [SuppressMessage("Performance", "CA1822")]
+ public bool IsElevated { get => Activation.GetElevated(); }
+
///
/// 打开界面命令
///
@@ -131,6 +152,11 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
///
public ICommand RemoveGameAccountCommand { get; }
+ ///
+ /// 绑定到Uid命令
+ ///
+ public ICommand AttachGameAccountCommand { get; }
+
private async Task OpenUIAsync()
{
(bool isOk, string gamePath) = await gameService.GetGamePathAsync().ConfigureAwait(false);
@@ -139,26 +165,110 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
{
MultiChannel multi = gameService.GetMultiChannel();
SelectedScheme = KnownSchemes.FirstOrDefault(s => s.Channel == multi.Channel && s.SubChannel == multi.SubChannel);
+ GameAccounts = gameService.GetGameAccountCollection();
+
+ // Sync from Settings
+ RetiveSetting();
}
}
+ private void RetiveSetting()
+ {
+ DbSet settings = appDbContext.Settings;
+
+ isFullScreen = settings.SingleOrAdd(SettingEntry.LaunchIsFullScreen, TrueString).GetBoolean();
+ OnPropertyChanged(nameof(IsFullScreen));
+
+ isBorderless = settings.SingleOrAdd(SettingEntry.LaunchIsBorderless, FalseString).GetBoolean();
+ OnPropertyChanged(nameof(IsBorderless));
+
+ screenWidth = settings.SingleOrAdd(SettingEntry.LaunchScreenWidth, "1920").GetInt32();
+ OnPropertyChanged(nameof(ScreenWidth));
+
+ screenHeight = settings.SingleOrAdd(SettingEntry.LaunchScreenHeight, "1080").GetInt32();
+ OnPropertyChanged(nameof(ScreenHeight));
+
+ unlockFps = settings.SingleOrAdd(SettingEntry.LaunchUnlockFps, FalseString).GetBoolean();
+ OnPropertyChanged(nameof(UnlockFps));
+
+ targetFps = settings.SingleOrAdd(SettingEntry.LaunchTargetFps, "60").GetInt32();
+ OnPropertyChanged(nameof(TargetFps));
+ }
+
+ private void SaveSetting()
+ {
+ DbSet settings = appDbContext.Settings;
+ settings.SingleOrAdd(SettingEntry.LaunchIsFullScreen, TrueString).SetBoolean(IsFullScreen);
+ settings.SingleOrAdd(SettingEntry.LaunchIsBorderless, FalseString).SetBoolean(IsBorderless);
+ settings.SingleOrAdd(SettingEntry.LaunchScreenWidth, "1920").SetInt32(ScreenWidth);
+ settings.SingleOrAdd(SettingEntry.LaunchScreenHeight, "1080").SetInt32(ScreenHeight);
+ settings.SingleOrAdd(SettingEntry.LaunchUnlockFps, FalseString).SetBoolean(UnlockFps);
+ settings.SingleOrAdd(SettingEntry.LaunchTargetFps, "60").SetInt32(TargetFps);
+ appDbContext.SaveChanges();
+ }
+
private async Task LaunchAsync()
{
+ if (gameService.IsGameRunning())
+ {
+ return;
+ }
+ if (SelectedScheme != null)
+ {
+ gameService.SetMultiChannel(SelectedScheme);
+ }
+
+ if (SelectedGameAccount != null)
+ {
+ if (!gameService.SetGameAccount(SelectedGameAccount))
+ {
+ Ioc.Default.GetRequiredService().Warning("切换账号失败");
+ }
+ }
+
+ SaveSetting();
+
+ LaunchConfiguration configuration = new(IsFullScreen, IsBorderless, ScreenWidth, ScreenHeight, IsElevated && UnlockFps, TargetFps);
+ await gameService.LaunchAsync(configuration).ConfigureAwait(false);
}
private async Task DetectGameAccountAsync()
{
-
+ await gameService.DetectGameAccountAsync().ConfigureAwait(false);
}
- private async Task ModifyGameAccountAsync()
+ private void AttachGameAccountToCurrentUserGameRole(GameAccount? gameAccount)
{
+ if (gameAccount != null)
+ {
+ IUserService userService = Ioc.Default.GetRequiredService();
+ IInfoBarService infoBarService = Ioc.Default.GetRequiredService();
+ if (userService.Current?.SelectedUserGameRole is UserGameRole role)
+ {
+ gameService.AttachGameAccountToUid(gameAccount, role.GameUid);
+ }
+ else
+ {
+ infoBarService.Warning("当前未选择角色");
+ }
+ }
}
- private async Task RemoveGameAccountAsync()
+ private async Task ModifyGameAccountAsync(GameAccount? gameAccount)
{
+ if (gameAccount != null)
+ {
+ await gameService.ModifyGameAccountAsync(gameAccount).ConfigureAwait(false);
+ }
+ }
+ private async Task RemoveGameAccountAsync(GameAccount? gameAccount)
+ {
+ if (gameAccount != null)
+ {
+ await gameService.RemoveGameAccountAsync(gameAccount).ConfigureAwait(false);
+ }
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
index 171dc8a9..7e77f7e4 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
@@ -126,7 +126,7 @@ internal class UserViewModel : ObservableObject
infoBarService.Information($"此 Cookie 不完整,操作失败");
break;
case UserOptionResult.Invalid:
- infoBarService.Information($"此 Cookie 无法,操作失败");
+ infoBarService.Information($"此 Cookie 无效,操作失败");
break;
case UserOptionResult.Updated:
infoBarService.Success($"用户 [{uid}] 更新成功");
@@ -162,7 +162,7 @@ internal class UserViewModel : ObservableObject
Verify.Operation(user != null, "待复制 Cookie 的用户不应为 null");
try
{
- Clipboard.SetText(user.Cookie.ToString());
+ Clipboard.SetText(user.Cookie!.ToString());
infoBarService.Success($"{user.UserInfo!.Nickname} 的 Cookie 复制成功");
}
catch (Exception e)
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs
index 7e558fe3..a06b6775 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs
@@ -68,7 +68,7 @@ internal static class HttpClientExtensions
/// 客户端
internal static HttpClient SetUser(this HttpClient httpClient, User user)
{
- httpClient.DefaultRequestHeaders.Set("Cookie", user.Cookie.ToString());
+ httpClient.DefaultRequestHeaders.Set("Cookie", user.Cookie!.ToString());
return httpClient;
}