This commit is contained in:
DismissedLight
2023-11-05 16:03:10 +08:00
parent f2ba316059
commit c0980fabe8
41 changed files with 257 additions and 116 deletions

View File

@@ -8,7 +8,7 @@ namespace Snap.Hutao.Core.Threading;
/// </summary>
internal interface ITaskContext
{
IProgress<T> CreateProgressForMainThread<T>(Action<T> handler);
SynchronizationContext GetSynchronizationContext();
/// <summary>
/// 在主线程上同步等待执行操作

View File

@@ -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;
}
}

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -3,7 +3,7 @@
using Windows.Storage.Pickers;
namespace Snap.Hutao.Factory.Abstraction;
namespace Snap.Hutao.Factory.Picker;
/// <summary>
/// 文件选择器工厂

View File

@@ -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;
}

View File

@@ -0,0 +1,6 @@
namespace Snap.Hutao.Factory.Progress;
internal interface IProgressFactory
{
IProgress<T> CreateForMainThread<T>(Action<T> handler);
}

View File

@@ -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());
}
}

View File

@@ -104,6 +104,8 @@ internal sealed partial class SettingEntry
public const string LaunchIsMonitorEnabled = "Launch.IsMonitorEnabled";
public const string LaunchUseStarwardPlayTimeStatistics = "Launch.UseStarwardPlayTimeStatistics";
/// <summary>
/// 启动游戏 多倍启动
/// </summary>

View File

@@ -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>

View File

@@ -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
{

View 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;
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -55,7 +55,7 @@ internal static class RegistryInterop
try
{
Process.Start(startInfo)?.WaitForExit();
System.Diagnostics.Process.Start(startInfo)?.WaitForExit();
}
catch (Win32Exception ex)
{

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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);

View File

@@ -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
{

View 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);
}
}
}

View File

@@ -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>

View File

@@ -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));
}

View File

@@ -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();
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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=&#xEC92;}">
<ToggleSwitch Width="120" IsOn="{Binding Options.UseStarwardPlayTimeStatistics, Mode=TwoWay}"/>
</cwc:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;