Skip to main content

Union

Union is an open-source framework for creating native C++ plugins for Gothic I and Gothic II. It provides direct access to the ZenGin engine internals — game objects in memory, engine functions, rendering pipeline, and more — allowing modifications that go far beyond what's possible with Daedalus scripting alone.

Union plugins are compiled as DLL files and loaded into the game process at runtime. They can hook virtually any engine function using the Detours library, intercept and modify game behavior, add new Daedalus external functions, and interact with the game world at a low level.

  • Build system: CMake 3.21+ with Ninja generator
  • Recommended IDE: Visual Studio 2022 (with CMake support)
  • C++ standard: C++20
  • License: BSD 3-Clause
  • Source code: GitLab

Use Cases

Union is used when Daedalus scripting is not sufficient — for example:

  • Modifying engine behavior (combat system, AI, rendering)
  • Adding new engine-level features (new particle effects, UI elements, input handling)
  • Fixing engine bugs
  • Exposing new Daedalus externals for script-side use
  • Optimizing game performance
  • Reading/writing custom configuration from .ini files

Architecture

The Union Framework consists of two main components, managed as Git submodules:

  • union-api — core framework providing hooking, memory operations, string utilities, and the plugin lifecycle
  • gothic-api — ZenGin engine headers with class definitions and memory addresses for all four game versions

Supported Game Versions

Union supports all four versions of the Gothic engine:

Preprocessor DefineNamespaceGame Version
__G1Gothic_I_ClassicGothic I
__G1AGothic_I_AddonGothic I Addon
__G2Gothic_II_ClassicGothic II
__G2AGothic_II_AddonGothic II: Night of the Raven

A single plugin project can target all four versions simultaneously. The build system compiles separate code paths for each version using preprocessor defines, and the correct path is activated at runtime based on the detected engine version.


How Plugins Work

  1. The plugin is compiled as a DLL (Dynamic Link Library)
  2. The DLL is placed in the game's System/Autorun/ directory
  3. When Gothic starts, the Union runtime loads all DLLs from Autorun/
  4. The plugin registers its game event functions — callbacks that the engine invokes at specific moments
  5. Hooks intercept engine functions at known memory addresses — each game version has different addresses for the same function

Game Event Functions

These are the main callbacks that a plugin can implement. They are not called by default — each one needs a corresponding hook to be enabled (the template provides them as commented-out code):

FunctionWhen it's called
Game_EntryPointGothic entry point (earliest possible moment)
Game_InitAfter DAT files are initialized, world is ready
Game_ExitWhen the game is shutting down
Game_PreLoopBefore each frame is rendered
Game_LoopEvery frame (main world render)
Game_PostLoopAfter each frame is rendered
Game_MenuLoopEvery frame while in menu
Game_SaveBegin / Game_SaveEndWhen a save starts / completes
Game_LoadBegin_* / Game_LoadEnd_*Various load scenarios (new game, save, change level)
Game_Pause / Game_UnpauseWhen the game pauses / unpauses
Game_DefineExternalsWhen Daedalus externals are registered
Game_ApplySettingsWhen game settings are applied

Multi-Game Compilation

The Plugin.cpp file uses conditional compilation to compile your code separately for each game version:

#ifdef __G1
#define GOTHIC_NAMESPACE Gothic_I_Classic
#define ENGINE Engine_G1
#include "Sources.hpp"
#endif

#ifdef __G2A
#define GOTHIC_NAMESPACE Gothic_II_Addon
#define ENGINE Engine_G2A
#include "Sources.hpp"
#endif

Your plugin logic is written inside the GOTHIC_NAMESPACE namespace in Plugin.hpp. The Sources.hpp file includes Plugin.hpp, and the same source is compiled once for each target game version.

Hooking Engine Functions

Union uses Microsoft Detours to hook engine functions. There are two types of hooks:

Full hooks (Union::CreateHook) — replace an entire engine function with your own implementation. You can call the original function before or after your code.

Hooks are declared as member functions of the engine class being hooked. The declaration goes in a .inl file inside the userapi/ folder (e.g. userapi/oCGame.inl):

// userapi/oCGame.inl
void UpdatePlayerStatus_Hook();

Then register and implement the hook in your plugin code:

auto Hook_oCGame_UpdatePlayerStatus = ::Union::CreateHook(
reinterpret_cast<void*>(zSwitch(0x00638F90, 0x0065F4E0, 0x00666640, 0x006C3140)),
&oCGame::UpdatePlayerStatus_Hook,
::Union::HookType::Hook_Detours
);

void oCGame::UpdatePlayerStatus_Hook()
{
// your code before original
(this->*Hook_oCGame_UpdatePlayerStatus)(); // call original
// your code after original
}

The (this->*Hook_Variable)(params) syntax calls the original engine function. The hook type Hook_Detours uses Microsoft Detours and should always be used — it ensures compatibility with other plugins, including those built with older Union versions.

Partial hooks (Union::CreatePartialHook) — inject code at a specific instruction address, giving access to CPU registers for low-level manipulation.

The zSwitch(G1, G1A, G2, G2A) macro provides different memory addresses for each engine version, using 0 for versions that should not be hooked.