Merge pull request #1816 from DGP-Studio/feat/unlockfps-loadexe

fix #1814
This commit is contained in:
DismissedLight
2024-07-17 11:18:26 +08:00
committed by GitHub
5 changed files with 50 additions and 57 deletions

View File

@@ -1,10 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// 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;
/// <summary>
@@ -19,19 +15,34 @@ internal static class GameFpsAddress
public static unsafe void UnsafeFindFpsAddress(GameFpsUnlockerContext context, in RequiredRemoteModule remoteModule, in RequiredLocalModule localModule)
{
int offsetToUserAssembly = IndexOfPattern(localModule.UserAssembly.AsSpan());
HutaoException.ThrowIfNot(offsetToUserAssembly >= 0, SH.ServiceGameUnlockerInterestedPatternNotFound);
Span<byte> executableSpan = localModule.Executable.AsSpan();
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;
nuint rip = localModule.Executable.Address + (uint)offsetToExecutable;
rip += 5U;
rip += (nuint)(*(int*)(rip + 2U) + 6);
rip += (nuint)(*(int*)(rip + 1U) + 5);
nuint remoteVirtualAddress = remoteModule.UserAssembly.Address + (rip - localModule.UserAssembly.Address);
if (*(byte*)rip is ASM_JMP)
{
localVirtualAddress = rip;
break;
}
nuint ptr = 0;
SpinWait.SpinUntil(() => UnsafeReadProcessMemory(context.AllAccess, remoteVirtualAddress, out ptr) && ptr != 0);
offsetToExecutable += patternLength;
}
while (true);
nuint localVirtualAddress = ptr - remoteModule.UnityPlayer.Address + localModule.UnityPlayer.Address;
ArgumentOutOfRangeException.ThrowIfZero(localVirtualAddress);
while (*(byte*)localVirtualAddress is ASM_CALL or ASM_JMP)
{
@@ -39,22 +50,15 @@ internal static class GameFpsAddress
}
localVirtualAddress += *(uint*)(localVirtualAddress + 2) + 6;
nuint relativeVirtualAddress = localVirtualAddress - localModule.UnityPlayer.Address;
context.FpsAddress = remoteModule.UnityPlayer.Address + relativeVirtualAddress;
nuint relativeVirtualAddress = localVirtualAddress - localModule.Executable.Address;
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
ReadOnlySpan<byte> part = [0xB9, 0x3C, 0x00, 0x00, 0x00, 0xFF, 0x15];
return memory.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;
// B9 3C 00 00 00 E8
ReadOnlySpan<byte> part = [0xB9, 0x3C, 0x00, 0x00, 0x00, 0xE8];
patternLength = part.Length;
return span.IndexOf(part);
}
}

View File

@@ -51,12 +51,9 @@ internal abstract class GameFpsUnlocker : IGameFpsUnlocker
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;
HMODULE unityPlayerAddress = LoadLibraryExW(System.IO.Path.Combine(gameFoler, "UnityPlayer.dll"), default, flags);
HMODULE userAssemblyAddress = LoadLibraryExW(System.IO.Path.Combine(dataFoler, "Native", "UserAssembly.dll"), default, flags);
HMODULE executaleAddress = LoadLibraryExW(gameFileSystem.GameFilePath, default, flags);
return new(unityPlayerAddress, userAssemblyAddress);
return new(executaleAddress);
}
}

View File

@@ -42,16 +42,15 @@ internal static class GameProcessModule
private static FindModuleResult UnsafeGetGameModuleInfo(in HANDLE hProcess, out RequiredRemoteModule info)
{
FindModuleResult unityPlayerResult = UnsafeFindModule(hProcess, "UnityPlayer.dll", out Module unityPlayer);
FindModuleResult userAssemblyResult = UnsafeFindModule(hProcess, "UserAssembly.dll", out Module userAssembly);
FindModuleResult result = UnsafeFindModule(hProcess, GameConstants.YuanShenFileName, GameConstants.GenshinImpactFileName, out Module executable);
if (unityPlayerResult is FindModuleResult.Ok && userAssemblyResult is FindModuleResult.Ok)
if (result is FindModuleResult.Ok)
{
info = new(unityPlayer, userAssembly);
info = new(executable);
return FindModuleResult.Ok;
}
if (unityPlayerResult is FindModuleResult.NoModuleFound && userAssemblyResult is FindModuleResult.NoModuleFound)
if (result is FindModuleResult.NoModuleFound)
{
info = default;
return FindModuleResult.NoModuleFound;
@@ -61,7 +60,7 @@ internal static class GameProcessModule
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];
if (!K32EnumProcessModules(hProcess, buffer, out uint actualSize))
@@ -86,7 +85,8 @@ internal static class GameProcessModule
fixed (char* lpBaseName = baseName)
{
if (!moduleName.SequenceEqual(MemoryMarshal.CreateReadOnlySpanFromNullTerminated(lpBaseName)))
ReadOnlySpan<char> baseNameSpan = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(lpBaseName);
if (!(moduleName1.SequenceEqual(baseNameSpan) || moduleName2.SequenceEqual(baseNameSpan)))
{
continue;
}

View File

@@ -12,30 +12,24 @@ namespace Snap.Hutao.Service.Game.Unlocker;
internal readonly struct RequiredLocalModule : IDisposable
{
public readonly bool HasValue = false;
public readonly Module UnityPlayer;
public readonly Module UserAssembly;
public readonly Module Executable;
private readonly HMODULE hModuleUnityPlayer;
private readonly HMODULE hModuleUserAssembly;
private readonly HMODULE hModuleExecutable;
public RequiredLocalModule(HMODULE unityPlayer, HMODULE userAssembly)
public RequiredLocalModule(HMODULE executable)
{
hModuleUnityPlayer = unityPlayer;
hModuleUserAssembly = userAssembly;
hModuleExecutable = executable;
// Align the pointer
nint unityPlayerMappedView = (nint)(unityPlayer & ~0x3L);
nint userAssemblyMappedView = (nint)(userAssembly & ~0x3L);
nint executableMappedView = (nint)(executable & ~0x3L);
Executable = new((nuint)executableMappedView, GetImageSize(executableMappedView));
HasValue = true;
UnityPlayer = new((nuint)unityPlayerMappedView, GetImageSize(unityPlayerMappedView));
UserAssembly = new((nuint)userAssemblyMappedView, GetImageSize(userAssemblyMappedView));
}
public void Dispose()
{
FreeLibrary(hModuleUnityPlayer);
FreeLibrary(hModuleUserAssembly);
FreeLibrary(hModuleExecutable);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@@ -6,13 +6,11 @@ namespace Snap.Hutao.Service.Game.Unlocker;
internal readonly struct RequiredRemoteModule
{
public readonly bool HasValue = false;
public readonly Module UnityPlayer;
public readonly Module UserAssembly;
public readonly Module Executable;
public RequiredRemoteModule(in Module unityPlayer, in Module userAssembly)
public RequiredRemoteModule(in Module executable)
{
HasValue = true;
UnityPlayer = unityPlayer;
UserAssembly = userAssembly;
Executable = executable;
}
}