From e8cd4e750a7cdd5698b6fff1057e367c71754ddc Mon Sep 17 00:00:00 2001
From: goat <16760685+goaaats@users.noreply.github.com>
Date: Wed, 7 Apr 2021 02:15:44 +0200
Subject: [PATCH] refactor: new code style in SigScanner.cs
---
Dalamud/Game/SigScanner.cs | 442 +++++++++++++++++++++----------------
1 file changed, 249 insertions(+), 193 deletions(-)
diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs
index cb57f3789..1242935ef 100644
--- a/Dalamud/Game/SigScanner.cs
+++ b/Dalamud/Game/SigScanner.cs
@@ -4,85 +4,288 @@ using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+
using Serilog;
-namespace Dalamud.Game {
+namespace Dalamud.Game
+{
///
/// A SigScanner facilitates searching for memory signatures in a given ProcessModule.
///
- public sealed class SigScanner : IDisposable {
+ public sealed class SigScanner : IDisposable
+ {
+ private IntPtr moduleCopyPtr;
+ private long moduleCopyOffset;
+
///
- /// Set up the SigScanner.
+ /// Initializes a new instance of the class.
///
- /// The ProcessModule to be used for scanning
+ /// 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.
- public SigScanner(ProcessModule module, bool doCopy = false) {
- Module = module;
- Is32BitProcess = !Environment.Is64BitProcess;
- IsCopy = doCopy;
+ public SigScanner(ProcessModule module, bool doCopy = false)
+ {
+ this.Module = module;
+ this.Is32BitProcess = !Environment.Is64BitProcess;
+ this.IsCopy = doCopy;
// Limit the search space to .text section.
- SetupSearchSpace(module);
+ this.SetupSearchSpace(module);
- if (IsCopy)
- SetupCopiedSegments();
- Log.Verbose("Module base: {Address}", TextSectionBase);
- Log.Verbose("Module size: {Size}", TextSectionSize);
+ if (this.IsCopy)
+ this.SetupCopiedSegments();
+
+ Log.Verbose("Module base: {Address}", this.TextSectionBase);
+ Log.Verbose("Module size: {Size}", this.TextSectionSize);
}
///
- /// If the search on this module is performed on a copy.
+ /// Gets a value indicating whether or not the search on this module is performed on a copy.
///
public bool IsCopy { get; }
///
- /// If the ProcessModule is 32-bit.
+ /// Gets a value indicating whether or not 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.
+ /// Gets 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 => this.IsCopy ? this.moduleCopyPtr : this.Module.BaseAddress;
///
- /// The base address of the .text section search area.
+ /// Gets the base address of the .text section search area.
///
- public IntPtr TextSectionBase => new IntPtr(SearchBase.ToInt64() + TextSectionOffset);
+ public IntPtr TextSectionBase => new IntPtr(this.SearchBase.ToInt64() + this.TextSectionOffset);
///
- /// The offset of the .text section from the base of the module.
+ /// Gets the offset of the .text section from the base of the module.
///
public long TextSectionOffset { get; private set; }
///
- /// The size of the text section.
+ /// Gets the size of the text section.
///
public int TextSectionSize { get; private set; }
///
- /// The base address of the .data section search area.
+ /// Gets the base address of the .data section search area.
///
- public IntPtr DataSectionBase => new IntPtr(SearchBase.ToInt64() + DataSectionOffset);
+ public IntPtr DataSectionBase => new IntPtr(this.SearchBase.ToInt64() + this.DataSectionOffset);
///
- /// The offset of the .data section from the base of the module.
+ /// Gets the offset of the .data section from the base of the module.
///
public long DataSectionOffset { get; private set; }
///
- /// The size of the .data section.
+ /// Gets the size of the .data section.
///
public int DataSectionSize { get; private set; }
///
- /// The ProcessModule on which the search is performed.
+ /// Gets the ProcessModule on which the search is performed.
///
public ProcessModule Module { get; }
- private IntPtr TextSectionTop => TextSectionBase + TextSectionSize;
+ private IntPtr TextSectionTop => this.TextSectionBase + this.TextSectionSize;
- private void SetupSearchSpace(ProcessModule module) {
+ ///
+ /// Scan memory for a signature.
+ ///
+ /// The base address to scan from.
+ /// The amount of bytes to scan.
+ /// The signature to search for.
+ /// The found offset.
+ public static IntPtr Scan(IntPtr baseAddress, int size, string 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;
+ }
+
+ ///
+ /// 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)
+ {
+ var instrAddr = this.ScanText(signature);
+ instrAddr = IntPtr.Add(instrAddr, offset);
+ var bAddr = (long)this.Module.BaseAddress;
+ long num;
+
+ do
+ {
+ instrAddr = IntPtr.Add(instrAddr, 1);
+ num = Marshal.ReadInt32(instrAddr) + (long)instrAddr + 4 - bAddr;
+ }
+ while (!(num >= this.DataSectionOffset && num <= this.DataSectionOffset + this.DataSectionSize));
+
+ return IntPtr.Add(instrAddr, Marshal.ReadInt32(instrAddr) + 4);
+ }
+
+ ///
+ /// 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(this.DataSectionBase, this.DataSectionSize, signature);
+
+ if (this.IsCopy)
+ scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset);
+
+ return scanRet;
+ }
+
+ ///
+ /// 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(this.SearchBase, this.Module.ModuleMemorySize, signature);
+
+ if (this.IsCopy)
+ scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset);
+
+ return scanRet;
+ }
+
+ ///
+ /// Resolve a RVA address.
+ ///
+ /// The address of the next instruction.
+ /// The relative offset.
+ /// The calculated offset.
+ public IntPtr ResolveRelativeAddress(IntPtr nextInstAddr, int relOffset)
+ {
+ if (this.Is32BitProcess) throw new NotSupportedException("32 bit is not supported.");
+ return nextInstAddr + relOffset;
+ }
+
+ ///
+ /// 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 = this.IsCopy ? this.moduleCopyPtr : this.TextSectionBase;
+
+ var scanRet = Scan(mBase, this.TextSectionSize, signature);
+
+ if (this.IsCopy)
+ scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset);
+
+ var insnByte = Marshal.ReadByte(scanRet);
+
+ if (insnByte == 0xE8 || insnByte == 0xE9)
+ return ReadCallSig(scanRet);
+
+ return scanRet;
+ }
+
+ ///
+ /// Free the memory of the copied module search area on object disposal, if applicable.
+ ///
+ public void Dispose()
+ {
+ Marshal.FreeHGlobal(this.moduleCopyPtr);
+ }
+
+ ///
+ /// 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 real offset of the signature.
+ private static IntPtr ReadCallSig(IntPtr sigLocation)
+ {
+ var jumpOffset = Marshal.ReadInt32(IntPtr.Add(sigLocation, 1));
+ return IntPtr.Add(sigLocation, 5 + jumpOffset);
+ }
+
+ private static (byte[] needle, bool[] mask) ParseSignature(string signature)
+ {
+ signature = signature.Replace(" ", string.Empty);
+ if (signature.Length % 2 != 0)
+ throw new ArgumentException("Signature without whitespaces must be divisible by two.", nameof(signature));
+ var needleLength = signature.Length / 2;
+ 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] = 0;
+ mask[i] = true;
+ continue;
+ }
+
+ needle[i] = byte.Parse(hexString, NumberStyles.AllowHexSpecifier);
+ mask[i] = false;
+ }
+
+ 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;
+ }
+
+ private void SetupSearchSpace(ProcessModule module)
+ {
var baseAddress = module.BaseAddress;
// We don't want to read all of IMAGE_DOS_HEADER or IMAGE_NT_HEADER stuff so we cheat here.
@@ -97,25 +300,27 @@ namespace Dalamud.Game {
var optionalHeader = fileHeader + 20;
IntPtr sectionHeader;
- if (Is32BitProcess) // IMAGE_OPTIONAL_HEADER32
+ if (this.Is32BitProcess) // IMAGE_OPTIONAL_HEADER32
sectionHeader = optionalHeader + 224;
else // IMAGE_OPTIONAL_HEADER64
sectionHeader = optionalHeader + 240;
// IMAGE_SECTION_HEADER
var sectionCursor = sectionHeader;
- for (var i = 0; i < numSections; i++) {
+ for (var i = 0; i < numSections; i++)
+ {
var sectionName = Marshal.ReadInt64(sectionCursor);
// .text
- switch (sectionName) {
+ switch (sectionName)
+ {
case 0x747865742E: // .text
- TextSectionOffset = Marshal.ReadInt32(sectionCursor, 12);
- TextSectionSize = Marshal.ReadInt32(sectionCursor, 8);
+ this.TextSectionOffset = Marshal.ReadInt32(sectionCursor, 12);
+ this.TextSectionSize = Marshal.ReadInt32(sectionCursor, 8);
break;
case 0x617461642E: // .data
- DataSectionOffset = Marshal.ReadInt32(sectionCursor, 12);
- DataSectionSize = Marshal.ReadInt32(sectionCursor, 8);
+ this.DataSectionOffset = Marshal.ReadInt32(sectionCursor, 12);
+ this.DataSectionSize = Marshal.ReadInt32(sectionCursor, 8);
break;
}
@@ -123,166 +328,17 @@ namespace Dalamud.Game {
}
}
- private IntPtr moduleCopyPtr;
- private long moduleCopyOffset;
-
- private unsafe void SetupCopiedSegments() {
- Log.Verbose("module copy START");
+ private unsafe void SetupCopiedSegments()
+ {
// .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!");
- }
+ this.moduleCopyPtr = Marshal.AllocHGlobal(this.Module.ModuleMemorySize);
+ Buffer.MemoryCopy(
+ this.Module.BaseAddress.ToPointer(),
+ this.moduleCopyPtr.ToPointer(),
+ this.Module.ModuleMemorySize,
+ this.Module.ModuleMemorySize);
- ///
- /// Free the memory of the copied module search area on object disposal, if applicable.
- ///
- public void Dispose() {
- Marshal.FreeHGlobal(this.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.
- ///
- /// The signature.
- /// The real offset of the found signature.
- public IntPtr ScanText(string signature) {
- var mBase = IsCopy ? this.moduleCopyPtr : TextSectionBase;
- var scanRet = Scan(mBase, TextSectionSize, signature);
- if (IsCopy)
- scanRet = new IntPtr(scanRet.ToInt64() - this.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.
- ///
- /// The address the CALL sig resolved to.
- /// The real offset of the signature.
- 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.
- ///
- /// 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) {
- var instrAddr = ScanText(signature);
- instrAddr = IntPtr.Add(instrAddr, offset);
- var bAddr = (long)Module.BaseAddress;
- long num;
- do {
- instrAddr = IntPtr.Add(instrAddr, 1);
- num = Marshal.ReadInt32(instrAddr) + (long)instrAddr + 4 - bAddr;
- } while (!(num >= DataSectionOffset && num <= DataSectionOffset + DataSectionSize));
-
- return IntPtr.Add(instrAddr, Marshal.ReadInt32(instrAddr) + 4);
- }
-
- ///
- /// 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);
- return scanRet;
- }
-
- ///
- /// 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);
- return scanRet;
- }
-
- public IntPtr Scan(IntPtr baseAddress, int size, string 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;
- }
-
- 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));
- var needleLength = signature.Length / 2;
- 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] = 0;
- mask[i] = true;
- continue;
- }
-
- needle[i] = byte.Parse(hexString, NumberStyles.AllowHexSpecifier);
- mask[i] = false;
- }
-
- 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;
+ this.moduleCopyOffset = this.moduleCopyPtr.ToInt64() - this.Module.BaseAddress.ToInt64();
}
}
}