RUN LENGTH ENCODING / DECODING

Back to RCT home

The TD4 and save game files in RCT are compressed with a Run Length Encoding technique. The uncompressed files have a fixed length and contain a lot of "00" bytes. The author apparently choose this compression technique to deal with those long runs of zeros. The useful data does not compress well with this technique but it is quick and simple. The last four bytes of the files contain a checksum. When you decode a file, omit these last four bytes. When you encode a file, calculate the checksum and add those four bytes onto the end of the file.
DECODING
START
1) read a byte (B) from the file
2) Test the most significant bit (MSB) of that byte
2a) if MSB=0 then B is a counter of how many bytes to copy. Execute the following loop: for COUNTER = 1 to B+1 {read a byte from the file and copy it to your target data stream.}
2b) however, if MSB=1 then B is flag to duplicate data. Read the next byte and copy it (-B+1) times to your target data stream.
3) if end of file not reached (remember to omit the last four bytes as they are a checksum) goto START
*******************************
For example, imagine a file containing the following data (no checksum)
00 47 FF 6F 05 64 20 6A 6F 62 21
The initial 00 indicates that the next 1 byte goes into your target stream: "47"
The next byte is FF which indicates the next byte should be copied 2 times: "6F 6F"
The next byte is 05 which indicates the next 6 bytes should go into the target: "64 20 6A 6F 62 21"
This gives "47 6F 6F 64 20 6A 6F 62 21" which is the ASCII code for "Good job!". This example shows how a compressed message of 11 bytes is decompressed to a message of 9 bytes. As mentioned, this is not a good technique for general compression but it works well for long runs of "00" bytes.
To compress a file, just reverse the process. You should limit the copies to a maximum of 125 bytes as this is a size limit in the internal RCT program.
The following is a Delphi unit. It is called with a file name (including path) and fills the previously created TMemoryStream object with the decoded contents of that file. Although it uses TD4 in the variable names, it will work with SV4 files as well.



unit Utils1;

interface
uses classes;

procedure DecodeTD4file(TD4FileName: string; Td4Stream : TMemoryStream);

implementation

procedure DecodeTD4file(TD4FileName: string; Td4Stream : TMemoryStream);
var FileBuffer : TMemoryStream;
Databyte, CopyByte: byte;
EncodeByte : shortint; //signed 8-bit number
i : integer;
begin
TD4Stream.clear;
FileBuffer := TMemoryStream.create;
FileBuffer.LoadFromFile(TD4FileName);
FileBuffer.Position := 0; // move to beginning of buffer
//the last 4 bytes are a checksum which should not be "decoded"
while FileBuffer.Position < (FileBuffer.Size-4) do
begin
FileBuffer.Read(EncodeByte, 1); // read one byte
if (EncodeByte >= 0)
then begin {positive value - copy this many bytes}
for i:=1 to (EncodeByte+1) do
begin
FileBuffer.Read(CopyByte, 1);
TD4Stream.Write(CopyByte, 1);
end;
end
else begin {negative value - copy next byte this many times}
EncodeByte := 1 - EncodeByte;
FileBuffer.Read(CopyByte, 1);
for i:=1 to EncodeByte do TD4Stream.Write(CopyByte, 1);
end;
end;
FileBuffer.free;
end;

end.