ZenGin Archives¶
The ZenGin Archive format is similar in concept to JSON or XML in that it is used to serialize nested data
into a file which can be read later. There are three different kinds of archives called ASCII
, BINARY
and BIN_SAFE
but the way these work exactly does not matter when using ZenKit.
For detailed documentation on the inner workings of archives, see the ZenGin Reference.
Overview¶
Like JSON, archives store values using a key-value structure where every key-value pair belongs to an object. Objects
can also be nested. Unlike JSON, archives do not have first-class support for arrays, instead choosing a special type
and encoding scheme. The class which is responsible for loading ZenGin Archives is called zenkit::ReadArchive
.
Loading an archive¶
Since archives are an intermediate format, they are not loaded into memory. Rather, zenkit::ReadArchive
functions as
a high-level reader-like interface with which data can be read from the archive in a stream. To create one, simply
call zenkit::ReadArchive::from
(which is analogous to zenkit::Read::from
) and start reading from it:
Warning
Archives are currently not supported with the C API.
#include <zenkit/Archive.hh> // for `ReadArchive`
#include <zenkit/Stream.hh> // for `Read`
int main(int, const char** argv) {
auto r = zenkit::Read::from("A.ZEN");
auto ar = zenkit::ReadArchive::from(r.get());
// ...
return 0;
}
Info
As with all resources, archives can also be loaded from a virtual file system, passing the
input obtained from zenkit::VfsNode::open_read
into zenkit::ReadArchive::from
.
It must be noted in this case, however, that the unique pointer returned by VfsNode::open_read
must be saved
for the lifetime of ar
since it is the data source for all read operations on it.
Warning
Archives are currently not supported with the C# API since it depends on the C wrapper.
Warning
Archives are currently not supported with the Java API since it depends on the C wrapper.
Warning
Archives are currently not supported with the Python API since it depends on the C wrapper.
Reading from an archive¶
ZenKit's implementation of ZenGin Archives is very bare-bones and does not provide a very rich interface. ZenKit will automatically determine the kind of archive but that's about it. Reading more data from it requires knowing the format you will be reading beforehand, no buffering or preloading of data is done. A more detailed explanation and demonstration can be found in the example below.
Warning
Archives are currently not supported with the C API.
#include <zenkit/Archive.hh> // for `ReadArchive`
#include <zenkit/Stream.hh> // for `Read`
#include <iostream>
int main(int, const char** argv) {
// First, open the archive
auto buf = zenkit::Read::from("WORLD.ZEN");
auto zen = zenkit::ReadArchive::from(buf.get());
// Second, read the definition of the root object.
// Every archive has one object at its root which can be used to
// identify what kind of data it is storing.
zenkit::ArchiveObject object {};
if (!zen->read_object_begin(object)) {
// If no object begins at the current position `read_object_begin`
// will return `false`.
std::cerr << "The root object was not found.\n";
return -1;
}
// Here, for example, we're checking whether the root object is a
// serialized game world.
if (object.class_name != "oCWorld:zCWorld") {
std::cerr << "This archive does not contain a serialized game world.\n";
return -1;
}
// From now on, we need to know the format worlds come in. It is not documented here
// but if necessary, the format of most objects can be found in ZenKit's source code.
//
// In this case, we know that worlds contain three sub-objects, one describing the
// world mesh, one containing the way-net and one containing all dynamic objects in
// the world.
while (!zen->read_object_end()) {
// `read_object_end` is used to read the end of an object. If it returns `false`,
// no object ends at the current position. It is required to `read_object_end`
// after reading all entries in that object.
zen->read_object_begin(object);
if (object.object_name == "VobTree") {
// Here, we're only interested in finding the sub-object containing the dynamic
// objects placed into the world.
// The VOb tree consists of more nested objects, each followed by an integer
// denoting the number entries following it which should be considered children
// of the VOb. The first integer denotes the number of root objects in the tree.
auto root_vobs = zen->read_int();
while (!zen->read_object_end()) {
zen->read_object_begin(object);
if (object.class_name == "zCVob") {
// There is a whole system to the class names in the VOb tree but here,
// to give an example, we're just interested in the basic `zCVob`. Also
// there is a lot more data saved in the `zCVob` object but I will omit
// most of it for brevity sake.
//
// So here's some of the types supported by archives read from one:
auto packed = zen->read_int() != 0;
auto preset_name = zen->read_string();
auto bbox = zen->read_bbox();
auto rotation = zen->read_mat3x3();
auto position = zen->read_vec3();
auto vob_name = zen->read_string();
auto visual_name = zen->read_string();
auto show_visual = zen->read_bool();
auto camera_alignment = zen->read_enum();
// As you can see, all fields must be read in order and with the correct type.
// If the archive contains a field of type `int` but you try to `read_string()`,
// ZenKit will throw an exception (as documented in the docstrings).
//
// For the full list of supported types, see the table below this example.
}
if (!zen->read_object_end()) {
// (see below for an explanation)
zen->skip_object(true);
}
}
}
// Now, we read the end of the current object.
if (!zen->read_object_end()) {
// But since this small example cannot parse the `WayNet` and `MeshAndBsp`
// objects of the world, we need to still make sure that the parser is in
// a sane state. `skip_object` can be used to fully ignore the next object
// in the archive or, if passed `true`, the object the parser is currently in.
//
// We pass `true` here, since we've already `read_object_begin` so the
// parser is now considered to be in the object we want to skip. Here, skipping
// the current object also has another great bonus: if, for some reason, one
// of the objects was not fully parsed, the remaining entries will be skipped
// so the parser is in a good state for the next iteration of the loop.
zen->skip_object(true);
}
}
return 0;
}
Supported data types:
Type Name | Read Function | Description |
---|---|---|
string |
read_string |
A Windows-1252 encoded std::string |
int |
read_int |
A 32-bit signed integer |
float |
read_float |
An IEEE 754 floating point number |
byte |
read_byte |
An 8-bit unsigned integer |
word |
read_word |
A 16-bit unsigned integer |
enum |
read_enum |
A 32-bit unsigned integer |
bool |
read_bool |
A boolean value |
color |
read_color |
An RGBA color quad |
vec3 |
read_vec3 |
A 3-dimensional vector with floating point values |
rawFloat |
read_bbox , read_mat3x3 , read_vec2 |
A mathematical structure consisting of multiple floating point values |
raw |
read_raw |
A set of raw bytes, returned as a zenkit::Read* |
Warning
Archives are currently not supported with the C# API since it depends on the C wrapper.
Warning
Archives are currently not supported with the Java API since it depends on the C wrapper.
Warning
Archives are currently not supported with the Python API since it depends on the C wrapper.