diff options
Diffstat (limited to 'docs/specs/nres.md')
| -rw-r--r-- | docs/specs/nres.md | 200 |
1 files changed, 0 insertions, 200 deletions
diff --git a/docs/specs/nres.md b/docs/specs/nres.md deleted file mode 100644 index 150b38b..0000000 --- a/docs/specs/nres.md +++ /dev/null @@ -1,200 +0,0 @@ -# NRes - -`NRes` — базовый контейнер ресурсов движка Parkan: Iron Strategy. -Страница фиксирует формат на диске и runtime-контракт чтения/поиска/сохранения в высокоуровневом виде, без привязки к внутренним адресам и именам из дизассемблера. - -Связанная страница: - -- [RsLi](rsli.md) - -## 1. Назначение - -`NRes` используется как универсальный архив: - -- 3D-модели (`*.msh`, `*.rlb`); -- текстуры (`Texm`); -- материалы (`MAT0`); -- эффекты (`FXID`); -- миссионные и служебные ресурсы. - -Формат поддерживает: - -- чтение; -- поиск по имени; -- редактирование (add/replace/remove); -- полную пересборку архива. - -## 2. Общий layout файла - -```text -[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. Поиск по имени - -Алгоритм поиска: - -1. Выполнить бинарный поиск по диапазону `i in [0, entry_count)`. -2. На шаге `i` взять `target = entries[i].sort_index`. -3. Сравнить искомое имя с `entries[target].name` (ASCII case-insensitive). -4. При совпадении вернуть `target`. - -Fail-safe поведение: - -- если `sort_index` некорректен (выход за диапазон), реализация должна перейти на линейный fallback по всем записям; -- fallback использует то же ASCII case-insensitive сравнение. - -## 7. Каноническая пересборка архива - -Канонический writer выполняет: - -1. Пишет заглушку заголовка (16 байт). -2. Пишет payload всех записей в текущем порядке. -3. После каждого payload добавляет 0-padding до кратности 8. -4. Пересчитывает `sort_index` через сортировку имен. -5. Дописывает каталог (`entry_count * 64`). -6. Пересчитывает и записывает `total_size`. - -Итоговый файл должен удовлетворять всем ограничениям из разделов 3–5. - -## 8. Режим `raw` (совместимость инструментов) - -Для служебных инструментов допускается `raw_mode`: - -- любой бинарный файл трактуется как один «сырой» ресурс; -- возвращается одна запись (`name = RAW`, `data_offset = 0`, `size = len(file)`). - -Этот режим не является форматом `NRes` на диске, это только режим открытия. - -## 9. Контрольные инварианты - -Минимальный набор проверок при чтении: - -1. `magic == "NRes"`. -2. `version == 0x100`. -3. `entry_count >= 0`. -4. `header.total_size == file_size`. -5. Каталог находится в конце файла. -6. Для каждой записи диапазон данных не пересекает каталог. -7. Имя корректно C-терминировано и не длиннее 35 байт. - -Минимальный набор проверок при записи: - -1. Все имена <= 35 байт и без внутренних NUL. -2. `sort_index` формирует валидную перестановку `0..N-1`. -3. Все паддинги между payload состоят из нулевых байт. -4. `total_size` равен фактической длине выходного файла. - -## 10. Эмпирическая проверка на retail-корпусе - -Валидация на полном наборе `testdata/Parkan - Iron Strategy`: - -- найдено `120` архивов `NRes`; -- roundtrip `unpack -> repack -> byte-compare`: `120/120` совпали побайтно; -- критических расхождений формата не обнаружено. - -Проверено legacy-валидатором архивов. - -## 11. Статус покрытия и что осталось до 100% - -Закрыто: - -- формат заголовка/каталога; -- правила поиска; -- каноническая пересборка; -- строгие инварианты валидатора; -- побайтовый roundtrip на retail-корпусе. - -Осталось до полного 100% архитектурного покрытия движка: - -1. Формальная семантика `attr1/attr2/attr3` для всех типов ресурсов (частично вынесена в профильные страницы `msh`, `material`, `texture`, `fxid`, `terrain`). -2. Полная спецификация поведения при не-ASCII именах (в реальных игровых архивах используется ASCII-практика; для Unicode-коллации движок не документирован). -3. Полная спецификация платформенных гарантий атомарной записи (формат данных закрыт, но OS-уровневые гарантии замены файла зависят от платформы и файловой системы). -## 12. Специализация `objects.rlb` - -Хотя `objects.rlb` формально является обычным `NRes`, его payload имеет отдельный семантический контракт: - -- запись каталога соответствует одному объектному прототипу; -- payload записи - массив фиксированных ссылок `ObjectRef64` (`archive_name[32] + resource_name[32]`); -- runtime-резолв меша выполняется через эти ссылки, а не через имя entry `*.msh` внутри `objects.rlb`. - -Это означает, что `objects.rlb` должен рассматриваться не как архив мешей, а как реестр привязок между mission/unit-ключами и фактическими ресурсами. - -См. детальную страницу: - -- [Object registry (`objects.rlb`)](object-registry.md) |
