From bytes to structure
The ARZ header: a map of the entire file in 24 bytes
The first 24 bytes contain no record payloads for items, skills, or other game entities. They define the current profile and tell us where to find the rest of the file, how much space it occupies, and how many records to read.
- 0x00–0x03 · profile fields
- 0x04–0x0F · record-table fields
- 0x10–0x17 · string-table fields
The labels below the color band map exact byte ranges to profile, record-table, and string-table fields. Color is only an additional cue. The boundaries of all seven fields are examined below.
A new task
00 · Turning seven numbers into a file map
In the previous article, we read the first four bytes as header_word_0 = 2 and header_word_1 = 3. We can now read the rest of the header and find the bounds of every major ARZ-X3 section. We do not need the contents of any record to do that.
First, we need the term record. In ARZ, this is a compiled DBR record: a distinct set of data that may describe an item, a skill, or another game entity. Every record has a record path: its full name inside the database. ARZ-X3 contains three synthetic records: records/example/dagger.dbr, records/example/shield.dbr, and records/example/strike.dbr. The first record includes the fields enabled = true and weight = 1.5: enabled and weight are field names; true and 1.5 are their values.
- fields and valuescompressed data
- The data of each record is compressed separately from the other records. The resulting byte sequence for one record is a compressed block. ARZ-X3 has three records, so it also has three blocks: 44, 45, and 44 bytes. They appear one after another in the file and occupy 133 bytes in total.
- strings by indexstring table
This is one shared, numbered list of strings. ARZ-X3 has nine entries:
Index String 0 records/example/dagger.dbr1 ExampleClass2 Class3 templateName4 database/templates/example.tpl5 enabled6 weight7 records/example/shield.dbr8 records/example/strike.dbrThe number on the left is an index: the string's number in this list.
- links between partsrecord table
Each record has one entry in this table. ARZ-X3 therefore has three entries. The first column gives the index of the path string in the list above:
path string index relative block offset compressed → decompressed 00 B44 → 48 B744 B45 → 48 B889 B44 → 48 BNumber base: every numeric cell in this table is decimal;
Bmeans bytes. Each block offset is measured fromfile+0x18, the beginning of the compressed-data region.
The compressed data holds fields and values. The record table holds the coordinates of all three blocks and each record's path-string index. In the first entry, index 0 resolves to records/example/dagger.dbr. Separately, after that block is decompressed, its decoded bytes form the record body. A numeric reference inside that body can resolve index 5 to the field name enabled.
- divide the 24-byte header into seven distinct fields;
- keep offsets, sizes, and counts distinct;
- calculate the start and end of every section;
- detect a gap, overlap, or out-of-bounds region.
Field boundaries first
01 · Seven fields have different widths and different roles
A field is a defined region of bytes with its own width and role. The first two fields occupy two bytes each. The next five occupy four bytes each. Together, their widths add up as follows:
The first two fields use neutral structural names, header_word_0 and header_word_1. The remaining fields are named by their roles: record_table_offset, record_table_size, record_count, string_table_offset, and string_table_size. The file stores only the values; the names help us distinguish them while reading.
Profile fields
2 fields · 4 bytesfile+0x002value roleFormat compatibility revision
file+0x023value roleHeader feature bits
Record table
3 fields · 12 bytesfile+0x040x9D · 157value roleStart of the record table
file+0x080x78 · 120value roleSize of the record table in bytes
file+0x0C3value roleNumber of records
String table
2 fields · 8 bytesfile+0x100x115 · 277value roleStart of the string table
file+0x140xBE · 190value roleSize of the string table in bytes
The first four bytes
02 · Two words carry two different contracts
The current official writer emits the pair (2, 3), but the official readers do not consume it as “magic and version.” They operate on two separate 16-bit words.
file+0x002value roleFormat compatibility revision
file+0x023value roleHeader feature bits
Readers reject the first word when it is below 2. Because this is a lower-bound check rather than exact equality, the field is not a conventional magic value. The specification uses the behavioral label format compatibility revision. This label is an interpretation of observed behavior, not a recovered private field name.
The writer constructs the second word through bit operations, and readers test those bits separately. The value 3 means 0x0001 | 0x0002, not “version three.”
0x0001layer compatibility- Every later archive layer must match the first layer's bit. What the states
0and1represent remains unknown. 0x0002regional-checksum footer- When set, the reviewed native path processes the final 16-byte footer.
0xFFFCremaining-bit mask- This mask covers the other fourteen bit positions; it is not one feature bit. The current writer leaves those positions clear. Their historical or future meanings are unknown.
Displaying all four bytes as one u32 LE produces 0x00030002, decimal 196610. That packed number is not the semantic unit used by the reviewed native code and is not one numeric version.
A number gets a role
03 · Offset, size, and count answer different questions
All five values are physically stored in the same way, as u32 LE. The distinction comes from the field that contains the bytes, not from their appearance. Field names mark these three roles as offset, size, and count.
- offset — where?
- The distance from the beginning of the file to the first byte of a section. The fields
record_table_offsetandstring_table_offset. - size — how many bytes?
- The physical size of a section in bytes. The fields
record_table_sizeandstring_table_size. In general prose, this byte size may also be called the section's length; the header field names use the suffix_size. - count — how many entries?
- The number of records — and therefore table entries. The
record_countfield.
Section end formula:end is the first position after the section.
A count does not replace a size. Record-table entries have variable width, so record_count = 3 does not tell us how many bytes the table occupies. That value is stored separately as record_table_size = 120.
Data and its coordinates
04 · The record table stores the bounds of every compressed block
A compressed block does not contain its record path, compressed size, or decompressed size. Those values live in the corresponding record-table entry. We therefore determine the block's bounds from the table, not from the block's contents.
ARZ-X3 has three records, so three compressed blocks lie between the header and the record table. The next article will explain how compression works. Here, we only need the shared bounds of this region.
The compressed data starts immediately after the 24-byte header, at position 0x18. There is no separate field for its size. Its end is the value record_table_offset = 0x9D, which is also the start of the record table.
| Region | Range | Calculation |
|---|---|---|
| compressed data | 0x18..0x9D | 0x9D − 0x18 = 133 bytes |
| record table | 0x9D..0x115 | 157 + 120 = 277 |
The three ARZ-X3 records correspond to three 40-byte entries, 120 bytes in total. In this example, their widths match because their embedded class tags have the same length. The format does not guarantee this for an arbitrary file, so the header stores both record_count and record_table_size.
Strings by index
05 · Every string in the string table has a numeric index
The record-table entry stores only the path's number in the string table — 0 — rather than the path itself. Entry 0 contains the full path records/example/dagger.dbr. The same table stores the field name enabled at index 5.
Now find the bounds of this table. Add record_table_offset and record_table_size:
The string_table_offset field also contains 0x115. Both calculations lead to the same boundary, so there is no hidden gap between the sections.
String-table bounds:
The map must add up
07 · The first three checks for the top-level map
For the verified build-24346246 profile with exact leading words (2, 3), the major regions are contiguous. Header values should not be trusted in isolation: they must cross-check one another and the full file size. Every addition below must use checked arithmetic; overflow is an invalid result.
- the table does not start inside the header
record_table_offset ≥ 0x180x9D ≥ 0x18 - the two tables meet without a gap
record_table_offset + record_table_size = string_table_offset0x9D + 0x78 = 0x115 - the footer ends with the file
string_table_offset + string_table_size + 16 = file_size277 + 190 + 16 = 483
string_table_offset = 0x119
The record table would still end at 0x115, but the string table would begin at 0x119. Four unexplained bytes form a gap, so this value must be rejected.
The complete picture
08 · The header really did describe all of ARZ-X3
We have read only the first 24 bytes, yet already know where every major section lies and where the file must end.
| Region | Range | Contents |
|---|---|---|
| header | 0x00..0x18 | 24 B |
| compressed data | 0x18..0x9D | 133 B · 3 blocks |
| record table | 0x9D..0x115 | 120 B · 3 entries |
| string table | 0x115..0x1D3 | 190 B · 9 strings |
| checksum footer | 0x1D3..0x1E3 | 16 B · 4 values |
The header defines the major section boundaries, but not the coordinates of an individual block. Those come from the corresponding record-table entry. The contents of an LZ4 block also remain compressed. Even so, every next step now has an exact file region to work with.
After reading
Five ideas to keep
- The verified current ARZ header occupies exactly 24 bytes.
- Two leading words carry compatibility and feature behavior; five fields describe sections.
- Offset, size, and count are not interchangeable.
- A section end is calculated as start + size.
- Four footer checksums cover exact regions, while native loading and strict verification remain different contracts.