Task 347: .KT File Format

Task 347: .KT File Format

File Format Specifications for the .KT File Format

The .KT file format is the KTX (Khronos Texture) format, a binary container for GPU textures used in OpenGL, OpenGL ES, Vulkan, and other graphics APIs. It supports various image formats, mipmaps, array layers, cubemaps, and supercompression. The format is little-endian, with a fixed identifier for validation. The file structure includes a header, an index section, optional descriptors and data sections, and the actual texture data.

List of all the properties of this file format intrinsic to its file system:

  • Identifier: 12-byte fixed sequence for file validation.
  • vkFormat: UInt32 specifying the Vulkan format enum for the texture data.
  • typeSize: UInt32 indicating the size in bytes of the base data type.
  • pixelWidth: UInt32 for the width in pixels of the base level.
  • pixelHeight: UInt32 for the height in pixels of the base level.
  • pixelDepth: UInt32 for the depth in pixels of the base level (0 for 2D textures).
  • layerCount: UInt32 for the number of array layers.
  • faceCount: UInt32 for the number of cubemap faces (1 or 6).
  • levelCount: UInt32 for the number of mipmap levels.
  • supercompressionScheme: UInt32 indicating the supercompression method (e.g., 0 for none, 1 for BasisLZ).
  • dfdByteOffset: UInt32 offset to the Data Format Descriptor (DFD).
  • dfdByteLength: UInt32 length of the DFD.
  • kvdByteOffset: UInt32 offset to the Key/Value Data (KVD).
  • kvdByteLength: UInt32 length of the KVD.
  • sgdByteOffset: UInt64 offset to the Supercompression Global Data (SGD).
  • sgdByteLength: UInt64 length of the SGD.
  • Level Index: Array of levelCount entries, each with byteOffset (UInt64), byteLength (UInt64), and uncompressedByteLength (UInt64) for each mipmap level's data.

Two direct download links for files of format .KT:

Ghost blog embedded HTML JavaScript for drag and drop .KT file to dump properties to screen:

KT File Parser
Drag and drop a .KT file here
  1. Python class that can open any file of format .KT and decode read and write and print to console all the properties from the above list:
import struct
import sys

class KTFile:
    def __init__(self, filename):
        self.filename = filename
        self.properties = {}
        self.read_file()

    def read_file(self):
        with open(self.filename, 'rb') as f:
            data = f.read()

        # Identifier (bytes 0-11)
        self.properties['identifier'] = data[0:12]

        # Unpack little-endian
        self.properties['vkFormat'] = struct.unpack_from('<I', data, 12)[0]
        self.properties['typeSize'] = struct.unpack_from('<I', data, 16)[0]
        self.properties['pixelWidth'] = struct.unpack_from('<I', data, 20)[0]
        self.properties['pixelHeight'] = struct.unpack_from('<I', data, 24)[0]
        self.properties['pixelDepth'] = struct.unpack_from('<I', data, 28)[0]
        self.properties['layerCount'] = struct.unpack_from('<I', data, 32)[0]
        self.properties['faceCount'] = struct.unpack_from('<I', data, 36)[0]
        level_count = struct.unpack_from('<I', data, 40)[0]
        self.properties['levelCount'] = level_count
        self.properties['supercompressionScheme'] = struct.unpack_from('<I', data, 44)[0]
        self.properties['dfdByteOffset'] = struct.unpack_from('<I', data, 48)[0]
        self.properties['dfdByteLength'] = struct.unpack_from('<I', data, 52)[0]
        self.properties['kvdByteOffset'] = struct.unpack_from('<I', data, 56)[0]
        self.properties['kvdByteLength'] = struct.unpack_from('<I', data, 60)[0]
        self.properties['sgdByteOffset'] = struct.unpack_from('<Q', data, 64)[0]
        self.properties['sgdByteLength'] = struct.unpack_from('<Q', data, 72)[0]

        # Level Index
        self.properties['levelIndex'] = []
        for i in range(level_count):
            base = 80 + i * 24
            byte_offset = struct.unpack_from('<Q', data, base)[0]
            byte_length = struct.unpack_from('<Q', data, base + 8)[0]
            uncompressed_byte_length = struct.unpack_from('<Q', data, base + 16)[0]
            self.properties['levelIndex'].append({
                'byteOffset': byte_offset,
                'byteLength': byte_length,
                'uncompressedByteLength': uncompressed_byte_length
            })

        self.print_properties()

    def print_properties(self):
        print("KT File Properties:")
        for key, value in self.properties.items():
            if key == 'identifier':
                print(f"{key}: {value}")
            elif key == 'levelIndex':
                print(f"{key}:")
                for idx, level in enumerate(value):
                    print(f"  Level {idx}:")
                    for k, v in level.items():
                        print(f"    {k}: {v}")
            else:
                print(f"{key}: {value}")

    def write_file(self, output_filename):
        # Simple write: recreate header from properties, assuming no data sections for simplicity
        header = b''
        header += self.properties['identifier']
        header += struct.pack('<I', self.properties['vkFormat'])
        header += struct.pack('<I', self.properties['typeSize'])
        header += struct.pack('<I', self.properties['pixelWidth'])
        header += struct.pack('<I', self.properties['pixelHeight'])
        header += struct.pack('<I', self.properties['pixelDepth'])
        header += struct.pack('<I', self.properties['layerCount'])
        header += struct.pack('<I', self.properties['faceCount'])
        header += struct.pack('<I', self.properties['levelCount'])
        header += struct.pack('<I', self.properties['supercompressionScheme'])
        header += struct.pack('<I', self.properties['dfdByteOffset'])
        header += struct.pack('<I', self.properties['dfdByteLength'])
        header += struct.pack('<I', self.properties['kvdByteOffset'])
        header += struct.pack('<I', self.properties['kvdByteLength'])
        header += struct.pack('<Q', self.properties['sgdByteOffset'])
        header += struct.pack('<Q', self.properties['sgdByteLength'])

        for level in self.properties['levelIndex']:
            header += struct.pack('<Q', level['byteOffset'])
            header += struct.pack('<Q', level['byteLength'])
            header += struct.pack('<Q', level['uncompressedByteLength'])

        with open(output_filename, 'wb') as f:
            f.write(header)
        print(f"Written to {output_filename}")

# Example usage
if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python kt_parser.py input.kt [output.kt]")
        sys.exit(1)
    kt = KTFile(sys.argv[1])
    if len(sys.argv) > 2:
        kt.write_file(sys.argv[2])
  1. Java class that can open any file of format .KT and decode read and write and print to console all the properties from the above list:
import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class KTFile {
    private String filename;
    private Map<String, Object> properties = new HashMap<>();

    public KTFile(String filename) {
        this.filename = filename;
        readFile();
    }

    private void readFile() {
        try {
            byte[] data = Files.readAllBytes(Paths.get(filename));
            ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);

            // Identifier (bytes 0-11)
            byte[] identifier = new byte[12];
            buffer.position(0);
            buffer.get(identifier);
            properties.put("identifier", identifier);

            // vkFormat (UInt32 at 12)
            properties.put("vkFormat", buffer.getInt(12));

            // typeSize (UInt32 at 16)
            properties.put("typeSize", buffer.getInt(16));

            // pixelWidth (UInt32 at 20)
            properties.put("pixelWidth", buffer.getInt(20));

            // pixelHeight (UInt32 at 24)
            properties.put("pixelHeight", buffer.getInt(24));

            // pixelDepth (UInt32 at 28)
            properties.put("pixelDepth", buffer.getInt(28));

            // layerCount (UInt32 at 32)
            properties.put("layerCount", buffer.getInt(32));

            // faceCount (UInt32 at 36)
            properties.put("faceCount", buffer.getInt(36));

            // levelCount (UInt32 at 40)
            int levelCount = buffer.getInt(40);
            properties.put("levelCount", levelCount);

            // supercompressionScheme (UInt32 at 44)
            properties.put("supercompressionScheme", buffer.getInt(44));

            // dfdByteOffset (UInt32 at 48)
            properties.put("dfdByteOffset", buffer.getInt(48));

            // dfdByteLength (UInt32 at 52)
            properties.put("dfdByteLength", buffer.getInt(52));

            // kvdByteOffset (UInt32 at 56)
            properties.put("kvdByteOffset", buffer.getInt(56));

            // kvdByteLength (UInt32 at 60)
            properties.put("kvdByteLength", buffer.getInt(60));

            // sgdByteOffset (UInt64 at 64)
            properties.put("sgdByteOffset", buffer.getLong(64));

            // sgdByteLength (UInt64 at 72)
            properties.put("sgdByteLength", buffer.getLong(72));

            // Level Index
            ArrayList<Map<String, Long>> levelIndex = new ArrayList<>();
            for (int i = 0; i < levelCount; i++) {
                int base = 80 + i * 24;
                Map<String, Long> level = new HashMap<>();
                level.put("byteOffset", buffer.getLong(base));
                level.put("byteLength", buffer.getLong(base + 8));
                level.put("uncompressedByteLength", buffer.getLong(base + 16));
                levelIndex.add(level);
            }
            properties.put("levelIndex", levelIndex);

            printProperties();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void printProperties() {
        System.out.println("KT File Properties:");
        for (Map.Entry<String, Object> entry : properties.entrySet()) {
            if (entry.getKey().equals("identifier")) {
                byte[] id = (byte[]) entry.getValue();
                System.out.print(entry.getKey() + ": ");
                for (byte b : id) {
                    System.out.print(b + " ");
                }
                System.out.println();
            } else if (entry.getKey().equals("levelIndex")) {
                System.out.println(entry.getKey() + ":");
                ArrayList<Map<String, Long>> levels = (ArrayList<Map<String, Long>>) entry.getValue();
                for (int i = 0; i < levels.size(); i++) {
                    System.out.println("  Level " + i + ":");
                    for (Map.Entry<String, Long> levelEntry : levels.get(i).entrySet()) {
                        System.out.println("    " + levelEntry.getKey() + ": " + levelEntry.getValue());
                    }
                }
            } else {
                System.out.println(entry.getKey() + ": " + entry.getValue());
            }
        }
    }

    public void writeFile(String outputFilename) {
        try (RandomAccessFile file = new RandomAccessFile(outputFilename, "rw");
             FileChannel channel = file.getChannel()) {
            ByteBuffer buffer = ByteBuffer.allocate(80 + ((int)properties.get("levelCount") * 24)).order(ByteOrder.LITTLE_ENDIAN);

            // Identifier
            buffer.put((byte[])properties.get("identifier"));

            // vkFormat
            buffer.putInt((int)properties.get("vkFormat"));

            // typeSize
            buffer.putInt((int)properties.get("typeSize"));

            // pixelWidth
            buffer.putInt((int)properties.get("pixelWidth"));

            // pixelHeight
            buffer.putInt((int)properties.get("pixelHeight"));

            // pixelDepth
            buffer.putInt((int)properties.get("pixelDepth"));

            // layerCount
            buffer.putInt((int)properties.get("layerCount"));

            // faceCount
            buffer.putInt((int)properties.get("faceCount"));

            // levelCount
            buffer.putInt((int)properties.get("levelCount"));

            // supercompressionScheme
            buffer.putInt((int)properties.get("supercompressionScheme"));

            // dfdByteOffset
            buffer.putInt((int)properties.get("dfdByteOffset"));

            // dfdByteLength
            buffer.putInt((int)properties.get("dfdByteLength"));

            // kvdByteOffset
            buffer.putInt((int)properties.get("kvdByteOffset"));

            // kvdByteLength
            buffer.putInt((int)properties.get("kvdByteLength"));

            // sgdByteOffset
            buffer.putLong((long)properties.get("sgdByteOffset"));

            // sgdByteLength
            buffer.putLong((long)properties.get("sgdByteLength"));

            // Level Index
            ArrayList<Map<String, Long>> levels = (ArrayList<Map<String, Long>>) properties.get("levelIndex");
            for (Map<String, Long> level : levels) {
                buffer.putLong(level.get("byteOffset"));
                buffer.putLong(level.get("byteLength"));
                buffer.putLong(level.get("uncompressedByteLength"));
            }

            buffer.flip();
            channel.write(buffer);
            System.out.println("Written to " + outputFilename);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println("Usage: java KTFile input.kt [output.kt]");
            System.exit(1);
        }
        KTFile kt = new KTFile(args[0]);
        if (args.length > 1) {
            kt.writeFile(args[1]);
        }
    }
}
  1. JavaScript class that can open any file of format .KT and decode read and write and print to console all the properties from the above list:
const fs = require('fs'); // For Node.js

class KTFile {
    constructor(filename) {
        this.filename = filename;
        this.properties = {};
        this.readFile();
    }

    readFile() {
        const data = fs.readFileSync(this.filename);
        const dataView = new DataView(data.buffer);

        // Identifier (bytes 0-11)
        let identifier = '';
        for (let i = 0; i < 12; i++) {
            identifier += dataView.getUint8(i) + ' ';
        }
        this.properties.identifier = identifier.trim();

        // vkFormat (UInt32 at 12)
        this.properties.vkFormat = dataView.getUint32(12, true);

        // typeSize (UInt32 at 16)
        this.properties.typeSize = dataView.getUint32(16, true);

        // pixelWidth (UInt32 at 20)
        this.properties.pixelWidth = dataView.getUint32(20, true);

        // pixelHeight (UInt32 at 24)
        this.properties.pixelHeight = dataView.getUint32(24, true);

        // pixelDepth (UInt32 at 28)
        this.properties.pixelDepth = dataView.getUint32(28, true);

        // layerCount (UInt32 at 32)
        this.properties.layerCount = dataView.getUint32(32, true);

        // faceCount (UInt32 at 36)
        this.properties.faceCount = dataView.getUint32(36, true);

        // levelCount (UInt32 at 40)
        const levelCount = dataView.getUint32(40, true);
        this.properties.levelCount = levelCount;

        // supercompressionScheme (UInt32 at 44)
        this.properties.supercompressionScheme = dataView.getUint32(44, true);

        // dfdByteOffset (UInt32 at 48)
        this.properties.dfdByteOffset = dataView.getUint32(48, true);

        // dfdByteLength (UInt32 at 52)
        this.properties.dfdByteLength = dataView.getUint32(52, true);

        // kvdByteOffset (UInt32 at 56)
        this.properties.kvdByteOffset = dataView.getUint32(56, true);

        // kvdByteLength (UInt32 at 60)
        this.properties.kvdByteLength = dataView.getUint32(60, true);

        // sgdByteOffset (UInt64 at 64)
        this.properties.sgdByteOffset = Number(dataView.getBigUint64(64, true));

        // sgdByteLength (UInt64 at 72)
        this.properties.sgdByteLength = Number(dataView.getBigUint64(72, true));

        // Level Index
        this.properties.levelIndex = [];
        for (let i = 0; i < levelCount; i++) {
            const base = 80 + i * 24;
            this.properties.levelIndex.push({
                byteOffset: Number(dataView.getBigUint64(base, true)),
                byteLength: Number(dataView.getBigUint64(base + 8, true)),
                uncompressedByteLength: Number(dataView.getBigUint64(base + 16, true))
            });
        }

        this.printProperties();
    }

    printProperties() {
        console.log('KT File Properties:');
        for (const [key, value] of Object.entries(this.properties)) {
            if (key === 'levelIndex') {
                console.log(`${key}:`);
                value.forEach((level, idx) => {
                    console.log(`  Level ${idx}:`);
                    for (const [k, v] of Object.entries(level)) {
                        console.log(`    ${k}: ${v}`);
                    }
                });
            } else {
                console.log(`${key}: ${value}`);
            }
        }
    }

    writeFile(outputFilename) {
        const buffer = new ArrayBuffer(80 + this.properties.levelCount * 24);
        const dataView = new DataView(buffer);

        // Identifier
        const idBytes = this.properties.identifier.split(' ').map(Number);
        idBytes.forEach((byte, i) => dataView.setUint8(i, byte));

        // vkFormat
        dataView.setUint32(12, this.properties.vkFormat, true);

        // typeSize
        dataView.setUint32(16, this.properties.typeSize, true);

        // pixelWidth
        dataView.setUint32(20, this.properties.pixelWidth, true);

        // pixelHeight
        dataView.setUint32(24, this.properties.pixelHeight, true);

        // pixelDepth
        dataView.setUint32(28, this.properties.pixelDepth, true);

        // layerCount
        dataView.setUint32(32, this.properties.layerCount, true);

        // faceCount
        dataView.setUint32(36, this.properties.faceCount, true);

        // levelCount
        dataView.setUint32(40, this.properties.levelCount, true);

        // supercompressionScheme
        dataView.setUint32(44, this.properties.supercompressionScheme, true);

        // dfdByteOffset
        dataView.setUint32(48, this.properties.dfdByteOffset, true);

        // dfdByteLength
        dataView.setUint32(52, this.properties.dfdByteLength, true);

        // kvdByteOffset
        dataView.setUint32(56, this.properties.kvdByteOffset, true);

        // kvdByteLength
        dataView.setUint32(60, this.properties.kvdByteLength, true);

        // sgdByteOffset
        dataView.setBigUint64(64, BigInt(this.properties.sgdByteOffset), true);

        // sgdByteLength
        dataView.setBigUint64(72, BigInt(this.properties.sgdByteLength), true);

        // Level Index
        this.properties.levelIndex.forEach((level, i) => {
            const base = 80 + i * 24;
            dataView.setBigUint64(base, BigInt(level.byteOffset), true);
            dataView.setBigUint64(base + 8, BigInt(level.byteLength), true);
            dataView.setBigUint64(base + 16, BigInt(level.uncompressedByteLength), true);
        });

        fs.writeFileSync(outputFilename, new Uint8Array(buffer));
        console.log(`Written to ${outputFilename}`);
    }
}

// Example usage: node kt_parser.js input.kt [output.kt]
if (process.argv.length < 3) {
    console.log('Usage: node kt_parser.js input.kt [output.kt]');
    process.exit(1);
}
const kt = new KTFile(process.argv[2]);
if (process.argv.length > 3) {
    kt.writeFile(process.argv[3]);
}
  1. C class that can open any file of format .KT and decode read and write and print to console all the properties from the above list:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

typedef struct {
    uint8_t identifier[12];
    uint32_t vkFormat;
    uint32_t typeSize;
    uint32_t pixelWidth;
    uint32_t pixelHeight;
    uint32_t pixelDepth;
    uint32_t layerCount;
    uint32_t faceCount;
    uint32_t levelCount;
    uint32_t supercompressionScheme;
    uint32_t dfdByteOffset;
    uint32_t dfdByteLength;
    uint32_t kvdByteOffset;
    uint32_t kvdByteLength;
    uint64_t sgdByteOffset;
    uint64_t sgdByteLength;
    struct Level {
        uint64_t byteOffset;
        uint64_t byteLength;
        uint64_t uncompressedByteLength;
    } *levelIndex;
} KTFile;

void read_kt_file(const char *filename, KTFile *kt) {
    FILE *f = fopen(filename, "rb");
    if (!f) {
        perror("Error opening file");
        exit(1);
    }

    fseek(f, 0, SEEK_END);
    long size = ftell(f);
    fseek(f, 0, SEEK_SET);

    uint8_t *data = malloc(size);
    fread(data, 1, size, f);
    fclose(f);

    memcpy(kt->identifier, data, 12);

    memcpy(&kt->vkFormat, data + 12, 4);
    memcpy(&kt->typeSize, data + 16, 4);
    memcpy(&kt->pixelWidth, data + 20, 4);
    memcpy(&kt->pixelHeight, data + 24, 4);
    memcpy(&kt->pixelDepth, data + 28, 4);
    memcpy(&kt->layerCount, data + 32, 4);
    memcpy(&kt->faceCount, data + 36, 4);
    memcpy(&kt->levelCount, data + 40, 4);
    memcpy(&kt->supercompressionScheme, data + 44, 4);
    memcpy(&kt->dfdByteOffset, data + 48, 4);
    memcpy(&kt->dfdByteLength, data + 52, 4);
    memcpy(&kt->kvdByteOffset, data + 56, 4);
    memcpy(&kt->kvdByteLength, data + 60, 4);
    memcpy(&kt->sgdByteOffset, data + 64, 8);
    memcpy(&kt->sgdByteLength, data + 72, 8);

    kt->levelIndex = malloc(kt->levelCount * sizeof(struct Level));
    for (uint32_t i = 0; i < kt->levelCount; i++) {
        size_t base = 80 + i * 24;
        memcpy(&kt->levelIndex[i].byteOffset, data + base, 8);
        memcpy(&kt->levelIndex[i].byteLength, data + base + 8, 8);
        memcpy(&kt->levelIndex[i].uncompressedByteLength, data + base + 16, 8);
    }

    free(data);
}

void print_kt_properties(const KTFile *kt) {
    printf("KT File Properties:\n");
    printf("identifier: ");
    for (int i = 0; i < 12; i++) {
        printf("%u ", kt->identifier[i]);
    }
    printf("\n");
    printf("vkFormat: %u\n", kt->vkFormat);
    printf("typeSize: %u\n", kt->typeSize);
    printf("pixelWidth: %u\n", kt->pixelWidth);
    printf("pixelHeight: %u\n", kt->pixelHeight);
    printf("pixelDepth: %u\n", kt->pixelDepth);
    printf("layerCount: %u\n", kt->layerCount);
    printf("faceCount: %u\n", kt->faceCount);
    printf("levelCount: %u\n", kt->levelCount);
    printf("supercompressionScheme: %u\n", kt->supercompressionScheme);
    printf("dfdByteOffset: %u\n", kt->dfdByteOffset);
    printf("dfdByteLength: %u\n", kt->dfdByteLength);
    printf("kvdByteOffset: %u\n", kt->kvdByteOffset);
    printf("kvdByteLength: %u\n", kt->kvdByteLength);
    printf("sgdByteOffset: %lu\n", kt->sgdByteOffset);
    printf("sgdByteLength: %lu\n", kt->sgdByteLength);
    printf("levelIndex:\n");
    for (uint32_t i = 0; i < kt->levelCount; i++) {
        printf("  Level %u:\n", i);
        printf("    byteOffset: %lu\n", kt->levelIndex[i].byteOffset);
        printf("    byteLength: %lu\n", kt->levelIndex[i].byteLength);
        printf("    uncompressedByteLength: %lu\n", kt->levelIndex[i].uncompressedByteLength);
    }
}

void write_kt_file(const char *output_filename, const KTFile *kt) {
    size_t header_size = 80 + kt->levelCount * 24;
    uint8_t *header = malloc(header_size);

    memcpy(header, kt->identifier, 12);
    memcpy(header + 12, &kt->vkFormat, 4);
    memcpy(header + 16, &kt->typeSize, 4);
    memcpy(header + 20, &kt->pixelWidth, 4);
    memcpy(header + 24, &kt->pixelHeight, 4);
    memcpy(header + 28, &kt->pixelDepth, 4);
    memcpy(header + 32, &kt->layerCount, 4);
    memcpy(header + 36, &kt->faceCount, 4);
    memcpy(header + 40, &kt->levelCount, 4);
    memcpy(header + 44, &kt->supercompressionScheme, 4);
    memcpy(header + 48, &kt->dfdByteOffset, 4);
    memcpy(header + 52, &kt->dfdByteLength, 4);
    memcpy(header + 56, &kt->kvdByteOffset, 4);
    memcpy(header + 60, &kt->kvdByteLength, 4);
    memcpy(header + 64, &kt->sgdByteOffset, 8);
    memcpy(header + 72, &kt->sgdByteLength, 8);

    for (uint32_t i = 0; i < kt->levelCount; i++) {
        size_t base = 80 + i * 24;
        memcpy(header + base, &kt->levelIndex[i].byteOffset, 8);
        memcpy(header + base + 8, &kt->levelIndex[i].byteLength, 8);
        memcpy(header + base + 16, &kt->levelIndex[i].uncompressedByteLength, 8);
    }

    FILE *f = fopen(output_filename, "wb");
    if (!f) {
        perror("Error opening output file");
        exit(1);
    }
    fwrite(header, 1, header_size, f);
    fclose(f);
    free(header);
    printf("Written to %s\n", output_filename);
}

void free_kt(KTFile *kt) {
    free(kt->levelIndex);
}

int main(int argc, char **argv) {
    if (argc < 2) {
        printf("Usage: %s input.kt [output.kt]\n", argv[0]);
        return 1;
    }

    KTFile kt;
    read_kt_file(argv[1], &kt);
    print_kt_properties(&kt);

    if (argc > 2) {
        write_kt_file(argv[2], &kt);
    }

    free_kt(&kt);
    return 0;
}