ver. 1.0
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.
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.
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 |
Apart from booleans, which must be encoded as Bytes, JSON is a subset of ESB and can be encoded losslessly.
int
s.float
s in Python.dict
s in Python and Object
s in JSON.list
s in Python and Array
s in JSON.None
in Python and null
in JSON.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.
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 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.
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"
}