-
Notifications
You must be signed in to change notification settings - Fork 29
Making mods with the SDK
In order to make mods with the SDK, knowledge of C++ is required. You will also need to have Visual Studio 2022 (any edition) with the C++ and game development workloads, and git, installed.
You can easily create a new ZHMModSDK mod by using the online mod generator. Simply visit this page, select your platform, enter the details of your mod, and press the "generate mod project" button. This will generate and download a zip file containing a git repository with your new mod project, which contains a CMake + vcpkg project for your mod, IDE configurations, and a github action to build the project.
Extract the zip file to a location of your choice.
After extracting it, you must initialize the submodules in the repository by opening a terminal in the project's root directory and running the following command:
git submodule update --init --recursive
You need to do this because the mod project depends on vcpkg, which is set up as a submodule.
Since this is a ready made git repository, you can now add your own remote and push to it if you want.
Now your mod project is basically ready to use. In order to start making your mod you can open the project in either Visual Studio or CLion. You can find instructions for each IDE below.
You should now have everything you need to start developing your mod. Every mod defines a ZHMModSDK "plugin". In your mod, you can find a very simple implementation in src/YourMod.h
and src/YourMod.cpp
. This plugin is the entry point of your mod. It is loaded by the game when the mod is loaded. You can use this plugin to build your mod's custom functionality.
Every plugin has several virtual functions that you can override. Some of the main ones are listed below:
-
Init()
: Called when the plugin is loaded (but possibly before the engine is fully initialized). You can use this function to install hooks and perform other initialization tasks. -
OnEngineInitialized()
: Called when the engine is fully initialized and can be interacted with. You can usually safely call engine functions (likeGameLookpManager::RegisterFrameUpdate
) here. -
OnDrawUI()
: You can use this function to draw custom UI elements using ImGui. -
OnDrawMenu()
: You can use this function to draw custom menu elements in the SDK's menu using ImGui. -
OnDraw3D()
: You can use this function to draw custom 3D elements using the providedIRenderer
instance.
You can find more information about these functions in IPluginInterface.h.
All plugins also have access to the global SDK()
function, which can be used to access various SDK features. For example, you can use SDK()->ScreenToWorld()
to convert a screen position to a world position. You can find more information about the SDK interface in IModSDK.h.
Engine functionality is exposed via Globals
, Hooks
, and Functions
, which are briefly explained below:
These are global engine objects that are exposed by the SDK. You can see all the ones that are available in Globals.h.
You can use globals by accessing them through the Globals
instance. For example, to access the ActorManager
you can use Globals::ActorManager
.
Hooks are used to intercept calls to engine functions. You can see all the ones that are available in Hooks.h.
Hooks are generally installed by overriding the Init()
function of your plugin. For example, to install a hook on ZEntitySceneContext_LoadScene
(which gets called when the game tries to load a new scene) you must do the following:
class MyMod : public IPluginInterface {
// ...
DECLARE_PLUGIN_DETOUR(MyMod, void, OnLoadScene, ZEntitySceneContext*, ZSceneData&);
};
void MyMod::Init() {
Hooks::ZEntitySceneContext_LoadScene->AddDetour(this, &MyMod::OnLoadScene);
}
DEFINE_PLUGIN_DETOUR(MyMod, void, OnLoadScene, ZEntitySceneContext* th, ZSceneData& p_SceneData) {
Logger::Trace("Loading scene: {}", p_SceneData.m_sceneName);
return HookResult<void>(HookAction::Continue());
}
From within this function you can control the flow of the original function. You can either call the original function, skip it, or replace it with your own implementation. You can also modify the arguments of the original function before calling it.
The DEFINE_PLUGIN_DETOUR
macro will automatically provide an additional p_Hook
parameter that can be used to call the original function. For example, to call the original function you can do the following:
p_Hook->CallOriginal(th, p_SceneData);
Using the HookResult
structure, you can control the flow of the original function. For example, to entirely skip calling the original function and return early, you can do the following:
return HookResult<void>(HookAction::Return());
If the hook returns a value, you can return a custom value by doing the following:
return HookResult(HookAction::Return(), 42);
Keep in mind that returning from a hook detour in this way will prevent further detours from other mods from being called.
Functions are used to call engine functions. You can see all the ones that are available in Functions.h.
Engine functions can generally be called at any time after OnEngineInitialized()
has been called. For example, to get the currently active camera entity you can do the following:
const auto s_CurrentCamera = Functions::GetCurrentCamera->Call();
NOTE This part of the wiki is in need of improvements. For now, you can check the various sample mods for reference on how to use the SDK.
To publish your mod, build and install the x64-Release-Install
configuration of your mod project. This will create an _install/x64-Release-Install/bin
directory which will contain your mod's DLL file. You can distribute this file to other people. Keep in mind that your mod will only work with the version of the SDK it was compiled with.
As mentioned above, your mod will only work with the version of the SDK it was compiled with. If you want to update your mod to use a newer version of the SDK, open your mods CMakeLists.txt
file and change the ZHMMODSDK_VER
variable to the latest version found in the releases page.