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