Hooking

Hooking is a C++ exclusive feature of SML that allows you to attach a custom function body to a existing function.

All C/C++ functioning hooking stuff can be found in #include "Patching/NativeHookManager.h". All Blueprint function hook stuff can be found in #include "Patching/BlueprintHookManager.h".

C/C++ Function Hooks

The hooking interface provides distinct 3 ways of hooking functions, each of which have two types of call order.

If multiple hooks are attached to the same function, these hooks will then get called in the order they were registered.

There is a normal hook which gets called before the actual function gets called. Through this hook you are able to prevent the final function call and you are also able to overwrite the return value. If you cancel the execution of the final function, you can prevent the following hooks from being called. Keep in mind that this means that another hook can prevent your hooks from being called by Satisfactory. The normal hook’s signature is void(TCallScope<HookFuncSignature>&, hookFuncParams). If you hook a member function, the this pointer is handled like a parameter and is the first parameter used. As long as you don’t cancel the final function execution, or do it yourself by calling the scope object, the final function will be implicitly called after your hook function returns.

Sometimes when hooking functions that return custom types such as structs and FStrings, the game can unexpectedly crash! The cause of this is not fully known and often depends on the function being hooked. As such, try to avoid hooking functions that return said custom types wherever possible.

The call scope object allows you to:

  • Cancel the final function execution (if the hook function returns void).

    void hook(TCallScope<...>& Scope, class* exampleClass, int exampleArg) {
     Scope.Cancel();
    }
  • Call the next hooks and the final function within your body. Calling the scope need to have all the same parameters as the func hook signature.

    void hook(TCallScope<int(int)>& Scope, class* exampleClass, int exampleArg) {
    	// stuff before final function call
    	int result = Scope(exampleClass, exampleArg); // call following hooks (final function might get called as long as following hooks don't cancel/overwrite it)
    	// stuff after final function call
    }
  • You can also override the return value before the final call (causes the final call to not occur)

    void hook(TCallScope<int(int)>& Scope, class* exampleClass, int exampleArg) {
    	// final function might get called
    	Scope.Override(customReturnValue);
    	// final function wont get called anymore
    }

Since you still want to make sure your hook gets called, no care about if the final function got called or not we introduce the "after" hooks. These hooks get all called after the normal hook calls and only allow you to read the parameters as well as the resulting return value. That means you can’t influence the final function call. Also, don’t use the TCallScope object, instead the first parameter of your hooks signature is the return value following by the function call parameters.

void hook(int returnValue, int exampleArg) {
	// do some stuff
}

The 2 Types of Hooks

By 'hook types' we mean the different ways of attaching a hook to a function. Each attachment method works differently under the hood, and it’s important to pay attention to the key differences between the different types of hooks.

Be aware that type of return values and parameters etc has nothing to do with each other or if it is a member function, you can use them in any way. Note that the Hook function is a std::function, which means that it can be any type a std::function can accept, such as function pointers, function pointers with bound placeholders, or even lambdas.

The behavior of hooks at editor time is highly unpredictable and can cause crashes. As such, you should ensure that your hooks are not applied at editor time, else you could potentially be unable to open the Unreal Editor until you edit the hook code externally.

The most convenient way is to put the SUBSCRIBE_ macros in your code inside an if (!WITH_EDITOR) { …​ } block:

if (!WITH_EDITOR) {
	SUBSCRIBE_METHOD(SomeClass::SomeMethod, &Hook_SomeMethod);
}

Using #if !WITH_EDITOR and #endif directives is also an option, but it is not recommended: it hides errors until building shipping and confuses IDEs, making development and debugging slightly more annoying with no benefit.

Type: SUBSCRIBE_METHOD

The SUBSCRIBE_METHOD-Macro attaches a hook such that the code you pass will be called before the function executes.

If multiple mods have subscribed to the same function, the hooks will be called in the order they were registered.

Usage goes as following:

// in target class SomeClass
public:
	int MemberFunction(int arg1);
	static void StaticFunction();

// in your code
#include "Patching/NativeHookManager.h"

void registerHooks() {
	SUBSCRIBE_METHOD(SomeClass::MemberFunction, [](auto& scope, SomeClass* self, int arg1) {
		// do some nice stuff there
	});

	SUBSCRIBE_METHOD(SomeClass::StaticFunction, [](auto& scope) {
		// do some nice stuff there
	});
}

Hooking an overloaded function might not work as intended since the compiler has no clue what exact symbol you now want to hook. For that you should have an look at the SUBSCRIBE_METHOD_MANUAL-Macro which allows you to explicitly set the symbol you want to hook.

Type: SUBSCRIBE_METHOD_VIRTUAL

The SUBSCRIBE_METHOD_VIRTUAL macro attaches the given hook to the function passed by pointer for the given class.

This hook only modifies the function that the virtual table for the given class points to. Functions in subclasses overriding the virtual function of the given class won’t be modified, but the hook will still run if the hooked function is called by the overriding implementation (i.e. "calls super"). If the overriding implementation of a subclass does not "call super", you have to hook said subclass separately. Because pure virtual functions do not have a proper function body, they cannot possibly be hooked.

Usage goes as following:

// in target parent class SomeClass
public:
	virtual int MemberFunction(int arg1);

// in child class SomeChild that we don't want to hook
// class SomeChild : public SomeClass
public:
	virtual int MemberFunction(int arg1) override;

// in your code
#include "Patching/NativeHookManager.h"

void registerHooks() {
	SUBSCRIBE_METHOD_VIRTUAL(SomeClass::MemberFunction, SomeClass, [](auto& scope, SomeClass* self, int arg1) {
		// do some nice stuff there
	});

	SomeClass parent;
	parent->MemberFunction(0); // hook gets called
	SomeChild c;
	c->MemberFunction(1); // hook does not get called
}

Special Cases

Depending on the type of function you are attempting to hook and what you want to do with it, you may need to make some adjustments.

Const Functions

When hooking a const function you will need to prefix the "self" pointer with const.

Non-Const Function

(auto& scope, SomeClass* self)

Const Function

(auto& scope, const SomeClass* self)

Hooking AFTER

For "after" hooks, add the _AFTER postfix to the macro names.

Be aware that the hook function signature changes accordingly and will no longer need the "scope":

Non-Virtual

SUBSCRIBE_METHOD_AFTER(SomeClass::MemberFunction, [](SomeClass* self))

Virtual

SUBSCRIBE_METHOD_VIRTUAL_AFTER(SomeClass::MemberFunction, [](SomeClass* self))

FORCEINLINE Functions

Functions that are FORCEINLINE cannot be hooked.

UFUNCTIONs

A function being a UFUNCTION or not makes no difference on whether it can be hooked.

Unhooking

Unhooking functionality has not been extensively tested. Please report issues you encounter on the Discord.

Macros will return a delegate that can be used with the UNSUBSCRIBE_METHOD or UNSUBSCRIBE_UOBJECT_METHOD macro respectively in order to unsubscribe from the function.

Blueprint-Hooking

Blueprint function hooking works by changing the instructions of a Blueprint UFunction so that first your hook gets called.

The hook function signature is void(FBlueprintHookHelper&). This helper structure provides a couple of functions allowing you to read and write data to local function (including parameters), output parameters and accessing the context pointer.

You can attach a hook with the HookBlueprintFunction-Macro which takes a pointer to the UFunction you want to attach the hook to.

Usage goes as following:

#include "Patching/BlueprintHookManager.h"

void registerHooks() {
	UClass* SomeClass = ...;
	UFunction* SomeFunc = SomeClass->FindFunctionByName(TEXT("TestFunc"));

	HookBlueprintFunction(SomeFunc, [](FBlueprintHookHelper& helper) {
		UObject* ctx = helper.GetContext(); // the object this function got called onto
		FString* localStr = helper.GetLocalVarPtr<FString>("StrVariable"); // getting the pointer to a local variable
		FString* output = helper.GetOutVariablePtr<FString>("OutValue"); // getting the pointer to a output variable
		// do some nice stuff there
	});
}

You can also provide a count of instruction as third parameter to hook as instruction based offset from the top. But we highly encourage you to not do so unless you know what you exactly do!

Protected/Private Function Hooking

If the function you are attempting to hook is protected or private to that specific class, you must use the friend declaration.

This also means that you can only hook this function from a class, not global scope.

For example, let’s assume you have a class called MyWatcher in a namespace called MyMod, and you wish to hook the function EnterChatMessage from AFGPlayerController class.

The suggested method of doing this is with Access Transformers. In your AccessTransformers.ini file you would create the entry:

Friend=(Class="AFGPlayerController", FriendClass="MyWatcher")

Alternatively, you can edit the header files directly. This is not advisable for reasons described in more detail on the Access Transformers page. You must first edit the FGPlayerController.h header and add the following block of code to it:

namespace MyMod
{
    class MyWatcher;
}

Then you have to add the friend declaration to the class itself, in result, it should look like this:

...

class FACTORYGAME_API AFGPlayerController : public AFGPlayerControllerBase
{
	GENERATED_BODY()
public:
	friend MyMod::MyWatcher;

...
}