Skip to content

Key Files

Robert Jordan edited this page Mar 9, 2020 · 9 revisions

CatSystem2 Key Files

The CatSystem2 engine looks for 3 different key files in the install folder on launch.

FileName Usage
cs2_debug_key.dat Enables debug mode features
direct.dat Required to launch the game without a CD
Used when /document/APP/<direct>1</direct>
key.dat Usage is not yet known
Likely used for CD installations

Generation

enum KeyFileType {
    [Description("direct.dat")]
    Direct = 0,      // "direct.dat"
    Key = 1,         // "key.dat"
    Cs2DebugKey = 2, // "cs2_debug_key.dat"
}
class KeyFileGenerator {
    #region Constants
    // Filenames of different key types, hardcoded in CatSystem2
    const string DirectFileName = "direct.dat";
    const string KeyFileName = "direct.dat";
    const string Cs2DebugKeyFileName = "direct.dat";

    // Encountered with cs2_open.exe Toolset v4.01, not fully understood
    // Likely used in the absence of /document/APP/v_code in startup.xml
    const string DefaultVCode1  = "open_cs2";
    //const string Cs2VCode1 = "cs2 V_CODE";
    #endregion

    #region GenerateTocSeed
    // Same as used in KIFINT archives
    // CRC-32/BZIP2 (but we negate after every byte, instead of at the end)
    public static uint GenerateTocSeed(string vcode) {
      unchecked {
        uint crc = 0xffffffff;
        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;
        }
        return crc;
      }
    }
    #endregion

    // Because "key.dat" requires volume serial number, we can supply our own when not running on the target machine
    public static byte[] CreateKeyFile(KeyFileType keyType, [Optional] string vcode1, [Optional] uint? volumeSerialNumber) {

        // 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 ignore... I think
        if (vcode1 == null)
            vcode1 = "open_cs2";
        // Extra step for "cs2_debug_key.dat":  + "@@--cs2-debug-key--@@";
        if (keyType == KeyFileType.Cs2DebugKey)
            vcode1 += "@@--cs2-debug-key--@@";
        
        // Use a CRC-32-style checksum of vcode for Mersenne Twister seed
        uint vcode1Seed = GenerateTocSeed(vcode1String);
        // Extra step for "key.dat":  + volumeSerialNumber
        if (keyType == KeyFileType.Key) {
            if (!volumeSerialNumber.HasValue) {
                // Get current machine's volume serial number
                //   For volume where /Windows is present
                volumeSerialNumber = GetVolumeSerialNumber();
            }
            unchecked {
                vcode1Seed += volumeSerialNumber.Value;
            }
        }

        // Generate PRNG values for the key file's data and encryption key.
        // One of the few times I’ve seen Mersenne Twister used to generate multiple numbers per seed in CatSystem2
        MersenneTwister mt = new MersenneTwister();
        mt.SetSeed(vcode1Seed);

        uint[] tmpUIntBuffer = uint[16];
        byte[] keyFileData = new byte[64];       // First 16 MT values
        byte[] keyFileBlowfishKey = new byte[64]; // Next 16 MT values

        // Generate key file data (mti=0,15)
        for (int i = 0; i < 16; i++) {
            tmpUIntBuffer[i] = mt.GenRand();
        }
        Buffer.BlockCopy(tmpUInts, 0, keyFileData, 0, 64);

        // Generate key to encrypt key file data (mti=16,31)
        for (int i = 0; i < 16; i++) {
            tmpUIntBuffer[i] = mt.GenRand();
        }
        Buffer.BlockCopy(tmpUInts, 0, keyFileBlowfishKey, 0, 64);

        // Encrypt key file data
        Blowfish bf = new Blowfish();
        blowfish.SetKey(keyFileBlowfishKey);
        blowfish.Encrypt(keyFileData, 0, 64);

        return keyFileData;
    }

    // Get volume serial number on the target machine
    // Default volume is where /Windows is present
    uint GetVolumeSerialNumber([Optional] string rootPathName) {
        if (rootPathName == null) {
            // Get serial number of volume where /Windows is present
            rootPathName = Environment.GetSpecialFolder(Environment.SpecialFolder.Windows);
        }
        bool result GetVolumeInformation(
            Path.GetRoot(windowsRoot),
            null, 0,
            out uint volumeSerialNumber,
            out _,
            out _,
            null, 0);
            volumeSerialNumber = currentSerialNumber;
        if (!result)
            throw new Win32Exceprion();
        return volumeSerialNumber;
    }

    #region Native Methods
    [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private extern static bool GetVolumeInformation(
        [In] 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);
    #endregion
}

Clone this wiki locally