fix unlocking fps

Opening any OSD can kill process
This commit is contained in:
DismissedLight
2024-06-23 00:26:36 +08:00
parent 6746610ab6
commit eeffa446a2
18 changed files with 278 additions and 47 deletions

View File

@@ -27,7 +27,7 @@ internal sealed class LaunchExecutionBetterGenshinImpactAutomationHandlder : ILa
{
context.Logger.LogInformation("Waiting game window to be ready");
SpinWait.SpinUntil(() => context.Process.MainWindowHandle != IntPtr.Zero);
SpinWait.SpinUntil(() => context.Process.MainWindowHandle is not 0);
}
catch (InvalidOperationException)
{

View File

@@ -4,6 +4,7 @@
using Snap.Hutao.Core;
using Snap.Hutao.Factory.Progress;
using Snap.Hutao.Service.Game.Unlocker;
using Snap.Hutao.Service.Game.Unlocker.Island;
namespace Snap.Hutao.Service.Game.Launching.Handler;
@@ -24,13 +25,17 @@ internal sealed class LaunchExecutionUnlockFpsHandler : ILaunchExecutionDelegate
return;
}
GameFpsUnlocker unlocker = new(context.ServiceProvider, context.Process, new(gameFileSystem, 100, 20000, 3000), progress);
IslandGameFpsUnlocker unlocker = new(context.ServiceProvider, context.Process, new(gameFileSystem, 100, 20000, 2000), progress);
try
{
if (await unlocker.UnlockAsync(context.CancellationToken).ConfigureAwait(false))
{
unlocker.PostUnlockAsync(context.CancellationToken).SafeForget();
unlocker.PostUnlockAsync(context.CancellationToken).SafeForget(context.Logger);
}
else
{
context.Logger.LogError("Unlocking FPS failed");
}
}
catch (Exception ex)

View File

@@ -0,0 +1,51 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
using System.Diagnostics;
using static Snap.Hutao.Win32.Kernel32;
namespace Snap.Hutao.Service.Game.Unlocker;
internal sealed class DefaultGameFpsUnlocker : GameFpsUnlocker
{
public DefaultGameFpsUnlocker(IServiceProvider serviceProvider, Process gameProcess, in UnlockOptions options, IProgress<GameFpsUnlockerContext> progress)
: base(serviceProvider, gameProcess, options, progress)
{
}
protected override async ValueTask PostUnlockOverrideAsync(GameFpsUnlockerContext context, LaunchOptions launchOptions, ILogger logger, CancellationToken token = default)
{
using (PeriodicTimer timer = new(context.Options.AdjustFpsDelay))
{
while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false))
{
if (!context.GameProcess.HasExited && context.FpsAddress != 0U)
{
UnsafeWriteProcessMemory(context.AllAccess, context.FpsAddress, launchOptions.TargetFps);
WIN32_ERROR error = GetLastError();
if (error is not WIN32_ERROR.NO_ERROR)
{
logger.LogError("Failed to WriteProcessMemory at FpsAddress, error code 0x{Code:X8}", error);
context.Description = SH.FormatServiceGameUnlockerWriteProcessMemoryFpsAddressFailed(error);
}
context.Report();
}
else
{
context.IsUnlockerValid = false;
context.FpsAddress = 0;
context.Report();
return;
}
}
}
}
[SuppressMessage("", "SH002")]
private static unsafe bool UnsafeWriteProcessMemory(HANDLE hProcess, nuint baseAddress, int value)
{
return WriteProcessMemory(hProcess, (void*)baseAddress, ref value, out _);
}
}

View File

@@ -8,6 +8,9 @@ using static Snap.Hutao.Win32.Kernel32;
namespace Snap.Hutao.Service.Game.Unlocker;
/// <summary>
/// Credit to https://github.com/34736384/genshin-fps-unlock
/// </summary>
internal static class GameFpsAddress
{
#pragma warning disable SA1310

View File

@@ -10,29 +10,22 @@ using static Snap.Hutao.Win32.Kernel32;
namespace Snap.Hutao.Service.Game.Unlocker;
/// <summary>
/// 游戏帧率解锁器
/// Credit to https://github.com/34736384/genshin-fps-unlock
/// </summary>
[HighQuality]
internal sealed class GameFpsUnlocker : IGameFpsUnlocker
internal abstract class GameFpsUnlocker : IGameFpsUnlocker
{
private readonly ILogger<GameFpsUnlocker> logger;
private readonly LaunchOptions launchOptions;
private readonly GameFpsUnlockerContext context = new();
public GameFpsUnlocker(IServiceProvider serviceProvider, Process gameProcess, in UnlockOptions options, IProgress<GameFpsUnlockerContext> progress)
{
logger = serviceProvider.GetRequiredService<ILogger<GameFpsUnlocker>>();
launchOptions = serviceProvider.GetRequiredService<LaunchOptions>();
context.GameProcess = gameProcess;
context.AllAccess = OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_ALL_ACCESS, false, (uint)gameProcess.Id);
context.Options = options;
context.Progress = progress;
context.Logger = serviceProvider.GetRequiredService<ILogger<GameFpsUnlocker>>();
}
/// <inheritdoc/>
public async ValueTask<bool> UnlockAsync(CancellationToken token = default)
{
HutaoException.ThrowIfNot(context.IsUnlockerValid, "This Unlocker is invalid");
@@ -49,40 +42,12 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
return context.FpsAddress != 0U;
}
public async ValueTask PostUnlockAsync(CancellationToken token = default)
public ValueTask PostUnlockAsync(CancellationToken token = default)
{
using (PeriodicTimer timer = new(context.Options.AdjustFpsDelay))
{
while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false))
{
if (!context.GameProcess.HasExited && context.FpsAddress != 0U)
{
UnsafeWriteProcessMemory(context.AllAccess, context.FpsAddress, launchOptions.TargetFps);
WIN32_ERROR error = GetLastError();
if (error is not WIN32_ERROR.NO_ERROR)
{
logger.LogError("Failed to WriteProcessMemory at FpsAddress, error code 0x{Code:X8}", error);
context.Description = SH.FormatServiceGameUnlockerWriteProcessMemoryFpsAddressFailed(error);
}
context.Report();
}
else
{
context.IsUnlockerValid = false;
context.FpsAddress = 0;
context.Report();
return;
}
}
}
return PostUnlockOverrideAsync(context, launchOptions, context.Logger, token);
}
[SuppressMessage("", "SH002")]
private static unsafe bool UnsafeWriteProcessMemory(HANDLE hProcess, nuint baseAddress, int value)
{
return WriteProcessMemory(hProcess, (void*)baseAddress, ref value, out _);
}
protected abstract ValueTask PostUnlockOverrideAsync(GameFpsUnlockerContext context, LaunchOptions launchOptions, ILogger logger, CancellationToken token = default);
private static RequiredLocalModule LoadRequiredLocalModule(GameFileSystem gameFileSystem)
{

View File

@@ -6,9 +6,6 @@ using System.Diagnostics;
namespace Snap.Hutao.Service.Game.Unlocker;
/// <summary>
/// 解锁状态
/// </summary>
internal sealed class GameFpsUnlockerContext
{
public string Description { get; set; } = default!;
@@ -27,6 +24,8 @@ internal sealed class GameFpsUnlockerContext
public IProgress<GameFpsUnlockerContext> Progress { get; set; } = default!;
public ILogger Logger { get; set; } = default!;
public void Report()
{
Progress.Report(this);

View File

@@ -0,0 +1,15 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
namespace Snap.Hutao.Service.Game.Unlocker.Island;
internal struct IslandEnvironment
{
public nuint Address;
public int Value;
public IslandState State;
public WIN32_ERROR LastError;
public int Reserved;
}

View File

@@ -0,0 +1,104 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
using System.Diagnostics;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using static Snap.Hutao.Win32.ConstValues;
using static Snap.Hutao.Win32.Kernel32;
using static Snap.Hutao.Win32.Macros;
using static Snap.Hutao.Win32.User32;
namespace Snap.Hutao.Service.Game.Unlocker.Island;
internal sealed class IslandGameFpsUnlocker : GameFpsUnlocker
{
private const string IslandEnvironmentName = "4F3E8543-40F7-4808-82DC-21E48A6037A7";
public IslandGameFpsUnlocker(IServiceProvider serviceProvider, Process gameProcess, in UnlockOptions options, IProgress<GameFpsUnlockerContext> progress)
: base(serviceProvider, gameProcess, options, progress)
{
}
protected override async ValueTask PostUnlockOverrideAsync(GameFpsUnlockerContext context, LaunchOptions launchOptions, ILogger logger, CancellationToken token = default(CancellationToken))
{
try
{
using (MemoryMappedFile file = MemoryMappedFile.CreateOrOpen(IslandEnvironmentName, 1024))
{
MemoryMappedViewAccessor accessor = file.CreateViewAccessor();
nint handle = accessor.SafeMemoryMappedViewHandle.DangerousGetHandle();
UpdateIslandEnvironment(handle, context, launchOptions);
InitializeIsland(context.GameProcess);
using (PeriodicTimer timer = new(context.Options.AdjustFpsDelay))
{
while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false))
{
context.Logger.LogInformation("context.GameProcess.HasExited: {Value}", context.GameProcess.HasExited);
context.Logger.LogInformation("context.FpsAddress: {Value}", context.FpsAddress);
if (!context.GameProcess.HasExited && context.FpsAddress != 0U)
{
IslandState state = UpdateIslandEnvironment(handle, context, launchOptions);
context.Logger.LogDebug("Island Environment State: {State}", state);
context.Report();
}
else
{
context.IsUnlockerValid = false;
context.FpsAddress = 0;
context.Report();
return;
}
}
}
}
}
finally
{
context.Logger.LogInformation("Exit PostUnlockOverrideAsync");
}
}
private static unsafe void InitializeIsland(Process gameProcess)
{
HANDLE hModule = default;
try
{
hModule = NativeLibrary.Load("Snap.Hutao.UnlockerIsland.dll");
nint pIslandGetWindowHook = NativeLibrary.GetExport((nint)(hModule & ~0x3L), "IslandGetWindowHook");
HOOKPROC hookProc = default;
((delegate* unmanaged[Stdcall]<HOOKPROC*, HRESULT>)pIslandGetWindowHook)(&hookProc);
SpinWait.SpinUntil(() => gameProcess.MainWindowHandle is not 0);
uint threadId = GetWindowThreadProcessId(gameProcess.MainWindowHandle, default);
HHOOK hHook = SetWindowsHookExW(WINDOWS_HOOK_ID.WH_GETMESSAGE, hookProc, (HINSTANCE)hModule, threadId);
if (hHook.Value is 0)
{
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(GetLastError()));
}
if (!PostThreadMessageW(threadId, WM_NULL, default, default))
{
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(GetLastError()));
}
}
finally
{
NativeLibrary.Free(hModule);
}
}
private unsafe IslandState UpdateIslandEnvironment(nint handle, GameFpsUnlockerContext context, LaunchOptions launchOptions)
{
IslandEnvironment* pIslandEnvironment = (IslandEnvironment*)handle;
pIslandEnvironment->Address = context.FpsAddress;
pIslandEnvironment->Value = launchOptions.TargetFps;
return pIslandEnvironment->State;
}
}

View File

@@ -0,0 +1,12 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Game.Unlocker.Island;
internal enum IslandState
{
None = 0,
Error = 1,
Started = 2,
Stopped = 3,
}

View File

@@ -338,7 +338,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Snap.Hutao.Elevated.Launcher.Runtime" Version="1.1.1">
<PackageReference Include="Snap.Hutao.Elevated.Launcher.Runtime" Version="1.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
@@ -346,6 +346,10 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Snap.Hutao.UnlockerIsland" Version="1.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.556">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -11,5 +11,7 @@ internal readonly struct HANDLE
public static unsafe implicit operator HANDLE(nint value) => *(HANDLE*)&value;
public static unsafe implicit operator nint(HANDLE handle) => *(nint*)&handle;
public static unsafe implicit operator HANDLE(BOOL value) => *(int*)&value;
}

View File

@@ -8,4 +8,6 @@ namespace Snap.Hutao.Win32.Foundation;
internal readonly struct HINSTANCE
{
public readonly nint Value;
public static unsafe implicit operator HINSTANCE(HANDLE value) => *(HINSTANCE*)&value;
}

View File

@@ -6,4 +6,8 @@ namespace Snap.Hutao.Win32.Foundation;
internal readonly struct PCSTR
{
public readonly unsafe byte* Value;
public static unsafe implicit operator PCSTR(byte* value) => *(PCSTR*)&value;
public static unsafe implicit operator byte*(PCSTR value) => *(byte**)&value;
}

View File

@@ -9,6 +9,7 @@ using Snap.Hutao.Win32.System.Memory;
using Snap.Hutao.Win32.System.ProcessStatus;
using Snap.Hutao.Win32.System.Threading;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

View File

@@ -0,0 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Win32.UI.WindowsAndMessaging;
// RAIIFree: UnhookWindowsHookEx
// InvalidHandleValue: -1, 0
internal readonly struct HHOOK
{
public readonly nint Value;
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
namespace Snap.Hutao.Win32.UI.WindowsAndMessaging;
internal unsafe readonly struct HOOKPROC
{
[SuppressMessage("", "IDE0052")]
private readonly delegate* unmanaged[Stdcall]<int, WPARAM, LPARAM, LRESULT> value;
public HOOKPROC(delegate* unmanaged[Stdcall]<int, WPARAM, LPARAM, LRESULT> method)
{
value = method;
}
public static HOOKPROC Create(delegate* unmanaged[Stdcall]<int, WPARAM, LPARAM, LRESULT> method)
{
return new(method);
}
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Win32.UI.WindowsAndMessaging;
internal enum WINDOWS_HOOK_ID
{
WH_CALLWNDPROC = 4,
WH_CALLWNDPROCRET = 12,
WH_CBT = 5,
WH_DEBUG = 9,
WH_FOREGROUNDIDLE = 11,
WH_GETMESSAGE = 3,
WH_JOURNALPLAYBACK = 1,
WH_JOURNALRECORD = 0,
WH_KEYBOARD = 2,
WH_KEYBOARD_LL = 13,
WH_MOUSE = 7,
WH_MOUSE_LL = 14,
WH_MSGFILTER = -1,
WH_SHELL = 10,
WH_SYSMSGFILTER = 6,
}

View File

@@ -180,6 +180,10 @@ internal static class User32
[SupportedOSPlatform("windows5.0")]
public static extern BOOL IsWindowVisible(HWND hWnd);
[DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
[SupportedOSPlatform("windows5.0")]
public static extern BOOL PostThreadMessageW(uint idThread, uint Msg, WPARAM wParam, LPARAM lParam);
[DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
[SupportedOSPlatform("windows5.0")]
public static unsafe extern ushort RegisterClassW(WNDCLASSW* lpWndClass);
@@ -263,6 +267,10 @@ internal static class User32
[SupportedOSPlatform("windows5.0")]
public static extern nint SetWindowLongPtrW(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, nint dwNewLong);
[DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
[SupportedOSPlatform("windows5.0")]
public static extern HHOOK SetWindowsHookExW(WINDOWS_HOOK_ID idHook, HOOKPROC lpfn, [AllowNull] HINSTANCE hmod, uint dwThreadId);
[DllImport("USER32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
[SupportedOSPlatform("windows5.0")]
public static extern BOOL ShowWindow(HWND hWnd, SHOW_WINDOW_CMD nCmdShow);