ZenKit has recently been updated to version 1.3! For information about how to migrate your projects, see the documentation.
Important
NEW: C#, Java and Python bindings for ZenKit are now available! See the NuGet Package Gallery, Maven Central and PyPI.
The ZenKit project aims to re-implement file formats used by the ZenGin made by Piranha Bytes for their early-2000s games Gothic and Gothic II. It is heavily based on ZenLib which is used as a reference implementation for the different file formats used.
ZenKit includes parsers and basic datastructures for most file formats used by the ZenGin as well as a type-safe VM for Daedalus scripts and supporting infrastructure like Gothic II class definitions. Tools for inspecting and converting ZenGin files can be found in phoenix studio.
To get started, take a look in the Reference Documentation. Don't hesitate to open a discussion thread over in Discussions if you have a question or need help. Please open an issue for any bug you encounter!
You can also contact me directly on Discord, ideally by pinging me (@lmichaelis) in the GMC Discord in the tools channel.
Currently, the following file formats are supported.
Format | Extension | Description | ZenKit Class Name |
---|---|---|---|
Model Animation | .MAN |
Contains animations for a model | ModelAnimation |
Model Hierarchy | .MDH |
Contains skeletal information for a model | ModelHierarchy |
Model Mesh | .MDM |
Contains the mesh of a model | ModelMesh |
Model | .MDL |
Contains a mesh and a hierarchy which make up a model | Model |
Morph Mesh | .MMB |
Contains a morph mesh with its mesh, skeleton and animation data | MorphMesh |
Multi Resolution Mesh | .MRM |
Contains a mesh with LOD information | MultiResolutionMesh |
Mesh | .MSH |
Contains mesh vertices and vertex features like materials | Mesh |
Daedalus Script | .DAT |
Contains a compiled Daedalus script | DaedalusScript |
Texture | .TEX |
Contains texture data in a variety of formats | Texture |
Font | .FNT |
Contains font data | Font |
ZenGin Archive | .ZEN |
Contains various structured data (general object persistence). | ReadArchive |
Text/Cutscenes | .BIN , .CSL , .DAT , .LSC |
Contains text and cutscene data | CutsceneLibrary |
Model Script | .MDS , .MSB |
Contains model animation script data and associated hierarchy and mesh information | ModelScript |
Virtual File System | .VDF |
Contains a directory structure containing multiple files; similar to TAR. | Vfs |
If you'd like to contribute, please read Contributing first.
ZenKit is currently only tested on Linux and while Windows should be supported you might run into issues. If so, feel free to create an issue or open a merge request. You will need
- A working compiler which supports C++17, like GCC 9
- CMake 3.10 or above
- Git
To build ZenKit from scratch, just open a terminal in a directory of your choice and run
git clone --recursive https://github.com/GothicKit/ZenKit
cd ZenKit
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
You will find the library in build/
.
#include <zenkit/Vfs.hh>
#include <zenkit/Model.hh>
#include <zenkit/Stream.hh>
int main(int, char**) {
// Open the VDF file for reading
zenkit::Vfs vfs {};
vfs.mount_disk("Models.VDF");
// Find the MyModel.MDL within the VDF
auto entry = vdf.find("MyModel.MDL");
if (entry == nullptr) {
// MyModel.MDL was not found in the VDF
return -1;
}
// Open MyModel.MDL for reading
auto buf = entry->open_read();
// One could also memory-map a normal file from disk:
// auto buf = zenkit::Read::from("/path/to/file");
// Or if you have a vector of data:
// std::vector<uint8_t> data { /* ... */ };
// auto buf = zenkit::Read::from(std::move(data));
// Parse the model
zenkit::Model mdl {};
mdl.load(buf.get());
// Do something with mdl ...
return 0;
}
ZenKit also provides a VM implementation for the Daedalus scripting language used by ZenGin:
#include <zenkit/Stream.hh>
#include <zenkit/DaedalusScript.hh>
#include <zenkit/DaedalusVm.hh>
#include <iostream>
#include <string>
enum class MyScriptEnum : int {
FANCY = 0,
PLAIN = 1,
};
// Declare a class to be bound to members in a script. This is used in `main`.
struct MyScriptClass : public zenkit::DaedalusInstance {
// Declare the members present in the script class.
// Supported types are:
// * int
// * float
// * std::string
// * `enum` types with sizeof(enum) == 4
// and their C-Style array versions.
std::string myStringVar;
int someIntegers[10];
float aFloat;
MyScriptEnum anEnum;
};
// Define a function to be bound to an external definition in a script. This is used in `main`.
// Supported parameter types are:
// * int
// * float
// * bool
// * std::string_view
// * std::shared_ptr<instance> or any subclass of instance
// Supported return types are:
// * int (or anything convertible to int32_t)
// * float (or anything convertible to float)
// * bool
// * void
// * std::shared_ptr<instance> or any subclass of instance
bool MyExternalFunction(std::string_view param1, int param2, std::shared_ptr<MyScriptClass> param3) {
std::cout << "Calling MyExternalFunction(" << param1 << ", " << param2 << ", " << param3->symbol_index() << ")\n";
return true;
}
// Define a function to be bound to an internal definition in a script. This is used in `main`.
// Supported parameter and return types are the same as for external functions.
std::string MyInternalFunction(int param1) {
return std::to_string(param1);
}
int main(int, char**) {
// Open a buffer containing the script.
auto buf = zenkit::Read::from("MyScript.DAT");
// Create the VM instance
zenkit::DaedalusScript script {};
script.load(buf.get());
zenkit::DaedalusVm vm {std::move(script)};
// You can register Daedalus -> C++ shared classes. The `register_member` function will automatically
// validate that the definitions match at runtime.
vm.register_member("MyScriptClass.myStringVar", &MyScriptClass::myStringVar);
vm.register_member("MyScriptClass.someIntegers", &MyScriptClass::someIntegers);
vm.register_member("MyScriptClass.aFloat", &MyScriptClass::aFloat);
vm.register_member("MyScriptClass.anEnum", &MyScriptClass::anEnum);
// You could also have differing member and/or class names:
// vm.register_member("SomeOtherClass.fancyness", &MyScriptClass::anEnum);
// ZenKit supports registering external script functions to a C++ function. The function signature is
// validated at runtime to match the definition of the function in the script file.
vm.register_external("MyExternalFunction", &MyExternalFunction);
// You can also register a function to be called if an external is not registered:
vm.register_default_external([](std::string_view name) {
std::cout << "External " << name << " not registered\n";
});
// ZenKit allows you to override internal script functions as well. The signature of the function
// is also validated at runtime.
vm.override_function("MyInternalFunction", &MyInternalFunction);
// You can call the instance initializer like this:
auto myNpc = vm.init_instance<MyScriptClass>("MyInstance");
// Alternatively, you can also provide a pointer to the instance instead of having it be allocated
// automatically:
//
// auto ptr = std::make_shared<MyScriptClass>();
// vm.init_instance(ptr, "MyInstance");
// Calling internal script function is also easy:
std::cout << "Result of MyInternalFunction(10): "
<< vm.call_function<std::string>("MyInternalFunction", 10) << "\n";
// If you'd like to avoid passing a string to this function, you can also fetch the
// function symbol beforehand and pass it instead:
//
// auto* functionSym = vm.find_symbol_by_name("MyInternalFunction");
// auto result = vm.call_function<std::string>(functionSym, 10);
// Sometimes it is required to set the HERO, SELF, OTHER, ITEM, or VICTIM global instance variables.
// This can be done like this:
auto oldValue = vm.global_other()->get_instance();
vm.global_other()->set_instance(myNpc);
// The other global variables can be accessed using:
// * vm.global_self()
// * vm.global_other()
// * vm.global_victim()
// * vm.global_hero()
// * vm.global_item()
// No special clean-up logic is required. All initialized instances will be
// valid even after the script is destructed because they are shared_ptrs.
return 0;
}
For more examples on how to use ZenKit, take a look into the examples directory and phoenix studio repository.
While the source code of ZenKit is licensed under the MIT license, the ZenKit logo is licensed under CC BY-NC 4.0.