Game tables file formats - rebaserHEAD/NexusForever GitHub Wiki

The three file formats the server reads at start-up, as its own readers read them: game tables (.tbl), text tables (.bin), and the map files NexusForever.MapGenerator writes (.nfmap). All integers are little-endian. How the server uses the tables is on Game tables.
Game tables
Header
96 bytes. Every offset after it counts from the end of the header.
| Offset |
Field |
Type |
Read as |
| 0 |
Signature |
u32 |
Must be 0x4454424C, DTBL |
| 4 |
Version |
u32 |
Not checked |
| 8 |
NameLength |
u64 |
Unused |
| 16 |
Unknown1 |
u64 |
Unused |
| 24 |
RecordSize |
u64 |
Bytes per row |
| 32 |
FieldCount |
u64 |
Number of column descriptors |
| 40 |
FieldOffset |
u64 |
Where the column descriptors start |
| 48 |
RecordCount |
u64 |
Number of rows |
| 56 |
TotalRecordSize |
u64 |
Rows plus the string table, in bytes |
| 64 |
RecordOffset |
u64 |
Where the first row starts |
| 72 |
MaxId |
u64 |
Size of the id lookup the reader builds |
| 80 |
LookupOffset |
u64 |
Unused; the reader builds its own lookup |
| 88 |
Unknown2 |
u64 |
Unused |
Column descriptors
24 bytes each, FieldCount of them.
| Offset |
Field |
Type |
Read as |
| 0 |
NameLength |
u64 |
Unused |
| 8 |
NameOffset |
u64 |
Unused: the reader never reads column names |
| 16 |
Type |
u16 |
The column's data type, below |
| 18 |
Unknown2 |
u16 |
Unused |
| 20 |
Unknown3 |
u32 |
Unused |
Data types
| Value |
Name |
Bytes in the row |
Row model field |
| 3 |
UInt |
4 |
uint, or any enum |
| 4 |
Single |
4 |
float |
| 11 |
Boolean |
4 |
bool, true when non-zero |
| 20 |
ULong |
8 |
ulong |
| 130 |
String |
8 |
string, see below |
Rows and strings
- Row
i starts at RecordOffset + i * RecordSize, and each row is read from its own start, so a bad row can't shift the next one.
- The first column is the row's id.
- The string table starts right after the last row. A string column holds two u32 offsets; the string is at the larger one, less the size of all the rows, read as null-terminated UTF-16.
- After a string column whose first offset is 0, the reader skips 4 more bytes unless the next field is also a string.
When a model doesn't match
| Condition |
Result |
| File shorter than the header, or a wrong signature |
InvalidDataException |
| Debug: field count differs from column count |
GameTableException, "GameTable model has an invalid field count" |
| Debug: a field's type differs from its column's |
GameTableException, "Invalid GameTable model field type" |
| Release: either of the above |
No check. Values are read into the wrong fields |
An id of MaxId or more |
IndexOutOfRangeException |
Text tables
Header
96 bytes.
| Offset |
Field |
Type |
Read as |
| 0 |
Signature |
u32 |
Must be 0x4C544558, LTEX |
| 4 |
Version |
u32 |
Not checked |
| 8 |
Language |
i32 |
1 English, 2 German, 3 French, 5 Chinese |
| 12 |
Unknown1 |
u32 |
Unused |
| 16 |
TagNameLength, TagNameOffset |
u64, u64 |
Unused |
| 32 |
ShortNameLength, ShortNameOffset |
u64, u64 |
Unused |
| 48 |
LongNameLength, LongNameOffset |
u64, u64 |
Unused |
| 64 |
RecordCount |
u64 |
Number of strings |
| 72 |
RecordOffset |
u64 |
Where the records start |
| 80 |
StringTableLength |
u64 |
Length of the string table, in UTF-16 characters |
| 88 |
StringTableOffset |
u64 |
Where the string table starts |
Records
Each record is a u32 id and a u32 offset into the string table, counted in UTF-16 characters. The reader sizes its lookup from the last record's id, so it expects records in id order.
| File |
Language |
Required |
en-US.bin |
English |
Yes |
de-DE.bin |
German |
No; a warning when missing |
fr-FR.bin |
French |
No; a warning when missing |
Map files
Layout
One .nfmap per world, named after the world's asset folder.
| Part |
Fields |
| Header |
u32 magic 0x504D464E, NFMP; u32 version, which must be 4; u32 client build, which must be 16042 |
| Asset |
The world's asset path, as a length-prefixed string |
| Grids |
u32 count, then each grid |
| Grid |
u32 X, u32 Y, u32 cell count, then each cell |
| Cell |
u32 X, u32 Y, u32 flags, then a block for each flag set, in bit order |
The file must end exactly after the last grid.
Cell blocks
| Flag |
Name |
Block |
0x01 |
Zone |
4 u32 world zone ids |
0x02 |
Height |
17 × 17 half-precision heights |
0x10 |
ZoneBound |
64 × 64 bytes of zone and aura flags |
0x04 (Aura) and 0x08 (Liquid) are declared but have no block, and the reader throws on either.
World geometry
| Constant |
Value |
| Grids per axis |
128, with grid 64 at world position 0 |
| Grid size |
512 world units |
| Cell size |
32 world units, so 16 cells per grid axis |
Where the code lives
| Piece |
Path |
| Table header and descriptors |
Source/NexusForever.GameTable/GameTableHeader.cs, GameTableField.cs |
| Data types |
Source/NexusForever.GameTable/DataType.cs |
| Table reader and layout check |
Source/NexusForever.GameTable/GameTable.cs |
| Text table reader |
Source/NexusForever.GameTable/TextTable.cs, TextTableHeader.cs |
| Map file reader |
Source/NexusForever.IO/Map/MapFile.cs, MapFileGrid.cs, MapFileCell.cs |
| Map constants |
Source/NexusForever.IO/Map/MapDefines.cs |
| Map file writer |
Source/NexusForever.MapGenerator/ |