Від байтів до структури
Заголовок ARZ: карта всього файла у 24 байтах
Перші 24 байти не містять даних самих записів про предмети, уміння чи інші сутності гри. Вони визначають поточний профіль і пояснюють, де шукати решту файла, скільки вона займає і скільки записів треба прочитати.
- 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 він має дев’ять елементів:
Індекс Рядок 0 records/example/dagger.dbr1 ExampleClass2 Class3 templateName4 database/templates/example.tpl5 enabled6 weight7 records/example/shield.dbr8 records/example/strike.dbrЧисло ліворуч — це індекс (
index), тобто номер рядка в цьому списку.- зв’язок між частинамитаблиця записів (
record table) Кожному запису відповідає один елемент цієї таблиці. ARZ-X3 містить три елементи. У першій колонці вказано індекс рядка зі шляхом у списку вище:
індекс рядка зі шляхом відносне зміщення блока стиснено → після розпакування 00 B44 → 48 B744 B45 → 48 B889 B44 → 48 BСистема числення: усі числові значення в клітинках цієї таблиці десяткові;
Bозначає байти. Кожне зміщення блока відлічується відfile+0x18, тобто від початку ділянки стиснених даних.
Стиснені дані містять поля та значення. Таблиця записів містить координати трьох блоків та індекс рядка зі шляхом для кожного запису. У першому елементі індекс 0 відповідає рядку records/example/dagger.dbr. Окремо, після розпакування блока декодовані байти утворюють тіло запису (record body). Числове посилання в цьому тілі може пов’язати індекс 5 із назвою поля enabled.
- розкласти 24 байти заголовка на сім окремих полів;
- не плутати зміщення, розмір і кількість;
- обчислити початок і кінець кожної секції;
- помітити розрив, накладання або вихід за межі файла.
Спочатку межі полів
01 · Сім полів мають різну ширину й різні ролі
Поле — це домовлена ділянка байтів із власною шириною та роллю. Перші два поля займають по два байти. Наступні п’ять — по чотири. Разом їхні ширини складаються так:
Для перших двох полів використовуємо нейтральні структурні назви header_word_0 і header_word_1. Решту називаємо за їхньою роллю: record_table_offset, record_table_size, record_count, string_table_offset і string_table_size. У самому файлі збережено лише значення; назви допомагають розрізняти їх під час читання.
Поля профілю
2 поля · 4 байтиfile+0x002роль значенняРевізія сумісності формату
file+0x023роль значенняБіти можливостей заголовка
Таблиця записів
3 поля · 12 байтівfile+0x040x9D · 157роль значенняПочаток таблиці записів
file+0x080x78 · 120роль значенняРозмір таблиці записів у байтах
file+0x0C3роль значенняКількість записів
Таблиця рядків
2 поля · 8 байтівfile+0x100x115 · 277роль значенняПочаток таблиці рядків
file+0x140xBE · 190роль значенняРозмір таблиці рядків у байтах
Перші чотири байти
02 · Два слова мають два різні контракти
Поточний офіційний інструмент запису створює пару (2, 3), але офіційні програми читання не сприймають її як поля magic і version. Вони працюють із двома окремими 16-бітовими словами.
file+0x002роль значенняРевізія сумісності формату
file+0x023роль значенняБіти можливостей заголовка
Перше слово відхиляється, якщо воно менше за 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..0x9D | 0x9D − 0x18 = 133 байти |
| таблиця записів | 0x9D..0x115 | 157 + 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) великі ділянки розташовані без проміжків. Значення із заголовка не варто приймати на віру: вони мають зійтися між собою й із повним розміром файла. Кожне додавання нижче виконується з перевіркою переповнення; переповнення означає некоректний результат.
- таблиця не починається всередині заголовка
record_table_offset ≥ 0x180x9D ≥ 0x18 - таблиці торкаються без розриву
record_table_offset + record_table_size = string_table_offset0x9D + 0x78 = 0x115 - завершальна секція закінчується разом із файлом
string_table_offset + string_table_size + 16 = file_size277 + 190 + 16 = 483
string_table_offset = 0x119
Таблиця записів однаково завершилася б на 0x115, але таблиця рядків почалася б на 0x119. Чотири непояснені байти — це розрив, тому таке значення треба відхилити.
Уся картина
08 · Заголовок справді описав увесь ARZ-X3
Ми прочитали лише перші 24 байти, але вже знаємо, де лежить кожна велика секція та де файл повинен закінчитися.
| Ділянка | Межі | Вміст |
|---|---|---|
| заголовок | 0x00..0x18 | 24 B |
| стиснені дані | 0x18..0x9D | 133 B · 3 блоки |
| таблиця записів | 0x9D..0x115 | 120 B · 3 елементи |
| таблиця рядків | 0x115..0x1D3 | 190 B · 9 рядків |
| завершальна секція з контрольними сумами | 0x1D3..0x1E3 | 16 B · 4 значення |
Заголовок визначає межі великих секцій, але не координати окремого блока. Їх читаємо з відповідного елемента таблиці записів. Вміст LZ4-блока теж залишається стисненим. Проте тепер для кожного наступного кроку маємо точну ділянку файла.
Після читання
П’ять опорних думок
- Заголовок перевіреного поточного профілю ARZ займає рівно 24 байти.
- Два початкові слова мають поведінку сумісності й можливостей; п’ять полів описують секції.
- Зміщення, розмір і кількість не взаємозамінні.
- Кінець секції обчислюється як початок + розмір.
- Чотири контрольні суми охоплюють окремі ділянки, але завантаження грою й сувора перевірка — різні контракти.