Westwood XOR Delta

From ModdingWiki
Jump to navigation Jump to search
Westwood XOR Delta
Format typeCompression algorithm
TypeStream
I/O unit size1-bit
Games

The Westwood XOR Delta compression is a format used in Westwood Studios's WSA animation file format, and in the C&C1/RA1 type of the Westwood SHP Format. It is commonly known as "Format 40" because it is indicated with byte value 0x40 in the frame headers of Command & Conquer 1 SHP files, the file type from which the format was originally reverse-engineered and documented.

Explanation

XOR Delta data applied to an image to create a new image.

The format is designed to be used when there are only minor differences between an image and a following one, and is often used for large animations. Animations in this format, as the name says, contain differences between frames to be xor-ed over each other.

Like Westwood LCW, the format is comparable to Code-type RLE, with byte commands containing manipulation instructions, but while RLE contains repeat and copy commands, the XOR Delta commands, which are applied to a second set of existing data, will either skip over a given number of bytes to leave them unmodified, or will apply an XOR operation on them, either using a single fixed value, or using a following range of bytes from the XOR commands buffer.

In Westwood RLE, a command with an unusable count of 0 is used as a different command that reads more bytes. This concept is expanded further in this format, to a total of six different commands. This principle is comparable to how expanding opcodes work in CPU instructions.

For the purpose of this explanation, the XOR data itself is in Source, and the image that the XOR needs to be applied to is already loaded in the Dest buffer. At the beginning of the operation, the pointers for both Source and Dest are set to the start of their respective buffers.

Note: in the following explanations, a dot (.) refers to a single bit, and a double underscore (__) means a whole byte.

Command 1: Short Skip

This command is 1 byte long. It can be identified by its highest bit being 1. The Count is 7 bits long.

              byte
 +---+---+---+---+---+---+---+---+
 | 1 | . | . | . | . | . | . | . |
 +---+---+---+---+---+---+---+---+
      \_________________________/
                  |
                Count

Skip Count bytes in Dest (move the pointer forward).

Command 2: Long Skip

This command is 3 bytes long. It can be identified by starting with a Short Skip command where the value is 0, with the highest bit in the following byte being 0. The Count is 15 bits long.

 +---+---+---+---+---+---+---+---+  +---+---+---+---+---+---+---+---+ +----+
 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |  | 0 | . | . | . | . | . | . | . | | __ |
 +---+---+---+---+---+---+---+---+  +---+---+---+---+---+---+---+---+ +----+
                                         \________________________________/
                                                         |
                                                       Count

Skip Count bytes.

Command 3: Long XOR

This command is 3 bytes long. It can be identified by starting with a Short skip command where the value is 0, with the highest bit in the following byte being 1, and the second-highest bit being 0. The Count is 14 bits long.

 +---+---+---+---+---+---+---+---+  +---+---+---+---+---+---+---+---+ +----+
 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |  | 1 | 0 | . | . | . | . | . | . | | __ |
 +---+---+---+---+---+---+---+---+  +---+---+---+---+---+---+---+---+ +----+
                                             \____________________________/
                                                           |
                                                         Count

Xor the next Count bytes. That means xor Count bytes from Source with bytes in Dest.

Command 4: XOR with Value

This command is 4 bytes long. This starts identically to the Long XOR, except that the second-highest bit in the second byte is 1. The Count is 14 bits long.

 +---+---+---+---+---+---+---+---+  +---+---+---+---+---+---+---+---+ +----+  +----+
 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |  | 1 | 1 | . | . | . | . | . | . | | __ |  | __ |
 +---+---+---+---+---+---+---+---+  +---+---+---+---+---+---+---+---+ +----+  +----+
                                             \____________________________/    Value
                                                           |
                                                         Count

Xor the next Count bytes in Dest with Value.

Command 5: Short XOR

This command is 1 byte long. It can be identified by its highest bit being 0. The Count is 7 bits long.

 +---+---+---+---+---+---+---+---+
 | 0 | . | . | . | . | . | . | . |
 +---+---+---+---+---+---+---+---+
      \_________________________/
                   |
                 Count

Xor the next Count bytes from Source with Dest.

Command 6: Long XOR With Value

This command is 3 bytes long. It can be identified by starting with a Short XOR command where the value is 0. The Count is 8 bits long.

 +---+---+---+---+---+---+---+---+  +----+  +----+
 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |  | __ |  | __ |
 +---+---+---+---+---+---+---+---+  +----+  +----+
                                     Count   Value

Xor the next Count bytes with Value.

All images end with a 80h 00h 00h command, which is a Long Skip with a Count of 0.

Code

Decompression

DP = destination pointer
SP = source pointer
Source is buffer containing the XOR Delta data
Dest is the buffer containing the image over which the second has to be xor-ed

  SP:=0;
  DP:=0;
  repeat
    Com:=Source[SP];
    Inc(SP);
 
    if (Com and $80)<>0 then {if bit 7 set}
    begin
      if Com<>$80 then  {small skip command (1)}
      begin
        Count:=Com and $7F;
        Inc(DP,Count);
      end
      else  {Big commands}
      begin
        Count:=Word(Source[SP]);
        if Count=0 then break;
        Inc(SP,2);
 
        Tc:=(Count and $C000) shr 14;  {Tc=two topmost bits of count}
 
        case Tc of
          0,1 : begin  {Big skip (2)}
                  Inc(DP,Count);
                end;
          2 : begin {big xor (3)}
                Count:=Count and $3FFF;
                for i:=1 to Count do
                begin
                  Dest[DP]:=Dest[DP] xor Source[SP];
                  Inc(DP);
                  Inc(SP);
                end;
              end;
          3 : begin  {big repeated xor (4)}
                Count:=Count and $3FFF;
                b:=Source[SP];
                Inc(SP);
                for i:=1 to Count do
                begin
                  Dest[DP]:=Dest[DP] xor b;
                  Inc(DP);
                end;
              end;
        end;
      end;
    end else  {xor command}
    begin
      Count:=Com;
      if Count=0 then
      begin {repeated xor (6)}
        Count:=Source[SP];
        Inc(SP);
        b:=Source[SP];
        Inc(SP);
        for i:=1 to Count do
        begin
          Dest[DP]:=Dest[DP] xor b;
          Inc(DP);
        end;
      end else  {copy xor (5)}
        for i:=1 to Count do
        begin
          Dest[DP]:=Dest[DP] xor Source[SP];
          Inc(DP);
          Inc(SP);
        end;
    end;
  until false;

Compression

Very few tools in the Command & Conquer modding community actually ever implemented XOR Delta compression. Even more versatile tool suites like XCC Mixer and the older RAMIX and Mix Manager packages never contained writers for the WSA format, the main file type that uses XOR Delta. Likewise, the use of XOR Delta in the C&C1/RA1 SHP format was never implemented.

A reconstruction of the original XOR Delta and LCW compression algorithms was written by OmniBlade of the Chronoshift (formerly RedAlert++) team, aided by descriptions of the used principles by Louis Castle himself, and was released as C++ code on the CnCNet forums.

A C# version of the code is available in the source code of Nyerguds's Engie File Converter, which handles a multitude of Westwood Studios formats.

OmniBlade's C++ code: (released under the GNU General Public License)

////////////////////////////////////////////////////////////////////////////////
//  Some utility functions to get worst case sizes for buffer allocation
////////////////////////////////////////////////////////////////////////////////

inline int XOR_Worst_Case(int datasize) {
	return datasize + ((datasize / 63) * 3) + 4;
}

////////////////////////////////////////////////////////////////////////////////
//  Some defines used by the encoders
////////////////////////////////////////////////////////////////////////////////
#define XOR_SMALL		127
#define XOR_MED			255
#define XOR_LARGE		16383
#define XOR_MAX			32767

////////////////////////////////////////////////////////////////////////////////
//  NAME:
//    Generate_XOR_Delta()
//
//  DESCRIPTION:
//    Generates a binary delta between two buffers. Mainly used for image data.
//
//  PARAMETERS:
//    dest   -- Buffer to store the generated delta.
//    source -- Buffer containing data to generate the delta for.
//    base   -- Buffer containing data that is the base for the delta.
//    size   -- Size of the two source buffers (or the smallest).
//
//  RETURN:
//    Size of the generated delta in bytes.
//
//  WARNINGS:
//    Delta can be larger than the source buffers in worse case, allocate 
//    storage accordingly.
//
////////////////////////////////////////////////////////////////////////////////
int Generate_XOR_Delta(void *dest, void *source, void *base, int size)
{
	uint8 *putp = static_cast<uint8*>(dest);	//our delta
	uint8 *getsp = static_cast<uint8*>(source);	//This is the image we go to
	uint8 *getbp = static_cast<uint8*>(base);	//This is image we come from
	uint8 *getsendp = getsp + size;

	//Only check getsp to save a redundant check. 
	//Both source and base should be same size and both pointers should be 
	//incremented at the same time.
	while ( getsp < getsendp) {
		uint fillcount = 0;
		uint xorcount = 0;
		uint skipcount = 0;
		uint8 lastxor = *getsp ^ *getbp;
		uint8 *testsp = getsp;
		uint8 *testbp = getbp;
		
		//Only evaluate other options if we don't have a matched pair
		while ( *testsp != *testbp && testsp < getsendp ) {
			if ( (*testsp ^ *testbp) == lastxor ) {
				++fillcount;
				++xorcount;
			} else {
				if ( fillcount > 3 ) {
					break;
				} else {
					lastxor = *testsp ^ *testbp;
					fillcount = 1;
					++xorcount;
				}
			}
			++testsp;
			++testbp;
		}
		
		//fillcount should always be lower than xorcount and should be greater
		//than 3 to warrant using the fill commands.
		fillcount = fillcount > 3 ? fillcount : 0;
		
		//Okay, lets see if we have any xor bytes we need to handle
		xorcount -= fillcount;
		while ( xorcount ) {
			uint16 count = 0;
			//Its cheaper to do the small cmd twice than do the large cmd once 
			//for data that can be handled by two small cmds.
			//cmd 0???????
			if ( xorcount < XOR_MED ) {
				count = xorcount <= XOR_SMALL ? xorcount : XOR_SMALL;
				*putp++ = count;
			//cmd 10000000 10?????? ??????
			} else {
				count = xorcount <= XOR_LARGE ? xorcount : XOR_LARGE;
				*putp++ = 0x80;
				*putp++ = count;
				*putp++ = (count >> 8) | 0x80;
			}

			while (count) {
				*putp++ = *getsp++ ^ *getbp++;
				--count;
				--xorcount;
			}
		}
		
		//lets handle the bytes that are best done as xorfill
		while ( fillcount ) {
			uint16 count = 0;
			//cmd 00000000 ????????
			if ( fillcount <= XOR_MED ) {
				count = fillcount;
				*putp++ = 0;
				*putp++ = count;
			//cmd 10000000 11?????? ??????
			} else {
				count = fillcount <= XOR_LARGE ? fillcount : XOR_LARGE;
				*putp++ = 0x80;
				*putp++ = count;
				*putp++ = (count >> 8) | 0xC0;
			}
				*putp++ = *getsp ^ *getbp;
				fillcount -= count;
				getsp += count;
				getbp += count;
		}
		
		//Handle regions that match exactly
		while ( *testsp == *testbp && testsp < getsendp) {
			++skipcount;
			++testsp;
			++testbp;
		}
		
		while ( skipcount ) {
			uint16 count = 0;
			//Again its cheaper to do the small cmd twice than do the large cmd 
			//once for data that can be handled by two small cmds.
			//cmd 1???????
			if ( skipcount < XOR_MED ) {
				count = skipcount <= XOR_SMALL ? skipcount : XOR_SMALL;
				*putp++ = count | 0x80;
			//cmd 10000000 0??????? ????????
			} else {
				count = skipcount <= XOR_MAX ? skipcount : XOR_MAX;
				*putp++ = 0x80;
				*putp++ = count;
				*putp++ = count >> 8;
			}
			skipcount -= count;
			getsp += count;
			getbp += count;
		}	
	}
	
	//final skip command of 0 to signal end of stream.
	*putp++ = 0x80;
	*putp++ = 0;
	*putp++ = 0;
	
	return putp - static_cast<uint8*>(dest);
}