Skip to content

KIF Archive

Robert Jordan edited this page Aug 28, 2020 · 5 revisions

Format fully deciphered.

The extension (.int) stands for "Integrated File" (as shown from assembly log message: "統合ファイルオープン : %s").
As such, the file signature (KIF) likely stands for "kclib Integrated File".

This format has been fully known for a long time.

File Structure

Data Type Value Description
char[4] "KIF" File Signature
uint32 EntryCount Number of entries in archive

Entry Table

N = EntryCount

Data Type Value Description
byte[64] FileNameRaw Obfuscated Name of the entry's file, or "__key__.dat"
uint32 Offset The offset to the entry's data
uint32 Length The length of the entry's data

EntryData Table

Position = Entries[i].Offset
N = EntryCount

Data Type Value Description
byte[Entries[i].Length] Data The raw data of the file

Additional Notes

Entry.FileNameRaw is listed as a byte[64] because the original read bytes are needed during UnobfuscateFileName (except for "__key__.dat"). Below is a property to access the FileName of an Entry. It shall be assumed that this get-only property is contained within the Entry structure definition during code samples.

string FileName {
    get {
        Encoding shiftJIS = Encoding.GetEncoding(932);
        int index = Array.IndexOf(fileNameRaw, (byte) 0);
        int length = (index != -1 ? index : fileNameRaw.Length);
        return shiftJIS.GetString(fileNameRaw, 0, length);
    }
}

See DecryptEntries for a code sample on how entries are decrypted.
See DecryptEntryData for a code sample on how entry data is decrypted.

Decryption

KIFINT archives are usually not readable without decryption.

Requirements

Key Name Location
V_CODE2 A resource in the CatSystem2 executable
"__key__.dat" An entry in Entry Table with this FileName
TocSeed The table of contents seed obtained from the V_CODE2
FileKey The Blowfish key obtained from "__key__.dat"

Key: V_CODE2

See V_CODEs for more information on how to acquire the V_CODE2. The V_CODE2 is used in GenerateTocSeed. The

Key: "__key__.dat" Entry

The "__key__.dat" Entry is a single Entry in the Entry Table signifies that the archive is encrypted, if it is not present, then no decryption is necessary, and none of the requirements are needed.

The "__key__.dat" Entry has the same size and structure as an normal Entry, but the values have different uses:

Data Type Value Description
char[64] "__key__.dat" (FileNameRaw) Signifies this is the key entry
uint32 Unknown (Offset) Value is non-zero
uint32 Seed (Length) The seed to pass to Mersenne Twister

Unlike a normal encrypted Entry, "__key__.dat"'s FileName will always be visible without calling UnobfuscateFileName. As such, it's data type is listed as char[64] instead of byte[64].

Key: TocSeed

The table of contents seed (TocSeed in code) is half of a Mersenne Twister seed used during the unobfuscation of encrypted Entry FileNames.

See GenerateTocSeed on how to get the TocSeed from the V_CODE2.

Key: FileKey

The FileKey is generated by using Mersenne Twister with "__key__.dat"'s Seed (Length) as the input seed.

The FileKey is then used as the key to initialize the Blowfish cipher which is used for the actual decryption of the Entry Table and EntryData Table.

Implementation

DecryptEntries

// Returns true and outputs a non-null blowfish if the entries are encrypted
bool DecryptEntries(Header header, Entry[] entries, string vcode2, out Blowfish blowfish) {
    Encoding shiftJIS = Encoding.GetEncoding(932);
    
    uint tocSeed = GenerateTocSeed(vcode2);
    uint fileKey = 0;
    int fileKeyIndex = -1;
    blowfish = null;

    // Not decrypted, our work here is done
    if (fileKeyIndex == -1)
        return false;

    // Obtain the decryption file key if one exists
    for (int i = 0; i < header.EntryCount; i++) {
        if (entries[i].FileName == "__key__.dat") {
            fileKey = MersenneTwister.GenRand(entries[i].Length); // Seed
            blowfish = new Blowfish(fileKey);
            fileKeyIndex = i;
            break;
        }
    }

    // Decrypt the entries using the file key
    if (fileKeyIndex != -1) {
        for (int i = 0; i < header.EntryCount; i++) {
            if (i == fileKeyIndex)
                continue;

            // Correct the entry's file name
            UnobfuscateFileName(entries[i].FileNameRaw, unchecked(tocSeed + (uint) i));
            
            // Apply the extra offset before decryption
            entries[i].Offset += i;

            // The following part can be done a better way in C#,
            // but is shown this way to make the explanation easier.

            // Get the bytes for the entry's encrypted offset and length combined
            byte[] entryBytes = new byte[8];
            Array.Copy(BitConverter.GetBytes(entries[i].Offset), 0, entryBytes, 0, 4);
            Array.Copy(BitConverter.GetBytes(entries[i].Length), 0, entryBytes, 4, 4);

            // Decrypt the entry's offset and length
            blowfish.Decrypt(entryBytes);

            // Return the entry's decrypted offset and length back to the entry
            entries[i].Offset = BitConverter.ToUInt32(entryBytes, 0);
            entries[i].Length = BitConverter.ToUInt32(entryBytes, 4);
        }
    }

    return true;
}

DecryptEntryData

Here is example code for how you would decrypt the data for an Entry. Note how the remaining bytes that are not a multiple of 8 are ignored and thus, are always unencrypted.

void DecryptEntryData(byte[] entryData, Blowfish blowfish) {
    // Decryption length of data is rounded down to the previous
    // multiple of 8 bytes. The remaining 0-7 bytes are unencrypted.
    // & ~7 is equivalent to `(int) / 8 * 8`.
    blowfish.Decrypt(data, data.Length & ~7);
}

GenerateTocSeed

See Key: TocSeed.

uint GenerateTocSeed(string vcode2) {
    const uint magic = 0x04C11DB7;
    uint seed = 0xFFFFFFFF;

    // For each character in V_CODE2
    for (int i = 0; i < vcode2.Length; i++) {
        seed ^= ((uint) vcode2[i]) << 24;

        for (int j = 0; j < 8; j++) {
            if ((seed & 0x80000000) != 0) {
                seed *= 2;
                seed ^= magic;
            }
            else {
                seed *= 2;
            }
        }

        seed = ~seed;
    }

    return seed;
}

UnobfuscateFileName

See Key: TocSeed.

void UnobfuscateFileName(byte[] fileName, uint seed) {
    const int Length = 52;
    const string FWD = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    const string REV = "zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA";

    uint key = MersenneTwister.GenRand(seed);
    int shift = (byte) ((key >> 24) + (key >> 16) + (key >> 8) + key);

    for (int i = 0; i < fileName.Length; i++, shift++) {
        byte c = fileName[i];

        if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
            int index = 0;
            int index2 = shift;

            while (REV[index2 % Length] != c) {
                if (REV[(shift + index + 1) % Length] == c) {
                    index += 1;
                    break;
                }

                if (REV[(shift + index + 2) % Length] == c) {
                    index += 2;
                    break;
                }

                if (REV[(shift + index + 3) % Length] == c) {
                    index += 3;
                    break;
                }

                index += 4;
                index2 += 4;

                if (index >= Length) // We're outside the array, no need to continue
                    break;
            }

            if (index < Length) // Only assign if we're inside the array
                fileName[i] = (byte) FWD[index];
        }
    }
}
Clone this wiki locally