Executioners RLE Format
Executioners contains images compressed using Run Length Encoding. The scheme is used to handle the masking of certain images in-game, rather than to compress the image pixels themselves, and thus only compresses the transparent space in the image. It is practically identical to code-based RLE with the high bit indicating a repeat, except that no extra byte is read for the value to repeat, since technically those bytes are not repeated but skipped.
Each 'block' of RLE data begins with a 4-byte header that gives the height and width of the uncompressed image (and thus the block's decompressed size) as well as having two flags identifying it as a block of the right format.
|char||ID||0x10, indicates a valid block|
|char||END||0xFF, ends header|
The RLE scheme itself is relatively simple and relies on the data being composed of two kinds of pixels; image pixels which are always read as literals, and unmasked 'empty' pixels, which, in the game, are simply skipped and left to their existing values. This does, however, mean that no compression can be performed on any non-empty pixels. The scheme can be considered as follows:
- While the output data is less than
width * height:
- Read a byte
codefrom the input, advancing the read position. Extract isRepeat and value from it:
- Highest bit indicates a repeat:
isRepeat = (code & 0x80) != 0
- The remaining 7 bits are the value:
value = code & 0x7F
- Highest bit indicates a repeat:
- Evaluate isRepeat:
- If false, read and copy value amount of bytes from the input to the output, advancing the read and write positions.
- If true, skip value amount of bytes in the output, advancing the write position.
Since these are technically instructions for painting an image into an existing buffer, the image data needs to be handled line by line, since no automatic wraparound can occur in a larger buffer, and the system can't move its output pointer to a new position inside the process of handling one run-length value. This means runs will never cross over the end of a line and wrap to the next line.
Practically, the simplest way to decode an image is to make a buffer of the size specified in the header, and either fill it with 0xFF bytes before the decompression, or make the isRepeat step of the decompression write 0xFF bytes instead of skipping them.
However, since this format is meant to write images into an existing buffer, the exact value to be used for the 'empty pixels' is unspecified. Images can contain pixels with value 0xFF and display that color in the palette correctly. To get around this problem without losing a potentially usable colour, converters could output the mask as separate image when decoding, and read a separate image as mask when encoding.
It is technically possible to have zero-length runs of both literals and empty pixels, allowing compressed data to be arbitrarily larger than uncompressed data.
Implementation Restrictions & Bugs
- Even though the format technically allows it, images with a width or height of 0 pixels will corrupt the entire screen in-game.
- All original data written in this format seems to have an extra byte at the end. The values in such bytes don't seem to have any relation to the actual data, and the game does not seem to mind if they are not present. Most likely, they are just caused by copying one byte too much out of the buffer after compressing the data.