- Обновлены спецификации `runtime-pipeline`, `sound`, `terrain-map-loading`, `texture`, `ui` и `wear`. - Добавлены разделы о статусе покрытия и оставшихся задачах для достижения 100% завершенности. - Внесены уточнения по архитектурным ролям, минимальным контрактам и требованиям к toolchain для каждой подсистемы. - Уточнены форматы данных и правила взаимодействия между компонентами системы.
8.1 KiB
NRes
NRes — базовый контейнер ресурсов движка Parkan: Iron Strategy.
Страница фиксирует формат на диске и runtime-контракт чтения/поиска/сохранения в высокоуровневом виде, без привязки к внутренним адресам и именам из дизассемблера.
Связанная страница:
1. Назначение
NRes используется как универсальный архив:
- 3D-модели (
*.msh,*.rlb); - текстуры (
Texm); - материалы (
MAT0); - эффекты (
FXID); - миссионные и служебные ресурсы.
Формат поддерживает:
- чтение;
- поиск по имени;
- редактирование (add/replace/remove);
- полную пересборку архива.
2. Общий layout файла
[Header: 16]
[Data region: variable, 8-byte aligned chunks]
[Directory: entry_count * 64, всегда в конце файла]
Критично: каталог всегда расположен в конце файла.
3. Заголовок (16 байт)
Все значения little-endian.
| Offset | Size | Type | Значение |
|---|---|---|---|
| 0 | 4 | char[4] | NRes |
| 4 | 4 | u32 | 0x00000100 (версия 1.0) |
| 8 | 4 | i32 | entry_count (должен быть >= 0) |
| 12 | 4 | u32 | total_size (должен быть равен фактическому размеру файла) |
Производные значения:
directory_size = entry_count * 64;directory_offset = total_size - directory_size.
Ограничения:
directory_offset >= 16;directory_offset + directory_size == total_size.
4. Запись каталога (64 байта)
| Offset | Size | Type | Поле |
|---|---|---|---|
| 0 | 4 | u32 | type_id |
| 4 | 4 | u32 | attr1 |
| 8 | 4 | u32 | attr2 |
| 12 | 4 | u32 | size (размер payload) |
| 16 | 4 | u32 | attr3 |
| 20 | 36 | char[36] | name_raw (C-строка) |
| 56 | 4 | u32 | data_offset |
| 60 | 4 | u32 | sort_index |
4.1. Имя ресурса (name_raw)
Контракт:
- максимум 35 полезных байт + NUL;
- допускается ровно один терминатор внутри 36-байтового поля;
- имя сравнивается регистронезависимо по ASCII-правилу (
A..Z->a..z).
Для writer/editor:
- запрещено писать NUL внутри полезной части имени;
- запрещены имена длиной > 35 байт.
4.2. Диапазон данных (data_offset, size)
Для каждой записи:
data_offset >= 16;data_offset + size <= directory_offset.
Практически (канонический writer): каждый payload начинается с 8-байтного выравнивания.
5. Таблица сортировки (sort_index)
sort_index задает перестановку «отсортированный список -> исходный индекс записи».
Пусть:
entries[i]— i-я запись каталога в исходном порядке;P— массив индексов0..entry_count-1, отсортированный поentries[idx].name(ASCII case-insensitive).
Тогда в канонической записи:
entries[i].sort_index = P[i].
Это именно таблица для бинарного поиска по имени, а не «ранг текущей записи».
6. Поиск по имени
Алгоритм поиска:
- Выполнить бинарный поиск по диапазону
i in [0, entry_count). - На шаге
iвзятьtarget = entries[i].sort_index. - Сравнить искомое имя с
entries[target].name(ASCII case-insensitive). - При совпадении вернуть
target.
Fail-safe поведение:
- если
sort_indexнекорректен (выход за диапазон), реализация должна перейти на линейный fallback по всем записям; - fallback использует то же ASCII case-insensitive сравнение.
7. Каноническая пересборка архива
Канонический writer выполняет:
- Пишет заглушку заголовка (16 байт).
- Пишет payload всех записей в текущем порядке.
- После каждого payload добавляет 0-padding до кратности 8.
- Пересчитывает
sort_indexчерез сортировку имен. - Дописывает каталог (
entry_count * 64). - Пересчитывает и записывает
total_size.
Итоговый файл должен удовлетворять всем ограничениям из разделов 3–5.
8. Режим raw (совместимость инструментов)
Для служебных инструментов допускается raw_mode:
- любой бинарный файл трактуется как один «сырой» ресурс;
- возвращается одна запись (
name = RAW,data_offset = 0,size = len(file)).
Этот режим не является форматом NRes на диске, это только режим открытия.
9. Контрольные инварианты
Минимальный набор проверок при чтении:
magic == "NRes".version == 0x100.entry_count >= 0.header.total_size == file_size.- Каталог находится в конце файла.
- Для каждой записи диапазон данных не пересекает каталог.
- Имя корректно C-терминировано и не длиннее 35 байт.
Минимальный набор проверок при записи:
- Все имена <= 35 байт и без внутренних NUL.
sort_indexформирует валидную перестановку0..N-1.- Все паддинги между payload состоят из нулевых байт.
total_sizeравен фактической длине выходного файла.
10. Эмпирическая проверка на retail-корпусе
Валидация на полном наборе testdata/Parkan - Iron Strategy:
- найдено
120архивовNRes; - roundtrip
unpack -> repack -> byte-compare:120/120совпали побайтно; - критических расхождений формата не обнаружено.
Инструмент:
tools/archive_roundtrip_validator.py
11. Статус покрытия и что осталось до 100%
Закрыто:
- формат заголовка/каталога;
- правила поиска;
- каноническая пересборка;
- строгие инварианты валидатора;
- побайтовый roundtrip на retail-корпусе.
Осталось до полного 100% архитектурного покрытия движка:
- Формальная семантика
attr1/attr2/attr3для всех типов ресурсов (частично вынесена в профильные страницыmsh,material,texture,fxid,terrain). - Полная спецификация поведения при не-ASCII именах (в реальных игровых архивах используется ASCII-практика; для Unicode-коллации движок не документирован).
- Полная спецификация платформенных гарантий атомарной записи (формат данных закрыт, но OS-уровневые гарантии замены файла зависят от платформы и файловой системы).