From ef9323772446e25a8d3dc5ee0e3954f921bb0074 Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Tue, 10 Feb 2026 01:58:16 +0400 Subject: Add .gitignore for Python and project-specific files; implement archive roundtrip validator - Updated .gitignore to include common Python artifacts and project-specific files. - Added `archive_roundtrip_validator.py` script for validating NRes and RsLi formats against real game data. - Created README.md for the tools directory, detailing usage and supported signatures. - Enhanced nres.md with practical nuances and empirical checks for game data. --- docs/specs/nres.md | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) (limited to 'docs/specs/nres.md') diff --git a/docs/specs/nres.md b/docs/specs/nres.md index 15cff63..52f4f79 100644 --- a/docs/specs/nres.md +++ b/docs/specs/nres.md @@ -298,6 +298,8 @@ def decrypt_rs_entries(encrypted_data: bytes, seed: int) -> bytes: `rsGetInfo` возвращает именно `unpacked_size` (то, сколько байт выдаст `rsLoad`). +Практический нюанс для метода `0x100` (Deflate): в реальных игровых данных встречается запись, где `packed_size` указывает на диапазон до `EOF + 1`. Поток успешно декодируется и без последнего байта; это похоже на lookahead-поведение декодера. + ## 2.7. Опциональный трейлер медиа (6 байт) При открытии с флагом `a2 & 2`: @@ -385,8 +387,8 @@ for i in range(N): # N = unpacked_size (для 0x20) или pack Если бит = 0 (ссылка): - Прочитать 2 байта: low_byte, high_byte - - offset = low_byte | ((high_byte & 0x0F) << 8) // 12 бит - - length = ((high_byte >> 4) & 0x0F) + 3 // 4 бита + 3 + - offset = low_byte | ((high_byte & 0xF0) << 4) // 12 бит + - length = (high_byte & 0x0F) + 3 // 4 бита + 3 - Скопировать length байт из ring_buffer[offset...]: для j от 0 до length-1: byte = ring_buffer[(offset + j) & 0xFFF] @@ -402,10 +404,10 @@ for i in range(N): # N = unpacked_size (для 0x20) или pack ``` Байт 0 (low): OOOOOOOO (биты [7:0] смещения) -Байт 1 (high): LLLLOOOO L = длина − 3, O = биты [11:8] смещения +Байт 1 (high): OOOOLLLL O = биты [11:8] смещения, L = длина − 3 -offset = low | ((high & 0x0F) << 8) // Диапазон: 0–4095 -length = (high >> 4) + 3 // Диапазон: 3–18 +offset = low | ((high & 0xF0) << 4) // Диапазон: 0–4095 +length = (high & 0x0F) + 3 // Диапазон: 3–18 ``` ## 3.3. LZSS с адаптивным кодированием Хаффмана (метод 0x80) @@ -703,3 +705,14 @@ struct RsLibEntry { // 64 байта (16 DWORD) - **Заголовок RsLi**: seed — **4 байта** (DWORD) по смещению 20, но используются только младшие 2 байта (`lo = byte[0]`, `hi = byte[1]`). - **Запись RsLi**: sort_to_original[i] — **2 байта** (int16) по смещению 18 записи. - **Данные при комбинированном XOR+LZSS**: seed — **4 байта** (DWORD) из поля по смещению 20 записи, но опять используются только 2 байта. + +## 6.7. Эмпирическая проверка на данных игры + +- Найдено архивов по сигнатуре: **122** (`NRes`: 120, `RsLi`: 2). +- Выполнен полный roundtrip `unpack -> pack -> byte-compare`: **122/122** архивов совпали побайтно. +- Для `RsLi` в проверенном наборе встретились методы: `0x040` и `0x100`. + +Подтверждённые нюансы: + +- Для LZSS (метод `0x040`) рабочая раскладка нибблов в ссылке: `OOOO LLLL`, а не `LLLL OOOO`. +- Для Deflate (метод `0x100`) возможен случай `packed_size == фактический_конец + 1` на последней записи файла. -- cgit v1.2.3