This commit is contained in:
DismissedLight
2023-08-28 22:30:09 +08:00
committed by Lightczx
parent 0bdc5d6c54
commit cd3ce6d338
15 changed files with 202 additions and 40 deletions

View File

@@ -1536,6 +1536,69 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 游戏进程已退出 的本地化字符串。
/// </summary>
internal static string ServiceGameLaunchPhaseProcessExited {
get {
return ResourceManager.GetString("ServiceGameLaunchPhaseProcessExited", resourceCulture);
}
}
/// <summary>
/// 查找类似 正在初始化游戏进程 的本地化字符串。
/// </summary>
internal static string ServiceGameLaunchPhaseProcessInitializing {
get {
return ResourceManager.GetString("ServiceGameLaunchPhaseProcessInitializing", resourceCulture);
}
}
/// <summary>
/// 查找类似 游戏进程已启动 的本地化字符串。
/// </summary>
internal static string ServiceGameLaunchPhaseProcessStarted {
get {
return ResourceManager.GetString("ServiceGameLaunchPhaseProcessStarted", resourceCulture);
}
}
/// <summary>
/// 查找类似 解锁帧率上限失败,正在结束游戏进程 的本地化字符串。
/// </summary>
internal static string ServiceGameLaunchPhaseUnlockFpsFailed {
get {
return ResourceManager.GetString("ServiceGameLaunchPhaseUnlockFpsFailed", resourceCulture);
}
}
/// <summary>
/// 查找类似 解锁帧率上限成功 的本地化字符串。
/// </summary>
internal static string ServiceGameLaunchPhaseUnlockFpsSucceed {
get {
return ResourceManager.GetString("ServiceGameLaunchPhaseUnlockFpsSucceed", resourceCulture);
}
}
/// <summary>
/// 查找类似 正在尝试解锁帧率上限 的本地化字符串。
/// </summary>
internal static string ServiceGameLaunchPhaseUnlockingFps {
get {
return ResourceManager.GetString("ServiceGameLaunchPhaseUnlockingFps", resourceCulture);
}
}
/// <summary>
/// 查找类似 等待游戏进程退出 的本地化字符串。
/// </summary>
internal static string ServiceGameLaunchPhaseWaitingProcessExit {
get {
return ResourceManager.GetString("ServiceGameLaunchPhaseWaitingProcessExit", resourceCulture);
}
}
/// <summary>
/// 查找类似 选择游戏本体 的本地化字符串。
/// </summary>

View File

@@ -665,6 +665,27 @@
<data name="ServiceGameFileOperationExceptionMessage" xml:space="preserve">
<value>游戏文件操作失败:{0}</value>
</data>
<data name="ServiceGameLaunchPhaseProcessExited" xml:space="preserve">
<value>游戏进程已退出</value>
</data>
<data name="ServiceGameLaunchPhaseProcessInitializing" xml:space="preserve">
<value>正在初始化游戏进程</value>
</data>
<data name="ServiceGameLaunchPhaseProcessStarted" xml:space="preserve">
<value>游戏进程已启动</value>
</data>
<data name="ServiceGameLaunchPhaseUnlockFpsFailed" xml:space="preserve">
<value>解锁帧率上限失败,正在结束游戏进程</value>
</data>
<data name="ServiceGameLaunchPhaseUnlockFpsSucceed" xml:space="preserve">
<value>解锁帧率上限成功</value>
</data>
<data name="ServiceGameLaunchPhaseUnlockingFps" xml:space="preserve">
<value>正在尝试解锁帧率上限</value>
</data>
<data name="ServiceGameLaunchPhaseWaitingProcessExit" xml:space="preserve">
<value>等待游戏进程退出</value>
</data>
<data name="ServiceGameLocatorFileOpenPickerCommitText" xml:space="preserve">
<value>选择游戏本体</value>
</data>

View File

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

View File

@@ -230,7 +230,7 @@ internal sealed partial class GameService : IGameService
}
/// <inheritdoc/>
public async ValueTask LaunchAsync()
public async ValueTask LaunchAsync(IProgress<LaunchStatus> 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

View File

@@ -50,11 +50,7 @@ internal interface IGameService
/// <returns>是否正在运行</returns>
bool IsGameRunning();
/// <summary>
/// 异步启动
/// </summary>
/// <returns>任务</returns>
ValueTask LaunchAsync();
ValueTask LaunchAsync(IProgress<LaunchStatus> progress);
/// <summary>
/// 异步修改游戏账号名称

View File

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

View File

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

View File

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

View File

@@ -49,19 +49,12 @@ internal static class ProcessInterop
};
}
/// <summary>
/// 解锁帧率
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="game">游戏进程</param>
/// <param name="token">取消令牌</param>
/// <returns>任务</returns>
public static ValueTask UnlockFpsAsync(IServiceProvider serviceProvider, Process game, CancellationToken token)
public static ValueTask UnlockFpsAsync(IServiceProvider serviceProvider, Process game, IProgress<LaunchStatus> progress, CancellationToken token = default)
{
IGameFpsUnlocker unlocker = serviceProvider.CreateInstance<GameFpsUnlocker>(game);
UnlockTimingOptions options = new(100, 20000, 3000);
Progress<UnlockerStatus> progress = new(); // TODO: do something.
return unlocker.UnlockAsync(options, progress, token);
Progress<UnlockerStatus> lockerProgress = new(unlockStatus => progress.Report(FromUnlockStatus(unlockStatus)));
return unlocker.UnlockAsync(options, lockerProgress, token);
}
/// <summary>
@@ -92,8 +85,7 @@ internal static class ProcessInterop
/// </summary>
/// <param name="hProcess">进程句柄</param>
/// <param name="libraryPathu8">库的路径,不包含'\0'</param>
[SuppressMessage("", "SH002")]
public static unsafe void LoadLibraryAndInject(HANDLE hProcess, ReadOnlySpan<byte> libraryPathu8)
public static unsafe void LoadLibraryAndInject(in HANDLE hProcess, in ReadOnlySpan<byte> 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<byte> lpProcName)
private static unsafe FARPROC GetProcAddress(in HINSTANCE hModule, in ReadOnlySpan<byte> 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<byte> buffer)
private static unsafe BOOL WriteProcessMemory(in HANDLE hProcess, void* lpBaseAddress, in ReadOnlySpan<byte> 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);
}
}
}

View File

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

View File

@@ -8,7 +8,7 @@ namespace Snap.Hutao.Service.Game.Unlocker;
/// <summary>
/// 解锁状态
/// </summary>
internal sealed class UnlockerStatus : ICloneable<UnlockerStatus>
internal sealed class UnlockerStatus
{
/// <summary>
/// 状态描述
@@ -29,9 +29,4 @@ internal sealed class UnlockerStatus : ICloneable<UnlockerStatus>
/// FPS 字节地址
/// </summary>
public nuint FpsAddress { get; set; }
public UnlockerStatus Clone()
{
throw new NotImplementedException();
}
}

View File

@@ -34,6 +34,12 @@
<Pivot>
<Pivot.RightHeader>
<CommandBar DefaultLabelPosition="Right">
<CommandBar.Content>
<TextBlock
Margin="12,14,12,0"
VerticalAlignment="Center"
Text="{Binding LaunchStatusOptions.LaunchStatus.Description}"/>
</CommandBar.Content>
<AppBarButton
Command="{Binding OpenScreenshotFolderCommand}"
Icon="{shcm:FontIcon Glyph=&#xED25;}"

View File

@@ -93,11 +93,11 @@ internal sealed class AvatarView : INameIconSide, ICalculableSource<ICalculableA
/// </summary>
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!;
/// <summary>
/// Id

View File

@@ -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
/// </summary>
public LaunchOptions Options { get => launchOptions; }
public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; }
/// <summary>
/// 胡桃选项
/// </summary>
@@ -187,10 +190,10 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
{
// Channel changed, we need to change local file.
LaunchGamePackageConvertDialog dialog = await contentDialogFactory.CreateInstanceAsync<LaunchGamePackageConvertDialog>().ConfigureAwait(false);
IProgress<PackageReplaceStatus> progress = taskContext.CreateProgressForMainThread<PackageReplaceStatus>(state => dialog.State = state/*.Clone()*/);
IProgress<PackageReplaceStatus> convertProgress = taskContext.CreateProgressForMainThread<PackageReplaceStatus>(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<LaunchStatus> launchProgress = taskContext.CreateProgressForMainThread<LaunchStatus>(status => launchStatusOptions.LaunchStatus = status);
await gameService.LaunchAsync(launchProgress).ConfigureAwait(false);
}
catch (Exception ex)
{

View File

@@ -58,7 +58,7 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
}
}
await gameService.LaunchAsync().ConfigureAwait(false);
await gameService.LaunchAsync(new Progress<LaunchStatus>()).ConfigureAwait(false);
}
catch (Exception ex)
{