From be4fa571cd6f6dee556088aa01205b746906837c Mon Sep 17 00:00:00 2001
From: Lightczx <1686188646@qq.com>
Date: Wed, 7 Jun 2023 15:52:51 +0800
Subject: [PATCH] fix unlock fps
---
src/Snap.Hutao/Snap.Hutao/App.xaml | 2 +-
...StreamCopyState.cs => StreamCopyStatus.cs} | 4 +-
.../Snap.Hutao/Core/IO/StreamCopyWorker.cs | 2 +-
src/Snap.Hutao/Snap.Hutao/NativeMethods.txt | 3 +
.../Resource/Localization/SH.Designer.cs | 54 +++++
.../Snap.Hutao/Resource/Localization/SH.resx | 15 ++
.../Snap.Hutao/Service/Game/GameService.cs | 2 +-
.../Snap.Hutao/Service/Game/ProcessInterop.cs | 6 +-
.../Service/Game/Unlocker/FindModuleResult.cs | 30 +++
.../Service/Game/Unlocker/GameFpsUnlocker.cs | 188 +++++++++++-------
.../Service/Game/Unlocker/IGameFpsUnlocker.cs | 4 +-
.../Service/Game/Unlocker/UnlockerStatus.cs | 37 ++++
.../View/Control/StatisticsCard.xaml | 29 ++-
.../Int32ToGradientColorConverter.cs | 82 ++++++++
.../Int32ToVisibilityRevertConverter.cs | 2 +-
.../Snap.Hutao/View/Page/AchievementPage.xaml | 16 +-
.../Snap.Hutao/ViewModel/WelcomeViewModel.cs | 4 +-
17 files changed, 392 insertions(+), 88 deletions(-)
rename src/Snap.Hutao/Snap.Hutao/Core/IO/{StreamCopyState.cs => StreamCopyStatus.cs} (86%)
create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/FindModuleResult.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/UnlockerStatus.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/View/Converter/Int32ToGradientColorConverter.cs
diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml b/src/Snap.Hutao/Snap.Hutao/App.xaml
index 3a4da8b0..050eba25 100644
--- a/src/Snap.Hutao/Snap.Hutao/App.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/App.xaml
@@ -44,7 +44,7 @@
2
212
- 268
+ 284
268
180
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyState.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyStatus.cs
similarity index 86%
rename from src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyState.cs
rename to src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyStatus.cs
index f830d955..db7e1fa2 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyState.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyStatus.cs
@@ -6,14 +6,14 @@ namespace Snap.Hutao.Core.IO;
///
/// 流复制状态
///
-internal sealed class StreamCopyState
+internal sealed class StreamCopyStatus
{
///
/// 构造一个新的流复制状态
///
/// 已复制字节
/// 总字节数
- public StreamCopyState(long bytesCopied, long totalBytes)
+ public StreamCopyStatus(long bytesCopied, long totalBytes)
{
BytesCopied = bytesCopied;
TotalBytes = totalBytes;
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyWorker.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyWorker.cs
index 3afcdc06..b32ef249 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyWorker.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/StreamCopyWorker.cs
@@ -38,7 +38,7 @@ internal sealed class StreamCopyWorker
///
/// 进度
/// 任务
- public async Task CopyAsync(IProgress progress)
+ public async Task CopyAsync(IProgress progress)
{
long totalBytesRead = 0;
int bytesRead;
diff --git a/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt b/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt
index e01ab1c5..8758bcd8 100644
--- a/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt
+++ b/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt
@@ -17,6 +17,9 @@ CreateRemoteThread
CreateToolhelp32Snapshot
GetModuleHandleW
GetProcAddress
+K32EnumProcessModules
+K32GetModuleBaseName
+K32GetModuleInformation
Module32First
Module32Next
ReadProcessMemory
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 24967897..bfd68fc2 100644
--- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs
@@ -1428,6 +1428,51 @@ namespace Snap.Hutao.Resource.Localization {
}
}
+ ///
+ /// 查找类似 在查找必要的模块时遇到问题:无法读取任何模块,可能是保护驱动已经加载完成 的本地化字符串。
+ ///
+ internal static string ServiceGameUnlockerFindModuleNoModuleFound {
+ get {
+ return ResourceManager.GetString("ServiceGameUnlockerFindModuleNoModuleFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 在查找必要的模块时遇到问题:查找模块超时 的本地化字符串。
+ ///
+ internal static string ServiceGameUnlockerFindModuleTimeLimitExeeded {
+ get {
+ return ResourceManager.GetString("ServiceGameUnlockerFindModuleTimeLimitExeeded", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 在匹配内存时遇到问题:无法匹配到期望的内容 的本地化字符串。
+ ///
+ internal static string ServiceGameUnlockerInterestedPatternNotFound {
+ get {
+ return ResourceManager.GetString("ServiceGameUnlockerInterestedPatternNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 在读取必要的模块内存时遇到问题:无法将模块内存复制到指定位置 的本地化字符串。
+ ///
+ internal static string ServiceGameUnlockerReadModuleMemoryCopyVirtualMemoryFailed {
+ get {
+ return ResourceManager.GetString("ServiceGameUnlockerReadModuleMemoryCopyVirtualMemoryFailed", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 在读取游戏进程内存时遇到问题:无法读取到指定地址的有效值 的本地化字符串。
+ ///
+ internal static string ServiceGameUnlockerReadProcessMemoryPointerAddressFailed {
+ get {
+ return ResourceManager.GetString("ServiceGameUnlockerReadProcessMemoryPointerAddressFailed", resourceCulture);
+ }
+ }
+
///
/// 查找类似 无法找到缓存的元数据文件 的本地化字符串。
///
@@ -1572,6 +1617,15 @@ namespace Snap.Hutao.Resource.Localization {
}
}
+ ///
+ /// 查找类似 选择账号或直接启动 的本地化字符串。
+ ///
+ internal static string ViewCardLaunchGameSelectAccountPlaceholder {
+ get {
+ return ResourceManager.GetString("ViewCardLaunchGameSelectAccountPlaceholder", resourceCulture);
+ }
+ }
+
///
/// 查找类似 等级 的本地化字符串。
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
index bdc1f58c..d2fe36dd 100644
--- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
+++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
@@ -2019,4 +2019,19 @@
选择账号或直接启动
+
+ 在查找必要的模块时遇到问题:无法读取任何模块,可能是保护驱动已经加载完成
+
+
+ 在查找必要的模块时遇到问题:查找模块超时
+
+
+ 在匹配内存时遇到问题:无法匹配到期望的内容
+
+
+ 在读取必要的模块内存时遇到问题:无法将模块内存复制到指定位置
+
+
+ 在读取游戏进程内存时遇到问题:无法读取到指定地址的有效值
+
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs
index e538100d..9944b736 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs
@@ -289,7 +289,7 @@ internal sealed partial class GameService : IGameService
if (isAdvancedOptionsAllowed && launchOptions.UnlockFps)
{
- await ProcessInterop.UnlockFpsAsync(serviceProvider, game).ConfigureAwait(false);
+ await ProcessInterop.UnlockFpsAsync(serviceProvider, game, default).ConfigureAwait(false);
}
else
{
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/ProcessInterop.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/ProcessInterop.cs
index a3ab702c..f111b770 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/ProcessInterop.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/ProcessInterop.cs
@@ -54,12 +54,14 @@ internal static class ProcessInterop
///
/// 服务提供器
/// 游戏进程
+ /// 取消令牌
/// 任务
- public static Task UnlockFpsAsync(IServiceProvider serviceProvider, Process game)
+ public static Task UnlockFpsAsync(IServiceProvider serviceProvider, Process game, CancellationToken token)
{
IGameFpsUnlocker unlocker = serviceProvider.CreateInstance(game);
UnlockTimingOptions options = new(100, 20000, 3000);
- return unlocker.UnlockAsync(options);
+ Progress progress = new(); // TODO: do something.
+ return unlocker.UnlockAsync(options, progress, token);
}
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/FindModuleResult.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/FindModuleResult.cs
new file mode 100644
index 00000000..0c9b4afe
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/FindModuleResult.cs
@@ -0,0 +1,30 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Service.Game.Unlocker;
+
+///
+/// 查找模块结果
+///
+internal enum FindModuleResult
+{
+ ///
+ /// 成功
+ ///
+ Ok,
+
+ ///
+ /// 超时
+ ///
+ TimeLimitExeeded,
+
+ ///
+ /// 模块尚未加载
+ ///
+ ModuleNotLoaded,
+
+ ///
+ /// 没有模块,保护驱动已加载,无法读取
+ ///
+ NoModuleFound,
+}
\ No newline at end of file
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 acdb3a52..2d22f8e8 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs
@@ -4,13 +4,11 @@
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;
using Windows.Win32.System.Diagnostics.ToolHelp;
+using Windows.Win32.System.ProcessStatus;
using static Windows.Win32.PInvoke;
namespace Snap.Hutao.Service.Game.Unlocker;
@@ -26,8 +24,7 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
private readonly LaunchOptions launchOptions;
private readonly ILogger logger;
- private nuint fpsAddress;
- private bool isValid = true;
+ private UnlockerStatus status = new();
///
/// 构造一个新的 对象,
@@ -47,30 +44,31 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
}
///
- public async Task UnlockAsync(UnlockTimingOptions options)
+ public async Task UnlockAsync(UnlockTimingOptions options, IProgress progress, CancellationToken token = default)
{
- Verify.Operation(isValid, "This Unlocker is invalid");
+ Verify.Operation(status.IsUnlockerValid, "This Unlocker is invalid");
- GameModuleEntryInfo moduleEntryInfo = await FindModuleAsync(options.FindModuleDelay, options.FindModuleLimit).ConfigureAwait(false);
- Must.Argument(moduleEntryInfo.HasValue, "读取游戏内存失败");
+ (FindModuleResult result, GameModule moduleEntryInfo) = await FindModuleAsync(options.FindModuleDelay, options.FindModuleLimit).ConfigureAwait(false);
+ Verify.Operation(result != FindModuleResult.TimeLimitExeeded, SH.ServiceGameUnlockerFindModuleTimeLimitExeeded);
+ Verify.Operation(result != FindModuleResult.NoModuleFound, SH.ServiceGameUnlockerFindModuleNoModuleFound);
// Read UnityPlayer.dll
- UnsafeTryReadModuleMemoryFindFpsAddress(moduleEntryInfo);
+ UnsafeFindFpsAddress(moduleEntryInfo);
// When player switch between scenes, we have to re adjust the fps
// So we keep a loop here
- await LoopAdjustFpsAsync(options.AdjustFpsDelay).ConfigureAwait(false);
+ await LoopAdjustFpsAsync(options.AdjustFpsDelay, progress, token).ConfigureAwait(false);
}
- private static unsafe bool UnsafeReadModulesMemory(Process process, in GameModuleEntryInfo moduleEntryInfo, out VirtualMemory memory)
+ private static unsafe bool UnsafeReadModulesMemory(Process process, in GameModule moduleEntryInfo, out VirtualMemory memory)
{
- MODULEENTRY32 unityPlayer = moduleEntryInfo.UnityPlayer;
- MODULEENTRY32 userAssembly = moduleEntryInfo.UserAssembly;
+ ref readonly Module unityPlayer = ref moduleEntryInfo.UnityPlayer;
+ ref readonly Module userAssembly = ref moduleEntryInfo.UserAssembly;
- memory = new VirtualMemory(unityPlayer.modBaseSize + userAssembly.modBaseSize);
+ memory = new VirtualMemory(unityPlayer.Size + userAssembly.Size);
byte* lpBuffer = (byte*)memory.Pointer;
- return ReadProcessMemory((HANDLE)process.Handle, unityPlayer.modBaseAddr, lpBuffer, unityPlayer.modBaseSize, default)
- && ReadProcessMemory((HANDLE)process.Handle, userAssembly.modBaseAddr, lpBuffer + unityPlayer.modBaseSize, userAssembly.modBaseSize, default);
+ return ReadProcessMemory((HANDLE)process.Handle, (void*)unityPlayer.Address, lpBuffer, unityPlayer.Size, default)
+ && ReadProcessMemory((HANDLE)process.Handle, (void*)userAssembly.Address, lpBuffer + unityPlayer.Size, userAssembly.Size, default);
}
private static unsafe bool UnsafeReadProcessMemory(Process process, nuint baseAddress, out nuint value)
@@ -79,40 +77,66 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
bool result = ReadProcessMemory((HANDLE)process.Handle, (void*)baseAddress, (byte*)&temp, 8, default);
if (!result)
{
- ThrowHelper.InvalidOperation("读取进程内存失败", null);
+ ThrowHelper.InvalidOperation(SH.ServiceGameUnlockerReadProcessMemoryPointerAddressFailed, null);
}
value = (nuint)temp;
return result;
}
- private static unsafe bool UnsafeWriteProcessMemory(Process process, nuint baseAddress, int write)
+ private static unsafe bool UnsafeWriteProcessMemory(Process process, nuint baseAddress, int value)
{
- return WriteProcessMemory((HANDLE)process.Handle, (void*)baseAddress, &write, sizeof(int), default);
+ return WriteProcessMemory((HANDLE)process.Handle, (void*)baseAddress, &value, sizeof(int), default);
}
- private static unsafe MODULEENTRY32 UnsafeFindModule(int processId, in ReadOnlySpan moduleName)
+ private static unsafe FindModuleResult UnsafeTryFindModule(in HANDLE hProcess, in ReadOnlySpan moduleName, out Module module)
{
- CREATE_TOOLHELP_SNAPSHOT_FLAGS flags = CREATE_TOOLHELP_SNAPSHOT_FLAGS.TH32CS_SNAPMODULE | CREATE_TOOLHELP_SNAPSHOT_FLAGS.TH32CS_SNAPMODULE32;
- HANDLE snapshot = CreateToolhelp32Snapshot(flags, (uint)processId);
- try
+ HMODULE[] buffer = new HMODULE[128];
+ uint actualSize = 0;
+ fixed (HMODULE* pBuffer = buffer)
{
- Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError());
- foreach (MODULEENTRY32 entry in StructMarshal.EnumerateModuleEntry32(snapshot))
+ if (!K32EnumProcessModules(hProcess, pBuffer, unchecked((uint)(buffer.Length * sizeof(HMODULE))), out actualSize))
{
- ReadOnlySpan szModuleNameLocal = MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)&entry.szModule);
- if (entry.th32ProcessID == processId && szModuleNameLocal.SequenceEqual(moduleName))
+ Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError());
+ }
+ }
+
+ if (actualSize == 0)
+ {
+ module = default!;
+ return FindModuleResult.NoModuleFound;
+ }
+
+ Span modules = new(buffer, 0, unchecked((int)(actualSize / sizeof(HMODULE))));
+
+ foreach (ref readonly HMODULE hModule in modules)
+ {
+ char[] baseName = new char[256];
+ fixed (char* lpBaseName = baseName)
+ {
+ if (K32GetModuleBaseName(hProcess, hModule, lpBaseName, 256) == 0)
{
- return entry;
+ continue;
+ }
+
+ ReadOnlySpan szModuleName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(lpBaseName);
+ if (!szModuleName.SequenceEqual(moduleName))
+ {
+ continue;
}
}
- return default;
- }
- finally
- {
- CloseHandle(snapshot);
+ if (!K32GetModuleInformation(hProcess, hModule, out MODULEINFO moduleInfo, unchecked((uint)sizeof(MODULEINFO))))
+ {
+ continue;
+ }
+
+ module = new((nuint)moduleInfo.lpBaseOfDll, moduleInfo.SizeOfImage);
+ return FindModuleResult.Ok;
}
+
+ module = default;
+ return FindModuleResult.ModuleNotLoaded;
}
private static int IndexOfPattern(in ReadOnlySpan memory)
@@ -136,30 +160,43 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
return -1;
}
- private static unsafe GameModuleEntryInfo UnsafeGetGameModuleEntryInfo(int processId)
+ private static unsafe FindModuleResult UnsafeGetGameModuleInfo(in HANDLE hProcess, out GameModule info)
{
- MODULEENTRY32 unityPlayer = UnsafeFindModule(processId, "UnityPlayer.dll"u8);
- MODULEENTRY32 userAssembly = UnsafeFindModule(processId, "UserAssembly.dll"u8);
+ FindModuleResult unityPlayerResult = UnsafeTryFindModule(hProcess, "UnityPlayer.dll", out Module unityPlayer);
+ FindModuleResult userAssemblyResult = UnsafeTryFindModule(hProcess, "UserAssembly.dll", out Module userAssembly);
- if (unityPlayer.modBaseSize != 0 && userAssembly.modBaseSize != 0)
+ if (unityPlayerResult == FindModuleResult.Ok && userAssemblyResult == FindModuleResult.Ok)
{
- return new(unityPlayer, userAssembly);
+ info = new(unityPlayer, userAssembly);
+ return FindModuleResult.Ok;
}
- return default;
+ if (unityPlayerResult == FindModuleResult.NoModuleFound || userAssemblyResult == FindModuleResult.NoModuleFound)
+ {
+ info = default;
+ return FindModuleResult.NoModuleFound;
+ }
+
+ info = default;
+ return FindModuleResult.ModuleNotLoaded;
}
- private async Task FindModuleAsync(TimeSpan findModuleDelay, TimeSpan findModuleLimit)
+ private async Task> FindModuleAsync(TimeSpan findModuleDelay, TimeSpan findModuleLimit)
{
ValueStopwatch watch = ValueStopwatch.StartNew();
using (PeriodicTimer timer = new(findModuleDelay))
{
while (await timer.WaitForNextTickAsync().ConfigureAwait(false))
{
- GameModuleEntryInfo moduleInfo = UnsafeGetGameModuleEntryInfo(gameProcess.Id);
- if (moduleInfo.HasValue)
+ FindModuleResult result = UnsafeGetGameModuleInfo((HANDLE)gameProcess.Handle, out GameModule gameModule);
+ if (result == FindModuleResult.Ok)
{
- return moduleInfo;
+ return new(FindModuleResult.Ok, gameModule);
+ }
+
+ if (result == FindModuleResult.NoModuleFound)
+ {
+ return new(FindModuleResult.NoModuleFound, default);
}
if (watch.GetElapsedTime() > findModuleLimit)
@@ -169,83 +206,94 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
}
}
- return default;
+ return new(FindModuleResult.TimeLimitExeeded, default);
}
- private async Task LoopAdjustFpsAsync(TimeSpan adjustFpsDelay)
+ private async Task LoopAdjustFpsAsync(TimeSpan adjustFpsDelay, IProgress progress, CancellationToken token)
{
using (PeriodicTimer timer = new(adjustFpsDelay))
{
- while (await timer.WaitForNextTickAsync().ConfigureAwait(false))
+ while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false))
{
- if (!gameProcess.HasExited && fpsAddress != 0)
+ if (!gameProcess.HasExited && status.FpsAddress != 0U)
{
- UnsafeWriteProcessMemory(gameProcess, fpsAddress, launchOptions.TargetFps);
+ UnsafeWriteProcessMemory(gameProcess, status.FpsAddress, launchOptions.TargetFps);
+ progress.Report(status);
}
else
{
- isValid = false;
- fpsAddress = 0;
+ status.IsUnlockerValid = false;
+ status.FpsAddress = 0;
+ progress.Report(status);
return;
}
}
}
}
- private unsafe void UnsafeTryReadModuleMemoryFindFpsAddress(in GameModuleEntryInfo moduleEntryInfo)
+ private unsafe void UnsafeFindFpsAddress(in GameModule moduleEntryInfo)
{
bool readOk = UnsafeReadModulesMemory(gameProcess, moduleEntryInfo, out VirtualMemory localMemory);
- Verify.Operation(readOk, "读取内存失败");
+ Verify.Operation(readOk, SH.ServiceGameUnlockerReadModuleMemoryCopyVirtualMemoryFailed);
using (localMemory)
{
- int offset = IndexOfPattern(localMemory.GetBuffer()[(int)moduleEntryInfo.UnityPlayer.modBaseSize..]);
- Must.Range(offset >= 0, "未匹配到FPS字节");
+ int offset = IndexOfPattern(localMemory.GetBuffer()[(int)moduleEntryInfo.UnityPlayer.Size..]);
+ Must.Range(offset >= 0, SH.ServiceGameUnlockerInterestedPatternNotFound);
byte* pLocalMemory = (byte*)localMemory.Pointer;
- MODULEENTRY32 unityPlayer = moduleEntryInfo.UnityPlayer;
- MODULEENTRY32 userAssembly = moduleEntryInfo.UserAssembly;
+ ref readonly Module unityPlayer = ref moduleEntryInfo.UnityPlayer;
+ ref readonly Module userAssembly = ref moduleEntryInfo.UserAssembly;
nuint localMemoryUnityPlayerAddress = (nuint)pLocalMemory;
- nuint localMemoryUserAssemblyAddress = localMemoryUnityPlayerAddress + unityPlayer.modBaseSize;
+ nuint localMemoryUserAssemblyAddress = localMemoryUnityPlayerAddress + unityPlayer.Size;
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 address = userAssembly.Address + (rip - localMemoryUserAssemblyAddress);
nuint ptr = 0;
SpinWait.SpinUntil(() => UnsafeReadProcessMemory(gameProcess, address, out ptr) && ptr != 0);
- 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);
+ rip = ptr - unityPlayer.Address + localMemoryUnityPlayerAddress;
while (*(byte*)rip == 0xE8 || *(byte*)rip == 0xE9)
{
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");
+ status.FpsAddress = unityPlayer.Address + actualOffset;
}
}
- private readonly struct GameModuleEntryInfo
+ private readonly struct GameModule
{
public readonly bool HasValue = false;
- public readonly MODULEENTRY32 UnityPlayer;
- public readonly MODULEENTRY32 UserAssembly;
+ public readonly Module UnityPlayer;
+ public readonly Module UserAssembly;
- public GameModuleEntryInfo(MODULEENTRY32 unityPlayer, MODULEENTRY32 userAssembly)
+ public GameModule(in Module unityPlayer, in Module userAssembly)
{
HasValue = true;
UnityPlayer = unityPlayer;
UserAssembly = userAssembly;
}
}
+
+ private readonly struct Module
+ {
+ public readonly bool HasValue = false;
+ public readonly nuint Address;
+ public readonly uint Size;
+
+ public Module(nuint address, uint size)
+ {
+ HasValue = true;
+ Address = address;
+ Size = size;
+ }
+ }
}
\ No newline at end of file
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 cbf843bf..c219d6b6 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/IGameFpsUnlocker.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/IGameFpsUnlocker.cs
@@ -13,6 +13,8 @@ internal interface IGameFpsUnlocker
/// 异步的解锁帧数限制
///
/// 选项
+ /// 进度
+ /// 取消令牌
/// 解锁的结果
- Task UnlockAsync(UnlockTimingOptions options);
+ Task UnlockAsync(UnlockTimingOptions options, IProgress progress, CancellationToken token = default);
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/UnlockerStatus.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/UnlockerStatus.cs
new file mode 100644
index 00000000..e13e3d35
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/UnlockerStatus.cs
@@ -0,0 +1,37 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Core.Abstraction;
+
+namespace Snap.Hutao.Service.Game.Unlocker;
+
+///
+/// 解锁状态
+///
+internal sealed class UnlockerStatus : ICloneable
+{
+ ///
+ /// 状态描述
+ ///
+ public string Description { get; set; } = default!;
+
+ ///
+ /// 查找模块状态
+ ///
+ public FindModuleResult FindModuleState { get; set; }
+
+ ///
+ /// 当前解锁器是否有效
+ ///
+ public bool IsUnlockerValid { get; set; } = true;
+
+ ///
+ /// 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/Control/StatisticsCard.xaml b/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml
index acc02f78..e8f6055d 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml
@@ -6,10 +6,12 @@
xmlns:cwucont="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:shc="using:Snap.Hutao.Control"
xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shcp="using:Snap.Hutao.Control.Panel"
- xmlns:shvc="using:Snap.Hutao.View.Control"
+ xmlns:shvconv="using:Snap.Hutao.View.Converter"
+ xmlns:shvcoont="using:Snap.Hutao.View.Control"
xmlns:shvg="using:Snap.Hutao.ViewModel.GachaLog"
d:DataContext="{d:DesignInstance shvg:TypedWishSummary}"
mc:Ignorable="d">
@@ -19,15 +21,35 @@
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
-
+/// Int32 转 色阶颜色
+///
+internal sealed class Int32ToGradientColorConverter : DependencyObject, IValueConverter
+{
+ private static readonly DependencyProperty MaximumProperty = Property.Depend(nameof(Maximum), StructMarshal.Color(0xFFFF4949));
+ private static readonly DependencyProperty MinimumProperty = Property.Depend(nameof(Minimum), StructMarshal.Color(0xFF48FF7A));
+ private static readonly DependencyProperty MaximumValueProperty = Property.Depend(nameof(MaximumValue), 90);
+ private static readonly DependencyProperty MinimumValueProperty = Property.Depend(nameof(MinimumValue), 1);
+
+ ///
+ /// 最小颜色
+ ///
+ public Color Minimum
+ {
+ get => (Color)GetValue(MinimumProperty);
+ set => SetValue(MinimumProperty, value);
+ }
+
+ ///
+ /// 最大颜色
+ ///
+ public Color Maximum
+ {
+ get => (Color)GetValue(MaximumProperty);
+ set => SetValue(MaximumProperty, value);
+ }
+
+ ///
+ /// 最小值
+ ///
+ public int MinimumValue
+ {
+ get => (int)GetValue(MinimumValueProperty);
+ set => SetValue(MinimumValueProperty, value);
+ }
+
+ ///
+ /// 最大值
+ ///
+ public int MaximumValue
+ {
+ get => (int)GetValue(MaximumValueProperty);
+ set => SetValue(MaximumValueProperty, value);
+ }
+
+ ///
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ double n = (value != null ? (int)value : MinimumValue) - MinimumValue;
+ int step = MaximumValue - MinimumValue;
+ double a = Minimum.A + ((Maximum.A - Minimum.A) * n / step);
+ double r = Minimum.R + ((Maximum.R - Minimum.R) * n / step);
+ double g = Minimum.G + ((Maximum.G - Minimum.G) * n / step);
+ double b = Minimum.B + ((Maximum.B - Minimum.B) * n / step);
+
+ Unsafe.SkipInit(out Color color);
+ color.A = (byte)a;
+ color.R = (byte)r;
+ color.G = (byte)g;
+ color.B = (byte)b;
+ return color;
+ }
+
+ ///
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Converter/Int32ToVisibilityRevertConverter.cs b/src/Snap.Hutao/Snap.Hutao/View/Converter/Int32ToVisibilityRevertConverter.cs
index 07544085..17966ff7 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Converter/Int32ToVisibilityRevertConverter.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Converter/Int32ToVisibilityRevertConverter.cs
@@ -15,7 +15,7 @@ internal sealed class Int32ToVisibilityRevertConverter : IValueConverter
///
public object Convert(object value, Type targetType, object parameter, string language)
{
- return (int)value == 0 ? Visibility.Visible : Visibility.Collapsed;
+ return value != null && (int)value == 0 ? Visibility.Visible : Visibility.Collapsed;
}
///
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml
index 00999653..81bcd803 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml
@@ -41,13 +41,15 @@
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{Binding FinishDescription}"/>
-
-
+
+
+
+
@@ -157,7 +164,7 @@
@@ -229,6 +236,7 @@
+
progress;
+ private readonly Progress progress;
private string description = SH.ViewModelWelcomeDownloadSummaryDefault;
private double progressValue;
private long updateCount;
@@ -196,7 +196,7 @@ internal sealed partial class WelcomeViewModel : ObservableObject
}
}
- private void UpdateProgressStatus(StreamCopyState status)
+ private void UpdateProgressStatus(StreamCopyStatus status)
{
if (Interlocked.Increment(ref updateCount) % 40 == 0)
{