From 866efc5bcf9023b0c735fd04626ab31c97767182 Mon Sep 17 00:00:00 2001 From: pohky Date: Wed, 13 Jan 2021 18:32:35 +0100 Subject: [PATCH] Update SigScanner.cs bit of a speed boost --- Dalamud/Game/SigScanner.cs | 217 ++++++++++++++++++------------------- 1 file changed, 107 insertions(+), 110 deletions(-) diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs index 9cd9b7694..483f54721 100644 --- a/Dalamud/Game/SigScanner.cs +++ b/Dalamud/Game/SigScanner.cs @@ -4,18 +4,20 @@ using System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using Serilog; namespace Dalamud.Game { /// - /// A SigScanner facilitates searching for memory signatures in a given ProcessModule. + /// A SigScanner facilitates searching for memory signatures in a given ProcessModule. /// public sealed class SigScanner : IDisposable { /// - /// Set up the SigScanner. + /// Set up the SigScanner. /// /// The ProcessModule to be used for scanning - /// Whether or not to copy the module upon initialization for search operations to use, as to not get disturbed by possible hooks. + /// + /// Whether or not to copy the module upon initialization for search operations to use, as to not get + /// disturbed by possible hooks. + /// public SigScanner(ProcessModule module, bool doCopy = false) { Module = module; Is32BitProcess = !Environment.Is64BitProcess; @@ -26,54 +28,55 @@ namespace Dalamud.Game { if (IsCopy) SetupCopiedSegments(); - - Log.Verbose("Module base: {Address}", TextSectionBase); - Log.Verbose("Module size: {Size}", TextSectionSize); } /// - /// If the search on this module is performed on a copy. + /// If the search on this module is performed on a copy. /// - public bool IsCopy { get; private set; } + public bool IsCopy { get; } /// - /// If the ProcessModule is 32-bit. + /// If the ProcessModule is 32-bit. /// public bool Is32BitProcess { get; } /// - /// The base address of the search area. When copied, this will be the address of the copy. + /// The base address of the search area. When copied, this will be the address of the copy. /// - public IntPtr SearchBase => IsCopy ? this.moduleCopyPtr : Module.BaseAddress; + public IntPtr SearchBase => IsCopy ? _moduleCopyPtr : Module.BaseAddress; /// - /// The base address of the .text section search area. + /// The base address of the .text section search area. /// public IntPtr TextSectionBase => new IntPtr(SearchBase.ToInt64() + TextSectionOffset); + /// - /// The offset of the .text section from the base of the module. + /// The offset of the .text section from the base of the module. /// public long TextSectionOffset { get; private set; } + /// - /// The size of the text section. + /// The size of the text section. /// public int TextSectionSize { get; private set; } /// - /// The base address of the .data section search area. + /// The base address of the .data section search area. /// public IntPtr DataSectionBase => new IntPtr(SearchBase.ToInt64() + DataSectionOffset); + /// - /// The offset of the .data section from the base of the module. + /// The offset of the .data section from the base of the module. /// public long DataSectionOffset { get; private set; } + /// - /// The size of the .data section. + /// The size of the .data section. /// public int DataSectionSize { get; private set; } /// - /// The ProcessModule on which the search is performed. + /// The ProcessModule on which the search is performed. /// public ProcessModule Module { get; } @@ -120,170 +123,164 @@ namespace Dalamud.Game { } } - private IntPtr moduleCopyPtr; - private long moduleCopyOffset; + private IntPtr _moduleCopyPtr; + private long _moduleCopyOffset; private unsafe void SetupCopiedSegments() { - Log.Verbose("module copy START"); // .text - this.moduleCopyPtr = Marshal.AllocHGlobal(Module.ModuleMemorySize); - Log.Verbose($"Alloc: {this.moduleCopyPtr.ToInt64():x}"); - Buffer.MemoryCopy(Module.BaseAddress.ToPointer(), this.moduleCopyPtr.ToPointer(), Module.ModuleMemorySize, - Module.ModuleMemorySize); - - this.moduleCopyOffset = this.moduleCopyPtr.ToInt64() - Module.BaseAddress.ToInt64(); - - Log.Verbose("copy OK!"); + _moduleCopyPtr = Marshal.AllocHGlobal(Module.ModuleMemorySize); + Buffer.MemoryCopy(Module.BaseAddress.ToPointer(), _moduleCopyPtr.ToPointer(), Module.ModuleMemorySize, Module.ModuleMemorySize); + _moduleCopyOffset = _moduleCopyPtr.ToInt64() - Module.BaseAddress.ToInt64(); } /// - /// Free the memory of the copied module search area on object disposal, if applicable. + /// Free the memory of the copied module search area on object disposal, if applicable. /// public void Dispose() { - Marshal.FreeHGlobal(this.moduleCopyPtr); + Marshal.FreeHGlobal(_moduleCopyPtr); + } + + public IntPtr ResolveRelativeAddress(IntPtr nextInstAddr, int relOffset) { + if (Is32BitProcess) throw new NotSupportedException("32 bit is not supported."); + return nextInstAddr + relOffset; } /// - /// Scan for a byte signature in the .text section. + /// Scan for a byte signature in the .text section. /// /// The signature. /// The real offset of the found signature. public IntPtr ScanText(string signature) { - var mBase = IsCopy ? this.moduleCopyPtr : TextSectionBase; - + var mBase = IsCopy ? _moduleCopyPtr : TextSectionBase; var scanRet = Scan(mBase, TextSectionSize, signature); - if (IsCopy) - scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); - - if (Marshal.ReadByte(scanRet) == 0xE8) + scanRet = new IntPtr(scanRet.ToInt64() - _moduleCopyOffset); + var insnByte = Marshal.ReadByte(scanRet); + if (insnByte == 0xE8 || insnByte == 0xE9) return ReadCallSig(scanRet); - return scanRet; } /// - /// Helper for ScanText to get the correct address for - /// IDA sigs that mark the first CALL location. + /// Helper for ScanText to get the correct address for + /// IDA sigs that mark the first CALL location. /// - /// The address the CALL sig resolved to. + /// The address the CALL sig resolved to. /// The real offset of the signature. - private IntPtr ReadCallSig(IntPtr SigLocation) - { - int jumpOffset = Marshal.ReadInt32(IntPtr.Add(SigLocation, 1)); - return IntPtr.Add(SigLocation, 5 + jumpOffset); + private static IntPtr ReadCallSig(IntPtr sigLocation) { + var jumpOffset = Marshal.ReadInt32(IntPtr.Add(sigLocation, 1)); + return IntPtr.Add(sigLocation, 5 + jumpOffset); } /// - /// Scan for a .data address using a .text function. - /// This is intended to be used with IDA sigs. - /// Place your cursor on the line calling a static address, and create and IDA sig. + /// Scan for a .data address using a .text function. + /// This is intended to be used with IDA sigs. + /// Place your cursor on the line calling a static address, and create and IDA sig. /// /// The signature of the function using the data. /// The offset from function start of the instruction using the data. /// An IntPtr to the static memory location. - public IntPtr GetStaticAddressFromSig(string signature, int offset = 0) - { - IntPtr instrAddr = ScanText(signature); + public IntPtr GetStaticAddressFromSig(string signature, int offset = 0) { + var instrAddr = ScanText(signature); instrAddr = IntPtr.Add(instrAddr, offset); - long bAddr = (long)Module.BaseAddress; + var bAddr = (long)Module.BaseAddress; long num; - do - { + do { instrAddr = IntPtr.Add(instrAddr, 1); num = Marshal.ReadInt32(instrAddr) + (long)instrAddr + 4 - bAddr; - } - while (!(num >= DataSectionOffset && num <= DataSectionOffset + DataSectionSize)); + } while (!(num >= DataSectionOffset && num <= DataSectionOffset + DataSectionSize)); + return IntPtr.Add(instrAddr, Marshal.ReadInt32(instrAddr) + 4); } /// - /// Scan for a byte signature in the .data section. + /// Scan for a byte signature in the .data section. /// /// The signature. /// The real offset of the found signature. public IntPtr ScanData(string signature) { var scanRet = Scan(DataSectionBase, DataSectionSize, signature); - if (IsCopy) - scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); - + scanRet = new IntPtr(scanRet.ToInt64() - _moduleCopyOffset); return scanRet; } /// - /// Scan for a byte signature in the whole module search area. + /// Scan for a byte signature in the whole module search area. /// /// The signature. /// The real offset of the found signature. public IntPtr ScanModule(string signature) { var scanRet = Scan(SearchBase, Module.ModuleMemorySize, signature); - if (IsCopy) - scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); - + scanRet = new IntPtr(scanRet.ToInt64() - _moduleCopyOffset); return scanRet; } public IntPtr Scan(IntPtr baseAddress, int size, string signature) { - var needle = SigToNeedle(signature); - - unsafe { - var pCursor = (byte*) baseAddress.ToPointer(); - var pTop = (byte*) (baseAddress + size - needle.Length); - while (pCursor < pTop) { - if (IsMatch(pCursor, needle)) return (IntPtr) pCursor; - - // Advance an offset - pCursor += 1; - } - } - - throw new KeyNotFoundException($"Can't find a signature of {signature}"); + var (needle, mask) = ParseSignature(signature); + var index = IndexOf(baseAddress, size, needle, mask); + if (index < 0) + throw new KeyNotFoundException($"Can't find a signature of {signature}"); + return baseAddress + index; } - public IntPtr ResolveRelativeAddress(IntPtr nextInstAddr, int relOffset) { - if (Is32BitProcess) throw new NotSupportedException("32 bit is not supported."); - - return nextInstAddr + relOffset; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe bool IsMatch(byte* pCursor, byte?[] needle) { - for (var i = 0; i < needle.Length; i++) { - var expected = needle[i]; - if (expected == null) continue; - - var actual = *(pCursor + i); - if (expected != actual) return false; - } - - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private byte?[] SigToNeedle(string signature) { - // Strip all whitespaces + private static (byte[] needle, bool[] mask) ParseSignature(string signature) { signature = signature.Replace(" ", ""); - if (signature.Length % 2 != 0) - throw new ArgumentException("Signature without whitespaces must be divisible by two.", - nameof(signature)); - + throw new ArgumentException("Signature without whitespaces must be divisible by two.", nameof(signature)); var needleLength = signature.Length / 2; - var needle = new byte?[needleLength]; - + var needle = new byte[needleLength]; + var mask = new bool[needleLength]; for (var i = 0; i < needleLength; i++) { var hexString = signature.Substring(i * 2, 2); if (hexString == "??" || hexString == "**") { - needle[i] = null; + needle[i] = 0; + mask[i] = true; continue; } needle[i] = byte.Parse(hexString, NumberStyles.AllowHexSpecifier); + mask[i] = false; } - return needle; + return (needle, mask); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe int IndexOf(IntPtr bufferPtr, int bufferLength, byte[] needle, bool[] mask) { + if (needle.Length > bufferLength) return -1; + var badShift = BuildBadCharTable(needle, mask); + var last = needle.Length - 1; + var offset = 0; + var maxoffset = bufferLength - needle.Length; + var buffer = (byte*)bufferPtr; + while (offset <= maxoffset) { + int position; + for (position = last; needle[position] == *(buffer + position + offset) || mask[position]; position--) + if (position == 0) + return offset; + offset += badShift[*(buffer + offset + last)]; + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int[] BuildBadCharTable(byte[] needle, bool[] mask) { + int idx; + var last = needle.Length - 1; + var badShift = new int[256]; + for (idx = last; idx > 0 && !mask[idx]; --idx) { } + + var diff = last - idx; + if (diff == 0) diff = 1; + + for (idx = 0; idx <= 255; ++idx) + badShift[idx] = diff; + for (idx = last - diff; idx < last; ++idx) + badShift[needle[idx]] = last - idx; + return badShift; } } }