From e44a20f202ad51925331ce54968b85203f66dc56 Mon Sep 17 00:00:00 2001
From: Lightczx <1686188646@qq.com>
Date: Thu, 25 May 2023 20:50:37 +0800
Subject: [PATCH] Try to fix unlock fps
---
.../DependencyInjection.cs | 3 +-
.../Snap.Hutao/Core/Logging/DebugLogger.cs | 69 +++++++
.../Logging/DebugLoggerFactoryExtensions.cs | 24 +++
.../Core/Logging/DebugLoggerProvider.cs | 22 +++
.../Snap.Hutao/Core/Logging/NullScope.cs | 24 +++
.../Snap.Hutao/Core/Setting/Feature.cs | 55 ++++++
.../Snap.Hutao/Core/Setting/FeatureOptions.cs | 33 ++++
src/Snap.Hutao/Snap.Hutao/NativeMethods.txt | 2 +
.../Service/Game/Unlocker/GameFpsUnlocker.cs | 174 ++++++++++++++----
.../Service/Game/Unlocker/VirtualMemory.cs | 54 ++++++
src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj | 1 -
11 files changed, 428 insertions(+), 33 deletions(-)
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLogger.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLoggerFactoryExtensions.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLoggerProvider.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Logging/NullScope.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Setting/Feature.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Setting/FeatureOptions.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/VirtualMemory.cs
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs
index c28606c0..85199c9c 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
+using Snap.Hutao.Core.Logging;
using Snap.Hutao.Service;
using System.Globalization;
using Windows.Globalization;
@@ -22,7 +23,7 @@ internal static class DependencyInjection
ServiceProvider serviceProvider = new ServiceCollection()
// Microsoft extension
- .AddLogging(builder => builder.AddDebug())
+ .AddLogging(builder => builder.AddUnconditionalDebug())
.AddMemoryCache()
// Hutao extensions
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLogger.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLogger.cs
new file mode 100644
index 00000000..bef2323b
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLogger.cs
@@ -0,0 +1,69 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Diagnostics;
+
+namespace Snap.Hutao.Core.Logging;
+
+///
+/// A logger that writes messages in the debug output window only when a debugger is attached.
+///
+internal sealed class DebugLogger : ILogger
+{
+ private readonly string name;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the logger.
+ public DebugLogger(string name)
+ {
+ this.name = name;
+ }
+
+ ///
+ public IDisposable BeginScope(TState state)
+ where TState : notnull
+ {
+ return NullScope.Instance;
+ }
+
+ ///
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ // If the filter is null, everything is enabled
+ return logLevel != LogLevel.None;
+ }
+
+ ///
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
+ {
+ if (!IsEnabled(logLevel))
+ {
+ return;
+ }
+
+ ArgumentNullException.ThrowIfNull(formatter);
+
+ string message = formatter(state, exception);
+
+ if (string.IsNullOrEmpty(message))
+ {
+ return;
+ }
+
+ message = $"{logLevel}: {message}";
+
+ if (exception != null)
+ {
+ message += Environment.NewLine + Environment.NewLine + exception;
+ }
+
+ DebugWriteLine(message, name);
+ }
+
+ private static void DebugWriteLine(string message, string name)
+ {
+ Debug.WriteLine(message, category: name);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLoggerFactoryExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLoggerFactoryExtensions.cs
new file mode 100644
index 00000000..852a90c9
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLoggerFactoryExtensions.cs
@@ -0,0 +1,24 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace Snap.Hutao.Core.Logging;
+
+///
+/// Extension methods for the class.
+///
+internal static class DebugLoggerFactoryExtensions
+{
+ ///
+ /// Adds a debug logger named 'Debug' to the factory.
+ ///
+ /// The extension method argument.
+ /// builder
+ public static ILoggingBuilder AddUnconditionalDebug(this ILoggingBuilder builder)
+ {
+ builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
+
+ return builder;
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLoggerProvider.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLoggerProvider.cs
new file mode 100644
index 00000000..2cf5dd5e
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLoggerProvider.cs
@@ -0,0 +1,22 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Core.Logging;
+
+///
+/// The provider for the .
+///
+[ProviderAlias("Debug")]
+internal sealed class DebugLoggerProvider : ILoggerProvider
+{
+ ///
+ public ILogger CreateLogger(string name)
+ {
+ return new DebugLogger(name);
+ }
+
+ ///
+ public void Dispose()
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/NullScope.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/NullScope.cs
new file mode 100644
index 00000000..65db7224
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Logging/NullScope.cs
@@ -0,0 +1,24 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Core.Logging;
+
+///
+/// An empty scope without any logic
+///
+internal sealed class NullScope : IDisposable
+{
+ private NullScope()
+ {
+ }
+
+ ///
+ /// 实例
+ ///
+ public static NullScope Instance { get; } = new NullScope();
+
+ ///
+ public void Dispose()
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Setting/Feature.cs b/src/Snap.Hutao/Snap.Hutao/Core/Setting/Feature.cs
new file mode 100644
index 00000000..d30c1f03
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Setting/Feature.cs
@@ -0,0 +1,55 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace Snap.Hutao.Core.Setting;
+
+///
+/// 功能
+///
+internal sealed class Feature : ObservableObject
+{
+ private readonly string displayName;
+ private readonly string description;
+ private readonly string settingKey;
+ private readonly bool defaultValue;
+
+ ///
+ /// 构造一个新的功能
+ ///
+ /// 显示名称
+ /// 描述
+ /// 键
+ /// 默认值
+ public Feature(string displayName, string description, string settingKey, bool defaultValue)
+ {
+ this.displayName = displayName;
+ this.description = description;
+ this.settingKey = settingKey;
+ this.defaultValue = defaultValue;
+ }
+
+ ///
+ /// 显示名称
+ ///
+ public string DisplayName { get => displayName; }
+
+ ///
+ /// 描述
+ ///
+ public string Description { get => description; }
+
+ ///
+ /// 值
+ ///
+ public bool Value
+ {
+ get => LocalSetting.Get(settingKey, defaultValue);
+ set
+ {
+ LocalSetting.Set(settingKey, value);
+ OnPropertyChanged(nameof(Value));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Setting/FeatureOptions.cs b/src/Snap.Hutao/Snap.Hutao/Core/Setting/FeatureOptions.cs
new file mode 100644
index 00000000..46f5e053
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Setting/FeatureOptions.cs
@@ -0,0 +1,33 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections;
+
+namespace Snap.Hutao.Core.Setting;
+
+///
+/// 功能选项
+///
+internal sealed class FeatureOptions : IReadOnlyCollection
+{
+ ///
+ /// 启用实时便笺无感验证
+ ///
+ public Feature IsDailyNoteSlientVerificationEnabled { get; } = new("IsDailyNoteSlientVerificationEnabled", "启用实时便笺无感验证", "IsDailyNoteSlientVerificationEnabled", true);
+
+ ///
+ public int Count { get => 1; }
+
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ // TODO: Use source generator
+ yield return IsDailyNoteSlientVerificationEnabled;
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+}
\ 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 2e9fc3cb..ef8b2b15 100644
--- a/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt
+++ b/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt
@@ -32,7 +32,9 @@ Module32First
Module32Next
ReadProcessMemory
SetEvent
+VirtualAlloc
VirtualAllocEx
+VirtualFree
VirtualFreeEx
WaitForSingleObject
WriteProcessMemory
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 9661578d..d7036464 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs
@@ -5,6 +5,7 @@ using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Win32;
using System.Diagnostics;
using System.Runtime.InteropServices;
+using System.Text;
using Windows.Win32.Foundation;
using Windows.Win32.System.Diagnostics.ToolHelp;
using static Windows.Win32.PInvoke;
@@ -14,12 +15,15 @@ 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
{
private readonly Process gameProcess;
private readonly LaunchOptions launchOptions;
+ private readonly ILogger logger;
private nuint fpsAddress;
private bool isValid = true;
@@ -37,31 +41,43 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
public GameFpsUnlocker(IServiceProvider serviceProvider, Process gameProcess)
{
launchOptions = serviceProvider.GetRequiredService();
+ logger = serviceProvider.GetRequiredService>();
this.gameProcess = gameProcess;
}
///
public async Task UnlockAsync(TimeSpan findModuleDelay, TimeSpan findModuleLimit, TimeSpan adjustFpsDelay)
{
+ logger.LogInformation("UnlockAsync called");
Verify.Operation(isValid, "This Unlocker is invalid");
- MODULEENTRY32 unityPlayer = await FindModuleAsync(findModuleDelay, findModuleLimit).ConfigureAwait(false);
+ GameModuleEntryInfo moduleEntryInfo = await FindModuleAsync(findModuleDelay, findModuleLimit).ConfigureAwait(false);
+ Must.Argument(moduleEntryInfo.HasValue, "读取游戏内存失败");
// Read UnityPlayer.dll
- UnsafeTryReadModuleMemoryFindFpsAddress(unityPlayer);
+ UnsafeTryReadModuleMemoryFindFpsAddress(moduleEntryInfo);
// When player switch between scenes, we have to re adjust the fps
// So we keep a loop here
await LoopAdjustFpsAsync(adjustFpsDelay).ConfigureAwait(false);
}
- private static unsafe bool UnsafeReadModuleMemory(Process process, MODULEENTRY32 entry, out Span memory)
+ private static unsafe bool UnsafeReadModulesMemory(Process process, GameModuleEntryInfo moduleEntryInfo, out VirtualMemory memory)
{
- memory = new byte[entry.modBaseSize];
+ MODULEENTRY32 unityPlayer = moduleEntryInfo.UnityPlayer;
+ MODULEENTRY32 userAssembly = moduleEntryInfo.UserAssembly;
- fixed (byte* lpBuffer = memory)
+ memory = new VirtualMemory(unityPlayer.modBaseSize + userAssembly.modBaseSize);
+ 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);
+ }
+
+ private static unsafe bool UnsafeReadProcessMemory(Process process, nuint offset, out nuint value)
+ {
+ fixed (nuint* pValue = &value)
{
- return ReadProcessMemory((HANDLE)process.Handle, entry.modBaseAddr, lpBuffer, entry.modBaseSize, null);
+ return ReadProcessMemory((HANDLE)process.Handle, (void*)offset, &pValue, unchecked((uint)sizeof(nuint)));
}
}
@@ -93,24 +109,62 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
}
}
- private async Task FindModuleAsync(TimeSpan findModuleDelay, TimeSpan findModuleLimit)
+ private static int FindPatternOffsetImplmentation(ReadOnlySpan memory)
{
- ValueStopwatch watch = ValueStopwatch.StartNew();
+ // E8 ?? ?? ?? ?? 85 C0 7E 07 E8 ?? ?? ?? ?? EB 05
+ int second = 0;
+ ReadOnlySpan secondPart = new byte[] { 0x85, 0xC0, 0x7E, 0x07, 0xE8, };
+ ReadOnlySpan thirdPart = new byte[] { 0xEB, 0x05, };
- while (true)
+ while (second >= 0 && second < memory.Length)
{
- MODULEENTRY32 module = UnsafeFindModule(gameProcess.Id, "UnityPlayer.dll"u8);
- if (!StructMarshal.IsDefault(module))
+ second += memory[second..].IndexOf(secondPart);
+ if (memory[second - 5].Equals(0xE8) && memory.Slice(second + 9, 2).SequenceEqual(thirdPart))
{
- return module;
+ return second - 5;
}
- if (watch.GetElapsedTime() > findModuleLimit)
- {
- break;
- }
+ second += 5;
+ }
- await Task.Delay(findModuleDelay).ConfigureAwait(false);
+ return -1;
+ }
+
+ private unsafe GameModuleEntryInfo UnsafeGetGameModuleEntryInfo(int processId)
+ {
+ logger.LogInformation("UnsafeGetGameModuleEntryInfo called");
+
+ MODULEENTRY32 unityPlayer = UnsafeFindModule(processId, "UnityPlayer.dll"u8);
+ MODULEENTRY32 userAssembly = UnsafeFindModule(processId, "UserAssembly.dll"u8);
+
+ if (unityPlayer.modBaseSize != 0 && userAssembly.modBaseSize != 0)
+ {
+ return new(unityPlayer, userAssembly);
+ }
+
+ return default;
+ }
+
+ private async Task FindModuleAsync(TimeSpan findModuleDelay, TimeSpan findModuleLimit)
+ {
+ logger.LogInformation("FindModuleAsync called");
+
+ ValueStopwatch watch = ValueStopwatch.StartNew();
+ using (PeriodicTimer timer = new(findModuleDelay))
+ {
+ while (await timer.WaitForNextTickAsync().ConfigureAwait(false))
+ {
+ GameModuleEntryInfo moduleInfo = UnsafeGetGameModuleEntryInfo(gameProcess.Id);
+ if (moduleInfo.HasValue)
+ {
+ return moduleInfo;
+ }
+
+ if (watch.GetElapsedTime() > findModuleLimit)
+ {
+ break;
+ }
+ }
}
return default;
@@ -118,6 +172,8 @@ 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))
@@ -136,24 +192,80 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
}
}
- private unsafe void UnsafeTryReadModuleMemoryFindFpsAddress(MODULEENTRY32 unityPlayer)
+ private unsafe void UnsafeTryReadModuleMemoryFindFpsAddress(GameModuleEntryInfo moduleEntryInfo)
{
- bool readOk = UnsafeReadModuleMemory(gameProcess, unityPlayer, out Span memory);
+ logger.LogInformation("UnsafeTryReadModuleMemoryFindFpsAddress called");
+
+ bool readOk = UnsafeReadModulesMemory(gameProcess, moduleEntryInfo, out VirtualMemory localMemory);
Verify.Operation(readOk, "读取内存失败");
- // Find FPS offset
- // 7F 0F jg 0x11
- // 8B 05 ? ? ? ? mov eax, dword ptr[rip+?]
- int adr = memory.IndexOf(new byte[] { 0x7F, 0x0F, 0x8B, 0x05 });
-
- Must.Range(adr >= 0, $"未匹配到FPS字节");
-
- fixed (byte* pSpan = memory)
+ using (localMemory)
{
- int rip = adr + 2;
- int rel = *(int*)(pSpan + rip + 2); // Unsafe.ReadUnaligned(ref image[rip + 2]);
- int ofs = rip + rel + 6;
- fpsAddress = (nuint)(unityPlayer.modBaseAddr + ofs);
+ int offset = FindPatternOffsetImplmentation(localMemory.GetBuffer().Slice(unchecked((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 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");
+ }
+
+ Thread.Sleep(100);
+ }
+
+ logger.LogInformation("ptr {addr}", ptr);
+ rip = ptr - (nuint)unityPlayer.modBaseAddr + localMemoryUnityPlayerAddress;
+
+ while (*(byte*)rip == 0xE8 || *(byte*)rip == 0xE9)
+ {
+ rip += *(uint*)(rip + 1) + 5;
+ }
+
+ nuint localMemoryActualAddress = rip + *(uint*)(rip + 2) + 6;
+ nuint actualOffset = localMemoryActualAddress - localMemoryUnityPlayerAddress;
+ fpsAddress = (nuint)(unityPlayer.modBaseAddr + actualOffset);
+ }
+ }
+
+ private readonly struct GameModuleEntryInfo
+ {
+ public readonly bool HasValue = false;
+ public readonly MODULEENTRY32 UnityPlayer;
+ public readonly MODULEENTRY32 UserAssembly;
+
+ public GameModuleEntryInfo(MODULEENTRY32 unityPlayer, MODULEENTRY32 userAssembly)
+ {
+ HasValue = true;
+ UnityPlayer = unityPlayer;
+ UserAssembly = userAssembly;
}
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/VirtualMemory.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/VirtualMemory.cs
new file mode 100644
index 00000000..945364f3
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/VirtualMemory.cs
@@ -0,0 +1,54 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Windows.Win32.System.Memory;
+using static Windows.Win32.PInvoke;
+
+namespace Snap.Hutao.Service.Game.Unlocker;
+
+///
+/// NativeMemory.AllocZeroed wrapper
+///
+internal readonly unsafe struct VirtualMemory : IDisposable
+{
+ ///
+ /// 缓冲区地址
+ ///
+ public readonly void* Pointer;
+
+ ///
+ /// 长度
+ ///
+ public readonly uint Length;
+
+ ///
+ /// 构造一个新的本地内存
+ ///
+ /// 长度
+ public unsafe VirtualMemory(uint dwSize)
+ {
+ Length = dwSize;
+ VIRTUAL_ALLOCATION_TYPE commitAndReserve = VIRTUAL_ALLOCATION_TYPE.MEM_COMMIT | VIRTUAL_ALLOCATION_TYPE.MEM_RESERVE;
+ Pointer = VirtualAlloc(default, dwSize, commitAndReserve, PAGE_PROTECTION_FLAGS.PAGE_READWRITE);
+ }
+
+ public static unsafe implicit operator Span(VirtualMemory memory)
+ {
+ return memory.GetBuffer();
+ }
+
+ ///
+ /// 获取缓冲区
+ ///
+ /// 内存
+ public unsafe Span GetBuffer()
+ {
+ return new Span(Pointer, (int)Length);
+ }
+
+ ///
+ public void Dispose()
+ {
+ VirtualFree(Pointer, 0, VIRTUAL_FREE_TYPE.MEM_RELEASE);
+ }
+}
\ 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 a093e214..a1f684f4 100644
--- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
+++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
@@ -253,7 +253,6 @@
-
all
runtime; build; native; contentfiles; analyzers; buildtransitive