diff options
Diffstat (limited to 'docs/specs/rsli.md')
| -rw-r--r-- | docs/specs/rsli.md | 230 |
1 files changed, 0 insertions, 230 deletions
diff --git a/docs/specs/rsli.md b/docs/specs/rsli.md deleted file mode 100644 index 298cf2a..0000000 --- a/docs/specs/rsli.md +++ /dev/null @@ -1,230 +0,0 @@ -# RsLi - -`RsLi` — библиотечный контейнер ресурсов движка Parkan: Iron Strategy с зашифрованной таблицей записей и несколькими методами упаковки данных. - -Страница описывает формат и runtime-контракт в высокоуровневом виде, без ссылок на внутренние адреса/функции дизассемблера. - -Связанная страница: - -- [NRes](nres.md) - -## 1. Общая структура файла - -```text -[Header: 32] -[Entry table: entry_count * 32, XOR-encrypted] -[Packed payloads] -[Optional trailer: "AO" + overlay:u32] -``` - -В отличие от `NRes`, таблица записей у `RsLi` расположена в начале файла. - -## 2. Заголовок (32 байта) - -Все значения little-endian. - -| Offset | Size | Type | Поле | -|---:|---:|---|---| -| 0 | 2 | char[2] | `NL` (магия) | -| 2 | 1 | u8 | зарезервировано, в retail = `0` | -| 3 | 1 | u8 | версия, в retail = `1` | -| 4 | 2 | i16 | `entry_count` (должен быть `>= 0`) | -| 14 | 2 | u16 | `presorted_flag` (`0xABBA` = таблица сортировки уже задана) | -| 20 | 4 | u32 | `xor_seed` | - -Остальные байты заголовка считаются служебными и должны сохраняться без нормализации. - -## 3. Таблица записей (после дешифровки) - -Таблица начинается с `offset = 32`, размер `entry_count * 32`. - -Каждая запись (32 байта): - -| Offset | Size | Type | Поле | -|---:|---:|---|---| -| 0 | 12 | char[12] | `name_raw` (обычно uppercase ASCII, NUL optional) | -| 12 | 4 | bytes | служебный хвост, сохранять как есть | -| 16 | 2 | i16 | `flags` | -| 18 | 2 | i16 | `sort_to_original` | -| 20 | 4 | u32 | `unpacked_size` | -| 24 | 4 | u32 | `data_offset_raw` | -| 28 | 4 | u32 | `packed_size` | - -### 3.1. Метод упаковки - -`method = flags & 0x1E0` - -Поддерживаемые значения: - -| Маска | Метод | -|---:|---| -| `0x000` | без сжатия | -| `0x020` | XOR only | -| `0x040` | LZSS | -| `0x060` | XOR + LZSS | -| `0x080` | LZSS + адаптивный Huffman | -| `0x0A0` | XOR + LZSS + адаптивный Huffman | -| `0x100` | raw Deflate (RFC1951) | - -Другие значения считаются неподдерживаемыми. - -## 4. XOR-дешифрование таблицы и данных - -Для таблицы и XOR-методов payload используется один и тот же потоковый XOR-алгоритм. - -Ключ: - -- `key16 = xor_seed & 0xFFFF` (используются только младшие 16 бит seed). - -Состояние: - -```text -lo = key16 & 0xFF -hi = key16 >> 8 -``` - -Для каждого байта: - -```text -lo = hi XOR ((lo << 1) mod 256) -out = in XOR lo -hi = lo XOR (hi >> 1) -``` - -## 5. `sort_to_original` и поиск по имени - -### 5.1. Режим `presorted_flag == 0xABBA` - -`sort_to_original` обязан быть перестановкой `0..entry_count-1` без дубликатов. - -### 5.2. Режим без presorted-флага - -Слой загрузки строит `sort_to_original` самостоятельно: - -- сортирует индексы по `strcmp`-порядку имен (байтовое сравнение); -- записывает эту перестановку в lookup-таблицу. - -### 5.3. Поиск - -Поиск выполняется бинарным поиском по lookup-таблице: - -1. запрос переводится в uppercase ASCII; -2. на шаге бинарного поиска используется индекс `sort_to_original[mid]`; -3. сравнение имен — bytewise (`strcmp`-логика). - -Fail-safe: - -- при невалидном индексе lookup-таблицы выполняется линейный fallback. - -## 6. AO-трейлер и media overlay - -Опциональный трейлер в конце файла: - -```text -"AO" + overlay:u32 -``` - -Если трейлер присутствует: - -- эффективный offset payload: `effective_offset = data_offset_raw + overlay`. - -Ограничение: - -- `overlay <= file_size`. - -## 7. Декодирование payload по методам - -## 7.1. Без сжатия (`0x000`) - -Берутся первые `unpacked_size` байт из packed-диапазона. - -## 7.2. XOR only (`0x020`) - -XOR-дешифрование первых `unpacked_size` байт. - -## 7.3. LZSS (`0x040`, `0x060`) - -Параметры: - -- ring buffer: `4096` байт; -- начальное заполнение ring: `0x20`; -- стартовый указатель ring: `0xFEE`; -- control-биты читаются LSB-first. - -Правила: - -- `bit=1`: literal byte; -- `bit=0`: ссылка из 2 байт - `offset = low | ((high & 0xF0) << 4)` - `length = (high & 0x0F) + 3`. - -Для `0x060` XOR применяется на лету к packed-потоку до LZSS-декодирования. - -## 7.4. LZSS + адаптивный Huffman (`0x080`, `0x0A0`) - -Параметры: - -- `N=4096`, `F=60`, `THRESHOLD=2`; -- адаптивное дерево Huffman обновляется по мере декодирования. - -Для `0x0A0` XOR применяется на лету к битовому потоку до Huffman/LZSS-декодирования. - -## 7.5. Deflate (`0x100`) - -Используется raw Deflate-поток (RFC1951). - -Важно: - -- zlib-обертка (`RFC1950`) не принимается. - -## 8. Quirk: Deflate EOF+1 - -На retail-корпусе встречается один подтвержденный случай, где: - -- `effective_offset + packed_size == file_size + 1`. - -Совместимое поведение: - -- для метода `0x100` допустить чтение `packed_size - 1` байт (если включен режим совместимости); -- в строгом режиме считать это ошибкой. - -## 9. Контрольные инварианты - -Минимальные проверки: - -1. `magic == "NL"`, `reserved == 0`, `version == 1`. -2. `entry_count >= 0`. -3. `table_end <= file_size`. -4. Если `presorted_flag == 0xABBA`, `sort_to_original` — валидная перестановка. -5. `effective_offset + packed_size` не выходит за EOF (кроме разрешенного deflate EOF+1 quirk). -6. Итоговый распакованный размер равен `unpacked_size`. - -## 10. Эмпирическая проверка на retail-корпусе - -Проверка на полном наборе `testdata/Parkan - Iron Strategy`: - -- обнаружено `2` архива `RsLi`; -- roundtrip `unpack -> repack -> byte-compare`: `2/2` совпали побайтно; -- подтвержден ровно один `deflate EOF+1` случай (`sprites.lib`, entry `23`). - -Инструменты: - -- `tools/archive_roundtrip_validator.py` -- `crates/rsli` tests - -## 11. Статус покрытия и что осталось до 100% - -Закрыто: - -- формат заголовка/таблицы; -- XOR-алгоритм; -- все используемые методы декодирования; -- AO overlay; -- lookup-поиск и fallback; -- retail-валидация и побайтовый roundtrip. - -Осталось до полного 100% архитектурного покрытия движка: - -1. Полная функциональная семантика битов `flags` вне маски метода (`0x1E0`) для геймплейных подсистем. -2. Канонический writer для авторинга новых архивов со стабильной стратегией выбора методов (`0x080/0x0A0/0x100`) и параметров компрессии. -3. Формализация поведения для не-ASCII имен (на практике архивы используют ASCII-диапазон). |
