mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Merge pull request #1816 from DGP-Studio/feat/unlockfps-loadexe
fix #1814
This commit is contained in:
@@ -1,10 +1,6 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Snap.Hutao.Core.ExceptionService;
|
|
||||||
using Snap.Hutao.Win32.Foundation;
|
|
||||||
using static Snap.Hutao.Win32.Kernel32;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Service.Game.Unlocker;
|
namespace Snap.Hutao.Service.Game.Unlocker;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -19,19 +15,34 @@ internal static class GameFpsAddress
|
|||||||
|
|
||||||
public static unsafe void UnsafeFindFpsAddress(GameFpsUnlockerContext context, in RequiredRemoteModule remoteModule, in RequiredLocalModule localModule)
|
public static unsafe void UnsafeFindFpsAddress(GameFpsUnlockerContext context, in RequiredRemoteModule remoteModule, in RequiredLocalModule localModule)
|
||||||
{
|
{
|
||||||
int offsetToUserAssembly = IndexOfPattern(localModule.UserAssembly.AsSpan());
|
Span<byte> executableSpan = localModule.Executable.AsSpan();
|
||||||
HutaoException.ThrowIfNot(offsetToUserAssembly >= 0, SH.ServiceGameUnlockerInterestedPatternNotFound);
|
int offsetToExecutable = 0;
|
||||||
|
nuint localVirtualAddress = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
int index = IndexOfPattern(executableSpan[offsetToExecutable..], out int patternLength);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
nuint rip = localModule.UserAssembly.Address + (uint)offsetToUserAssembly;
|
offsetToExecutable += index;
|
||||||
rip += 5U;
|
|
||||||
rip += (nuint)(*(int*)(rip + 2U) + 6);
|
|
||||||
|
|
||||||
nuint remoteVirtualAddress = remoteModule.UserAssembly.Address + (rip - localModule.UserAssembly.Address);
|
nuint rip = localModule.Executable.Address + (uint)offsetToExecutable;
|
||||||
|
rip += 5U;
|
||||||
|
rip += (nuint)(*(int*)(rip + 1U) + 5);
|
||||||
|
|
||||||
nuint ptr = 0;
|
if (*(byte*)rip is ASM_JMP)
|
||||||
SpinWait.SpinUntil(() => UnsafeReadProcessMemory(context.AllAccess, remoteVirtualAddress, out ptr) && ptr != 0);
|
{
|
||||||
|
localVirtualAddress = rip;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
nuint localVirtualAddress = ptr - remoteModule.UnityPlayer.Address + localModule.UnityPlayer.Address;
|
offsetToExecutable += patternLength;
|
||||||
|
}
|
||||||
|
while (true);
|
||||||
|
|
||||||
|
ArgumentOutOfRangeException.ThrowIfZero(localVirtualAddress);
|
||||||
|
|
||||||
while (*(byte*)localVirtualAddress is ASM_CALL or ASM_JMP)
|
while (*(byte*)localVirtualAddress is ASM_CALL or ASM_JMP)
|
||||||
{
|
{
|
||||||
@@ -39,22 +50,15 @@ internal static class GameFpsAddress
|
|||||||
}
|
}
|
||||||
|
|
||||||
localVirtualAddress += *(uint*)(localVirtualAddress + 2) + 6;
|
localVirtualAddress += *(uint*)(localVirtualAddress + 2) + 6;
|
||||||
nuint relativeVirtualAddress = localVirtualAddress - localModule.UnityPlayer.Address;
|
nuint relativeVirtualAddress = localVirtualAddress - localModule.Executable.Address;
|
||||||
context.FpsAddress = remoteModule.UnityPlayer.Address + relativeVirtualAddress;
|
context.FpsAddress = remoteModule.Executable.Address + relativeVirtualAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int IndexOfPattern(in ReadOnlySpan<byte> memory)
|
private static int IndexOfPattern(in ReadOnlySpan<byte> span, out int patternLength)
|
||||||
{
|
{
|
||||||
// B9 3C 00 00 00 FF 15
|
// B9 3C 00 00 00 E8
|
||||||
ReadOnlySpan<byte> part = [0xB9, 0x3C, 0x00, 0x00, 0x00, 0xFF, 0x15];
|
ReadOnlySpan<byte> part = [0xB9, 0x3C, 0x00, 0x00, 0x00, 0xE8];
|
||||||
return memory.IndexOf(part);
|
patternLength = part.Length;
|
||||||
}
|
return span.IndexOf(part);
|
||||||
|
|
||||||
private static unsafe bool UnsafeReadProcessMemory(HANDLE hProcess, nuint baseAddress, out nuint value)
|
|
||||||
{
|
|
||||||
value = 0;
|
|
||||||
bool result = ReadProcessMemory(hProcess, (void*)baseAddress, ref value, out _);
|
|
||||||
HutaoException.ThrowIfNot(result, SH.ServiceGameUnlockerReadProcessMemoryPointerAddressFailed);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,12 +51,9 @@ internal abstract class GameFpsUnlocker : IGameFpsUnlocker
|
|||||||
|
|
||||||
private static RequiredLocalModule LoadRequiredLocalModule(GameFileSystem gameFileSystem)
|
private static RequiredLocalModule LoadRequiredLocalModule(GameFileSystem gameFileSystem)
|
||||||
{
|
{
|
||||||
string gameFoler = gameFileSystem.GameDirectory;
|
|
||||||
string dataFoler = gameFileSystem.DataDirectory;
|
|
||||||
LOAD_LIBRARY_FLAGS flags = LOAD_LIBRARY_FLAGS.LOAD_LIBRARY_AS_IMAGE_RESOURCE;
|
LOAD_LIBRARY_FLAGS flags = LOAD_LIBRARY_FLAGS.LOAD_LIBRARY_AS_IMAGE_RESOURCE;
|
||||||
HMODULE unityPlayerAddress = LoadLibraryExW(System.IO.Path.Combine(gameFoler, "UnityPlayer.dll"), default, flags);
|
HMODULE executaleAddress = LoadLibraryExW(gameFileSystem.GameFilePath, default, flags);
|
||||||
HMODULE userAssemblyAddress = LoadLibraryExW(System.IO.Path.Combine(dataFoler, "Native", "UserAssembly.dll"), default, flags);
|
|
||||||
|
|
||||||
return new(unityPlayerAddress, userAssemblyAddress);
|
return new(executaleAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,16 +42,15 @@ internal static class GameProcessModule
|
|||||||
|
|
||||||
private static FindModuleResult UnsafeGetGameModuleInfo(in HANDLE hProcess, out RequiredRemoteModule info)
|
private static FindModuleResult UnsafeGetGameModuleInfo(in HANDLE hProcess, out RequiredRemoteModule info)
|
||||||
{
|
{
|
||||||
FindModuleResult unityPlayerResult = UnsafeFindModule(hProcess, "UnityPlayer.dll", out Module unityPlayer);
|
FindModuleResult result = UnsafeFindModule(hProcess, GameConstants.YuanShenFileName, GameConstants.GenshinImpactFileName, out Module executable);
|
||||||
FindModuleResult userAssemblyResult = UnsafeFindModule(hProcess, "UserAssembly.dll", out Module userAssembly);
|
|
||||||
|
|
||||||
if (unityPlayerResult is FindModuleResult.Ok && userAssemblyResult is FindModuleResult.Ok)
|
if (result is FindModuleResult.Ok)
|
||||||
{
|
{
|
||||||
info = new(unityPlayer, userAssembly);
|
info = new(executable);
|
||||||
return FindModuleResult.Ok;
|
return FindModuleResult.Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unityPlayerResult is FindModuleResult.NoModuleFound && userAssemblyResult is FindModuleResult.NoModuleFound)
|
if (result is FindModuleResult.NoModuleFound)
|
||||||
{
|
{
|
||||||
info = default;
|
info = default;
|
||||||
return FindModuleResult.NoModuleFound;
|
return FindModuleResult.NoModuleFound;
|
||||||
@@ -61,7 +60,7 @@ internal static class GameProcessModule
|
|||||||
return FindModuleResult.ModuleNotLoaded;
|
return FindModuleResult.ModuleNotLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe FindModuleResult UnsafeFindModule(in HANDLE hProcess, in ReadOnlySpan<char> moduleName, out Module module)
|
private static unsafe FindModuleResult UnsafeFindModule(in HANDLE hProcess, in ReadOnlySpan<char> moduleName1, in ReadOnlySpan<char> moduleName2, out Module module)
|
||||||
{
|
{
|
||||||
HMODULE[] buffer = new HMODULE[128];
|
HMODULE[] buffer = new HMODULE[128];
|
||||||
if (!K32EnumProcessModules(hProcess, buffer, out uint actualSize))
|
if (!K32EnumProcessModules(hProcess, buffer, out uint actualSize))
|
||||||
@@ -86,7 +85,8 @@ internal static class GameProcessModule
|
|||||||
|
|
||||||
fixed (char* lpBaseName = baseName)
|
fixed (char* lpBaseName = baseName)
|
||||||
{
|
{
|
||||||
if (!moduleName.SequenceEqual(MemoryMarshal.CreateReadOnlySpanFromNullTerminated(lpBaseName)))
|
ReadOnlySpan<char> baseNameSpan = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(lpBaseName);
|
||||||
|
if (!(moduleName1.SequenceEqual(baseNameSpan) || moduleName2.SequenceEqual(baseNameSpan)))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,30 +12,24 @@ namespace Snap.Hutao.Service.Game.Unlocker;
|
|||||||
internal readonly struct RequiredLocalModule : IDisposable
|
internal readonly struct RequiredLocalModule : IDisposable
|
||||||
{
|
{
|
||||||
public readonly bool HasValue = false;
|
public readonly bool HasValue = false;
|
||||||
public readonly Module UnityPlayer;
|
public readonly Module Executable;
|
||||||
public readonly Module UserAssembly;
|
|
||||||
|
|
||||||
private readonly HMODULE hModuleUnityPlayer;
|
private readonly HMODULE hModuleExecutable;
|
||||||
private readonly HMODULE hModuleUserAssembly;
|
|
||||||
|
|
||||||
public RequiredLocalModule(HMODULE unityPlayer, HMODULE userAssembly)
|
public RequiredLocalModule(HMODULE executable)
|
||||||
{
|
{
|
||||||
hModuleUnityPlayer = unityPlayer;
|
hModuleExecutable = executable;
|
||||||
hModuleUserAssembly = userAssembly;
|
|
||||||
|
|
||||||
// Align the pointer
|
// Align the pointer
|
||||||
nint unityPlayerMappedView = (nint)(unityPlayer & ~0x3L);
|
nint executableMappedView = (nint)(executable & ~0x3L);
|
||||||
nint userAssemblyMappedView = (nint)(userAssembly & ~0x3L);
|
|
||||||
|
|
||||||
|
Executable = new((nuint)executableMappedView, GetImageSize(executableMappedView));
|
||||||
HasValue = true;
|
HasValue = true;
|
||||||
UnityPlayer = new((nuint)unityPlayerMappedView, GetImageSize(unityPlayerMappedView));
|
|
||||||
UserAssembly = new((nuint)userAssemblyMappedView, GetImageSize(userAssemblyMappedView));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
FreeLibrary(hModuleUnityPlayer);
|
FreeLibrary(hModuleExecutable);
|
||||||
FreeLibrary(hModuleUserAssembly);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|||||||
@@ -6,13 +6,11 @@ namespace Snap.Hutao.Service.Game.Unlocker;
|
|||||||
internal readonly struct RequiredRemoteModule
|
internal readonly struct RequiredRemoteModule
|
||||||
{
|
{
|
||||||
public readonly bool HasValue = false;
|
public readonly bool HasValue = false;
|
||||||
public readonly Module UnityPlayer;
|
public readonly Module Executable;
|
||||||
public readonly Module UserAssembly;
|
|
||||||
|
|
||||||
public RequiredRemoteModule(in Module unityPlayer, in Module userAssembly)
|
public RequiredRemoteModule(in Module executable)
|
||||||
{
|
{
|
||||||
HasValue = true;
|
HasValue = true;
|
||||||
UnityPlayer = unityPlayer;
|
Executable = executable;
|
||||||
UserAssembly = userAssembly;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user