Unreal Engine 4/5 Scripting System
WARNING: This is the dev version of the UE4SS docs. The API and features are subject to change at any time. If you are developing mods for UE4SS, you should reference the latest release instead.
Lua scripting system platform, C++ Modding API, SDK generator, blueprint mod loader, live property editor and other dumping utilities for UE4/5 games.
Major features
- Lua Scripting API: Write lua mods based on the UE object system
- Blueprint Modloading: Spawn blueprint mods automatically without editing/replacing game files
- C++ Modding API: Write C++ mods based on the UE object system
- Live Property Viewer and Editor: Search, view, edit & watch the properties of every loaded object, great for debugging mods or figuring out how values are changed during runtime
- UHT Dumper: Generate Unreal Header Tool compatible C++ headers for creating a mirror .uproject for your game
- C++ Header Dumper: Generate standard C++ headers from reflected classes and blueprints, with offsets
- Universal UE Mods: Unlock the game console and other universal mods
- Dumpers for File Parsing: Generate
.usmap
mapping files for unversioned properties - UMAP Recreation Dumper: Dump all loaded actors to file to generate
.umaps
in-editor - Other Features, including Experimental features at times
Targeting UE Versions: From 4.12 To 5.5
The goal of UE4SS is not to be a plug-n-play solution that always works with every game. The goal is to have an underlying system that works for most games. You may need to update AOBs on your own, and there's a guide for that below.
Basic Installation
The easiest installation is via downloading the non-dev version of the latest non-experimental build from Releases and extracting the zip content to /{Gameroot}/GameName/Binaries/Win64/
.
If your game is in the custom config list, extract the contents from the relevant folder to Win64
as well.
If you are planning on doing mod development using UE4SS, you can do the same as above but download the zDEV version instead.
Links
Generating UHT compatible headers
Creating Compatible Blueprint Mods
Unreal Engine Modding Discord Server Invite
Build requirements
- A computer running Windows.
- Linux support might happen at some point but not soon.
- A version of MSVC that supports C++23:
- MSVC toolset version >= 14.39.0
- MSVC version >= 19.39.0
- Visual Studio version >= 17.9
- More compilers will hopefully be supported in the future.
- Rust toolchain >= 1.73.0
- xmake >= 2.9.3
Build instructions
- Clone the repo.
- Execute this command:
git submodule update --init --recursive
Make sure your Github account is linked to your Epic Games account for UE source access. Do not use the--remote
option because that will force third-party dependencies to update to the latest commit, and that can break things. You will need your github account to be linked to an Epic games account to pull the Unreal pseudo code submodule.
There are several different ways you can build UE4SS.
Building from cli
Configuration settings
xmake
allows you to flexibly configure some build options to suit your specific needs. The following is a non-comprehensive list of configuration settings you might find useful.
important
All configuration changes are made by using the xmake config
command. You can also use xmake f
as an alias for config.
After configuring xmake
with any of the following options, you can build the project with xmake
or xmake build
.
Mode
The build modes are structured as follows: <Target>__<Config>__<Platform>
Currently supported options for these are:
-
Target
Game
- for regular games on UE versions greater than UE 4.21LessEqual421
- for regular games on UE versions less than or equal to UE 4.21CasePreserving
- for games built with case preserving enabled
-
Config
Dev
- development buildDebug
- debug buildShipping
- shipping(release) buildTest
- build for tests
-
Platform
Win64
- 64-bit windows
tip
Configure the project using this command: xmake f -m "<BuildMode>"
. -m
is an alias for --mode=<BuildMode>.
Patternsleuth (Experimental)
By default, the patternsleuth tool installs itself as an xmake package. If you do not intend on modifying the patternsleuth source code, then you don't have to configure anything special. If you want to be able to modify the patternsleuth source code, you have to supply the --patternsleuth=local
option to xmake config
in order to recompile patternsleuth as part of the UE4SS build.
Proxy Path
By default, UE4SS generates a proxy based on C:\Windows\System32\dwmapi.dll
. If you want to change this for any reason, you can supply the --ue4ssProxyPath=<path proxy dll>
to the xmake config
command..
Profiler Flavor
By default, UE4SS uses Tracy for profiling. You can pass --profilerFlavor=<profiler>
to the xmake config
command to set the profiler flavor. The currently supported flavors are Tracy
, Superluminal
, and None
.
Version Check
By default, xmake will check if you have the minimum required version of Rust or MSVC installed (if you are using the MSVC toolchain). If you do not, it will throw an error on the configure step. If you want to ignore this check, you can pass --versionCheck=n
to the xmake config
command.
Once you set the flag, the option value be set until you specify otherwise.
Therefore, to not check versions when running xmake project -k vsxmake2022
, you must first run the xmake config --versionCheck=n
command, then run the xmake project -k vsxmake2022
command.
Helpful xmake
commands
You may encounter use for the some of the more advanced xmake
commands. A non-comprehensive list of some useful commands is included below.
Syntax | Aliases | Uses |
---|---|---|
xmake <command> --yes | xmake <command> -y | Automatically confirm any user prompts. |
xmake --verbose <command> | xmake -v <command> | Enable verbose level logging. |
xmake --Diagnostic <command> | xmake -D <command> | Enable diagnostic level logging. |
xmake --verbose --Diagnostic --yes <command> | xmake -vDy <command> | You can combine most flags into a single -flagCombo . |
xmake config | xmake f | Configure xmake with any of these options. |
xmake clean --all | xmake c --all | Cleans binaries and intermediate output of all targets. |
xmake clean <target> | xmake c <target> | Cleans binaries and intermediates of a specific target. |
xmake build | xmake b | Incrementally builds UE4SS using input file detection. |
xmake build --rebuild | xmake b -r | Forces a full rebuild of UE4SS. |
xmake build <target> | xmake b <target> | Incrementally builds a specific target. |
xmake show | Shows xmake info and current project info. | |
xmake show --target=<target> | xmake show -t <target> | Prints lots of information about a target. Useful for debugging scripts, compiler flags, dependency tree, etc. |
xmake require --clean | xmake q -c | Clears all package caches and uninstalls all not-referenced packages. |
xmake require --force | xmake q -f | Force re-installs all dependency packages. |
xmake require --list | xmake q -l | Lists all packages that are needed for the project. |
xmake project --kind=vsxmake2022 --modes="Game__Shipping__Win64" | xmake project -k vsxmake2022 -m "Game__Shipping__Win64" | Generates a Visual Studio project based on your current xmake config uration. You can specify multiple modes to generate by supplying -m "Comma,Separated,Modes" . If you do not supply any modes, the VS project will generate all permutations of modes. |
Opening in an IDE
Visual Studio / Rider
To generate Visual Studio project files, run the xmake project -k vsxmake2022 -m "Game__Shipping__Win64"
command.
Afterwards open the generated .sln
file inside of the vsxmake2022
directory
Note that you should also commit & push the submodules that you've updated if the reason why you updated was not because someone else pushed an update, and you're just catching up to it.
warning
The vs. build plugin performs the compile operation by directly calling the xmake command under vs, and also supports intellisense and definition jumps, as well as breakpoint debugging.
This means that modifying the project properties within Visual Studio will not affect which flags are passed to the build when VS executes xmake
. XMake provides some configurable project settings which can be found in VS under the Project Properties -> Configuration Properties -> Xmake
menu.
caution
If you have multiple Visual Studio versions installed, run xmake f --vs=2022
, otherwise you may encounter issues with the project generation.
Configuring additional modes
tip
Additional modes can be generated by running xmake project -k vsxmake2022 -m "Game__Shipping__Win64,Game__Debug__Win64"
.
Further explanation can be found in the xmake
command table.
Regenerating solution best practices
caution
If you change your configuration with xmake config
, you may need to regenerate your Visual Studio solution to pick up on changes to your configuration. You can simply re-run the xmake project -k vsxmake2022 -m "<modes>"
command to regenerate the solution.
Building Windows binaries on Linux
We only officially support msvc-wine for cross-compiling.
Make sure you have winbind (libwbclient & samba on Arch) installed.
caution
You must use xmake v2.9.7 or later, and as of early December 2024, this version is not yet released which means you must install the dev version of xmake.
You need to install the x86_64-pc-windows-msvc
target (not the windows-gnu
target) with rustup.
When invoking xmake f
, you must set --plat
, --arch
, and --sdk
.
You must also use --ue4ssCross=msvc-wine
, and disable the version check.
The following projects are not supported when cross-compiling and are automatically disabled:
proxy
proxy_generator
UVTD
When invoking the xmake
build command, patternsleuth will automatically be built without xmake.
The binary files are available in deps/first/patternsleuth_bind/target/x86_64-pc-windows-msvc
.
They are automatically used by xmake when --ue4ssCross
is set to msvc-wine
.
Here's an example of a full command that will build Windows binaries on a Linux machine:
xmake f -m "Game__Shipping__Win64" -p windows -a x64 --sdk=/home/<username>/my_msvc/opt/msvc --versionCheck=n --ue4ssCross=msvc-wine
Updating git submodules
If you want to update git submodules, you do so one of three ways:
- You can execute
git submodule update --init --recursive
to update all submodules. - You can also choose to update submodules one by one, by executing
git submodule update --init --recursive deps/<first-or-third>/<Repo>
. Do not use the--remote
option unless you actually want to update to the latest commit. - If you would rather pick a specific commit or branch to update a submodule to then
cd
into the submodule directory for that dependency and executegit checkout <branch name or commit>
. The main dependency you might want to update from time to time isdeps/first/Unreal
.
Credits
All contributors since the project became open source: https://github.com/UE4SS-RE/RE-UE4SS/graphs/contributors
- Original Creator The original creator no longer wishes to be involved in or connected to this project. Please respect their wishes, and avoid using their past usernames in connection with this project.
- Archengius
- UHT compatible header generator
- CasualGamer
- Injector code & aob scanner is heavily based on his work, 90% of that code is his.
- SunBeam
- Extra signature for function 'GetFullName' for UE4.25.
- Regex to check for proper signature format when loaded from ini.
- Lots and lots of work on signatures
- tomsa
- const char* to vector<int> converter
- tomsa: Idea & most of the code
- Original Creator: Nibblet support
- const char* to vector<int> converter
- boop / usize
- New UFunction hook method
- RussellJ
- Blueprint Modloader inspiration
- Narknon
- Certain features and maintenance/rehosting of the project
- DeadMor0z
- Certain features and Lua updates/maintenance
- OutTheShade
- Unreal Mappings (USMAP) Generator
- DmgVol
- Inspiration for map dumper
- Buckminsterfullerene
- Rewriting the documentation, various fixes
- trumank
- Lua bindings generator, various fixes, automation & improvements
- localcc
- C++ API
Thanks to everyone who helped with testing
- GreenHouse
- Otis_Inf
- SunBeam
- Motoson
- hooter
- Synopis
- Buckminsterfullerene
Installation
Core structure concept
There are four concepts you need to know about.
- The
root directory
.- This directory contains the UE4SS dll file.
- The
working directory
.- This directory contains configuration and mod files and is located inside the
root directory
.
- This directory contains configuration and mod files and is located inside the
- The
game directory
.- This directory usually contains a small executable with the name of your game and a folder with the same name.
- This executable is not your actual game but instead it's just a small wrapper that starts any 3rd party launcher such as Steam or if there is none then it launches the real executable.
- Example of a
game directory
:D:\Games\Epic Games\SatisfactoryEarlyAccess\
- The
game executable directory
.- This directory contains the real executable file for your game and is not part of the UE4SS directory structure.
- You can also recognize it as the game executable located there is usually the largest (much larger than the wrapper above) and is the one running as the child process of the wrapper when the game is running.
- Example of a
game executable directory
:D:\Games\Epic Games\SatisfactoryEarlyAccess\FactoryGame\Binaries\Win64\
Choosing an installation method
You can install UE4SS in a couple of different ways.
The goal is to have the *.dll of UE4SS to be loaded by the target game one way or another, and have the configuration files and \Mods\
directory in the correct place for UE4SS to find them.
Method #1 - Basic Install
Using method #1 will mean that the
root directory
andworking directory
are treated as one single directory that happens to also be the same directory as yourgame executable directory
.
The basic install is intended for end-users who are using UE4SS for mods that use it and don't need to do any development work. There should be no extra windows visible when using this method.
The preferred and most straightforward way to install UE4SS is to choose the UE4SS_v{version_number}
download (e.g. UE4SS_v3.0.0
) and then just drag & drop all the necessary files into the game executable directory
.
Now all you need to do is start your game and UE4SS will automatically be injected.
Method #2 - Developer Install
Using method #2 will mean that the
root directory
andworking directory
are treated as one single directory that happens to also be the same directory as yourgame executable directory
.
The developer install is intended for mod developers or users who wish to use UE4SS for debugging, dumping or other utilities. The difference between this version and the basic install version is that there are some extra files included, and slightly different default settings in the UE4SS-settings.ini
file, such as the console and GUI console being visible.
The preferred and most straightforward way to install UE4SS is to choose the zDEV-UE4SS_v{version_number}
download (e.g. zDEV-UE4SS_v3.0.0
) and then just drag & drop all the necessary files into the game executable directory
.
Now all you need to do is start your game and UE4SS will automatically be injected.
Experimental Install
If you want the latest and greatest features and don't mind the potential for more bugs than the main release, you can visit the experimental part of releases which is automatically updated for each commit to the main branch.
There are a lot of older files in the experimental releases, so you will need to look for the latest downloads. You can tell which are the latest by looking at the date of the release.
There are two main packages you need to look for: basic, and dev. They are in a similar formats as above, but with -commit number-commit hash
appended to the end of the version number. For example, a basic install might look like UE4SS_v3.0.0-5-ga5e818e.zip
and a dev install might look like zDEV-UE4SS_v3.0.0-5-ga5e818e.zip
.
Note: If you are using the experimental version for development, you should be using the dev version of the docs, which you can get to by appending docs.ue4ss.com with
/dev
(e.g. this page would behttps://docs.ue4ss.com/dev/installation-guide
).
Overriding Install Location
This method allows you to override the location of the root directory
while proxy injection still works.
In your game executable directory
alongside the dwmapi.dll
, create a file called override.txt
and inside it you can write either an absolute path or a relative path to your new UE4SS.dll
.
For example, possible paths could be:
C:/ue4ss/
../../Content/Paks
Manual Injection
Using manual injection will mean that the
root directory
andworking directory
are treated as one single directory that happens to also be the same directory as yourgame executable directory
, but any directory may be used.
Following the download of basic or dev methods (stable or experimental) and delete dwmapi.dll
. Afterwards, launch the game and manually inject UE4SS.dll
using your injector of choice.
Central Install Location via Manual Injection
This method is a way to install UE4SS in one place for all your games. Simply extract the zip file of your choice (basic or dev) in any directory outside the game directory
, this is what's known as the root directory
.
You will then create a folder inside with the name of your game and drag UE4SS-settings.ini
in to it, this is what's known as the working directory
.
If the path to your game executable is
D:\Games\Epic Games\SatisfactoryEarlyAccess\FactoryGame\Binaries\Win64\FactoryGame-Win64-Shipping.exe
Then the name of your working directory
should be SatisfactoryEarlyAccess
.
This directory will be automatically found and used by UE4SS if it exists.
The following files & folders exist inside the working directory
:
- Mods
- Mod folders
- mods.txt
- UE4SS-settings.ini
- UE4SS.log
- UE4SS.dll
- ...and some other auxiliary, optional files that are not required for UE4SS to function.
While dwmapi.dll
(can have a name of any DLL that is loaded by the game engine or by its dependencies) is in the game executable directory
.
Now all you need to do is start your game and point your injector of choice to <root directory>/UE4SS.dll
.
If you use this method, if you keep a copy of UE4SS-settings.ini
inside the root directory
then this file will act as a default for all the games that don't have a working directory
as long as you still point your injector to the root directory
.
This way you can use this method for most of your games and at the same time you can use method #1 or method #2 for other games.
How to verify that UE4SS is running successfully?
Try any of the following:
- Press any of the default keyboard shortcuts, such as
@
orF10
that open the in-game console (using built-inConsoleEnablerMod
) - Check that the log file
UE4SS.log
is created in the same folder as the UE4SS main DLL, and that the log file contains fresh timestamps and no errors. - Enable the GUI console in
UE4SS-settings.ini
and check that it appears as a separate window (rendered with OpenGL by default). - (For developers, if the game is confirmed to be safely debuggable) Check that the UE4SS library is being loaded in a debugger and has its threads spawned in the target game's process and in a reasonable state.
Blueprint Modloader
As our BP system is based on RussellJ's, this tutorial video is applicable for creating a blueprint mod for UE4SS:
Live Viewer
The Live Viewer is a tool that allows you to search, view, edit & watch the properties of every object making it very powerful for debugging mods or figuring out how values are changed during runtime. Note however that it cannot show unreflected data.
In order to see it, you must make sure that the following configuration settings are set to 1:
GuiConsoleEnabled
GuiConsoleVisible
If you are having any issues seeing the window, for example if it opens as blanked out/white, you can change the GraphicsAPI
setting, for example to dx11
.
Sometimes the font is too small to easily see (particularly on larger resolutions). You can edit the GuiConsoleFontScaling
setting to change this.
You can right-click the search bar to show options.
Setting | Explanation |
---|---|
Refresh search | Refreshes the search results. |
Save filters | Saves the current search filters to working directory/liveview/filters.meta.json . Saved filters are loaded when the game is next launched. |
Apply when not searching | Applies the filters while not searching for any objects by name. |
Include inheritance | Includes any child objects of any search results. |
Instances only | Only includes object instances. These are objects present in the level as part of actors (actors themselves, widgets, components etc.), and their properties are a reflection of the real-time values. Examples include <package name>_C or <package name>_C_<some instance id> . |
Non-instances only | Only includes the default state of object packages, which are loaded in memory but are not present in the level. You cannot change the values of these properties. |
CDOs only | Only includes the class default objects (CDOs), which are the reflected properties inherited by non-instances from a UClass object. Examples include <full path>_GEN_VARIABLE or <package path>.Default__<package name> . |
Include CDOs | Includes CDOs in any of your search criteria. |
Use Regex for search | Allows you to use regex to make more specific searches. |
Exclude class names | Allows you to exclude specific class names, e.g., CanvasPanelSlot or StaticMeshComponent . Comma seperated list, e.g., CanvasPanelSlot, StaticMeshComponent,Package , Function . |
Include class names | Accessible by exclude class names dropdown. Allows you to include only specific class names, e.g., CanvasPanelSlot or StaticMeshComponent . Comma seperated list, e.g., CanvasPanelSlot, StaticMeshComponent,Package , Function . |
Has property | Finds objects only if they have a property of a specific name, e.g., Player Name . This setting is applied only if you have entered any value in the search bar. |
Has property type | Finds objects only if they have a property of a specific type, such as BoolProperty or MulticastInlineDelegateProperty . |
Function parameter flags | Clicking on this checkbox opens a new popup. Allows you to select any number of flags to filter functions by and whether or not you want it to use your selected flags to check for the return flag |
In the property viewer pane at the bottom, there are three sub-controls:
<<
which goes backwards through your history.>>
which goes forwards through your history.Find functions
which opens a window to search for and call any functions associated with this object and any of its children. You can use this to test calling functions in-game without having to write any code.
Watches
You can right click most property types, and functions, to add a watch.
To view your watches, go into the watches tab.
Click the plus buttons on the left of each watch to expand the values box.
On the left side of the watch, there are two checkboxes:
- Enable/disable watch
- Write the watch to a file. If this is enabled, when you close the game, the values from your watche will be saved as a text file in
working directory/watches/
.
On the right side of the watch, there is an option to save the watch which will be automatically re-added when you restart the game. This data is stored inside of working directory/watches/watches.meta.json
Dumpers
C++ Header Generator
The C++ dumper is a tool that generates C++ headers from UE4 classes and blueprints.
The keybind to generate headers is by default CTRL
+ H
, and it can be changed in Mods/Keybinds/Scripts/main.lua
.
It generates a .hpp
file for each blueprint (including animation blueprint and widget blueprint), and then all of the base classes inside of <ProjectName>.hpp
or <EngineModule>.hpp
. All classes are at the top of the files, followed by all structs. Enums are seperated into files named the same as their class, but with _enums
appended to the end.
Configurations
-
DumpOffsetsAndSizes
(bool)- Whether to property offsets and sizes
- Default: 1
-
KeepMemoryLayout
(bool)- Whether memory layouts of classes and structs should be accurate
- This must be set to 1, if you want to use the generated headers in an actual C++ project
- When set to 0, padding member variables will not be generated
- Default: 0
Warning: A value of 1 has no purpose yet as memory value is not accurate either way!
-
LoadAllAssetsBeforeGeneratingCXXHeaders
(bool)- Whether to force all assets to be loaded before generating headers
- Default: 0
Warning: Can require multiple gigabytes of extra memory, is not stable & will crash the game if you load past the main menu after dumping
Unreal Header Tool (UHT) Dumper
Generates Unreal Header Tool compatible C++ headers for creating a mirror .uproject
for your game. The guide for using these headers can be found here.
The keybind to generate headers is by default CTRL
+ Numpad 9
, and it can be changed in Mods/Keybinds/Scripts/main.lua
.
Configurations
-
IgnoreAllCoreEngineModules
(bool)- Whether to skip generating packages that belong to the engine, particularly useful for any games that make alterations to the engine
- Default: 0
-
IgnoreEngineAndCoreUObject
(bool)- Whether to skip generating the
Engine
andCoreUObject
packages - Default: 1
- Whether to skip generating the
-
MakeAllFunctionsBlueprintCallable
(bool)- Whether to force all UFUNCTION macros to have
BlueprintCallable
- Default: 1
Warning: This will cause some errors in the generated headers that you will need to manually fix
- Whether to force all UFUNCTION macros to have
-
MakeAllPropertyBlueprintsReadWrite
(bool)- Whether to force all UPROPERTY macros to have
BlueprintReadWrite
- Also forces all UPROPERTY macros to have
meta=(AllowPrivateAccess=true)
- Default: 1
- Whether to force all UPROPERTY macros to have
-
MakeEnumClassesBlueprintType
(bool)- Whether to force UENUM macros on enums to have
BlueprintType
if the underlying type was implicit or uint8 - Default: 1
Warning: This also forces the underlying type to be uint8 where the type would otherwise be implicit
- Whether to force UENUM macros on enums to have
-
MakeAllConfigsEngineConfig
(bool)- Whether to force
Config = Engine
on all UCLASS macros that use either one of:DefaultConfig
,GlobalUserConfig
orProjectUserConfig
- Default: 1
- Whether to force
Object Dumper
Dumps all loaded objects to the file UE4SS_ObjectDump.txt
(you can turn on force loading for all assets).
The keybind to dump objects is by default CTRL
+ J
, and can be changed in Mods/Keybinds/Scripts/main.lua
.
Example output:
[000002A70F57E5C0] Function /Game/UI/Art/WidgetParts/Basic_ButtonScalable2.Basic_ButtonScalable2_C:BndEvt__Button_0_K2Node_ComponentBoundEvent_0_OnButtonClickedEvent__DelegateSignature [n: 5343AA] [c: 000002A727993A00] [or: 000002A708466980]
[000002A70F57E4E0] Function /Game/UI/Art/WidgetParts/Basic_ButtonScalable2.Basic_ButtonScalable2_C:PreConstruct [n: 4057B] [c: 000002A727993A00] [or: 000002A708466980]
[000002A70F876600] BoolProperty /Game/UI/Art/WidgetParts/Basic_ButtonScalable2.Basic_ButtonScalable2_C:PreConstruct:IsDesignTime [o: 0] [n: 4D63DB] [c: 00007FF683722CC0] [owr: 000002A70F57E4E0]
There are multiple sets of opening & closing square brackets and each set has a different meaning and the letters in this table explains what they mean.
Within the first set of brackets is the location in memory where the object or property is stored.
Letters | Meaning | UE Member Variable |
---|---|---|
n | Name of an object/property | NamePrivate |
c | Class of the object/property/enum value | ClassPrivate |
or | Outer of the object | OuterPrivate |
o | Offset of a property value in an object | Offset_Internal |
owr | Owner of an FField, 4.25+ only | Owner |
kp | Key property of an FMapProperty | KeyProp |
vp | Value property of an FMapProperty | ValueProp |
mc | Class that this FClassProperty refers to | MetaClass |
df | Function that this FDelegateProperty refers to | FunctionSignature |
pc | Class that this FObjectProperty/FFieldPathProperty refers to | PropertyClass |
ic | Class that this FInterfaceProperty refers to | InterfaceClass |
ss | Struct that this FStructProperty refers to | Struct |
em | Enum that this FEnumProperty refers to | Enum |
fm | Field mask that this FBoolProperty refers to | FieldMask |
bm | Byte mask that this FBoolProperty refers to | ByteMask |
v | Value corresponding to this enum key | N/A |
sps | SuperStruct of this UClass | SuperStruct |
ai | Property that this FArrayProperty stores | Inner |
Configurations
LoadAllAssetsBeforeDumpingObjects
(bool)- Whether to force all assets to be loaded before dumping objects
- Default: 0
Warning: Can require multiple gigabytes of extra memory, is not stable & will crash the game if you load past the main menu after dumping
.usmap Dumper
Generate .usmap
mapping files for unversioned properties.
The keybind to dump mappings is by default Ctrl
+ Numpad 6
, and can be changed in Mods/Keybinds/Scripts/main.lua
.
Thanks to OutTheShade for the original implementation.
.umap Recreation Dumper
Dump all loaded actors to the file ue4ss_static_mesh_data.csv
to generate .umaps
in-editor.
Two prerequisites are required to load the dumped actors in-editor to reconstruct the .umap
:
- All dumped actors (static meshes, their materials/textures) must be reconstructed in the editor
- Download
zMapGenBP.zip
from the Releases page and follow the instructions in the Readme file inside of it
The keybind to dump mappings is by default Ctrl
+ Numpad 7
, and can be changed in Mods/Keybinds/Scripts/main.lua
.
Lua API
These are the Lua API functions available in UE4SS, on top of the standard libraries that Lua comes with by defualt.
For version: 3.1.0
Current status: mostly complete
Full API Overview
This is an overall list of API definitions available in UE4SS. For more readable information, see the individual API definition pages in the collapsible sections 4.1, 4.2 and 4.3.
Warning: This API list is not updated since 2.5.2, so it out of date. Please refer to the individual API definition pages for the most up-to-date information.
Table Definitions
- The definitions appear as: FieldName | FieldValueType
- Fields that only have numeric field names have '#' as their name in this definition for clarity
- All fields are required unless otherwise specified
ModifierKeys
# | string (Microsoft Virtual Key-Code)
PropertyTypes
ObjectProperty | internal_value
ObjectPtrProperty | internal_value
Int8Property | internal_value
Int16Property | internal_value
IntProperty | internal_value
Int64Property | internal_value
NameProperty | internal_value
FloatProperty | internal_value
StrProperty | internal_value
ByteProperty | internal_value
UInt16Property | internal_value
UIntProperty | internal_value
UInt64Property | internal_value
BoolProperty | internal_value
ArrayProperty | internal_value
MapProperty | internal_value
StructProperty | internal_value
ClassProperty | internal_value
WeakObjectProperty | internal_value
EnumProperty | internal_value
TextProperty | internal_value
OffsetInternalInfo
Property | string (Name of the property to use as relative start instead of base)
RelativeOffset | integer (Offset from relative start to this property)
ArrayPropertyInfo
Type | table (PropertyTypes)
CustomPropertyInfo
Name | string (Name to use with the __index metamethod)
Type | table (PropertyTypes)
BelongsToClass | string (Full class name without type that this property belongs to)
OffsetInternal | integer or table (if table: OffsetInternalInfo, otherwise: offset from base to this property)
ArrayProperty | table (Optional, ArrayPropertyInfo)
EObjectFlags
- A table of object flags that can be or'd together by using |.
RF_NoFlags | 0x00000000
RF_Public | 0x00000001
RF_Standalone | 0x00000002
RF_MarkAsNative | 0x00000004
RF_Transactional | 0x00000008
RF_ClassDefaultObject | 0x00000010
RF_ArchetypeObject | 0x00000020
RF_Transient | 0x00000040
RF_MarkAsRootSet | 0x00000080
RF_TagGarbageTemp | 0x00000100
RF_NeedInitialization | 0x00000200
RF_NeedLoad | 0x00000400
RF_KeepForCooker | 0x00000800
RF_NeedPostLoad | 0x00001000
RF_NeedPostLoadSubobjects | 0x00002000
RF_NewerVersionExists | 0x00004000
RF_BeginDestroyed | 0x00008000
RF_FinishDestroyed | 0x00010000
RF_BeingRegenerated | 0x00020000
RF_DefaultSubObject | 0x00040000
RF_WasLoaded | 0x00080000
RF_TextExportTransient | 0x00100000
RF_LoadCompleted | 0x00200000
RF_InheritableComponentTemplate | 0x00400000
RF_DuplicateTransient | 0x00800000
RF_StrongRefOnFrame | 0x01000000
RF_NonPIEDuplicateTransient | 0x01000000
RF_Dynamic | 0x02000000
RF_WillBeLoaded | 0x04000000
RF_HasExternalPackage | 0x08000000
RF_AllFlags | 0x0FFFFFFF
EInternalObjectFlags
- A table of internal object flags that can be or'd together by using |.
ReachableInCluster | 0x00800000
ClusterRoot | 0x01000000
Native | 0x02000000
Async | 0x04000000
AsyncLoading | 0x08000000
Unreachable | 0x10000000
PendingKill | 0x20000000
RootSet | 0x40000000
GarbageCollectionKeepFlags | 0x0E000000
AllFlags | 0x7F800000
Global Functions
print(any... Message)
- Does not have the capability to format. Use 'string.format' if you require formatting.
CreateInvalidObject() -> UObject
- Creates an object with an IsValid function that always returns false
StaticFindObject(string ObjectName) -> { UObject | AActor | nil }
StaticFindObject(UClass Class=nil, UObject InOuter=nil, string ObjectName, bool ExactClass=false)
- Maps to https://docs.unrealengine.com/4.26/en-US/API/Runtime/CoreUObject/UObject/StaticFindObject/
FindFirstOf(string ShortClassName) -> { UObject | AActor | nil }
- Find the first non-default instance of the supplied class name
- Param 'ShortClassName': Should only contains the class name itself without path info
FindAllOf(string ShortClassName) -> table -> { UObject | AActor } | nil
- Find all non-default instances of the supplied class name
- Param 'ShortClassName': Should only contains the class name itself without path info
RegisterKeyBind(integer Key, function Callback)
RegisterKeyBind(integer Key, table ModifierKeys, function callback)
- Registers a callback for a key-bind
- Callbacks can only be triggered while the game or debug console is on focus
IsKeyBindRegistered(integer Key)
IsKeyBindRegistered(integer Key, table ModifierKeys)
- Checks if, at the time of the invocation, the supplied keys have been registered
RegisterHook(string UFunctionName, function Callback) -> integer, integer
- Registers a callback for a UFunction
- Callbacks are triggered when a UFunction is executed
- The callback params are: UObject self, UFunctionParams...
- Returns two ids, both of which must be passed to 'UnregisterHook' if you want to unregister the hook.
UnregisterHook(string UFunctionName, integer PreId, integer PostId)
- Unregisters a hook.
ExecuteInGameThread(function Callback)
- Execute code inside the game thread using ProcessEvent.
- Will execute as soon as the game has time to execute.
FName(string Name) -> FName
FName(integer ComparisonIndex) -> FName
- Returns the FName for this string/ComparisonIndex or the FName for "None" if the name doesn't exist
FText(string Text) -> FText
- Returns the FText representation of this string
StaticConstructObject(UClass Class,
UObject Outer,
FName Name, #Optional
EObjectFlags Flags, #Optional
EInternalObjectFlags InternalSetFlags, #Optional
bool CopyTransientsFromClassDefaults, #Optional
bool AssumeTemplateIsArchetype, #Optional
UObject Template, #Optional
FObjectInstancingGraph InstanceGraph, #Optional
UPackage ExternalPackage, #Optional
void SubobjectOverrides #Optional) -> UObject
- Attempts to construct a UObject of the passed UClass
- (>=4.26) Maps to https://docs.unrealengine.com/4.27/en-US/API/Runtime/CoreUObject/UObject/StaticConstructObject_Internal/1/
- (<4.25) Maps to https://docs.unrealengine.com/4.27/en-US/API/Runtime/CoreUObject/UObject/StaticConstructObject_Internal/2/
RegisterCustomProperty(table CustomPropertyInfo)
- Registers a custom property to be used automatically with 'UObject.__index'
ForEachUObject(function Callback)
- Execute the callback function for each UObject in GUObjectArray
- The callback params are: UObject object, integer ChunkIndex, integer ObjectIndex
NotifyOnNewObject(string UClassName, function Callback)
- Executes the provided Lua function whenever an instance of the provided class is constructed.
- Inheritance is taken into account, so if you provide "/Script/Engine.Actor" as the class then it will execute your
- Lua function when any object is constructed that's either an AActor or is derived from AActor.
RegisterCustomEvent(string EventName, function Callback)
- Registers a callback that will get called when a BP function/event is called with the name 'EventName'.
RegisterLoadMapPreHook(function Callback)
- Registers a callback that will get called before UEngine::LoadMap is called.
- The callback params are: UEngine Engine, struct FWorldContext& WorldContext, FURL URL, class UPendingNetGame* PendingGame, FString& Error
- Params (except strings & bools & FOutputDevice) must be retrieved via 'Param:Get()' and set via 'Param:Set()'.
RegisterLoadMapPostHook(function Callback)
- Registers a callback that will get called after UEngine::LoadMap is called.
- The callback params are: UEngine Enigne, struct FWorldContext& WorldContext, FURL URL, class UPendingNetGame* PendingGame, FString& Error
- Params (except strings & bools & FOutputDevice) must be retrieved via 'Param:Get()' and set via 'Param:Set()'.
RegisterInitGameStatePreHook(function Callback)
- Registers a callback that will get called before AGameModeBase::InitGameState is called.
- The callback params are: AGameModeBase Context
- Params (except strings & bools & FOutputDevice) must be retrieved via 'Param:Get()' and set via 'Param:Set()'.
RegisterInitGameStatePostHook(function Callback)
- Registers a callback that will get called after AGameModeBase::InitGameState is called.
- The callback params are: AGameModeBase Context
- Params (except strings & bools & FOutputDevice) must be retrieved via 'Param:Get()' and set via 'Param:Set()'.
RegisterBeginPlayPreHook(function Callback)
- Registers a callback that will get called before AActor::BeginPlay is called.
- The callback params are: AActor Context
- Params (except strings & bools & FOutputDevice) must be retrieved via 'Param:Get()' and set via 'Param:Set()'.
RegisterBeginPlayPostHook(function Callback)
- Registers a callback that will get called after AActor::BeginPlay is called.
- The callback params are: AActor Context
- Params (except strings & bools & FOutputDevice) must be retrieved via 'Param:Get()' and set via 'Param:Set()'.
RegisterProcessConsoleExecPreHook(function Callback)
- Registers a callback that will get called before UObject::ProcessConsoleExec is called.
- The callback params are: UObject Context, string Cmd, table CommandParts, FOutputDevice Ar, UObject Executor
- Params (except strings & bools & FOutputDevice) must be retrieved via 'Param:Get()' and set via 'Param:Set()'.
- If the callback returns nothing (or nil), the original return value of ProcessConsoleExec will be used.
- If the callback returns true or false, the supplied value will override the original return value of ProcessConsoleExec.
RegisterProcessConsoleExecPostHook(function Callback)
- Registers a callback that will get called after UObject::ProcessConsoleExec is called.
- The callback params are: UObject Context, string Cmd, table CommandParts, FOutputDevice Ar, UObject Executor
- Params (except strings & bools & FOutputDevice) must be retrieved via 'Param:Get()' and set via 'Param:Set()'.
- If the callback returns nothing (or nil), the original return value of ProcessConsoleExec will be used.
- If the callback returns true or false, the supplied value will override the original return value of ProcessConsoleExec.
RegisterCallFunctionByNameWithArgumentsPreHook(function Callback)
- Registers a callback that will be called before UObject::CallFunctionByNameWithArguments is called.
- The callback params are: UObject Context, string Str, FOutputDevice Ar, UObject Executor, bool bForceCallWithNonExec
- Params (except strings & bools & FOutputDevice) must be retrieved via 'Param:Get()' and set via 'Param:Set()'.
- If the callback returns nothing (or nil), the original return value of CallFunctionByNameWithArguments will be used.
- If the callback returns true or false, the supplied value will override the original return value of CallFunctionByNameWithArguments.
RegisterCallFunctionByNameWithArgumentsPostHook(function Callback)
- Registers a callback that will be called after UObject::CallFunctionByNameWithArguments is called.
- The callback params are: UObject Context, string Str, FOutputDevice Ar, UObject Executor, bool bForceCallWithNonExec
- Params (except strings & bools & FOutputDevice) must be retrieved via 'Param:Get()' and set via 'Param:Set()'.
- If the callback returns nothing (or nil), the original return value of CallFunctionByNameWithArguments will be used.
- If the callback returns true or false, the supplied value will override the original return value of CallFunctionByNameWithArguments.
RegisterULocalPlayerExecPreHook(function Callback)
- Registers a callback that will be called before ULocalPlayer::Exec is called.
- The callback params are: ULocalPlayer Context, UWorld InWorld, string Cmd, FOutputDevice Ar
- Params (except strings & bools & FOutputDevice) must be retrieved via 'Param:Get()' and set via 'Param:Set()'.
- The callback can have two return values.
- If the first return value is nothing (or nil), the original return value of Exec will be used.
- If the first return value is true or false, the supplied value will override the original return value of Exec.
- The second return value controls whether the original Exec will execute.
- If the second return value is nil or true, the orginal Exec will execute.
- If the second return value is false, the original Exec will not execute.
RegisterULocalPlayerExecPostHook(function Callback)
- Registers a callback that will be called after ULocalPlayer::Exec is called.
- The callback params are: ULocalPlayer Context, UWorld InWorld, string Cmd, FOutputDevice Ar
- Params (except strings & bools & FOutputDevice) must be retrieved via 'Param:Get()' and set via 'Param:Set()'.
- The callback can have two return values.
- If the first return value is nothing (or nil), the original return value of Exec will be used.
- If the first return value is true or false, the supplied value will override the original return value of Exec.
- The second return value controls whether the original Exec will execute.
- If the second return value is nil or true, the orginal Exec will execute.
- If the second return value is false, the original Exec will not execute.
RegisterConsoleCommandHandler(string CommandName, function Callback)
- Registers a callback for a custom console commands.
- The callback only runs in the context of UGameViewportClient.
- The callback params are: string Cmd, table CommandParts, FOutputDevice Ar
- Params (except strings & bools & FOutputDevice) must be retrieved via 'Param:Get()' and set via 'Param:Set()'.
- The callback must return either true or false.
- If the callback returns true, no further handlers will be called for this command.
RegisterConsoleCommandGlobalHandler(string CommandName, function Callback)
- Registers a callback for a custom console command.
- Unlike 'RegisterConsoleCommandHandler', this global variant runs the callback for all contexts.
- The callback params are: string Cmd, table CommandParts, FOutputDevice Ar
- Params (except strings & bools & FOutputDevice) must be retrieved via 'Param:Get()' and set via 'Param:Set()'.
- The callback must return either true or false.
- If the callback returns true, no further handlers will be called for this command.
ExecuteAsync(function Callback)
- Asynchronously executes the specified function
ExecuteWithDelay(integer DelayInMilliseconds, function Callback)
- Asynchronously executes the specified function after the specified delay
RegisterConsoleCommandHandler(string CommandName, function Callback)
- Executes the provided Lua function whenever the CommandName is entered into the UE console.
- The parameters for the callback are the full command (string),
- and the parameters (table, containing the full command split by spaces), and FOutputDevice.
- In the callback, return true to prevent other handlers from handling the command, or false to allow other handlers.
LoadAsset(string AssetPathAndName)
- Loads an asset by name.
- Must only be called from within the game thread.
- For example, from within a UFunction hook or RegisterConsoleCommandHandler callback.
FindObject(string|FName|nil ClassName, string|FName|nil ObjectShortName, EObjectFlags RequiredFlags, EObjectFlags BannedFlags) -> UObject derivative
- Finds an object by either class name or short object name.
- ClassName or ObjectShortName can be nil, but not both.
- Returns a UObject of a derivative of UObject.
FindObject(UClass InClass, UObject InOuter, string Name, bool ExactClass)
- Finds an object. Works the same way as the function by the same name in the UE source.
FindObjects(integer NumObjectsToFind, string|FName|nil ClassName, string|FName|nil ObjectShortName, EObjectFlags RequiredFlags, EObjectFlags BannedFlags, bool bExactClass) -> table -> { UObject derivative }
- Finds the first specified number of objects by class name or short object name.
- To find all objects that match your criteria, set NumObjectsToFind to 0 or nil.
- Returns a table of UObject derivatives
LoopAsync(integer DelayInMilliseconds, function Callback)
- Starts a loop that sleeps for the supplied number of milliseconds and stops when the callback returns true.
IterateGameDirectories() -> table
- Returns a table of all game directories.
- An example of an absolute path to Win64: Q:\SteamLibrary\steamapps\common\Deep Rock Galactic\FSD\Binaries\Win64
- To get to the same directory, do: IterateGameDirectories().Game.Binaries.Win64
- Note that the game name is replaced by 'Game' to keep things generic.
- You can use '.__name' and '.__absolute_path' to retrieve values.
- You can use '.__files' to retrieve a table containing all files in this directory.
- You also use '.__name' and '.__absolute_path' for files.
DumpAllObjects()
GenerateSDK()
GenerateLuaTypes()
GenerateUHTCompatibleHeaders()
DumpStaticMeshes()
DumpAllActors()
DumpUSMAP()
Classes
RemoteObject
Inheritance:
- The first of two base objects that all other objects inherits from
- Contains a pointer to a C/C++ object that's typically owned by the game
Methods
IsValid() -> bool
- Returns whether this object is valid or not
LocalObject
Inheritance:
- The second of two base objects that all other objects inherits from
- Contains an inlined object which is fully owned by Lua
Methods
UnrealVersion
Inheritance:
Methods
GetMajor() -> integer
GetMinor() -> integer
IsEqual(number MajorVersion, number MinorVersion) -> bool
IsAtLeast(number MajorVersion, number MinorVersion) -> bool
IsAtMost(number MajorVersion, number MinorVersion) -> bool
IsBelow(number MajorVersion, number MinorVersion) -> bool
IsAbove(number MajorVersion, number MinorVersion) -> bool
UE4SS
Inheritance:
- Class for interacting with UE4SS metadata
Methods
GetVersion() -> 3x integer
- Returns major, minor and hotfix version numbers
- To detect version 1.0 or below, check if "UE4SS" or "UE4SS.GetVersion" is nil
Mod
Inheritance: RemoteObject
- Class for interacting with the local mod object
Methods
SetSharedVariable(string VariableName, any Value)
- Sets a variable that can be accessed by any mod.
- The second parameter (Value) can only be one of the following types: nil, string, number, bool, UObject(+derivatives), lightuserdata.
- These variables do not get reset when hot-reloading.
GetSharedVariable(string VariableName) -> any
- Gets a variable that could've been set from another mod.
- The return value can only be one of the following types: nil, string, number, bool, UObject(+derivatives), lightuserdata.
type() -> string
- Returns "ModRef"
UObject
Inheritance: RemoteObject
- This is the base class that most other Unreal Engine game objects inherit from
Methods
__index(string MemberVariableName) -> auto
- Attempts to return either a member variable or a callable UFunction
- Can return any type, you can use the 'type()' function on the returned value to figure out what Lua class it's using (if non-trivial type)
__newindex(string MemberVariableName, auto NewValue)
- Attempts to set the value of a member variable
GetFullName() -> string
- Returns the full name & path info for a UObject & its derivatives
GetFName() -> FName
- Returns the FName of this object by copy
- All FNames returned by '__index' are returned by reference
GetAddress() -> integer
- Returns the memory address of this object
GetClass() -> UClass
- Returns the class of this object, this is equivalent to 'UObject->ClassPrivate' in Unreal
GetOuter() -> UObject
- Returns the Outer of this object
IsAnyClass() -> bool
- Returns true if this UObject is a UClass or a derivative of UClass
Reflection() -> UObjectReflection
- Returns a reflection object
GetPropertyValue(string MemberVariableName) -> auto
- Identical to __index
SetPropertyValue(string MemberVariableName auto NewValue)
- Identical to __newindex
IsClass() -> bool
- Returns whether this object is a UClass or UClass derivative
GetWorld() -> UWorld
- Returns the UWorld that this UObject is contained within.
CallFunction(UFunction function, auto Params...)
- Calls the supplied UFunction on this UObject.
IsA(UClass Class) -> bool
IsA(string FullClassName) -> bool
- Returns whether this object is of the specified class.
HasAllFlags(EObjectFlags FlagsToCheck)
- Returns whether the object has all of the specified flags.
HasAnyFlags(EObjectFlags FlagsToCheck)
- Returns whether the object has any of the specified flags.
HasAnyInternalFlags(EInternalObjectFlags InternalFlagsToCheck)
- Return whether the object has any of the specified internal flags.
ProcessConsoleExec(string Cmd, nil Reserved, UObject Executor)
- Calls UObject::ProcessConsoleExec with the supplied params.
type() -> string
- Returns the type of this object as known by UE4SS
- This does not return the type as known by Unreal
UStruct
Inheritance: UObject
Methods
GetSuperStruct() -> UClass
- Returns the SuperStruct of this struct (can be invalid).
ForEachFunction(function Callback)
- Iterates every UFunction that belongs to this struct.
- The callback has one param: UFunction Function.
- Return true in the callback to stop iterating.
ForEachProperty(function Callback)
- Iterates every Property that belongs to this struct.
- The callback has one param: Property Property.
- Return true in the callback to stop iterating.
UClass
Inheritance: UClass
Methods
GetCDO() -> UClass
- Returns the ClassDefaultObject of a UClass.
IsChildOf(UClass Class) -> bool
- Returns whether or not the class is a child of another class.
AActor
Inheritance: UObject
Methods
GetWorld() -> UObject | nil
- Returns the UWorld that this actor belongs to
GetLevel() -> UObject | nil
- Returns the ULevel that this actor belongs to
FName
Inheritance: LocalObject
Methods
ToString() -> string
- Returns the string for this FName
GetComparisonIndex() -> integer
- Returns the ComparisonIndex for this FName (index into global names array)
TArray
Inheritance: RemoteObject
Methods
__index(integer ArrayIndex)
- Attempts to retrieve the value at the specified offset in the array
- Can return any type, you can use the 'type()' function on the returned value to figure out what Lua class it's using (if non-trivial type)
__newindex(integer ArrayIndex, auto NewValue)
- Attempts to set the value at the specified offset in the array
GetArrayAddress() -> integer
- Returns the address in memory where the TArray struct is located
GetArrayNum() -> integer
- Returns the number of current elements in the array
GetArrayMax() -> integer
- Returns the maximum number of elements allowed in this array (aka capacity)
GetArrayDataAddress -> integer
- Returns the address in memory where the data for this array is stored
ForEach(function Callback)
- Iterates the entire TArray and calls the callback function for each element in the array
- The callback params are: integer index, RemoteUnrealParam | LocalUnrealParam elem
- Use 'elem:get()' and 'elem:set()' to access/mutate an array element
UEnum
Inheritance: RemoteObject
Methods
GetNameByValue(integer Value) -> FName
- Returns the FName that corresponds to the specified value.
ForEachName(LuaFunction Callback) -> FName
- Iterates every FName/Value combination that belongs to this enum.
- The callback has two params: FName Name, integer Value.
- Return true in the callback to stop iterating.
RemoteUnrealParam | LocalUnrealParam
Inheritance: RemoteObject | LocalObject
- This is a dynamic wrapper for any and all types & classes
- Whether the Remote or Local variant is used depends on the requirements of the data but the usage is identical with either param types
Methods
get() -> auto
- Returns the underlying value for this param
set(auto NewValue)
- Sets the underlying value for this param
type() -> string
- Returns "RemoteUnrealParam" or "LocalUnrealParam"
UScriptStruct
Inheritance: LocalObject
Methods
__index(string StructMemberVarName) -> auto
- Attempts to return the value for the supplied variable
- Can return any type, you can use the 'type()' function on the returned value to figure out what Lua class it's using (if non-trivial type)
__newindex(string StructMemberVarName, auto NewValue)
- Attempts to set the value for the supplied variable
GetBaseAddress() -> integer
- Returns the address in memory where the UObject that this UScriptStruct belongs to is located
GetStructAddress() -> integer
- Returns the address in memory where this UScriptStruct is located
GetPropertyAddress() -> integer
- Returns the address in memory where the corresponding U/FProperty is located
IsValid() -> bool
- Returns whether the struct is valid
IsMappedToObject() -> bool
- Returns whether the base object is valid
IsMappedToProperty() -> bool
- Returns whether the property is valid
type() -> string
- Returns "UScriptStruct"
UFunction
Inheritance: UObject
Methods
__call(UFunctionParams...)
- Attempts to call the UFunction
GetFunctionFlags() -> integer
- Returns the flags for the UFunction.
SetFunctionFlags(integer Flags)
Sets the flags for the UFuction.
FString
Inheritance: RemoteObject
- A TArray of characters
Methods
ToString()
- Returns a string that Lua can understand
Clear()
- Clears the string by setting the number of elements in the TArray to 0
FieldClass
Inheritance: LocalObject
Methods
GetFName()
- Returns the FName of this class by copy.
Property
Inheritance: RemoteObject
Methods
GetFullName() -> string
- Returns the full name & path for this property.
GetFName() -> FName
- Returns the FName of this property by copy.
- All FNames returned by '__index' are returned by reference.
IsA(PropertyTypes PropertyType) -> bool
- Returns true if the property is of type PropertyType.
GetClass() -> PropertyClass
ContainerPtrToValuePtr(UObjectDerivative Container, integer ArrayIndex) -> LightUserdata
- Equivalent to FProperty::ContainerPtrToValuePtr<uint8> in UE.
ImportText(string Buffer, LightUserdata Data, integer PortFlags, UObject OwnerObject)
- Equivalent to FProperty::ImportText in UE, except without the 'ErrorText' param.
ObjectProperty
Inheritance: Property
Methods
GetPropertyClass() -> UClass
- Returns the class that this property holds.
BoolProperty
Inheritance: Property
Methods
GetByteMask() -> integer
GetByteOffset() -> integer
GetFieldMask() -> integer
GetFieldSize() -> integer
StructProperty
Inheritance: Property
Methods
GetStruct() -> UScriptStruct
- Returns the UScriptStruct that's mapped to this property.
ArrayProperty
Inheritance: Property
Methods
GetInner() -> Property
- Returns the inner property of the array.
UObjectReflection
Inheritance:
Methods
GetProperty(string PropertyName) -> Property
- Returns a property meta-data object
FOutputDevice
Inheritance: RemoteObject
Methods
Log(string Message)
- Logs a message to the output device (i.e: the in-game console)
FWeakObjectPtr
Inheritance: LocalObject
Methods
Get() -> UObjectDerivative
- Returns the pointed to UObject or UObject derivative (can be invalid, so call UObject:IsValid after calling Get).
Key
The Key
table contains Microsoft virtual key-code strings.
This table is automatically populated with data. Do not modify the data inside this table.
Key-code strings
LEFT_MOUSE_BUTTON
RIGHT_MOUSE_BUTTON
CANCEL
MIDDLE_MOUSE_BUTTON
XBUTTON_ONE
XBUTTON_TWO
BACKSPACE
TAB
CLEAR
RETURN
PAUSE
CAPS_LOCK
IME_KANA
IME_HANGUEL
IME_HANGUL
IME_ON
IME_JUNJA
IME_FINAL
IME_HANJA
IME_KANJI
IME_OFF
ESCAPE
IME_CONVERT
IME_NONCONVERT
IME_ACCEPT
IME_MODECHANGE
SPACE
PAGE_UP
PAGE_DOWN
END
HOME
LEFT_ARROW
UP_ARROW
RIGHT_ARROW
DOWN_ARROW
SELECT
PRINT
EXECUTE
PRINT_SCREEN
INS
DEL
HELP
ZERO
ONE
TWO
THREE
FOUR
FIVE
SIX
SEVEN
EIGHT
NINE
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
Y
Z
LEFT_WIN
RIGHT_WIN
APPS
SLEEP
NUM_ZERO
NUM_ONE
NUM_TWO
NUM_THREE
NUM_FOUR
NUM_FIVE
NUM_SIX
NUM_SEVEN
NUM_EIGHT
NUM_NINE
MULTIPLY
ADD
SEPARATOR
SUBTRACT
DECIMAL
DIVIDE
F1
F2
F3
F4
F5
F6
F7
F8
F9
F10
F11
F12
F13
F14
F15
F16
F17
F18
F19
F20
F21
F22
F23
F24
NUM_LOCK
SCROLL_LOCK
BROWSER_BACK
BROWSER_FORWARD
BROWSER_REFRESH
BROWSER_STOP
BROWSER_SEARCH
BROWSER_FAVORITES
BROWSER_HOME
VOLUME_MUTE
VOLUME_DOWN
VOLUME_UP
MEDIA_NEXT_TRACK
MEDIA_PREV_TRACK
MEDIA_STOP
MEDIA_PLAY_PAUSE
LAUNCH_MAIL
LAUNCH_MEDIA_SELECT
LAUNCH_APP1
LAUNCH_APP2
OEM_ONE
OEM_PLUS
OEM_COMMA
OEM_MINUS
OEM_PERIOD
OEM_TWO
OEM_THREE
OEM_FOUR
OEM_FIVE
OEM_SIX
OEM_SEVEN
OEM_EIGHT
OEM_102
IME_PROCESS
PACKET
ATTN
CRSEL
EXSEL
EREOF
PLAY
ZOOM
PA1
OEM_CLEAR
Example
local enter_key = Key.RETURN
ModifierKey
The ModifierKey
table contains Microsoft virtual key-code strings that are meant to be modifier keys such as CONTROL
and ALT
.
This table is automatically populated with data.
Do not modify the data inside this table.
Modifier key-code strings
SHIFT
CONTROL
ALT
Example
local CTRL_Key = ModifierKey.CONTROL
PropertyTypes
The PropertyTypes
table contains type information for Unreal Engine properties.
This is primarily used with the RegisterCustomProperty
Lua function.
This table is automatically populated with data.
Do not modify the data inside this table.
Structure
Key | Value |
---|---|
ObjectProperty | internal_value |
Int8Property | internal_value |
Int16Property | internal_value |
IntProperty | internal_value |
Int64Property | internal_value |
NameProperty | internal_value |
FloatProperty | internal_value |
StrProperty | internal_value |
ByteProperty | internal_value |
BoolProperty | internal_value |
ArrayProperty | internal_value |
MapProperty | internal_value |
StructProperty | internal_value |
ClassProperty | internal_value |
WeakObjectProperty | internal_value |
EnumProperty | internal_value |
TextProperty | internal_value |
Example
local PropertyType = PropertyTypes.ObjectProperty
OffsetInternalInfo
The OffsetInternalInfo
table contains information related to a custom property.
You must supply data yourself when using this table.
Structure
Key | Value Type | Information |
---|---|---|
Property | string | Name of the property to use as relative start instead of base |
RelativeOffset | integer | Offset from relative start to this property |
Example
local PropertyInfo = {
["Property"] = "HistoryBuffer",
["RelativeOffset"] = 0x10
}
ArrayPropertyInfo
The ArrayPropertyInfo
table contains type information for custom ArrayProperty properties.
You must supply data yourself when using this table.
Structure
Key | Value Type | Sub Type |
---|---|---|
Type | table | PropertyTypes |
Example
local ArrayPropertyInfo = {
["Type"] = PropertyTypes.IntProperty
}
CustomPropertyInfo
The CustomPropertyInfo
table contains information about a custom property.
You must supply data yourself when using this table.
Structure
Key | Value Type | Sub Type | Information |
---|---|---|---|
Name | string | Name to use with the __index metamethod | |
Type | table | PropertyTypes | |
BelongsToClass | string | Full class name without type, that this property belongs to | |
OffsetInternal | integer or table | OffsetInternalInfo | Sub Type only valid if Type is table |
ArrayProperty | table | ArrayPropertyInfo | Only use when 'Type' is PropertyTypes.ArrayProperty |
Simple Example
Creates a custom property with the name MySimpleCustomProperty
that accesses whatever data is at offset 0xF40
in any instance of class Character
as if it was an IntProperty
.
local CustomPropertyInfo = {
["Name"] = "MySimpleCustomProperty",
["Type"] = PropertyTypes.IntProperty,
["BelongsToClass"] = "/Script/Engine.Character"
["OffsetInternal"] = 0xF40
}
Advanced Example
Creates a custom property with the name MyAdvancedCustomProperty
that accesses whatever data is at offset 0xF48
in any instance of class Character
as if it was an ArrayProperty
with an inner type of IntProperty
.
local CustomPropertyInfo = {
["Name"] = "MyAdvancedCustomProperty",
["Type"] = PropertyTypes.ArrayProperty,
["BelongsToClass"] = "/Script/Engine.Character"
["OffsetInternal"] = 0xF48,
["ArrayProperty"] = {
["Type"] = PropertyTypes.IntProperty
}
}
EObjectFlags
A table of object flags that can be or'd together by using |
Field Name | Field Value Type |
---|---|
RF_NoFlags | 0x00000000 |
RF_Public | 0x00000001 |
RF_Standalone | 0x00000002 |
RF_MarkAsNative | 0x00000004 |
RF_Transactional | 0x00000008 |
RF_ClassDefaultObject | 0x00000010 |
RF_ArchetypeObject | 0x00000020 |
RF_Transient | 0x00000040 |
RF_MarkAsRootSet | 0x00000080 |
RF_TagGarbageTemp | 0x00000100 |
RF_NeedInitialization | 0x00000200 |
RF_NeedLoad | 0x00000400 |
RF_KeepForCooker | 0x00000800 |
RF_NeedPostLoad | 0x00001000 |
RF_NeedPostLoadSubobjects | 0x00002000 |
RF_NewerVersionExists | 0x00004000 |
RF_BeginDestroyed | 0x00008000 |
RF_FinishDestroyed | 0x00010000 |
RF_BeingRegenerated | 0x00020000 |
RF_DefaultSubObject | 0x00040000 |
RF_WasLoaded | 0x00080000 |
RF_TextExportTransient | 0x00100000 |
RF_LoadCompleted | 0x00200000 |
RF_InheritableComponentTemplate | 0x00400000 |
RF_DuplicateTransient | 0x00800000 |
RF_StrongRefOnFrame | 0x01000000 |
RF_NonPIEDuplicateTransient | 0x01000000 |
RF_Dynamic | 0x02000000 |
RF_WillBeLoaded | 0x04000000 |
RF_HasExternalPackage | 0x08000000 |
RF_AllFlags | 0x0FFFFFFF |
EInternalObjectFlags
A table of internal object flags that can be or'd together by using |
Field Name | Field Value Type |
---|---|
ReachableInCluster | 0x00800000 |
ClusterRoot | 0x01000000 |
Native | 0x02000000 |
Async | 0x04000000 |
AsyncLoading | 0x08000000 |
Unreachable | 0x10000000 |
PendingKill | 0x20000000 |
RootSet | 0x40000000 |
GarbageCollectionKeepFlags | 0x0E000000 |
AllFlags | 0x7F800000 |
EFindName
Field Name | Field Value Type |
---|---|
FNAME_Find | 0 |
FNAME_Add | 1 |
RemoteObject
The RemoteObject
class is the first of two base objects that all other objects inherits from, the other one being LocalObject.
It contains a pointer to a C++ object that is typically owned by the game.
Inheritance
None
Methods
IsValid()
- Return type:
bool
- Returns: whether this object is valid or not
Example
-- 'StaticFindObject' returns a UObject which inherits from RemoteObject.
local Object = StaticFindObject("/Script/CoreUObject.Object")
if Object:IsValid() then
print("Object is valid\n")
else
print("Object is NOT valid\n")
end
LocalObject
The LocalObject
class is the second of two base objects that all other objects inherits from, the other one being RemoteObject.
It contains an inlined C++ object that is owned by Lua.
Inheritance
None
Methods
None
UnrealVersion
The UnrealVersion
class contains helper functions for retrieving which version of Unreal Engine that is being used.
Inheritance
None
Methods
GetMajor()
- Return type:
integer
GetMinor()
- Return type:
integer
IsEqual(number MajorVersion, number MinorVersion)
- Return type:
bool
IsAtLeast(number MajorVersion, number MinorVersion)
- Return type:
bool
IsAtMost(number MajorVersion, number MinorVersion)
- Return type:
bool
IsBelow(number MajorVersion, number MinorVersion)
- Return type:
bool
IsAbove(number MajorVersion, number MinorVersion)
- Return type:
bool
Examples
local Major = UnrealVersion.GetMajor()
local Minor = UnrealVersion.GetMinor()
print(string.format("Version: %s.%s\n", Major, Minor))
if UnrealVersion.IsEqual(5, 0) then print("Version is 5.0\n") end
if UnrealVersion.IsAtLeast(5, 0) then print("Version is >=5.0\n") end
if UnrealVersion.IsAtMost(5, 0) then print("Version is <=5.0\n") end
if UnrealVersion.IsBelow(5, 0) then print("Version is <5.0\n") end
if UnrealVersion.IsAbove(5, 0) then print("Version is >5.0\n") end
UE4SS
The UE4SS
class is for interacting with UE4SS metadata.
Inheritance
None
Methods
GetVersion()
Returns: the current version of UE4SS that is being used.
Return Value:
# | Type | Information |
---|---|---|
1 | integer | Major version |
2 | integer | Minor version |
3 | integer | Hotfix version |
Example #1
Warning: This only works in UE4SS 1.1+. See example #2 for UE4SS <=1.0.
local Major, Minor, Hotfix = UE4SS.GetVersion()
print(string.format("UE4SS v%d.%d.%d\n", Major, Minor, Hotfix))
Example #2
This example shows how to distinguish between UE4SS <=1.0, which didn't have the UE4SS
class, and UE4SS >=1.1.
if UE4SS == nil then
print("Running UE4SS <=1.0\n")
end
Mod
The Mod
class is responsible for interacting with the local mod object.
Inheritance
Methods
SetSharedVariable(string VariableName, any Value)
- Sets a variable that can be accessed by any mod.
- The second parameter
Value
can only be one of the following types:nil
,string
,number
,bool
,UObject
(+derivatives),lightuserdata
.
Warning: These variables do not get reset when hot-reloading.
Example
-- When sharing a UObject, make absolutely sure that it's a UObject that doesn't cease to exist before it's used again.
-- It's a very bad idea to share transient objects like actors as they might die and stop existing.
local StaticObject = StaticFindObject("/Script/Engine.Default__GameplayStatics")
-- The 'ModRef' variable is a global variable that's automatically created and is the instance of the current mod.
ModRef:SetSharedVariable("MyVariable", StaticObject)
GetSharedVariable(string VariableName)
- Return type:
any
- Returns: a variable that could've been set from another mod.
- The return value can only be one of the following types:
nil
,string
,number
,bool
,UObject
(+derivatives),lightuserdata
.
Example
-- Assuming that the example script for 'SetSharedVariable' has been executed.
local SharedObject = ModRef:GetSharedVariable("MyVariable")
-- 'GetSharedVariable' may return anything that its able to store.
-- Any mod is able to override the value for any shared variable.
if SharedObject and type(SharedObject) == "userdata" and SharedObject:type() == "UObject" and SharedObject:IsValid() then
print(string.format("SharedObject '%s' is valid.\n", SharedObject:GetFullName()))
else
print("SharedObject was nil, not userdata, not a UObject, or an invalid UObject")
end
type()
- Return type:
string
- Returns: "ModRef"
UObject
The UObject
class is the base class that most other Unreal Engine game objects inherit from.
Inheritance
Metamethods
__index
-
Usage:
UObject["ObjectMemberName"]
orUObject.ObjectMemberName
-
Returns either a member variable (reflected property or custom property) or a UFunction.
-
This method can return any type, and you can use the UObject-specific
type()
function on the returned value to figure out the type if the type is non-trivial. -
If the type is trivial, use the regular
type()
Lua function. -
Return Value:
# | Type | Information |
---|---|---|
1 | UObject or UFunction | If the type is UObject , then the actual type may be any class that inherits from UObject . |
- Example:
local Character = FindFirstOf("Character") -- Retrieve a non-trivial type local MovementComponent = Character.CharacterMovement -- Retrieve a trivial type local JumpMaxCount = Character.JumpMaxCount -- Call a UFunction member on the object -- Remember to use a colon (:) for calls local CanCharacterJump = Character:CanJump()
__newindex
-
Usage:
UObject["ObjectMemberName"] = NewValue
orUObject.ObjectMemberName = NewValue
-
Sets the value of a member variable to
NewValue
. -
Example: Sets the value of
MaxParticleResize
in the first instance of classUEngine
in memory.local Engine = FindFirstOf("Engine") Engine.MaxParticleResize = 4
Methods
GetFullName()
-
Returns: the full name & path info for a
UObject
& its derivatives -
Return Value:
# | Type | Information |
---|---|---|
1 | string | Full name and path of the UObject |
- Example:
local Engine = FindFirstOf("Engine") print(string.format("Engine Name: %s", Engine:GetFullName())) -- Output -- Engine Name: FGGameEngine /Engine/Transient.FGGameEngine_2147482618
GetFName()
- Returns: the FName of the UObject. This is equivalent to
Object->NamePrivate
in Unreal.
Warning: All FNames returned by
__index
are returned by reference.
- Return Value:
# | Type | Information |
---|---|---|
1 | FName | FName of the UObject |
- Example:
local Character = FindFirstOf("Character") if Character:IsValid() then local CharacterName = Character:GetFName() print(string.format("ComparisonIndex: 0x%X\n", CharacterName:GetComparisonIndex())) end
GetAddress()
-
Returns: where in memory the UObject is located.
-
Return Value:
# | Type | Information |
---|---|---|
1 | integer | 64-bit integer, address of the UObject |
- Example:
local Character = FindFirstOf("Character") if Character:IsValid() then print(string.format("Character: 0x%X\n", Character:GetAddress())) end
GetClass()
-
Returns: the class of this object. This is equivalent to
UObject->ClassPrivate
in Unreal. -
Return Value:
# | Type | Information |
---|---|---|
1 | UClass | The class of the UObject |
- Example:
local Character = FindFirstOf("Character") if Character:IsValid() then print(string.format("Character Class: 0x%X\n", Character:GetClass():GetAddress())) end
GetOuter()
-
Returns: the outer of the UObject. This is equivalent to
Object->OuterPrivate
in Unreal. -
Return Value:
# | Type | Information |
---|---|---|
1 | UObject | The outer UObject of this UObject |
- Example:
local Character = FindFirstOf("Character") if Character:IsValid() then print(string.format("Character Outer: 0x%X\n", Character:GetOuter():GetAddress())) end
IsAnyClass()
- Return type:
bool
- Returns: true if this
UObject
is aUClass
or a derivative ofUClass
Reflection()
- Return type:
UObjectReflection
- Returns: a reflection object
GetPropertyValue(string MemberVariableName)
- Return type:
auto
- Identical to
__index
metamethod (doingUObject["ObjectMemberName"]
)
SetPropertyValue(string MemberVariableName, auto NewValue)
- Identical to
__newindex
metamethod (doingUObject["ObjectMemberName"] = NewValue
)
IsClass()
- Return type:
bool
- Returns: whether this object is a
UClass
orUClass
derivative
GetWorld()
- Return type:
UWorld
- Returns: the
UWorld
that thisUObject
is contained within.
IsA(UClass Class)
- Return type:
bool
- Returns: whether this object is of the specified
UClass
.
IsA(string FullClassName)
- Return type:
bool
- Returns: whether this object is of the specified class name.
HasAllFlags(EObjectFlags FlagsToCheck)
- Return type:
bool
- Returns: whether the object has all of the specified flags.
HasAnyFlags(EObjectFlags FlagsToCheck)
- Return type:
bool
- Returns: whether the object has any of the specified flags.
HasAnyInternalFlags(EInternalObjectFlags InternalFlagsToCheck)
- Return type:
bool
- Returns: whether the object has any of the specified internal flags.
CallFunction(UFunction Function, auto Params...)
- Calls the supplied
UFunction
on thisUObject
.
ProcessConsoleExec(string Cmd, nil Reserved, UObject Executor)
- Calls
UObject::ProcessConsoleExec
with the supplied params.
type()
- Return type:
string
- Returns: the type of this object as known by UE4SS
- This does not return the type as known by Unreal
- Not equivalent to doing
type(UObject)
, which returns the type as known by Lua (a 'userdata')
UStruct
Inheritance
Methods
GetSuperStruct()
- Return type:
UClass
- Returns: the
SuperStruct
of this struct (can be invalid).
ForEachFunction(function Callback)
- Iterates every
UFunction
that belongs to this struct. - The callback has one param:
UFunction Function
. - Return
true
in the callback to stop iterating.
ForEachProperty(function Callback)
- Iterates every
Property
that belongs to this struct. - The callback has one param:
Property Property
. - Return
true
in the callback to stop iterating.
UScriptStruct
Inheritance
Metamethods
__index
-
Usage:
UScriptStruct["StructMemberName"]
orUScriptStruct.StructMemberName
-
Return type:
auto
-
Returns the value for the supplied member name.
-
Can return any type, you can use the
type()
function on the returned value to figure out what Lua class it's using (if non-trivial type). -
Example:
local scriptStruct = FindFirstOf('_UI_Items_C')
-- Either of the following can be used:
local item = scriptStruct['Item']
local item = scriptStruct.Item
__newindex
-
Usage:
UScriptStruct["StructMemberName"] = NewValue
orUScriptStruct.StructMemberName = NewValue
-
Attempts to set the value for the supplied member name to
NewValue
. -
Example:
local scriptStruct = FindFirstOf('_UI_Items_C')
-- Either of the following can be used:
scriptStruct['Item'] = 5
scriptStruct.Item = 5
Methods
GetBaseAddress()
- Return type:
integer
- Returns: the address in memory where the
UObject
that thisUScriptStruct
belongs to is located
GetStructAddress()
- Return type:
integer
- Returns: the address in memory where this
UScriptStruct
is located
GetPropertyAddress()
- Return type:
integer
- Returns: the address in memory where the corresponding
UProperty
/FProperty
is located
IsValid()
- Return type:
bool
- Returns: whether the struct is valid
IsMappedToObject()
- Return type:
bool
- Returns: whether the base object is valid
IsMappedToProperty()
- Return type:
bool
- Returns: whether the property is valid
type()
- Return type:
string
- Returns: "UScriptStruct"
UClass
Inheritance
Methods
GetCDO()
- Return type:
UClass
- Returns: the
ClassDefaultObject
of aUClass
.
IsChildOf(UClass Class)
- Return type:
bool
- Returns: whether or not the class is a child of another class.
UFunction
Inheritance
Metamethods
__call
- Usage:
UFunction(UFunctionParams...)
- Return type:
auto
- Attempts to call the
UFunction
and returns the result, if any. - If the
UFunction
is obtained without a context (e.g. fromStaticFindObject
), aUObject
context must be passed as the first parameter.
Methods
GetFunctionFlags()
- Return type:
integer
- Returns: the flags for the
UFunction
.
SetFunctionFlags(integer Flags)
- Sets the flags for the
UFunction
.
UEnum
Inheritance
Methods
GetNameByValue(integer Value)
- Return type:
FName
- Returns: the
FName
that corresponds to the specified value.
ForEachName(LuaFunction Callback)
- Iterates every
FName
/Value
combination that belongs to this enum. - The callback has two params:
FName Name
,integer Value
. - Return
true
in the callback to stop iterating.
GetEnumNameByIndex(integer Index)
- Return types:
FName
,Integer
- Returns: the
FName
that coresponds the givenIndex
. - Returns: the
Integer
value that coresponds the givenIndex
.
InsertIntoNames(string Name, integer Value, integer Index, boolean ShiftValues = true)
- Inserts a
FName
/Value
combination into a aUEnum
at the givenIndex
. - If
ShiftValues = true
, will shift all enum values greater than inserted value by one.
EditNameAt(integer Index, string NewName)
- At a given
Index
, will modify the found element in theUEnum
and replace itsName
with the givenNewName
.
EditValueAt(integer Index, integer NewValue)
- At a given
Index
, will modify the found element in theUEnum
and replace its value with the givenNewValue
.
RemoveFromNamesAt(integer Index, integer Count = 1, boolean AllowShrinking = true)
- Will remove
Count
element(s) at the givenIndex
from aUEnum
. - If
AllowShrinkning = true
, will shrink the enum array when removing elements.
AActor
Inheritance
Methods
GetWorld()
- Return types:
UObject
|nil
- Returns: the
UWorld
that this actor belongs to
GetLevel()
- Return type:
UObject
|nil
- Returns: the
ULevel
that this actor belongs to
FString
FString
is a TArray of characters.
Inheritance
Methods
ToString()
- Return type:
string
- Returns: a string that Lua can understand.
Clear()
- Clears the string by setting the number of elements in the
TArray
to 0.
FName
Inheritance
Methods
ToString()
- Return type:
string
- Returns: the string for this
FName
.
GetComparisonIndex()
- Return type:
integer
- Returns: the
ComparisonIndex
for thisFName
(index into global names array).
FText
Inheritance
Methods
ToString()
- Return type:
string
- Returns: the string representation of this
FText
.
FieldClass
Inheritance
Methods
GetFName()
- Return type:
FName
- Returns: the FName of this class by copy.
TArray
Inheritance
Metamethods
__index
- Usage:
TArray[ArrayIndex]
- Return type:
auto
- Attempts to retrieve the value at the specified integer offset
ArrayIndex
in the array. - Can return any type, you can use the
type()
function on the returned value to figure out what Lua class it's using (if non-trivial type).
__newindex
- Usage:
TArray[ArrayIndex] = NewValue
- Attempts to set the value at the specified integer offset
ArrayIndex
in the array toNewValue
.
__len
- Usage:
#TArray
- Return type:
integer
- Returns the number of current elements in the array.
Methods
GetArrayAddress()
- Return type:
integer
- Returns: the address in memory where the
TArray
struct is located.
GetArrayNum()
- Return type:
integer
- Returns: the number of current elements in the array.
GetArrayMax()
- Return type:
integer
- Returns: the maximum number of elements allowed in this array (aka capacity).
GetArrayDataAddress()
- Return type:
integer
- Returns: the address in memory where the data for this array is stored.
Empty()
- Clears the array.
ForEach(function Callback)
- Iterates the entire
TArray
and calls the callback function for each element in the array. - The callback params are:
integer index
,RemoteUnrealParam elem
|LocalUnrealParam elem
. - Use
elem:get()
andelem:set()
to access/mutate an array element.
RemoteUnrealParam
This is a dynamic wrapper for any and all types & classes.
Whether the Remote or Local variant is used depends on the requirements of the data but the usage is identical with either param types.
Inheritance
Methods
get()
- Return type:
auto
- Returns: the underlying value for this param.
set(auto NewValue)
- Sets the underlying value for this param.
type()
- Return type:
string
- Returns: "RemoteUnrealParam".
LocalUnrealParam
This is a dynamic wrapper for any and all types & classes.
Whether the Remote or Local variant is used depends on the requirements of the data but the usage is identical with either param types.
Inheritance
Methods
get()
- Return type:
auto
- Returns: the underlying value for this param.
set(auto NewValue)
- Sets the underlying value for this param.
type()
- Return type:
string
- Returns: "LocalUnrealParam".
Property
Inheritance
Methods
GetFullName()
- Return type:
string
- Returns: the full name & path for this property.
GetFName()
- Return type:
FName
- Returns: the FName of this property by copy.
All FNames returned by
__index
are returned by reference.
IsA(PropertyTypes PropertyType)
- Return type:
bool
- Returns:
true
if the property is of typePropertyType
.
GetClass()
- Return type:
PropertyClass
ContainerPtrToValuePtr(UObjectDerivative Container, integer ArrayIndex)
- Return type:
LightUserdata
- Equivalent to
FProperty::ContainerPtrToValuePtr<uint8>
in UE.
ImportText(string Buffer, LightUserdata Data, integer PortFlags, UObject OwnerObject)
- Equivalent to
FProperty::ImportText
in UE, except without theErrorText
param.
ObjectProperty
Inheritance
Methods
GetPropertyClass()
- Return type:
UClass
- Returns: the class that this property holds.
StructProperty
Inheritance
Methods
GetStruct()
- Return type:
UScriptStruct
- Returns: the
UScriptStruct
that's mapped to this property.
BoolProperty
Inheritance
Methods
GetByteMask()
- Return type:
integer
GetByteOffset()
- Return type:
integer
GetFieldMask()
- Return type:
integer
GetFieldSize()
- Return type:
integer
ArrayProperty
Inheritance
Methods
GetInner()
- Return type:
Property
- Returns: the inner property of the array.
UObjectReflection
Inheritance
None
Methods
GetProperty(string PropertyName)
- Return type:
Property
- Returns: a property meta-data object.
FOutputDevice
Inheritance
Methods
Log(string Message)
- Logs a message to the output device (i.e: the in-game console).
FWeakObjectPtr
Inheritance
Methods
Get()
- Return type:
UObjectDerivative
- Returns: the pointed to
UObject
orUObject
derivative.
The return can be invalid, so call
UObject:IsValid
after calling this function.
UWorld
Inheritance
Methods
SpawnActor(UClass ActorClass, FVector Location, FRotator Rotation)
This function uses UGameplayStatics:BeginDeferredActorSpawnFromClass
and UGameplayStatics:FinishSpawningActor
to spawn an actor.
- Return type:
AActor
- Returns: Spawned actor object or an invalid object.
The print
function is used for debugging and outputs a string to the debug console.
This function cannot be used to format strings, please use string.format
for string formatting purposes.
New lines are not automatically appended, so make sure to use
\n
whenever you want a new line.
Parameters
# | Type | Information |
---|---|---|
... | any | String or variable to output |
Example
print("Hello Debug Console\n")
CreateInvalidObject
The function CreateInvalidObject
always returns an object with an IsValid
function that returns flase
.
The sole purpose of the function is to ensure that the mod's Lua code adheres to UE4SS code conventions, where all functions return an invalid UObject
instead of nil
.
Example
The example code below ensures that you never need to check if EngineCache
is nil
, and the same applies to the return value of GetEngine()
.
local EngineCache = CreateInvalidObject() ---@cast EngineCache UEngine
---Returns instance of UEngine
---@return UEngine
function GetEngine()
if EngineCache:IsValid() then return EngineCache end
EngineCache = FindFirstOf("Engine") ---@type UEngine
return EngineCache
end
FName
The FName
function is used to get an FName
representation of a string
or integer
.
Parameters (overload #1)
This overload mimics FName::FName with the FindType
param set to EFindName::FName_Add
.
# | Type | Information |
---|---|---|
1 | string | String that you'd like to get an FName representation of |
2 | EFindName | Finding or adding name type. It can be either FNAME_Find or FNAME_Add . Default is FNAME_Add if not explicitly supplied |
Parameters (overload #2)
# | Type | Information |
---|---|---|
1 | integer | 64-bit integer representing the ComparisonIndex part that you'd like to get an FName representation of |
2 | EFindName | Finding or adding name type. It can be either FNAME_Find or FNAME_Add . Default is FNAME_Add if not explicitly supplied |
Return Value
# | Type | Information |
---|---|---|
1 | FName | FName corresponding to the string or ComparisonIndex , if one exists, or the "None" FName if one doesn't exist. If FNAME_Add is supplied then it adds the name if it doesn't exist |
Example
local name = FName("MyName")
print(name) -- MyName
FText
The FText
function is used to get an FText
representation of a string
.
Useful when you have to interact with UserWidget
-related classes for the UI of your mods, and call their SetText(FText("My New Text"))
methods.
Parameters (overload #1)
This overload mimics FText::FText( FString&& InSourceString ).
# | Type | Information |
---|---|---|
1 | string | String that you'd like to get an FText representation of |
Return Value
# | Type | Information |
---|---|---|
1 | FText | FText representation of incoming string |
Example
local some_text = FText("MyText")
print(some_text) -- MyText
IterateGameDirectories
Returns a table of all game directories.
An example of an absolute path to Win64
: Q:\SteamLibrary\steamapps\common\Deep Rock Galactic\FSD\Binaries\Win64
.
To get to the same directory, do IterateGameDirectories().<Game Name>.Binaries.Win64
.
- You can use
.__name
and.__absolute_path
to retrieve values. - You can use
.__files
to retrieve a table containing all files in this directory. - You also use
.__name
and.__absolute_path
for files.
Return Value
# | Type | Information |
---|---|---|
1 | table | The game directories table |
Example
for _, GameDirectory in pairs(IterateGameDirectories()) do
print(GameDirectory.__name)
print(GameDirectory.__absolute_path)
end
FindObject
FindObject
is a function that finds an object.
Overload #1 finds by either class name or short object name.
Overload #2 works the same way as FindObject
in the UE source.
Parameters (overload #1)
# | Type | Information |
---|---|---|
1 | string|FName|nil | The short name of the class of the object |
2 | string|FName|nil | The short name of the object itself |
3 | EObjectFlags | Any flags that the object cannot have. Uses | as a seperator |
4 | EObjectFlags | Any flags that the object must have. Uses | as a seperator |
Param 1 or Param 2 can be nil, but not both.
Parameters (overload #2)
# | Type | Information |
---|---|---|
1 | UClass | The class to find |
2 | UObject | The outer to look inside. If this is null then param 3 should start with a package name |
3 | string | The object path to search for an object, relative to param 2 |
4 | bool | Whether to require an exact match with the UClass parameter |
Return Value (overload #1 & #2)
# | Type | Information |
---|---|---|
1 | UObject | The derivative of the UObject |
Example (overload #1)
-- SceneComponent instance called TransformComponent0
local Object = FindObject("SceneComponent", "TransformComponent0")
-- FirstPersonCharacter_C instance called FirstPersonCharacter_C_0
local Object = FindObject("FirstPersonCharacter_C", "FirstPersonCharacter_C_0", EObjectFlags.RF_NoFlags, EObjectFlags.RF_ClassDefaultObject)
Example (overload #2)
local Object = FindObject(UClass, World, "Character", true)
FindObjects
Finds the first specified number of objects by class name or short object name.
To find all objects that match your criteria, set param 1 to 0
or nil
.
Parameters
# | Type | Information |
---|---|---|
1 | integer | The number of objects to find |
1 | string|FName|nil | The short name of the class of the object |
2 | string|FName|nil | The short name of the object itself |
3 | EObjectFlags | Any flags that the object cannot have. Uses | as a seperator |
4 | EObjectFlags | Any flags that the object must have. Uses | as a seperator |
6 | bool | Whether to require an exact match with the UClass parameter |
Return Value
# | Type | Sub Type | Information |
---|---|---|---|
1 | table | UObject | The derivative of the UObject |
Example
local Object = FindObjects(4, "SceneComponent", "TransformComponent0", EObjectFlags.RF_NoFlags, EObjectFlags.RF_ClassDefaultObject, true)
for _, Object in pairs(Objects) do
-- Do something with Object
end
StaticFindObject
The StaticFindObject
function is used to find any object that inherits from UObject
that currently exists in memory.
This function is the recommended way of retrieving non-instance objects such as objects of type UClass or UFunction.
Parameters (overload #1)
# | Type | Information |
---|---|---|
1 | string | Full name of the object to find, without the type prefix |
Parameters (overload #2)
The parameters for this overload mimics the StaticFindObject
function from UE4.
For more information see: Unreal Engine API -> StaticFindObject
# | Type | Information |
---|---|---|
1 | UClass | The class of the object to find, can be nil. |
2 | UObject | The outer to look inside. All packages are searched if nil. |
3 | string | Name of the object to find |
4 | bool | Whether to require an exact match with the UClass parameter |
Return Value (overload #1 & #2)
# | Type | Information |
---|---|---|
1 | UObject, UClass, or AActor | Object is only valid if an instance was found |
Example (overload #1)
local CharacterInstance = StaticFindObject("/Script/Engine.Character")
if not CharacterInstance:IsValid() then
print("No instance of class 'Character' was found.")
end
FindFirstOf
The FindFirstOf
function will find the first non-default instance of the supplied class name.
This function cannot be used to find non-instances or default instances.
Parameters
# | Type | Information |
---|---|---|
1 | string | Short name of the class to find an instance of |
Return Value
# | Type | Information |
---|---|---|
1 | UObject, UClass, or AActor | Object is only valid if an instance was found |
Example
local CharacterInstance = FindFirstOf("Character")
if not CharacterInstance:IsValid() then
print("No instance of class 'Character' was found.")
end
FindAllOf
The FindAllOf
function will find all non-default instances of the supplied class name.
This function cannot be used to find non-instances or default instances.
Parameters
# | Type | Information |
---|---|---|
1 | string | Short name of the class to find instances of |
Return Value
# | Type | Sub Type | Information |
---|---|---|---|
1 | nil or table | UObject, UClass, or AActor | nil if no instances were found, otherwise a numerically indexed table of all instances |
Example
Outputs the name of all objects that inherit from the Actor
class.
local ActorInstances = FindAllOf("Actor")
if not ActorInstances then
print("No instances of 'Actor' were found\n")
else
for Index, ActorInstance in pairs(ActorInstances) do
print(string.format("[%d] %s\n", Index, ActorInstance:GetFullName()))
end
end
StaticConstructObject
The StaticConstructObject
function attempts to construct a UE4 object of some type.
This function mimics the function StaticConstructObject_Internal.
Parameters (overload #1)
# | Type | Information |
---|---|---|
1 | UClass | The class of the object to construct |
2 | UObject | The outer to construct the object inside |
3 | FName | Optional |
4 | integer | Optional, 64 bit integer |
5 | integer | Optional, 64 bit integer |
6 | bool | Optional |
7 | bool | Optional |
8 | UObject | Optional |
9 | integer | Optional, 64 bit integer |
10 | integer | Optional, 64 bit integer |
11 | integer | Optional, 64 bit integer |
Parameters (overload #2)
# | Type | Information |
---|---|---|
1 | UClass | The class of the object to construct |
2 | UObject | The outer to construct the object inside |
3 | integer | Optional, 64 bit integer representation (ComparisonIndex & Number) of an FName |
4 | integer | Optional, 64 bit integer |
5 | integer | Optional, 64 bit integer |
6 | bool | Optional |
7 | bool | Optional |
8 | UObject | Optional |
9 | integer | Optional, 64 bit integer |
10 | integer | Optional, 64 bit integer |
11 | integer | Optional, 64 bit integer |
Return Value
# | Type | Information |
---|---|---|
1 | UObject | Object is only valid if an object was successfully constructed |
Example
This example constructs a UConsole
object.
local Engine = FindFirstOf("Engine")
local ConsoleClass = Engine.ConsoleClass
local GameViewport = Engine.GameViewport
if not ConsoleClass:IsValid() or not GameViewport:IsValid() then
print("Was unable to construct UConsole because the console class didn't exist\n")
else
local CreatedConsole = StaticConstructObject(ConsoleClass, GameViewport, 0, 0, 0, nil, false, false, nil)
if CreatedConsole:IsValid() then
print(string.format("CreatedConsole: %s\n", CreatedConsole:GetFullName()))
else
print("Was unable to construct UConsole\n")
end
end
ForEachUObject
The ForEachUObject
function iterates every UObject that currently exists in GUObjectArray
.
The GUObjectArray
UE4 variable is a large chunked array that contains UObjects.
The structure of this array has changed over the years and the ForEachUObject
function is designed to work identically across all engine versions.
Parameters
# | Type | Information |
---|---|---|
1 | function | Callback to execute for every UObject in GUObjectArray |
Callback Parameters
# | Type | Information |
---|---|---|
1 | UObject | The UObject |
2 | integer | The chunk index of the UObject |
3 | integer | The object index of the UObject |
Example
-- Warning: This will take quite a while to finish executing due to all of the 'print' calls
ForEachUObject(function(Object, ChunkIndex, ObjectIndex)
print(string.format("Chunk: %X | Object: %X | Name: %s\n", ChunkIndex, ObjectIndex, Object:GetFullName()))
end)
NotifyOnNewObject
The NotifyOnNewObject
function executes a callback whenever an instance of the supplied class is constructed via StaticConstructObject_Internal
by UE4.
Inheritance is taken into account, so if you provide "/Script/Engine.Actor"
as the class then it will execute the callback when any object is constructed that's either an AActor
or is derived from AActor
.
The callback can return true
to remove (unregister) that specific NotifyOnNewObject
callback.
The provided class must exist before this calling this function.
Parameters
# | Type | Information |
---|---|---|
1 | string | Full name of the class to get instance construction notifications for, without the type prefix |
2 | function | The callback to execute when an instance of the supplied class is constructed |
Return Value
# | Type | Information |
---|---|---|
1 | UObject | The constructed object |
Callback Return Value
# | Type | Information |
---|---|---|
1 | boolean | If true, remove the current callback from listening to any more notifications |
Example
NotifyOnNewObject("/Script/Engine.World", function(ConstructedObject)
print(string.format("World Constructed: %s\n", ConstructedObject:GetFullName()))
end)
NotifyOnNewObject("/Script/Engine.Actor", function(ConstructedObject)
print(string.format("Actor Constructed: %s\n", ConstructedObject:GetFullName()))
-- Unregister this callback after the first actor is found,
-- meaning this current function will only be called exactly once
return true
end)
What NOT to do
Please don't duplicate the NotifyOnNewObject
call for the same class multiple times, as it could cause performance issues if multiple mods are doing it (which has been seen in the wild).
For example, this:
NotifyOnNewObject("/Script/Engine.PlayerController", function(PlayerController)
PlayerController.bShowMouseCursor = true
end)
NotifyOnNewObject("/Script/Engine.PlayerController", function(PlayerController)
PlayerController.bForceFeedbackEnabled = false
end)
NotifyOnNewObject("/Script/Engine.PlayerController", function(PlayerController)
PlayerController.InputYawScale = 2.5
end)
NotifyOnNewObject("/Script/Engine.PlayerController", function(PlayerController)
PlayerController.InputPitchScale = -2.5
end)
NotifyOnNewObject("/Script/Engine.PlayerController", function(PlayerController)
PlayerController.InputRollScale = 1.0
end)
should just be this:
NotifyOnNewObject("/Script/Engine.PlayerController", function(PlayerController)
PlayerController.bShowMouseCursor = true
PlayerController.bForceFeedbackEnabled = false
PlayerController.InputYawScale = 2.5
PlayerController.InputPitchScale = -2.5
PlayerController.InputRollScale = 1.0
end)
ExecuteWithDelay
The ExecuteWithDelay
function asynchronously executes the supplied callback after the supplied delay is over.
Parameters
# | Type | Information |
---|---|---|
1 | integer | Delay, in milliseconds, to wait before executing the supplied callback |
2 | function | The callback to execute after the supplied delay is over |
Example
ExecuteWithDelay(2000, function()
print("Executed asynchronously after a 2 second delay\n")
end)
ExecuteInGameThread
ExecuteInGameThread
is a function that allows you to execute code using ProcessEvent
.
It will execute as soon as the game has time to execute it.
Parameters
# | Type | Information |
---|---|---|
1 | function | Callback to execute when the game has time |
Example
ExecuteInGameThread(function()
print("Hello from the game thread!\n")
end)
ExecuteAsync
The ExecuteAsync
function asynchronously executes the supplied callback.
It works in a similar manner to ExecuteWithDelay, except that there is no delay beyond the cost of registering the callback.
Parameters
# | Type | Information |
---|---|---|
1 | function | The callback to execute |
Example
ExecuteAsync(function()
print("Executed asynchronously\n")
end)
LoopAsync
Starts a loop that sleeps for the supplied number of milliseconds and stops when the callback returns true.
Parameters
# | Type | Information |
---|---|---|
1 | integer | The number of milliseconds to sleep |
2 | function | The callback function |
Example
LoopAsync(1000, function()
print("Hello World!")
return false -- Loops forever
end)
LoadAsset
The LoadAsset
function loads an asset by name.
It must only be called from within the game thread. For example, from within a
UFunction
hook orRegisterConsoleCommandHandler
callback.
Parameters
# | Type | Information |
---|---|---|
1 | string | Path and name of the asset |
Example
RegisterConsoleCommandHandler("summon", function(FullCommand, Parameters)
if #Parameters < 1 then return false end
-- Parameters[1] example: /Game/LevelElements/Refinery/Pipeline/BP_Pipeline_Start
LoadAsset(Parameters[1])
return false
end)
RegisterKeyBind
The RegisterKeyBind
function is used to bind a key on the keyboard to a Lua function.
Callbacks registered with this function are only executed when either the game or the debug console is in focus.
Parameters (overload #1)
# | Type | Sub Type | Information |
---|---|---|---|
1 | table | Key | Key to bind |
2 | function | Callback to execute when the key is hit on the keyboard |
Parameters (overload #2)
# | Type | Sub Type | Information |
---|---|---|---|
1 | integer | Key to bind, use the 'Key' table | |
2 | table | ModifierKeys | Modifier keys required alongside the 'Key' parameter |
3 | function | Callback to execute when the key is hit on the keyboard |
Example (overload #1)
RegisterKeyBind(Key.O, function()
print("Key 'O' hit.\n")
end)
Example (overload #2)
RegisterKeyBind(Key.O, {ModifierKey.CONTROL, ModifierKey.ALT}, function()
print("Key 'CTRL + ALT + O' hit.\n")
end)
Advanced Example (overload #1)
This registers a key bind with a callback that does nothing unless there are no widgets currently open
local AnyWidgetsOpen = false
RegisterHook("/Script/UMG.UserWidget:Construct", function()
AnyWidgetsOpen = true
end)
RegisterHook("/Script/UMG.UserWidget:Destruct", function()
AnyWidgetsOpen = false
end)
RegisterKeyBind(Key.B, function()
if AnyWidgetsOpen then return end
print("Key 'B' hit, while no widgets are open.\n")
end)
IsKeyBindRegistered
The IsKeyBindRegistered
checks if, at the time of the invocation, the supplied keys have been registered
Parameters (overload #1)
# | Type | Sub Type | Information |
---|---|---|---|
1 | integer | Key | Key to check |
Parameters (overload #2)
# | Type | Sub Type | Information |
---|---|---|---|
1 | integer | Key | Key to bind, use the 'Key' table |
2 | table | ModifierKeys | Modifier keys to check alongside the 'Key' parameter |
RegisterHook
The RegisterHook
registers a callback for a UFunction
Callbacks are triggered when a UFunction
is executed.
The callback params are: UObject self
, UFunctionParams..
.
Returns two ids, both of which must be passed to UnregisterHook
if you want to unregister the hook.
Any
UFunction
that you attempt to register withRegisterHook
must already exist in memory when you register it.
RegisterHook
doesn't support delegate functions!
RegisterHook Parameters
# | Type | Information |
---|---|---|
1 | string | Full name of the UFunction to hook. Type prefix has no effect. |
2 | function | If UFunction path starts with /Script/ : Callback to execute before the UFunction is executed.Otherwise: Callback to execute after the UFunction is executed. |
3 | function | (optional) If UFunction path starts with /Script/ : Callback to execute after the UFunction is executedOtherwise: Param does nothing. |
RegisterHook Return Values
# | Type | Information |
---|---|---|
1 | integer | The PreId of the hook |
2 | integer | The PostId of the hook |
Callback Parameters
# | Type | Information |
---|---|---|
1 | RemoteUnrealParam | Object representation of the "this"-pointer ("self" in lua) of the function, also known as "Context". It contains the object wrapped as RemoteUnrealParam that called the function. |
2..N | RemoteUnrealParam... | All function parameters wrapped as RemoteUnrealParam |
Example
PreId, PostId = RegisterHook("/Script/Engine.PlayerController:ClientRestart", function(Context, NewPawn)
local PlayerController = Context:get()
local Pawn = NewPawn:get()
print(string.format("PlayerController FullName: %s\n", PlayerController:GetFullName()))
if Pawn:IsValid() then
print(string.format("NewPawn FullName: %s\n", Pawn:GetFullName()))
end
if PreId then
-- Unhook once the function has been called
UnregisterHook("/Script/Engine.PlayerController:ClientRestart", PreId, PostId)
end
end)
UnregisterHook
The UnregisterHook
unregisters a callback for a UFunction
.
Parameters
# | Type | Information |
---|---|---|
1 | string | Full name of the UFunction to hook. Type prefix has no effect. |
2 | integer | The PreId of the hook |
3 | integer | The PostId of the hook |
Example
local preId, postId = RegisterHook("/Script/Engine.PlayerController:ClientRestart", function()
print("PlayerController restarted\n")
end)
UnregisterHook("/Script/Engine.PlayerController:ClientRestart", preId, postId)
RegisterCustomProperty
The RegisterCustomProperty
function is used to register custom properties for use just as if it were a reflected native or BP property.
This is an advanced function that's used to add support for non-reflected properties in the __index
metamethod in multiple metatables.
Parameters
# | Type | Sub Type | Information |
---|---|---|---|
1 | table | CustomPropertyInfo | A table containing all of the required information for registering a custom property |
Example
Registers a custom property with the name MySimpleCustomProperty
that accesses whatever data is at offset 0xF40
in any instance of class Character
as if it was an IntProperty
.
It then grabs that value of the first instance of the class Character
as an example of how the system works.
RegisterCustomProperty({
["Name"] = "MySimpleCustomProperty",
["Type"] = PropertyTypes.IntProperty,
["BelongsToClass"] = "/Script/Engine.Character"
["OffsetInternal"] = 0xF40
})
local CharacterInstance = FindFirstOf("Character")
if CharacterInstance:IsValid() then
local MySimplePropertyValue = CharacterInstance.MySimpleCustomProperty
end
RegisterCustomEvent
This registers a callback that will get called when a blueprint function or event is called with the name EventName
.
Parameters
# | Type | Information |
---|---|---|
1 | string | Name of the event to hook. |
2 | function | The callback to call when the event is called. |
Example
RegisterCustomEvent("MyCustomEvent", function()
print("MyCustomEvent was called\n")
end)
RegisterInitGameStatePreHook
This registers a callback that will get called before AGameModeBase::InitGameState
is called.
Parameters (except strings & bools & FOutputDevice
) must be retrieved via Param:Get()
and set via Param:Set()
.
Parameters
# | Type | Information |
---|---|---|
1 | function | The callback to register |
Callback Parameters
# | Type | Information |
---|---|---|
1 | AGameStateBase | The game state context |
Example
RegisterInitGameStatePreHook(function(GameState)
print("InitGameStatePreHook")
end)
RegisterInitGameStatePostHook
This registers a callback that will get called after AGameModeBase::InitGameState
is called.
Parameters (except strings & bools & FOutputDevice
) must be retrieved via Param:Get()
and set via Param:Set()
.
Parameters
# | Type | Information |
---|---|---|
1 | function | The callback to register |
Callback Parameters
# | Type | Information |
---|---|---|
1 | AGameStateBase | The game state context |
Example
RegisterInitGameStatePostHook(function(GameState)
print("InitGameStatePostHook")
end)
RegisterBeginPlayPreHook
This registers a callback that will get called before AActor::BeginPlay
is called.
Parameters (except strings & bools & FOutputDevice
) must be retrieved via Param:Get()
and set via Param:Set()
.
Parameters
# | Type | Information |
---|---|---|
1 | function | The callback to register |
Callback Parameters
# | Type | Information |
---|---|---|
1 | AActor | The actor context |
Example
RegisterBeginPlayPreHook(function(Actor)
print("BeginPlayPreHook")
end)
RegisterBeginPlayPostHook
This registers a callback that will get called after AActor::BeginPlay
is called.
Parameters (except strings & bools & FOutputDevice
) must be retrieved via Param:Get()
and set via Param:Set()
.
Parameters
# | Type | Information |
---|---|---|
1 | function | The callback to register |
Callback Parameters
# | Type | Information |
---|---|---|
1 | AActor | The actor context |
Example
RegisterBeginPlayPostHook(function(Actor)
print("BeginPlayPostHook")
end)
RegisterProcessConsoleExecPreHook
This registers a callback that will get called before UObject::ProcessConsoleExec
is called.
Parameters (except strings & bools & FOutputDevice
) must be retrieved via Param:Get()
and set via Param:Set()
.
If the callback returns nothing (or nil), the original return value of ProcessConsoleExec
will be used.
If the callback returns true or false, the supplied value will override the original return value of ProcessConsoleExec
.
Parameters
# | Type | Information |
---|---|---|
1 | function | The callback to register |
Callback Parameters
# | Type | Information |
---|---|---|
1 | UObject | The object context |
2 | string | Full command string |
3 | table | All command parts, including the command name |
4 | FOutputDevice | The AR |
5 | UObject | The executor |
Callback Return Value
# | Type | Information |
---|---|---|
1 | bool | Whether to override the original return value of ProcessConsoleExec |
Example
RegisterProcessConsoleExecPreHook(function(Context, FullCommand, CommandParts, Ar, Executor)
print("RegisterProcessConsoleExecPreHook:\n")
local ContextObject = Context:get()
local ExecutorObject = Executor:get()
print(string.format("Context FullName: %s\n", ContextObject:GetFullName()))
if ExecutorObject:IsValid() then
print(string.format("Executor FullName: %s\n", ExecutorObject:GetFullName()))
end
print(string.format("Command: %s\n", FullCommand))
print(string.format("Number of command parts: %i\n", #CommandParts))
if #CommandParts > 0 then
print(string.format("Command Name: %s\n", CommandParts[1]))
for PartNumber, CommandPart in ipairs(CommandParts) do
print(string.format("CommandPart: #%i -> '%s'\n", PartNumber, CommandPart))
end
end
Ar:Log("Write something to game console")
return true
end)
-- Entered into console: CommandExample param1 "param 2" 3
-- Output
--[[
RegisterProcessConsoleExecPreHook:
Context FullName: GameState /Game/Maps/Map_MainMenu.Map_MainMenu:PersistentLevel.GameState_2147479851
Executor FullName: BP_MainMenuPawn_C /Game/Maps/Map_MainMenu.Map_MainMenu:PersistentLevel.BP_MainMenuPawn_C_2147479061
Command: CommandExample param1 "param 2" 3
Number of command parts: 4
Command Name: CommandExample
CommandPart: #1 -> 'CommandExample'
CommandPart: #2 -> 'param1'
CommandPart: #3 -> 'param 2'
CommandPart: #4 -> '3'
--]]
RegisterProcessConsoleExecPostHook
This registers a callback that will get called after UObject::ProcessConsoleExec
is called.
Parameters (except strings & bools & FOutputDevice
) must be retrieved via Param:Get()
and set via Param:Set()
.
If the callback returns nothing (or nil), the original return value of ProcessConsoleExec
will be used.
If the callback returns true or false, the supplied value will override the original return value of ProcessConsoleExec
.
Parameters
# | Type | Information |
---|---|---|
1 | function | The callback to register |
Callback Parameters
# | Type | Information |
---|---|---|
1 | UObject | The object context |
2 | string | Full command string |
3 | table | All command parts, including the command name |
4 | FOutputDevice | The AR |
5 | UObject | The executor |
Callback Return Value
# | Type | Information |
---|---|---|
1 | bool | Whether to override the original return value of ProcessConsoleExec |
Example
RegisterProcessConsoleExecPostHook(function(Context, FullCommand, CommandParts, Ar, Executor)
print("RegisterProcessConsoleExecPostHook:\n")
local ContextObject = Context:get()
local ExecutorObject = Executor:get()
print(string.format("Context FullName: %s\n", ContextObject:GetFullName()))
if ExecutorObject:IsValid() then
print(string.format("Executor FullName: %s\n", ExecutorObject:GetFullName()))
end
print(string.format("Command: %s\n", FullCommand))
print(string.format("Number of command parts: %i\n", #CommandParts))
if #CommandParts > 0 then
print(string.format("Command Name: %s\n", CommandParts[1]))
for PartNumber, CommandPart in ipairs(CommandParts) do
print(string.format("CommandPart: #%i -> '%s'\n", PartNumber, CommandPart))
end
end
Ar:Log("Write something to game console")
return true
end)
-- Entered into console: CommandExample param1 "param 2" 3
-- Output
--[[
RegisterProcessConsoleExecPostHook:
Context FullName: GameState /Game/Maps/Map_MainMenu.Map_MainMenu:PersistentLevel.GameState_2147479851
Executor FullName: BP_MainMenuPawn_C /Game/Maps/Map_MainMenu.Map_MainMenu:PersistentLevel.BP_MainMenuPawn_C_2147479061
Command: CommandExample param1 "param 2" 3
Number of command parts: 4
Command Name: CommandExample
CommandPart: #1 -> 'CommandExample'
CommandPart: #2 -> 'param1'
CommandPart: #3 -> 'param 2'
CommandPart: #4 -> '3'
--]]
RegisterCallFunctionByNameWithArgumentsPreHook
This registers a callback that will get called before UObject::CallFunctionByNameWithArguments
is called.
Parameters (except strings & bools & FOutputDevice
) must be retrieved via Param:Get()
and set via Param:Set()
.
If the callback returns nothing (or nil), the original return value of CallFunctionByNameWithArguments
will be used.
If the callback returns true or false, the supplied value will override the original return value of CallFunctionByNameWithArguments
.
Parameters
# | Type | Information |
---|---|---|
1 | function | The callback to register |
Callback Parameters
# | Type | Information |
---|---|---|
1 | UObject | The object context |
2 | string | The string |
3 | FOutputDevice | The AR |
4 | UObject | The executor |
5 | bool | The bForceCallWithNonExec value |
Callback Return Value
# | Type | Information |
---|---|---|
1 | bool | Whether to override the original return value of CallFunctionByNameWithArguments |
Example
local function MyCallback(Context, Str, Ar, Executor, bForceCallWithNonExec)
-- Do something with the parameters
-- Return nil to use the original return value of CallFunctionByNameWithArguments
-- Return true or false to override the original return value of CallFunctionByNameWithArguments
return nil
end
RegisterCallFunctionByNameWithArgumentsPreHook(MyCallback)
RegisterCallFunctionByNameWithArgumentsPostHook
This registers a callback that will get called after UObject::CallFunctionByNameWithArguments
is called.
Parameters (except strings & bools & FOutputDevice
) must be retrieved via Param:Get()
and set via Param:Set()
.
If the callback returns nothing (or nil), the original return value of CallFunctionByNameWithArguments
will be used.
If the callback returns true or false, the supplied value will override the original return value of CallFunctionByNameWithArguments
.
Parameters
# | Type | Information |
---|---|---|
1 | function | The callback to register |
Callback Parameters
# | Type | Information |
---|---|---|
1 | UObject | The object context |
2 | string | The string |
3 | FOutputDevice | The AR |
4 | UObject | The executor |
5 | bool | The bForceCallWithNonExec value |
Callback Return Value
# | Type | Information |
---|---|---|
1 | bool | Whether to override the original return value of CallFunctionByNameWithArguments |
Example
local function MyCallback(Context, Str, Ar, Executor, bForceCallWithNonExec)
-- Do something with the parameters
-- Return nil to use the original return value of CallFunctionByNameWithArguments
-- Return true or false to override the original return value of CallFunctionByNameWithArguments
return nil
end
RegisterCallFunctionByNameWithArgumentsPreHook(MyCallback)
RegisterULocalPlayerExecPreHook
This registers a callback that will get called before ULocalPlayer::Exec
is called.
Parameters (except strings & bools & FOutputDevice
) must be retrieved via Param:Get()
and set via Param:Set()
.
The callback can have two return values.
- If the first return value is nothing (or nil), the original return value of Exec will be used.
- If the first return value is true or false, the supplied value will override the original return value of Exec.
- The second return value controls whether the original Exec will execute.
- If the second return value is nil or true, the orginal Exec will execute.
- If the second return value is false, the original Exec will not execute.
Parameters
# | Type | Information |
---|---|---|
1 | function | The callback to register |
Callback Parameters
# | Type | Information |
---|---|---|
1 | ULocalPlayer | The local player context |
2 | UWorld | The world |
3 | string | The command |
4 | FOutputDevice | The AR |
Callback Return Values
# | Type | Information |
---|---|---|
1 | bool | Whether to override the original return value of Exec |
2 | bool | Whether to execute the original Exec |
Example
local function MyCallback(Context, InWorld, Command, Ar)
-- Do something with the parameters
-- Return true or false to override the original return value of Exec
-- Return false to prevent the original Exec from executing
return nil, true
end
RegisterULocalPlayerExecPreHook(MyCallback)
RegisterULocalPlayerExecPostHook
This registers a callback that will get called after ULocalPlayer::Exec
is called.
Parameters (except strings & bools & FOutputDevice
) must be retrieved via Param:Get()
and set via Param:Set()
.
The callback can have two return values.
- If the first return value is nothing (or nil), the original return value of Exec will be used.
- If the first return value is true or false, the supplied value will override the original return value of Exec.
- The second return value controls whether the original Exec will execute.
- If the second return value is nil or true, the orginal Exec will execute.
- If the second return value is false, the original Exec will not execute.
Parameters
# | Type | Information |
---|---|---|
1 | function | The callback to register |
Callback Parameters
# | Type | Information |
---|---|---|
1 | ULocalPlayer | The local player context |
2 | UWorld | The world |
3 | string | The command |
4 | FOutputDevice | The AR |
Callback Return Values
# | Type | Information |
---|---|---|
1 | bool | Whether to override the original return value of Exec |
2 | bool | Whether to execute the original Exec |
Example
local function MyCallback(Context, InWorld, Command, Ar)
-- Do something with the parameters
-- Return true or false to override the original return value of Exec
-- Return false to prevent the original Exec from executing
return nil, true
end
RegisterULocalPlayerExecPostHook(MyCallback)
RegisterConsoleCommandHandler
The RegisterConsoleCommandHandler
function executes the provided Lua function whenever the supplied custom command is entered into the UE console.
Parameters
# | Type | Information |
---|---|---|
1 | string | The name of the custom command |
2 | function | The callback to execute when the custom command is entered into the UE console |
Callback Parameters
# | Type | Information |
---|---|---|
1 | string | Full command string |
2 | table | Table with parameters (without the command name) |
3 | FOutputDevice | The output device to write to |
Callback Return Value
# | Type | Information |
---|---|---|
1 | bool | Whether to prevent other handlers from handling this command |
Example
RegisterConsoleCommandHandler("CommandExample", function(FullCommand, Parameters, OutputDevice)
print("RegisterConsoleCommandHandler:\n")
print(string.format("Command: %s\n", FullCommand))
print(string.format("Number of parameters: %i\n", #Parameters))
for ParameterNumber, Parameter in ipairs(Parameters) do
print(string.format("Parameter #%i -> '%s'\n", ParameterNumber, Parameter))
end
OutputDevice:Log("Write something to game console")
return false
end)
-- Entered into console: CommandExample param1 "param 2" 3
-- Output
--[[
RegisterConsoleCommandHandler:
Command: CommandExample param1 "param 2" 3
Number of parameters: 3
Parameter #1 -> 'param1'
Parameter #2 -> 'param 2'
Parameter #3 -> '3'
--]]
RegisterConsoleCommandGlobalHandler
The RegisterConsoleCommandGlobalHandler
function executes the provided Lua function whenever the supplied custom command is entered into the UE console.
Unlike RegisterConsoleCommandHandler
, this global variant runs the callback for all contexts.
Parameters
# | Type | Information |
---|---|---|
1 | string | The name of the custom command |
2 | function | The callback to execute when the custom command is entered into the UE console |
Callback Parameters
# | Type | Information |
---|---|---|
1 | string | Full command string |
2 | table | Table with parameters (without the command name) |
3 | FOutputDevice | The output device to write to |
Callback Return Value
# | Type | Information |
---|---|---|
1 | bool | Whether to prevent other handlers from handling this command |
Example
RegisterConsoleCommandGlobalHandler("CommandExample", function(FullCommand, Parameters, OutputDevice)
print("RegisterConsoleCommandGlobalHandler:\n")
print(string.format("Command: %s\n", FullCommand))
print(string.format("Number of parameters: %i\n", #Parameters))
for ParameterNumber, Parameter in ipairs(Parameters) do
print(string.format("Parameter #%i -> '%s'\n", ParameterNumber, Parameter))
end
OutputDevice:Log("Write something to game console")
return false
end)
-- Entered into console: CommandExample param1 "param 2" 3
-- Output
--[[
RegisterConsoleCommandGlobalHandler:
Command: CommandExample param1 "param 2" 3
Number of parameters: 3
Parameter #1 -> 'param1'
Parameter #2 -> 'param 2'
Parameter #3 -> '3'
--]]
DumpAllObjects
Dumps all objects from memory to the file UE4SS_ObjectDump.txt
.
The function does the same as the Dump Objects & Properties
button in the UE4SS Debugging Tools aka. the GUI Console.
Example
RegisterKeyBind(Key.F1, function()
DumpAllObjects()
end)
GenerateSDK
Generates C++ Headers in the CXXHeaderDump
directory.
The function does the same as the Dump CXX Headers
button in the UE4SS Debugging Tools aka. the GUI Console under the Dumpers
tab.
Example
RegisterKeyBind(Key.F1, function()
GenerateSDK()
end)
GenerateLuaTypes
Generates Lua types for Custom Lua Bindings in the Mods/shared/types
directory.
The function does the same as the Generate Lua Types
button in the UE4SS Debugging Tools aka. the GUI Console under the Dumpers
tab.
Example
RegisterKeyBind(Key.F1, function()
GenerateLuaTypes()
end)
GenerateUHTCompatibleHeaders
Generates Unreal Header Tool Headers in the UHTHeaderDump
directory.
The function does the same as the Generate UHT Compatible Headers
button in the UE4SS Debugging Tools aka. the GUI Console under the Dumpers
tab.
Example
RegisterKeyBind(Key.F1, function()
GenerateUHTCompatibleHeaders()
end)
DumpStaticMeshes
Dumps all static actor meshes from memory to the file (timestamp here)-ue4ss_static_mesh_data.csv
.
The function does the same as the Dump all static actor meshes to file
button in the UE4SS Debugging Tools aka. the GUI Console under the Dumpers
tab.
Example
RegisterKeyBind(Key.F1, function()
DumpStaticMeshes()
end)
DumpAllActors
Dumps all actors from memory to the file (timestamp here)-ue4ss_actor_data.csv
.
The function does the same as the Dump all actors to file
button in the UE4SS Debugging Tools aka. the GUI Console under the Dumpers
tab.
Example
RegisterKeyBind(Key.F1, function()
DumpAllActors()
end)
DumpUSMAP
Generates an Unreal Mapping file Mappings.usmap
.
The function does the same as the Generate .usmap file UnrealMappingsDumper by OutTheShade
button in the UE4SS Debugging Tools aka. the GUI Console under the Dumpers
tab.
Example
RegisterKeyBind(Key.F1, function()
DumpUSMAP()
end)
Examples
- Check the code snippets at the bottom of the individual pages in the Lua API section and tutorials in this repository.
- Hogwarts Legacy modding uses UE4SS' Lua API for its primary logic mods. This website contains example code for some of the mods.
- You can search for interesting code in that page, the collapsed sections of the webpage will auto-expand when the text is found.
- The Palworld modding wiki has some decent docs and examples on how to develop Lua mods for new learners.
- Search GitHub for any Lua code calling reasonably uniquely-named UE4SS API functions, excluding the actual UE4SS repository from the search:
- https://github.com/search?q=language%3ALua+StaticFindObject+NOT+repo%3AUE4SS-RE%2FRE-UE4SS&type=code
- https://github.com/search?q=language%3ALua+FindFirstOf+NOT+repo%3AUE4SS-RE%2FRE-UE4SS&type=code
- and so on, as long as
language:Lua
is specified in the query
Creating a Lua mod
Before you start
To create a Lua mod in UE4SS, you should first:
- know how to install UE4SS in your target game and make sure it is running OK;
- be able to write basic Lua code (see the official book Programming in Lua and its later editions, or any other recommended tutorial online);
- have an understanding of the object model of the Unreal Engine and the basics of game modding.
How does a minimal Lua mod look like
A Lua mod in UE4SS is a set of Lua scripts placed in a folder inside the Mods/
folder of UE4SS installation.
Let's call it MyLuaMod
for the purpose of this example.
In order to be loaded and executed:
- The mod folder must have a
scripts
subfolder and amain.lua
file inside, so it looks like:
Mods\
...
MyLuaMod\
scripts\
main.lua
...
- The
Mods\MyLuaMod\scripts\main.lua
file has some Lua code inside it, e.g.:
print("[MyLuaMod] Mod loaded\n")
- The mod must be added and enabled in
Mods\mods.txt
with a new line containing the name of your mod folder (name of your mod) and1
for enabling or0
for disabling the mod:
...
MyLuaMod : 1
...
Your custom functionality goes inside main.lua
, from which you can include other Lua files if needed, including creating your own Lua modules or importing various libraries.
What can you do in a Lua mod
The API provided by UE4SS and available to you in Lua is documented in sub-sections of chapter "Lua API" here. Using those functions and classes, you find and manipulate the instances of Unreal Engine objects in memory, creating new objects or modifying existing ones, calling their methods and accessing their fields.
Basically, you are doing the exact same thing that an Unreal Engine game developer does in their code, but using UE4SS to locate the necessary objects and guessing a bit, while the developers already knew where and what they are (because they have their source code).
Creating simple data types
If you need to create an object of a structure-like class, e.g. FVector
, in order to pass it into a Unreal Engine function, UE4SS allows you to pass a Lua table with the fields of the class like {X=1.0, Y=2.0, Z=3.0}
instead.
Using Lua C libraries
If you ever need to load Lua C libraries, that have native code (i.e. with DLLs on Windows),
you can place these DLLs directly inside the same \scripts\
folder.
Setting up a Lua mod development environment
It is much easier to write mods if your code editor or IDE is properly configured for Lua development and knows about UE4SS API.
-
Configure your code editor/IDE to support Lua syntax highlighting and code completion. If you use VSCode, see here in Using Custom Lua Bindings.
-
Make sure that your build of UE4SS contains
Mods\shared\Types.lua
(a development build from Github releases contains it). This will load the UE4SS API definitions in your IDE. -
(Optional) Dump the Lua Bindings fromm UE4SS Gui console, and follow the recommendations to load them here.
Then open the Mods/
folder of your UE4SS installation in your IDE, and create or modify your mod inside it.
Applying code changes
The main benefit of developing Lua mods is that you can quickly edit Lua sources without recompiling/rebuilding the C++ mod library as is always the case with C++ mods, and retry without restarting the game.
You can either:
- reload all mods from the UE4SS GUI Console with the "Restart All Mods" button on the "Console" tab, or,
- enable "Hot reload" in
UE4SS-settings.ini
and use the assigned hotkey (Ctrl+R
by default) to do the same.
Your first mod
In the main.lua
file of your mod, write some code that will try to access the objects of Unreal Engine inside your target game and do something that you can observe in the UE4SS console.
You can start by trying just
print("[MyLuaMod] Mod loaded\n")
and once you have verified that it runs OK, you can start implementing some actual functionality.
The example code below is fairly generic and should work for many games supported by UE4SS.
It registers a hotkey Ctrl+F1
and when pressed, it reads the player coordinates
and calculates how far the player has moved since the last time the hotkey was pressed.
Note that the logging
The player coordinates are retrieved in the following way:
- Gets the player controller using UE4SS
UEHelpers
class. - Get the
Pawn
, which represents the actual "physical" entity that the player can control in Unreal Engine. - Call the appropriate Unreal Engine method
K2_GetActorLocation
that returns aPawn
's location (by accessing its parentActor
class). - The location is a 3-component vector of Unreal Engine type
FVector
, havingX
,Y
andZ
as its fields.
local UEHelpers = require("UEHelpers")
print("[MyLuaMod] Mod loaded\n")
local lastLocation = nil
function ReadPlayerLocation()
local FirstPlayerController = UEHelpers:GetPlayerController()
local Pawn = FirstPlayerController.Pawn
local Location = Pawn:K2_GetActorLocation()
print(string.format("[MyLuaMod] Player location: {X=%.3f, Y=%.3f, Z=%.3f}\n", Location.X, Location.Y, Location.Z))
if lastLocation then
print(string.format("[MyLuaMod] Player moved: {delta_X=%.3f, delta_Y=%.3f, delta_Z=%.3f}\n",
Location.X - lastLocation.X,
Location.Y - lastLocation.Y,
Location.Z - lastLocation.Z)
)
end
lastLocation = Location
end
RegisterKeyBind(Key.F1, { ModifierKey.CONTROL }, function()
print("[MyLuaMod] Key pressed\n")
ExecuteInGameThread(function()
ReadPlayerLocation()
end)
end)
When you load the game until you can move the character, press the hotkey, move the player, press it again, the mod will generate a following output or something very similar:
...
[2024-01-09 19:37:27] Starting Lua mod 'MyLuaMod'
[2024-01-09 19:37:27] [Lua] [MyLuaMod] Mod loaded
...
[2024-01-09 19:37:32] [Lua] [MyLuaMod] Key pressed
[2024-01-09 19:37:32] [Lua] [MyLuaMod] Player location: {X=-63.133, Y=4.372, Z=90.000}
[2024-01-09 19:37:39] [Lua] [MyLuaMod] Key pressed
[2024-01-09 19:37:39] [Lua] [MyLuaMod] Player location: {X=788.232, Y=-639.627, Z=90.000}
[2024-01-09 19:37:39] [Lua] [MyLuaMod] Player moved: {delta_X=851.364, delta_Y=-643.999, delta_Z=0.000}
...
Using Custom Lua Bindings
To make development of Lua mods easier, we've added the ability to dump custom Lua bindings from your game. We also have a shared types file that contains default UE types and the API functions/classes/objects that are available to you.
Dumping Custom Lua Bindings
Simply open the Dumpers tab in the GUI console window and hit the "Dump Lua Bindings" button.
The generator will place the files into the Mods/shared/types
folder.
Warning: Do not include any of the generated files in your Lua scripts. If they are included, any globals set by UE4SS will be overridden and things will break.
To Use Bindings
I recommend using Visual Studio Code to do your Lua development. You can install the extension just called "Lua" by sumneko.
Open the Mods
folder as a workspace. You can also save this workspace so you don't have to do this every time you open VS Code.
When developing your Lua mods, the language server should automatically parse all the types files and give you intellisense.
Warning: For many games the number of types is so large that the language server will fail to parse everything. In this case, you can add a file called
.luarc.json
into the root of your workspace and add the following:
{
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
"workspace.maxPreload": 50000,
"workspace.preloadFileSize": 5000
}
How to use your mod's directory as workspace
As alternative you can open just your mod's root directory as workspace.
In this case you need to add a .luarc.json
with "workspace.library"
entries containing a path to the "shared" folder and the "Scripts" directory of your mod.
Both paths can be relative.
Example .luarc.json:
{
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
"workspace.maxPreload": 50000,
"workspace.preloadFileSize": 5000,
"workspace.library": ["../shared", "Scripts"]
}
Annotating
To get context sensitive information about the custom game types, you need to annotate your code (alternative documentation). This is done by adding a comment above the function/class/object that you want to annotate.
Example
---@class ITM_MisSel_Biome_C
local biome = FindFirstOf("ITM_MisSel_Biome_C")
---@type int
local numMissions = biome.NumMissions
---@type FVector
local soundCoords = { 420.5, 69.0, 3.1 }
biome:SetSoundCoordinate(soundCoords)
C++ API
These are the C++ API functions available in UE4SS, on top of the standard libraries that C++ comes with by default and the reflected functions available in Unreal Engine.
You are expected to have a basic understanding of C++ and Unreal Engine's C++ API before using these functions.
You may need to read code in the UEPsuedo repository (more specifically, the include/Unreal
directory) to understand how to use these functions.
For version: 3.1.0
Current status: incomplete
Blueprint Macros
The following macros are used to manipulate blueprint functions from C++.
Note: Param names for wrappers must be identical to the names used for the function in UE, and they should then be passed to macros with a
PropertyName
param as shown inAActor.cpp
.
This does not apply to macros with the _CUSTOM
suffix.
With those macros you have to supply both the UE property name as well as the name of your C++ param.
These _CUSTOM
suffixed macros are useful when the UE property name contains spaces or other characters that aren't valid for a C++ variable.
Regular macros:
Intended for normal use by modders.
UE_BEGIN_SCRIPT_FUNCTION_BODY
:
Finds non-native (meaning BP) UFunction by its full name without the type prefixed, throws if not found.
UE_BEGIN_NATIVE_FUNCTION_BODY
:
Same as above except for native, meaning non-BP UFunctions.
See: AActor::K2_DestroyActor
UE_SET_STATIC_SELF
:
Used for static functions, and should be the CDO to the class that the UFunction belongs to.
See: UKismetNodeHelperLibrary::GetEnumeratorUserFriendlyName.
UE_COPY_PROPERTY
:
Copies the property of the supplied name into the already allocated params struct.
- Param 1: The name, without quotes, of a property that exists for this UFunction.
- Param 2: The type that you want the underlying value to be copied as. For example, without quotes, "float" for
FFloatProperty
.
UE_COPY_PROPERTY_CUSTOM
:
Copies the property of the supplied name into the already allocated params struct.
- Param 1: The name, without quotes, of a property that exists for this UFunction.
- Param 2: A C++ compatible variable name for the property.
- Param 3: The type that you want the underlying value to be copied as. For example, without quotes, "float" for
FFloatProperty
.
UE_COPY_STRUCT_PROPERTY_BEGIN
:
Begins the process of copying an entire struct.
- Param 1: The name, without quotes, of an
FStructProperty
that exists for this UFunction.
UE_COPY_STRUCT_PROPERTY_CUSTOM_BEGIN
:
Begins the process of copying an entire struct.
- Param 1: The name, without quotes, of an
FStructProperty
that exists for this UFunction. - Param 2: A C++ compatible variable name for the property.
UE_COPY_STRUCT_INNER_PROPERTY
:
Copies a property from within an FStructProperty
into the already allocated params struct.
- Param 1: The name, without quotes, of the
FStructProperty
supplied toUE_COPY_STRUCT_PROPERTY_BEGIN
. - Param 2: The name, without quotes, of a property that exists in the supplied
FStructProperty
. - Param 3: The type that you want the underlying value to be copied as. For example, without quotes, "float" for
FFloatProperty
. - Param 4: The name of the C++ variable that you're copying.
See: AActor::K2_SetActorRotation
UE_COPY_STRUCT_INNER_PROPERTY_CUSTOM
:
- Param 1: The name, without quotes, of the
FStructProperty
supplied toUE_COPY_STRUCT_PROPERTY_BEGIN
. - Param 2: The name, without quotes, of a property that exists in the supplied
FStructProperty
. - Param 3: A C++ compatible variable name for the property.
- Param 4: The type that you want the underlying value to be copied as. For example, without quotes, "float" for
FFloatProperty
. - Param 5: The name of the C++ variable that you're copying.
UE_COPY_OUT_PROPERTY
:
Copies the out property of the supplied name from the params struct into the supplied C++ variable.
This means the wrapper param (which is named the same as the property supplied) must be a reference, meaning suffixed with a "&".
- Param 1: The name, without quotes, of a property that exists for this UFunction.
- Param 2: The type that you want the underlying value to be copied as. For example, without quotes, "float" for
FFloatProperty
.
See: UGameplayStatics::FindNearestActor
UE_COPY_OUT_PROPERTY_CUSTOM
:
Copies the out property of the supplied name from the params struct into the supplied C++ variable.
- Param 1: The name, without quotes, of a property that exists for this UFunction.
- Param 2: A C++ compatible variable name for the property.
- Param 3: The type that you want the underlying value to be copied as. For example, without quotes, "float" for
FFloatProperty
.
This means the wrapper param (which is named the same as the property supplied) must be a reference, meaning suffixed with a "&".
UE_COPY_VECTOR:
Helper for copying an FVector. Must use UE_COPY_STRUCT_PROPERTY_BEGIN
first.
- Param 1: The C++ name, without quotes, of the FVector to copy from.
- Param 2: The name, without quotes, of the FVector, same as supplied to
UE_COPY_STRUCT_PROPERTY_BEGIN
.
UE_COPY_STL_VECTOR_AS_TARRAY
:
Helper for copying a TArray.
- Param 1: The name, without quotes, of an
FArrayProperty
that exists for this UFunction. - Param 2: The C++ type, without quotes, that the TArray holds. For example, without quotes, "float", for
FFloatProperty
. - Param 3: The C++ that the contents of the TArray will be copied into.
UE_CALL_FUNCTION
:
Performs a non-static function call. All non-out params must be copied ahead of this.
UE_CALL_STATIC_FUNCTION
:
Performs a static function call, using the CDO provided by UE_SET_STATIC_SELF
as the static instance. All non-out params must be copied ahead of this.
UE_RETURN_PROPERTY
:
Copies the underlying value that the UFunction returned and returns it.
- Param 1: The type that you want the underlying value to be copied as. For example, without quotes, "float" for
FFloatProperty
.
UE_RETURN_PROPERTY_CUSTOM
:
- Param 1: The type that you want the underlying value to be copied as. For example, without quotes, "float" for
FFloatProperty
. - Param 2: The name, without quotes, for the property of this function where the return value will be copied from.
UE_RETURN_VECTOR
:
Helper for returning an FVector
.
UE_RETURN_STRING
:
Helper for returning an FStrProperty
. Converts to StringType
.
UE_RETURN_STRING_CUSTOM
:
Helper for returning an FStrProperty
. Converts to StringType
.
- Param 1: The name, without quotes, for the
FStrProperty
of this function where the return value will be copied from.
WITH_OUTER
:
Used for templated C++ types passed to macros, like TArray or TMap.
For example, pass, without quotes, WITH_OUTER(TMap, FName, int)
instead of TMap<FName, int>
to all macros.
Internal macros
These are only used by other macros, or by users of our C++ API if they properly understand the internals of the macros, and this requires preexisting knowledge around how UFunctions work, and you'll likely have to BPMacros.hpp to understand how to use them properly.
UE_BEGIN_FUNCTION_BODY_INTERNAL
:
Throws if the UFunction doesn't exist, and allocates enough space (on the stack when possible, otherwise the heap) for the params and return value(s).
UE_COPY_PROPERTY_INTERNAL
:
Finds the property, and throws if not found.
UE_COPY_PROPERTY_CUSTOM_INTERNAL
:
Finds the property with the supplied name, and throws if not found.
UE_RETURN_PROPERTY_INTERNAL
:
Finds the property to be used for the return value, throws if not found.
C++ Examples
Template repository for making UE4SS C++ mods: UE4SSCPPTemplate - note that if you are developing on latest main branch, you should use the dev
branch of this template repo if there are any changes to the template that are not yet merged into main
.
Search GitHub for any Lua code calling reasonably uniquely-named UE4SS API functions, excluding the actual UE4SS repository from the search:
https://github.com/search?q=language%3Acpp++%22+%3A+public+CppUserModBase%22+NOT+repo%3AUE4SS-RE%2FRE-UE4SS+NOT+repo%3AEpicGames%2FUnrealEngine&type=code
Creating a C++ mod
This guide will help you create a C++ mod using UE4SS.
It's split up into four parts.
Part one goes over the prerequisites.
Part two goes over creating the most basic C++ mod possible.
Part three will show you how to interact with UE4SS and UE itself (via UE4SS).
Part four will cover installation of the mod.
The guide requires having a working C++ development environment with
xmake
andgit
, preferably similar to the one required to build UE4SS itself from sources.
Part 1
Make sure you have downloaded all the build requirements mentioned in the README before following these steps!
- Make an Epic account and link it to your GitHub account
- Check your email and accept the invitation to the @EpicGames GitHub organization for Unreal source access.
- Setup SSH keys on your GitHub account which will let git access the Unreal source you got access for in 2 and 3.
- Make a directory somewhere on your computer, the name doesn't matter but I named mine
MyMods
. - Clone the RE-UE4SS repo so that you end up with
MyMods/RE-UE4SS
. - Open CMD and cd into
RE-UE4SS
and execute:git submodule update --init --recursive
- Go back to the
MyMods
directory and create a new directory, this directory will contain your mod source files. I named mineMyAwesomeMod
. - Create a file called
xmake.lua
insideMyMods
and put this inside it:
includes("RE-UE4SS")
includes("MyAwesomeMod")
Part #2
- Create a file called
xmake.lua
insideMyMods/MyAwesomeMod
and put this inside it:
local projectName = "MyAwesomeMod"
target(projectName)
add_rules("ue4ss.mod")
add_includedirs(".")
add_files("dllmain.cpp")
- Make a file called
dllmain.cpp
inMyMods/MyAwesomeMod
and put this inside it:
#include <stdio.h>
#include <Mod/CppUserModBase.hpp>
class MyAwesomeMod : public RC::CppUserModBase
{
public:
MyAwesomeMod() : CppUserModBase()
{
ModName = STR("MyAwesomeMod");
ModVersion = STR("1.0");
ModDescription = STR("This is my awesome mod");
ModAuthors = STR("UE4SS Team");
// Do not change this unless you want to target a UE4SS version
// other than the one you're currently building with somehow.
//ModIntendedSDKVersion = STR("2.6");
printf("MyAwesomeMod says hello\n");
}
~MyAwesomeMod() override
{
}
auto on_update() -> void override
{
}
};
#define MY_AWESOME_MOD_API __declspec(dllexport)
extern "C"
{
MY_AWESOME_MOD_API RC::CppUserModBase* start_mod()
{
return new MyAwesomeMod();
}
MY_AWESOME_MOD_API void uninstall_mod(RC::CppUserModBase* mod)
{
delete mod;
}
}
- In the command prompt, in the
MyMods
directory, execute either: A.
xmake f -m "Game__Shipping__Win64"
xmake
or B.
xmake project -k vsxmake2022
If you chose option B
, the VS solution will be in the vsxmake2022
directory.
- Open
MyMods/vsxmake2022/MyMods.sln
- Make sure that you're set to the
Game___Shipping__Win64
configuration unless you want to debug. - Find your project (in my case: MyAwesomeMod) in the solution explorer and right click it and hit
Build
.
Part #3
In this part, we're going to learn how to log to file, and both consoles, as well as find a UObject by name, and log that name.
- Add
#include <DynamicOutput/DynamicOutput.hpp>
under#include <Mod/CppUserModBase.hpp>
.
You can now also remove#include <stdio.h>
because we'll be removing the use ofprintf
which was the only thing that required it. - To save some time and annoyance and make the code look a bit better, add this line below all the includes:
using namespace RC;
- Replace the call to printf in the body of the
MyAwesomeMod
constructor with:
Output::send<LogLevel::Verbose>(STR("MyAwesomeMod says hello\n"));
It's longer than a call to printf
, but in return the message gets propagated to the log file and both the regular console and the GUI console.
We also get some support for colors via the LogLevel
enum.
- Add this below the DynamicOutput include:
#include <Unreal/UObjectGlobals.hpp>
#include <Unreal/UObject.hpp>
- Let's again utilize the
using namespace
shortcut by adding this below the first one:using namespace RC::Unreal;
- Add this function in your mod class:
auto on_unreal_init() -> void override
{
// You are allowed to use the 'Unreal' namespace in this function and anywhere else after this function has fired.
auto Object = UObjectGlobals::StaticFindObject<UObject*>(nullptr, nullptr, STR("/Script/CoreUObject.Object"));
Output::send<LogLevel::Verbose>(STR("Object Name: {}\n"), Object->GetFullName());
}
Note that Output::send
doesn't require a LogLevel
and that we're using {}
in the format string instead of %s
.
The Output::send
function uses std::format
in the back-end so you should do some research around std::format or libfmt if you want to know more about it.
- Right click your project and hit
Build
.
Part #4
Click to go to guide for installing a C++ Mod
Installing a C++ Mod
-
This part assumes you have UE4SS installed and working for your game already. If not, refer to the installation guide.
-
After building, you will have the following file:
MyAwesomeMod.dll
inMyMods\Binaries\<Configuration>\MyAwesomeMod
-
Navigate over to your game's executable folder and open the
Mods
folder. Here we'll do a couple things:- Create a folder structure in
Mods
that looks likeMyAwesomeMod\dlls
. - Move
MyAwesomeMod.dll
inside thedlls
folder and rename it tomain.dll
.
- Create a folder structure in
The result should look like:
Mods\
MyAwesomeMod\
dlls\
main.dll
- To enable loading of your mod in-game you will have to edit the
mods.txt
located in theMods
folder. By default it looks something like this:
CheatManagerEnablerMod : 1
ActorDumperMod : 0
ConsoleCommandsMod : 1
ConsoleEnablerMod : 1
SplitScreenMod : 0
LineTraceMod : 1
BPModLoaderMod : 1
jsbLuaProfilerMod : 0
; Built-in keybinds, do not move up!
Keybinds : 1
Here you will want to add the line:
MyAwesomeMod : 1
above the keybinds to enable MyAwesomeMod
.
Alternatively, place an empty text file named enabled.txt
inside of the MyAwesomeMod folder. This method is not recommended because it does not allow load ordering
and bypasses mods.txt, but may allow for easier installation by end users.
- Launch your game and if everything was done correctly, you should see the text "MyAwesomeMod says hello" highlighted in blue somewhere at the top of UE4SS console (before all the scanning occurs), and if you used the
on_unreal_init
function, you should see "Object Name: /Script/CoreUObject.Object" highlighted in blue as well (right after the scanning finishes).
Automation
Now that you understand how the process works, you can use the UE4SS CPP Template repository that automates the process of creating a mod, building it, and installing it. Be aware that the new_mod_setup.bat
script will checkout the commit at the latest release so that you can be sure that your mod is being built with the correct ABI as latest release.
NOTE: Any changes to the build system that affects the mod template is pushed to the
dev
branch, which is then merged into main when a new UE4SS release is created. This makes sure that the template is always in-sync with the latest UE4SS release.
Creating GUI tabs with a C++ mod
UE4SS already includes the ImGui library to render its console GUI, built from the UE4SS-RE/imgui repo. Refer to ImGui documentation in that repo on how to use ImGui-specific classes and methods for rendering actual buttons and textboxes and other window objects.
This guide will show how you create custom tabs for the GUI with a C++ mod, and the guide will take the form of comments in the code example below:
#include <Mod/CppUserModBase.hpp>
#include <UE4SSProgram.hpp>
class MyAwesomeMod : public RC::CppUserModBase
{
private:
int m_private_number{33};
std::shared_ptr<GUI::GUITab> m_less_safe_tab{};
public:
MyAwesomeMod() : CppUserModBase()
{
ModName = STR("MyAwesomeMod");
ModVersion = STR("1.0");
ModDescription = STR("This is my awesome mod");
ModAuthors = STR("UE4SS Team");
// The 'register_tab' function will tell UE4SS to render a tab.
// Tabs registered this way will be automatically cleaned up when this C++ mod is destructed.
// The first param is the display name of your tab.
// The second param is a callback that UE4SS will use to render the contents of the tab.
// The param to the callback is a pointer to your mod.
register_tab(STR("My Test Tab"), [](CppUserModBase* instance) {
// In this callback, you can start rendering the contents of your tab with ImGui.
ImGui::Text("This is the contents of the tab");
// You can access members of your mod class with the 'instance' param.
auto mod = dynamic_cast<MyAwesomeMod*>(instance);
if (!mod)
{
// Something went wrong that caused the 'instance' to not be correctly set.
// Let's abort the rest of the function so that you don't access an invalid pointer.
return;
}
// You can access both public and private members.
mod->render_some_stuff(mod->m_private_number);
});
// The 'UE4SSProgram::add_gui_tab' function is another way to tell UE4SS to render a tab.
// This way of registering a tab will make you responsible for cleaning up the tab when your mod destructs.
// Failure to clean up the tab on mod destruction will result in a crash.
// It's recommended that you use 'register_tab' instead of this function.
m_less_safe_tab = std::make_shared<GUI::GUITab>(STR("My Less Safe Tab"), [](CppUserModBase* instance) {
// This callback is identical to the one used with 'register_tab' except 'instance' is always nullptr.
ImGui::Text("This is the contents of the less safe tab");
});
UE4SSProgram::get_program().add_gui_tab(m_less_safe_tab);
}
~MyAwesomeMod() override
{
// Because you created a tab with 'UE4SSProgram::add_gui_tab', you must manually remove it.
// Failure to remove the tab will result in a crash.
UE4SSProgram::get_program().remove_gui_tab(m_less_safe_tab);
}
auto on_ui_init() -> void override
{
// It's critical that you enable ImGui if you intend to use ImGui within the context of UE4SS.
// If you don't do this, a crash will occur as soon as ImGui tries to render anything, for example in your tab.
UE4SS_ENABLE_IMGUI()
}
auto render_some_stuff(int Number) -> void
{
auto calculated_value = Number + 1;
ImGui::Text(std::format("calculated_value: {}", calculated_value).c_str());
}
};
#define MY_AWESOME_MOD_API __declspec(dllexport)
extern "C"
{
MY_AWESOME_MOD_API RC::CppUserModBase* start_mod()
{
return new MyAwesomeMod();
}
MY_AWESOME_MOD_API void uninstall_mod(RC::CppUserModBase* mod)
{
delete mod;
}
}
Fixing missing AOBs
If UE4SS won't properly start because of missing AOBs, you can provide your own AOB and callback using Lua.
For this guide you'll need to know what a root directory
and working directory
is.
A root directory
is always the directory that contains ue4ss.dll
.
A working directory
is either the directory that contains ue4ss.dll
OR a game specific directory, for example <root directory>/SatisfactoryEarlyAccess
.
How to find AOBs
caution
Finding AOBs for a game is no simple task and requires research into basic reverse engineering principles. It's not something for which we can just make an all-encompassing guide.
Since the process is quite complicated, here will just cover the general steps you need to take.
- Make a blank shipped game in your game's UE version, with PDBs
- Read game's memory using x64dbg
- Look for the signature you need - those can be found below
- Grab a copy of the bytes from that function, sometimes the header is enough. If it is not, it may be better to grab a call to the function and if it's not a virtual function, you can grab the RIP address there
- Open your game's memory in x64dbg and search it for the same block of bytes
- If you find it, you can use the swiss army knife tool to extract the AOB for it which you can use in a simple script such as example here
For more in-depth instructions, see the advanced guide.
How to setup your own AOB and callback
- Create the directory
UE4SS_Signatures
if it doesn't already exist in yourworking directory
. - Identify which AOBs are broken and needs fixing.
- Make the following files inside
UE4SS_Signatures
, depending on which AOBs are broken:- GUObjectArray.lua
- FName_ToString.lua
- FName_Constructor.lua
- FText_Constructor.lua
- StaticConstructObject.lua
- GMalloc.lua
- Inside the
.lua
file you need a globalRegister
function with no params- Keep in mind that the names of functions in Lua files in the
UE4SS_Signatures
directory are case-senstive.
- Keep in mind that the names of functions in Lua files in the
- The
Register
function must return the AOB that you want UE4SS to scan for.- The format is a list of nibbles, and every two forms a byte.
- I like putting a space between each byte just for clarity but this is not a requirement.
- An example of an AOB:
8B 51 04 85
. - Another example of an AOB:
8B510485
. - The AOB scanner supports wildcards for either nibble or the entire byte.
- Next you need to create a global
OnMatchFound
function.- This function has one param,
MatchAddress
, and this is the address of the match. - It's in this function that you'll place all your logic for calculating the final address.
- The most simple way to do this is to make sure that your AOB leads directly to the start of the final address. That way you can simply return
MatchAddress
. - In the event that you're doing something more advanced (e.g. indirect aob scan), UE4SS makes available two global functions,
DerefToInt32
which takes an address and returns, as a 32-bit integer, whatever data is located there ORnil
if the address could not be dereferenced, andprint
for debugging purposes.
- This function has one param,
What 'OnMatchFound' must return for each AOB
- GUObjectArray
- Must return the exact address of the global variable named 'GUObjectArray'.
- FName_ToString
- Must return the exact address of the start of the function 'FName::ToString'.
Function signature:public: void cdecl FName::ToString(class FString & ptr64)const __ptr64
- Must return the exact address of the start of the function 'FName::ToString'.
- FName_Constructor
- Must return the exact address of the start of the function 'FName::FName'.
This callback is likely to be called many times and we do a check behind the scenes to confirm if we found the right constructor.
It doesn't matter if your AOB finds both 'char*' versions and 'wchar_t*' versions.
Function signature:public: cdecl FName::FName(wchar_t const * ptr64,enum EFindName) __ptr64
- Must return the exact address of the start of the function 'FName::FName'.
- FText_Constructor
- Must return the exact address of the start of the function 'FText::FText'.
Function signature:public: cdecl FText::FText(class FString & ptr64)const __ptr64
- Must return the exact address of the start of the function 'FText::FText'.
- StaticConstructObject
- Must return the exact address of the start of the global function 'StaticConstructObject_Internal'.
In UE4SS, we scan for a call in the middle of 'UUserWidget::InitializeInputComponent' and then resolve the call location.
Function signature:class UObject * __ptr64 __cdecl StaticConstructObject_Internal(struct FStaticConstructObjectParameters const & __ptr64)
- Must return the exact address of the start of the global function 'StaticConstructObject_Internal'.
- GMalloc
- Must return the exact address of the global variable named 'GMalloc'.
In UE4SS, we scan forFMemory::Free
and then resolve the MOV instruction closest to the first CALL instruction.
- Must return the exact address of the global variable named 'GMalloc'.
Example script (Simple, direct scan)
function Register()
return "48 8B C4 57 48 83 EC 70 80 3D ?? ?? ?? ?? ?? 48 89"
end
function OnMatchFound(MatchAddress)
return MatchAddress
end
Example script (Advanced, indirect scan)
function Register()
return "41 B8 01 00 00 00 48 8D 15 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? E9"
end
function OnMatchFound(MatchAddress)
local InstrSize = 0x05
local JmpInstr = MatchAddress + 0x14
local Offset = DerefToInt32(JmpInstr + 0x1)
local Destination = JmpInstr + Offset + InstrSize
return Destination
end
Fixing Missing AOBs (Advanced & In-Depth)
When UE4SS fails to properly launch due to missing AOBs (Array of Bytes signatures), you can provide custom AOBs and callback functions using Lua.
Doing so, however, requires a level of reverse engineering knowledge and tooling setup that may feel complex at first.
This guide expands upon the original instructions, providing more detail, some context and tips.
Prerequisites
-
Knowledge of Basic Reverse Engineering Concepts:
You should have a general idea of what a signature (AOB) is, how to use a debugger, and how to navigate memory in x64dbg. -
Familiarity with UE4SS Setup & Directories:
Make sure you know where theUE4SS_Signatures
folder should be created (it should be next toue4ss.dll
or in a game-specific working directory). -
Preparation and Tools Installed:
- Epic Games Launcher & Unreal Engine: For creating a “blank shipped game” environment with the correct engine version.
- x64dbg: A debugger tool for Windows (https://x64dbg.com/).
- (Optional) Baymax Tools: A plugin to help generate signatures easily.
- (Optional) Swiss Army Knife (by Nukem9): For more easily extracting signatures with correct wildcards.
When is this needed, and why ?
Some games don't use Unreal Engine with its default configuration, and we only support the default configuration out of the box.
Anything that affects the code generated by the compiler, including the devs using Clang instead of MSVC, can make our built-in AOBs no longer be valid.
These AOBs are used to find functions and variables that are critical for UE4SS to work.
High-Level Overview
- Identify Which Signatures Are Missing: Determine which functions or variables UE4SS cannot find (e.g., GUObjectArray, GMalloc, FName or FText constructors).
- Set Up a Reference Environment: Create a blank Unreal Engine game (using the Shipping target) with debug files (PDBs) that uses the same Unreal Engine version as your game. This environment helps you identify function signatures cleanly.
- Reverse Engineer and Extract AOBs: Using x64dbg (and optional plugins), open the blank game and locate the desired function in memory. Copy out the unique bytes that form a reliable signature. This signature should be properly wildcarded, if it's not, it won't be found in your game.
- Apply Your Signatures to the Actual Game: Attach x64dbg to the target game, find the matching bytes, and confirm that the signature you extracted matches code in the game you want to mod.
- Create a Lua Script: Write a Lua file in
UE4SS_Signatures
to tell UE4SS what AOB pattern to search for (throughRegister
) and what final address to return (throughOnMatchFound
).
Finding AOBs: A More Detailed Explanation
caution
Reverse engineering these signatures isn’t trivial. You may need to step outside the scope of this guide, read reverse-engineering tutorials, or ask for community support. The steps below are a starting point, not a complete guide on reverse engineering.
Step 1: Determine Your Game’s Unreal Engine Version
UE4SS tries to detect the engine version automatically. If you need to verify, the following steps usually work:
- Right-click on the game’s
.exe
file (often inBinaries
folder). - Select Properties -> Details tab.
- Look for the “File Version” or “Product Version” field, which often correlates to the Unreal Engine version.
For example: If it says 5.3.2.0
, it likely corresponds to UE 5.3.2.
In rare cases, the version will either be empty, or it will refer to the game version instead of the engine version.
Note that the last number doesn't usually matter, so if your game is using UE 5.3.2, your blank game can generally use any 5.3 version.
Step 2: Installing the Matching Unreal Engine Version
- Create an Epic Games account and install the Epic Games Launcher.
- In the launcher, go to Unreal Engine -> Library tab and install the engine version matching your game’s version (e.g., UE 5.3.2).
Step 3: Creating a Blank Shipped Game with PDBs
- Launch the installed Unreal Engine version.
- In the New Project window, select the Games tab -> Blank template.
- Uncheck “Starter Content” because it's not needed, and unchecking this will save time and space.
- Name your project and specify a directory.
- Once created, open Platforms -> Packaging Settings, and enable “Include Debug Files in Shipping Builds”.
- From Platforms -> Windows, select “Shipping” configuration (or whichever build matches your target game’s build type).
- Package Project -> Choose a folder.
- After packaging completes, verify that the output folder’s
Binaries
directory contains both a.exe
and a.pdb
. The.pdb
file provides symbolic information for reverse engineering.
Step 4: Using x64dbg to Analyze the Blank Project
- Install x64dbg from https://x64dbg.com/.
- Run the
.exe
of your newly packaged blank project from its root directory. - Open x64dbg.
- Go to File -> Attach -> Select the blank project
.exe
. - Ensure you’re attaching to the shipped
.exe
located inBinaries
or root (whichever works).
- Go to File -> Attach -> Select the blank project
Step 5: Identifying the Function of Interest
You need to know which function or variable you’re trying to match in your target game. For example, if UE4SS fails on GMalloc
, you must find FMemory::Free
as a reference to locate GMalloc
.
Optional Steps:
- Connect your Epic Games account with GitHub to access the Unreal Engine source code.
- Epic Games Website -> Manage Account -> Apps and Accounts -> GitHub
- Accept invitation via email.
- Browse the Unreal Engine source for the function you need. For
FMemory::Free
in UE5.3.2, you might look at:
FMemory::Free in UE source
Step 6: Locating the Function in x64dbg
Note that there's a bug in x64dbg where navigating to code or memory from the symbols tab sometimes doesn't work properly.
If you're navigating and not seeing what you expect, it's worth restarting x64dbg and trying again.
You can also try copy the address from the symbols tab and manually navigate to it in the correct panel in the CPU tab.
- In x64dbg, switch to the Symbols tab.
- In the left pane, select the
.exe
. - In the right pane, search for the function name (e.g.,
FMemory::Free
). - Double-click the found function to navigate back to the CPU view, positioning the instruction pointer at the start of the function in memory.
Step 7: Extracting a Signature
Once you’ve identified the start of the function, you need to copy a unique sequence of bytes:
- Consider installing Baymax Tools or Swiss Army Knife for x64dbg to ease signature extraction.
- Highlight a set of instructions at the start of the function.
- Right-click -> Copy -> Selection (Bytes only) to get a raw byte sequence.
- With Baymax Tools: Right-click -> Baymax Tools -> Copy Signature for a ready-made signature pattern.
- Save these bytes or patterns for later comparison. You may want to store them in a file to easily refer back.
Understanding Terminology
- Signature: A carefully chosen sequence of bytes that uniquely identifies a function or code snippet.
- Block of Bytes: A simple, possibly unstructured, segment of raw data without inherent uniqueness.
- RIP (Instruction Pointer): The CPU register that holds the address of the next instruction to execute.
Step 8: Searching in the Actual Target Game
Now that you have a reference signature, you need to find it in your target game:
- Launch the target game
.exe
. - Attach x64dbg as before: File -> Attach -> Select the game’s
.exe
. - In x64dbg, search memory for the signature you extracted from the blank project.
- If direct search fails, try partial sequences of bytes.
- Try patterns generated by Baymax Tools.
- Compare and contrast instructions between the blank project and the actual game to locate a similar code region.
If you find a match, you’ve identified the address that corresponds to the target function or variable in the actual game.
If you can’t find it, you may need to refine your signature, pick a different part of the function, or ask for community help (UE4SS Discord or GitHub Issues).
Step 9: Applying the Signature in UE4SS
Once you have a working AOB:
- Create the
UE4SS_Signatures
directory in your working directory (if it doesn’t already exist). - Make a
.lua
file corresponding to the missing AOB (e.g.,GMalloc.lua
if you’re fixing GMalloc). - Inside this
.lua
file, define theRegister
andOnMatchFound
functions.
Register function: Returns your AOB signature as a string (spaces optional), e.g.:
function Register()
return "48 8B D9 48 8B 0D ?? ?? ?? ?? 48 85 C9 75 0C E8 ?? ?? ?? ??"
end
OnMatchFound function: Receives the match address and must return the exact memory address of the target function or variable. Use DerefToInt32
if needed to resolve relative addresses.
function OnMatchFound(MatchAddress)
local MovInstr = MatchAddress + 0x03
local Offset = DerefToInt32(MovInstr + 0x3)
local RIP = MovInstr + 0x7
local GMallocAddress = RIP + Offset
return GMallocAddress
end
Verifying Your Work
- Run the game with UE4SS again. If successful, UE4SS now uses your custom script to find the previously missing address.
- If it fails silently, confirm:
- That the Lua script is in the correct directory.
- That your AOB is correct and unique.
- That
OnMatchFound
returns the correct final address.
If still stuck, consider posting detailed steps, logs, and code snippets to the UE4SS community channels.
The more detail you provide, the more likely someone can guide you to a solution.
What ‘OnMatchFound’ Should Return, and Example Scripts
Tips, Tricks, and Troubleshooting
- Patience & Iteration: Extracting and refining AOBs can be trial-and-error. If a signature doesn’t work, try a different sequence of bytes or look elsewhere in the function.
- Partial Signatures: If the full function signature isn’t found, try unique parts of it.
- Community Help: If stuck, show your steps, scripts, and logs on the UE4SS Discord or GitHub Issues.
- Check Offsets Carefully: Off-by-one or incorrect indexing is a common issue. Double-check your calculations.
- Manual Verification: Sometimes running the blank project again in x64dbg and comparing with the target game’s memory can highlight discrepancies.
By following these expanded steps and leveraging the provided tools, you’ll have a more comprehensive understanding of how to fix missing AOBs with UE4SS.
Although still complex, this extended guide should help clarify the process and offer practical insights into the reverse engineering territory.
Generating UHT compatible headers
Supported versions
While the UHT header generator is only officially supported in 4.25+
, it has worked for older game versions (tested on 4.18.3
; 4.17
(has some default property issues that should be fixed soon)). It also works for 5.0+
.
How to use
The key bind to generate headers is by default CTRL
+ Numpad 9
, and it can be changed in Mods/Keybinds/Scripts/main.lua
.
To utilize the generated headers to their full potential, see UE4GameProjectGenerator by Archengius (link to Buck's fork because of a couple fixes that Arch is too lazy to merge).
The project generator will only compile for UE versions
4.22
and higher. Engine customizations by developers may lead to unexpected results. If generating a project for an engine version older than4.22
, generate it by compiling the project generator for4.22
or higher first.
Before compiling the projectgencommandlet, open GameProjectGenerator.uproject
and your game's pluginmanifest or .uproject
and add any default engine plugins used by the game or plugins that the game uses and you found open source or purchased (it is not recommended to include purchased plugins in a public uproject) to the commandlet's uproject file.
After compiling the commandlet and running it on your game files, simply change the engine version in the generated .uproject
to the correct engine version for your game.
This commandlet (by Spuds) will enter the CLI commands for the project gen for you, and make a batch file to regenerate with the same settings (e.g., to regenerate after a major game update).
Possible inaccurate generation issues:
UE4SS has two different types of generators, a UHT compatible generator and what's called a CXX generator.
The UHT compatible generator is what's used when creating a .uproject
file with the UE4GameProjectGenerator, and the CXX generator is a very shoddily made generator that doesn't generate UHT macros or proper #include
statements but it does generate headers for core UE classes which the UHT generator doesn't.
Note the UE4SS CXX dumps do not currently have accurate padding. An SDK dump generated from another source may be a better source for determining the below corrections if it generates with correct padding, particularly for the bitfield checks.
Certain default properties may not generate correctly in older engine versions. For example, SoftObjectProperty
was called AssetObjectProperty
and SoftClassProperty
was AssetClassProperty
in <4.17
. It is recommended to also generate an SDK/CXX dump to check for those properties and correct them in your project.
Bitfields will always generate as uint8
. However, they may actually be declared as uint32
in the original source. You can try to determine the actual size based on the CXX/SDK dump to correct these. In a CXX dump the bitfields will show the same offset. If there are multiple bitfields at the same offset and the next property is 4 bytes after that offset, then the bitfield should be changed to uint32.
Instructions for possible errors you may encounter
These are some general instructions of how to generate a project and it also covers a few errors that you are likely to encounter.
The following errors & solutions is what was found when generating projects for various games.
Note that you can check here for solutions even if your game isn't listed below. Error lists compiled by Buckminsterfullerene, CheatingMuppet, Narknon & Blubb.
Inherited Virtuals
UE4SS is unable to generate inherited virtuals if they are unreflected. This is often the source of LNK2001: unresolved external symbol
errors, particularly when a class inherits from an interface. The build log is often not helpful for determining which file needs these virtuals.
To determine the file that they need to be added to, search for the virtual function listed in the error or for the class of the function in the engine, e.g., Module.AkAudio.cpp.obj : error LNK2001: unresolved external symbol "public: virtual class FString const __cdecl UInterpTrack::GetEdHelperClassName(void)const
you could search for GetEdHelperClassName
or UInterpTrack
. Find the parent function and then find any classes within your project that inherit from same. Ideally find a sample of another class that inherits those virtuals within the engine on which to base your fixes, and copy the implementations from same into your affected project files, being sure to change the class name to match the class in your project.
You typically will also want to delete the logic in the implementations to simply return the correct type of data or "null" without actually running any logic.
Game Target Generation
The project gen commandlet does not generate a game target file. Copy and duplicate your GameNameEditor.target.cs
file in the same location. Remove Editor from the name. Open the file and delete "Editor" in the red crossed locations, and replace "Editor" with "Game" in the highlighted location.
Deep Rock Galactic
========================== First do: ==========================
Generate project using commandlet
Then open it in Rider/VS.
========================== Then do, in no particular order: ==========================
Find out what version of mod.io game currently uses. At time of writing it is https://github.com/modio/modio-ue4/releases/tag/v2.16.1792. Delete the existing 'Modio' folder first. Paste the 'Modio', 'ModioTests' and 'ThirdParty' folders from this into Plugins/Modio/Source, replacing the existing 'Modio' folder. Do not replace the .uplugin file. Delete the ModioEx section form the .uplugin file instead.
In:
- CharacterSightSensor.h, FCharacterSightSensorDelegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FCharacterSightSensorDelegate);
- FSDProjectileMovementComponent.h top delegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnProjectilePenetrateDelegate);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnProjectileOutOfPropulsion);
Add the macro DECLARE_DYNAMIC_MULTICAST_DELEGATE(<\DelegateName>); above the UCLASS
In:
- SubHealthComponent.h, line 56
- HealthComponentBase.h, line 117
- HealthComponent.h, line 98
- EnemyHealthComponent.h, line 39
- FriendlyHealthComponent.h, line 33
Comment out UFUNCTION
Errors that look like this: "ActorFunctionLibrary.gen.cpp(153): [C2664] 'void UActorFunctionLibrary::DissolveMaterials(UObject *,const UMeshComponent *&,float)': cannot convert argument 2 from 'UMeshComponent *' to 'const UMeshComponent *&'":
Remove the const before the arguments that have the error (remember to also remove them in the definition stub too)
OR use this regex string (const) ((\w+)\*\&) and replace with $2
In "ShowroomStage.cpp" inside of the implementation of the constructor, comment out "this->SceneCapture = CreateDefaultSubobject<\USceneCaptureComponent2D>(TEXT("SceneCapture"));"
Set supported platforms to windows
cyubeVR
Add the following 4 lines in the "Plugins" section in the generated "cyubeVR.uproject":
{
"Name": "ChaosEditor",
"Enabled": false
}
Copy and paste the cyubeVREditor.Target.cs file (inside Source folder) and name it cyubeVRGame.Target.cs. Then replace any mentions of "editor" and replace with "game" inside of this new file
Right click generated project and open with IDE (e.g. Rider)
Comment out UFUNCTION() in ReceiveLightActor.h
- UseActorCustomLocation
- GetActorCustomLocation
Set the "_MAX UMETA(Hidden)," to "_MAX = 0xFF UMETA(Hidden)," in:
- EUGCMatchingUGCTypeBP.h
- EItemPreviewTypeBP.h
Remove the constructor from IpNetDriverUWorks.h and cpp files.
Remove TEnumAsByte<> (but not the type inside of it) in:
- OnInput inside VRGripInterface.h
- OnEndPlay inside VRGripScriptBase.h and its _Implementation version in the .cpp file
- SetMobilityAllEvent inside DeerCPP.h and its _Implementation version in the .cpp file
Then right click the .uproject and hit "regenerate solution files".
If you get the "failed to create version memory for PCH" errors when trying to build or pack, do it again.
Game 3
Error 1
In an Enum class:
System.ArgumentException - String cannot contain a minus sign if the base is not 10.
Fix:
Remove the BlueprintType meta tag and the uint8 override on the enum ': uint8'.
Error 2
Unable to find 'class', 'delegate', 'enum', or 'struct' with name 'XYZ', where XYZ is an FStruct used within a class with no separate UStruct declaration.
Fix:
DECLARE_DYNAMIC_MULTICAST_DELEGATE(XYZ); , close to the Top of header Files.
Error 3
"is not supported by blueprint."
Fix:
-> Remove BlueprintReadWrite
-> or Remove BlueprintCallable
Error 4
cannot instantiate abstract class
fix:
cpp looks like:
UAbilitySystemComponent* AActorWithGAS::GetAbilitySystemComponent() const {
return nullptr;
}
Go to Header File and add:
UAbilitySystemComponent* GetAbilitySystemComponent() const override;
Error 5
modifiers not allowed on static member functions
Fix:
Remove the modifier, like "const"
Example:
static TSoftObjectPtr<Test> SomeFunction(some args) const; <- remove const
In both h and cpp File.
Error 6
'AAkAMbientSound' no appropriate default consturctor available.
Fix:
-------
Header File
-------
AkAmbientSound();
->
AkAmbientSound(const class FObjectInitializer& ObjectInitializer);
-------
CPP File
-------
AkAmbientSound::AkAmbientSound() {
this->AkEvent = NULL;
}
->
AkAmbientSound::AkAmbientSound(const class FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) {
this->AkEvent = NULL;
}
Astro Colony
========================== First do: ==========================
Generate project using commandlet
Then open it in Rider/VS.
========================== Then do, in no particular order: ==========================
Copy the EditorTarget file, rename it to AstroColonyGame.Target, and inside of it change target type to Game
In:
- VoxelPhysicsPartSpawner_VoxelWorlds.h, FConfigureVoxelWorld;
- TGNamedSlot.h, FOnNamedSlotAdded/Removed
- EHLogicObject.h, FOnSelectedResourcesChanged
- EHSignalObject.h, FOnResourcesSignalOutChanged/FOnSelectedDeviceChanged
- EHInteractableServiceObject, FOnAIInsideChanged
- EHModsBrowsedOptionViewModel, FOnInstalProgressChanged/FOnInstalCompleted
- EHSaveLoadListViewModel, FOnScenarioDetailsUpdated
- EHTrainingObject, FOnTrainedChanged
- EHSchoolObject, FOnAwaitingSpecialistTrainingsChange
- EHSignalReceiver, FOnSignalSendChanged
- EHModsListViewModel, FOnModsOptionSelected
- EHSignalNetwork, FOnSignalChanged
- AbilityAsync_WaitGameplayTagAdded, FAsyncWaitGameplayTagDelegate (put it inside of AbilityAsync_WaitGameplayTag)
Add the macro DECLARE_DYNAMIC_MULTICAST_DELEGATE(<\DelegateName>); above the UCLASS
In:
- AbilityAsync_WaitGameplayTagRemoved.h
- AbilityAsync_WaitGameplayTagAdded.h
Remove the UAbilityAsync_WaitGameplayTag:: from the front of each member
In EHSummaryViewModel.h add #include "EHSaveLoadListViewModel.h"
In:
- MaterialExpressionBlendMaterialAttributesBarycentric.h (every property)
- MaterialExpressionUnpack.h (FExpressionInput Input)
- GameplayCueInterface.h (ForwardGameplayCueToParent)
remove BlueprintReadWrite/BlueprintCallable (where appropriate) flag from the 'UPROPERTY' macro.
In MaterialPackInput.h, add #include "MaterialExpressionIO.h" and remove BlueprintReadWrite flag from the 'UPROPERTY' macro for FExpressionInput Input;
In EAbilityTaskWaitState.h, add None = 0 to the enum
In:
- AbilityTask.h/.cpp
- UMovieSceneGameplayCueTriggerSection
- UMovieSceneGameplayCueSection
comment out the constructor/definition
In AbilitySystemComponent.h/.cpp, comment out:
- The constructor
- ServerSetReplicatedEventWithPayload
- ServerSetReplicatedEvent
- ClientSetReplicatedEvent
In EHBaseButtonWidget.h, add:
#include "Components/HorizontalBox.h"
#include "Components/BackgroundBlur.h"
#include "Components/SizeBox.h"
then remove the forward declarations for UHorizontalBox, UBackgroundBlur, USizeBox.
Then comment out
UFUNCTION(BlueprintImplementableEvent)
void OnInputControllerChanged(TEnumAsByte<ETGInputControllerType> InputControllerType);
In:
- EHPlanetoidDestructibleItem.h
- EHPlanetoidVisualItem.h (also remove array from SpawnDensity)
- EHGridComponent.h, BillboardTextures
- EHHUDGame.h, PopMenuClasses/HUDMenuClasses (also change GetPopMenuClass return type)
- EHScenarioParams.h, TerrainTypeSpawnChances/ShapeTypeSpawnChances
- EHDataProvider.h, every array
replace the array decleration with TArray<> and add BlueprintReadWrite+other normal flags to the 'UPROPERTY' macro. Then update the .cpp constructor.
In VoxelProceduralMeshComponent.h/.cpp, add the UPrimitiveComponent interface, i.e. like this:
VoxelProceduralMeshComponent.h:
#pragma once
#include "CoreMinimal.h"
#include "Components/ModelComponent.h"
#include "VoxelIntBox.h"
#include "VoxelProceduralMeshComponent.generated.h"
class UBodySetup;
class UStaticMeshComponent;
class AVoxelWorld;
class UModelComponent;
UCLASS(Blueprintable, ClassGroup=Custom, meta=(BlueprintSpawnableComponent))
class VOXEL_API UVoxelProceduralMeshComponent : public UModelComponent {
GENERATED_BODY()
public:
private:
UPROPERTY(BlueprintReadWrite, EditAnywhere, Transient, meta=(AllowPrivateAccess=true))
UBodySetup* BodySetup;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Transient, meta=(AllowPrivateAccess=true))
UBodySetup* BodySetupBeingCooked;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Export, Transient, meta=(AllowPrivateAccess=true))
UStaticMeshComponent* StaticMeshComponent;
public:
UVoxelProceduralMeshComponent(const FObjectInitializer& ObjectInitializer);
UFUNCTION(BlueprintCallable)
static void SetVoxelCollisionsFrozen(const AVoxelWorld* VoxelWorld, bool bFrozen);
UFUNCTION(BlueprintImplementableEvent)
void InitChunk(uint8 ChunkLOD, FVoxelIntBox ChunkBounds);
UFUNCTION(BlueprintCallable, BlueprintPure)
static bool AreVoxelCollisionsFrozen(const AVoxelWorld* VoxelWorld);
//~ Begin UPrimitiveComponent Interface.
virtual void CreateRenderState_Concurrent(FRegisterComponentContext* Context) override;
virtual void DestroyRenderState_Concurrent() override;
virtual bool GetLightMapResolution( int32& Width, int32& Height ) const override;
virtual int32 GetStaticLightMapResolution() const override;
virtual void GetLightAndShadowMapMemoryUsage( int32& LightMapMemoryUsage, int32& ShadowMapMemoryUsage ) const override;
virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override;
virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
virtual bool ShouldRecreateProxyOnUpdateTransform() const override;
#if WITH_EDITOR
virtual void GetStaticLightingInfo(FStaticLightingPrimitiveInfo& OutPrimitiveInfo,const TArray<ULightComponent*>& InRelevantLights,const FLightingBuildOptions& Options) override;
virtual void AddMapBuildDataGUIDs(TSet<FGuid>& InGUIDs) const override;
#endif
virtual ELightMapInteractionType GetStaticLightingType() const override { return LMIT_Texture; }
virtual void GetStreamingRenderAssetInfo(FStreamingTextureLevelContext& LevelContext, TArray<FStreamingRenderAssetPrimitiveInfo>& OutStreamingRenderAssets) const override;
virtual void GetUsedMaterials(TArray<UMaterialInterface*>& OutMaterials, bool bGetDebugMaterials = false) const override;
virtual class UBodySetup* GetBodySetup() override { return ModelBodySetup; };
virtual int32 GetNumMaterials() const override;
virtual UMaterialInterface* GetMaterial(int32 MaterialIndex) const override;
virtual UMaterialInterface* GetMaterialFromCollisionFaceIndex(int32 FaceIndex, int32& SectionIndex) const override;
virtual bool IsPrecomputedLightingValid() const override;
//~ End UPrimitiveComponent Interface.
//~ Begin UActorComponent Interface.
virtual void InvalidateLightingCacheDetailed(bool bInvalidateBuildEnqueuedLighting, bool bTranslationOnly) override;
virtual void PropagateLightingScenarioChange() override;
//~ End UActorComponent Interface.
//~ Begin UObject Interface.
virtual void Serialize(FArchive& Ar) override;
virtual void PostLoad() override;
virtual bool IsNameStableForNetworking() const override;
#if WITH_EDITOR
virtual void PostEditUndo() override;
#endif // WITH_EDITOR
static void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector);
//~ End UObject Interface.
//~ Begin Interface_CollisionDataProvider Interface
virtual bool GetPhysicsTriMeshData(struct FTriMeshCollisionData* CollisionData, bool InUseAllTriData) override;
virtual bool ContainsPhysicsTriMeshData(bool InUseAllTriData) const override;
virtual bool WantsNegXTriMesh() override { return false; }
//~ End Interface_CollisionDataProvider Interface
//#if WITH_EDITOR
/**
* Generate the Elements array.
*
* @param bBuildRenderData If true, build render data after generating the elements.
*
* @return bool true if successful, false if not.
*/
virtual bool GenerateElements(bool bBuildRenderData);
//#endif // WITH_EDITOR
};
VoxelProceduralMeshComponent.cpp:
#include "VoxelProceduralMeshComponent.h"
class AVoxelWorld;
void UVoxelProceduralMeshComponent::SetVoxelCollisionsFrozen(const AVoxelWorld* VoxelWorld, bool bFrozen) {
}
bool UVoxelProceduralMeshComponent::AreVoxelCollisionsFrozen(const AVoxelWorld* VoxelWorld) {
return false;
}
UVoxelProceduralMeshComponent::UVoxelProceduralMeshComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
this->BodySetup = NULL;
this->BodySetupBeingCooked = NULL;
this->StaticMeshComponent = NULL;
}
void UVoxelProceduralMeshComponent::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
{
/*UVoxelProceduralMeshComponent* This = CastChecked<UVoxelProceduralMeshComponent>(InThis);
Collector.AddReferencedObject( This->StaticMeshComponent, This );
AddReferencedObjects( This, Collector );*/
}
void UVoxelProceduralMeshComponent::Serialize(FArchive& Ar)
{
/*Serialize(Ar);
Ar << StaticMeshComponent;*/
}
void UVoxelProceduralMeshComponent::PostLoad()
{
/*PostLoad();
// Fix for old StaticMeshComponent components which weren't created with transactional flag.
SetFlags( RF_Transactional );
// BuildRenderData relies on the StaticMeshComponent having been post-loaded, so we ensure this by calling ConditionalPostLoad.
check(StaticMeshComponent);
StaticMeshComponent->ConditionalPostLoad();*/
}
bool UVoxelProceduralMeshComponent::IsNameStableForNetworking() const
{
// UVoxelProceduralMeshComponent is always persistent for the duration of a game session, and so can be considered to have a stable name
return true;
}
void UVoxelProceduralMeshComponent::GetUsedMaterials(TArray<UMaterialInterface*>& OutMaterials, bool bGetDebugMaterials) const
{
}
int32 UVoxelProceduralMeshComponent::GetNumMaterials() const
{
return 0;
}
UMaterialInterface* UVoxelProceduralMeshComponent::GetMaterial(int32 MaterialIndex) const
{
UMaterialInterface* Material = nullptr;
return Material;
}
UMaterialInterface* UVoxelProceduralMeshComponent::GetMaterialFromCollisionFaceIndex(int32 FaceIndex, int32& SectionIndex) const
{
UMaterialInterface* Result = nullptr;
SectionIndex = 0;
return Result;
}
bool UVoxelProceduralMeshComponent::IsPrecomputedLightingValid() const
{
return false;
}
void UVoxelProceduralMeshComponent::GetStreamingRenderAssetInfo(FStreamingTextureLevelContext& LevelContext, TArray<FStreamingRenderAssetPrimitiveInfo>& OutStreamingRenderAssets) const
{
}
void UVoxelProceduralMeshComponent::CreateRenderState_Concurrent(FRegisterComponentContext* Context)
{
}
void UVoxelProceduralMeshComponent::DestroyRenderState_Concurrent()
{
}
FPrimitiveSceneProxy* UVoxelProceduralMeshComponent::CreateSceneProxy()
{
return NULL;
}
bool UVoxelProceduralMeshComponent::ShouldRecreateProxyOnUpdateTransform() const
{
return true;
}
FBoxSphereBounds UVoxelProceduralMeshComponent::CalcBounds(const FTransform& LocalToWorld) const
{
return FBoxSphereBounds(LocalToWorld.GetLocation(), FVector::ZeroVector, 0.f);
}
void UVoxelProceduralMeshComponent::InvalidateLightingCacheDetailed(bool bInvalidateBuildEnqueuedLighting, bool bTranslationOnly)
{
}
void UVoxelProceduralMeshComponent::PropagateLightingScenarioChange()
{
}
bool UVoxelProceduralMeshComponent::GetLightMapResolution( int32& Width, int32& Height ) const
{
return false;
}
int32 UVoxelProceduralMeshComponent::GetStaticLightMapResolution() const
{
/*int32 Width;
int32 Height;
GetLightMapResolution(Width, Height);
return FMath::Max<int32>(Width, Height);*/
return NULL;
}
void UVoxelProceduralMeshComponent::GetLightAndShadowMapMemoryUsage( int32& LightMapMemoryUsage, int32& ShadowMapMemoryUsage ) const
{
/*return;*/
}
#if WITH_EDITOR
void UVoxelProceduralMeshComponent::GetStaticLightingInfo(FStaticLightingPrimitiveInfo& OutPrimitiveInfo,const TArray<ULightComponent*>& InRelevantLights,const FLightingBuildOptions& Options)
{
/*check(0);*/
}
void UVoxelProceduralMeshComponent::AddMapBuildDataGUIDs(TSet<FGuid>& InGUIDs) const
{
}
void UVoxelProceduralMeshComponent::PostEditUndo()
{
/*PostEditUndo();*/
}
#endif // WITH_EDITOR
bool UVoxelProceduralMeshComponent::GetPhysicsTriMeshData(struct FTriMeshCollisionData* CollisionData, bool InUseAllTriData)
{
return false;
}
bool UVoxelProceduralMeshComponent::ContainsPhysicsTriMeshData(bool InUseAllTriData) const
{
return false;
}
bool UVoxelProceduralMeshComponent::GenerateElements(bool bBuildRenderData)
{
return false;
}
Set supported platforms to windows
Creating a C++ mod
This guide will help you create a C++ mod using UE4SS.
It's split up into four parts.
Part one goes over the prerequisites.
Part two goes over creating the most basic C++ mod possible.
Part three will show you how to interact with UE4SS and UE itself (via UE4SS).
Part four will cover installation of the mod.
The guide requires having a working C++ development environment with
xmake
andgit
, preferably similar to the one required to build UE4SS itself from sources.
Part 1
Make sure you have downloaded all the build requirements mentioned in the README before following these steps!
- Make an Epic account and link it to your GitHub account
- Check your email and accept the invitation to the @EpicGames GitHub organization for Unreal source access.
- Setup SSH keys on your GitHub account which will let git access the Unreal source you got access for in 2 and 3.
- Make a directory somewhere on your computer, the name doesn't matter but I named mine
MyMods
. - Clone the RE-UE4SS repo so that you end up with
MyMods/RE-UE4SS
. - Open CMD and cd into
RE-UE4SS
and execute:git submodule update --init --recursive
- Go back to the
MyMods
directory and create a new directory, this directory will contain your mod source files. I named mineMyAwesomeMod
. - Create a file called
xmake.lua
insideMyMods
and put this inside it:
includes("RE-UE4SS")
includes("MyAwesomeMod")
Part #2
- Create a file called
xmake.lua
insideMyMods/MyAwesomeMod
and put this inside it:
local projectName = "MyAwesomeMod"
target(projectName)
add_rules("ue4ss.mod")
add_includedirs(".")
add_files("dllmain.cpp")
- Make a file called
dllmain.cpp
inMyMods/MyAwesomeMod
and put this inside it:
#include <stdio.h>
#include <Mod/CppUserModBase.hpp>
class MyAwesomeMod : public RC::CppUserModBase
{
public:
MyAwesomeMod() : CppUserModBase()
{
ModName = STR("MyAwesomeMod");
ModVersion = STR("1.0");
ModDescription = STR("This is my awesome mod");
ModAuthors = STR("UE4SS Team");
// Do not change this unless you want to target a UE4SS version
// other than the one you're currently building with somehow.
//ModIntendedSDKVersion = STR("2.6");
printf("MyAwesomeMod says hello\n");
}
~MyAwesomeMod() override
{
}
auto on_update() -> void override
{
}
};
#define MY_AWESOME_MOD_API __declspec(dllexport)
extern "C"
{
MY_AWESOME_MOD_API RC::CppUserModBase* start_mod()
{
return new MyAwesomeMod();
}
MY_AWESOME_MOD_API void uninstall_mod(RC::CppUserModBase* mod)
{
delete mod;
}
}
- In the command prompt, in the
MyMods
directory, execute either: A.
xmake f -m "Game__Shipping__Win64"
xmake
or B.
xmake project -k vsxmake2022
If you chose option B
, the VS solution will be in the vsxmake2022
directory.
- Open
MyMods/vsxmake2022/MyMods.sln
- Make sure that you're set to the
Game___Shipping__Win64
configuration unless you want to debug. - Find your project (in my case: MyAwesomeMod) in the solution explorer and right click it and hit
Build
.
Part #3
In this part, we're going to learn how to log to file, and both consoles, as well as find a UObject by name, and log that name.
- Add
#include <DynamicOutput/DynamicOutput.hpp>
under#include <Mod/CppUserModBase.hpp>
.
You can now also remove#include <stdio.h>
because we'll be removing the use ofprintf
which was the only thing that required it. - To save some time and annoyance and make the code look a bit better, add this line below all the includes:
using namespace RC;
- Replace the call to printf in the body of the
MyAwesomeMod
constructor with:
Output::send<LogLevel::Verbose>(STR("MyAwesomeMod says hello\n"));
It's longer than a call to printf
, but in return the message gets propagated to the log file and both the regular console and the GUI console.
We also get some support for colors via the LogLevel
enum.
- Add this below the DynamicOutput include:
#include <Unreal/UObjectGlobals.hpp>
#include <Unreal/UObject.hpp>
- Let's again utilize the
using namespace
shortcut by adding this below the first one:using namespace RC::Unreal;
- Add this function in your mod class:
auto on_unreal_init() -> void override
{
// You are allowed to use the 'Unreal' namespace in this function and anywhere else after this function has fired.
auto Object = UObjectGlobals::StaticFindObject<UObject*>(nullptr, nullptr, STR("/Script/CoreUObject.Object"));
Output::send<LogLevel::Verbose>(STR("Object Name: {}\n"), Object->GetFullName());
}
Note that Output::send
doesn't require a LogLevel
and that we're using {}
in the format string instead of %s
.
The Output::send
function uses std::format
in the back-end so you should do some research around std::format or libfmt if you want to know more about it.
- Right click your project and hit
Build
.
Part #4
Click to go to guide for installing a C++ Mod
Installing a C++ Mod
-
This part assumes you have UE4SS installed and working for your game already. If not, refer to the installation guide.
-
After building, you will have the following file:
MyAwesomeMod.dll
inMyMods\Binaries\<Configuration>\MyAwesomeMod
-
Navigate over to your game's executable folder and open the
Mods
folder. Here we'll do a couple things:- Create a folder structure in
Mods
that looks likeMyAwesomeMod\dlls
. - Move
MyAwesomeMod.dll
inside thedlls
folder and rename it tomain.dll
.
- Create a folder structure in
The result should look like:
Mods\
MyAwesomeMod\
dlls\
main.dll
- To enable loading of your mod in-game you will have to edit the
mods.txt
located in theMods
folder. By default it looks something like this:
CheatManagerEnablerMod : 1
ActorDumperMod : 0
ConsoleCommandsMod : 1
ConsoleEnablerMod : 1
SplitScreenMod : 0
LineTraceMod : 1
BPModLoaderMod : 1
jsbLuaProfilerMod : 0
; Built-in keybinds, do not move up!
Keybinds : 1
Here you will want to add the line:
MyAwesomeMod : 1
above the keybinds to enable MyAwesomeMod
.
Alternatively, place an empty text file named enabled.txt
inside of the MyAwesomeMod folder. This method is not recommended because it does not allow load ordering
and bypasses mods.txt, but may allow for easier installation by end users.
- Launch your game and if everything was done correctly, you should see the text "MyAwesomeMod says hello" highlighted in blue somewhere at the top of UE4SS console (before all the scanning occurs), and if you used the
on_unreal_init
function, you should see "Object Name: /Script/CoreUObject.Object" highlighted in blue as well (right after the scanning finishes).
Automation
Now that you understand how the process works, you can use the UE4SS CPP Template repository that automates the process of creating a mod, building it, and installing it. Be aware that the new_mod_setup.bat
script will checkout the commit at the latest release so that you can be sure that your mod is being built with the correct ABI as latest release.
NOTE: Any changes to the build system that affects the mod template is pushed to the
dev
branch, which is then merged into main when a new UE4SS release is created. This makes sure that the template is always in-sync with the latest UE4SS release.
Creating GUI tabs with a C++ mod
UE4SS already includes the ImGui library to render its console GUI, built from the UE4SS-RE/imgui repo. Refer to ImGui documentation in that repo on how to use ImGui-specific classes and methods for rendering actual buttons and textboxes and other window objects.
This guide will show how you create custom tabs for the GUI with a C++ mod, and the guide will take the form of comments in the code example below:
#include <Mod/CppUserModBase.hpp>
#include <UE4SSProgram.hpp>
class MyAwesomeMod : public RC::CppUserModBase
{
private:
int m_private_number{33};
std::shared_ptr<GUI::GUITab> m_less_safe_tab{};
public:
MyAwesomeMod() : CppUserModBase()
{
ModName = STR("MyAwesomeMod");
ModVersion = STR("1.0");
ModDescription = STR("This is my awesome mod");
ModAuthors = STR("UE4SS Team");
// The 'register_tab' function will tell UE4SS to render a tab.
// Tabs registered this way will be automatically cleaned up when this C++ mod is destructed.
// The first param is the display name of your tab.
// The second param is a callback that UE4SS will use to render the contents of the tab.
// The param to the callback is a pointer to your mod.
register_tab(STR("My Test Tab"), [](CppUserModBase* instance) {
// In this callback, you can start rendering the contents of your tab with ImGui.
ImGui::Text("This is the contents of the tab");
// You can access members of your mod class with the 'instance' param.
auto mod = dynamic_cast<MyAwesomeMod*>(instance);
if (!mod)
{
// Something went wrong that caused the 'instance' to not be correctly set.
// Let's abort the rest of the function so that you don't access an invalid pointer.
return;
}
// You can access both public and private members.
mod->render_some_stuff(mod->m_private_number);
});
// The 'UE4SSProgram::add_gui_tab' function is another way to tell UE4SS to render a tab.
// This way of registering a tab will make you responsible for cleaning up the tab when your mod destructs.
// Failure to clean up the tab on mod destruction will result in a crash.
// It's recommended that you use 'register_tab' instead of this function.
m_less_safe_tab = std::make_shared<GUI::GUITab>(STR("My Less Safe Tab"), [](CppUserModBase* instance) {
// This callback is identical to the one used with 'register_tab' except 'instance' is always nullptr.
ImGui::Text("This is the contents of the less safe tab");
});
UE4SSProgram::get_program().add_gui_tab(m_less_safe_tab);
}
~MyAwesomeMod() override
{
// Because you created a tab with 'UE4SSProgram::add_gui_tab', you must manually remove it.
// Failure to remove the tab will result in a crash.
UE4SSProgram::get_program().remove_gui_tab(m_less_safe_tab);
}
auto on_ui_init() -> void override
{
// It's critical that you enable ImGui if you intend to use ImGui within the context of UE4SS.
// If you don't do this, a crash will occur as soon as ImGui tries to render anything, for example in your tab.
UE4SS_ENABLE_IMGUI()
}
auto render_some_stuff(int Number) -> void
{
auto calculated_value = Number + 1;
ImGui::Text(std::format("calculated_value: {}", calculated_value).c_str());
}
};
#define MY_AWESOME_MOD_API __declspec(dllexport)
extern "C"
{
MY_AWESOME_MOD_API RC::CppUserModBase* start_mod()
{
return new MyAwesomeMod();
}
MY_AWESOME_MOD_API void uninstall_mod(RC::CppUserModBase* mod)
{
delete mod;
}
}
Creating a Lua mod
Before you start
To create a Lua mod in UE4SS, you should first:
- know how to install UE4SS in your target game and make sure it is running OK;
- be able to write basic Lua code (see the official book Programming in Lua and its later editions, or any other recommended tutorial online);
- have an understanding of the object model of the Unreal Engine and the basics of game modding.
How does a minimal Lua mod look like
A Lua mod in UE4SS is a set of Lua scripts placed in a folder inside the Mods/
folder of UE4SS installation.
Let's call it MyLuaMod
for the purpose of this example.
In order to be loaded and executed:
- The mod folder must have a
scripts
subfolder and amain.lua
file inside, so it looks like:
Mods\
...
MyLuaMod\
scripts\
main.lua
...
- The
Mods\MyLuaMod\scripts\main.lua
file has some Lua code inside it, e.g.:
print("[MyLuaMod] Mod loaded\n")
- The mod must be added and enabled in
Mods\mods.txt
with a new line containing the name of your mod folder (name of your mod) and1
for enabling or0
for disabling the mod:
...
MyLuaMod : 1
...
Your custom functionality goes inside main.lua
, from which you can include other Lua files if needed, including creating your own Lua modules or importing various libraries.
What can you do in a Lua mod
The API provided by UE4SS and available to you in Lua is documented in sub-sections of chapter "Lua API" here. Using those functions and classes, you find and manipulate the instances of Unreal Engine objects in memory, creating new objects or modifying existing ones, calling their methods and accessing their fields.
Basically, you are doing the exact same thing that an Unreal Engine game developer does in their code, but using UE4SS to locate the necessary objects and guessing a bit, while the developers already knew where and what they are (because they have their source code).
Creating simple data types
If you need to create an object of a structure-like class, e.g. FVector
, in order to pass it into a Unreal Engine function, UE4SS allows you to pass a Lua table with the fields of the class like {X=1.0, Y=2.0, Z=3.0}
instead.
Using Lua C libraries
If you ever need to load Lua C libraries, that have native code (i.e. with DLLs on Windows),
you can place these DLLs directly inside the same \scripts\
folder.
Setting up a Lua mod development environment
It is much easier to write mods if your code editor or IDE is properly configured for Lua development and knows about UE4SS API.
-
Configure your code editor/IDE to support Lua syntax highlighting and code completion. If you use VSCode, see here in Using Custom Lua Bindings.
-
Make sure that your build of UE4SS contains
Mods\shared\Types.lua
(a development build from Github releases contains it). This will load the UE4SS API definitions in your IDE. -
(Optional) Dump the Lua Bindings fromm UE4SS Gui console, and follow the recommendations to load them here.
Then open the Mods/
folder of your UE4SS installation in your IDE, and create or modify your mod inside it.
Applying code changes
The main benefit of developing Lua mods is that you can quickly edit Lua sources without recompiling/rebuilding the C++ mod library as is always the case with C++ mods, and retry without restarting the game.
You can either:
- reload all mods from the UE4SS GUI Console with the "Restart All Mods" button on the "Console" tab, or,
- enable "Hot reload" in
UE4SS-settings.ini
and use the assigned hotkey (Ctrl+R
by default) to do the same.
Your first mod
In the main.lua
file of your mod, write some code that will try to access the objects of Unreal Engine inside your target game and do something that you can observe in the UE4SS console.
You can start by trying just
print("[MyLuaMod] Mod loaded\n")
and once you have verified that it runs OK, you can start implementing some actual functionality.
The example code below is fairly generic and should work for many games supported by UE4SS.
It registers a hotkey Ctrl+F1
and when pressed, it reads the player coordinates
and calculates how far the player has moved since the last time the hotkey was pressed.
Note that the logging
The player coordinates are retrieved in the following way:
- Gets the player controller using UE4SS
UEHelpers
class. - Get the
Pawn
, which represents the actual "physical" entity that the player can control in Unreal Engine. - Call the appropriate Unreal Engine method
K2_GetActorLocation
that returns aPawn
's location (by accessing its parentActor
class). - The location is a 3-component vector of Unreal Engine type
FVector
, havingX
,Y
andZ
as its fields.
local UEHelpers = require("UEHelpers")
print("[MyLuaMod] Mod loaded\n")
local lastLocation = nil
function ReadPlayerLocation()
local FirstPlayerController = UEHelpers:GetPlayerController()
local Pawn = FirstPlayerController.Pawn
local Location = Pawn:K2_GetActorLocation()
print(string.format("[MyLuaMod] Player location: {X=%.3f, Y=%.3f, Z=%.3f}\n", Location.X, Location.Y, Location.Z))
if lastLocation then
print(string.format("[MyLuaMod] Player moved: {delta_X=%.3f, delta_Y=%.3f, delta_Z=%.3f}\n",
Location.X - lastLocation.X,
Location.Y - lastLocation.Y,
Location.Z - lastLocation.Z)
)
end
lastLocation = Location
end
RegisterKeyBind(Key.F1, { ModifierKey.CONTROL }, function()
print("[MyLuaMod] Key pressed\n")
ExecuteInGameThread(function()
ReadPlayerLocation()
end)
end)
When you load the game until you can move the character, press the hotkey, move the player, press it again, the mod will generate a following output or something very similar:
...
[2024-01-09 19:37:27] Starting Lua mod 'MyLuaMod'
[2024-01-09 19:37:27] [Lua] [MyLuaMod] Mod loaded
...
[2024-01-09 19:37:32] [Lua] [MyLuaMod] Key pressed
[2024-01-09 19:37:32] [Lua] [MyLuaMod] Player location: {X=-63.133, Y=4.372, Z=90.000}
[2024-01-09 19:37:39] [Lua] [MyLuaMod] Key pressed
[2024-01-09 19:37:39] [Lua] [MyLuaMod] Player location: {X=788.232, Y=-639.627, Z=90.000}
[2024-01-09 19:37:39] [Lua] [MyLuaMod] Player moved: {delta_X=851.364, delta_Y=-643.999, delta_Z=0.000}
...
Using Custom Lua Bindings
To make development of Lua mods easier, we've added the ability to dump custom Lua bindings from your game. We also have a shared types file that contains default UE types and the API functions/classes/objects that are available to you.
Dumping Custom Lua Bindings
Simply open the Dumpers tab in the GUI console window and hit the "Dump Lua Bindings" button.
The generator will place the files into the Mods/shared/types
folder.
Warning: Do not include any of the generated files in your Lua scripts. If they are included, any globals set by UE4SS will be overridden and things will break.
To Use Bindings
I recommend using Visual Studio Code to do your Lua development. You can install the extension just called "Lua" by sumneko.
Open the Mods
folder as a workspace. You can also save this workspace so you don't have to do this every time you open VS Code.
When developing your Lua mods, the language server should automatically parse all the types files and give you intellisense.
Warning: For many games the number of types is so large that the language server will fail to parse everything. In this case, you can add a file called
.luarc.json
into the root of your workspace and add the following:
{
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
"workspace.maxPreload": 50000,
"workspace.preloadFileSize": 5000
}
How to use your mod's directory as workspace
As alternative you can open just your mod's root directory as workspace.
In this case you need to add a .luarc.json
with "workspace.library"
entries containing a path to the "shared" folder and the "Scripts" directory of your mod.
Both paths can be relative.
Example .luarc.json:
{
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
"workspace.maxPreload": 50000,
"workspace.preloadFileSize": 5000,
"workspace.library": ["../shared", "Scripts"]
}
Annotating
To get context sensitive information about the custom game types, you need to annotate your code (alternative documentation). This is done by adding a comment above the function/class/object that you want to annotate.
Example
---@class ITM_MisSel_Biome_C
local biome = FindFirstOf("ITM_MisSel_Biome_C")
---@type int
local numMissions = biome.NumMissions
---@type FVector
local soundCoords = { 420.5, 69.0, 3.1 }
biome:SetSoundCoordinate(soundCoords)
Custom Game Configs
important
Some of these files may be out of date as the games/UE4SS updates. If you find that a game's custom game config is out of date, please open an issue on the UE4SS-RE/RE-UE4SS repository. Make sure that you first test if the game works without the custom game config, as it may have been fixed in the latest version of UE4SS.
These settings are for games that have altered the engine in ways that make UE4SS not work out of the box.
You need to download the files from each folder for your game and place them in the same folder in your UE4SS installation. For example, downloading the configs for Kingdom Hearts 3 should result in your files being in the following structure:
Binaries/Win64/
├── CustomGameConfigs/
│ └── Kingdom Hearts 3/
│ ├── UE4SS_Signatures/
│ │ ├── FName_Constructor.lua
│ │ ├── FName_ToString.lua
│ │ ├── StaticConstructObject.lua
│ ├── MemberVariableLayout.ini
│ ├── UE4SS-settings.ini
│ ├── VTableLayout.ini
... but obviously the file structure will change depending on the game's configs.
If you download the zDEV version, all these files are already included in the zip file.
Devlogs
This section will contain a list of development logs that have been written by contributors of UE4SS. These logs are intended to be a way for contributors to share their experiences and knowledge with the community, and to provide a way for the community to understand the development process of UE4SS.
DataTables in UE4SS - bitonality (2024-02-07)
DataTables in UE4SS
Background
DataTables are a data structure in Unreal Engine that allows for hashed key-value pairs to be loaded at runtime. Common use cases include storing loot tables, experience point requirements for leveling up, base health/armor for actors, etc...
DataTables are intended to be populating as part of game compilation and aren't technically supposed to be modified at runtime. The documentation from Unreal sometimes contradicts this statement, so it's a bit hard to parse what's intended versus what's possible. My goal is to allow for full read/write/update/delete/iterate operations at runtime from a C++ context without the use of blueprints.
Why not just create a blueprint mod that replaces a DataTable?
This technically works. The problem is that your mod is the only mod that can change this DataTable. This is obviously not ideal for clients that want to use multiple mods that want to modify the same DataTable. I rate this solution around a 2/10 from a extensibility perspective.
What is the structure of a DataTable?
DataTables are build by using TMap
and TSet
from native Unreal. If you are familiar with Java's HashMap
or C#'s Dictionary
then you'll understand the gist of the contracts/usage. Unreal DataTable
has keys of FName
and the value is a struct that inherits from FTableRowBase
. More on this later...
So what needs to be done?
I will outline a couple of possibilities for the modification of DataTables. I will be evaluating the feasibility/stability of each proposed solution to give some perspective.
Solution 1 (TMap implementation)
A DataTable in Unreal Engine exposes a RowMap property that can be accessed:
// DataTable.h
virtual const TMap< FName, uint8 * > & GetRowMap() const
virtual const TMap< FName, uint8 * > & GetRowMap()
The GetRowMap() function is reflected and is easily callable by using the UVTD files. The problem is that UE4SS has a bare-bones implementation of TMap. The current TMap implementation in UE4SS can be leveraged in the following manner:
// DataTable row format is <FName, CoolStruct>
struct CoolStruct : FTableRowBase
{
FString SomeString;
int_32 SomeNumber;
bool SomeBoolean;
}
TMap<FName, unsigned char*> rowMap = dataTable->GetRowMap();
auto ptrElem = rowMap.GetElementsPtr();
for(int32_t i = 0; i < rowMap.Num(); i++)
{
auto pair = &ptrElem[i];
pair->Key;
pair->Value;
CoolStruct* row = reinterpret_cast<CoolStruct*>(pair->Value);
}
So what's the big deal?
UE4SS's TMap does not like when the underlying data is changed. This way of accessing data works reasonably well for DataTable reads/iterators, but after we call dt->AddRow()
or dt->RemoveRow()
, the underlying .GetElementsPtr()
is inaccurate. If you look at the UE4SS implementation of TMap, you can see that it's fairly fragile unless you intend to read only.
Note that the current .Num()
function in UE4SS TMap does not actually perform calculations on the TMap. The Num
property is just set when we construct a TMap in UE4SS, so we don't get updates when the underlying size changes.
I suppose this solution is reasonable for reading a DataTable if that's all you want to do.
So how can we make this work?
Theoretically we can implement TMap in UE4SS with mirrored functionality to UE native. UE4SS has done a similar approach with TArray
. The potential downsides are that if TMap underlying logic/structures have changed between UE versions, then we would need multiple implementations that represent the state of UE TMaps at different versions. Either that, or, we could have #if UE5_1
etc. to keep things consolidated in a single TMap.hpp/cpp file.
Will implementing TMap in UE4SS work for modifying DataTables? I haven't completed a thorough investigation, but my gut says... probably?
Why can't we use FindRow/GetRow on the DataTable object?
The only useful reflected functions we get from UDataTable
dump is GetRowMap()
, RemoveRow()
, and AddRow()
. Not too shabby, but unfortunate that we can't get a row directly or use a UE4SS TMap
to get a row.
Solution 2 (Kismet DataTable Helper Library)
This approach leverages a blueprint DataTable helper class built into Unreal Engine. The reflected functions from this blueprint helper are:
static bool DoesDataTableRowExist
(
UDataTable * Table,
FName RowName
)
static void GetDataTableRowNames
(
UDataTable * Table,
TArray< FName > & OutRowNames
)
static bool GetDataTableRowFromName
(
UDataTable * Table,
FName RowName,
FTableRowBase & OutRow
)
If you've been paying attention, then a light bulb might be going off in your head. Seems like we could accomplish full DataTable support by utilizing
// DataTable reflected functions
AddRow();
RemoveRow();
Empty();
// DataTableFunctionLibrary reflected functions
DoesDataTableRowExist();
GetDataTableRowNames();
GetDataTableRowFromName();
But there's always a catch...
GetDataTableRowFromName();
is an especially cursed function. The TLDR is that it's probably usable, but will require some further experimentation.
This next section benefits from somewhat of an intimate knowledge of how Kismet/blueprints/FFrame and the blueprint scripting stack works. I'll include some pre-reads to familiarize yourself.
GetDataTableRowFromName()
has the specifiers CustomThunk
and CustomStructureParam
.
CustomThunk:
The UnrealHeaderTool code generator will not produce a thunk for this function; it is up to the user to provide one with the DECLARE_FUNCTION or DEFINE_FUNCTION macros.
CustomStructureParam:
The listed parameters are all treated as wildcards. This specifier requires the UFUNCTION-level specifier, CustomThunk, which will require the user to provide a custom exec function. In this function, the parameter types can be checked and the appropriate function calls can be made based on those parameter types. The base UFUNCTION should never be called, and should assert or log an error if it is.
Under the hood, the GetDataTableRowFromName()
UFunction is just a stub. The DataTableFunctionLibrary provides the actual behavior with a DEFINE_FUNCTION(execGetDataTableRowFromName)
macro. Let's take a look at what the defined function is:
// DataTableFunctionLibrary.h
/** Based on UDataTableFunctionLibrary::GetDataTableRow */
DECLARE_FUNCTION(execGetDataTableRowFromName)
{
P_GET_OBJECT(UDataTable, Table);
P_GET_PROPERTY(FNameProperty, RowName);
Stack.StepCompiledIn<FStructProperty>(NULL);
void* OutRowPtr = Stack.MostRecentPropertyAddress;
P_FINISH;
bool bSuccess = false;
// The following line fails to find the StructProp. See notes below this code block for the specifics.
FStructProperty* StructProp = CastField<FStructProperty>(Stack.MostRecentProperty);
if (!Table)
{
FBlueprintExceptionInfo ExceptionInfo(
EBlueprintExceptionType::AccessViolation,
NSLOCTEXT("GetDataTableRow", "MissingTableInput", "Failed to resolve the table input. Be sure the DataTable is valid.")
);
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
}
else if(StructProp && OutRowPtr)
{
UScriptStruct* OutputType = StructProp->Struct;
const UScriptStruct* TableType = Table->GetRowStruct();
const bool bCompatible = (OutputType == TableType) ||
(OutputType->IsChildOf(TableType) && FStructUtils::TheSameLayout(OutputType, TableType));
if (bCompatible)
{
P_NATIVE_BEGIN;
bSuccess = Generic_GetDataTableRowFromName(Table, RowName, OutRowPtr);
P_NATIVE_END;
}
else
{
FBlueprintExceptionInfo ExceptionInfo(
EBlueprintExceptionType::AccessViolation,
NSLOCTEXT("GetDataTableRow", "IncompatibleProperty", "Incompatible output parameter; the data table's type is not the same as the return type.")
);
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
}
}
else
{
FBlueprintExceptionInfo ExceptionInfo(
EBlueprintExceptionType::AccessViolation,
NSLOCTEXT("GetDataTableRow", "MissingOutputProperty", "Failed to resolve the output parameter for GetDataTableRow.")
);
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
}
*(bool*)RESULT_PARAM = bSuccess;
}
The issue is that the Stack.MostRecentProperty does not get populated when we call the GetDataTableRowFromName()
from a C++ context. This specifics of this have been documented at by the following GitHub issues:
Under the hood:
static bool GetDataTableRowFromName
(
UDataTable * Table,
FName RowName,
FTableRowBase & OutRow
)
// Does some property reading, type checking, etc,
// Then internally it calls
static bool Generic_GetDataTableRowFromName
(
const UDataTable * Table,
FName RowName,
void * OutRowPtr
)
It would be suitable for us to use a void*
for the OutRow
instead of a ref FTableRowBase
, but as fate would have it, this Generic_GetDataTableRowFromName()
is not accessible via reflection.
The core of the problem is that the execGetDataTableRowFromName()
is particularly aggressive at typechecking and ensuring that the function will work or gracefully exit. This is expected since this function is a blueprint node and needs to be a robust function to work within the blueprint framework. The specific way that Stack.MostRecentProperty
is used is to determine the target type of Struct that we expect to retrieve from the DataTable. In the blueprint caller context, this property would be populated as part of the Kismet FFrame/Stack pipeline.
Anything we can do?
I am currently playing with manually setting the Stack.MostRecentProperty
to trick the GetDataTableRowFromName()
into thinking that we're calling the function as part of a legal blueprint function and not directly from C++ code. Like solution 1, I rate this solution as a probably? in the functionality department.
One final wrench in the machine...
There's also further research needed about how DataTable row structs are stored in memory. It appears some games might have compiler packing, but the extent of this is still unknown. Furthermore, some games have reasonably laid out struct members for memory footprint/alignment/padding purposes, and other games have their struct members in a way that makes sense from a readability standpoint, but not from a memory optimization standpoint.
// NameTypes.hpp (UE4SS)
// TODO: Figure out what's going on here
// It shouldn't be required to use 'alignas' here to make sure it's aligned properly in containers (like TArray)
// I've never seen an FName not be 8-byte aligned in memory,
// but it is 4-byte aligned in the source so hopefully this doesn't cause any problems
// UPDATE: This matters in the UE VM, when ElementSize is 0xC in memory for case-preserving games, it must be aligned by 0x4 in that case
#pragma warning(disable: 4324) // Suppressing warning about struct alignment
#ifdef WITH_CASE_PRESERVING_NAME
struct alignas(4) RC_UE_API FName
#else
struct alignas(8) RC_UE_API FName // FNames in DataTable rows seem to only work with alignas(4)
The above code is a TODO: that's still in UE4SS. The investigation of alignment will likely have benefits across other non-DataTable parts! We'll need to understand the full extent of alignment/padding regardless of which solution we use (TMap or Blueprint Library or Other).
Disclaimer
While I feel that I have a good understanding of the factors at play, I have no doubt that I've missed some of the nuance and have misunderstood parts of the underlying systems. Please let me know if you think something operates differently than is currently documented. I would really appreciate the help!
Got any ideas?
Please reach out in the UE4SS Discord to brainstorm/share any ideas you might have. While I am currently in the role as feature lead for DataTables, I appreciate all the help I can get.
Other Resources
- DataTable Pull Request - I think you need Epic Games group access to view this?
- UE5 Wiki (CN)
- UE4SS Docs
- JIP Blog
Credits
Special thanks to localcc for being a wonderful mentor. Shout out to all early adopters of the DataTable branches (special thanks to El for being our first early adopter).
Thanks for your continued patience.
-- bitonality