mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
fix unlocking fps
Opening any OSD can kill process
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 _);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user