Difference between revisions of "TBL File Format in KCD"
AdamSporka (talk | contribs) m (moved KCD TBL File Format to TBL File Format in KCD: Ensuring a better alpha order of articles) |
Pickysaurus (talk | contribs) m |
||
(5 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
== Top-Level File Format == | == Top-Level File Format == | ||
− | {| | + | {| class="wikitable" |
!Offset | !Offset | ||
!Name | !Name | ||
!Type | !Type | ||
|- | |- | ||
− | |0 | + | |<code>0</code> |
|Header | |Header | ||
|Header | |Header | ||
|- | |- | ||
− | |28 | + | |<code>28</code> |
|Rows | |Rows | ||
|Type Depends On Table Structure, count is <code>Line Count</code> in <code>Header</code> | |Type Depends On Table Structure, count is <code>Line Count</code> in <code>Header</code> | ||
+ | |- | ||
+ | |<code>28 + line size * line count</code> | ||
+ | |String Data | ||
|} | |} | ||
+ | |||
+ | === Notes === | ||
+ | * Line size can be calculated as <code>(file size - header size - string data size) / line count</code>. | ||
+ | * For more info about table data types, see [[Table Data Types in KCD]] | ||
== Header Format == | == Header Format == | ||
− | {| | + | {| class="wikitable" |
!Offset | !Offset | ||
!Name | !Name | ||
Line 50: | Line 57: | ||
|32-bit signed int | |32-bit signed int | ||
|} | |} | ||
+ | |||
+ | == Python Script For Decoding TBL File == | ||
+ | |||
+ | Example output: | ||
+ | |||
+ | <pre> | ||
+ | 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 | ||
+ | </pre> | ||
+ | |||
+ | Source: | ||
+ | |||
+ | <pre> | ||
+ | # 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() | ||
+ | </pre> | ||
[[Category:Kingdom_Come_Deliverance]] | [[Category:Kingdom_Come_Deliverance]] | ||
+ | [[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()