.tng
application/x-tirnanog
This format is used to store Adventure or Action RPG games. It can be created with the TirNanoG Editor tnge, and played with the TirNanoG Player, tngp. For the directory structure used by the editor during development, see the project layout.
I, bzt, the copyright holder of the file format release The TirNanoG File Format with dual licensing:
by default, the unencrypted game file format is licensed under CC-by-nc-sa (attribution required, non-commercial, share-alike)
with the written permission of the TirNanoG File Format's author, you can you can create proprietary encrypted game files.
The decision and the responsibility of which licensing version to use for the game files is up to the editor's users, and it does not influence the game's license stored in this format. Under no circumstances can I, bzt, the TirNanoG Editor and TirNanoG File Format's author be held responsible for the game files created by others.
Trying to decrypt, disassemble or any other way reverse engineer the proprietary encrypted format is strictly forbidden to protect the rights of the copyright holders of the assets inside the file.
IMPORTANT NOTE: the file format's license has nothing to do with the game's license stored in this format, just like how PKWARE's copyright does not influence the file's licenses stored in a .zip file.
The format was designed in a way that multiple files may store a single game, thus allowing modular expansion packs. The license of separate files of the same game should, but do not need to match; an encrypted file format might have an unencrypted expansion, but an unencrypted game cannot have an encrypted expansion (a technical limitation in the TirNanoG Player).
[[TOC]]
This is a binary format, starting with magic bytes to be identifiable. Integers are stored in little-endian format, floating points as per the IEEE-754 specification. Strings are always stored as zero terminated UTF-8 multibyte characters (except for the game id, which is always 16 bytes and ASCII only).
Offset | Length | Description |
---|---|---|
0 | 16 | magic, '#!/usr/bin/tngp' + ASCII 10 (newline) |
16 | 16 | game id, padded with zeros (may not be zero terminated) |
32 | 1 | file format revision (0 for now) |
33 | 1 | game type |
34 | 1 | tile width (bits 8-9 in game type byte bits 4-5) |
35 | 1 | tile height (bits 8-9 in game type byte bits 6-7) |
36 | 1 | map size in power of two |
37 | 1 | atlas size in power of two |
38 | 1 | audio samples frequency, (0 = 44100 Hz) |
39 | 1 | number of frames per second (by default 30) |
40 | 1 | number of default action handlers (by default 9) |
41 | 1 | number of map layers (by default 11) |
42 | 1 | number of transportation methods (by default 3) |
43 | 1 | highest bytecode command used in scripts |
44 | 1 | character sprites upwards delta y |
45 | 1 | number of character sprites per layer (by default 20) |
46 | 2 | must be zero |
48 | 8 | unique id (used by the client to detect if the server has a newer version) |
56 | 4 | CRC of the entire file (zero when calculating) |
60 | 4 | must be zero for CC-by-nc-sa licensed files, and it is non-zero for encrypted files |
Game type is as follows: 0 - top-down, 1 - isometric, 2 - hex base vertical, 3 - hex based horizontal, 4 - 3D
The header is 64 bytes in length. For commercial and proprietary files, the rest of the file is encrypted.
Limits: one section max 16M (2^24), sections altogether max 4G (2^32), one atlas png max 2G (2^31), number of atlases max 4096 (2^12), one asset max 4G (2^32), tng archive total size max 2^63.
After the header comes the list of sections, compressed by deflate. It is prefixed by a 4 byte size value, and suffixed by a crc32 (included in the compressed size).
Offset | Length | Description |
---|---|---|
64 | 4 | size of the compressed sections (n) |
68 | n | compressed sections + crc |
The uncompressed buffer starts with a section table, where each entry is 8 bytes, followed by concatenated section data where the data size varies on section kind. The first entry in the section table points to the first section data, and thus its offset encodes the size of the section table as well.
Offset | Length | Description |
---|---|---|
0 | 4 | section offset (relative to uncompressed sections block) |
4 | 3 | section length |
7 | 1 | section type |
It is common that section data is a list of asset descriptors, which looks like
Offset | Length | Description |
---|---|---|
0 | 8 | asset offset (relative to compressed sections' end in file) |
8 | 4 | asset length |
12 | 4 | asset name (offset to string section) or sprite index (upper 2 bytes must be zero) |
Asset offsets are relative to the end of the compressed table (actual file offset = offset + 68 + compressed sections size).
Hereafter "string index", "font index", "music index", "sound index" etc. refers to an offset into the string section (so not a real record index but a byte offset), but not "text index" (which is the actual index of a string record in translatations section's text asset) nor "sprite index" (which is an actual index to the corresponding sprite section's record).
The first string is an empty string, so string index 0 means not defined. For the others -1 (0xffffff) encodes not set.
Relations are as follows: 0: = equal, 1: != not equal, 2: >= greater or equal, 3: > greater than, 4: <= less or equal, 5: < less, 0xff: set (not a required attribute relation but a provided attribute).
Event handler codes: 0: on click, 1: on touch, 2: on leave, 3: on approach, 4: on action1, 5: on action2, 6: on action3, 7: on action4, 8: on action5, 9: on action6, 10: on action7, 11: on action8, 12: on action9, 13: on using object1, 14: on using object2, 15: on using object3, 16: on timer, 17: on sell, 18: on buy.
This section always exists and it always must be the first. It points to variable length records, zero terminated ASCII strings. These contain the internal names of entities.
Has a header, followed by asset descriptors. The asset in the header points to a text asset in the file, encoded as a concatenated list of zero terminated UTF-8 characters compressed with deflate. Other assets point to an audio asset in the file, encoded as OGG vorbis, mono channels.
Offset | Length | Description |
---|---|---|
0 | 2 | ISO-639-1 language code |
2 | 8 | asset offset (relative to compressed sections' end in file) |
10 | 4 | asset length |
14 | 4 | language name text index |
The very first string in the text asset is always the human readable name of the game (title).
This section is special in a way that it might appear multiple times, but with different language codes. When it does appear multiple times, then the number of assets must be the same in every section, and they point to the same audio, just in a different language. If an audio file is missing for a specific language, then the corresponding asset descriptor contains zero length.
Here each entry is 16 bytes long, thus section length must be multiple of 16. Each entry points to a font asset in the file, encoded as SSFN2.
Here each entry is 16 bytes long, thus section length must be multiple of 16. Each entry points to an audio asset in the file, encoded as OGG vorbis, stereo (or more) channels.
Here each entry is 16 bytes long, thus section length must be multiple of 16. Each entry points to an audio asset in the file, encoded as OGG vorbis, mono channels.
Here each entry is 16 bytes long, thus section length must be multiple of 16. Each entry points to a movie asset in the file, encoded as Theora OGV.
Here each entry is 16 bytes long, thus section length must be multiple of 16. Each entry points to an image asset in the file, encoded as PNG.
Offset | Length | Description |
---|---|---|
0 | 8 | asset offset (relative to compressed sections' end in file) |
8 | 4 | asset length |
12 | 4 | must be zero |
Here each entry is 16 bytes long, thus section length must be multiple of 16. Each entry points to a sprite map asset in the file, which is compressed binary data.
Offset | Length | Description |
---|---|---|
0 | 8 | asset offset (relative to compressed sections' end in file) |
8 | 4 | asset length |
12 | 3 | must be zero |
15 | 1 | number of directions stored for this sprite (d, 1 - 8) |
A sprite in dimension is limited to 2048 x 2048 and 32 frames. When uncompressed, it contains (d) animation records:
Offset | Length | Description |
---|---|---|
0 | 2 | sprite width |
2 | 2 | sprite height |
4 | 1 | type (bits 0 - 5: direction, bits 6 - 7: loop type) |
5 | 1 | number of frames (n, 1 - 32) |
6 | n * 14 | sprite map descriptors |
Loop type is as follows: 0: play once, 1: play forth and back, 2: play in a loop. The sprite map descriptor looks like:
Offset | Length | Description |
---|---|---|
0 | 2 | atlas id (index to section type 6 entries) and operation |
2 | 2 | position x on atlas |
4 | 2 | position y on atlas |
6 | 2 | width on atlas |
8 | 2 | height on atlas |
10 | 2 | left margin |
12 | 2 | top margin |
It is important that the atlas stores only a cropped version of the sprite, so sprite width and height can be bigger than width and height on atlas. Also it is possible that multiple sprite map descriptor points to the same sprite on the atlas, because atlases are deduplicated.
There can be max. 4096 atlases (referenced as 0 - 0xfff), so the upper tetrad of the atlas id encodes operation: 0 - simple copy, 1 - flip vertically, 2 - flip horizontally, 3 - rotate clock-wise, 4 - rotate 180 degrees, 5 - rotate counter clock-wise.
Same as section type 7.
Same as section type 7.
Same as section type 7.
Here each entry is 16 bytes long, thus section length must be multiple of 16. Each entry points to an image asset in the file, encoded as PNG.
NOTE: section types 12 - 15 are reserved for future use.
Offset | Length | Description |
---|---|---|
0 | 1 | number of color definitions (10) |
1 | 40 | 10 * 4 bytes ABGR colors |
41 | 1 | number of font definitions (6) |
42 | 42 | 6 * (1 byte style + 1 byte size + 3 bytes font index + 2 bytes padding) |
82 | 1 | number of sound effects (3) |
83 | 9 | 3 * 3 bytes sound index |
92 | x | rest is 3 bytes sprite records, 1 byte category, 2 bytes index |
Font index is an offset to strings section. Sprite index selects a record from one of the sprite sections pointed by category (category is the same as section type - 7).
If the "main menu enabled" checkbox isn't set, then this block is only 3 bytes long and contains only the intro cutscene. Otherwise it's length is 141.
Offset | Length | Description |
---|---|---|
0 | 3 | intro cutscene index |
3 | 3 | background music index |
6 | 3 | background movie index |
9 | 3 | background image category (1 byte) and index (2 bytes) |
12 | 1 | number of background parallax (3) |
13 | 27 | 3 * (2 bytes dx, 2 bytes dy, 2 bytes time, 1 byte category, 2 bytes sprite index) |
40 | 3 | title image sprite, 1 byte category, 2 bytes index |
43 | 1 | bit 0: set if title text is shown, bit 1: don't crop background image / video |
44 | 4 | title text color |
48 | 4 | title text shadow color |
52 | 5 | title font (1 byte style, 1 byte size, 3 bytes font index) |
57 | 3 | website URL string index |
60 | 2 | button top padding |
62 | 5 | button font (1 byte style, 1 byte size, 3 bytes font index) |
67 | 1 | number of button definitions (5) |
68 | 65 | 5 * (4 bytes color, 3 bytes left sprite, 3 bytes background, 3 bytes right) |
133 | 4 | attribute graph chart color |
137 | 4 | attribute graph background color |
Offset | Length | Description |
---|---|---|
0 | 3 | navigator circle sprite (1 byte category, 2 bytes index) |
3 | 3 | action buttons sprite (1 byte category, 2 bytes index) |
6 | 3 | item bar background sprite (1 byte category, 2 bytes index) |
9 | 3 | item bar foreground sprite (1 byte category, 2 bytes index) |
12 | 7 | one byte each, number of items, width, height, left, top, gap, handheld gap |
19 | 1 | zero if status bar isn't combined with item bar |
20 | 3 | status bar background sprite (1 byte category, 2 bytes index -1 if combined) |
23 | 3 | status bar foreground sprite (1 byte category, 2 bytes index -1 if combined) |
26 | 3 | inventory icon sprite (1 byte category, 2 bytes index) |
29 | 3 | inventory pressed icon sprite (1 byte category, 2 bytes index) |
32 | 4 | one byte each, inventory slot width, height, left, top |
36 | 5 | number of items font (1 byte style, 1 byte size, 3 bytes font index) |
41 | 3 | inventory slot background sprite (1 byte category, 2 bytes index) |
44 | 3 | inventory selected slot background sprite (1 byte category, 2 bytes index) |
47 | 3 | equipment slots background sprite (1 byte category, 2 bytes index) |
50 | 1 | number of progress bars (n) |
51 | n * 17 | progress bars, n * (1 type, 2 left, 2 top, 3 value attr, 3 max attr, 3 sprite, 3 bg) |
Here each entry is 4 bytes long, thus section length must be multiple of 4. Each entry is a coordinate on equipment slots background sprite (specified in section type 18).
Offset | Length | Description |
---|---|---|
0 | 2 | left |
2 | 2 | top |
Offset | Length | Description |
---|---|---|
0 | 5 | alert font (1 byte style, 1 byte size, 3 bytes font index) |
5 | 2 | alert display time (in 1/100th second units) |
7 | 2 | alert fade-in end time (in 1/100th second units) |
9 | 2 | alert fade-out start time (in 1/100th second units) |
11 | 1 | number of alerts (n, at least 3) |
12 | n * 10 | n * (4 bytes color, 3 bytes sound index, 3 bytes translateable text index) |
Offset | Length | Description |
---|---|---|
0 | 3 | background music index |
3 | 3 | background image sprite, 1 byte category, 2 bytes index |
6 | 4 | header text color |
10 | 5 | header text font (1 byte style, 1 byte size, 3 bytes font index) |
15 | 4 | names text color |
19 | 5 | names text font (1 byte style, 1 byte size, 3 bytes font index) |
This is followed by variable length records:
Offset | Length | Description |
---|---|---|
0 | 1 | category code ("g", "c", "d", "a", "f", "m", "s", "v", "t", "p") |
1 | 2 | number of authors in this category (n) |
3 | n * 3 | string indeces |
Here each entry is 16 bytes long, thus section length must be multiple of 16. Each entry points to a chooser asset in the file, which is compressed binary data. When uncompressed, each asset has a 10 bytes long header:
Offset | Length | Description |
---|---|---|
0 | 2 | chooser left margin |
2 | 2 | chooser top margin |
4 | 2 | chooser horizontal padding |
6 | 2 | chooser vertical padding |
8 | 2 | number of options in this chooser (n) |
This is followed by (n) chooser options, each 31 bytes:
Offset | Length | Description |
---|---|---|
0 | 1 | option position, 0 - 13, bit 7 means keep chooser running |
1 | 6 | if position 13, then 1 byte x1, y1, x2, y2; 3 * 2 bytes sprite index otherwise |
7 | 4 | 1 byte type, 3 bytes required attribute index |
11 | 4 | required value |
15 | 4 | 1 byte type, 3 bytes required attribute index |
19 | 4 | required value |
23 | 4 | 1 byte type, 3 bytes provided attribute index |
27 | 4 | provided value |
If position is less than 13, then sprite index chooses from UI sprites section. If it's 13, then a rectangle with percentage coordinates is stored, and the last 2 bytes are zero.
If type is greater than 1, then it encodes a local variable (2 = a, 3 = b, ... 27 = z). If it's zero (or 1, but that should not be here), then attribute index with offset to strings section is used. If the offset is zero, then required or provided attribute is not set.
Here each entry is 16 bytes long, thus section length must be multiple of 16. Each entry points to a cutscene asset in the file, which is compressed binary data. When uncompressed, each asset has a 21 bytes long header:
Offset | Length | Description |
---|---|---|
0 | 3 | play once movie index |
3 | 3 | play once music index |
6 | 3 | play in a loop movie index |
9 | 3 | play in a loop music index |
12 | 2 | number of slide records |
14 | 2 | number of speech records |
16 | 2 | number of subtitle records |
18 | 3 | script bytecode length |
This is followed by the specified amount of slide records, speech records, subtitle records and a script bytecode, in this order.
Slide records (19 bytes each):
Offset | Length | Description |
---|---|---|
0 | 3 | start time (in 1/100th seconds) |
3 | 3 | fade-in time |
6 | 3 | fade-out time |
9 | 3 | end time |
12 | 4 | slide color |
16 | 3 | slide image sprite, 1 byte category, 2 bytes index |
Speech records (6 bytes each):
Offset | Length | Description |
---|---|---|
0 | 3 | start time (in 1/100th seconds) |
3 | 3 | speech audio index |
Subtitle records (27 bytes each):
Offset | Length | Description |
---|---|---|
0 | 3 | start time (in 1/100th seconds) |
3 | 3 | fade-in time |
6 | 3 | fade-out time |
9 | 3 | end time |
12 | 4 | text color |
16 | 3 | text position, x, y (in percentage) and align (0: left, 1: center, 2: right) |
19 | 5 | text font (1 byte style, 1 byte size, 3 bytes font index) |
24 | 3 | text index |
For the script bytecode, see script assets below.
Here each entry has 16 bytes records, this section length must be multiple of 16. Each entry points to a script bytecode asset (see script assets below). The first script runs once when the game runs, the others are executed again in regular intervals. The records must be sorted by ascending interval value order (smaller interval first).
Offset | Length | Description |
---|---|---|
0 | 8 | asset offset (relative to compressed sections' end in file) |
8 | 4 | asset length |
12 | 4 | re-run interval in 1/100th of seconds |
NOTE: although looks like a normal asset descriptor, the name encodes the time intervals, and not the asset's name.
Starts with a 19 bytes long header:
Offset | Length | Description |
---|---|---|
0 | 2 | free-form mode total points |
2 | 2 | free-form mode maximum points per attribute |
4 | 1 | number of engine specific attribute definitons (4) |
5 | 3 | light radius attribute index |
8 | 3 | weight attribute index |
11 | 3 | speed attribute index |
14 | 3 | level up points attribute index |
17 | 2 | number of attributes (n) |
This is followed (n) varying length attribute records, the record format depends on the first byte.
Offset | Length | Description |
---|---|---|
0 | 1 | 0, primary attribute |
1 | 3 | internal attribute name, string index |
4 | 3 | title of the attribute, text index |
Offset | Length | Description |
---|---|---|
0 | 1 | 1, character variable attribute or 3, global variable attribute |
1 | 3 | internal attribute name, string index |
4 | 3 | title of the attribute, text index |
7 | 4 | default value |
11 | 3 | on change bytecode length (b) |
14 | b | on change event handler bytecode |
For the script bytecode, see script assets below.
Offset | Length | Description |
---|---|---|
0 | 1 | 2, character calculated attribute or 4, global calculated attribute |
1 | 3 | internal attribute name, string index |
4 | 3 | title of the attribute, text index |
7 | 2 | expression bytecode length (b) |
9 | b | expression bytecode |
For the script bytecode, see script assets below.
This section always has fix number of records (9, see number of default action handlers in the header), each 16 bytes long. Each entry points to a script bytecode asset (see script assets below).
Offset | Length | Description |
---|---|---|
0 | 8 | asset offset (relative to compressed sections' end in file) |
8 | 4 | asset length |
12 | 4 | range attribute index |
NOTE: although looks like a normal asset descriptor, the name encodes the range attribute, and not the asset's name.
Here each entry is 16 bytes long, thus section length must be multiple of 16. Each entry points to a character option group asset in the file, which is compressed binary data. When uncompressed, each asset has a 7 byte long header:
Offset | Length | Description |
---|---|---|
0 | 3 | option group name text index |
3 | 3 | palettes sprite, 1 byte sprite category, 2 bytes sprite index |
6 | 1 | number of options in this group (n) |
This is followed by (n) option records:
Offset | Length | Description |
---|---|---|
0 | 3 | option name text index |
3 | 3 | option description text index |
6 | 1 | number of attributes (a) |
7 | 1 | number of inventory items (i) |
8 | 1 | number of animation sprites (s) |
9 | a * 8 | attributes, each 1 byte relation, 3 bytes attribute index, 4 bytes value |
x | i * 4 | inventory items, each 1 byte quantity, 1 byte category, 2 bytes object sprite index |
x | s * 3 | animation sprites, each 1 byte sprite category, 2 bytes sprite index |
If the project have used multiple layers for sprites, those are merged and saved as a single layer. The last one is the portrait sprite.
Here each entry is 16 bytes long, thus section length must be multiple of 16. Each entry points to an NPC asset in the file, which is compressed binary data. When uncompressed, each asset has a 13 bytes long header, followed by data:
Offset | Length | Description |
---|---|---|
0 | 3 | NPC title (name) text index |
3 | 1 | default behaviour (movement) type |
4 | 2 | speed percentage |
6 | 2 | direction change frequency in 1/100th second |
8 | 1 | partol path id |
9 | 1 | number of attributes (a) |
10 | 1 | number of inventory items (i) |
11 | 1 | number of animation sprites (s) |
12 | 1 | number of event handlers (e) |
13 | a * 8 | attributes, each 1 byte relation, 3 bytes attribute index, 4 bytes value |
x | i * 10 | inventory items, each 1 b chance, 3 b quantity, 3 b resupply freq, 3 b object index |
x | s * 3 | animation sprites, each 1 byte sprite category, 2 bytes sprite index |
x | e * x | event handlers |
If the project have used multiple layers for sprites, those are merged and saved as a single layer. The last one is the portrait sprite.
Chance is in percentage, resupply frequency is in minutes (not in 1/100th seconds!). Object index has the category 1 and a 2 bytes long sprite index.
Each event handler record looks like:
Offset | Length | Description |
---|---|---|
0 | 1 | event handler type (0 - 18) |
1 | 3 | parameter |
4 | 3 | event handler bytecode length (b) |
7 | b | event handler bytecode |
If type is 2 or 3, then parameter is a number, for 13, 14 and 15 it is an object sprite index (with category 2), for type 16 it is a timer interval code (0 = 1/10 sec, 1 = 1 sec, 2 = 10 sec, 3 = 20 sec, 4 = 30 sec, 5 = 1 min, 6 = 2 min, ... 12 = 1 hour, see project_eventtimers in project.c), otherwise it is zero. For the script bytecode, see script assets below.
Here each entry is 16 bytes long, thus section length must be multiple of 16. Each entry points to a spawner asset in the file, which is compressed binary data. When uncompressed, each asset has a 6 bytes long header:
Offset | Length | Description |
---|---|---|
0 | 1 | maximum number of NPCs handled by this spawner |
1 | 3 | respawn frequency in 1/100th second |
4 | 2 | number of spawned NPC kind records (n) |
This is followed by (n) NPC specification records:
Offset | Length | Description |
---|---|---|
0 | 3 | required player attribute string index |
3 | 1 | relation |
4 | 4 | required value |
8 | 3 | provided NPC attribute string index |
11 | 4 | provided value |
15 | 3 | spawning animation sprite, 1 byte sprite category, 2 bytes sprite index |
18 | 3 | NPC sprite, 1 byte sprite category, 2 bytes sprite index |
If the attribute string index is zero, then the corresponding required or provided attribute is not set.
Here each entry is 16 bytes long, thus section length must be multiple of 16. Each entry points to a dialog asset in the file, which is compressed binary data. When uncompressed, each asset has a 14 bytes long header:
Offset | Length | Description |
---|---|---|
0 | 3 | dialog title text index |
3 | 3 | dialog description text index |
6 | 3 | dialog description spoken speech index |
9 | 3 | dialog portrait, 1 byte sprite category, 2 bytes sprite index |
12 | 1 | 0: portrait on right, 1: portrait on left |
13 | 1 | number of dialog answer options (n) |
The dialog sprite index is special in this case: FFFF encodes no sprite as usual, but FFF0 to FFFE means one of the user's portrait with expression. FFF0 is the normal, FFF1 is the happy, FFF2 is the sad etc. There can be 15 different expressions, but currently only one is supported by the editor.
This is followed by (n) dialog answer option records:
Offset | Length | Description |
---|---|---|
0 | 3 | required attribute string index |
3 | 1 | relation |
4 | 4 | required value |
8 | 3 | provided attribute string index |
11 | 4 | provided value |
15 | 3 | dialog answer spoken speech index |
18 | 3 | dialog answer text index |
If the attribute string index is zero, then the corresponding required or provided attribute is not set.
Here each entry is 16 bytes long, thus section length must be multiple of 16.
Offset | Length | Description |
---|---|---|
0 | 8 | asset offset (relative to compressed sections' end in file) |
8 | 4 | asset length |
12 | 2 | object sprite index |
14 | 2 | must be zero |
NOTE: although looks like a normal asset descriptor, the name encodes a sprite index, and not the asset's name.
Each entry points to an object meta info asset in the file, which is compressed binary data. When uncompressed, each asset has a 39 bytes long header, followed by data:
Offset | Length | Description |
---|---|---|
0 | 3 | object title text index |
3 | 2 | collision mask width (w) |
5 | 2 | collision mask height (h) |
7 | 1 | default action group (select default action handler, 0xff if none) |
8 | 3 | in inventory sprite, 1 byte sprite category, 2 bytes sprite index |
11 | 3 | inventory placement sound index |
14 | 3 | base price |
17 | 2 | price unit, object sprite index |
19 | 1 | object category (freely choosable) |
20 | 3 | projectile sprite, 1 byte sprite category, 2 bytes sprite index |
23 | 3 | projectile animation duration (in 1/100th seconds) |
26 | 3 | projectile ammo, object index, 1 byte sprite category, 2 bytes sprite index |
29 | 1 | sprite layering order |
30 | 4 | equipment position bit mask (0xffffffff = skill) |
34 | 1 | number of sound effects (s) |
35 | 1 | number of required attributes (a) |
36 | 1 | number of attribute modifiers (m) |
37 | 1 | number of character option sprite modifiers (c) |
38 | 1 | number of event handlers (e) |
39 | w * h | collision mask, each slot 1 byte |
39+w*h | 96 | 32 times equipted sprites, 1 byte category, 2 bytes sprite index |
x | s * 3 | sound effects, each 3 byte index |
x | 32 * 3 | equipment slot sprites, each 1 byte sprite category, 2 bytes sprite index |
x | a * 8 | required attributes, each 1 byte relation, 3 bytes attribute index, 4 bytes value |
x | m * 8 | attribute modifiers, each 1 byte type, 3 bytes attribute index, 4 bytes value |
x | c * 123 | sprite modifiers, each 3 bytes charoption text index, 40 * (1+2) bytes sprite index |
x | e * x | event handlers |
For the attribute modifier type, bit 0: set if modifier only applies when equipted, bit 1: set if value is in percentage. If attribute index is zero, then value modifies the transportation method.
For the sprite modifiers, if charoption text index is 0xffffff then it is the default sprite modifier (applies to all character options). Otherwise there are as many records as modified character options. The first 20 are the behind character sprites (per action, 19 basic actions + 1 portrait), and the next 20 is for the above character sprites.
Each event handler record looks like:
Offset | Length | Description |
---|---|---|
0 | 1 | event handler type (0 - 16) |
1 | 3 | parameter |
4 | 3 | event handler bytecode length (b) |
7 | b | event handler bytecode |
If type is 2 or 3, then parameter is a number, for 13, 14 and 15 it is an object sprite index (with category 2), for type 16 it is a timer interval code (0 = 1/10 sec, 1 = 1 sec, 2 = 10 sec, 3 = 20 sec, 4 = 30 sec, 5 = 1 min, 6 = 2 min, ... 12 = 1 hour, see project_eventtimers in project.c), otherwise it is zero. For the script bytecode, see script assets below.
Here each entry is 16 bytes long, thus section length must be multiple of 16. Each entry points to a crafting dialog asset in the file, which is compressed binary data. When uncompressed, each asset has a 14 bytes long header:
Offset | Length | Description |
---|---|---|
0 | 3 | dialog title text index |
3 | 3 | dialog description text index |
6 | 3 | item crafted sound effect index |
9 | 3 | dialog portrait, 1 byte sprite category, 2 bytes sprite index |
12 | 1 | 0: ingredients order matters, 1: order irrelevant |
13 | 1 | number of recipes (n) |
This is followed by (n) crafting recipe records:
Offset | Length | Description |
---|---|---|
0 | 2 | produced quantity |
2 | 2 | product, 2 bytes object sprite index |
4 | 16*4 | ingredients (2 bytes required quantity, 2 bytes object sprite index) |
An ingredient slot might contain sprite index 0xffff, meaning no ingredient. All sprites belong to the objects category. Required quantity can be 0, meaning the item won't disappear from the player's inventory, but it is required during crafting (for example a pan).
Here each entry is 17 bytes long, thus section length must be multiple of 17. (More precisely, each entry is 2 + number of transportation methods * 5 bytes long, see header).
Offset | Length | Description |
---|---|---|
0 | 2 | tile sprite index (category 1) for which this meta belongs |
2 | 2 | on walk speed modifier (percentage) |
4 | 3 | on walk sound effect string index |
7 | 2 | on swim speed modifier (percentage) |
9 | 3 | on swim sound effect string index |
12 | 2 | on fly speed modifier (percentage) |
14 | 3 | on fly sound effect string index |
17 | 2 | on drive speed modifier (percentage) |
19 | 3 | on drive sound effect string index |
22 | 2 | on X speed modifier (percentage) |
25 | 3 | on X sound effect string index |
Percentage is stored on two bytes, because it can be larger than a byte, for example 300% (3 times the average).
Here each entry is 16 bytes long, thus section length must be multiple of 16. Each entry points to a quest asset in the file, which is compressed binary data. When uncompressed, each asset has a 10 bytes long header:
Offset | Length | Description |
---|---|---|
0 | 1 | 0: every player can complete, 1: global quest, only one player can complete |
1 | 3 | quest title text index |
4 | 3 | quest description text index |
7 | 3 | on complete bytecode length (b) |
10 | b | on complete event handler bytecode |
Here each entry is 16 bytes long, thus section length must be multiple of 16. Each entry points to a map asset in the file, which is compressed binary data. When uncompressed, each asset has a 34 bytes long header:
Offset | Length | Description |
---|---|---|
0 | 3 | map title (name), text index |
3 | 6 * 3 | neightbour maps string index |
21 | 2 | daylight length (in minutes) |
23 | 3 | daylight sprite, 1 byte sprite category, 2 bytes sprite index |
26 | 3 | background music index |
29 | 1 | number of parallaxes (p) |
30 | 1 | number of the patrol paths (n) |
31 | 3 | size of the RLE compressed map block (uncompressed numlayer * mapsize * mapsize * 2) |
This is followed by (p) parallax record, each 10 bytes long:
Offset | Length | Description |
---|---|---|
0 | 1 | 0: foreground parallax, 1: background parallax |
1 | 1 | speed x (time based delta x, -100 to 100 it is signed!) |
2 | 1 | delta x (player movement based, 0 to 100) |
3 | 1 | speed y (time based delta y, -100 to 100 it is signed!) |
4 | 1 | delta y (player movement based, 0 to 100) |
5 | 2 | time interval (in 1/100th seconds) |
7 | 3 | parallax sprite, 1 byte sprite category, 2 bytes sprite index |
Followed by (n) patrol paths, varying length records:
Offset | Length | Description |
---|---|---|
0 | 1 | patrol path id (0 - 98) |
1 | 2 | patrol path length (l) |
3 | l * 4 | path elements (in map coordinates), each 2 bytes x, 2 bytes y |
Followed by the map layers, 2 bytes sprite indices without category. 0xffff means not set. The category for the layers are fixed: layer 5 NPCs (section 28), layer 6 spawners (section 29), all the other layers tiles (section 8). This is RLE compressed, 1 byte header (n, bit 7 means repeat), followed by 2 bytes if its a repeat block, otherwise (n + 1) * 2 bytes. The RLE packets do not stop at layer boundaries.
Neightbours are listed differently depending on game type (see header), but basically starting South and counter-clockwise.
The compressed sections block is followed by inlined assets, each with a size that appears in their asset descriptors in the sections. Except for the assets in some standard format (which is already compressed), the assets defined by this specification are always deflate compressed. The order of the assets is irrelevant, except map assets MUST be the last.
These are in Scalable Screen Font format.
Always in OGG Vorbis format, 44.1 kHz, stereo channels. See sample frequency in header.
Always in OGG Vorbis format, 44.1 kHz, mono channels. See sample frequency in header.
Always in OGV Theora format. Pixels in planar IYUV420 format. If it has audio, then that must be in Vorbis format, 44100 Hz stereo channels (unless sample frequency specified otherwise in the header).
Always in PNG format with alpha channel. If possible, then in indexed format, otherwise in true-color.
See User Interface Sprites (section type 7) for details.
See Maps section (section type 34) for details. These must be the last in the file.
Several assets, most notably the script assets contain machine independent bytecode to describe event handlers. This is encoded as a series of varying length instructions. In each instruction the first byte is an opcode, the others are opcode dependent arguments.
OpCode | Mnemonic | Arguments (bytes) | Description |
---|---|---|---|
0 | END | - | End of Program |
1 | SWITCH | 1 (n), n * 3 | Switch, n + 1 branch with addresses (first is implicit) |
2 | JMP | 3 (address) | Jump to address |
3 | JZ | 3 (address) | Pop from stack and jump to address if zero |
4 | FNC0/CALL | 1 (cmd) | Call command function with zero arguments on stack |
5 | FNC1/CALL | 1 (cmd) | Call command function with one argument on stack |
6 | FNC2/CALL | 1 (cmd) | Call command function with two arguments on stack |
7 | FNC3/CALL | 1 (cmd) | Call command function with three arguments on stack |
8 | CNT0 | - | Push the total number of items in inventory |
9 | CNT1 | 2 (obj sprite idx) | Push the number of a specific object in inventory |
10 | SUM | 3 (attr idx) | Push the sum of attributes of objects in inventory |
11 | CNTO0 | - | Push the total number of items in opponent's inventory |
12 | CNTO1 | 2 (obj sprite idx) | Push the number of a specific object in oppo's inventory |
13 | SUMO | 3 (attr idx) | Push the sum of attributes of objects in oppos' inventory |
14 | RND | 4 (constant) | Push a random value between 0 and constant to stack |
15 | MIN | 1 (n) | Pop (n) values from stack, and push the smallest back |
16 | MAX | 1 (n) | Pop (n) values from stack, and push the biggest back |
17 | ADD | - | Pop two values from stack, and push addition result |
18 | SUB | - | Pop two values from stack, and push substract result |
19 | MUL | - | Pop two values from stack, and push multiplication result |
20 | DIV | - | Pop two values from stack, and push divider result |
21 | MOD | - | Pop two values from stack, and push remainder result |
22 | EQ | - | Pop two values from stack, and push 0/1 if they are equal |
23 | NE | - | Pop two values from stack, and push 0/1 if they are not equal |
24 | GE | - | Pop two values from stack, and push 0/1 if greater or equal |
25 | GT | - | Pop two values from stack, and push 0/1 if greater than |
26 | LE | - | Pop two values from stack, and push 0/1 if less or equal |
27 | LT | - | Pop two values from stack, and push 0/1 if less than |
28 | NOT | - | Pop one value from stack, and push 0/1 if its zero |
29 | OR | - | Pop two values from stack, and push logical or result |
30 | AND | - | Pop two values from stack, and push logical and result |
31 | POP | 1 (local var) | Pop into a local variable |
32 | POPA | 3 (attr idx) | Pop into an attribute |
33 | POPO | 3 (attr idx) | Pop into opponent's attribute |
34 | PUSH | 1 (local var) | Push a local variable |
35 | PUSHA | 3 (attr idx) | Push an attribute |
36 | PUSHO | 3 (attr idx) | Push opponent's attribute |
37 | PUSH8 | 1 (constant) | Push a 8-bit constant |
38 | PUSH16 | 2 (constant) | Push a 16-bit constant |
39 | PUSH24 | 3 (constant) | Push a 24-bit constant |
40 | PUSH32 | 4 (constant) | Push a 32-bit constant |
41 | PUSHMAP | 3 (idx) + 2 + 2 | Push map reference, string index, x and y in map coordinates |
42 | PUSHSPR | 1 + 2 | Push sprite reference, category, index |
43 | PUSHMUS | 3 (music idx) | Push a music reference |
44 | PUSHSND | 3 (sound idx) | Push a sound reference |
45 | PUSHSPC | 3 (speech idx) | Push a speech reference |
46 | PUSHCHR | 3 (chooser idx) | Push a chooser reference |
47 | PUSHCUT | 3 (cutscn idx) | Push a cutscene reference |
48 | PUSHDLG | 3 (dialog idx) | Push a dialog reference |
49 | PUSHCFT | 3 (craft idx) | Push a crafting ruleset reference |
50 | PUSHQST | 3 (quest idx) | Push a quest reference |
51 | PUSHTXT | 3 (text idx) | Push a translatable text reference |
The command functions are defined in ui_cmd.c, in the
cmd_types
array. One can add new commands to it without modifying the other parts of the source code, bytecode will be
generated automatically for these new functions, and the visual script editor will handle them correctly too.
Cmd | Command | Arguments (on stack) | Description |
---|---|---|---|
0-4 | - | - | Reserved |
5 | exit | - | Exit game, return to main menu |
6 | delay | hsec | Delay execution (in 1/100th of seconds) |
7 | music | music | Replace background music |
8 | sound | sound | Play sound effect |
9 | speak | speech | Play translatable speech |
10 | chooser | chooser | Run a chooser |
11 | cutscn | cutscene | Play a cutscene |
12 | dialog | dialog,hsec | Display a dialog for the given time (in 1/100th of seconds) |
13 | location | map,direction | Change player's location (map argument includes x,y) |
14 | quest | completed,quest | Add a quest or mark one as completed |
15 | canim | action,hsec | Play character action animation for given time |
16 | oanim | object sprite,hsec | Play object animation for given time |
17 | npc | map,npc | Place an NPC on map (also selects it as an actor) |
18 | actor | npc | Select an NPC |
19 | behave | behaviour | Change NPC's behaviour |
20 | market | - | Open NPC's inventory for exchange |
21 | south | number | Move (or turn if number is zero) number map slots in direction |
22 | southwest | number | Move (or turn if number is zero) number map slots in direction |
23 | west | number | Move (or turn if number is zero) number map slots in direction |
24 | northwest | number | Move (or turn if number is zero) number map slots in direction |
25 | north | number | Move (or turn if number is zero) number map slots in direction |
26 | northeast | number | Move (or turn if number is zero) number map slots in direction |
27 | east | number | Move (or turn if number is zero) number map slots in direction |
28 | southeast | number | Move (or turn if number is zero) number map slots in direction |
29 | remove | Remove current object from map | |
30 | drop | object sprite | Drop object to map at current object's position |
31 | delete | map | Remove object from map (map argument includes x,y) |
32 | add | map,object sprite | Place object on map at a given location |
33 | replace | object sprite | Replace object on map at current location |
34 | give | object sprite,amount | Give object(s) to player |
35 | take | object sprite,amount | Take away object(s) from player |
36 | event | object sprite | Call another object's "on click" event handler |
37 | transport | method | Change player's transportation method (walk, swim, fly, drive) |
38 | scene | map,daytime | Select scene for scripted cutscenes (no actors involved) |
39 | waitnpc | - | Wait until the selected actor finishes with their tasks |
40 | craft | craftruleset | Display an object crafting dialog |
41 | match | map,object sprite | Sets a to 1 if the object at location is the given one |
42 | alert | alert | Display a custom alert message |
43 | altitude | number | Change altitude (z buffer display order) |
Map arguments use two values on the stack which are popped together, combined x,y coordinates value and a map id (see PUSHMAP).
A few things about object related commands: 29 and 30 is almost the same as 31 and 32, the difference is, the former two can only be used in an object event handler, and refers to the current object. The latter two has a map argument. Furthermore, while 30 and 33 uses a configured object (with meta info and translatable in-game name) as argument, 32 works with any sprite in the "objects" category.
To quickly decide if a player can decode the scripts in a game file, the largest (cmd) is recorded in the header. This means that future versions of the editor will automatically generate backward compatible game files if those new functions aren't used in a game.
Dialogs are a bit special. Depending wether the bytecode is in a cutscene script or not, it acts differently: in a cutscene the provided hsec is used and no answer options allowed. For everywhere else, hsec doesn't matter, it requires user's interaction to dismiss a dialog and dialogs might have answer options too. The hsec argument is stored regardless, because a player might use it with answer option-less dialogs if it implements some kind of "auto mode" feature.