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) {