MegaZeux File Format Reference

MegaZeux 2.92f — November 22nd, 2020

This guide contains specifications for most MegaZeux file formats.


Contents

  1. Key for data types
  2. Character Set (.CHR)
  3. Palette Formats
    1. Palette (.PAL)
    2. Palette Indices (.PALIDX)
  4. World and Save Format (all versions)
    1. World Format Header and Magic
    2. Save Format Header and Magic
  5. World and Save Format (2.00 through 2.84X)
    1. Encryption
    2. Global Data
      1. World Block 1
      2. Save Block 1 (.SAV only)
      3. World Block 2
      4. Save Block 2 (.SAV only)
      5. Counters List (.SAV only)
      6. Strings List (2.80X through 2.84X) (.SAV only)
      7. Sprites List (2.65 through 2.84X) (.SAV only)
      8. Strings List (2.68 through 2.70) (.SAV only)
      9. Math and File IO (2.68 through 2.84X) (.SAV only)
      10. SMZX Data and Commands (2.69 through 2.84X) (.SAV only)
      11. Vlayer (2.69c through 2.84X) (.SAV only)
    3. Custom SFX Table
    4. Board Names, Sizes, and Offsets
    5. Boards
    6. Board RLE Planes
    7. Board Parameters (2.00 through 2.82X)
    8. Board Parameters (2.83 and 2.84X)
    9. Board Objects
    10. Robots
    11. Scrolls
    12. Sensors
  6. World and Save Format (2.90 onward)
    1. Files List
    2. Properties Format
    3. World Properties
    4. Custom SFX Table
    5. Character Sets
    6. Palette, Palette Indices, and Palette Intensities
    7. Vlayer Planes
    8. Sprite Properties (.SAV only)
    9. Counter and String Lists (.SAV only)
    10. Board Properties
    11. Board Planes
    12. Robot Properties
    13. Scroll Properties
    14. Sensor Properties
  7. Board File (.MZB)
  8. Image File (.MZM)
    1. MZM3 (current)
    2. MZM2
    3. MZMX
  9. Counters File
  10. Robotic Bytecode (.BC)
  11. License

Key for data types

The data types used to describe the MZX file formats are as follows:

| Data Type | Description | Min. | Max. | |-----------|-----------------------------------------|-------------|------| | b | byte (8-bit) | 0 | 255 | bs | byte (8-bit, signed) | -128 | 127 | w | word (16-bit, little endian) | 0 | 65535 | ws | word (16-bit, little endian, signed) | -32768 | 32767 | d | dword (32-bit, little endian) | 0 | 4294967295 | ds | dword (32-bit, little endian, signed) | -2147483648 | 2147483647 | s# | string of fixed length # (i.e. # bytes) | | s... | string of variable length |

Character Set (.CHR)

This file format describes one or more MZX graphical characters. A CHR file contains raw graphical data only. Each character in the CHR file consists of 14 bytes representing each graphical row of the character.

Within the byte, each pixel of the row is encoded either as a single bit (MZX) or as two bits (SMZX). The first byte of the next character immediately follows the 14th byte of the previous character.

A typical character set file contains 256 chars (for a total size of 3584). However, partial character sets are possible, as are character sets with more than 256 chars. Complete and partial character sets can be loaded from files or from MZX strings with the load char set command. The bytes representing a character in a character set are also the same as the values provided to the char edit command or to the CHAR_BYTE counter.

MZX

The most significant bit of the byte represents the leftmost pixel in the row and the least significant bit of the byte represents the rightmost pixel.

Example: setting the third, fifth, and seventh pixels.

| 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 | |-----|-----|-----|-----|-----|-----|-----|-----| | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
| | | |---------|-----| | Binary | `00101010` | Hex | `2A` | Decimal | (128 * 0) + (64 * 0) + (32 * 1) + (16 * 0) +
(8 * 1) + (4 * 0) + (2 * 1) + (1 * 0) = 42.

SMZX

The most significant two bits of the byte represent the leftmost pixel in the row and the least significant two bits represent the rightmost pixel. The four colors are encoded as the following bit pairs and quaternary digits:

| Color | Binary | Quaternary | |-------|--------|------------| | 1 | `00` | `0` | 2 | `01` | `1` | 3 | `10` | `2` | 4 | `11` | `3`

Example: setting the first and second pixels to color 2, third to color 4, and fourth to color 1.

| 64 | 16 | 4 | 1 | |-----|-----|-----|-----| | `1` | `1` | `3` | `0` |
| | | |------------|------| | Quaternary | `1130` | Binary | `01011100` | Hex | `5C` | Decimal | (64 * 1) + (16 * 1) + (4 * 3) + (1 * 0) = 92

Palette Formats

Palette (.PAL)

This file format describes an MZX palette. A PAL file contains raw palette data only. Each color in the PAL file consists of 3 bytes representing the red, green, and blue components of the color. Each RGB component of a color is a value from 0 to 63 (inclusive).

A typical MZX or SMZX mode 1 PAL file contains 16 colors (48 bytes) and a typical SMZX mode 2 or mode 3 pal file contains 256 colors (768 bytes).

The PAL file does not encode any SMZX 3 color index information (see PALIDX).

Examples

| Color | R | G | B | Hex representation in-file: | |---------------|-----|-----|-----|-----------------------------| | Black | 0 | 0 | 0 | `00 00 00` | Dark Blue | 0 | 0 | 42 | `00 00 2A` | Dark Green | 0 | 42 | 0 | `00 2A 00` | Dark Cyan | 0 | 42 | 42 | `00 2A 2A` | Dark Red | 42 | 0 | 0 | `2A 00 00` | Dark Magenta | 42 | 0 | 42 | `2A 00 2A` | Brown | 42 | 21 | 0 | `2A 15 00` | Light Grey | 42 | 42 | 42 | `2A 2A 2A` | Dark Grey | 21 | 21 | 21 | `15 15 15` | Light Blue | 21 | 21 | 63 | `15 15 3F` | Light Green | 21 | 63 | 21 | `15 3F 15` | Light Cyan | 21 | 63 | 63 | `15 3F 3F` | Light Red | 63 | 21 | 21 | `3F 15 15` | Light Magenta | 63 | 21 | 63 | `3F 15 3F` | Yellow | 63 | 63 | 21 | `3F 3F 15` | White | 63 | 63 | 63 | `3F 3F 3F`

Palette Indices (.PALIDX)

This file format describes SMZX mode 3 subpalette indices. A PALIDX file contains 256 sets of indices (one for each subpalette). Each set of indices is a sequence of four bytes (for a total of 1024 bytes per file); the first indicates color 1 of the subpalette, the second color 2, and so on.


World and Save Format (all versions)

This section documents the portions of the world and save formats that are consistent between all MZX versions.

World Format Header and Magic

Most world files begin with a 29-byte header. The only exceptions are encrypted worlds from MegaZeux versions prior to 2.6 (see Encryption) and rearchived MegaZeux 2.90 worlds (see the 2.90+ world format).

| Pos. | Size | Description | |------|------|-------------| | 0 | s25 | World title (1) (2) | 25 | b | Protection method (always zero) | 26 | s3 | Magic

The following magic values are valid:

| Magic | World/File format version | |-------------|---------------------------| | `MZX` | 1.00 | `MZ2` | 2.00 through 2.51 | `MZA` | 2.51s1 | `M\x02\x09` | 2.51s2 through 2.61 (including MZXAk, SMZX 1.00a) | `M\x02\x11` | Used for decrypted worlds by newer MZX versions.
Likely derived from misreading 2.51s2 magic M\002\011 (octal) as hex. | `M\x02\x32` | 2.62 and 2.62b (from octal M\002\062) | `M\x02\x41` | 2.65 (from decimal 65) | `M\x02\x44` | 2.68 (from decimal 68) (3) | `M\x02\x45` | 2.69 (from decimal 69) | `M\x02\x46` | 2.69b | `M\x02\x48` | 2.69c | `M\x02\x49` | 2.70 | `M\x02\x50` | 2.80X (from decimal 80) (first port releases) | `M\x02\x51` | 2.81X (and so on...) | `M\x02\x52` | 2.82X | `M\x02\x53` | 2.83 | `M\x02\x54` | 2.84X | `M\x02\x5A` | 2.90X | `M\x02\x5B` | 2.91X | `M\x02\x5C` | 2.92X

The internal MZX version value is derived from the world magic by 1) treating the last two bytes as a big-endian word if the magic is from 2.51s2 or later or 2) using 0x0100, 0x0205, and 0x0208 for 1.00, 2.00, and 2.51s1 worlds (respectively).

Notes:

  1. The world title is a duplicate of the name of the title board.
  2. The world title is null terminated within its fixed buffer.
  3. The MZX 2.68 magic was erroneously never saved with world files, meaning worlds from MZX 2.68 relying on MZX 2.68 features won't work (as they are versioned for MZX 2.65). Resaving these worlds in a newer version (such as MZX 2.69) usually fixes this issue.

Save Format Header and Magic

Save files use a different header from world files, but the idea is generally the same.

| Pos. | Size | Description | |------|------|-------------| | 0 | s5 | Magic | 5 | w | World version (separate from file format version) | 7 | b | Current board number
| Magic | File Format Version | |---------------|---------------------| | `MZSV2` | 2.00 through 2.51 | `MZXSA` | 2.51s1 | `MZS\x02\x09` | 2.51s2 through 2.61 (including MZXAk, SMZX 1.00a) | `MZS\x02\x32` | 2.62 and 2.62b | `MZS\x02\x41` | 2.65 | `MZS\x02\x44` | 2.68 | `MZS\x02\x45` | 2.69 | `MZS\x02\x46` | 2.69b | `MZS\x02\x48` | 2.69c | `MZS\x02\x49` | 2.70 | `MZS\x02\x50` | 2.80X (first port releases) | `MZS\x02\x51` | 2.81X | `MZS\x02\x52` | 2.82X | `MZS\x02\x53` | 2.83 | `MZS\x02\x54` | 2.84X | `MZS\x02\x5A` | 2.90X | `MZS\x02\x5B` | 2.91X | `MZS\x02\x5C` | 2.92X

The internal MZX file format version is derived from the save magic the same way it is derived from the world magic, and is always the same as the MZX version that produced the save file.

The world version field (saved separately here) is always the same as the world version derived from the world magic when the world was initially loaded. Notably, this value is saved as a little endian word (whereas it is represented as a big endian word in the world magic string).


World and Save Format (2.00 through 2.84X)

MegaZeux 2.00 world (.MZX) and save (.SAV) files share very similar formats, so they are documented together here. This section also tries to note changes in these formats and when they occurred, but save format modifications between versions are somewhat of a mess, so please report any inaccuracies in this document.

Encryption

Encrypted worlds (from versions that supported them) begin with a 44-byte header similar to the regular header:

| Pos. | Size | Description | |------|------|-------------| | 0 | s25 | World title | 25 | b | Protection method (always non-zero) | 26 | s15 | Password (encrypted) (1) | 41 | s3 | Magic (not encrypted)

The password can be decrypted with the following algorithm:

/** * This code is copied from the MZX source code, which is GPL 2+ licensed. */ char magic_code[16] = "\xE6\x52\xEB\xF2\x6D\x4D\x4A\xB7\x87\xB2\x92\x88\xDE\x91\x24"; for(i = 0; i < 15; i++) { password[i] ^= magic_code[i]; password[i] -= 0x12 - protection_method; password[i] ^= 0x8D; }

The rest of the MZX file follows XORed with a byte calculated from the password. In practice, this byte can be determined by using the character set starting at the 44th byte of the encrypted world, the ID chars space damage value at the 3955th byte of the encrypted world (which is almost always 0 unencrypted) or by using recognizable colors in the palette (offsets 4197 through 4244).

Notes

  1. The password field is null terminated if it is 14 or fewer characters long. If the password is 15 characters long, it is NOT null terminated, and the null terminator must be added by the decryption algorithm.

Global Data

World data immediately follows the header. For ease of readability, it has been broken down into several blocks. Each block starts immediately after the previous block ends.

World Block 1

Total block length is 4129 bytes.

| Pos. | Size | Description | |-------|----------|-------------| | 0 | b * 3584 | Character set (see Character Sets) | 3584 | b * 128 | ID Chars block 1 (thing chars) | 3712 | b * 195 | ID Chars block 2 (animations and colors) | 3907 | b | ID Chars missile color | 3908 | b * 3 | ID Chars bullet colors | 3911 | b * 128 | ID Chars block 3 (damage) | 4039 | s15 * 6 | Status counter names (null terminated)

Save Block 1 (.SAV only)

This block is only present in save files. Total block length is 73 bytes plus the length of the current playing mod name.

| Pos. | Size | Description | |-------|-------|-------------| | 0 | b * 16| Keys | 16 | b | Blind timer | 17 | b | Firewalker timer | 18 | b | Freeze time timer | 19 | b | Slow time timer | 20 | b | Wind timer | 21 | w * 8 | Saved player X positions | 37 | w * 8 | Saved player Y positions | 53 | b * 8 | Saved player board numbers | 61 | b | Player color | 62 | b * 3 | Under player ID/color/param (1) | 65 | b | Message edges enabled (1) or disabled (0) | 66 | b | Scroll base color | 67 | b | Scroll corner color | 68 | b | Scroll pointer color | 69 | b | Scroll title color | 70 | b | Scroll arrow color | 71 | w | Mod playing length (2) | 73 | s... | Mod playing (NOT null terminated) (2)
Notes
  1. The under player fields are used as an extra "layer" beneath where the player is standing. This is so e.g. the player can be placed on top of something that is on top of a floor and the floor won't be erased when the thing immediately beneath the player is pushed to the under layer. These fields are effectively unused though, because there is a duplicate in the second save block that is always loaded over these.
  2. Prior to MZX 2.83, the mod playing length and mod playing fields were a single 13 byte null terminated buffer (total block length 84 bytes). Prior to MZX 2.51s2, the current playing mod was not saved at all (as it was always the same as the current board mod) (total block length 71 bytes).

World Block 2

Total block length is 72.

| Pos. | Size | Description | |-------|------------|-------------| | 0 | b | Viewport edge color | 1 | b | First board number | 2 | b | Endgame board number (255: no endgame board) | 3 | b | Death board number (254: same position, 255: restart board) | 4 | w | Endgame board teleport X | 6 | w | Endgame board teleport Y | 8 | b | Game over SFX enabled (1) or disabled (0) | 9 | w | Death board teleport X | 11 | w | Death board teleport Y | 13 | ws | Starting lives | 15 | ws | Lives limit | 17 | ws | Starting health | 19 | ws | Health limit | 21 | b | Enemies' bullets hurt other enemies | 22 | b | Clear messages and projectiles on exit | 23 | b | Can only play world from a SWAP WORLD | 24 | b * 3 * 16 | Palette (see Palettes)

Save Block 2 (.SAV only)

This block is only present in save files has a length of 24 bytes.

| Pos. | Size | Description | |------|---------|-------------| | 0 | b * 16 | Palette intensities (1) | 16 | b | Faded state (1: faded out, 0: faded in) | 17 | w | Player restart X | 19 | w | Player restart Y | 21 | b * 3 | Under player ID/color/param (2)
Notes
  1. Only the first 16 palette intensities were ever saved for SMZX modes 2 and 3.
  2. Yes, this is duplicate info from the first save block, and no, I don't know why MZX saved this info twice. This copy is always loaded over the copy in save block 1.

Counters List (.SAV only)

WARNING: The format of the save blocks following this frequently changed from version to version. If there are any inaccuracies in the legacy world format documentation, they're probably below. MZX versions prior to 2.90 regularly dropped support for saves from older MZX versions due to the format changes in this part of the format being too messy to support. Difficulty in supporting changes to the world/save format are one of the main reasons the world format was replaced in MZX 2.90.

This block is only present in save files and has an variable length. The counters list begins with the number of counters.

| Pos. | Size | Description | |------|---------|-------------| | 0 | (1) | Number of counters = N

A list of N counters follows, each in the following format in MZX versions 2.00 through 2.80X:

| Pos. | Size | Description | |------|------|-------------| | 0 | s15 | Counter name (null terminated) | 15 | ws | Counter value (2.80X saves this as a ds instead)

In MZX versions 2.81X and up, the counters are instead stored in the following format:

| Pos. | Size | Description | |------|------|-------------| | 0 | ds | Counter value | 4 | d | Name length | 8 | s... | Counter name (NOT null terminated)
Notes
  1. In MZX 2.51 and below, the maximum number of counters saved was 59, and the number of counters was saved as a byte. In DOS versions afterward, the maximum was 1020, and the number of counters was saved as a word (2 bytes). In MZX 2.80 to 2.84X, the maximum number of counters was lifted, and the number of counters was saved as a dword (4 bytes).
  2. Save files created by MZX 2.84c will save two additional "counters" for MZX_SPEED and the MZX_SPEED locked status. These are named "mzx_speed" and "_____lock_speed". This vile hack was made unnecessary in MZX 2.90 by the new world format.

Strings List (2.80X through 2.84X) (.SAV only)

The strings block in versions 2.80X through 2.84X follows the counters block and is stored in a similar way.

| Pos. | Size | Description | |------|---------|-------------| | 0 | d | Number of strings = N

A list of N string definitions follows. In 2.80X the strings were saved in the following format:

| Pos. | Size | Description | |------|---------|-------------| | 0 | s15 | String name (null terminated) | 15 | s64 | String value (null terminated)

In versions 2.81b and onward (1), the following structure is used instead:

| Pos. | Size | Description | |-------|---------|-------------| | 0 | d | String name length = X | 4 | d | String value length = Y | 8 | b * X | String name (NOT null terminated) | 8 + X | b * Y | String value (NOT null terminated)
Notes
  1. MegaZeux 2.81 tries to load strings in the format provided above but saves them in a different format unique to this release. Combined with no format validation at the time, the result was that 2.81 saves with strings always crashed on load.

Sprites List (2.65 through 2.84X) (.SAV only)

In versions that support sprites, the sprites list begins with N sprite definitions, where N = 64 for MZX 2.65 through 2.69b and N = 256 for MZX 2.69c and all releases afterward.

| Pos. | Size | Description | |------|------|-------------| | 0 | w | Sprite X | 2 | w | Sprite Y | 4 | w | Sprite reference X | 6 | w | Sprite reference Y | 8 | b | Sprite color | 9 | b | Sprite flags (1) | 10 | b | Sprite width | 11 | b | Sprite height | 12 | bs | Sprite collision X | 13 | bs | Sprite collision Y | 14 | b | Sprite collision width | 15 | b | Sprite collision height

The sprite data is followed by the following global sprite variables:

| Pos. | Size | Description | |------|----------|-------------| | 0 | b | Total number of active sprites | 1 | b | Sprite Y order enabled (1) or disabled (0) | 2 | w | Current number of sprite collisions

The sprite collision list follows. In MZX 2.65 through 2.69b it is stored as follows:

| Pos. | Size | Description | |------|----------|-------------| | 4 | bs * N | Sprite collision list (2.65: N=32, 2.68 through 2.69b: N=64)

From MZX 2.69c onward, the sprite collision list is saved as words and is always 256 in length:

| Pos. | Size | Description | |------|----------|-------------| | 4 | ws * 256 | Sprite collision list
Notes
  1. Sprite flags:
    | Flag | Description | |--------|-------------| | `0x01` | Sprite is initialized | `0x02` | Sprite ccheck 1 is enabled | `0x04` | Sprite is drawn over the overlay | `0x08` | Sprite uses reference colors | `0x10` | Sprite is static relative to the viewport | `0x20` | Sprite ccheck 2 is enabled | `0x40` | Sprite references the vlayer

Strings List (2.68 through 2.70) (.SAV only)

Strings in DOS MZX versions do not have names and are instead numbered from 1 to 16. They are saved as fixed length buffers after the sprites list (rather than before it as in port versions). The length of this block is always 16*64=1024 (these weren't saved in versions with 16 char strings).

| Pos. | Size | Description | |------|-------------|-------------| | 0 | b * 16 * 64 | Strings data (16 strings, fixed length null terminated values)

Math and File IO (2.68 through 2.84X) (.SAV only)

MZX versions 2.68 through 2.70 save the math and file IO vars as follows:

| Pos. | Size | Description | |------|------|-------------| | 0 | ws | Multiplier | 2 | ws | Divider | 4 | ws | Circle divisions | 6 | s12 | Input filename (null terminated) | 18 | d | Input file position | 22 | s12 | Output filename (null terminated) | 24 | d | Output file position

MZX versions 2.80X through 2.82X save the following:

| Pos. | Size | Description | |------|------|-------------| | 0 | w(1) | Multiplier | 2 | w(1) | Divider | 4 | w(1) | Circle divisions | 6 | b | Built-in message status (0: disabled, 1: enabled) | 7 | s12 | Input filename (null terminated) | 19 | d | Input file position | 21 | s12 | Output filename (null terminated) | 25 | d | Output file position

MZX 2.83 saves the following:

| Pos. | Size | Description | |------------|------|-------------| | 0 | w(1) | Multiplier | 2 | w(1) | Divider | 4 | w(1) | Circle divisions | 6 | b | Built-in message status (0: disabled, 1: enabled) | 7 | w | Input filename length = X | 9 | s... | Input filename (NOT null terminated) | 9 + X | d | Input file position | 13 + X | w | Output filename length = Y | 15 + X | s... | Output filename (NOT null terminated) | 15 + X + Y | d | Output file position

MZX 2.84X saves the following:

| Pos. | Size | Description | |------------|------|-------------| | 0 | w(1) | Multiplier | 2 | w(1) | Divider | 4 | w(1) | Circle divisions | 6 | ws | Input file delimiter | 8 | ws | Output file delimiter | 10 | b | Built-in shooting status (SPACELOCK) (0: disabled, 1: enabled) | 11 | b | Built-in message status (0: disabled, 1: enabled) | 12 | w | Input filename length = X | 14 | s... | Input filename (NOT null terminated) | 14 + X | d | Input file position | 18 + X | w | Output filename length = Y | 20 + X | s... | Output filename (NOT null terminated) | 20 + X + Y | d | Output file position
Notes
  1. These variables were still saved as a word from 2.80X through 2.84X despite the internal variable being expanded to a signed dword, meaning these were truncated and then loaded as unsigned words. This wasn't corrected until MZX 2.90. OOPS!

SMZX Data and Commands (2.69 through 2.84X) (.SAV only)

MZX versions 2.69 and up save the SMZX mode here.

| Pos. | Size | Description | |------|------|-------------| | 0 | w | SMZX mode (0: disabled)

If SMZX mode is 2 or 3 (2.81+), the SMZX palette (but NOT intensities, which were unsaved prior to 2.90) follows:

| Pos. | Size | Description | |------|-------------|-------------| | 0 | b * 3 * 768 | SMZX palette (see Palettes)

The commands value follows. MZX 2.69 through MZX 2.83 saved it as a word (despite the internal value being expanded to a dword in 2.80):

| Pos. | Size | Description | |------|------|-------------| | 0 | w | Commands

MZX 2.84X finally corrected this to a dword:

| Pos. | Size | Description | |------|------|-------------| | 0 | d | Commands

Vlayer (2.69c through 2.84X) (.SAV only)

The vlayer is saved as follows in 2.69c and 2.70:

| Pos. | Size | Description | |-------|-----------|-------------| | 0 | w | Vlayer width | 2 | w | Vlayer height | 4 | b * 32768 | Vlayer chars | 32772 | b * 32768 | Vlayer colors

MegaZeux 2.80X saves this instead:

| Pos. | Size | Description | |-------------|-----------|-------------| | 0 | w | Vlayer width = X | 2 | w | Vlayer height = Y | 4 | b * X * Y | Vlayer chars | 4 + (X * Y) | b * X * Y | Vlayer colors

This was changed to the following in 2.81X:

| Pos. | Size | Description | |-------|-------|-------------| | 0 | d | Vlayer size = X | 4 | w | Vlayer width | 6 | w | Vlayer height | 8 | b * X | Vlayer chars | 8 + X | b * X | Vlayer colors

Global Robot Offset and Board Count

In an unencrypted world file this will always be at offset 4230.

| Pos. | Size | Description | |------|------|-------------| | 0 | d | Offset of global robot within the file | 4 | b | Number of boards (1-250) or 0 for SFX table

Custom SFX Table

If the board count byte is 0, a custom SFX table is present. If there is not a custom SFX table in the world file, the world will use the MZX default SFX instead. The SFX table begins with:

| Pos. | Size | Description | |------|------|-------------| | 0 | w | Length of the SFX table in bytes

Then, for each SFX (50 total):

| Pos. | Size | Description | |------|------|-------------| | 0 | b | Length of SFX string, including null terminator (69 max) | 1 | s... | SFX string

Finally:

| Pos. | Size | Description | |------|------|-------------| | 0 | b | Number of boards in the world (1-250)

Board Names, Sizes, and Offsets

Following the board count byte is the board names list and the board sizes and offsets table.

For each board:

| Pos. | Size | Description | |------|------|-------------| | 0 | s25 | Board names (null terminated)

For each board:

| Pos. | Size | Description | |------|------|-------------| | 0 | d | Size of board data within the file | 4 | d | Offset of board data within the file

Typically, board data immediately follows these tables, and the global robot is placed at the very end of the world file.

Boards

Each board begins with the following fields:

| Pos. | Size | Description | |------|------|-------------| | 0 | b | Board mode (1) | 1 | b | Varies (2)

Notes

  1. Boards in DOS MZX had a fixed buffer size of 10000 for each board plane. DOS MZX used the board mode field to determine what the maximum dimensions of the board were within the available space. MegaZeux 2.80+ ignores this field and uses the plane dimensions to to determine the board size.
    | Board Mode | Max. Width | Max. Height | |------------|------------|-------------| | `0` | 60 | 166 | | `1` | 80 | 125 | | `2` | 100 | 100 | | `3` | 200 | 50 | | `4` | 400 | 25 |
  2. If the second byte of the board non-zero, it is the lower byte of the level_id plane's width field. If this byte is 00, the board has an overlay and has the following extra field. As a consequence of this, saving a world with overlay disabled and a board width of 256 (which results in a lower byte of 00) in DOS MZX versions will generate a corrupt world and crash MZX. Port versions avoid this by incrementing the board width to a non-multiple of 256 when setting the board size in the editor.
| Pos. | Size | Description | |------|------|-------------| | 2 | b | Overlay mode (1: enabled, 2: static, 3: transparent)

Board RLE Planes

Board and overlay contents are compressed with run length encoding. Each board has either 6 or 8 planes (depending on whether the overlay is enabled), which immediately follow the mode fields and are stored in the following order:

| name | description | |-------------------|-------------| | overlay_char | Overlay char data (only if overlay is enabled) | overlay_color | Overlay color data (only if overlay is enabled) | level_id | Board thing IDs | level_color | Board thing colors | level_param | Board thing parameters | level_under_id | Board thing IDs for floors beneath non-floor things | level_under_color | Board thing colors for floors beneath non-floor things | level_under_param | Board thing parameters for floors beneath non-floor things

Each compressed plane starts with the following fields:

| Pos. | Size | Description | |------|------|-------------| | 0 | w | Width | 2 | w | Height

The RLE data follows after. Values 0 through 127 represent a single literal value in the uncompressed data. If bit 7 is set, instead a run of length (byte & 127) should be filled with the next byte in the stream. The RLE plane can be decompressed with the following algorithm:

/** * This code fragment is a simplified version of the RLE2 unpacking code from * MegaZeux's source code, and is therefore GPL 2+ licensed. */ int i = 0; while(i < width * height) { current_char = *(stream++); if(!(current_char & 0x80)) { plane[i++] = current_char; } else { int runsize = current_char & 0x7F; current_char = *(stream++); for(int j = 0; j < runsize; j++) plane[i++] = current_char; } }

The RLE stream ends when (board width * board height) bytes have been expanded from the stream, and the next compressed plane or the board parameters block immediately follows.

Board Parameters (2.00 through 2.82X)

The board parameters block follows the board RLE planes. The following is the board parameters format for MZX versions prior to 2.83.

| Pos. | Size | Description | |------|------|-------------| | 0 | s13 | Mod playing (null terminated) | 13 | b | Viewport X | 14 | b | Viewport Y | 15 | b | Viewport width | 16 | b | Viewport height | 17 | b | Can shoot? | 18 | b | Can bomb? | 19 | b | Fire burns brown? | 20 | b | Fire burns space? | 21 | b | Fire burns fakes? | 22 | b | Fire burns trees? | 23 | b | Explosions leave (0: space, 1: ash, 2: fire) | 24 | b | Save mode (0: enabled, 1: disabled, 2: sensor only) | 25 | b | Forest to floor? | 26 | b | Collect bombs? | 27 | b | Fire burns forever? | 28 | b | Board # to north | 29 | b | Board # to south | 30 | b | Board # to east | 31 | b | Board # to west | 32 | b | Restart if zapped? | 33 | w | Time limit | 35 | b | Last key pressed | 36 | ws | Input (numeric value) | 38 | b | Inputsize | 39 | s81 | Input (string value) (null terminated) | 120 | b | Last player direction | 121 | s81 | Bottom message (null terminated) | 202 | b | Bottom message timer | 203 | b | Lazerwall timer | 204 | b | Bottom message row | 205 | b | Bottom message column | 206 | w | Scroll X | 208 | w | Scroll Y | 210 | w | Scroll locked X | 212 | w | Scroll locked Y | 214 | b | Player locked NS? | 215 | b | Player locked EW? | 216 | b | Player attack locked? | 217 | b | Volume | 218 | b | Volume increment | 219 | b | Volume target

Board Parameters (2.83 and 2.84X)

The board parameters block follows the board RLE planes. The following is the board format for MZX versions 2.83 and 2.84X.

| Pos. | Size | Description | |------|------|-------------| | 0 | w | Mod playing length | 2 | s... | Mod playing (NOT null terminated)

This block immediately follows the playing mod:

| Pos. | Size | Description | |------|------|-------------| | 0 | b | Viewport X | 1 | b | Viewport Y | 2 | b | Viewport width | 3 | b | Viewport height | 4 | b | Can shoot? | 5 | b | Can bomb? | 6 | b | Fire burns brown? | 7 | b | Fire burns space? | 8 | b | Fire burns fakes? | 9 | b | Fire burns trees? | 10 | b | Explosions leave (0: space, 1: ash, 2: fire) | 11 | b | Save mode (0: enabled, 1: disabled, 2: sensor only) | 12 | b | Forest to floor? | 13 | b | Collect bombs? | 14 | b | Fire burns forever? | 15 | b | Board # to north | 16 | b | Board # to south | 17 | b | Board # to east | 18 | b | Board # to west | 19 | b | Restart if zapped? | 20 | w | Time limit

The following block is only present in save files.

| Pos. | Size | Description | |---------|------|-------------| | 0 | b | Last key pressed | 1 | w | Input (numeric value) | 3 | w | Inputsize | 5 | w | Input string value length = x | 7 | s... | Input (string value) (NOT null terminated) | 7+x | b | Last player direction | 8+x | w | Bottom message length = y | 10+x | s... | Bottom message (NOT null terminated) | 10+x+y | b | Bottom message timer | 11+x+y | b | Lazerwall timer | 12+x+y | b | Bottom message row | 13+x+y | b | Bottom message column | 14+x+y | w | Scroll X | 16+x+y | w | Scroll Y | 18+x+y | w | Scroll locked X | 20+x+y | w | Scroll locked Y

The following block is in both world files and save files.

| Pos. | Size | Description | |------|------|-------------| | 0 | b | Player locked NS? | 1 | b | Player locked EW? | 2 | b | Player attack locked?

The final block is only present in save files.

| Pos. | Size | Description | |------|------|-------------| | 0 | b | Volume | 1 | b | Volume increment | 2 | b | Volume target

Notes

  1. Some MZX worlds created with an early alpha version of MZX 2.83 have boards parameters in the older format. Since these were saved with the MZX 2.83 world magic, they can't be loaded in newer versions of MZX, but they can be fixed by hex editing the magic from M\x02\x53 to M\x02\x52.

Board Objects

Following the board parameters are the robot, scroll, and sensor lists. Each list is stored as a single byte N indicating the number of robots, scrolls, or sensors on the board (0 to 255), followed by N stored objects of that type. Each object in its respective list immediately follows the prior, and the next list starts immediately following the last object in the previous list. The board data ends after all three lists and their objects have been read.

The board robots/scrolls/sensors in the list count from ID 1 upward, as ID 0 is the global robot and is invalid for scrolls and sensors. Since objects in the file are sequential, robot/scroll/sensor IDs may have to be reassigned when saving worlds or saves in these versions to optimize out gaps in the list.

Robots

Robots are stored in world/save files in the following format. Several of these fields are only used during runtime and thus initial values used when saving worlds are also listed.

| Pos. | Size | Default | Description | |------|------|---------|-------------| | 0 | w | | Program length in bytes | 2 | w | | unused (1) | 4 | s15 | | Robot name (null-terminated) | 19 | b | 2 | Robot char | 20 | w | 1 | Current position in program | 22 | b | 0 | Position within current line (for WAIT/GO/etc) | 23 | b | 1 | Robot cycle | 24 | b | 0 | Cycle counter | 25 | b | 1 | Bullet type | 26 | b | 0 | Locked status (0: unlocked, 1: locked) (2) | 27 | b | 0 | Can lavawalk (0: no, 1: yes) | 28 | b | 0 | Walk direction (0: idle) (2) | 29 | b | 0 | Last touch direction (3) | 30 | b | 0 | Last shot direction (3) | 31 | ws | note 4 | X position | 33 | ws | note 4 | Y position | 35 | b | 0 | Status for the current board cycle (5) | 36 | ws | 0 | Local (6) | 38 | b | 1 | Used (1) or unused (0) | 39 | ws | 0 | Loopcount (7)

Notes

  1. DOS MZX versions wrote robot, scroll, and sensor structs directly to world or save files. The unused 2-byte field at offset 2 corresponds to the program memory pointer in these versions.
  2. In DOS MZX versions from 2.69b to 2.70, the LOCAL2 counter was stored by using the robot walk direction as the upper byte and the robot locked status as the lower byte. Setting LOCAL2 had the side effects you would expect from modifying those variables.
  3. In DOS MZX versions from 2.69b to 2.70, the LOCAL3 counter was stored by using the robot last touch direction as the upper byte and the robot last shot direction as the lower byte. Setting LOCAL3 had the side effects you would expect from modifying those variables.
  4. The X/Y position variables are generally initialized to 0 in world files from DOS versions. Versions 2.80+ typically save the actual X and Y position in these variables in world files. The global robot is saved with the coordinates (-1,-1).
  5. Robot statuses:
    | Status | Description | |--------|-------------| | 0 | Normal | 1 | Did not execute this cycle or only executed end, wait | 2 | Same as 1 but has also been sent a label
    When a robot's status is equal to 2, it will execute the label it was sent on reverse board scan. Every robot's status is reset to 0 at the start of each board cycle.
  6. The local field in this block is completely ignored in versions 2.80+ and is saved in the robot save block instead. In versions prior to 2.51s1, this field existed but was not used.
  7. The loopcount field in this block is completely ignored in 2.84X save files and is saved in the robot save block instead.

In save files from MegaZeux 2.80 and onward, an extra data block follows:

| Pos. | Size | Description | |------|---------|-------------| | 41 | ds | Loopcount (8) | 45 | ds * 32 | Locals | 173 | d | Stack length = X | 177 | d | Current position in stack | 181 | d * X | Robot stack

Notes

  1. The loopcount field in this block was not present prior to 2.84 (i.e. the block started with the local counters instead and was 4 bytes shorter).

In both world and save files, the robot program immediately follows the above block(s). The size of the program in file is the same as the program length field in the robot data.

In older worlds, robots marked unused may have uninitialized memory saved where their program would usually go (the global robot from Slave Pit is an example). In this situation, the Robotic program should not be validated and should instead be ignored or replaced with FF 00.

Scrolls

Each scroll block is 7 bytes plus the length of the scroll text long.

| Pos. | Size | Description | |------|------|-------------| | 0 | w | Number of lines | 2 | w | unused (1) | 4 | w | Scroll text length in bytes | 6 | b | Used (1) or unused (0) | 7 | s... | Scroll text (2)

Notes

  1. The unused 2-byte field corresponds to the scroll text memory pointer in DOS versions of MZX.
  2. The scroll text must begin with a 01 hex byte, must contain at least one line break (0A hex), and must be null terminated (i.e. the smallest valid scroll is 01 0A 00 with number of lines=1 and length=3).

Sensors

Each sensor block is 32 bytes long.

| Pos. | Size | Description | |------|------|-------------| | 0 | s15 | Sensor name (null-terminated) | 15 | b | Sensor char | 16 | s15 | Robot to message (null-terminated) | 31 | b | Used (1) or unused (0)

World and Save Format (2.90 onward)

The legacy world format was replaced starting from MegaZeux 2.90 with a ZIP-based format. The old world format was replaced for the following reasons:

World and Save Headers

ZIP-based worlds begin with the same 29-byte header that unencrypted legacy worlds do, and ZIP-based saves begin with the same 8-byte header that legacy save files do. This is possible as ZIP archives parse starting from the end of the file, so ZIP archives with prefixed data are still valid ZIP archives. The header data is stored redundantly in the world properties file so ZIP worlds can be extracted and rearchived by an external program and still work even without the header.

Files List

World and save files split their data into multiple internal files for modularization and to allow for compression of specific parts of the world data. The files supported by the world format and the order they are loaded in are as follows:

| Filename | Description | Notes | Compressed | |-----------|-------------------------------------------------------------|-----------------------------|------------| | `world` | World Properties | | Never | `gr` | Global Robot | | `sfx` | Custom SFX Table | Only if custom SFX enabled | As of 2.91c | `chars` | Character Sets | | Always | `pal` | Palette | | `palidx` | Palette Indices | Save-only before 2.91, SMZX 3 only | `vco` | Vlayer Colors Plane | Save-only before 2.91 | Always | `vch` | Vlayer Chars Plane | Save-only before 2.91 | Always | `palint` | Palette Intensities | Save-only | `spr` | Sprite Properties | Save-only | Always | `counter` | Counters List | Save-only | `string` | Strings List | Save-only | `b##` | Board Properties | See below. | `b##bid` | Board level_id Plane | | Always | `b##bpr` | Board level_param Plane | | Always | `b##bco` | Board level_color Plane | | Always | `b##uid` | Board level_under_id Plane | | Always | `b##upr` | Board level_under_param Plane | | Always | `b##uco` | Board level_under_color Plane | | Always | `b##och` | Board overlay_char Plane | Only if overlay enabled | Always | `b##oco` | Board overlay_color Plane | Only if overlay enabled | Always | `b##r##` | Board Robot Properties | Only if robot exists | `b##sc##` | Board Scroll Properties | Only if scroll exists | `b##se##` | Board Sensor Properties | Only if sensor exists

The board files (prefixed with b##) corresponding to a single board are all loaded before loading any files corresponding to the next board. The first ## in the names of these files is the board number in hex. Examples: b00 for the title board, b0A for board 10, bF9 for board 249 (the highest possible regular board number), and bFF for the special temporary board that may sometimes exist in save files.

Extra storage data for robots, scrolls, and sensors is stored in separate files. The file bXXrYY corresponds to the robot on board XX (hex) with the robot ID YY (also hex). Values from 01 (for robot 1) to FF (for robot 255) are valid. The same applies for scrolls and sensors. The robot/scroll/sensor must exist on the board for its corresponding properties file to be valid.

Properties Format

World, board, sprite, and object data are all stored in a simple extensible format created for MZX files similar to RIFF or a (very) simplified EBML. All internal files labeled "properties" use this format.

These files consist of an unspecified number of blocks of "properties" in the format:

| Pos. | Size | Description | |------|-------|-------------| | 0 | w | Property ID | 2 | d | Property length = X | 6 | b * X | Property data

Property data contents vary between different properties, so in the property list specifications the expected contents are explicitly defined. Most properties expect an int value and accept either 1 byte (equivalent to b), 2 bytes (w), or 4 bytes (ds) (expected size and signedness are explicitly noted). The next most common stored type is a variable length string with no null terminator (equivalent to s...). strings that are stored with null terminators are marked (with \0).

The file ends immediately when a property ID of 0x0000 is encountered indicating the end of the file. This value is the same for all different properties files. Unrecognized properties IDs are usually skipped unless noted otherwise.

It is possible to nest properties files to create a file with a more complex structure (though MZX currently does not do this).

World Properties

Global data is stored in a properties file for both world and save files. This is a common pattern in the new world format; save data is typically implemented as either extra properties only present in save files or (occasionally) as save-exclusive raw data files.

Unlike every other properties file in the format, the world properties file is very strict: every property expected for a particular file version must exist and must be in the order listed below. Also unlike other properties files in the world format, unrecognized properties in this file will usually generate an error. An integrity check is performed to enforce this early in the load process to determine whether or not the provided file is actually a world/save.

Additionally, compressing this file should be avoided, as it may be useful in the future for MZX to be able to peek at the contents of this file prior to initializing ZIP archive data structures.

| ID | Property | Data Type | Notes | |----------|--------------------------------------|-------------------------|-------| | `0x0001` | World name | string(25) (with \0) | | `0x0002` | World version | int(w) | | `0x0003` | File version | int(w) | | `0x0004` | Save start board # | int(b) | 255: temporary board | `0x0005` | Save has temporary board? | int(b) | | `0x0008` | Number of boards in world | int(b) | | `0x0010` | ID Chars blocks 1 and 2 | array(b * 323) | | `0x0011` | ID Chars missile color | int(b) | | `0x0012` | ID Chars bullet colors | array(b * 3) | | `0x0013` | ID Chars block 3 (damage) | array(b * 128) | | `0x0018` | Status counters | array(s15 with \0 * 6) | | `0x0020` | Edge color | int(b) | `0x0021` | First board # | int(b) | `0x0022` | Endgame board # | int(b) | 255: no endgame board | `0x0023` | Death board # | int(b) | 254: same position
255: restart board | `0x0024` | Endgame board teleport X | int(w) | `0x0025` | Endgame board teleport Y | int(w) | `0x0026` | Game over SFX enabled? | int(b) | `0x0027` | Death board teleport X | int(w) | `0x0028` | Death board teleport Y | int(w) | `0x0029` | Starting lives | int(w) | `0x002A` | Lives limit | int(w) | `0x002B` | Starting health | int(w) | `0x002C` | Health limit | int(w) | `0x002D` | Enemies' bullets hurt other enemies | int(b) | `0x002E` | Clear messages/projectiles on exit | int(b) | `0x002F` | Can only play world from a SWAP WORLD| int(b) | `0x8030` | SMZX mode (0: disabled) | int(b) | Save-only before 2.91 | `0x8031` | Vlayer width | int(w) | Save-only before 2.91 | `0x8032` | Vlayer height | int(w) | Save-only before 2.91 | `0x8033` | Vlayer size | int(d) | Save-only before 2.91 | `0x8040` | Mod playing | string | Save-only | `0x8041` | MZX speed | int(b) | Save-only | `0x8042` | MZX speed is locked? | int(b) | Save-only | `0x8043` | Commands per cycle | int(ds) | Save-only | `0x8044` | Commands per cycle breakpoint limit | int(ds) | Save-only | `0x8048` | Saved positions | array((w + w + b) * 8) | Save-only | `0x8049` | Under player ID/param/color | array(b * 3) | Save-only | `0x804A` | Player restart X | int(w) | Save-only | `0x804B` | Player restart Y | int(w) | Save-only | `0x804C` | Player color | int(b) | Save-only | `0x804D` | Keys | array(b * 16) | Save-only | `0x8050` | Blind timer | int(b) | Save-only | `0x8051` | Firewalker timer | int(b) | Save-only | `0x8052` | Freeze time timer | int(b) | Save-only | `0x8053` | Slow time timer | int(b) | Save-only | `0x8054` | Wind timer | int(b) | Save-only | `0x8058` | Scroll base color | int(b) | Save-only | `0x8059` | Scroll corner color | int(b) | Save-only | `0x805A` | Scroll pointer color | int(b) | Save-only | `0x805B` | Scroll title color | int(b) | Save-only | `0x805C` | Scroll arrow color | int(b) | Save-only | `0x8060` | Message edges enabled? | int(b) | Save-only | `0x8061` | Built-in shooting enabled? | int(b) | Save-only | `0x8062` | Built-in messages enabled? | int(b) | Save-only | `0x8063` | Faded state (1: faded out) | int(b) | Save-only | `0x8070` | Input filename | string | Save-only | `0x8074` | Input file position | int(d) | Save-only | `0x8075` | Input file delimiter | int(ds) | Save-only | `0x8078` | Output filename | string | Save-only | `0x807C` | Output file position | int(d) | Save-only | `0x807D` | Output file delimiter | int(ds) | Save-only | `0x8080` | Multiplier | int(ds) | Save-only | `0x8081` | Divider | int(ds) | Save-only | `0x8082` | Circle divisions | int(ds) | Save-only | `0x8090` | Maximum simultaneous sound effects | int(ds) | Save-only, 2.91+ | `0x8091` | SMZX message enabled in SMZX mode? | int(b) | Save-only, 2.91+ | `0x8092` | Joystick presses simulate keypresses?| int(b) | Save-only, 2.92+

Custom SFX Table

The custom SFX table is saved as a raw data file of (NUM_SFX * LEGACY_SFX_SIZE bytes, where NUM_SFX = 50 and LEGACY_SFX_SIZE = 69 (for a total of 3450 bytes prior to compression). If this file is present, custom SFX will be enabled for the world. If this file is absent, custom SFX will be disabled.

This data block is loaded directly over the custom SFX string data and each SFX string is expected to be null terminated within the block. This was a bad idea and will likely be replaced with a properties file in the near future.

Character Sets

The world character sets are saved as a single raw charset file. In 2.90+ saves and 2.91+ worlds, all 15 user charsets will be saved for a total size of 14*15*256 = 53760 bytes prior to compression. Worlds from MZX 2.90 will only save the main charset (for a total size of 3840 bytes).

Palette, Palette Indices, and Palette Intensities

The world palette is saved as a raw palette file containing 256 colors (regardless of the current SMZX mode). If SMZX mode 3 is enabled, the palette indices will also be saved as a raw palette indices file in 2.90+ save files and 2.91+ world files.

In save files, the palette intensities are saved as a third file. All 256 palette intensities are saved regardless of the current SMZX mode in an array of 256 bytes (note: this will truncate extreme intensity values and was probably a bad idea).

Vlayer Planes

The vlayer chars and colors planes are saved as raw data in two separate files in saves (2.90 and up) and world files (2.91 and up). These files are expected to be (vlayer_size) bytes long, where vlayer_size is specified in the world properties. If one of these files is missing, an error will be displayed and the plane will be zero-initialized.

Sprite Properties (.SAV only)

Sprite data is stored in a single properties file for all sprites with the following properties:

| ID | Property | Data Type | Notes | |----------|----------------------------|---------------|-------| | `0x0001` | Set current sprite ID | int(b) | Each sprite(1) | `0x0002` | Sprite X | int(ds) | Each sprite | `0x0003` | Sprite Y | int(ds) | Each sprite | `0x0004` | Sprite reference X | int(ds) | Each sprite | `0x0005` | Sprite reference Y | int(ds) | Each sprite | `0x0006` | Sprite color | int(b) | Each sprite | `0x0007` | Sprite flags | int(b) | Each sprite(2) | `0x0008` | Sprite width | int(d) | Each sprite | `0x0009` | Sprite height | int(d) | Each sprite | `0x000A` | Sprite collision X | int(ds) | Each sprite | `0x000B` | Sprite collision Y | int(ds) | Each sprite | `0x000C` | Sprite collision width | int(d) | Each sprite | `0x000D` | Sprite collision height | int(d) | Each sprite | `0x000E` | Sprite transparent color | int(ds) | Each sprite | `0x000F` | Sprite character offset | int(ds) | Each sprite | `0x0010` | Sprite Z | int(ds) | Each sprite, 2.92+ | `0x8000` | Number of active sprites | int(d) | Only once | `0x8001` | Sprite Y order enabled | int(ds) | Only once | `0x8002` | Sprite collision count = N | int(d) | Only once | `0x8003` | Sprite collision list | array(ds * N) | Only once | `0x8004` | `SPR_NUM` | int(ds) | Only once
  1. This property sets the sprite ID that all following individual sprite properties will be loaded to. This must be present before the properties of a given sprite for the sprite to be correctly loaded. All 256 sprites and their properties will typically be saved regardless of whether they have been used or not, which is why this file is compressed.
  2. Sprite flags:
    | Flag | Description | |--------|-------------| | `0x01` | Sprite is initialized | `0x02` | Sprite ccheck 1 is enabled | `0x04` | Sprite is drawn over the overlay | `0x08` | Sprite uses reference colors | `0x10` | Sprite is static relative to the viewport | `0x20` | Sprite ccheck 2 is enabled | `0x40` | Sprite references the vlayer | `0x80` | Sprite is unbound
    For unbound sprites, if both the ccheck 1 and ccheck 2 flags are set, sprite ccheck 3 is enabled instead.

Counter and String Lists (.SAV only)

The counter and string lists are packed in the same binary format they were in prior versions, each in their own file.

Counters List

The counter list file starts with the number of counters:

| Pos. | Size | Description | |------|---------|-------------| | 0 | d | Number of counters = N

A list of N counter definitions follows:

| Pos. | Size | Description | |------|-------|-------------| | 0 | ds | Counter value | 4 | d | Name length = X | 8 | b * X | Counter name (NOT null terminated)

Strings List

The string list file starts with the number of strings:

| Pos. | Size | Description | |------|---------|-------------| | 0 | d | Number of strings = N

A list of N string definitions follows:

| Pos. | Size | Description | |-------|---------|-------------| | 0 | d | String name length = X | 4 | d | String value length = Y | 8 | b * X | String name (NOT null terminated) | 8 + X | b * Y | String value (NOT null terminated)

Board Properties

Board data is stored in a properties file with the following properties:

| ID | Property | Data Type | Notes | |----------|------------------------|-----------------------|-------| | `0x0001` | Board name | string(25) (with \0) | Strict(1) | `0x0002` | Board width | int(w) | Strict | `0x0003` | Board height | int(w) | Strict | `0x0004` | Overlay mode | int(b) | Strict | `0x0005` | Robot count | int(b) | Strict | `0x0006` | Scroll count | int(b) | Strict | `0x0007` | Sensor count | int(b) | Strict | `0x0008` | File version | int(w) | Strict(2) | `0x0010` | Mod playing | string | `0x0011` | Viewport X | int(b) | `0x0012` | Viewport Y | int(b) | `0x0013` | Viewport width | int(b) | `0x0014` | Viewport height | int(b) | `0x0015` | Can shoot? | int(b) | `0x0016` | Can bomb? | int(b) | `0x0017` | Fire burns brown? | int(b) | `0x0018` | Fire burns space? | int(b) | `0x0019` | Fire burns fakes? | int(b) | `0x001A` | Fire burns trees? | int(b) | `0x001B` | Explosions leave | int(b) | `0x001C` | Save mode | int(b) | `0x001D` | Forest to floor? | int(b) | `0x001E` | Collect bombs? | int(b) | `0x001F` | Fire burns forever? | int(b) | `0x0020` | Board # to north | int(b) | `0x0021` | Board # to south | int(b) | `0x0022` | Board # to east | int(b) | `0x0023` | Board # to west | int(b) | `0x0024` | Restart if zapped? | int(b) | `0x0025` | Time limit | int(w) | `0x0026` | Player locked NS? | int(b) | `0x0027` | Player locked EW? | int(b) | `0x0028` | Player attack locked? | int(b) | `0x0029` | Reset board on entry? | int(b) | `0x002A` | Board charset path | string | `0x002B` | Board palette path | string | `0x0100` | Scroll X | int(w) | Save-only | `0x0101` | Scroll Y | int(w) | Save-only | `0x0102` | Scroll locked X | int(w) | Save-only | `0x0103` | Scroll locked Y | int(w) | Save-only | `0x0104` | Last player direction | int(b) | Save-only | `0x010A` | Lazerwall timer | int(b) | Save-only | `0x010B` | Last key pressed | int(b) | Save-only | `0x010C` | Input (numeric value) | int(ds) | Save-only | `0x010D` | Inputsize | int(ds) | Save-only | `0x010E` | Input (string value) | string | Save-only | `0x0110` | Bottom message | string | Save-only | `0x0111` | Bottom message timer | int(b) | Save-only | `0x0112` | Bottom message row | int(b) | Save-only | `0x0113` | Bottom message column | int(b) | Save-only | `0x0114` | Volume | int(b) | Save-only | `0x0115` | Volume increment | int(b) | Save-only | `0x0116` | Volume target | int(b) | Save-only
  1. Properties marked "strict" must be present and in the order specified in the table for the board data to be considered valid. If the board properties file is invalid, the entire board contents will be dummied out.
  2. This field exists mainly to provide redundancy for the .MZB file header. In world and save files, this field should be redundant with the file version field derived from the world or save magic.

Board Planes

Each board has 6 or 8 planes (depending on if overlay is enabled) saved in separate files as raw data of size (board width * board height) bytes each. If any of the expected planes are missing when loading a board, an error will display and the plane will be zero-initialized.

Robot Properties

Robot data is stored in a properties file with the following properties:

| ID | Property | Data Type | Notes | |----------|------------------------------|-----------------------|-------| | `0x0001` | Robot name | string(15) (with \0) | Strict(1) | `0x0002` | Robot char | int(b) | Strict | `0x0003` | X position | int(ws) | Strict | `0x0004` | Y position | int(ws) | Strict | `0x00FF` | Program bytecode | array(b * N) | Strict | `0x0100` | Current position in program | int(d) | Save-only | `0x0101` | Position within current line | int(d) | Save-only | `0x0102` | Robot cycle | int(b) | Save-only | `0x0103` | Cycle counter | int(b) | Save-only | `0x0104` | Bullet type | int(b) | Save-only | `0x0105` | Locked status | int(b) | Save-only | `0x0106` | Can lavawalk | int(b) | Save-only | `0x0107` | Walk direction | int(b) | Save-only | `0x0108` | Last touch direction | int(b) | Save-only | `0x0109` | Last shot direction | int(b) | Save-only | `0x010A` | Status | int(b) | Save-only | `0x010B` | Loopcount | int(ds) | Save-only | `0x010C` | Locals | array(ds * 32) | Save-only | `0x0110` | Current position in stack | int(d) | Save-only | `0x0111` | Robot stack | array(d * N) | Save-only | `0x0120` | Can goopwalk | int(b) | Save-only
  1. Properties marked "strict" must be present and in the order specified in the table for the robot data to be considered valid.

Scroll Properties

Scroll data is stored in a properties file with the following properties:

| ID | Property | Data Type | |----------|-----------------|-----------| | `0x0001` | Number of lines | int(w) | `0x0002` | Scroll text | string (with \0)

Sensor Properties

Sensor data is stored in a properties file with the following properties:

| ID | Property | Data Type | |----------|------------------|-----------| | `0x0001` | Sensor name | string(15) (with \0) | `0x0002` | Sensor char | int(b) | `0x0003` | Robot to message | string(15) (with \0)

Board File (.MZB)

A MegaZeux board file begins with a 4-byte header:

| Pos. | Size | Description | |------|------|-------------| | 0 | b | Always hex FF | 1 | s3 | Magic (MB2 for 2.00 through 2.51s1, same as world magic after)

Board Files for 2.00 through 2.84X

The header is immediately followed by the raw data for a single board in the world format (including robots, scrolls, and sensors as-needed). See the world format documentation for boards for more info.

Board Files for 2.90 onward

The header is immediately followed by a ZIP archive containing the board properties, board planes, and object properties files for a single board. The following filenames are used:

| Filename | Description | |-----------|-------------| | `b00` | Board properties | `b00bid` | Board level_id plane | `b00bpr` | Board level_param plane | `b00bco` | Board level_color plane | `b00uid` | Board level_under_id plane | `b00upr` | Board level_under_param plane | `b00uco` | Board level_under_color plane | `b00och` | Board overlay_char plane (if overlay is enabled) | `b00oco` | Board overlay_color plane (if overlay is enabled) | `b00r##` | Robot properties (up to 255) | `b00sc##` | Scroll properties (up to 255) | `b00se##` | Sensor properties (up to 255)

Image File (.MZM)

Raw blocks of board and overlay/vlayer data can be saved to and loaded from MZMs (or MZX image files). Three variants of the MZM format exist. The current MZM format is MZM3, which was introduced in MegaZeux 2.84.

MZM3

An MZM3 file begins with the following 20-byte header:

| Pos. | Size | Description | Values | |------|------|-----------------------------------------------|--------| | 0 | s4 | Magic | MZM3 | 4 | w | Width | 6 | w | Height | 8 | d | Location in file of robot data | 0 if not present | 12 | b | Number of robots in data | 0 if not present | 13 | b | Storage mode | 0: board, 1: layer | 14 | b | "Savegame" MZM? (includes runtime robot data) | 0 if false, 1 if true | 15 | w | World version | See below. | 17 | b | reserved | 18 | b | reserved | 19 | b | reserved

The world version is stored as a 2-byte little endian value where the upper byte is the major version number and the lower byte is the minor version number (like the world version field in the save format header). MZM3 files are forward compatible unless they contain robot information, in which case the robots in the MZM are replaced with customblocks.

The header is immediately followed by a board data block or a layer data block depending on the storage mode value indicated in the header.

Storage Mode 0 (Board)

The MZM data is stored in the following format if the "board" storage mode is selected. The data is composed of (width * height) blocks, where each block is 6 bytes and contains the following:

| Pos. | Size | Description | |------|------|-------------| | 0 | b | ID | 1 | b | Param | 2 | b | Color | 3 | b | Under ID | 4 | b | Under param | 5 | b | Under color

Signs, scrolls, sensors, players, and IDs >=128 will be replaced with spaces when an MZM is loaded. Robots in a board MZM require extra storage information.

When a board MZM is created from the overlay or vlayer, the ID is typically set to 5, the param to the char, the color to the color, and all other values are set to 0.

Storage Mode 1 (Layer)

The MZM data is stored in the following format if the "layer" storage mode is selected. The data is composed of (width * height) blocks, where each block is 2 bytes and contains the following:

| Pos. | Size | Description | |------|------|-------------| | 0 | b | Char | 1 | b | Color

Robot Data

MZMs using the "board" storage mode may additionally include a robot data block if the MZM board data contains robots. This block will always be located after the board data, and the format of this block is based on the MZX world format corresponding to the world version indicated in the header.

MZMs created in MZX 2.84X will contain N robot blocks as described in the 2.00 through 2.84X world format documentation. If this is a savegame MZM, the robots will be in the save format instead of the world format.

MZMs created in MZX 2.90 and onward will contain a ZIP archive after the board data containing N robot properties files named in the format r##, where ## is a hexadecimal number between 1 and 255 corresponding to a robot ID in the board data. If this is a savegame MZM, the robots will contain savegame properties.

MZM2

MZM2 was introduced in MegaZeux 2.80 and is essentially the same as MZM3 without the version field in the header. The MZM2 header is 16 bytes long:

| Pos. | Size | Description | |------|------|-------------| | 0 | s4 | `MZM2` | 4 | w | Width | 6 | w | Height | 8 | d | Location in file of robot table (0 for no robots) | 12 | b | Number of robots (0 for no robots) | 13 | b | Storage mode (0: board, 1: layer) | 14 | b | "Savegame" MZM? (includes runtime robot data) (0 if false, 1 if true) | 15 | b | unused

The board data and robot data follows exactly as MZM3 with the exception that robots are always use the MZX 2.80 through 2.83 robot format rather than newer formats.

MZMX

MZMX is the original MZM format that was introduced in MZX 2.68. It is much more limited than newer MZM formats and is generally only supported for compatibility. MZMX only supports blocks with dimensions of 255 or smaller, does not save robots, and only supports storage mode 0. The MZMX header is 16 bytes long:

| Pos. | Size | Description | |------|--------|-------------| | 0 | s4 | `MZMX` | 4 | b | Width | 5 | b | Height | 6 | b * 10 | unused

The board data follows as described by MZM3's Storage Mode 0.


Counters File

The counters file format is a light wrapper around the MZX 2.90+ save format counter list and string list files. Counters files are created and loaded by the SAVE_COUNTERS and LOAD_COUNTERS pseudo-commands and currently have no standard filename extension. A counters file beings with this header:

| Pos. | Size | Description | |------|------|-------------| | 0 | s8 | Magic (COUNTERS)

This header is followed by a ZIP archive containing only the counter and string files.


Robotic Bytecode (.BC)

This section describes the general structure of Robotic bytecode. The specifics are out of the scope of this document. More info can be found in the file info_mzx.txt distributed with MegaZeux source packages.


License

Copyright © 2020 Lachesis — https://github.com/AliceLR/megazeux/

Permission to use, copy, modify, and distribute this document for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

THE DOCUMENT IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS DOCUMENT INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS DOCUMENT.