diff --git a/unlockfps_nc/AboutForm.Designer.cs b/unlockfps_nc/AboutForm.Designer.cs index 18a2cfa..132256a 100644 --- a/unlockfps_nc/AboutForm.Designer.cs +++ b/unlockfps_nc/AboutForm.Designer.cs @@ -41,7 +41,7 @@ private void InitializeComponent() LabelTitle.Name = "LabelTitle"; LabelTitle.Size = new Size(320, 36); LabelTitle.TabIndex = 0; - LabelTitle.Text = "Genshin FPS Unlocker\r\nv3.2.0"; + LabelTitle.Text = "Genshin FPS Unlocker\r\nv3.3.0"; LabelTitle.TextAlign = ContentAlignment.TopCenter; // // LabelDescription diff --git a/unlockfps_nc/Service/IpcService.cs b/unlockfps_nc/Service/IpcService.cs index d4c666b..005732c 100644 --- a/unlockfps_nc/Service/IpcService.cs +++ b/unlockfps_nc/Service/IpcService.cs @@ -61,7 +61,7 @@ public void Start(int processId, IntPtr pFpsValue) } var stubWndProc = Native.GetProcAddress(_stubModule, "WndProc"); - var targetWindow = GetWindowFromProcessId(processId); + var targetWindow = ProcessUtils.GetWindowFromProcessId(processId); var threadId = Native.GetWindowThreadProcessId(targetWindow, out uint _); _wndHook = Native.SetWindowsHookEx(3, stubWndProc, _stubModule, threadId); @@ -144,25 +144,6 @@ private string GetUnlockerStubPath() return filePath; } - private IntPtr GetWindowFromProcessId(int processId) - { - IntPtr windowHandle = IntPtr.Zero; - - Native.EnumWindows((hWnd, lParam) => - { - Native.GetWindowThreadProcessId(hWnd, out uint pid); - if (pid == processId) - { - windowHandle = hWnd; - return false; - } - - return true; - }, IntPtr.Zero); - - return windowHandle; - } - public void Dispose() { Stop(); diff --git a/unlockfps_nc/Service/ProcessService.cs b/unlockfps_nc/Service/ProcessService.cs index 01266bb..7117d07 100644 --- a/unlockfps_nc/Service/ProcessService.cs +++ b/unlockfps_nc/Service/ProcessService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Net; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; @@ -161,8 +162,7 @@ private async Task Worker() Native.CloseHandle(pi.hThread); - if (!await UpdateRemoteModules()) - return; + SpinWait.SpinUntil(() => ProcessUtils.GetWindowFromProcessId(_gamePid) != IntPtr.Zero); if (!SetupData()) return; @@ -251,12 +251,22 @@ private unsafe bool SetupData() if (!pUnityPlayer || !pUserAssembly) { + if (!File.Exists(unityPlayerPath) && !File.Exists(userAssemblyPath)) + { + if (SetupDataEx()) + return true; + goto BAD_PATTERN; + } + MessageBox.Show( @"Failed to load UnityPlayer.dll or UserAssembly.dll", @"Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } + if (!UpdateRemoteModules()) + return false; + var dosHeader = Marshal.PtrToStructure(pUnityPlayer); var ntHeader = Marshal.PtrToStructure((IntPtr)(pUnityPlayer.BaseAddress.ToInt64() + dosHeader.e_lfanew)); @@ -326,7 +336,40 @@ private unsafe bool SetupData() return false; } - private async Task UpdateRemoteModules() + private unsafe bool SetupDataEx() + { + var gameName = Path.GetFileNameWithoutExtension(_config.GamePath); + var remoteExe = ProcessUtils.GetModuleBase(_gameHandle, $"{gameName}.exe"); + if (remoteExe == IntPtr.Zero) + return false; + + using ModuleGuard pGenshinImpact = Native.LoadLibraryEx(_config.GamePath, IntPtr.Zero, 32); + if (!pGenshinImpact) + return false; + + var vaResults = ProcessUtils.PatternScanAllOccurrences(pGenshinImpact, "B9 3C 00 00 00 E8"); + if (vaResults.Count == 0) + return false; + + var localVa = (byte*)vaResults + .Select(x => x + 5) + .Select(x => x + *(int*)(x + 1) + 5) + .FirstOrDefault(x => *(byte*)x == 0xE9); + + if (localVa == null) + return false; + + while (localVa[0] == 0xE8 || localVa[0] == 0xE9) + localVa += *(int*)(localVa + 1) + 5; + + localVa += *(int*)(localVa + 2) + 6; + var rva = localVa - pGenshinImpact.BaseAddress.ToInt64(); + _pFpsValue = (IntPtr)(remoteExe + rva); + + return true; + } + + private bool UpdateRemoteModules() { int retries = 0; @@ -341,7 +384,7 @@ private async Task UpdateRemoteModules() if (retries > 10) break; - await Task.Delay(2000, _cts.Token); + Task.Delay(2000, _cts.Token).Wait(); retries++; } diff --git a/unlockfps_nc/SetupForm.cs b/unlockfps_nc/SetupForm.cs index 91b5843..d77746c 100644 --- a/unlockfps_nc/SetupForm.cs +++ b/unlockfps_nc/SetupForm.cs @@ -181,8 +181,8 @@ private void BtnBrowse_Click(object sender, EventArgs e) return; } - var unityPlayer = Path.Combine(directory, "UnityPlayer.dll"); - if (!File.Exists(unityPlayer)) + var dataDir = Path.Combine(directory, $"{fileName}_Data"); + if (!Directory.Exists(dataDir)) { MessageBox.Show(@"That's not the right place", @"Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; diff --git a/unlockfps_nc/Utility/Native.cs b/unlockfps_nc/Utility/Native.cs index 673cc46..76751f7 100644 --- a/unlockfps_nc/Utility/Native.cs +++ b/unlockfps_nc/Utility/Native.cs @@ -124,6 +124,15 @@ public static bool IsWine() return ver != 0; } + + public static uint GetModuleImageSize(IntPtr lpBaseAddress) + { + var dosHeader = Marshal.PtrToStructure(lpBaseAddress); + var ntHeader = Marshal.PtrToStructure(lpBaseAddress + dosHeader.e_lfanew); + + return ntHeader.OptionalHeader.SizeOfImage; + } + } internal class ModuleGuard(IntPtr module) : IDisposable diff --git a/unlockfps_nc/Utility/ProcessUtils.cs b/unlockfps_nc/Utility/ProcessUtils.cs index 131d6b0..5c2e54a 100644 --- a/unlockfps_nc/Utility/ProcessUtils.cs +++ b/unlockfps_nc/Utility/ProcessUtils.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection.PortableExecutable; using System.Runtime.InteropServices; @@ -30,6 +31,25 @@ public static string GetProcessPathFromPid(uint pid, out IntPtr processHandle) return sb.ToString(); } + public static IntPtr GetWindowFromProcessId(int processId) + { + IntPtr windowHandle = IntPtr.Zero; + + Native.EnumWindows((hWnd, lParam) => + { + Native.GetWindowThreadProcessId(hWnd, out uint pid); + if (pid == processId) + { + windowHandle = hWnd; + return false; + } + + return true; + }, IntPtr.Zero); + + return windowHandle; + } + public static bool InjectDlls(IntPtr processHandle, List dllPaths) { #if !RELEASEMIN @@ -71,23 +91,11 @@ public static bool InjectDlls(IntPtr processHandle, List dllPaths) public static unsafe IntPtr PatternScan(IntPtr module, string signature) { - var tokens = signature.Split(' '); - var patternBytes = tokens - .Select(x => x == "?" ? (byte)0xFF : Convert.ToByte(x, 16)) - .ToArray(); - var maskBytes = tokens - .Select(x => x == "?") - .ToArray(); - - var dosHeader = Marshal.PtrToStructure(module); - var ntHeader = Marshal.PtrToStructure((IntPtr)(module.ToInt64() + dosHeader.e_lfanew)); + var (patternBytes, maskBytes) = ParseSignature(signature); - var sizeOfImage = ntHeader.OptionalHeader.SizeOfImage; + var sizeOfImage = Native.GetModuleImageSize(module); var scanBytes = (byte*)module; - var s = patternBytes.Length; - var d = patternBytes; - if (Native.IsWine()) { /* @@ -98,12 +106,58 @@ public static unsafe IntPtr PatternScan(IntPtr module, string signature) Native.VirtualProtect(module, sizeOfImage, MemoryProtection.EXECUTE_READWRITE, out _); } - for (var i = 0U; i < sizeOfImage - s; i++) + var span = new ReadOnlySpan(scanBytes, (int)sizeOfImage); + var offset = PatternScan(span, patternBytes, maskBytes); + + if (offset != -1) + return (IntPtr)(module.ToInt64() + offset); + + + return IntPtr.Zero; + } + + public static unsafe List PatternScanAllOccurrences(IntPtr module, string signature) + { + var (patternBytes, maskBytes) = ParseSignature(signature); + + var sizeOfImage = Native.GetModuleImageSize(module); + var scanBytes = (byte*)module; + + if (Native.IsWine()) + Native.VirtualProtect(module, sizeOfImage, MemoryProtection.EXECUTE_READWRITE, out _); + + var span = new ReadOnlySpan(scanBytes, (int)sizeOfImage); + var offsets = new List(); + + var totalProcessed = 0L; + while (true) + { + var offset = PatternScan(span, patternBytes, maskBytes); + if (offset == -1) + break; + + offsets.Add((IntPtr)(module.ToInt64() + offset + totalProcessed)); + + var processedOffset = offset + patternBytes.Length; + totalProcessed += processedOffset; + + span = span.Slice((int)processedOffset); + } + + return offsets; + } + + public static long PatternScan(ReadOnlySpan data, byte[] patternBytes, bool[] maskBytes) + { + var s = patternBytes.Length; + var d = patternBytes; + + for (var i = 0; i < data.Length - s; i++) { var found = true; for (var j = 0; j < s; j++) { - if (d[j] != scanBytes[i + j] && !maskBytes[j]) + if (d[j] != data[i + j] && !maskBytes[j]) { found = false; break; @@ -111,10 +165,23 @@ public static unsafe IntPtr PatternScan(IntPtr module, string signature) } if (found) - return (IntPtr)(module.ToInt64() + i); + return i; } - return IntPtr.Zero; + return -1; + } + + private static (byte[], bool[]) ParseSignature(string signature) + { + var tokens = signature.Split(' '); + var patternBytes = tokens + .Select(x => x == "?" ? (byte)0xFF : Convert.ToByte(x, 16)) + .ToArray(); + var maskBytes = tokens + .Select(x => x == "?") + .ToArray(); + + return (patternBytes, maskBytes); } public static IntPtr GetModuleBase(IntPtr hProcess, string moduleName)