Class InstructionHook
- Namespace
- SkyrimForge
- Assembly
- SkyrimForge.dll
Runs a handler on each thread that reaches the instruction at Address, before that instruction runs.
public sealed class InstructionHook : CodePatch
- Inheritance
-
InstructionHook
- Inherited Members
Remarks
IFunctionHooks replaces a whole function, or a call to one, with a plugin's method. An instruction hook runs a handler at one instruction inside a function and leaves the function's code in place: the thread then runs the hooked instruction and continues, with every register as the handler left it. The handler skips the hooked instruction, or continues anywhere else, by setting Rip. To run a handler after an instruction, hook the one following it.
SafetyHook writes a five-byte jump over the whole instructions beginning at the address, and moves them to a trampoline that runs them after the handler. The expected bytes cover every moved byte: at least the instructions spanning the first five bytes. SafetyHook writes a fourteen-byte absolute jump instead when no trampoline lies within a 32-bit displacement of the address, and Apply(params ReadOnlySpan<CodePatch>) then throws unless the expected bytes cover the instructions spanning those fourteen bytes. Apply(params ReadOnlySpan<CodePatch>) throws where a moved instruction is a call that another moved instruction follows, where a moved branch targets a moved byte, or where a direct branch elsewhere in the function lands inside the moved bytes past their first byte. It detects a direct branch in the function part the exception directory lists, not a branch through a jump table or a register. A moved call runs from the trampoline, which has no unwind data, so an exception or a stack walk passing through that call is not unwound correctly.
The handler runs on the thread that reached the instruction, which can be any thread. An exception escaping it is written to the plugin's log, and the process then ends.
The process also ends where managed code reaches the instruction through a P/Invoke marked SuppressGCTransition,
since such a call leaves the thread in the runtime's cooperative mode, and the runtime ends the process when an UnmanagedCallersOnly method,
through which the handler is entered, is called in that mode.
A handler on code the game runs often avoids allocating, since a garbage collection inside it stalls the game.
A hook remains until the process exits, so a handler that should act only part of the time tests a flag of its own.
At most 1024 instruction hooks exist in a process, and Apply(params ReadOnlySpan<CodePatch>) throws for a hook past that number.
Constructors
InstructionHook(nint, BytePattern, InstructionHandler)
Creates a hook running handler before the instruction at address.
public InstructionHook(nint address, BytePattern expected, InstructionHandler handler)
Parameters
addressnintThe address of the hooked instruction.
expectedBytePatternThe bytes the code at
addressmust match: at least the whole instructions spanning the first five bytes.handlerInstructionHandlerThe handler run before the instruction.
Exceptions
- ArgumentOutOfRangeException
addressis 0, orexpectedis shorter than five bytes.- ArgumentNullException
expectedorhandlerisnull.
Properties
Handler
The handler run before the instruction.
public InstructionHandler Handler { get; }