Xeen Wiki
Register
Advertisement

The Xeen games use the CC file format to store many smaller files in a way that discourages users from making any modifications. Each official CC file contains between 100 and 1200+ files. A CC file may additionally be 'XORed', which merely requires that the stored file data be run through an extremely simple XOR routine before it can be used. The primary purpose of this XORing is to make any text strings impossible to find and read without first decoding the file. No form of compression is used.

"CC" stands for "concat".

Usage[]

CC files are used to hold three different sets of resources: game files, cut-scene files, and saved game files.

Resource CCs are used to store all the graphics, static map data, music, sounds, and other non-changing resources that make up that game's world. Each game has exactly one resource CC file, and it is always named after the four-digit code which internally identifies that game: XEEN for Clouds, DARK for the Darkside, and SWRD for Swords. These CCs are always XORed.

The Cutscene CC, INTRO.CC, is shipped with the Darkside of Xeen, and primarily stores the large cutscene graphics for the World of Xeen end-game sequences, as well as other cutscene resources such as background music. It also contains the Darkside introduction sequence. This CC is always XORed.

Saved games are also stored in CC format. These CCs contain the dynamic files which make up the state of the user's game world: the state of the party, character stats, and dynamic map data, including minimap data, which monsters have been killed, events that have been triggered, etc. These CCs are the only kind of CCs which are never XORed.

File Format[]

The file consists of three sections, the file count, table of contents, and the file data.

File Count[]

The first two bytes of the file are a uint16 which specifies the number of files stored in this CC. These two bytes are never XORed or encrypted in any way.

Table of Contents[]

This section immediately follows the file count, and always begins at offset 0x0002. This is an array of (8 * file count) bytes. This section is ALWAYS encrypted with a custom algorithm that is used only for this purpose.

To decrypt:

  • A counter byte is used, and initialized to 0xAC.
  • For each data byte in the TOC array:
    1. The data byte is bit-shifted so the bits are reordered from '01234567' to '23456701'
    2. The data byte has the counter byte added to it, and any overflow is ignored.
    3. The counter byte is incremented by 0x67, and any overflow is ignored.

The following pseudocode (C#) decrypts this section after it has been loaded into an array of bytes named rawToc:

// Read the TOC from the stream, and decode it
int ah = 0xac;
for (int i = 0; i < rawToc.Length; i++)
{
    rawToc[i] = (byte)(((rawToc[i] << 2 | rawToc[i] >> 6) + ah) & 0xff);
    ah += 0x67;
}

Note that rawToc is the entire array of all encrypted entries. To encrypt only one specific entry the counter byte must be initialized to account for previous entries:

int index /* index of this particular entry */
int ah = (0xac + index * 8 * 0x67) & 0xff;

Once decrypted, this array of bytes contain as an eight-byte TocEntry structure for each file stored in this CC.

TocEntry Structure
Offset Data Type Description
0x00 uint16 File ID
0x02 uint24 File offset
0x05 uint16 File length
0x07 byte Padding byte

The file ID is the hash of the original file's filename. Unfortunately, the original filename is not stored so it has been necessary to find or reverse-engineer a list of the original filenames.

The file offset is the absolute offset of this CC file at which the stored file begins. This is stored as a three-byte unsigned integer, with the least significant byte first. As this is a 24-bit number, the 'last' file stored in the CC file must begin at or before the 16,777,215th byte in the CC file - limiting the maximum CC filesize to approximately 16mb.

The file length specifies the length of the stored file. As a 16-bit integer, the largest individual file that can be stored is 65,535 bytes. For details on how the template saved game CC file - which is 250kb or larger in size - is stored, please see the Saved games section.

The padding byte should always be zero. If it is not zero, it probably means that the Table of Contents was not properly decrypted (see the section above).

To retrieve a file stored in a CC file:

  1. Convert the filename (eg. DARK.PAL) into a file ID: 0x48AA
  2. Scan through the TocEntries to find the entry matching that file ID.
  3. Read ('file length) bytes from the specified position (file offset) in the CC file.
  4. Perform any XORing (if necessary).

File Data / Encryption[]

The remainder of the file consists of the stored files.

If a CC is encrypted (all game resource CCs such as DARK.CC, XEEN.CC, and SWRD.CC are encrypted) then every byte in this section must be XORed with 0x35 (decimal 53) to return it to its original value. This XORing is done primarily to hide any text strings which may be visible, and discourage tampering with stored file data.

It is important to note that the CC file does not contain a value which indicates whether or not it must be XORed. Whatever program is working with the file must know whether it is encrypted or not. As a rule, saved-game CC files like DARK01.SAV, XEEN01.SAV, etc. are NOT XORed, where all others (XEEN.CC, DARK.CC, INTRO.CC, SWRD.CC) ARE XORed.

Filename hashing algorithm[]

To convert a filename to it's corresponding file ID, hash the filename using the following method. The "name" variable contains the filename, and the hashing algorithm is used on each byte of the filename (not including any null bytes at the end used to terminate the string).

int hashFileName( char *name, int len )
{
  int i, h;

  if( len < 1 ) return( -1 );

  for( i = 1, h = name[0] ; i < len ; h += name[i++] )
  {
    // Rotate the bits in 'h' right 7 places
    // In assembly it would be: ror h, 7
    // 01234567 89ABCDEF -> 9ABCDEF0 12345678
    // 0x007F = 00000000 01111111
    // 0xFF80 = 11111111 10000000
    h = ( h & 0x007F ) << 9 | ( h & 0xFF80 ) >> 7;
  }

  return( h );
}

Or if you prefer assembly code:

xor eax, eax
xor ebx, ebx
mov esi, name                 // esi points to the beginning of name
mov edx, esi
add edx, len                  // edx points to the end of name
HASH_LOOP_START:
  cmp esi, edx
  jge HASH_LOOP_END

  ror ax, 7                   // Rotate the bits in ax 7 places right
                              // 01234567 89ABCDEF -> 9ABCDEF0 12345678

  movzx bx, BYTE PTR [esi]    // Move the next byte in name to bx
  add ax, bx                  // Add the next byte in name to ax

  add esi, 1                  // Move to the next byte in name
  jmp HASH_LOOP_START         // Loop
HASH_LOOP_END:

Saved Game[]

The template for the saved game is stored in DARK.CC for the Darkside, and XEEN.CC for Clouds. As the saved game file is 240kb or larger, and the maximum size of a file that can be stored in a CC file is 65535 bytes, it is stored in up to six parts. These parts are stored at the start of the CC file in every observed game, and have the following IDs: 0x2A0C, 0x2A1C, 0x2A2C, 0x2A3C, 0x284C, and 0x2A5C. Note that they do not necessarily use all these IDs, it may only use from 0x2A0C to 0x284C, for example.

Advertisement