diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs
index 7bf5d25a..91bf8add 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs
@@ -28,6 +28,20 @@ internal static class ThrowHelper
throw new OperationCanceledException(message, inner);
}
+ ///
+ /// 无效操作
+ ///
+ /// 消息
+ /// 内部错误
+ /// nothing
+ /// 无效操作异常
+ [DoesNotReturn]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static InvalidOperationException InvalidOperation(string message, Exception? inner)
+ {
+ throw new InvalidOperationException(message, inner);
+ }
+
///
/// 游戏文件操作失败
///
diff --git a/src/Snap.Hutao/Snap.Hutao/NativeMethods.json b/src/Snap.Hutao/Snap.Hutao/NativeMethods.json
index f7b56332..bcd1d020 100644
--- a/src/Snap.Hutao/Snap.Hutao/NativeMethods.json
+++ b/src/Snap.Hutao/Snap.Hutao/NativeMethods.json
@@ -1,6 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/microsoft/CsWin32/main/src/Microsoft.Windows.CsWin32/settings.schema.json",
"allowMarshaling": true,
- "public": true,
"useSafeHandles": false
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt b/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt
index ef8b2b15..236f52f3 100644
--- a/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt
+++ b/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt
@@ -51,6 +51,7 @@ GetForegroundWindow
GetWindowPlacement
GetWindowThreadProcessId
SetForegroundWindow
+ReleaseDC
// WinRT
IMemoryBufferByteAccess
\ 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 9c4ba836..637d6281 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/ProcessInterop.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/ProcessInterop.cs
@@ -58,12 +58,8 @@ internal static class ProcessInterop
public static Task UnlockFpsAsync(IServiceProvider serviceProvider, Process game)
{
IGameFpsUnlocker unlocker = serviceProvider.CreateInstance(game);
-
- TimeSpan findModuleDelay = TimeSpan.FromMilliseconds(100);
- TimeSpan findModuleLimit = TimeSpan.FromMilliseconds(10000);
- TimeSpan adjustFpsDelay = TimeSpan.FromMilliseconds(3000);
-
- return unlocker.UnlockAsync(findModuleDelay, findModuleLimit, adjustFpsDelay);
+ UnlockTimingOptions options = new(100, 10000, 3000);
+ return unlocker.UnlockAsync(options);
}
///
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 d7036464..2a6b981d 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs
@@ -2,8 +2,11 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.Diagnostics;
+using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Win32;
+using System.Buffers.Binary;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Windows.Win32.Foundation;
@@ -15,8 +18,6 @@ namespace Snap.Hutao.Service.Game.Unlocker;
///
/// 游戏帧率解锁器
/// Credit to https://github.com/34736384/genshin-fps-unlock
-///
-/// TODO: Save memory alloc on GameModuleEntryInfo
///
[HighQuality]
internal sealed class GameFpsUnlocker : IGameFpsUnlocker
@@ -46,12 +47,11 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
}
///
- public async Task UnlockAsync(TimeSpan findModuleDelay, TimeSpan findModuleLimit, TimeSpan adjustFpsDelay)
+ public async Task UnlockAsync(UnlockTimingOptions options)
{
- logger.LogInformation("UnlockAsync called");
Verify.Operation(isValid, "This Unlocker is invalid");
- GameModuleEntryInfo moduleEntryInfo = await FindModuleAsync(findModuleDelay, findModuleLimit).ConfigureAwait(false);
+ GameModuleEntryInfo moduleEntryInfo = await FindModuleAsync(options.FindModuleDelay, options.FindModuleLimit).ConfigureAwait(false);
Must.Argument(moduleEntryInfo.HasValue, "读取游戏内存失败");
// Read UnityPlayer.dll
@@ -59,10 +59,10 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
// When player switch between scenes, we have to re adjust the fps
// So we keep a loop here
- await LoopAdjustFpsAsync(adjustFpsDelay).ConfigureAwait(false);
+ await LoopAdjustFpsAsync(options.AdjustFpsDelay).ConfigureAwait(false);
}
- private static unsafe bool UnsafeReadModulesMemory(Process process, GameModuleEntryInfo moduleEntryInfo, out VirtualMemory memory)
+ private static unsafe bool UnsafeReadModulesMemory(Process process, in GameModuleEntryInfo moduleEntryInfo, out VirtualMemory memory)
{
MODULEENTRY32 unityPlayer = moduleEntryInfo.UnityPlayer;
MODULEENTRY32 userAssembly = moduleEntryInfo.UserAssembly;
@@ -73,17 +73,22 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
&& ReadProcessMemory((HANDLE)process.Handle, userAssembly.modBaseAddr, lpBuffer + unityPlayer.modBaseSize, userAssembly.modBaseSize, default);
}
- private static unsafe bool UnsafeReadProcessMemory(Process process, nuint offset, out nuint value)
+ private static unsafe bool UnsafeReadProcessMemory(Process process, nuint baseAddress, out nuint value)
{
- fixed (nuint* pValue = &value)
+ ulong temp = 0;
+ bool result = ReadProcessMemory((HANDLE)process.Handle, (void*)baseAddress, (byte*)&temp, 8, default);
+ if (!result)
{
- return ReadProcessMemory((HANDLE)process.Handle, (void*)offset, &pValue, unchecked((uint)sizeof(nuint)));
+ ThrowHelper.InvalidOperation("读取进程内存失败", null);
}
+
+ value = (nuint)temp;
+ return result;
}
- private static unsafe bool UnsafeWriteModuleMemory(Process process, nuint baseAddress, int write)
+ private static unsafe bool UnsafeWriteProcessMemory(Process process, nuint baseAddress, int write)
{
- return WriteProcessMemory((HANDLE)process.Handle, (void*)baseAddress, &write, sizeof(int), null);
+ return WriteProcessMemory((HANDLE)process.Handle, (void*)baseAddress, &write, sizeof(int), default);
}
private static unsafe MODULEENTRY32 UnsafeFindModule(int processId, in ReadOnlySpan moduleName)
@@ -109,7 +114,7 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
}
}
- private static int FindPatternOffsetImplmentation(ReadOnlySpan memory)
+ private static int IndexOfPattern(in ReadOnlySpan memory)
{
// E8 ?? ?? ?? ?? 85 C0 7E 07 E8 ?? ?? ?? ?? EB 05
int second = 0;
@@ -130,10 +135,8 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
return -1;
}
- private unsafe GameModuleEntryInfo UnsafeGetGameModuleEntryInfo(int processId)
+ private static unsafe GameModuleEntryInfo UnsafeGetGameModuleEntryInfo(int processId)
{
- logger.LogInformation("UnsafeGetGameModuleEntryInfo called");
-
MODULEENTRY32 unityPlayer = UnsafeFindModule(processId, "UnityPlayer.dll"u8);
MODULEENTRY32 userAssembly = UnsafeFindModule(processId, "UserAssembly.dll"u8);
@@ -147,8 +150,6 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
private async Task FindModuleAsync(TimeSpan findModuleDelay, TimeSpan findModuleLimit)
{
- logger.LogInformation("FindModuleAsync called");
-
ValueStopwatch watch = ValueStopwatch.StartNew();
using (PeriodicTimer timer = new(findModuleDelay))
{
@@ -172,15 +173,13 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
private async Task LoopAdjustFpsAsync(TimeSpan adjustFpsDelay)
{
- logger.LogInformation("LoopAdjustFpsAsync called");
-
using (PeriodicTimer timer = new(adjustFpsDelay))
{
while (await timer.WaitForNextTickAsync().ConfigureAwait(false))
{
if (!gameProcess.HasExited && fpsAddress != 0)
{
- UnsafeWriteModuleMemory(gameProcess, fpsAddress, launchOptions.TargetFps);
+ UnsafeWriteProcessMemory(gameProcess, fpsAddress, launchOptions.TargetFps);
}
else
{
@@ -192,66 +191,46 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
}
}
- private unsafe void UnsafeTryReadModuleMemoryFindFpsAddress(GameModuleEntryInfo moduleEntryInfo)
+ private unsafe void UnsafeTryReadModuleMemoryFindFpsAddress(in GameModuleEntryInfo moduleEntryInfo)
{
- logger.LogInformation("UnsafeTryReadModuleMemoryFindFpsAddress called");
-
bool readOk = UnsafeReadModulesMemory(gameProcess, moduleEntryInfo, out VirtualMemory localMemory);
Verify.Operation(readOk, "读取内存失败");
using (localMemory)
{
- int offset = FindPatternOffsetImplmentation(localMemory.GetBuffer().Slice(unchecked((int)moduleEntryInfo.UnityPlayer.modBaseSize)));
+ int offset = IndexOfPattern(localMemory.GetBuffer()[(int)moduleEntryInfo.UnityPlayer.modBaseSize..]);
Must.Range(offset > 0, "未匹配到FPS字节");
byte* pLocalMemory = (byte*)localMemory.Pointer;
MODULEENTRY32 unityPlayer = moduleEntryInfo.UnityPlayer;
MODULEENTRY32 userAssembly = moduleEntryInfo.UserAssembly;
- logger.LogInformation("Pattern: {bytes}", BitConverter.ToString(localMemory.GetBuffer().Slice((int)(offset + unityPlayer.modBaseSize), 16).ToArray())); //
-
nuint localMemoryUnityPlayerAddress = (nuint)pLocalMemory;
nuint localMemoryUserAssemblyAddress = localMemoryUnityPlayerAddress + unityPlayer.modBaseSize;
- {
- logger.LogInformation("localMemoryUnityPlayerAddress {addr:X8}", localMemoryUnityPlayerAddress);
- logger.LogInformation("localMemoryUserAssemblyAddress {addr:X8}", localMemoryUserAssemblyAddress);
- logger.LogInformation("memory end at {addr:X8}", localMemoryUserAssemblyAddress + userAssembly.modBaseSize);
- }
nuint rip = localMemoryUserAssemblyAddress + (uint)offset;
rip += *(uint*)(rip + 1) + 5;
rip += *(uint*)(rip + 3) + 7;
+ nuint address = (nuint)userAssembly.modBaseAddr + (rip - localMemoryUserAssemblyAddress);
+ logger.LogInformation("Game Process handle: {handle}", gameProcess.Handle);
+
nuint ptr = 0;
- nuint address = rip - localMemoryUserAssemblyAddress + (nuint)userAssembly.modBaseAddr;
- logger.LogInformation("UnsafeReadModuleMemory at {addr:x8}|{uaAddr:x8}", address, (nuint)userAssembly.modBaseAddr);
- while (ptr == 0)
- {
- // Critial: The pointer here is always returning 0
- // Make this a dead loop.
- if (UnsafeReadProcessMemory(gameProcess, address, out ptr))
- {
- logger.LogInformation("UnsafeReadProcessMemory succeed {addr:x8}", ptr);
- }
- else
- {
- logger.LogInformation("UnsafeReadProcessMemory failed");
- }
+ SpinWait.SpinUntil(() => UnsafeReadProcessMemory(gameProcess, address, out ptr) && ptr != 0);
- Thread.Sleep(100);
- }
-
- logger.LogInformation("ptr {addr}", ptr);
+ logger.LogInformation("UnsafeReadProcessMemory succeed {addr:x8}", ptr);
rip = ptr - (nuint)unityPlayer.modBaseAddr + localMemoryUnityPlayerAddress;
-
+ logger.LogInformation("UnityPlayer addr: {up:x8}, rip addr: {rip:x8}", localMemoryUnityPlayerAddress, rip);
while (*(byte*)rip == 0xE8 || *(byte*)rip == 0xE9)
{
- rip += *(uint*)(rip + 1) + 5;
+ rip += (nuint)(*(int*)(rip + 1) + 5);
+ logger.LogInformation("rip ? addr: {rip:x8}", rip);
}
nuint localMemoryActualAddress = rip + *(uint*)(rip + 2) + 6;
nuint actualOffset = localMemoryActualAddress - localMemoryUnityPlayerAddress;
fpsAddress = (nuint)(unityPlayer.modBaseAddr + actualOffset);
+ logger.LogInformation("UnsafeTryReadModuleMemoryFindFpsAddress finished");
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/IGameFpsUnlocker.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/IGameFpsUnlocker.cs
index 6cc1f46a..cbf843bf 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/IGameFpsUnlocker.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/IGameFpsUnlocker.cs
@@ -12,9 +12,7 @@ internal interface IGameFpsUnlocker
///
/// 异步的解锁帧数限制
///
- /// 每次查找UnityPlayer的延时,推荐100毫秒
- /// 查找UnityPlayer的最大阈值,推荐10000毫秒
- /// 每次循环调整的间隔时间,推荐2000毫秒
+ /// 选项
/// 解锁的结果
- Task UnlockAsync(TimeSpan findModuleDelay, TimeSpan findModuleLimit, TimeSpan adjustFpsDelay);
+ Task UnlockAsync(UnlockTimingOptions options);
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/UnlockTimingOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/UnlockTimingOptions.cs
new file mode 100644
index 00000000..a3a835bf
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/UnlockTimingOptions.cs
@@ -0,0 +1,38 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Service.Game.Unlocker;
+
+///
+/// 解锁时机选项
+///
+internal readonly struct UnlockTimingOptions
+{
+ ///
+ /// 每次查找 Module 的延时
+ ///
+ public readonly TimeSpan FindModuleDelay;
+
+ ///
+ /// 查找 Module 的最大时间阈值
+ ///
+ public readonly TimeSpan FindModuleLimit;
+
+ ///
+ /// 每次循环调整的间隔时间
+ ///
+ public readonly TimeSpan AdjustFpsDelay;
+
+ ///
+ /// 构造一个新的解锁器选项
+ ///
+ /// 每次查找UnityPlayer的延时,推荐100毫秒
+ /// 查找UnityPlayer的最大阈值,推荐10000毫秒
+ /// 每次循环调整的间隔时间,推荐2000毫秒
+ public UnlockTimingOptions(int findModuleDelayMilliseconds, int findModuleLimitMilliseconds, int adjustFpsDelayMilliseconds)
+ {
+ FindModuleDelay = TimeSpan.FromMilliseconds(findModuleDelayMilliseconds);
+ FindModuleLimit = TimeSpan.FromMilliseconds(findModuleLimitMilliseconds);
+ AdjustFpsDelay = TimeSpan.FromMilliseconds(adjustFpsDelayMilliseconds);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
index a1f684f4..565ba4f7 100644
--- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
+++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
@@ -258,7 +258,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive