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.
What is encoded and what is plain
Section titled “What is encoded and what is plain”| 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.
The _e calls and _ENCSTR
Section titled “The _e calls and _ENCSTR”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.
INI configs
Section titled “INI configs”Reading
Section titled “Reading”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.
Scrambled .ini entries
Section titled “Scrambled .ini entries”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
Section titled “DAT tables”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.
File layout
Section titled “File layout”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.
Column types
Section titled “Column types”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).
Rules to keep
Section titled “Rules to keep”- 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 - 1bytes. - 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.
Differences between the sources
Section titled “Differences between the sources”| 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 |
Not confirmed yet
Section titled “Not confirmed yet”- The full list of INI files each client region sets to comment
1. - Type numbers 12 and above 14 in
.datcolumns. - How the 2021 client handles a
.dattable that is missing from the pack (the loader logs “Data Load Fail”; what the game does next depends on the table).
