Event System Binary file format definition

ver. 1.0

Introduction

The Event System Binary (abbreviated as ESB, using the file extension .esb) format is a key-value based structure designed for encoding heavily nested data in a maximally compressed binary format with minimal overhead, devised for storing save and source files for the game Stelios.

Specification

Keys and values

Data entries in the ESB format are preceded by a single byte describing the type of the following data, unless the type is already known. The type byte can then followed by a string which is the key of the value, however for certain structures the key is omitted. The bytes describing the actual value itself follow lastly.

Data types

The data type byte usually in front of key-value entries describes how to proceed with reading the values.

decimal hex type name description
0 0x0 Close statement (null byte) The null byte is strictly not a type of its own but simply a closing statement for other types. It is used for closing arrays and ending strings.
1 0x1 Byte A single byte, read as a signed integer: −(2⁷) to 2⁷ − 1
2 0x2 Short 2 bytes, read as a signed integer: −(2¹⁵) to 2¹⁵ − 1
3 0x3 Integer 4 bytes, read as a signed integer: −(2³¹) to 2³¹ − 1
4 0x4 Long 8 bytes, read as a signed integer: −(2⁶³) to 2⁶³ − 1
5 0x5 Number First read one byte as an unsigned integer n, then read n bytes as a signed integer
6 0x6 Double Read 8 bytes in IEEE 754 64-bit format (as a double)
7 0x7 String Read UTF-8 bytes until a null byte 0x0 (Close statement type)
8 0x8 Named Array Begin reading key-value entries that each define their own type, until a null byte 0x0 (Close statement type)
9 0x9 Byte Array Begin reading value entries that are all of type Byte (0x1), thus omitting both the type byte and the key string, until a null byte 0x0 (Close statement type)
10 0xA Short Array Begin reading value entries that are all of type Short (0x2), thus omitting both the type byte and the key string, until a null byte 0x0 (Close statement type)
11 0xB Integer Array Begin reading value entries that are all of type Integer (0x3), thus omitting both the type byte and the key string, until a null byte 0x0 (Close statement type)
12 0xC Long Array Begin reading value entries that are all of type Long (0x4), thus omitting both the type byte and the key string, until a null byte 0x0 (Close statement type)
13 0xD Number Array Begin reading value entries that are all of type Number (0x5), thus omitting both the type byte and the key string, until a null byte 0x0 (Close statement type)
14 0xE Double Array Begin reading value entries that are all of type Double (0x6), thus omitting both the type byte and the key string, until a null byte 0x0 (Close statement type)
15 0xF String Array Begin reading value entries that are all of type String (0x7), thus omitting both the type byte and the key string, until a null byte 0x0 (Close statement type)
16 0x10 Unnamed Array Begin reading value entries that each define their own type, skipping the key string, until a null byte 0x0 (Close statement type)
255 0xFF Null Not strictly a type because it carries no payload, the Null type represents the profound absence of a value

JSON compatibility

Apart from booleans, which must be encoded as Bytes, JSON is a subset of ESB and can be encoded losslessly.

Reading into JSON or Python data types

.esb file

Format

A fully formed .esb file consists of a single String representing the header of the file, followed by the top level Named Array, which has no key. In the absence of a header the file must begin with a null byte.

Compression

an .esb file is compressed with zlib, at the default compression level of 6. An explicitly uncompressed ESB format file may be marked with the extension .esbu

Example

Encoding

Example small-scale dataset as JSON:

{
  "f": [1, 1, 2, 3, 5],
  "abc": "def"
}

This is a Named Array with two elements: a Byte Array (as all the values fit inside a Byte) and a String, which both have keys.

We begin with the type of the top-level structure, the Named Arrary, which is 0x8. As the top-level structure has no key we proceed with the type of the first value contained in it, the Byte Array, which is 0x9.
Because it is part of a Named Array, it must have a key, which in this case is simply “f”. As UTF-8 it is 0x66 0x0. Note the null-byte at the end to terminate the String. The contents of the Byte Array are then listed out, each a single byte as specified by the Byte type, with a null byte at the end, resulting in 0x1 0x1 0x2 0x3 0x5 0x0. The contents of a Typed Array lack keys.
As we’ve closed this Byte Array, we can proceed with the next element of the Named Array, which is a string, resulting in type 0x7. Its key is 0x61 0x62 0x63 0x0. The String itself is however 0x64 0x65 0x66 0x0.
Finally, the top level Named Array must also be closed off with a null byte. The final result in hexadecimal is the following:

08 09 66 00 01 01 02 03 05 00 07 61 62 63 00 64 65 66 00 00

This is a valid ESB structure, however it is not a valid .esb as it lacks a header and is not compressed.

Decoding

We use the same data as in the encoding example, this time in reverse, into JSON:

08 09 66 00 01 01 02 03 05 00 07 61 62 63 00 64 65 66 00 00

As we know that all ESB files must begin with a Named Array, we can expect the it (0x8) at the beginning, which matches the data. The key is skipped. ({ ... })
What follows is the type of the first entry in the Named Array: 0x9, which means it is a Byte Array. We can also expect a key for this value. Since it’s a String, we read bytes until a null byte (0x0): 0x66 0x0, converted from UTF-8 is the string “f”. ({ "f": Byte Array...})
The contents of the Byte Array come next. As they’re all of type Byte, we read it in single-byte groups, until we read a null byte: 0x1 0x1 0x2 0x3 0x5 0x0. These are converted into integers and become members of an array. ({ "f": [1, 1, 2, 3, 5], ...})
Since the Byte Array was closed we can expect the type of a new value for the Named Array, which is 0x7, representing a String. We can then read its key as a String: 0x61 0x62 0x63 0x0, which converted from UTF-8 is the string “abc”. ({ "f": [1, 1, 2, 3, 5], "abc": String...})
The contents of the String are also read as UTF-8: 0x64 0x65 0x66 0x0, resulting in ({ "f": [1, 1, 2, 3, 5], "abc": "def"}).
Since the String was closed we can expect the type of a new value for the Named Array, however we receive a null byte instead, meaning it is closed, concluding the datastream.

The output of the decoding example matches the input of the encoding example

{
  "f": [1, 1, 2, 3, 5],
  "abc": "def"
}