Information Topics


General Information

ZZT World Support

Reference

Miscellaneous

ZZT Ultra File Formats


Through experimentation and reverse-engineering, dedicated individuals have managed to crack virtually all of the original file format specifications used for ZZT and Super ZZT world and board files. Because so much documentation exists on these formats, these will not be discussed here.

The most comprehensive source for the older formats is this Modding Wiki.

The new formats, as used by ZZT Ultra exclusively, are discussed here. The overall goal of the new file formats is twofold: extensibility and compatibility.




GUI Definition


These files have extension ZZTGUI. A file is composed of a single JSON dictionary containing all the GUI property definitions. See the GUI Editor documentation for more information about the properties.

When ZZT Ultra initializes, it loads the file guis/zzt_guis.txt relative to the startup directory. This file contains the individual startup GUI specs arranged in a parent JSON dictionary, with the dictionary key representing the GUI name.

The Text property within this dictionary contains a string with text characters of the GUI. Each row of the GUI is delimited with the text "\n". There must be a "\n" before the first row and another "\n" after the last row. Also note that because this is JSON, appropriate characters must be marked up if required.

The Color property contains a JSON array with color attribute numbers for each cell. The numbers alternate between color attributes and length counters for the preceding attribute (a number between 1 and the maximum number of characters for the GUI).

Both Text and Color must contain an appropriate length as indicated by the properties GuiWidth (columns) and GuiHeight (rows).




Object Type Definition


When ZZT Ultra initializes, it loads the file guis/zzt_objs.txt relative to the startup directory. See Type Definitions in ZZT Ultra for more information about individual type definition specs.

ZZT Ultra does not establish much in the way of "hard-coded" object behaviors. All of the original types and behaviors for ZZT and Super ZZT are recreated in guis/zzt_objs.txt. It is relatively easy to fine-tune such behaviors by tweaking the behavioral code.

The CODE object property defines an object's ZZT-OOP code. Generally speaking, the object will ignore the first line if it is empty. Also note that JSON string special characters (namely, double quotes) must be marked up if present.




Game World


The WAD file format is used to store game world data in ZZT Ultra. This favors an overall better modular file format design, plus the possibility of future editing and patching from third-party editors.

The two basic data structures of WAD are the following:

struct WAD_Header {
	char id[4];       // "IWAD"
	int numLumps;     // Count of all lumps
	int infoTableOfs; // Directory offset, byte
};

struct WAD_Directory_Entry {
	int filePos;      // Offset of lump, byte
	int size;         // Size of lump, bytes
	char name[8];     // Name of lump
};
		

A WAD_Header always appears as the first 12 bytes of the file. The directory offset points to the location within the file of the directory entries. There are numLumps entries at this location, making the directory (16 * numLumps) in length.

The following types of lumps are defined for a ZZT Ultra world. A lump type is identified by the name in the directory entry. If the name is shorter than 8 bytes, space characters are used to pad out the remainder of the name.

WORLDHDR

x1, required. This is a JSON-encoded dictionary containing the world properties.

GLOBALS

x1, required. This is a JSON-encoded dictionary of global variables.

TYPEMAP

x1, required. This is a 256-byte binary block, containing the type look-up code for each original ZZT/SZT kind number. The nature of dynamic compilation dictates that this will not necessarily be the same for each world file, because the type look-up info is assigned linearly, while kind numbers tend to jump. When loading a WAD, status elements of a specific type will need to be translated to kind number using this table, and possibly re-translated back to the new type number as compiled in the new version.

PLAYBACK

x1, optional. This is a composited string of unprocessed sound channel queues. If BGM was playing at the time of the save, the exact position (minus latency considerations) should be restored.

EXTRATYP

x1, optional. This is a JSON-encoded dictionary containing extra (or replaced) type definitions. When a world file is loaded, these types are added to the preexisting type definitions, possibly replacing existing types if there is name overlap.

EXTRAGUI

x1, optional. This is JSON-encoded dictionary containing GUI definitions specific to the world. When a world file is loaded, these GUIs are added to the preexisting GUI definitions, possibly replacing existing GUIs if there is name overlap.

SOUNDFX

x1, optional. This is a JSON-encoded dictionary containing PLAY strings for effects specific to the world. Each key is an effect name, and each value is a string that would work with a PLAY statement. Note that it is possible to replace the built-in effects in ZZT with customized effects defined here if there is name overlap.

MASKS

x1, optional. This is a JSON-encoded dictionary containing masks specific to the world. Each key is a mask name, and each value is an array of strings containing "010101" mask info, with "0" indicating a "false" hit and and "1" indicating a "true" hit. The number of rows in the mask is implied from the length of the array, while the number of columns in the mask is implied from the highest number of characters in any one string.

CUSTCODE

x (number of objects with compiled code), optional. Composed of text representing uncompiled object code. The lines are ZZT-OOP code lines. These must be compiled again upon load. The order in which CUSTCODE blocks appear is the same order as original compilation.

The first line of a CUSTCODE lump is the ZZT/SZT kind number indicating the type associated with this code. ZZT Ultra needs this information when reconstituting a composite rendition of the original type code and the custom code. All subsequent lines of the CUSTCODE lump compose the actual custom portion of the code.

BOARDHDR

x (number of boards), required. This is a JSON-encoded dictionary containing the properties for a board. Boards are always stored in the directory in the order they would have appeared in the original ZZT world file, which is, zero (title screen) up to the last board.

BOARDRGN

x (number of boards), required. This is a JSON-encoded dictionary containing the regions for a board. Boards are always stored in the directory in the order they would have appeared in the original ZZT world file, which is, zero (title screen) up to the last board.

STATELEM

x (number of boards), required. This is a JSON-encoded list of status element objects for a board, in the same order they had appeared in the statElem vector during run-time. These lumps are also stored in lexical order like BOARDHDR. Note that default or otherwise unnecessary values of some element attributes are not included as dictionary keys for the status element, because these can be repopulated with defaults upon load. The excluded defaults are UNDERID and UNDERCOLOR (if zero), CYCLE (if matching the type default), and STEPX and STEPY (if matching the type default).

BOARDRLE

x (number of boards), required. This is a binary block containing three RLE-compressed data streams for the board. First is type stream, followed by the color stream, followed by the lighting stream. These lumps are also stored in lexical order like BOARDHDR.




Run-Length-Encoded Streams


BOARDRLE does not compress streams in the same way as the original ZZT, because there were numerous inefficiencies in that format that sometimes resulted in negative compression or otherwise poor compression. The streams are instead compressed in a way that will consistently yield a reduced size (unless the board is extremely irregular and "tooty-fruity" colored).

Type Stream

This stream is stored as the real-time look-up types. Because kind numbers are not necessarily the same as types, one must use the TYPEMAP to translate between types found in this stream and kind numbers.

The stream is composed of a series of one-two [L][B] combinations, with L=Length byte and B=One or more bytes. If 0<L<128, [B] is one byte, repeated. If -128<=L<0, [B] is a run of -L bytes.

This sequence proceeds until all grid space is exhausted. The minimum positive value of L should be 4 in practice, because no compression advantage is realized unless the repeated sequence is at least 4 spaces long.

The value of L=0 is special, indicating an end to the type stream (rest of grid should be considered EMPTY).

Color Stream

This is actually two back-to-back substreams. The first substream represents foreground colors, and the second substream represents background. Both substreams are repeated until all grid space is exhausted. The format for a substream is a series of bytes of [L.C], where L+1 is the length the color is repeated (for foreground or background), and C is the 16-bit color. L is high bits (4-7) while C is low bits (0-3).

The system is designed to take advantage of the fact that color variation is less frequent than type variation, and also the fact that background is likely to remain the same even if foreground varies.

Lighting Stream

This was not stored in original ZZT format, but it is necessary for the new format. "Lit" portions of the level are stored as lengths because the lighting profile is boolean: either lit or unlit. The first byte, [P], identifies if the stream exists. If 0, there is no lighting stream. If 1, a lighting stream follows.

When a lighting stream follows, it alternates between "unlit range" and "lit range" bytes: [UR][LR], allowing for the full range of the unsigned byte for either lit or unlit ranges (0-255). The "lit range" code of LR=0 is special, indicating an end to the lighting stream (rest of grid should be considered unlit).




World File Patch


ZZT Ultra leaves open the possibilty of patching content on the basis of dictionary-style storage for text-based properties, and the use of the PWAD file format for overall binary-based content.

The way a PWAD typically works is that an equivalent lump in a targeted IWAD file is replaced or otherwise modified wherever a lump by the same name is found in the PWAD file.

For ZZT Ultra's world files, there are a few additional nuances to patching that should be taken into consideration. These details are as follows.




Dictionary-based patching


Dictionary-based patching is a very simple concept. If a "patch" dictionary must be applied to an existing dictionary, a key that is brand-new to the dictionary is simply assigned to it, while an existing key is replaced by a key of the same name. This applies to WORLDHDR, BOARDHDR, GLOBALS, EXTRATYP, EXTRAGUI, SOUNDFX, MASKS, and STATELEM.

If for some reason it becomes necessary to remove an existing key instead of replacing it with something, one can do so by having the "patch" dictionary include this member:

			"delete" : "ExistingKey"
		

The "delete" key in the above example would change the following dictionary...

			{
				"oneKey" : 1,
				"twoKey" : 2,
				"ExistingKey" : 3
			}
		

...into this...

			{
				"oneKey" : 1,
				"twoKey" : 2
			}
		

If a PWAD lists a lump containing a JSON dictionary, it should patch the existing dictionary using this "add-replace-delete" system.

The STATELEM lump is organized as an array of structures, which makes deletion of status elements follow a slightly amended model. The "X" and "Y" keys of a status element identify a location. If the "TYPE" key is set to "delete", an existing status element at (X, Y) should be removed.




Binary-based patching


For binary-based patching or text patching that does not make use of JSON dictionaries, the ability to patch content is heavily dependent on the WAD lump type itself. Most lumps are replaced in their entirety, but some need to have a more sophisticated patching mechanism.

Of primary importance to the complex patching of binary lumps is BOARDRLE. Patches, if they are necessary for a board, must break down the gridded data in a highly piecemeal fashion.

ZZT Ultra supports the PATCH type, with number 255, to indicate that the square should not be replaced in the patched board. This "transparent" type compresses easily if there is only one small part of a board that needs to be patched.

Color and lighting streams are the same format as for an IWAD. These generally would also compress well for the PATCH type, because a uniform zero byte can be set for PATCH, which ends up compressing just as well as any other consistent color or lighting.

If a board within a world does not need to be patched at all, it can have a lump length of zero, indicating that the board is acknowledged within the overall order, but it should remain the same as before. The reason to do this is if only the 5th board out of a 20-board world needs to be patched, the first four boards can have empty lumps.




Legacy file patching


ZZT Ultra allows an interesting possibility when it comes to patching old ZZT and SZT world files. Even though no "patch" file per se was supported by the legacy formats, it is possible in ZZT Ultra to create such a PWAD patch that will work for the world when loaded in ZZT Ultra.

As long as there is reasonable consistency in the board order, object positions, etc. across the distributed version of the legacy world file, a PWAD in ZZT Ultra can perform targeted fixes upon load.

Return to Table of Contents



This page is Copyright © 2016 Christopher Allen.