Gear CounselCompendium

Заголовок ARZ: карта всього файла у 24 байтах

Перші 24 байти не містять даних самих записів про предмети, уміння чи інші сутності гри. Вони визначають поточний профіль і пояснюють, де шукати решту файла, скільки вона займає і скільки записів треба прочитати.

Синтетичний заголовок ARZ-X324 байти · 7 полів
  • 0x00–0x03 · поля профілю
  • 0x04–0x0F · поля таблиці записів
  • 0x10–0x17 · поля таблиці рядків

Підписи під кольоровою смугою пов’язують точні діапазони байтів із полями профілю, таблиці записів і таблиці рядків. Колір тут лише додаткова підказка. Межі всіх семи полів розберемо нижче.

00 · Перетворити сім чисел на карту файла

Минулого разу ми прочитали перші чотири байти як header_word_0 = 2 і header_word_1 = 3. Тепер прочитаємо решту заголовка й знайдемо межі всіх великих секцій ARZ-X3. Вміст записів для цього не потрібен.

Спершу введемо запис (record). В ARZ це скомпільований DBR-запис — окремий набір даних, який може описувати предмет, уміння чи іншу сутність гри. Кожен запис має шлях (record path) — повну назву всередині бази. В ARZ-X3 є три синтетичні записи: records/example/dagger.dbr, records/example/shield.dbr і records/example/strike.dbr. Серед полів (field) першого є enabled = true і weight = 1.5: enabled та weight — назви полів, true і 1.5 — їхні значення.

Як три записи розкладено між трьома частинами файла
поля та значеннястиснені дані
Дані кожного запису стискаються окремо від інших. Отримана для одного запису послідовність байтів — це стиснений блок (block). В ARZ-X3 є три записи, тому й блоків три: 44, 45 і 44 байти. У файлі вони лежать один за одним і разом займають 133 байти.
рядки за індексомтаблиця рядків (string table)

Це спільний нумерований список рядків. В ARZ-X3 він має дев’ять елементів:

ІндексРядок
0records/example/dagger.dbr
1ExampleClass
2Class
3templateName
4database/templates/example.tpl
5enabled
6weight
7records/example/shield.dbr
8records/example/strike.dbr

Число ліворуч — це індекс (index), тобто номер рядка в цьому списку.

зв’язок між частинамитаблиця записів (record table)

Кожному запису відповідає один елемент цієї таблиці. ARZ-X3 містить три елементи. У першій колонці вказано індекс рядка зі шляхом у списку вище:

індекс рядка зі шляхомвідносне зміщення блокастиснено → після розпакування
00 B44 → 48 B
744 B45 → 48 B
889 B44 → 48 B

Система числення: усі числові значення в клітинках цієї таблиці десяткові; B означає байти. Кожне зміщення блока відлічується від file+0x18, тобто від початку ділянки стиснених даних.

Стиснені дані містять поля та значення. Таблиця записів містить координати трьох блоків та індекс рядка зі шляхом для кожного запису. У першому елементі індекс 0 відповідає рядку records/example/dagger.dbr. Окремо, після розпакування блока декодовані байти утворюють тіло запису (record body). Числове посилання в цьому тілі може пов’язати індекс 5 із назвою поля enabled.

  1. розкласти 24 байти заголовка на сім окремих полів;
  2. не плутати зміщення, розмір і кількість;
  3. обчислити початок і кінець кожної секції;
  4. помітити розрив, накладання або вихід за межі файла.

01 · Сім полів мають різну ширину й різні ролі

Поле — це домовлена ділянка байтів із власною шириною та роллю. Перші два поля займають по два байти. Наступні п’ять — по чотири. Разом їхні ширини складаються так:

Для перших двох полів використовуємо нейтральні структурні назви header_word_0 і header_word_1. Решту називаємо за їхньою роллю: record_table_offset, record_table_size, record_count, string_table_offset і string_table_size. У самому файлі збережено лише значення; назви допомагають розрізняти їх під час читання.

Поля профілю

2 поля · 4 байти
file+0x00
u16 header_word_0
2

роль значенняРевізія сумісності формату

file+0x02
u16 header_word_1
3

роль значенняБіти можливостей заголовка

Таблиця записів

3 поля · 12 байтів
file+0x04
u32 record_table_offset
0x9D · 157

роль значенняПочаток таблиці записів

file+0x08
u32 record_table_size
0x78 · 120

роль значенняРозмір таблиці записів у байтах

file+0x0C
u32 record_count
3

роль значенняКількість записів

Таблиця рядків

2 поля · 8 байтів
file+0x10
u32 string_table_offset
0x115 · 277

роль значенняПочаток таблиці рядків

file+0x14
u32 string_table_size
0xBE · 190

роль значенняРозмір таблиці рядків у байтах

02 · Два слова мають два різні контракти

Поточний офіційний інструмент запису створює пару (2, 3), але офіційні програми читання не сприймають її як поля magic і version. Вони працюють із двома окремими 16-бітовими словами.

Чотири байти, але два окремі поля · HEX · LE
file+0x00
u16 header_word_0
2

роль значенняРевізія сумісності формату

file+0x02
u16 header_word_1
3

роль значенняБіти можливостей заголовка

Перше слово відхиляється, якщо воно менше за 2. Оскільки це перевірка нижньої межі, а не точної рівності, поле не є звичайним magic-значенням. У специфікації його спостережену роль названо ревізією сумісності формату. Це тлумачення поведінки, а не відновлена приватна назва поля.

Друге слово інструмент запису складає за допомогою бітових операцій, а програми читання перевіряють його біти окремо. Значення 3 означає 0x0001 | 0x0002, а не «версію три».

0x0001 сумісність шарів
У кожного наступного шару архіву цей біт має збігатися з першим шаром. Що саме означають стани 0 і 1, невідомо.
0x0002 контрольні суми ділянок
Коли біт установлено, досліджений шлях у коді гри обробляє останню 16-байтову завершальну секцію.
0xFFFC маска решти бітів
Ця маска охоплює решту чотирнадцяти бітових позицій; сама вона не є одним бітом можливостей. Поточний інструмент запису залишає ці позиції нульовими. Їхнє історичне чи майбутнє значення невідоме.

Якщо показати всі чотири байти як одне u32 LE, отримаємо 0x00030002, або десяткове 196610. Досліджений код гри не використовує це упаковане число як одну семантичну одиницю чи числову версію.

03 · Зміщення, розмір і кількість відповідають на різні запитання

Усі п’ять значень фізично записані однаково — як u32 LE. Різницю створює не вигляд байтів, а поле, у якому вони лежать. У назвах полів ці три ролі позначено як зміщення (offset), розмір (size) і кількість (count).

зміщення — де?
Відстань від початку файла до першого байта секції. Поля record_table_offset і string_table_offset.
розмір — скільки байтів?
Фізичний розмір секції в байтах. Поля record_table_size і string_table_size. У звичайному тексті цей розмір також можна назвати довжиною секції, але назви полів у заголовку використовують суфікс _size.
кількість — скільки елементів?
Кількість записів — і, відповідно, елементів таблиці. Поле record_count.

Формула кінця секції:end — перша позиція після секції.

Кількість не замінює розмір. Елементи таблиці записів мають змінну ширину, тому record_count = 3 не пояснює, скільки байтів займає таблиця. Для цього окремо збережено record_table_size = 120.

04 · У таблиці записів збережено межі кожного стисненого блока

У самому стисненому блоці не збережено шлях запису, стиснений розмір та розмір після розпакування. Ці значення збережено у відповідному елементі таблиці записів. Тому межі блока визначаємо за таблицею, а не за його вмістом.

В ARZ-X3 є три записи, тому між заголовком і таблицею лежать три стиснені блоки. Як працює стиснення, розберемо в наступній статті; тут достатньо знайти спільні межі цієї області.

Стиснені дані починаються одразу після 24-байтового заголовка, на позиції 0x18. Окремого поля з їхнім розміром немає. Їхній кінець збігається зі значенням record_table_offset = 0x9D — початком таблиці записів.

Від заголовка до кінця таблиці записів
ДілянкаМежіОбчислення
стиснені дані0x18..0x9D0x9D − 0x18 = 133 байти
таблиця записів0x9D..0x115157 + 120 = 277

Трьом записам ARZ-X3 відповідають три 40-байтові елементи, разом 120 байтів. У цьому прикладі вони однакової ширини, бо мають однакову за довжиною вбудовану назву класу (class tag). Формат не гарантує цього для довільного файла, тому заголовок зберігає і record_count, і record_table_size.

05 · У таблиці рядків кожен рядок має числовий індекс

В елементі таблиці записів замість самого шляху збережено лише його індекс у таблиці рядків — 0. Під цим індексом міститься повний шлях records/example/dagger.dbr. Там само під індексом 5 збережено назву поля enabled.

Тепер знайдемо межі цієї таблиці. Додаємо record_table_offset і record_table_size:

Поле string_table_offset також містить 0x115. Обидва значення приводять до тієї самої межі — між секціями немає прихованого проміжку.

Межі таблиці рядків:

07 · Перші три перевірки верхньорівневої карти

У перевіреному профілі збірки 24346246 з точною парою (2, 3) великі ділянки розташовані без проміжків. Значення із заголовка не варто приймати на віру: вони мають зійтися між собою й із повним розміром файла. Кожне додавання нижче виконується з перевіркою переповнення; переповнення означає некоректний результат.

  1. таблиця не починається всередині заголовка

    record_table_offset ≥ 0x18
    0x9D ≥ 0x18

  2. таблиці торкаються без розриву

    record_table_offset + record_table_size = string_table_offset
    0x9D + 0x78 = 0x115

  3. завершальна секція закінчується разом із файлом

    string_table_offset + string_table_size + 16 = file_size
    277 + 190 + 16 = 483

Приклад відхиленого розриву

string_table_offset = 0x119

Таблиця записів однаково завершилася б на 0x115, але таблиця рядків почалася б на 0x119. Чотири непояснені байти — це розрив, тому таке значення треба відхилити.

08 · Заголовок справді описав увесь ARZ-X3

Ми прочитали лише перші 24 байти, але вже знаємо, де лежить кожна велика секція та де файл повинен закінчитися.

483 байти від початку до кінця
ДілянкаМежіВміст
заголовок0x00..0x1824 B
стиснені дані0x18..0x9D133 B · 3 блоки
таблиця записів0x9D..0x115120 B · 3 елементи
таблиця рядків0x115..0x1D3190 B · 9 рядків
завершальна секція з контрольними сумами0x1D3..0x1E316 B · 4 значення

Заголовок визначає межі великих секцій, але не координати окремого блока. Їх читаємо з відповідного елемента таблиці записів. Вміст LZ4-блока теж залишається стисненим. Проте тепер для кожного наступного кроку маємо точну ділянку файла.

П’ять опорних думок

  1. Заголовок перевіреного поточного профілю ARZ займає рівно 24 байти.
  2. Два початкові слова мають поведінку сумісності й можливостей; п’ять полів описують секції.
  3. Зміщення, розмір і кількість не взаємозамінні.
  4. Кінець секції обчислюється як початок + розмір.
  5. Чотири контрольні суми охоплюють окремі ділянки, але завантаження грою й сувора перевірка — різні контракти.