Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

KFC File Format Specification

OUTDATED: This format is based on KFC2. The lastest game version uses KFC3 which is not documented yet.

Everything is little-endian unless otherwise noted.

The format of resources is defined in the resource section.

NameTypeSize (bytes)Description
magicuint324File signature: 4B 46 43 32 ("KFC2")
sizeuint324Size in bytes of the header area.
unknownuint324Always 12
paddinguint324Padding? Always 0
versionKFCLocation8Points to uint8[count] containing the version string.
containersKFCLocation8Points to ContainerInfo[count] describing .dat files.
unused0KFCLocation8Unused, always null location.
unused1KFCLocation8Unused, always null location.
resource_locationsKFCLocation8Points to ResourceLocation[count] describing where resources are stored within this file.
resource_indicesKFCLocation8Points to uint32[count] mapping ResourceBundleEntry::index to an index in resource_keys. See Resource Bundles.
content_bucketsKFCLocation8Points to StaticMapBucket[count] for content static map.
content_keysKFCLocation8Points to ContentHash[count] for content static map.
content_valuesKFCLocation8Points to ContentEntry[count] for content static map.
resource_bucketsKFCLocation8Points to StaticMapBucket[count] for resources static map.
resource_keysKFCLocation8Points to ResourceId[count] for resources static map.
resource_valuesKFCLocation8Points to ResourceEntry[count] for resources static map.
resource_bundle_bucketsKFCLocation8Points to StaticMapBucket[count] for resource bundles static map.
resource_bundle_keysKFCLocation8Points to uint32[count] for resource bundles static map. (the internal hash of the resource type)
resource_bundle_valuesKFCLocation8Points to ResourceBundleEntry[count] for resource bundles static map.

Addtional notes:

  • version is a non-null-terminated ASCII string.
  • resource_indices.count == resource_keys.count == resource_values.count

KFCLocation

NameTypeSize (bytes)Description
relative_offsetuint324The amount of bytes between the offset of this field and the start of the data it is pointing to.
countuint324The number of entries of the type being pointed to.

To get the absolute file offset of the data, add relative_offset to the file offset of the relative_offset field itself. For example, if the relative_offset field is at file offset 0x20 and its value is 0x100, the data starts at file offset 0x120.

ContainerInfo

NameTypeSize (bytes)Description
sizeuint648Total size of the .dat container file in bytes.
countuint648Number of contents in this container.

While the size is a uint64, ContentEntry uses a uint32 for the offset and size of each content, so no single content can be larger than 4 GiB.

ResourceLocation

NameTypeSize (bytes)Description
offsetuint324Offset to where the resources start in this file. (absolute)
sizeuint324Total size of all resources in bytes.
countuint324Number of resources.

There is currently always exactly one ResourceLocation entry. It may work with multiple entries, but this has not been tested yet.

StaticMapBucket

NameTypeSize (bytes)Description
indexuint324Start index into the map's key/value arrays.
countuint324Number of entries in this bucket. (linear probe range)

See Static Map for details.

ContentHash

A content hash is used to reference content assets within .dat files.

Here is how it is structured:

NameTypeSize (bytes)Description
sizeuint324The size of the content.
hash0uint324First part of the hash.
hash1uint324Second part of the hash.
hash2uint324Third part of the hash.

The hash of the content is computed with a custom algorithm which produces a 128-bit hash. The first 4 bytes of the result is then replaced with the size of the content.

The size field is used for determining the size of the content and there is no other way to get it.

Since content with the same data have the same ContentHash, you don't need to store the same content multiple times.

ContentEntry

NameTypeSize (bytes)Description
offsetuint324Offset inside the referenced .dat file where the content starts.
flagsuint162Currently unused, always 0.
container_indexuint162Index into the containers array.
paddinguint8[8]8Padding to make the struct 16 bytes long. Always 0.

Content is always aligned to 4096 bytes inside the .dat files. The size of the content is not stored here, but in the ContentHash.

ResourceId

A resource ID is used to reference resources within the .kfc file. It is a completely unique identifier for each resource. Here is how it is structured:

NameTypeSize (bytes)Description
hashuint32[4]16The hash of the resource.
type_hashuint324The qualified hash of the resource type.
part_indexuint324The index of the part if there are multiple instances of this resource.
reserved0uint324Reserved, always 0.
reserved1uint324Reserved, always 0.

The hash of the resource id is a randomly generated UUIDv4.

ResourceEntry

NameTypeSize (bytes)Description
offsetuint324Offset inside the resource location where the resource starts.
sizeuint324Size of the resource in bytes.

The absolute offset of the resource can be calculated by adding the offset to the offset of the ResourceLocation.

ResourceBundleEntry

NameTypeSize (bytes)Description
internal_hashuint324The internal hash of the resource type.
indexuint324Index into the resource_keys array.
countuint324Number of resources of this type.

Static Map

Static maps consists of three arrays: buckets, keys and values. There are always N buckets, where N is a power of two, and the key and value arrays must always have the same length.

Here is how to look up a key in a static map in pseudocode:

hash = hash_function(key) % buckets.count
bucket = buckets[hash]

for i in 0 until bucket.count:
    idx = bucket.index + i

    if keys[idx] == key:
        return values[idx]

return not_found

Hash Functions

Key TypeHash Function
ContentHashhash0 field of the ContentHash (precomputed)
ResourceIdA seeded FNV-1a32 over a 64-bit word formed from type_hash (low 32 bits) and part_index (high 32 bits) with hash[0] as the seed.
uint32The uint32 value itself.

Note: The 64-bit word data = type_hash | (part_index << 32) is serialized in little-endian byte order and then fed, byte-by-byte, into FNV-1a32.

FNV-1a32

See FNV hash on Wikipedia.

Here is the pseudocode for FNV-1a32 used by the game:

function fnv1a32(data: byte[]) -> uint32:
    return fnv1a32_with_seed(data, 0x811C9DC5)

function fnv1a32_with_seed(data: byte[], seed: uint32) -> uint32:
    hash = seed

    for byte in data:
        hash = hash XOR byte
        hash = hash * 0x01000193

    return hash

Resource Bundles

Resource bundles are a collection of resources of the same type. You can use them to efficiently look up all resources of a specific type.

Here is how to get all resources of a specific type in pseudocode:

bundle = resource_bundles.get(type_hash)
bundle_resources = []

for i in 0 until bundle.count:
    idx = bundle.index + i

    resource_index = resource_indices[idx]
    resource_id = resource_keys[resource_index]
    resource_entry = resource_values[resource_index]

    bundle_resources.append((resource_id, resource_entry))

return bundle_resources