mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
impl #1071
This commit is contained in:
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Core.Threading;
|
||||
/// </summary>
|
||||
internal interface ITaskContext
|
||||
{
|
||||
IProgress<T> CreateProgressForMainThread<T>(Action<T> handler);
|
||||
SynchronizationContext GetSynchronizationContext();
|
||||
|
||||
/// <summary>
|
||||
/// 在主线程上同步等待执行操作
|
||||
|
||||
@@ -42,8 +42,8 @@ internal sealed class TaskContext : ITaskContext
|
||||
dispatcherQueue.Invoke(action);
|
||||
}
|
||||
|
||||
public IProgress<T> CreateProgressForMainThread<T>(Action<T> handler)
|
||||
public SynchronizationContext GetSynchronizationContext()
|
||||
{
|
||||
return new DispatcherQueueProgress<T>(handler, synchronizationContext);
|
||||
return synchronizationContext;
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,8 @@
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
|
||||
namespace Snap.Hutao.Factory;
|
||||
namespace Snap.Hutao.Factory.ContentDialog;
|
||||
|
||||
/// <inheritdoc cref="IContentDialogFactory"/>
|
||||
[HighQuality]
|
||||
@@ -13,15 +12,15 @@ namespace Snap.Hutao.Factory;
|
||||
[Injection(InjectAs.Singleton, typeof(IContentDialogFactory))]
|
||||
internal sealed partial class ContentDialogFactory : IContentDialogFactory
|
||||
{
|
||||
private readonly ICurrentWindowReference currentWindowReference;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly ICurrentWindowReference currentWindowReference;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<ContentDialogResult> CreateForConfirmAsync(string title, string content)
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ContentDialog dialog = new()
|
||||
Microsoft.UI.Xaml.Controls.ContentDialog dialog = new()
|
||||
{
|
||||
XamlRoot = currentWindowReference.GetXamlRoot(),
|
||||
Title = title,
|
||||
@@ -37,7 +36,7 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
|
||||
public async ValueTask<ContentDialogResult> CreateForConfirmCancelAsync(string title, string content, ContentDialogButton defaultButton = ContentDialogButton.Close)
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ContentDialog dialog = new()
|
||||
Microsoft.UI.Xaml.Controls.ContentDialog dialog = new()
|
||||
{
|
||||
XamlRoot = currentWindowReference.GetXamlRoot(),
|
||||
Title = title,
|
||||
@@ -51,10 +50,10 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<ContentDialog> CreateForIndeterminateProgressAsync(string title)
|
||||
public async ValueTask<Microsoft.UI.Xaml.Controls.ContentDialog> CreateForIndeterminateProgressAsync(string title)
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ContentDialog dialog = new()
|
||||
Microsoft.UI.Xaml.Controls.ContentDialog dialog = new()
|
||||
{
|
||||
XamlRoot = currentWindowReference.GetXamlRoot(),
|
||||
Title = title,
|
||||
@@ -65,7 +64,7 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
|
||||
}
|
||||
|
||||
public async ValueTask<TContentDialog> CreateInstanceAsync<TContentDialog>(params object[] parameters)
|
||||
where TContentDialog : ContentDialog
|
||||
where TContentDialog : Microsoft.UI.Xaml.Controls.ContentDialog
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
TContentDialog contentDialog = serviceProvider.CreateInstance<TContentDialog>(parameters);
|
||||
@@ -74,7 +73,7 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
|
||||
}
|
||||
|
||||
public TContentDialog CreateInstance<TContentDialog>(params object[] parameters)
|
||||
where TContentDialog : ContentDialog
|
||||
where TContentDialog : Microsoft.UI.Xaml.Controls.ContentDialog
|
||||
{
|
||||
TContentDialog contentDialog = serviceProvider.CreateInstance<TContentDialog>(parameters);
|
||||
contentDialog.XamlRoot = currentWindowReference.GetXamlRoot();
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Snap.Hutao.Factory.Abstraction;
|
||||
namespace Snap.Hutao.Factory.ContentDialog;
|
||||
|
||||
/// <summary>
|
||||
/// 内容对话框工厂
|
||||
@@ -33,11 +33,11 @@ internal interface IContentDialogFactory
|
||||
/// </summary>
|
||||
/// <param name="title">标题</param>
|
||||
/// <returns>内容对话框</returns>
|
||||
ValueTask<ContentDialog> CreateForIndeterminateProgressAsync(string title);
|
||||
ValueTask<Microsoft.UI.Xaml.Controls.ContentDialog> CreateForIndeterminateProgressAsync(string title);
|
||||
|
||||
TContentDialog CreateInstance<TContentDialog>(params object[] parameters)
|
||||
where TContentDialog : ContentDialog;
|
||||
where TContentDialog : Microsoft.UI.Xaml.Controls.ContentDialog;
|
||||
|
||||
ValueTask<TContentDialog> CreateInstanceAsync<TContentDialog>(params object[] parameters)
|
||||
where TContentDialog : ContentDialog;
|
||||
where TContentDialog : Microsoft.UI.Xaml.Controls.ContentDialog;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Windows.Storage.Pickers;
|
||||
|
||||
namespace Snap.Hutao.Factory.Abstraction;
|
||||
namespace Snap.Hutao.Factory.Picker;
|
||||
|
||||
/// <summary>
|
||||
/// 文件选择器工厂
|
||||
@@ -4,11 +4,10 @@
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Core.Windowing;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Windows.Storage.Pickers;
|
||||
using WinRT.Interop;
|
||||
|
||||
namespace Snap.Hutao.Factory;
|
||||
namespace Snap.Hutao.Factory.Picker;
|
||||
|
||||
/// <inheritdoc cref="IPickerFactory"/>
|
||||
[HighQuality]
|
||||
@@ -18,7 +17,7 @@ internal sealed partial class PickerFactory : IPickerFactory
|
||||
{
|
||||
private const string AnyType = "*";
|
||||
|
||||
private readonly ICurrentWindowReference currentWindow;
|
||||
private readonly ICurrentWindowReference currentWindowReference;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FileOpenPicker GetFileOpenPicker(PickerLocationId location, string commitButton, params string[] fileTypes)
|
||||
@@ -80,10 +79,11 @@ internal sealed partial class PickerFactory : IPickerFactory
|
||||
{
|
||||
// Create a folder picker.
|
||||
T picker = new();
|
||||
if (currentWindow.Window is IWindowOptionsSource optionsSource)
|
||||
{
|
||||
InitializeWithWindow.Initialize(picker, optionsSource.WindowOptions.Hwnd);
|
||||
}
|
||||
nint hwnd = currentWindowReference.Window is IWindowOptionsSource optionsSource
|
||||
? (nint)optionsSource.WindowOptions.Hwnd
|
||||
: WindowNative.GetWindowHandle(currentWindowReference.Window);
|
||||
|
||||
InitializeWithWindow.Initialize(picker, hwnd);
|
||||
|
||||
return picker;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Snap.Hutao.Factory.Progress;
|
||||
|
||||
internal interface IProgressFactory
|
||||
{
|
||||
IProgress<T> CreateForMainThread<T>(Action<T> handler);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Snap.Hutao.Factory.Progress;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Transient, typeof(IProgressFactory))]
|
||||
internal sealed partial class ProgressFactory : IProgressFactory
|
||||
{
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public IProgress<T> CreateForMainThread<T>(Action<T> handler)
|
||||
{
|
||||
return new DispatcherQueueProgress<T>(handler, taskContext.GetSynchronizationContext());
|
||||
}
|
||||
}
|
||||
@@ -104,6 +104,8 @@ internal sealed partial class SettingEntry
|
||||
|
||||
public const string LaunchIsMonitorEnabled = "Launch.IsMonitorEnabled";
|
||||
|
||||
public const string LaunchUseStarwardPlayTimeStatistics = "Launch.UseStarwardPlayTimeStatistics";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 多倍启动
|
||||
/// </summary>
|
||||
|
||||
@@ -2021,6 +2021,9 @@
|
||||
<data name="ViewPageLaunchGameFileHeader" xml:space="preserve">
|
||||
<value>文件</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameInterProcessHeader" xml:space="preserve">
|
||||
<value>进程间</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameMonitorsDescription" xml:space="preserve">
|
||||
<value>在指定的显示器上运行</value>
|
||||
</data>
|
||||
@@ -2036,6 +2039,12 @@
|
||||
<data name="ViewPageLaunchGameOptionsHeader" xml:space="preserve">
|
||||
<value>游戏选项</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGamePlayTimeDescription" xml:space="preserve">
|
||||
<value>在游戏启动后尝试启动并使用 Starward 进行游戏时长统计</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGamePlayTimeHeader" xml:space="preserve">
|
||||
<value>时长统计</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameProcessHeader" xml:space="preserve">
|
||||
<value>进程</value>
|
||||
</data>
|
||||
|
||||
@@ -106,6 +106,8 @@ internal sealed partial class AppOptions : DbStoreOptions
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用高级功能
|
||||
/// DO NOT MOVE TO OTHER CLASS
|
||||
/// We are binding this property in SettingPage
|
||||
/// </summary>
|
||||
public bool IsAdvancedLaunchOptionsEnabled
|
||||
{
|
||||
|
||||
42
src/Snap.Hutao/Snap.Hutao/Service/AppOptionsExtension.cs
Normal file
42
src/Snap.Hutao/Snap.Hutao/Service/AppOptionsExtension.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Service;
|
||||
|
||||
internal static class AppOptionsExtension
|
||||
{
|
||||
public static bool TryGetGameFolderAndFileName(this AppOptions appOptions, [NotNullWhen(true)] out string? gameFolder, [NotNullWhen(true)] out string? gameFileName)
|
||||
{
|
||||
string gamePath = appOptions.GamePath;
|
||||
|
||||
gameFolder = Path.GetDirectoryName(gamePath);
|
||||
if (string.IsNullOrEmpty(gameFolder))
|
||||
{
|
||||
gameFileName = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
gameFileName = Path.GetFileName(gamePath);
|
||||
if (string.IsNullOrEmpty(gameFileName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryGetGameFileName(this AppOptions appOptions, [NotNullWhen(true)] out string? gameFileName)
|
||||
{
|
||||
string gamePath = appOptions.GamePath;
|
||||
|
||||
gameFileName = Path.GetFileName(gamePath);
|
||||
if (string.IsNullOrEmpty(gameFileName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
using Snap.Hutao.Web.Request.QueryString;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -45,8 +45,6 @@ internal sealed partial class GameAccountService : IGameAccountService
|
||||
|
||||
if (account is null)
|
||||
{
|
||||
// ContentDialog must be created by main thread.
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
LaunchGameAccountNameDialog dialog = await contentDialogFactory.CreateInstanceAsync<LaunchGameAccountNameDialog>().ConfigureAwait(false);
|
||||
(bool isOk, string name) = await dialog.GetInputNameAsync().ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ internal static class RegistryInterop
|
||||
|
||||
try
|
||||
{
|
||||
Process.Start(startInfo)?.WaitForExit();
|
||||
System.Diagnostics.Process.Start(startInfo)?.WaitForExit();
|
||||
}
|
||||
catch (Win32Exception ex)
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Game.Account;
|
||||
using Snap.Hutao.Service.Game.Configuration;
|
||||
using Snap.Hutao.Service.Game.Package;
|
||||
using Snap.Hutao.Service.Game.Process;
|
||||
using Snap.Hutao.Service.Game.Scheme;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ internal sealed class LaunchOptions : DbStoreOptions
|
||||
private NameValue<int>? monitor;
|
||||
private bool? isMonitorEnabled;
|
||||
private AspectRatio? selectedAspectRatio;
|
||||
private bool? useStarwardPlayTimeStatistics;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的启动游戏选项
|
||||
@@ -183,6 +184,12 @@ internal sealed class LaunchOptions : DbStoreOptions
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseStarwardPlayTimeStatistics
|
||||
{
|
||||
get => GetOption(ref useStarwardPlayTimeStatistics, SettingEntry.LaunchUseStarwardPlayTimeStatistics, false);
|
||||
set => SetOption(ref useStarwardPlayTimeStatistics, SettingEntry.LaunchUseStarwardPlayTimeStatistics, value);
|
||||
}
|
||||
|
||||
private static void InitializeMonitors(List<NameValue<int>> monitors)
|
||||
{
|
||||
// This list can't use foreach
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Factory.Picker;
|
||||
using Windows.Storage.Pickers;
|
||||
|
||||
namespace Snap.Hutao.Service.Game.Locator;
|
||||
|
||||
@@ -20,10 +20,10 @@ internal sealed partial class GamePackageService : IGamePackageService
|
||||
|
||||
public async ValueTask<bool> EnsureGameResourceAsync(LaunchScheme launchScheme, IProgress<PackageReplaceStatus> progress)
|
||||
{
|
||||
string gamePath = appOptions.GamePath;
|
||||
string? gameFolder = Path.GetDirectoryName(gamePath);
|
||||
ArgumentException.ThrowIfNullOrEmpty(gameFolder);
|
||||
string gameFileName = Path.GetFileName(gamePath);
|
||||
if (!appOptions.TryGetGameFolderAndFileName(out string? gameFolder, out string? gameFileName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
progress.Report(new(SH.ServiceGameEnsureGameResourceQueryResourceInformation));
|
||||
Response<GameResource> response = await serviceProvider
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Service.Game.Scheme;
|
||||
using Snap.Hutao.Service.Game.Unlocker;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Windows.System;
|
||||
using static Snap.Hutao.Service.Game.GameConstants;
|
||||
|
||||
namespace Snap.Hutao.Service.Game;
|
||||
namespace Snap.Hutao.Service.Game.Process;
|
||||
|
||||
/// <summary>
|
||||
/// 进程互操作
|
||||
@@ -30,8 +31,8 @@ internal sealed partial class GameProcessService : IGameProcessService
|
||||
return false;
|
||||
}
|
||||
|
||||
return Process.GetProcessesByName(YuanShenProcessName).Any()
|
||||
|| Process.GetProcessesByName(GenshinImpactProcessName).Any();
|
||||
return System.Diagnostics.Process.GetProcessesByName(YuanShenProcessName).Any()
|
||||
|| System.Diagnostics.Process.GetProcessesByName(GenshinImpactProcessName).Any();
|
||||
}
|
||||
|
||||
public async ValueTask LaunchAsync(IProgress<LaunchStatus> progress)
|
||||
@@ -45,7 +46,7 @@ internal sealed partial class GameProcessService : IGameProcessService
|
||||
ArgumentException.ThrowIfNullOrEmpty(gamePath);
|
||||
|
||||
progress.Report(new(LaunchPhase.ProcessInitializing, SH.ServiceGameLaunchPhaseProcessInitializing));
|
||||
using (Process game = InitializeGameProcess(launchOptions, gamePath))
|
||||
using (System.Diagnostics.Process game = InitializeGameProcess(gamePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -53,12 +54,18 @@ internal sealed partial class GameProcessService : IGameProcessService
|
||||
game.Start();
|
||||
progress.Report(new(LaunchPhase.ProcessStarted, SH.ServiceGameLaunchPhaseProcessStarted));
|
||||
|
||||
if (launchOptions.UseStarwardPlayTimeStatistics && appOptions.TryGetGameFileName(out string? gameFileName))
|
||||
{
|
||||
bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileName);
|
||||
await Starward.LaunchForPlayTimeStatisticsAsync(isOversea).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (runtimeOptions.IsElevated && appOptions.IsAdvancedLaunchOptionsEnabled && launchOptions.UnlockFps)
|
||||
{
|
||||
progress.Report(new(LaunchPhase.UnlockingFps, SH.ServiceGameLaunchPhaseUnlockingFps));
|
||||
try
|
||||
{
|
||||
await UnlockFpsAsync(serviceProvider, game, progress).ConfigureAwait(false);
|
||||
await UnlockFpsAsync(game, progress).ConfigureAwait(false);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
@@ -85,23 +92,23 @@ internal sealed partial class GameProcessService : IGameProcessService
|
||||
}
|
||||
}
|
||||
|
||||
private static Process InitializeGameProcess(LaunchOptions options, string gamePath)
|
||||
private System.Diagnostics.Process InitializeGameProcess(string gamePath)
|
||||
{
|
||||
string commandLine = string.Empty;
|
||||
|
||||
if (options.IsEnabled)
|
||||
if (launchOptions.IsEnabled)
|
||||
{
|
||||
Must.Argument(!(options.IsBorderless && options.IsExclusive), "无边框与独占全屏选项无法同时生效");
|
||||
Must.Argument(!(launchOptions.IsBorderless && launchOptions.IsExclusive), "无边框与独占全屏选项无法同时生效");
|
||||
|
||||
// https://docs.unity.cn/cn/current/Manual/PlayerCommandLineArguments.html
|
||||
// https://docs.unity3d.com/2017.4/Documentation/Manual/CommandLineArguments.html
|
||||
commandLine = new CommandLineBuilder()
|
||||
.AppendIf("-popupwindow", options.IsBorderless)
|
||||
.AppendIf("-window-mode", options.IsExclusive, "exclusive")
|
||||
.Append("-screen-fullscreen", options.IsFullScreen ? 1 : 0)
|
||||
.AppendIf("-screen-width", options.IsScreenWidthEnabled, options.ScreenWidth)
|
||||
.AppendIf("-screen-height", options.IsScreenHeightEnabled, options.ScreenHeight)
|
||||
.AppendIf("-monitor", options.IsMonitorEnabled, options.Monitor.Value)
|
||||
.AppendIf("-popupwindow", launchOptions.IsBorderless)
|
||||
.AppendIf("-window-mode", launchOptions.IsExclusive, "exclusive")
|
||||
.Append("-screen-fullscreen", launchOptions.IsFullScreen ? 1 : 0)
|
||||
.AppendIf("-screen-width", launchOptions.IsScreenWidthEnabled, launchOptions.ScreenWidth)
|
||||
.AppendIf("-screen-height", launchOptions.IsScreenHeightEnabled, launchOptions.ScreenHeight)
|
||||
.AppendIf("-monitor", launchOptions.IsMonitorEnabled, launchOptions.Monitor.Value)
|
||||
.ToString();
|
||||
}
|
||||
|
||||
@@ -118,7 +125,7 @@ internal sealed partial class GameProcessService : IGameProcessService
|
||||
};
|
||||
}
|
||||
|
||||
private static ValueTask UnlockFpsAsync(IServiceProvider serviceProvider, Process game, IProgress<LaunchStatus> progress, CancellationToken token = default)
|
||||
private ValueTask UnlockFpsAsync(System.Diagnostics.Process game, IProgress<LaunchStatus> progress, CancellationToken token = default)
|
||||
{
|
||||
IGameFpsUnlocker unlocker = serviceProvider.CreateInstance<GameFpsUnlocker>(game);
|
||||
UnlockTimingOptions options = new(100, 20000, 3000);
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.Game;
|
||||
namespace Snap.Hutao.Service.Game.Process;
|
||||
|
||||
internal interface IGameProcessService
|
||||
{
|
||||
19
src/Snap.Hutao/Snap.Hutao/Service/Game/Process/Starward.cs
Normal file
19
src/Snap.Hutao/Snap.Hutao/Service/Game/Process/Starward.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Windows.System;
|
||||
|
||||
namespace Snap.Hutao.Service.Game.Process;
|
||||
|
||||
internal static class Starward
|
||||
{
|
||||
public static async ValueTask LaunchForPlayTimeStatisticsAsync(bool isOversea)
|
||||
{
|
||||
string gameBiz = isOversea ? "hk4e_global" : "hk4e_cn";
|
||||
Uri starwardPlayTimeUri = $"starward://playtime/{gameBiz}".ToUri();
|
||||
if (await Launcher.QueryUriSupportAsync(starwardPlayTimeUri, LaunchQuerySupportType.Uri) is LaunchQuerySupportStatus.Available)
|
||||
{
|
||||
await Launcher.LaunchUriAsync(starwardPlayTimeUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,16 @@ internal class LaunchScheme
|
||||
|
||||
public bool IsNotCompatOnly { get; private protected set; } = true;
|
||||
|
||||
public static bool ExecutableIsOversea(string gameFileName)
|
||||
{
|
||||
return gameFileName switch
|
||||
{
|
||||
GameConstants.GenshinImpactFileName => true,
|
||||
GameConstants.YuanShenFileName => false,
|
||||
_ => throw Requires.Fail("无效的游戏可执行文件名称:{0}", gameFileName),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多通道相等
|
||||
/// </summary>
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Snap.Hutao.Service.Game.Unlocker;
|
||||
[HighQuality]
|
||||
internal sealed class GameFpsUnlocker : IGameFpsUnlocker
|
||||
{
|
||||
private readonly Process gameProcess;
|
||||
private readonly System.Diagnostics.Process gameProcess;
|
||||
private readonly LaunchOptions launchOptions;
|
||||
private readonly UnlockerStatus status = new();
|
||||
|
||||
@@ -33,7 +33,7 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
/// <param name="gameProcess">游戏进程</param>
|
||||
public GameFpsUnlocker(IServiceProvider serviceProvider, Process gameProcess)
|
||||
public GameFpsUnlocker(IServiceProvider serviceProvider, System.Diagnostics.Process gameProcess)
|
||||
{
|
||||
launchOptions = serviceProvider.GetRequiredService<LaunchOptions>();
|
||||
this.gameProcess = gameProcess;
|
||||
@@ -57,7 +57,7 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
|
||||
await LoopAdjustFpsAsync(options.AdjustFpsDelay, progress, token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static unsafe bool UnsafeReadModulesMemory(Process process, in GameModule moduleEntryInfo, out VirtualMemory memory)
|
||||
private static unsafe bool UnsafeReadModulesMemory(System.Diagnostics.Process process, in GameModule moduleEntryInfo, out VirtualMemory memory)
|
||||
{
|
||||
ref readonly Module unityPlayer = ref moduleEntryInfo.UnityPlayer;
|
||||
ref readonly Module userAssembly = ref moduleEntryInfo.UserAssembly;
|
||||
@@ -68,7 +68,7 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
|
||||
&& ReadProcessMemory((HANDLE)process.Handle, (void*)userAssembly.Address, lpBuffer + unityPlayer.Size, userAssembly.Size);
|
||||
}
|
||||
|
||||
private static unsafe bool UnsafeReadProcessMemory(Process process, nuint baseAddress, out nuint value)
|
||||
private static unsafe bool UnsafeReadProcessMemory(System.Diagnostics.Process process, nuint baseAddress, out nuint value)
|
||||
{
|
||||
ulong temp = 0;
|
||||
bool result = ReadProcessMemory((HANDLE)process.Handle, (void*)baseAddress, (byte*)&temp, 8);
|
||||
@@ -78,7 +78,7 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
|
||||
return result;
|
||||
}
|
||||
|
||||
private static unsafe bool UnsafeWriteProcessMemory(Process process, nuint baseAddress, int value)
|
||||
private static unsafe bool UnsafeWriteProcessMemory(System.Diagnostics.Process process, nuint baseAddress, int value)
|
||||
{
|
||||
return WriteProcessMemory((HANDLE)process.Handle, (void*)baseAddress, &value, sizeof(int));
|
||||
}
|
||||
|
||||
@@ -29,11 +29,11 @@ internal sealed partial class HutaoAsAService : IHutaoAsAService
|
||||
|
||||
ApplicationDataCompositeValue excludedIds = LocalSetting.Get(SettingKeys.ExcludedAnnouncementIds, new ApplicationDataCompositeValue());
|
||||
List<long> data = excludedIds.Select(kvp => long.Parse(kvp.Key, CultureInfo.InvariantCulture)).ToList();
|
||||
Response<List<HutaoAnnouncement>> respose = await hutaoAsServiceClient.GetAnnouncementListAsync(data, token).ConfigureAwait(false);
|
||||
Response<List<HutaoAnnouncement>> response = await hutaoAsServiceClient.GetAnnouncementListAsync(data, token).ConfigureAwait(false);
|
||||
|
||||
if (respose.IsOk())
|
||||
if (response.IsOk())
|
||||
{
|
||||
List<HutaoAnnouncement> list = respose.Data;
|
||||
List<HutaoAnnouncement> list = response.Data;
|
||||
list.ForEach(item => item.DismissCommand = dismissCommand);
|
||||
announcements = list.ToObservableCollection();
|
||||
}
|
||||
|
||||
@@ -277,7 +277,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.6.11" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.756" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231008000" />
|
||||
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.507">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
CompactPaneLength="48"
|
||||
IsBackEnabled="{x:Bind ContentFrame.CanGoBack, Mode=OneWay}"
|
||||
IsPaneOpen="True"
|
||||
OpenPaneLength="188"
|
||||
OpenPaneLength="192"
|
||||
PaneDisplayMode="Left"
|
||||
UseLayoutRounding="False">
|
||||
<NavigationView.MenuItems>
|
||||
|
||||
@@ -153,6 +153,7 @@
|
||||
Message="{shcm:ResourceString Name=ViewPageLaunchGameConfigurationSaveHint}"
|
||||
Severity="Informational"/>
|
||||
|
||||
<!-- 文件 -->
|
||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameFileHeader}"/>
|
||||
<cwc:SettingsCard
|
||||
Header="{shcm:ResourceString Name=ViewPageLaunchGameSwitchSchemeHeader}"
|
||||
@@ -179,6 +180,7 @@
|
||||
</StackPanel>
|
||||
</cwc:SettingsCard>
|
||||
|
||||
<!-- 注册表 -->
|
||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameRegistryHeader}"/>
|
||||
<cwc:SettingsCard
|
||||
ActionIconToolTip="{shcm:ResourceString Name=ViewPageLaunchGameSwitchAccountDetectAction}"
|
||||
@@ -193,6 +195,8 @@
|
||||
ItemsSource="{Binding GameAccounts}"
|
||||
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}"/>
|
||||
</Border>
|
||||
|
||||
<!-- 进程 -->
|
||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameProcessHeader}"/>
|
||||
<cwc:SettingsExpander
|
||||
Description="{shcm:ResourceString Name=ViewPageLaunchGameArgumentsDescription}"
|
||||
@@ -293,6 +297,15 @@
|
||||
OnContent="{shcm:ResourceString Name=ViewPageLaunchGameUnlockFpsOn}"/>
|
||||
</StackPanel>
|
||||
</cwc:SettingsCard>
|
||||
|
||||
<!-- 进程间 -->
|
||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameInterProcessHeader}"/>
|
||||
<cwc:SettingsCard
|
||||
Description="{shcm:ResourceString Name=ViewPageLaunchGamePlayTimeDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageLaunchGamePlayTimeHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||
<ToggleSwitch Width="120" IsOn="{Binding Options.UseStarwardPlayTimeStatistics, Mode=TwoWay}"/>
|
||||
</cwc:SettingsCard>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
@@ -5,7 +5,8 @@ using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Core.IO.DataTransfer;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Factory.Picker;
|
||||
using Snap.Hutao.Model.InterChange.Achievement;
|
||||
using Snap.Hutao.Service.Achievement;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
|
||||
@@ -5,7 +5,8 @@ using CommunityToolkit.WinUI.Collections;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Factory.Picker;
|
||||
using Snap.Hutao.Model.InterChange.Achievement;
|
||||
using Snap.Hutao.Service.Achievement;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
|
||||
@@ -8,7 +8,7 @@ using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.Control.Media;
|
||||
using Snap.Hutao.Core.IO.DataTransfer;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Message;
|
||||
using Snap.Hutao.Model.Calculable;
|
||||
using Snap.Hutao.Model.Entity.Primitive;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.Metadata.Item;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.DailyNote;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
|
||||
@@ -6,7 +6,9 @@ using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Factory.Picker;
|
||||
using Snap.Hutao.Factory.Progress;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.InterChange.GachaLog;
|
||||
using Snap.Hutao.Service.GachaLog;
|
||||
@@ -27,10 +29,11 @@ namespace Snap.Hutao.ViewModel.GachaLog;
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
|
||||
{
|
||||
private readonly HutaoCloudStatisticsViewModel hutaoCloudStatisticsViewModel;
|
||||
private readonly IGachaLogQueryProviderFactory gachaLogQueryProviderFactory;
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly HutaoCloudStatisticsViewModel hutaoCloudStatisticsViewModel;
|
||||
private readonly HutaoCloudViewModel hutaoCloudViewModel;
|
||||
private readonly IProgressFactory progressFactory;
|
||||
private readonly IGachaLogService gachaLogService;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly JsonSerializerOptions options;
|
||||
@@ -162,7 +165,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
|
||||
throw;
|
||||
}
|
||||
|
||||
IProgress<GachaLogFetchStatus> progress = taskContext.CreateProgressForMainThread<GachaLogFetchStatus>(dialog.OnReport);
|
||||
IProgress<GachaLogFetchStatus> progress = progressFactory.CreateForMainThread<GachaLogFetchStatus>(dialog.OnReport);
|
||||
bool authkeyValid;
|
||||
|
||||
try
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.GachaLog;
|
||||
using Snap.Hutao.Service.Hutao;
|
||||
|
||||
@@ -5,7 +5,8 @@ using Microsoft.Extensions.Caching.Memory;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Factory.Progress;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Service.Game;
|
||||
@@ -38,10 +39,11 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly LaunchStatusOptions launchStatusOptions;
|
||||
private readonly INavigationService navigationService;
|
||||
private readonly IProgressFactory progressFactory;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly ResourceClient resourceClient;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly LaunchOptions launchOptions;
|
||||
private readonly RuntimeOptions hutaoOptions;
|
||||
private readonly IUserService userService;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly IGameServiceFacade gameService;
|
||||
@@ -96,7 +98,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
||||
/// <summary>
|
||||
/// 胡桃选项
|
||||
/// </summary>
|
||||
public RuntimeOptions HutaoOptions { get => hutaoOptions; }
|
||||
public RuntimeOptions HutaoOptions { get => runtimeOptions; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用选项
|
||||
@@ -191,45 +193,44 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
||||
[Command("LaunchCommand")]
|
||||
private async Task LaunchAsync()
|
||||
{
|
||||
if (SelectedScheme is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
gameService.SetChannelOptions(SelectedScheme);
|
||||
|
||||
// Whether or not the channel options changed, we always ensure game resouces
|
||||
LaunchGamePackageConvertDialog dialog = await contentDialogFactory.CreateInstanceAsync<LaunchGamePackageConvertDialog>().ConfigureAwait(false);
|
||||
IProgress<PackageReplaceStatus> convertProgress = taskContext.CreateProgressForMainThread<PackageReplaceStatus>(state => dialog.State = state);
|
||||
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
|
||||
{
|
||||
if (!await gameService.EnsureGameResourceAsync(SelectedScheme, convertProgress).ConfigureAwait(false))
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelLaunchGameEnsureGameResourceFail);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (SelectedGameAccount is not null)
|
||||
{
|
||||
if (!gameService.SetGameAccount(SelectedGameAccount))
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelLaunchGameSwitchGameAccountFail);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
IProgress<LaunchStatus> launchProgress = taskContext.CreateProgressForMainThread<LaunchStatus>(status => launchStatusOptions.LaunchStatus = status);
|
||||
await gameService.LaunchAsync(launchProgress).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ExceptionFormat.Format(ex));
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
if (SelectedScheme is null)
|
||||
{
|
||||
infoBarService.Error(SH.ViewModelLaunchGameSchemeNotSelected);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Always ensure game resources
|
||||
gameService.SetChannelOptions(SelectedScheme);
|
||||
|
||||
LaunchGamePackageConvertDialog dialog = await contentDialogFactory.CreateInstanceAsync<LaunchGamePackageConvertDialog>().ConfigureAwait(false);
|
||||
IProgress<PackageReplaceStatus> convertProgress = progressFactory.CreateForMainThread<PackageReplaceStatus>(state => dialog.State = state);
|
||||
|
||||
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
|
||||
{
|
||||
if (!await gameService.EnsureGameResourceAsync(SelectedScheme, convertProgress).ConfigureAwait(false))
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelLaunchGameEnsureGameResourceFail);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (SelectedGameAccount is not null)
|
||||
{
|
||||
if (!gameService.SetGameAccount(SelectedGameAccount))
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelLaunchGameSwitchGameAccountFail);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
IProgress<LaunchStatus> launchProgress = progressFactory.CreateForMainThread<LaunchStatus>(status => launchStatusOptions.LaunchStatus = status);
|
||||
await gameService.LaunchAsync(launchProgress).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Service.Hutao;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
|
||||
@@ -11,7 +11,8 @@ using Snap.Hutao.Core.IO.DataTransfer;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Core.Shell;
|
||||
using Snap.Hutao.Core.Windowing;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Factory.Picker;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||
@@ -40,12 +41,12 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
{
|
||||
private readonly HomeCardOptions homeCardOptions = new();
|
||||
|
||||
private readonly HutaoPassportViewModel hutaoPassportViewModel;
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly IGameLocatorFactory gameLocatorFactory;
|
||||
private readonly INavigationService navigationService;
|
||||
private readonly IClipboardInterop clipboardInterop;
|
||||
private readonly IShellLinkInterop shellLinkInterop;
|
||||
private readonly HutaoPassportViewModel hutaoPassportViewModel;
|
||||
private readonly HutaoUserOptions hutaoUserOptions;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
|
||||
@@ -7,7 +7,7 @@ using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Core.IO.DataTransfer;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.Service.SignIn;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Collections;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Model.Calculable;
|
||||
using Snap.Hutao.Model.Entity.Primitive;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Collections;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Model.Calculable;
|
||||
using Snap.Hutao.Model.Entity.Primitive;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
Reference in New Issue
Block a user