Template repository for creating Union plugins based on the Union API and Gothic API projects. It's created to provide an easy way of starting a Union API project and includes quality of life features:
- CMake as the build system generator
- Union API and Gothic API are downloaded as a source code to build or as a prebuilt library
Configuration.cmake
lets you configure all aspects of the build without touching any CMakeLists.txt- Multiplatform development (single plugin, many engines)
- Union::Signature files generator and bundler
- Gothic UserAPI structure (adding new methods to Gothic classes)
- Build target to create VDFS package
- GitHub Actions
- QoL: Build targets to copy plugin into the Gothic directory
- QoL: PowerShell module to manage the project setup
- MSVC 14.30+ or 14.40+ (Visual Studio 2022)
- CMake 3.28+
- PowerShell 7+ (optional)
The template project downloads Union API and Gothic API from union-api.cmake that's non-official CMake Targets for Union API. The CMake repository fetches Union API and Gothic API directly from respective sources. It's convenient, because we don't need to include the CMake configuration for Union API here, and we can use prebuilt libraries instead of source compilation. The drawback is that the CMake repository may not have the newest Union API version, however I'll do my best to keep it updated, and you can't download arbitrary Union API versions not provided by union-api.cmake.
If you have concerns about using a non-official repo as the source for dependencies, or you'd like to change Union API code,
feel free to clone the union-api.cmake repository and change the URLs in Configuration.cmake
. You can also download it
locally and modify cmake/FindUnionAPI.cmake
to use add_subdirectory(path/to/local)
for SOURCE builds instead of
FetchContent
. It would look like this:
if(${UNION_API_FETCH_TYPE} STREQUAL "SOURCE")
# Comment out or delete FetchContent
#FetchContent_Declare(UnionAPI GIT_REPOSITORY ${UNION_API_URL} GIT_TAG ${UNION_API_COMMIT_REF})
#FetchContent_MakeAvailable(UnionAPI)
#FetchContent_GetProperties(UnionAPI SOURCE_DIR UnionAPI_SOURCES)
# Set UnionAPI_SOURCES variable to point to the union-api.cmake directory
set(UnionAPI_SOURCES ${CMAKE_SOURCE_DIR}/union-api.cmake)
# Add subdirectory with Union API
add_subdirectory(${UnionAPI_SOURCES})
# The rest of the code stays the same
set(UnionAPI_INCLUDE ${UnionAPI_SOURCES}/union-api/union-api)
set(UnionAPI_LIB_SHARED UnionAPI)
set(UnionAPI_LIB_STATIC UnionAPIStatic)
set(GothicAPI_INCLUDE ${UnionAPI_SOURCES}/gothic-api)
set(GothicAPI_LIB GothicAPI)
Click "Use this template" on GitHub (top-right, green) or download the repository as a ZIP file ("Code" button -> Download ZIP).
The first thing you would like to do is setting up CMake variables. Open Configuration.cmake
and set your project's name:
set(PROJECT_NAME "MyPlugin"
CACHE STRING "The name of the project" FORCE)
By default, Union API is linked as a shared DLL. If you prefer static linking, change this:
set(UNION_API_LINK_TYPE STATIC
CACHE STRING "The linking method of Union API. STATIC = static linking of .lib, SHARED = DLL" FORCE)
You can also change the Union API to fetch and build sources (default BINARY uses a prebuilt library). It doesn't change the version of Union API, though - it's still the same distribution.
set(UNION_API_FETCH_TYPE "SOURCE"
CACHE STRING "How to fetch Union API: SOURCE = download source and build locally, BINARY = download prebuilt artifacts" FORCE)
Every option is described, so you can have a look. The defaults are also good.
There are some options (like COPY_DLL_AUTORUN_DIRECTORY
) that would be different for different people so for them,
you can create Configuration.override.cmake
and put the config here. This file's settings take precedence over normal
configuration and this file is not commited to the repository.
To build the project use IDE or code editor with CMake integration (Visual Studio, CLion, VS Code) or use CMake directly
from the command line. The first thing is to choose one of the profiles from CMakePresets.json
and run CMake configuration.
- x86-debug - debug builds for development
- x86-release - release builds for publication
- x86-relwithdebinfo - release with debug symbols
- x86-minsizerel - most optimized release (longer build)
If you IDE doesn't support CMakeProfiles.json
set up the profile manually using:
- Generator "Ninja" (not "Ninja Multi-Config", just "Ninja")
- CMake variable
CMAKE_BUILD_TYPE
, one of: Debug, Release, RelWithDebInfo, MinSizeRel
Manually, we can configure the project using CLI CMake:
cmake --preset x86-debug
Then build and install:
$PRESET = "x86-debug"
cmake --build out/build/$PRESET --target all -j 16
cmake --install out/build/$PRESET --prefix out/install/$PRESET
Build artifacts will be available at out/install/{preset}/bin
.
The project sets up several targets for different use cases.
Builds the plugin and puts the binaries in out/build/{preset}
.
Creates a VDF package with the plugin.
Available if you set up COPY_BUILD_DLL_AUTORUN
option in the configuration. Builds the project and copies all
required files to the Autorun directory set in COPY_BUILD_DLL_AUTORUN_DIRECTORY
.
Use this for development to not copy the files manually a milion times.
Available if you set up COPY_BUILD_VDF_DATA
option in the configuration. Builds the project (VDF) and copies it
to the Data directory set in COPY_BUILD_VDF_DATA_DIRECTORY
.
Use this for development to not copy the files manually a milion times.
The project includes a PowerShell module to help with repository management. To run it, you need PowerShell 7,
and the default installation for Windows is 5.1, so if you haven't done it yet, download and install PowerShell 7.
To activate the module, run Nek.ps1
.
PS C:\dev\union-api-template> .\Nek.ps1
Module Name Usage About Arguments
------ ----------------------- ----- ----- ---------
Nek Clear-Build Nek\Clear-Build Clears CMake build directory {}
Nek Get-UnionApiVersion Nek\Get-UnionApiVersion Shows the available versions of Union API {}
Nek Set-UnionApiVersion Nek\Set-UnionApiVersion [version] Sets the Union API version in Configuration.cmake {[Required] Version: string}
To get help for some command, run Get-Help Nek\NameOfCommand
with the command name.
The best solution for CMake issues is to clear the output directory with its cache. You can do it either manually by
deleting out
directory or using Nek/Clear-Build
command.
CMake downloads the required files for Union API during configuration stage from the defined repository, and it gets only the configured version. By default, the version is pinned to specific release, so we don't break someone's build by a Union API update.
You can update the dependencies using the PowerShell module. First, get the list of available releases.
PS C:\dev\union-api-template> Nek\Get-UnionApiVersion
Union API fetch: BINARY
Union API version: 20240603.1019
Version Union API Ref Gothic API Ref Date
-------------------- ------------- -------------- ----
20240603.1019 406e3fb32232300bae2ff2ee0018685c15c7f1ef 102f42aaf6fe2f2c9c296f8ec66ee8fcde08d646 03.06.2024 08:21:46
20240602.0235 406e3fb32232300bae2ff2ee0018685c15c7f1ef 102f42aaf6fe2f2c9c296f8ec66ee8fcde08d646 02.06.2024 00:38:01
20240602.0024 406e3fb32232300bae2ff2ee0018685c15c7f1ef 102f42aaf6fe2f2c9c296f8ec66ee8fcde08d646 01.06.2024 22:26:30
20240601.1424 102f42aaf6fe2f2c9c296f8ec66ee8fcde08d646 unknonw 01.06.2024 12:26:12
To change the version: Nek\Set-UnionApiVersion [version]
To install the newest: Nek\Set-UnionApiVersion 20240603.1019
The releases are fetched from union-api.cmake repository using GitHub API.
To change the release use Nek\Set-UnionApiVersion
. It will update config keys in Configuration.cmake
PS C:\dev\union-api-template> Nek\Set-UnionApiVersion 20240603.1019
[Source] Union API commitRef: tags/20240602.0235
[Source] Union API version: 20240602.0235
[Binary] Union API version: 20240602.0235
Changed configuration UNION_API_COMMIT_REF = tags/20240602.0235
to value UNION_API_COMMIT_REF = tags/20240603.1019
Changed configuration UNION_API_VERSION = 20240602.0235
to value UNION_API_VERSION = 20240603.1019
[Source] Union API commitRef: tags/20240603.1019
[Source] Union API version: 20240603.1019
[Binary] Union API version: 20240603.1019
Run CMake configure again to apply the changes. You may need to Nek\Clear-Build first.
Important information is that UNION_API_COMMIT_REF
format depends on the input. If it starts with 202
or v
, we
add tags/
to the beginning, because it's a version tag. In other cases, we assume that the provided version is a valid
Git ref (branch, commit hash). If something is wrong, you can always fix it manually in Configuration.cmake
.
You can also set the config keys to always download latest version. I don't recommend that, because an update can break something in your build and should be tested first, but if you'd like to:
# Configuration.cmake
# ...
# For SOURCE builds
set(UNION_API_COMMIT_REF "main"
CACHE STRING "The Git branch or commit ref to download" FORCE)
# ...
# For BINARY builds
# Change URL because it's different for latest release
set(UNION_API_URL "https://github.com/piotrmacha/union-api.cmake/releases/{version}/download/{package}.zip"
CACHE STRING "The URL to download the Union API release. Use {version} and {package} replacements" FORCE)
set(UNION_API_VERSION "latest"
CACHE STRING "The version of Union API build from https://github.com/piotrmacha/union-api.cmake/releases/" FORCE)
# ...
The source code is located in src/
for the plugin sources and userapi/
for .inl files included by Gothic API.
Entrypoint Plugin.cpp
takes care of setting up Multiplatform build
and the Plugin.hpp
contains the actual code (or #include
directives). An important thing to know is that the
Plugin.hpp
is included 4 times, once for every game engine, and all code resides in the final binary, so you
have to take care of proper includes and global variables using Gothic classes.
Read the Multiplatform docs
to better understand what can go wrong and how to write safe code.
The Plugin.hpp
also uses GOTHIC_NAMESPACE
as the namespace name, which is a macro expanded to the currently
included namespace of a single engine.
Gothic_I_Classic
Gothic_I_Addon
Gothic_II_Classic
Gothic_II_Addon
There are also Plugin_[ENGINE].hpp
files, which are included only for specific engine.
You can structure the code however you'd like, but for a good separation of concerns I suggest following split:
src/
YourProjectName/
# files that don't depend on Gothic API and are in namespace "YourProjectName"
Gothic/
# .hpp files that depend on Gothic API
SomeModuleName/
# if you do many features in a plugin, split them in modules
Hooks.hpp
Externals.hpp
SomeModuleName.hpp
# Common files
Globals.hpp # Global variables (if needed)
Hooks.hpp # Hook definitions
Externals.hpp # Externals definitions
Options.hpp # Function to load .ini settings
Gothic.cpp
Gothic.hpp
Gothic_G1.hpp
Gothic_G1A.hpp
Gothic_G2.hpp
Gothic_G2A.hpp
Then your Gothic.hpp
would contain only #include
s to the files inside Gothic
directory.
// Globals and Options first, because they are used by others
#include <Gothic/Globals.hpp>
#include <Gothic/Options.hpp>
// Here any files with code used by Externals or Hooks
// ...
// Here the includes for module main file (which includes other module files)
#include <Gothic/SomeModuleName/SomeModuleName.hpp>
// Externals definitions and hooks go last, because they use the other code
#include <Gothic/Externals.hpp>
#include <Gothic/Hooks.hpp>
All headers in Gothic/
must not contain #pragma once
or any include guards, because we need to load them for
every engine version. That's way the includes are in one file, and you should not include other Gothic/
files
inside themselves (excl. Gothic.hpp
and module roots).
Including external files (like YourProjectName/
) is fine and there you should use #pragma once
or include guards.
For an example of this structure, you can have a look at zBassMusic.
Gothic UserAPI files are included in that order:
userapi/
<Union API>/ZenGin/Gothic_UserAPI/
The local directory takes precedence over the default directory so you only have to copy the files you would like to override. Full list of available files is here: https://gitlab.com/union-framework/gothic-api/-/tree/main/ZenGin/Gothic_UserAPI
If you would like to build the project only for a single engine or a limited set of engines, disable them using Configuration.cmake
.
set(GOTHIC_API_G1 ON
CACHE BOOL "Enable Gothic API for Gothic 1" FORCE)
set(GOTHIC_API_G1A ON
CACHE BOOL "Enable Gothic API for Gothic Sequel" FORCE)
set(GOTHIC_API_G2 ON
CACHE BOOL "Enable Gothic API for Gothic 2 Classic" FORCE)
set(GOTHIC_API_G2A ON
CACHE BOOL "Enable Gothic API for Gothic 2 Night of the Raven" FORCE)
The src/BuildInfo.h.in
is a template that's configured by CMake and put in the build directory. It contains
variables with the information about current build. You can #include <BuildInfo.h>
somewhere and use the
variables to provide build info at runtime.
The src/Union/HookUtils.h
has a macro and a function to retrieve an original address of some game method
by using information in files generated during CMake configuration. You can use it to set Hooks with the target
method reference instead of looking for an address.
auto CGameManager_Init_Ivk = Union::CreateHook(
ADDRESS_OF(&CGameManager::Init), // use ADDRESS_OF(what) macro to get the address of a Gothic function
&CGameManager::Init_Hooked)
Only Gothic classes are supported by this method. If you need to hook some other code, you have to use an address.
The signature files are generated from Gothic API Names.txt
using Convert-Gothic-Names.ps1
and placed in the build artifacts inside Singatures/*.tsv
. If you install the plugin in physical Autorun, you also
have to copy the Signatures
directory. VDF build already packs them inside the archive.
To link other libraries you have to edit CMakeLists.cmake
. The best place for it is right after the plugin definition.
add_library(${PLUGIN_LIBRARY} SHARED ${PLUGIN_SOURCES})
target_include_directories(${PLUGIN_LIBRARY} PRIVATE ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}/generated)
target_include_directories(${PLUGIN_LIBRARY} PRIVATE BEFORE ${CMAKE_SOURCE_DIR}/userapi)
set_target_properties(${PLUGIN_LIBRARY} PROPERTIES
OUTPUT_NAME ${OUTPUT_BINARY_NAME})
# Here you can link other libaries using, for example:
# Subdirectories:
# add_subdirectory(library_sub_dir)
# target_link_libraries(${PLUGIN_LIBRARY} PRIVATE SomeLib)
#
# FindPackage:
# find_package(SomeLib CONFIG REQUIRED)
# target_link_libraries(${PLUGIN_LIBRARY} PRIVATE SomeLib::SomeTarget)
#
# FetchContent:
# include(FetchContent)
# FetchContent_Declare(SomeLib GIT_REPOSITORY git@github.com:SomeAuthor/SomeRepo.git GIT_TAG main)
# FetchContent_MakeAvailable(SomeLib)
# target_link_libraries(${PLUGIN_LIBRARY} PRIVATE SomeLib::SomeTarget)
#
# VCPKG/Conan
# # Setup VCPKG or Conan separately, the template doesn't have any shortcuts
# target_link_libraries(${PLUGIN_LIBRARY} PRIVATE SomeLib::SomeTarget)
The template has workflows for building and releasing the plugin using GitHub Actions. You can find them in .github/workflows/
.
build.yaml
is a reusable workflow that builds the project on a single build type. It's not executed directly but called
by other workflows.
on-push.yaml
runs on push to "main" or "dev" branches and on Pull Requests to them. Workflows start 4 builds,
one for every build type, to verify that the plugin compiles.
release.yaml
runs on tags starting with v
(e.g. v0.0.1
). Release starts 3 builds (Release, RelWithDebInfo, MinSizeRel),
then it creates a GitHub Release and uploads the artifacts ready to be included in game. Changelog is generated automatically from
Pull Requests. It's highly recommended to use Semantic Versioning for versions. CMake requires the format with 3 or 4 numbers
separated by a dot (0.1.2 or 0.1.2.3) but you can freely use formats like "v0.1.2", "v0.1.2-rc3", "v0.1.2.3", "v0.1.2.3-rc4"
because the build job strips all characters that are not a digit or a dot. As said before, best if you just use
Semantic Versioning.
To trigger the release job, you have to create and push a tag. You can't create a tag on GitHub UI without creating a release, so we need to do it in the console.
# Make sure that you are on main and with current state
git checkout -b main
git pull origin main
# Create a tag with version
git tag v0.1.2 -m v0.1.2
# Push tags to origin
git push origin --tags
The workflow for release will trigger, and soon it will create a release on GitHub. Best strategy for versioning is using Semantic Versioning with following suggestions:
v0.y.z
- development stage, plugin not stablev1.y.z
- stable plugin was publishedy
- increment on every new feature (resetz
to 0)z
- increment on every bug fix
You would rather not bump the first segment beyond 1
because it means that the change is not backwards compatible,
and it's hard to define compatibility in a Union plugin. It makes sense for libraries but for plugins you can stay
with 1
or bump it only for very huge releases just for the flex. Up to you.
Btw. SemVer can go beyond single digit. If you have v0.1.9
, you don't have to create v0.2.0
. The correct bump
for a bugfix would be v0.1.10
and beyond. That may be obvious, but I have seen people who thought they are limited
to 0-9 (not looking at you Microsoft, good job with MSVC 14.40 / v143).
If your release job fail for some reason, you need to fix it, obviously. But then our tag still exists, so to reuse it, first we need to delete it from GitHub UI in "Tags" menu. Then, we need to delete a local tag and create it again.
# Make sure that you are on main and with current state
git checkout -b main
git pull origin main
# Remove local tag
git tag -d v0.1.2
# Create a tag with version
git tag v0.1.2 -m v0.1.2
# Push tags to origin
git push origin --tags
Instead of deleting the tag from GitHub UI, you can also git push origin --tags --force
but don't. If we don't have
to force-push, we don't force-push.
By default, each push starts 4 build jobs and every release starts 3 (+4 from the commit). If your repository is private, and you would like to not waste the CI minutes, you can remove some jobs from the workflows.
on-push.yaml
: just remove the job fromjobs:
keyrelease.yaml
: remove build job fromjobs:
, remove download action fromjobs.publish.steps:
, removeCompress-Archive
andCopy-Item
pair from the PowerShell script injobs.publish.steps.[id: prepare-release]
.
You can also set up a self-hosted runner. To do it, you would need to set up a Windows Server 2022 (or Windows 10/11) on some host with Internet access (public IP not required) and install Visual Studio 2022 with MSVC 14.39, CMake and the GitHub Runner agent on it. You can use other MSVC versions if you update them in the workflows YAMLs. This setup should work well with our workflows.
Never setup self-hosted runner on a public repository. Unless you can secure it for executing completely arbitrary code from very bad people(1) :)
(1) Tricky question. Instead of trying to secure such runner, the proper way is to spin an ephemeral VM for every action run with full network and storage isolation, and then destroy it after workflow execution. That's how GitHub is doing it, and this is the only reasonable way of running public runners.
The template doesn't have any prepared pipelines for other CI (excl. Gitea which is compatible with GitHub Actions, just rename .github to .gitea).
You can create such pipeline yourself based on the GitHub workflows, and if you do, please open a Pull Request :)
The template is licensed under MIT License.
union-api and gothic-api are licensed under GNU GENERAL PUBLIC LICENSE V3.
GothicVDFS 2.6 Copyright (c) 2001-2003, Nico Bendlin, Copyright (c) 1994-2002, Peter Sabath / TRIACOM Software GmbH