Skip to content

Key Files

Robert Jordan edited this page Dec 2, 2021 · 9 revisions

CatSystem2 Key Files

The CatSystem2 engine looks for 3 different key files in the install folder on launch. These keys are used as a basic form of DRM, and as a method to hide debug mode from players.

About each Key File

Each key file is 64 bytes long, and generated with slightly different methods. All key files are generated using the game's V_CODE value.

Note: The keys, key_com.dat and cs2_gk.dat are not used in modern CatSystem2 versions. Last appearance was around 2010.

Filename Usage Difference
direct.dat Launch game without a CD
/document/APP/direct is 1
(base behavior)
key.dat Launch game from CD install
when not direct launch
Add serial number to TocSeed
(for volume of %WINDOWS% path)
cs2_debug_key.dat Enable debug mode features
(always checked)
Append "@@--cs2-debug-key--@@"
to V_CODE
key_com.dat (old) Common key used before direct.dat key Append "@@--cs2-common-key--@@"
to V_CODE
cs2_gk.dat (old) Global key with exe hashes to prevent tampering Encrypted MD5 hashes for variable number of exes (see below)

Unlike key.dat with its required volume serial number, both direct.dat and cs2_debug_key.dat are a constant for each game (V_CODE).

Notes on cs2_gk.dat

Games requiring this key have never been encountered in assembly. However the file has appeared in a select number of trial games, like Djibril 4. This does not follow the typical 64-byte format of other keys. It contains encrypted MD5 hashes for a variable number of files that are considered "protected", in this case, we see the game binary (cs2.exe) and installer (install.exe).

Note: Newer engine versions using 64-character filenames in KIF archives seem to used char[64] filenames here as well.

Data Type Value Description
char[32] Cs2File Filename of cs2.exe for this game
byte[16] Cs2Hash Encrypted MD5 hash of Cs2File (Blowfish encryption using V_CODE seed as key)
char[32] InstallFile Filename of install.exe for this game
byte[16] InstallHash Encrypted MD5 hash of InstallFile (Blowfish encryption using V_CODE seed as key)
... ...

Generation

These are the outlined steps required in creating a key file, however a Code Example for generating them is below.

  1. Create string using V_CODE1:
    1. Set string to V_CODE1 value
    2. (cs2_debug_key.dat) Append "@@--cs2-debug-key--@@"
    3. (key_com.dat) Append "@@--cs2-common-key--@@"
  2. Generate Seed for Mersenne Twister:
    1. GenerateTocSeed with new V_CODE1 string
    2. (key.dat) Add volume serial number (volume of %WINDOWS% path)
  3. Generate Blowfish Key and File data:
    1. MersenneTwister.SetSeed with new Seed
    2. MersenneTwister.GenRand 16 values (64 bytes for encryption key)
    3. MersenneTwister.GenRand 16 more values (64 bytes for key file data)
  4. Encrypt Key File data:
    1. Blowfish.SetKey with new 64-byte key (first 16 MT values)
    2. Blowfish.Encrypt with 64-byte file data (next 16 MT values)

Generation (cs2_gk.dat)

🚧 This section is a work in progress

  1. Create string using V_CODE1:
    1. Set string to V_CODE1 value
  2. Generate Blowfish Key:
    1. GenerateTocSeed with new V_CODE1 string
  3. Generate Blowfish Key and File Hash:
    1. Blowfish.SetKey with new Seed (treat as 4-byte array)
    2. Create MD5 hash from target file (16 bytes)
    3. Blowfish.Encrypt MD5 hash value
  4. Write Global Key Entry:
    1. Write target file name (32 bytes)
    2. Write encrypted MD5 hash (16 bytes)
    3. (repeat step 3 for each target file, append each to key data)

Technical requirements

  • Blowfish Cipher
  • Mersenne Twister (except cs2_gk.dat)
  • CRC-32, BZIP2 variation (see GenerateTocSeed)
  • Game V_CODE string
  • For key.dat only: Volume serial number (of %WINDOWS% path)
  • For cs2_gk.dat only: MD5 hash algorithm

Code example

GenerateKeyFile

enum KeyFileType {
    Direct,      // "direct.dat"
    Key,         // "key.dat"  (requires volume serial number)
    Cs2DebugKey, // "cs2_debug_key.dat"
    CommonKey,   // "key_com.dat"
}
static class Cs2KeyFile {
    // Generate the data of a key file, that can then be saved to a game's installdir
    public static byte[] GenerateKeyFile(KeyFileType keyType, string vcode1) {
        // "open_cs2" encountered with cs2_open.exe Toolset v4.01, not fully understood
        //   (Likely used in the absence of /document/APP/v_code in startup.xml)
        //   (V_CODE (1) executable resource is ignored... I think)
        if (vcode1 == null)
            vcode1 = "open_cs2";

        //======== GENERATE MERSENNE TWISTER SEED ========
        // Difference for "cs2_debug_key.dat":  append "@@--cs2-debug-key--@@"
        if (keyType == KeyFileType.Cs2DebugKey)
            vcode1 += "@@--cs2-debug-key--@@";
        // Difference for "key_com.dat":  append "@@--cs2-common-key--@@"
        if (keyType == KeyFileType.CommonKey)
            vcode1 += "@@--cs2-common-key--@@";

        // Use a CRC-32-style checksum (same as in KIF Archives) of vcode for Mersenne Twister seed
        uint vcode1Seed = Util.GenerateTocSeed(vcode1);
        // Difference for "key.dat":  add volume serial number (of %WINDOWS% path)
        if (keyType == KeyFileType.Key)
            vcode1Seed += Util.GetVolumeSerialNumber();

        //======== GENERATE BLOWFISH KEY + FILE DATA ========
        // Generate MT PRNG values for the key file's Blowfish key and file data.
        //   (I've rarely seen Cs2 generate multiple MT values from one seed)
        uint[] tmpUIntBuffer = uint[32]; // C# workaround without unsafe
        byte[] blowfishKey = new byte[64]; // First 16 MT values
        byte[] fileData    = new byte[64]; // Next 16 MT values

        // Generate 32 MT values: Blowfish key (i=0-15), File data (i=16-31)
        MersenneTwister mt = new MersenneTwister();
        mt.SetSeed(vcode1Seed);
        for (int i = 0; i < 32; i++) {
            tmpUIntBuffer[i] = mt.GenRand();
        }
        // C# workaround uint[] -> byte[]: Blowfish key (i=0-63), File data (i=64-127)
        Buffer.BlockCopy(tmpUIntBuffer,  0, blowfishKey, 0, 64); 
        Buffer.BlockCopy(tmpUIntBuffer, 64, fileData,    0, 64);

        //======== ENCRYPT FILE DATA ========
        // Encrypt key file data using generated Blowfish key
        Blowfish bf = new Blowfish();
        blowfish.SetKey(blowfishKey);
        blowfish.Encrypt(fileData, 0, 64); // Encrypt to the same buffer

        return fileData; // Return the now-encrypted data
    }
}

GenerateTocSeed + GetVolumeSerialNumber

static class Util {
    // Same as used in KIFINT archives, (modified CRC-32/BZIP2)
    internal static uint GenerateTocSeed(string vcode) {
        uint crc = 0xffffffff; // Initial value
        for (int i = 0; i < vcode.Length; i++) {
            crc ^= ((uint) vcode[i] << 24);
            for (int j = 0; j < 8; j++) {
                if ((crc & 0x80000000) != 0)
                    crc = (crc << 1) ^ 0x04c11db7; // Polynomial
                else
                    crc <<= 1;
            }
            crc = ~crc; // Negate very byte instead of at the end
        }
        return crc;
    }
    // Get volume serial number of %Windows% path on the target machine
    internal static uint GetVolumeSerialNumber() {
        // Includes path root, thus is accepted by the WINAPI function
        string rootPathName = Environment.GetFolderPath(Environment.SpecialFolder.Windows);

        // Be aware: there is a difference between hard drive and volume serial numbers
        bool result = NativeMethods.GetVolumeInformation(
            rootPathName,
            null, 0, // (unused parameters)
            out uint volumeSerialNumber,
            out _, out _, null, 0); // (unused parameters)
        if (!result)
            throw new Win32Exception();
        return volumeSerialNumber;
    }
}

NativeMethods

static class NativeMethods {
    // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationa
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal extern static bool GetVolumeInformation(
        string rootPathName, // only input
        StringBuilder volumeNameBuffer,
        int volumeNameSize,
        out uint volumeSerialNumber,
        out uint maximumComponentLength,
        out uint fileSystemFlags, // flags that we don’t need
        StringBuilder fileSystemNameBuffer,
        int nFileSystemNameSize);
}
Clone this wiki locally