internal List <Patch> PatchJumpTargets(AddressRange searchRange, long originalJmpTarget, AddressRange jumpTargetRange, IntPtr newOriginalPrologue) { /* * On both modern Intel and AMD CPUs, the instruction decoder fetches instructions 16 bytes per cycle. * These 16 bytes are always aligned, so you can only fetch 16 bytes from a multiple of 16. * * Some hooks, such as Reloaded.Hooks itself exploit this for micro-optimisation. * Valve seems to be doing this with the Steam overlay too. */ const int intelCodeAlignment = 16; const int immediateAreaSize = intelCodeAlignment * 4; // Keep as multiple of code alignment. var result = new List <Patch>(); /* * When looking at a whole page range, there are occasional cases where the * padding (e.g. with 00 padding) may lead to having the instruction incorrectly decoded * if we start disassembling from the page. * * Therefore we first test only the immediate area at specifically crafted starting points * to take account either the code being aligned or unaligned. * * This should fix e.g. the odd case of Steam Overlay hooks not being patched. * We only expect one jump in practically all cases so it's safe to end if a single jump is found. */ // Case 1: Our jump target is part of an aligned 16 byte code frame. // Check 1 frame before + 3 frames ahead if (TryCodeAlignmentRange(AddressRange.FromStartAndLength(originalJmpTarget / intelCodeAlignment * intelCodeAlignment, immediateAreaSize))) { return(result); } // Case 2: Original code comes before jump target and is in the frame before. // Check 2 frame before + 0 frames ahead if (TryCodeAlignmentRange(AddressRange.FromStartAndLength((originalJmpTarget / intelCodeAlignment * intelCodeAlignment) - intelCodeAlignment, intelCodeAlignment * 2))) { return(result); } // Case 3: Code is unaligned and original code comes right after the jump target. // 0 frames before + 2 frames ahead. if (TryCodeAlignmentRange(AddressRange.FromStartAndLength(originalJmpTarget, intelCodeAlignment * 2))) { return(result); } // Case 4: Fall back to searching whole memory page. // This is successful 90% of the time, but sometimes fails due to code misalignment // (disassembler can interpret 00 as beginning of an instruction when it's padding). var patchesForPage = PatchJumpTargets_Internal(searchRange, jumpTargetRange, (long)newOriginalPrologue); result.AddRange(patchesForPage); return(result); bool TryCodeAlignmentRange(AddressRange range) { // Clamp to current memory page if the start/end cannot be read. if (range.StartPointer < searchRange.StartPointer && Utilities.IsBadReadPtr((IntPtr)range.StartPointer)) { range.StartPointer = searchRange.StartPointer; } if (range.EndPointer > searchRange.EndPointer && Utilities.IsBadReadPtr((IntPtr)range.EndPointer)) { range.EndPointer = searchRange.EndPointer; } var patchesForImmediateArea = PatchJumpTargets_Internal(range, jumpTargetRange, (long)newOriginalPrologue); result.AddRange(patchesForImmediateArea); return(patchesForImmediateArea.Count > 0); } }