From cd3ce6d3381297b93e5520e228133f1061687523 Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Mon, 28 Aug 2023 22:30:09 +0800 Subject: [PATCH] fix #815 --- .../Resource/Localization/SH.Designer.cs | 63 +++++++++++++++++++ .../Snap.Hutao/Resource/Localization/SH.resx | 21 +++++++ .../Factory/SummaryAvatarFactory.cs | 2 - .../Snap.Hutao/Service/Game/GameService.cs | 24 +++++-- .../Snap.Hutao/Service/Game/IGameService.cs | 6 +- .../Snap.Hutao/Service/Game/LaunchPhase.cs | 15 +++++ .../Snap.Hutao/Service/Game/LaunchStatus.cs | 31 +++++++++ .../Service/Game/LaunchStatusOptions.cs | 14 +++++ .../Snap.Hutao/Service/Game/ProcessInterop.cs | 34 +++++----- .../Service/Game/Unlocker/GameFpsUnlocker.cs | 1 + .../Service/Game/Unlocker/UnlockerStatus.cs | 7 +-- .../Snap.Hutao/View/Page/LaunchGamePage.xaml | 6 ++ .../ViewModel/AvatarProperty/AvatarView.cs | 6 +- .../ViewModel/Game/LaunchGameViewModel.cs | 10 ++- .../ViewModel/Game/LaunchGameViewModelSlim.cs | 2 +- 15 files changed, 202 insertions(+), 40 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchPhase.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchStatus.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchStatusOptions.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs index a0360730..bd0f23c2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs @@ -1536,6 +1536,69 @@ namespace Snap.Hutao.Resource.Localization { } } + /// + /// 查找类似 游戏进程已退出 的本地化字符串。 + /// + internal static string ServiceGameLaunchPhaseProcessExited { + get { + return ResourceManager.GetString("ServiceGameLaunchPhaseProcessExited", resourceCulture); + } + } + + /// + /// 查找类似 正在初始化游戏进程 的本地化字符串。 + /// + internal static string ServiceGameLaunchPhaseProcessInitializing { + get { + return ResourceManager.GetString("ServiceGameLaunchPhaseProcessInitializing", resourceCulture); + } + } + + /// + /// 查找类似 游戏进程已启动 的本地化字符串。 + /// + internal static string ServiceGameLaunchPhaseProcessStarted { + get { + return ResourceManager.GetString("ServiceGameLaunchPhaseProcessStarted", resourceCulture); + } + } + + /// + /// 查找类似 解锁帧率上限失败,正在结束游戏进程 的本地化字符串。 + /// + internal static string ServiceGameLaunchPhaseUnlockFpsFailed { + get { + return ResourceManager.GetString("ServiceGameLaunchPhaseUnlockFpsFailed", resourceCulture); + } + } + + /// + /// 查找类似 解锁帧率上限成功 的本地化字符串。 + /// + internal static string ServiceGameLaunchPhaseUnlockFpsSucceed { + get { + return ResourceManager.GetString("ServiceGameLaunchPhaseUnlockFpsSucceed", resourceCulture); + } + } + + /// + /// 查找类似 正在尝试解锁帧率上限 的本地化字符串。 + /// + internal static string ServiceGameLaunchPhaseUnlockingFps { + get { + return ResourceManager.GetString("ServiceGameLaunchPhaseUnlockingFps", resourceCulture); + } + } + + /// + /// 查找类似 等待游戏进程退出 的本地化字符串。 + /// + internal static string ServiceGameLaunchPhaseWaitingProcessExit { + get { + return ResourceManager.GetString("ServiceGameLaunchPhaseWaitingProcessExit", resourceCulture); + } + } + /// /// 查找类似 选择游戏本体 的本地化字符串。 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index 6e41fefe..f8d20387 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -665,6 +665,27 @@ 游戏文件操作失败:{0} + + 游戏进程已退出 + + + 正在初始化游戏进程 + + + 游戏进程已启动 + + + 解锁帧率上限失败,正在结束游戏进程 + + + 解锁帧率上限成功 + + + 正在尝试解锁帧率上限 + + + 等待游戏进程退出 + 选择游戏本体 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs index ba2b4b49..61327acb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs @@ -22,8 +22,6 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory; [HighQuality] internal sealed class SummaryAvatarFactory { - private static readonly DateTimeOffset DefaultRefreshTime = new(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)); - private readonly ModelAvatarInfo avatarInfo; private readonly DateTimeOffset showcaseRefreshTime; private readonly DateTimeOffset gameRecordRefreshTime; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs index ff23744e..647588b3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs @@ -230,7 +230,7 @@ internal sealed partial class GameService : IGameService } /// - public async ValueTask LaunchAsync() + public async ValueTask LaunchAsync(IProgress progress) { if (IsGameRunning()) { @@ -240,21 +240,37 @@ internal sealed partial class GameService : IGameService string gamePath = appOptions.GamePath; ArgumentException.ThrowIfNullOrEmpty(gamePath); + progress.Report(new(LaunchPhase.ProcessInitializing, SH.ServiceGameLaunchPhaseProcessInitializing)); using (Process game = ProcessInterop.InitializeGameProcess(launchOptions, gamePath)) { try { - bool isFirstInstance = Interlocked.Increment(ref runningGamesCounter) == 1; - game.Start(); + progress.Report(new(LaunchPhase.ProcessStarted, SH.ServiceGameLaunchPhaseProcessStarted)); if (runtimeOptions.IsElevated && appOptions.IsAdvancedLaunchOptionsEnabled && launchOptions.UnlockFps) { - await ProcessInterop.UnlockFpsAsync(serviceProvider, game, default).ConfigureAwait(false); + progress.Report(new(LaunchPhase.UnlockingFps, SH.ServiceGameLaunchPhaseUnlockingFps)); + try + { + await ProcessInterop.UnlockFpsAsync(serviceProvider, game, progress).ConfigureAwait(false); + } + catch (InvalidOperationException) + { + // The Unlocker can't unlock the process + game.Kill(); + throw; + } + finally + { + progress.Report(new(LaunchPhase.ProcessExited, SH.ServiceGameLaunchPhaseProcessExited)); + } } else { + progress.Report(new(LaunchPhase.WaitingForExit, SH.ServiceGameLaunchPhaseWaitingProcessExit)); await game.WaitForExitAsync().ConfigureAwait(false); + progress.Report(new(LaunchPhase.ProcessExited, SH.ServiceGameLaunchPhaseProcessExited)); } } finally diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs index 5a25104f..6afc58eb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameService.cs @@ -50,11 +50,7 @@ internal interface IGameService /// 是否正在运行 bool IsGameRunning(); - /// - /// 异步启动 - /// - /// 任务 - ValueTask LaunchAsync(); + ValueTask LaunchAsync(IProgress progress); /// /// 异步修改游戏账号名称 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchPhase.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchPhase.cs new file mode 100644 index 00000000..3fdaf493 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchPhase.cs @@ -0,0 +1,15 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.Game; + +internal enum LaunchPhase +{ + ProcessInitializing, + ProcessStarted, + UnlockingFps, + UnlockFpsSucceed, + UnlockFpsFailed, + WaitingForExit, + ProcessExited, +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchStatus.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchStatus.cs new file mode 100644 index 00000000..f98c6449 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchStatus.cs @@ -0,0 +1,31 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core; +using Snap.Hutao.Core.ExceptionService; +using Snap.Hutao.Core.IO.Ini; +using Snap.Hutao.Model.Entity; +using Snap.Hutao.Service.Game.Locator; +using Snap.Hutao.Service.Game.Package; +using Snap.Hutao.View.Dialog; +using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher; +using Snap.Hutao.Web.Response; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using static Snap.Hutao.Service.Game.GameConstants; + +namespace Snap.Hutao.Service.Game; + +internal sealed class LaunchStatus +{ + public LaunchStatus(LaunchPhase phase, string description) + { + Phase = phase; + Description = description; + } + + public LaunchPhase Phase { get; set; } + + public string Description { get; set; } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchStatusOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchStatusOptions.cs new file mode 100644 index 00000000..1b8290bd --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchStatusOptions.cs @@ -0,0 +1,14 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Snap.Hutao.Service.Game; + +[Injection(InjectAs.Singleton)] +internal sealed class LaunchStatusOptions : ObservableObject +{ + private LaunchStatus? launchStatus; + + public LaunchStatus? LaunchStatus { get => launchStatus; set => SetProperty(ref launchStatus, value); } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/ProcessInterop.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/ProcessInterop.cs index 123fb6d6..5b7e8122 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/ProcessInterop.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/ProcessInterop.cs @@ -49,19 +49,12 @@ internal static class ProcessInterop }; } - /// - /// 解锁帧率 - /// - /// 服务提供器 - /// 游戏进程 - /// 取消令牌 - /// 任务 - public static ValueTask UnlockFpsAsync(IServiceProvider serviceProvider, Process game, CancellationToken token) + public static ValueTask UnlockFpsAsync(IServiceProvider serviceProvider, Process game, IProgress progress, CancellationToken token = default) { IGameFpsUnlocker unlocker = serviceProvider.CreateInstance(game); UnlockTimingOptions options = new(100, 20000, 3000); - Progress progress = new(); // TODO: do something. - return unlocker.UnlockAsync(options, progress, token); + Progress lockerProgress = new(unlockStatus => progress.Report(FromUnlockStatus(unlockStatus))); + return unlocker.UnlockAsync(options, lockerProgress, token); } /// @@ -92,8 +85,7 @@ internal static class ProcessInterop /// /// 进程句柄 /// 库的路径,不包含'\0' - [SuppressMessage("", "SH002")] - public static unsafe void LoadLibraryAndInject(HANDLE hProcess, ReadOnlySpan libraryPathu8) + public static unsafe void LoadLibraryAndInject(in HANDLE hProcess, in ReadOnlySpan libraryPathu8) { HINSTANCE hKernelDll = GetModuleHandle("kernel32.dll"); Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError()); @@ -132,8 +124,7 @@ internal static class ProcessInterop } } - [SuppressMessage("", "SH002")] - private static unsafe FARPROC GetProcAddress(HINSTANCE hModule, ReadOnlySpan lpProcName) + private static unsafe FARPROC GetProcAddress(in HINSTANCE hModule, in ReadOnlySpan lpProcName) { fixed (byte* lpProcNameLocal = lpProcName) { @@ -141,12 +132,23 @@ internal static class ProcessInterop } } - [SuppressMessage("", "SH002")] - private static unsafe BOOL WriteProcessMemory(HANDLE hProcess, void* lpBaseAddress, ReadOnlySpan buffer) + private static unsafe BOOL WriteProcessMemory(in HANDLE hProcess, void* lpBaseAddress, in ReadOnlySpan buffer) { fixed (void* lpBuffer = buffer) { return Windows.Win32.PInvoke.WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, unchecked((uint)buffer.Length)); } } + + private static LaunchStatus FromUnlockStatus(UnlockerStatus unlockerStatus) + { + if (unlockerStatus.FindModuleState == FindModuleResult.Ok) + { + return new(LaunchPhase.UnlockFpsSucceed, SH.ServiceGameLaunchPhaseUnlockFpsSucceed); + } + else + { + return new(LaunchPhase.UnlockFpsFailed, SH.ServiceGameLaunchPhaseUnlockFpsFailed); + } + } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs index 40adc789..e7a0ed68 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs @@ -50,6 +50,7 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker // Read UnityPlayer.dll UnsafeFindFpsAddress(moduleEntryInfo); + progress.Report(status); // When player switch between scenes, we have to re adjust the fps // So we keep a loop here diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/UnlockerStatus.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/UnlockerStatus.cs index e13e3d35..2f7ebbc0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/UnlockerStatus.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/UnlockerStatus.cs @@ -8,7 +8,7 @@ namespace Snap.Hutao.Service.Game.Unlocker; /// /// 解锁状态 /// -internal sealed class UnlockerStatus : ICloneable +internal sealed class UnlockerStatus { /// /// 状态描述 @@ -29,9 +29,4 @@ internal sealed class UnlockerStatus : ICloneable /// FPS 字节地址 /// public nuint FpsAddress { get; set; } - - public UnlockerStatus Clone() - { - throw new NotImplementedException(); - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml index d246fe86..7f43cca7 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml @@ -34,6 +34,12 @@ + + + public uint FetterLevel { get; set; } - public string ShowcaseRefreshTimeFormat { get; set; } + public string ShowcaseRefreshTimeFormat { get; set; } = default!; - public string GameRecordRefreshTimeFormat { get; set; } + public string GameRecordRefreshTimeFormat { get; set; } = default!; - public string CalculatorRefreshTimeFormat { get; set; } + public string CalculatorRefreshTimeFormat { get; set; } = default!; /// /// Id diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs index 0980a3d7..0e7a8d6e 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs @@ -37,6 +37,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel private readonly INavigationService navigationService; private readonly IInfoBarService infoBarService; private readonly LaunchOptions launchOptions; + private readonly LaunchStatusOptions launchStatusOptions; private readonly RuntimeOptions hutaoOptions; private readonly ResourceClient resourceClient; private readonly IUserService userService; @@ -88,6 +89,8 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel /// public LaunchOptions Options { get => launchOptions; } + public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; } + /// /// 胡桃选项 /// @@ -187,10 +190,10 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel { // Channel changed, we need to change local file. LaunchGamePackageConvertDialog dialog = await contentDialogFactory.CreateInstanceAsync().ConfigureAwait(false); - IProgress progress = taskContext.CreateProgressForMainThread(state => dialog.State = state/*.Clone()*/); + IProgress convertProgress = taskContext.CreateProgressForMainThread(state => dialog.State = state); using (await dialog.BlockAsync(taskContext).ConfigureAwait(false)) { - if (!await gameService.EnsureGameResourceAsync(SelectedScheme, progress).ConfigureAwait(false)) + if (!await gameService.EnsureGameResourceAsync(SelectedScheme, convertProgress).ConfigureAwait(false)) { infoBarService.Warning(SH.ViewModelLaunchGameEnsureGameResourceFail); return; @@ -207,7 +210,8 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel } } - await gameService.LaunchAsync().ConfigureAwait(false); + IProgress launchProgress = taskContext.CreateProgressForMainThread(status => launchStatusOptions.LaunchStatus = status); + await gameService.LaunchAsync(launchProgress).ConfigureAwait(false); } catch (Exception ex) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs index ce903995..cf373fbe 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs @@ -58,7 +58,7 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli } } - await gameService.LaunchAsync().ConfigureAwait(false); + await gameService.LaunchAsync(new Progress()).ConfigureAwait(false); } catch (Exception ex) {