-
Notifications
You must be signed in to change notification settings - Fork 12
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.
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).
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) |
| ... | ... |
These are the outlined steps required in creating a key file, however a Code Example for generating them is below.
-
Create string using V_CODE1:
- Set string to V_CODE1 value
- (cs2_debug_key.dat) Append "@@--cs2-debug-key--@@"
- (key_com.dat) Append "@@--cs2-common-key--@@"
-
Generate Seed for Mersenne Twister:
- GenerateTocSeed with new V_CODE1 string
- (key.dat) Add volume serial number (volume of %WINDOWS% path)
-
Generate Blowfish Key and File data:
- MersenneTwister.SetSeed with new Seed
- MersenneTwister.GenRand 16 values (64 bytes for encryption key)
- MersenneTwister.GenRand 16 more values (64 bytes for key file data)
-
Encrypt Key File data:
- Blowfish.SetKey with new 64-byte key (first 16 MT values)
- Blowfish.Encrypt with 64-byte file data (next 16 MT values)
🚧 This section is a work in progress
-
Create string using V_CODE1:
- Set string to V_CODE1 value
-
Generate Blowfish Key:
- GenerateTocSeed with new V_CODE1 string
-
Generate Blowfish Key and File Hash:
- Blowfish.SetKey with new Seed (treat as 4-byte array)
- Create MD5 hash from target file (16 bytes)
- Blowfish.Encrypt MD5 hash value
-
Write Global Key Entry:
- Write target file name (32 bytes)
- Write encrypted MD5 hash (16 bytes)
- (repeat step 3 for each target file, append each to key data)
- Blowfish Cipher
- Mersenne Twister (except cs2_gk.dat)
- CRC-32, BZIP2 variation (see GenerateTocSeed)
- Game
V_CODEstring -
For key.dat only: Volume serial number (of
%WINDOWS%path) - For cs2_gk.dat only: MD5 hash algorithm
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 } }
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; } }
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); }