Sometimes you really need to redirect a UClass implementation, and CoreRedirects aren’t enough. In my case, I needed to redirect the UOnlineEngineInterface
implementation, even though the path is hard coded within the engine.
First a bit of context. What even is this interface? Well this interface contains an assortment of methods relating to online functionality where the calling code doesn’t just use the online subsystem directly. The header is actually defined in the engine itself under Engine/Public/Net/OnlineEngineInterface.h
, unlike the rest of the online code which tends to sit in plugins.
It contains everything from obtaining the online subsystem instance name for a particular world (used during play-in-editor with multiple instances running), to trying to automatically log in players in the base AGameSession
implementation. That last case caused issues in the EOS Online Subsystem plugin, because calling AutoLogin
with a user that is already signed in can be treated as a “please link an Epic Games account” and that would pop up a web browser:
Note: This code is from the Unreal Engine source code and is not MIT licensed.
In order to fix the issue of AGameSession
causing a web browser to open, we basically need to turn this AutoLogin
call into a no-op, instead of allowing the call to propagate to the online subsystem.
When we look at the implementation of Get
, we find that the engine loads the UClass implementation from a hard coded path:
Note: This code is from the Unreal Engine source code and is not MIT licensed.
This is… not great. I don’t know about you, but I can’t exactly reach out to the OGS team. If you’re using a custom version of Unreal Engine, you can easily change the path yourself, but in my case I’m shipping a plugin that has to work with pre-built versions of Unreal Engine, so that’s not an option.
But hey, it’s a UClass path, so we should be able to use [CoreRedirects]
to point the reference somewhere else right?
Not quite. When we dig into the implementation StaticLoadClass
and debug it, we find that the only redirects that apply to this kind of call are Package Redirects, not Class Redirects. The problem is that the package it’s located in is OnlineSubsystemUtils
. This package also contains the implementation of UIpNetDriver
, making it impractical to copy or redirect the package as a whole.
So with [CoreRedirects]
eliminated, we need to find a different strategy.
Unreal Engine contains a bunch of hashtables that map string paths to UObject instances. If you’re interested in how this happens, you can take a look at HashObject
inside UObjectHash.cpp
.
If we could somehow register our implementation with the OnlineSubsystemUtils.OnlineEngineInterfaceImpl
name in the hashtables, then when Unreal Engine goes to run StaticLoadClass
, it will point at our code instead.
It will still think it’s loading the original class, but the hashtables have been pointed elsewhere, and that’s good enough for us.
Well it turns out, this is actually pretty tricky. Most of the functions that surface modifying or interacting with these hashtables are private or not declared with CORE_API
, meaning we can’t access them from our plugin code.
Interestingly enough though, UObjectBase
has a LowLevelRename
function. It’s declared protected
which means we can access it on derived implementations of UObjectBase
(which all UObjects are).
The problem is we’re not trying to rename the path to our UObject instance, we’re trying to rename the path to our UObject class. These are different. If we just call LowLevelRename
inside a function on our UObject instance, we’d be redirecting a UClass
into a UWhateverMyInstanceIs
, and those types aren’t comparable at all.
But the thing is, once you can get the pointer to a function, you can call it. We know that both UClass
and UWhateverOurOverridingClassIs
both inherit from UObjectBase
. When we check UClass
, we can see that at no point does it or any of it’s parents override LowLevelRename
. Which means that if we grab the address of LowLevelRename
from inside UWhateverOurOverridingClassIs
and then we call it on our UClass
, we’ll be able to effectively call LowLevelRename
on the UClass
instance without the limitations of protected
getting in our way.
Doing that, looks like this:
Now this code might look a bit weird, so let’s break it down for a moment.
We get the pointer to the LowLevelRename
function with &UOnlineEngineInterfaceEOS::LowLevelRename
. We get a pointer to our UClass
instance with UOnlineEngineInterfaceEOS::StaticClass()
. Then we use the ->*
pointer-to-member operator to be able to invoke LowLevelRename
in the context of the UClass
pointer.
With our function call now operating in the context of our UClass
, we call it and rename our UClass
instance over the top of the one already registered by OnlineSubsystemUtils
. Later, when StaticLoadClass
runs, it looks up the hashtables as normal and loads our implementation instead.
Now it’s important to note, you must do this after the target module you are overriding has registered it’s UObject classes, but before any code has tried to actually get an instance with StaticLoadClass
. Otherwise you can end up with either your override not working at all, or only parts of the runtime referring to your override.
For completeness, this is how you should invoke DoHack
, within your StartupModule
function:
It should go without saying, but modifying the UObject hashtables with pointer magic like this is a pretty big hack. You should try everything else - CoreRedirects, forking the engine, anything - before you go down this path. I would not be surprised if a future version of Unreal Engine breaks this code.
But at least for now, when you’re all out of options, you can have one last trick up your sleeve to really make the engine do what you want.
All code examples are MIT licensed unless otherwise specified.