Add .gitignore for Python and project-specific files; implement archive roundtrip validator
Some checks failed
Test / cargo test (push) Failing after 50s

- 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.
This commit is contained in:
2026-02-10 01:58:16 +04:00
parent 58a896221f
commit ef93237724
4 changed files with 1172 additions and 6 deletions

View File

@@ -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) // Диапазон: 04095
length = (high >> 4) + 3 // Диапазон: 318
offset = low | ((high & 0xF0) << 4) // Диапазон: 04095
length = (high & 0x0F) + 3 // Диапазон: 318
```
## 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` на последней записи файла.