Esempio n. 1
0
        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);
            }
        }