Skip to content

DAT Tables and INI Configs

The client keeps its data in two kinds of files. INI configs are text files with [sections] and key=value lines (heroes, items, maps, settings). DAT tables are binary files made from Excel sheets (newer clients only). Both live inside the packs.

“Encoded” is used for three different things in the code. Mixing them up is the most common confusion, so they are separated first.

This page follows the 2014, 2019 and 2021 sources and the web tools. Anything marked not confirmed yet was not verified. No key or password values are given here.

Thing Encoded? Where
Section and key names in the C++ code (LoadInt_e("hp", ...)) Yes, inside the exe Only the compiled program. The INI file itself keeps plain names
The file name in SetFileName_e("config/x.ini") Yes, inside the exe Same: only the program
An .ini entry in a pack whose comment is 1 Yes, the whole file is scrambled The pack entry
An .ini entry with another comment, or no comment No Plain text
A .dat table No (plain binary) Inside the pack, which has its own encryption
The pack entries themselves Yes, by the pack password See IOP Pack Format

So when you open a game INI in the tools, you always get plain text. The tools undo the scrambling on read and apply it again on write.

ioINILoader has plain functions (LoadInt, LoadString, SetFileName, SetTitle) and _e versions of each. In a shipping build, the _e versions run the text through _ENCSTR (StringProtect.h): the literal is stored in the exe in scrambled form and decoded onto the stack when used. In a normal development build, LoadInt_e is just another name for LoadInt.

The same goes for sprintf_e, ioHashString::operator== with a literal and a few more helpers.

This hides strings from someone who scans the exe. It does not change any data file. Facts that matter when you add code:

  • Only string literals work. Passing a char* variable gives a compile error.
  • A literal can be at most 48 characters; the loader class notes 63 for its own functions.
  • A literal of exactly three letters (four bytes with the zero) is rejected by the macro.
  • In a debug build nothing is hidden.

ioINILoader::SetFileName reads the file through the resource loader (the packs), then ioINIParser parses it from memory. The parser rules:

Rule Detail
Section A line starting with [. The name between the first and last character is lower-cased
Comment A line starting with ; is skipped. A ; later in a line is not a comment, it stays in the value
Key line Anything with an =. The key is trimmed of blanks and lower-cased. The value is everything after the = with leading blanks cut
Empty section A section with no key is dropped
Other lines Lines that do not start with [ and are outside a section are skipped

Keys and sections are not case-sensitive. If a key repeats in a section, the later value replaces the earlier one (SetAt).

The file is read as bytes. Korean builds use CP949; other regions use their own code page. The text tools show an encoding choice for this.

When the client loads a file whose name contains .ini from a pack, it reads the entry comment. If the comment is 1, the file content is run through a fixed XOR built into the client, which undoes it. The XOR is the same in both directions, so the tools use one function to encode and decode. Only .ini names are checked; other file types ignore the comment.

An unpacked client skips this: the loose file is read as is.

You need to keep the comment 1 when you replace such an entry, or the client reads your plain text through the XOR and gets garbage. IOP Manager keeps the comment and scrambles for you.

DAT tables exist in the 2019 and 2021 sources (LSClient\DataHeaders, 68 and 69 files). The 2014 client has none. The client loads them as Resource/Tables/<sheet>.dat through BaseDataManager::LoadBin. The server reads its own copies from ls_gamesvr\config\tables.

All numbers are little-endian int32 unless noted.

Part Field
Header tool version, header version, data version
Columns column count, then for each column: size, type, name length, name bytes
Rows row count, then the rows one after another

Each row is the columns in order, packed without gaps (#pragma pack(1)). The row size is the sum of the column sizes.

The client reads the column list only to skip it. It then copies row count × sizeof(TData) bytes straight into an array of the C++ struct for that table. So the column list in the file must describe exactly the struct the client was compiled with.

The header version is checked. If it differs from the version compiled into the client, the load fails and the table stays empty (a debug build shows a “DataVersionError” box). Do not change the column list; change rows and values only.

The type numbers the web tools know, with the size stored in the column entry:

Type Name Size
1 BYTE 1
2 WORD 2
3 DWORD 4
4 STRING the column size, zero-padded text
5 SHORT 2
6 USHORT 2
7 INT 4
8 UINT 4
9 LONG 4
10 ULONG 4
11 FLOAT 4
13 ENUM 4
14 BOOL 1

A type not in this list is read by its size (1, 2, 4 or 8 bytes). Type 12 and anything above 14 have not been seen (not confirmed yet).

A STRING cell holds size - 1 bytes of text plus a zero. The code page is the client’s (Korean for the original sheets).

  • Do not change the column count, order, sizes or the header version.
  • A cell must fit its type: a BYTE is 0 to 255, a STRING must fit in size - 1 bytes.
  • Keep the zero padding. The tools write untouched cells back byte for byte.
  • Client and server tables have the same idea but not always the same layout; match columns by name when converting.
Part 2014 2019 2021
ioINILoader, ioINIParser same same same
_e calls and scrambled .ini entries yes yes yes
.dat tables (DataHeaders) none 68 files 69 files
Tool Use
IOP Manager Previews and edits .ini entries; keeps comment 1 and the scrambling
Items, Quests, Heroes Read and edit INI configs as forms
Tables Opens .dat as a grid, edits rows, exports .xlsx or .csv, writes client and server layouts
  • The full list of INI files each client region sets to comment 1.
  • Type numbers 12 and above 14 in .dat columns.
  • How the 2021 client handles a .dat table that is missing from the pack (the loader logs “Data Load Fail”; what the game does next depends on the table).