Difference between revisions of "TBL File Format in KCD"

From Nexus Mods Wiki
Jump to: navigation, search
m
m
 
Line 216: Line 216:
 
[[Category:Kingdom_Come_Deliverance]]
 
[[Category:Kingdom_Come_Deliverance]]
 
[[Category:Game Info]]
 
[[Category:Game Info]]
 +
[[Category: Documentation]]

Latest revision as of 12:13, 25 July 2019

Top-Level File Format

Offset Name Type
0 Header Header
28 Rows Type Depends On Table Structure, count is Line Count in Header
28 + line size * line count String Data

Notes

  • Line size can be calculated as (file size - header size - string data size) / line count.
  • For more info about table data types, see Table Data Types in KCD

Header Format

Offset Name Type
0 File Format Version 32-bit signed int
4 Descriptors Hash 32-bit unsigned int
8 Layout Hash 32-bit unsigned int
12 Table Version 32-bit signed int
16 Line Count 32-bit signed int
20 String Data Size 32-bit signed int
24 Unique String Count 32-bit signed int

Python Script For Decoding TBL File

Example output:

python tblfile.py poi_type.tbl
======================================================================
== C:\WH\kcd_mod_kit\poi_type.tbl
======================================================================
Header:
  File Format Version ... 3
  Descriptors Hash ...... 1537641125
  Layout Hash ........... 2716330613
  Table Version ......... 1
  Line Count ............ 54
  String Data Size ...... 1241
  Unique String Count ... 55
  File Size ............. 5589
  Calculated Line Size .. 80 (rem. 0)
String Table:
  [0] ui_maplegend_carpenter
  [1] ui_maplegend_armourer
  [2] ui_maplegend_shoemaker
  ...
  [54] ui_maplegend_henhouse

Source:

# tblfile.py
# Usage: tblfile.py FILE [ FILE [...] ]
#        FILE - path to a TBL file (extracted, not in pak)

import struct
import sys
import os.path as path
import codecs


class TBLFileHeader:
    FORMAT = struct.Struct("iIIiiii")

    def __init__(self):
        self.file_format_version = None
        self.descriptors_hash = None
        self.layout_hash = None
        self.table_version = None
        self.line_count = None
        self.string_data_size = None
        self.unique_string_count = None

    def print(self, out, indent="", file_size=None):
        w = out.write
        w(indent)
        w("File Format Version ... {self.file_format_version}\n".format(self=self))
        w(indent)
        w("Descriptors Hash ...... {self.descriptors_hash}\n".format(self=self))
        w(indent)
        w("Layout Hash ........... {self.layout_hash}\n".format(self=self))
        w(indent)
        w("Table Version ......... {self.table_version}\n".format(self=self))
        w(indent)
        w("Line Count ............ {self.line_count}\n".format(self=self))
        w(indent)
        w("String Data Size ...... {self.string_data_size}\n".format(self=self))
        w(indent)
        w("Unique String Count ... {self.unique_string_count}\n".format(self=self))

        if not file_size is None:
            line_size, remainder = self.calculate_line_size(file_size)
            w(indent)
            w("File Size ............. {file_size}\n".format(file_size=file_size))
            w(indent)
            w("Calculated Line Size .. {line_size} (rem. {remainder})\n".format(line_size=line_size, remainder=remainder))

    @property
    def size(self):
        return self.FORMAT.size
    
    def unpack(self, buffer):
        t = self.FORMAT.unpack(buffer)
        (
            self.file_format_version,
            self.descriptors_hash,
            self.layout_hash,
            self.table_version,
            self.line_count,
            self.string_data_size,
            self.unique_string_count,
        ) = t
        return self

    def calculate_line_size(self, file_size):
        line_data_size = file_size - self.size - self.string_data_size
        remainder = line_data_size % self.line_count
        return int(line_data_size / self.line_count), remainder


class TBLFile:
    def __init__(self):
        self.header = TBLFileHeader()
        self.file_name = None
        self.file_size = None
        self.row_data = None
        self.string_data = None

    def print(self, out):
        out.write("======================================================================\n")
        out.write("== {}\n".format(self.file_name))
        out.write("======================================================================\n")
        out.write("Header:\n")
        self.header.print(out, indent="  ", file_size=self.file_size)

        if len(self.string_data) > 0:
            try:
                strings = codecs.decode(self.string_data).split("\0")[:-1]
                out.write("String Table:\n")
                for i, str in enumerate(strings):
                    out.write("  [{}] {}\n".format(i, str))
            except Exception as e:
                out.write(str(e))

    def read(self, file_name):
        self.file_name = file_name
        self.file_size = path.getsize(file_name)

        with open(file_name, "rb") as f:
            header_raw = f.read(self.header.size)
            self.header.unpack(header_raw)

            line_size, _ = self.header.calculate_line_size(self.file_size)

            self.row_data = f.read(line_size * self.header.line_count)
            self.string_data = f.read(self.header.string_data_size)


def main():
    for file_name in sys.argv[1:]:
        if not path.isfile(file_name):
            print("File {} doesn't exist!".format(file_name))
            continue

        header = TBLFileHeader()

        with open(file_name, "rb") as f:
            tbl_file = TBLFile()
            tbl_file.read(file_name)
            tbl_file.print(sys.stdout)
            print()


if __name__ == "__main__":
    main()