Skip to content

Extending game and room data format

ivan-mogilko edited this page Mar 3, 2021 · 4 revisions

In the past we made a mistake by modifying game format in the middle of existing data several times. That was convenient for developers because allowed to keep related object data together, but turned out very inconvenient for long-term tool support (for instance, if there were a tool which parsed or extracted game data), as in such case tools had to be updated all the time or they will fail to read even previously known chunks in games made for the newest engine versions.

For this reason we introduce a rather simple extension logic, practically copied from the room format, which originally consisted of a sequence of data blocks. We strongly suggest to use it when expanding both game and room format.


Format description

Extensions are a sequence of self-described blocks. Each block starts with the following header:

bytes meaning
1 byte > 0 is an old-style unsigned numeric ID;
0 = anticipate following string ID;
0xFF = indicates end of extension list.
16 bytes string ID of an extension;
present only if numeric ID == 0, otherwise missing;
does not have to be null-terminated if 16-bytes long
8 bytes length of extension data, in bytes.
N bytes extension data.

The total block's size is (1 + 16 + 8 + data length) bytes.

This list of extensions is appended right after default game data.

Extensions are read until meeting 0xFF at the first byte of next header, which indicates end of extension list. If end of file is found earlier that is considered an error.

After the block is read the stream position supposed to be at (start + 25 + length of data), where "start" is a first byte of the block header. If it's further - the engine stops reading with error. If it's not far enough - the engine logs a warning and skips remaining bytes.


Adding new extension: in the Editor

At the time of writing game data is serialized using DataFileWriter class. It provides helper method WriteExtension(), which accepts extension ID and a delegate function of type WriteExtensionProc. WriteExtension method already handles extension header, so your function only has to write extension data itself.

Room data is serialized in native code, by WriteRoomData() function. WriteBlock() function is called for each block, passing ID and function pointer of type PfnWriteBlock which suppose to write extension data itself.

Adding new extension: in the Engine

Game extensions are currently read in ReadGameData() function, where optional extension list is parsed and ReadExtBlock() function is called for each block. Inside it checks provided block ID and determines what to read next.

Room extensions are read in ReadRoomData() function, ReadRoomBlock() is called for each block. Inside it checks provided block ID and calls one of the dedicated functions which read particular data. Note that room format supports both numeric ID (> 0) and string IDs (in which case numeric ID == 0).


See also

Original ticket describing the problem: https://github.com/adventuregamestudio/ags/issues/828

Pull request adding this format to game data: https://github.com/adventuregamestudio/ags/pull/1183