Function Hooks - TekkaGB/Inaba-Exe-Patcher GitHub Wiki
Function hooks allow you to hook into the game's existing code and either replace or add onto it using assembly instructions.
Example
If you just want to look at a heavily commented example check out Function Hooks Example.expatch.
Specification
Below is a specification for the format used by expatch function hooks, any heading suffixed with an * is mandatory for the patches to work, everything else is optional.
Declaring A Function Hook*
To start a function hook put a [patch]
tag in your expatch, optionally you can also name your patches (this is recommended as the names are used in logging) by putting it inside of the tag such as [patch My Patch]
.
Everything below this patch tag up until another patch or replacement tag will be considered part of the same patch.
Code Location*
Patches use signature scanning to decide where in memory you want to apply them. For information about creating patterns please check Reloaded's signature scanning cheat sheet.
Once you have a pattern for your desired section of code you set it using pattern = ...
where everything after the =
is the pattern.
For example, you may have pattern = 6A 4C 6A 24 51 ?? ?? F3 0F
, note that spacing between characters is not required (but it looks nice), and wildcards are done using ??
as described in the cheat sheet. This (as well as all other definitions) needs to be on its own line as patches are parsed on a line-by-line basis.
Offsets
Sometimes it is easier to scan for an address slightly away from where you want your code and then inject at an offset of that. As such you can define an offset using offset = x
where x
is an integer (positive or negative) that will be added to the address found by your pattern.
For example, if you've defined a pattern that finds the address 1340
but the code you actually want is at 1337
you could have offset = -3
inside of your patch.
Indices
If there are duplicates of what you are patching and you either can't get a unique pattern for the one you want to patch or you want to patch multiple you can use indices. By adding index = x, y, z
you can patch specific occurrences of your pattern. If you just want to patch every occurrence of it you can instead use index = all
. If you do not define an index only the first occurrence will ever be patched.
For example, if you are trying to patch something that has 5 duplicate occurrences of it and you only want to change the 1st and 4th you could add index = 1, 4
to your patch. Whilst indices do work with function hooks they were primarily designed for replacements, if you can avoid using them I recommend you do so.
Execution Order
There are three different ways you can hook into code, either having your new code execute before the existing, after the existing or completely replacing the existing. By default it will replace the existing, however, often you want one of the other options, to do so you can use order = theOrder
where theOrder
is either before
, after
, or only
.
Note that the "existing code" is considered the first 7 or more bytes from the start address. If 7 bytes from the start is in the middle of an instruction it will continue going to the end of that instruction (hence 7 or more).
Writing Code
Anything that isn't a way to define something such as order =
or offset =
that is under a patch is interpreted as assembly code that is assembled when the patch is applied. The specific assembler used is flat assembler (FASM) so if you want to look into what exactly it can do you can check the FASM documentation.
Variables
Sometimes you need a small bit of memory where you can store information that needs to persist between calls of functions or different functions, this is where variables come in. Defining a variable allocates a section in memory of the specified size so you have somewhere that you can place whatever information you need without worrying about it being overwritten by other code.
Creating Variables
Variables are always global to an expatch and as such can be defined wherever you like, a nice place may be at the top of your file however, you can also define them inside of patches or replacements (they won't be local to those though).
To define a variable use var variableName(size) = defaultValue
. Here size
is the number of bytes you want to allocate (you can exclude it and it will default to 4 or the length of the string for strings) and defaultValue
is the value to start the variable at, this can be any valid value type such as a string, int or float (for more information on types check out Value Types). Setting a default value is technically optional however, it is highly recommended that you do as there is no guarantee as to the value of your memory unless you do so.
Using Variables
Using variables is as simple as putting {variableName}
anywhere inside of your expatch file. Internally this tag will be replaced with the address of the assigned memory so it can really be used anywhere (even in replacements!).
For example, you could check the value of a variable like this cmp byte [{inSkillMenu}], 1
and you could change the value of a variable like this mov byte [{inSkillMenu}], 0
. Of course, there are many other things you could do with a variable, however, this isn't a tutorial on assembly, these are just to get you thinking.
Constants
Constants are named values that you can use in expatches for things like adding basic "configuration" to your mods or just to remove magic numbers. Unlike variables these are not assigned space in memory, instead, they simply act as values that are found and replaced throughout your expatch file. They are also global and can be used before or after they're defined.
To define a constant use const constantName = value
. Here value
does not have to be a valid value type, it is simply any text (there is no parsing done directly on it).
To use a constant similarly to variables you simply put {constantName}
anywhere in your file.
Scanning For Constants
When defining a constant you can set the value as scan(pattern)
and a signature scan will be performed, the address found will then be used as the constant's value.
For example, you could define the constant const timePtr = scan(E8 ?? ?? ?? ?? A1 ?? ?? ?? ?? 8D 55 ?? 8B 0D ?? ?? ?? ??)
and then use that value in the assembly code mov dword edx, [{timePtr} + 106]
if the address changes between versions of the game.
Macros
Included with expatches are a few macros that make writing assembly code a bit easier. These are used in a similar manner to constants and variables where you surround the function name in curly braces such as {pushCaller}
.
{pushCaller}
pushes eax, ecx, edx in that order{popCaller}
pops edx, ecx, eax in that order{pushXmm}
pushes xmm0 to xmm7 (0-15 for 64 bit) to the stack (restore them using popXmm){popXmm}
pops xmm7 to xmm0 (15-0 for 64 bit) back from the stack (use after pushing them with pushXmm){pushXmm2}
pushes a specfic xmm register to the stack (in this case xmm2){popXmm2}
pops a specific xmm register back from the stack (in this case xmm2){patchAddress}
the address of the start of the current patch including any offset ({replacementAddress}
and this are interchangeable){replacementAddress}
the address of the start of the current replacement including any offset ({patchAddress}
and this are interchangeable){jump address}
assembles an absolute jump to the specified address- Can use hex with
0x
prefix, or decimal as well as addition or subtraction - Can use
{jmp address}
instead of{jump address}
(does the same thing, just a different way to type it) - e.g.
{jump {patchAddress + 20}}
- Can use hex with
{call address}
assembles an absolute call to the specified address- Can use hex with
0x
prefix, or decimal as well as addition or subtraction - e.g.
{call {patchAddress - 0x69}}
- Can use hex with