Interface IFunctionHooks
- Namespace
- SkyrimForge
- Assembly
- SkyrimForge.dll
Hooks a game function at a virtual method table entry, a call instruction, or the function's entry.
public interface IFunctionHooks
Remarks
A hook here redirects a whole function to a plugin's method. An InstructionHook, applied through ICodePatcher, runs a handler at one instruction inside a function instead.
A replacement is the address of a static method marked UnmanagedCallersOnly, whose parameters and calling convention match the game function's.
A parameter of a view type addresses nothing where the game passes null, and Exists tests for that.
The game calls a replacement directly, on the calling thread. An exception that reaches game code ends the process, and the framework's fault log does not record it.
The process also ends where managed code reaches the hooked function 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 is called in that mode.
A replacement on a function the game calls often avoids allocating, since a garbage collection inside it stalls the game.
The calling thread can be any thread, so work that calls a member documented for the game's thread is queued through ITaskQueue.
A replacement calls the displaced address at most once on any path, a catch block included.
A hook remains installed until the process exits. A table entry, call instruction, or function entry accepts one hook, and a second throws, as does a hook overlapping one already recorded or a patch ICodePatcher applied. The exception names the owner of the recorded hook or patch: the plugin that wrote it, or SkyrimForge. SkyrimForge.Runtime.log lists each recorded hook's owner and its address as a module and an offset, and records each hook another module has changed by the time the game's data has loaded, as ICodePatcher describes. The framework hooks the call from MainLoop to Main__Update. It also detours BGSStoryEventManager__AddEvent_Impl, TESQuest__GetJournalTextForInstance, DialogueItem__Ctor, and BookMenu__OpenMenu_Impl, and replaces entry 0x4C of Character's table, each once a plugin uses the service that depends on it.
Methods
DetourFunction(GameFunction, nint)
Detours a game function's entry, so every call to the function reaches the replacement.
nint DetourFunction(GameFunction function, nint replacement)
Parameters
functionGameFunctionThe function, by its address library identifiers.
replacementnintThe replacement's address, subject to the remarks on IFunctionHooks.
Returns
- nint
The address of a trampoline, which the replacement calls to reach the game's own code.
Remarks
The write over the entry is not atomic, so a function is detoured while no thread is executing it. A plugin's Start is such a moment for most functions.
Exceptions
- ArgumentOutOfRangeException
replacementis 0.- InvalidOperationException
The address library does not name the function, its entry is not game code, its opening instructions cannot be moved, overlap a recorded hook, or contain the target of a recorded jump or call past their first byte, it is already detoured, or the trampoline has no room. Nothing is written.
FindCallSite(GameFunction, GameFunction, int, int)
Finds the call instruction by which one game function calls another.
nint FindCallSite(GameFunction caller, GameFunction callee, int occurrence = 0, int searchLength = 512)
Parameters
callerGameFunctionThe function containing the call.
calleeGameFunctionThe function the call targets.
occurrenceintThe zero-based index of the call among the caller's calls to the callee.
searchLengthintThe number of bytes to search from the caller's entry.
Returns
- nint
The call instruction's address. It is 0 where the searched bytes contain no such call, the address library does not name either function, or the call's target is already replaced. A call another SKSE plugin replaced is found by FindCallSite(GameFunction, GameOffset) instead.
Exceptions
- ArgumentOutOfRangeException
occurrenceis negative, orsearchLengthis less than 1 or greater than 0x2000.
FindCallSite(GameFunction, GameOffset)
Finds the call instruction at an offset into a game function.
nint FindCallSite(GameFunction caller, GameOffset offset)
Parameters
callerGameFunctionThe function containing the call.
offsetGameOffsetThe call instruction's offset from the caller's entry.
Returns
- nint
The call instruction's address. It is 0 where the offset does not address a call to the game's code or to an SKSE trampoline stub, or the address library does not name the caller.
Remarks
An offset is valid only for the game build on which it was measured. A call already replaced through SKSE's trampoline, by another SKSE plugin or by a SkyrimForge plugin, is found as well: its target is the stub SKSE placed for it.
ReplaceCallTarget(nint, nint)
Replaces the target of one call instruction and returns the target it displaced.
nint ReplaceCallTarget(nint callSite, nint replacement)
Parameters
callSitenintThe call instruction's address, as FindCallSite(GameFunction, GameFunction, int, int) returns it.
replacementnintThe replacement's address, subject to the remarks on IFunctionHooks.
Returns
Remarks
The game can run the replacement before this returns the target it displaced, when the replacement has no address to call yet, so a call is replaced while no thread executes it. A plugin's Start is such a moment for most calls.
A call another SKSE plugin already replaced through SKSE's trampoline can be replaced again. The displaced target is then the stub that leads to that plugin's replacement, so a replacement that calls the displaced target runs that plugin's code as well.
Exceptions
- ArgumentOutOfRangeException
callSiteorreplacementis 0.- InvalidOperationException
The address is not a call instruction in game code, the call overlaps a recorded hook, or its target is neither game code nor an SKSE trampoline stub.
ReplaceVirtualTableEntry(string, int, nint)
Replaces one entry of a game type's virtual method table and returns the entry it displaced.
nint ReplaceVirtualTableEntry(string typeName, int index, nint replacement)
Parameters
typeNamestringThe game type's CommonLib name, such as "TESNPC".
indexintThe entry's index in the game's table.
replacementnintThe replacement's address, subject to the remarks on IFunctionHooks.
Returns
Remarks
Only calls through this type's table, on objects of exactly this type, reach the replacement. Only that the entry addresses game code is checked, so an index naming another entry of the table replaces that entry. The game can call the replacement before this returns the entry it displaced, when the replacement has no address to call yet, so an entry is replaced while no thread calls the function. A plugin's Start is such a moment for most functions.
Exceptions
- ArgumentNullException
typeNameisnull.- ArgumentOutOfRangeException
indexis negative or greater than 0x1000, orreplacementis 0.- InvalidOperationException
Either no table of the name has an address in the running game version, as for a name the framework does not declare or a class a game update removed, or the entry does not address game code, as when the index lies past the table's end or the entry is already replaced.