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 apparantly choose this compression technique to deal with
those long runs of zeros. The usefull 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.