Add NRes format documentation and decompression algorithms
Some checks failed
Test / cargo test (push) Failing after 40s
Some checks failed
Test / cargo test (push) Failing after 40s
- Created `huffman_decompression.md` detailing the Huffman decompression algorithm used in NRes, including context structure, block modes, and decoding methods. - Created `overview.md` for the NRes format, outlining file structure, header details, file entries, and packing algorithms. - Updated `mkdocs.yml` to include new documentation files in the navigation structure.
This commit is contained in:
578
docs/specs/assets/nres/overview.md
Normal file
578
docs/specs/assets/nres/overview.md
Normal file
@@ -0,0 +1,578 @@
|
||||
# Документация по формату NRes
|
||||
|
||||
## Обзор
|
||||
|
||||
NRes — это формат контейнера ресурсов, используемый в игровом движке Nikita. Файл представляет собой архив, содержащий несколько упакованных файлов с метаданными и поддержкой различных методов сжатия.
|
||||
|
||||
## Структура файла NRes
|
||||
|
||||
### 1. Заголовок файла (16 байт)
|
||||
|
||||
```c
|
||||
struct NResHeader {
|
||||
uint32_t signature; // +0x00: Сигнатура "NRes" (0x7365526E в little-endian)
|
||||
uint32_t version; // +0x04: Версия формата (0x00000100 = версия 1.0)
|
||||
uint32_t fileCount; // +0x08: Количество файлов в архиве
|
||||
uint32_t fileSize; // +0x0C: Общий размер файла в байтах
|
||||
};
|
||||
```
|
||||
|
||||
**Детали:**
|
||||
|
||||
- `signature`: Константа `0x7365526E` (1936020046 в десятичном виде). Это ASCII строка "nRes" в обратном порядке байт
|
||||
- `version`: Всегда должна быть `0x00000100` (256 в десятичном виде) для версии 1.0
|
||||
- `fileCount`: Общее количество файлов в архиве (используется для валидации)
|
||||
- `fileSize`: Полный размер NRes файла, включая заголовок
|
||||
|
||||
### 2. Данные файлов
|
||||
|
||||
Сразу после заголовка (с offset 0x10) начинаются данные упакованных файлов. Они хранятся последовательно, один за другим. Точное расположение каждого файла определяется записью в каталоге (см. раздел 3).
|
||||
|
||||
**⚠️ ВАЖНО: Выравнивание данных**
|
||||
|
||||
Данные каждого файла **выравниваются по границе 8 байт**. После записи данных файла добавляется padding (нулевые байты) до ближайшего кратного 8 адреса.
|
||||
|
||||
**Формула выравнивания:**
|
||||
|
||||
```
|
||||
aligned_size = (packed_size + 7) & ~7
|
||||
padding_bytes = aligned_size - packed_size
|
||||
```
|
||||
|
||||
**Пример:**
|
||||
|
||||
- Файл размером 100 байт → padding 4 байта (до 104)
|
||||
- Файл размером 104 байт → padding 0 байт (уже выровнен)
|
||||
- Файл размером 105 байт → padding 3 байта (до 108)
|
||||
|
||||
Это означает, что:
|
||||
|
||||
1. `dataOffset` следующего файла всегда кратен 8
|
||||
2. Между данными файлов могут быть 0-7 байт нулевого padding
|
||||
3. При чтении нужно использовать `packedSize`, а не выравнивать вручную
|
||||
|
||||
### 3. Каталог файлов (Directory)
|
||||
|
||||
Каталог находится в **конце файла**. Его расположение вычисляется по формуле:
|
||||
|
||||
```
|
||||
DirectoryOffset = FileSize - (FileCount * 64)
|
||||
```
|
||||
|
||||
Каждая запись в каталоге имеет **фиксированный размер 64 байта (0x40)**:
|
||||
|
||||
```c
|
||||
struct NResFileEntry {
|
||||
char name[16]; // +0x00: Имя файла (NULL-terminated, uppercase)
|
||||
uint32_t crc32; // +0x10: CRC32 хеш упакованных данных
|
||||
uint32_t packMethod; // +0x14: Флаги метода упаковки и опции
|
||||
uint32_t unpackedSize; // +0x18: Размер файла после распаковки
|
||||
uint32_t packedSize; // +0x1C: Размер упакованных данных
|
||||
uint32_t dataOffset; // +0x20: Смещение данных от начала файла
|
||||
uint32_t fastDataPtr; // +0x24: Указатель для быстрого доступа (в памяти)
|
||||
uint32_t xorSize; // +0x28: Размер данных для XOR-шифрования
|
||||
uint32_t sortIndex; // +0x2C: Индекс для сортировки по имени
|
||||
uint32_t reserved[4]; // +0x30: Зарезервировано (обычно нули)
|
||||
};
|
||||
```
|
||||
|
||||
## Подробное описание полей каталога
|
||||
|
||||
### Поле: name (смещение +0x00, 16 байт)
|
||||
|
||||
- **Назначение**: Имя файла в архиве
|
||||
- **Формат**: NULL-terminated строка, максимум 15 символов + NULL
|
||||
- **Особенности**:
|
||||
- Все символы хранятся в **UPPERCASE** (заглавными буквами)
|
||||
- При поиске файлов используется регистронезависимое сравнение (`_strcmpi`)
|
||||
- Если имя короче 16 байт, остаток заполняется нулями
|
||||
|
||||
### Поле: crc32 (смещение +0x10, 4 байта)
|
||||
|
||||
- **Назначение**: Контрольная сумма CRC32 упакованных данных
|
||||
- **Использование**: Проверка целостности данных при чтении
|
||||
|
||||
### Поле: packMethod (смещение +0x14, 4 байта)
|
||||
|
||||
**Критически важное поле!** Содержит битовые флаги, определяющие метод обработки данных:
|
||||
|
||||
```c
|
||||
// Маски для извлечения метода упаковки
|
||||
#define PACK_METHOD_MASK 0x1E0 // Биты 5-8 (основной метод)
|
||||
#define PACK_METHOD_MASK2 0x1C0 // Биты 6-7 (альтернативная маска)
|
||||
|
||||
// Методы упаковки (биты 5-8)
|
||||
#define PACK_NONE 0x000 // Нет упаковки (копирование)
|
||||
#define PACK_XOR 0x020 // XOR-шифрование
|
||||
#define PACK_FRES 0x040 // FRES компрессия (устаревшая)
|
||||
#define PACK_FRES_XOR 0x060 // FRES + XOR (два прохода)
|
||||
#define PACK_ZLIB 0x080 // Zlib сжатие (устаревшее)
|
||||
#define PACK_ZLIB_XOR 0x0A0 // Zlib + XOR (два прохода)
|
||||
#define PACK_HUFFMAN 0x0E0 // Huffman кодирование (основной метод)
|
||||
|
||||
// Дополнительные флаги
|
||||
#define FLAG_ENCRYPTED 0x040 // Файл зашифрован/требует декодирования
|
||||
```
|
||||
|
||||
**Алгоритм определения метода:**
|
||||
|
||||
1. Извлечь биты `packMethod & 0x1E0`
|
||||
2. Проверить конкретные значения:
|
||||
- `0x000`: Данные не сжаты, простое копирование
|
||||
- `0x020`: XOR-шифрование с двухбайтовым ключом
|
||||
- `0x040` или `0x060`: FRES компрессия (может быть + XOR)
|
||||
- `0x080` или `0x0A0`: Zlib компрессия (может быть + XOR)
|
||||
- `0x0E0`: Huffman кодирование (наиболее распространенный)
|
||||
|
||||
### Поле: unpackedSize (смещение +0x18, 4 байта)
|
||||
|
||||
- **Назначение**: Размер файла после полной распаковки
|
||||
- **Использование**:
|
||||
- Для выделения памяти под распакованные данные
|
||||
- Для проверки корректности распаковки
|
||||
|
||||
### Поле: packedSize (смещение +0x1C, 4 байта)
|
||||
|
||||
- **Назначение**: Размер сжатых данных в архиве
|
||||
- **Особенности**:
|
||||
- Если `packedSize == 0`, файл пустой или является указателем
|
||||
- Для несжатых файлов: `packedSize == unpackedSize`
|
||||
|
||||
### Поле: dataOffset (смещение +0x20, 4 байта)
|
||||
|
||||
- **Назначение**: Абсолютное смещение данных файла от начала NRes файла
|
||||
- **Формула вычисления**: `BaseAddress + dataOffset = начало данных`
|
||||
- **Диапазон**: Обычно от 0x10 (после заголовка) до начала каталога
|
||||
|
||||
### Поле: fastDataPtr (смещение +0x24, 4 байта)
|
||||
|
||||
- **Назначение**: Указатель на данные в памяти для быстрого доступа
|
||||
- **Использование**: Только во время выполнения (runtime)
|
||||
- **В файле**: Обычно равно 0 или содержит относительный offset
|
||||
- **Особенность**: Используется функцией `rsLoadFast()` для файлов без упаковки
|
||||
|
||||
### Поле: xorSize (смещение +0x28, 4 байта)
|
||||
|
||||
- **Назначение**: Размер данных для XOR-шифрования при комбинированных методах
|
||||
- **Использование**:
|
||||
- Когда `packMethod & 0x60 == 0x60` (FRES + XOR)
|
||||
- Сначала применяется XOR к этому количеству байт, затем FRES к результату
|
||||
- **Значение**: Может отличаться от `packedSize` при многоэтапной упаковке
|
||||
|
||||
### Поле: sortIndex (смещение +0x2C, 4 байта)
|
||||
|
||||
- **Назначение**: Индекс для быстрого поиска по отсортированному каталогу
|
||||
- **Использование**:
|
||||
- Каталог сортируется по алфавиту (имени файлов)
|
||||
- `sortIndex` хранит оригинальный порядковый номер файла
|
||||
- Позволяет использовать бинарный поиск для функции `rsFind()`
|
||||
|
||||
### Поле: reserved (смещение +0x30, 16 байт)
|
||||
|
||||
- **Назначение**: Зарезервировано для будущих расширений
|
||||
- **В файле**: Обычно заполнено нулями
|
||||
- **Может содержать**: Дополнительные метаданные в новых версиях формата
|
||||
|
||||
## Алгоритмы упаковки
|
||||
|
||||
### 1. Без упаковки (PACK_NONE = 0x000)
|
||||
|
||||
```
|
||||
Простое копирование данных:
|
||||
memcpy(destination, source, packedSize);
|
||||
```
|
||||
|
||||
### 2. XOR-шифрование (PACK_XOR = 0x020)
|
||||
|
||||
```c
|
||||
// Ключ берется из поля crc32
|
||||
uint16_t key = (uint16_t)(crc32 & 0xFFFF);
|
||||
|
||||
for (int i = 0; i < packedSize; i++) {
|
||||
uint8_t byte = source[i];
|
||||
destination[i] = byte ^ (key >> 8) ^ (key << 1);
|
||||
|
||||
// Обновление ключа
|
||||
uint8_t newByte = (key >> 8) ^ (key << 1);
|
||||
key = (newByte ^ ((key >> 8) >> 1)) | (newByte << 8);
|
||||
}
|
||||
```
|
||||
|
||||
**Ключевые особенности:**
|
||||
|
||||
- Используется 16-битный ключ из младших байт CRC32
|
||||
- Ключ изменяется после каждого байта по специальному алгоритму
|
||||
- Операции: XOR с старшим байтом ключа и со сдвинутым значением
|
||||
|
||||
### 3. [FRES компрессия](fres_decompression.md) (PACK_FRES = 0x040, 0x060)
|
||||
|
||||
Алгоритм FRES — это RLE-подобное сжатие с особой кодировкой повторов:
|
||||
|
||||
```
|
||||
sub_1001B22E() - функция декомпрессии FRES
|
||||
- Читает управляющие байты
|
||||
- Декодирует литералы и повторы
|
||||
- Использует скользящее окно для ссылок
|
||||
```
|
||||
|
||||
### 4. [Huffman кодирование](huffman_decompression.md) (PACK_HUFFMAN = 0x0E0)
|
||||
|
||||
Наиболее сложный и эффективный метод:
|
||||
|
||||
```c
|
||||
// Структура декодера
|
||||
struct HuffmanDecoder {
|
||||
uint32_t bitBuffer[0x4000]; // Буфер для битов
|
||||
uint32_t compressedSize; // Размер сжатых данных
|
||||
uint32_t outputPosition; // Текущая позиция в выходном буфере
|
||||
uint32_t inputPosition; // Позиция в входных данных
|
||||
uint8_t* sourceData; // Указатель на сжатые данные
|
||||
uint8_t* destData; // Указатель на выходной буфер
|
||||
uint32_t bitPosition; // Позиция бита в буфере
|
||||
// ... дополнительные поля
|
||||
};
|
||||
```
|
||||
|
||||
**Процесс декодирования:**
|
||||
|
||||
1. Инициализация структуры декодера
|
||||
2. Чтение битов и построение дерева Huffman
|
||||
3. Декодирование символов по дереву
|
||||
4. Запись в выходной буфер
|
||||
|
||||
## Высокоуровневая инструкция по реализации
|
||||
|
||||
### Этап 1: Открытие файла
|
||||
|
||||
```python
|
||||
def open_nres_file(filepath):
|
||||
with open(filepath, 'rb') as f:
|
||||
# 1. Читаем заголовок (16 байт)
|
||||
header_data = f.read(16)
|
||||
signature, version, file_count, file_size = struct.unpack('<4I', header_data)
|
||||
|
||||
# 2. Проверяем сигнатуру
|
||||
if signature != 0x7365526E: # "nRes"
|
||||
raise ValueError("Неверная сигнатура файла")
|
||||
|
||||
# 3. Проверяем версию
|
||||
if version != 0x100:
|
||||
raise ValueError(f"Неподдерживаемая версия: {version}")
|
||||
|
||||
# 4. Вычисляем расположение каталога
|
||||
directory_offset = file_size - (file_count * 64)
|
||||
|
||||
# 5. Читаем весь файл в память (или используем memory mapping)
|
||||
f.seek(0)
|
||||
file_data = f.read()
|
||||
|
||||
return {
|
||||
'file_count': file_count,
|
||||
'file_size': file_size,
|
||||
'directory_offset': directory_offset,
|
||||
'data': file_data
|
||||
}
|
||||
```
|
||||
|
||||
### Этап 2: Чтение каталога
|
||||
|
||||
```python
|
||||
def read_directory(nres_file):
|
||||
data = nres_file['data']
|
||||
offset = nres_file['directory_offset']
|
||||
file_count = nres_file['file_count']
|
||||
|
||||
entries = []
|
||||
|
||||
for i in range(file_count):
|
||||
entry_offset = offset + (i * 64)
|
||||
entry_data = data[entry_offset:entry_offset + 64]
|
||||
|
||||
# Парсим 64-байтовую запись
|
||||
name = entry_data[0:16].decode('ascii').rstrip('\x00')
|
||||
crc32, pack_method, unpacked_size, packed_size, data_offset, \
|
||||
fast_ptr, xor_size, sort_index = struct.unpack('<8I', entry_data[16:48])
|
||||
|
||||
entry = {
|
||||
'name': name,
|
||||
'crc32': crc32,
|
||||
'pack_method': pack_method,
|
||||
'unpacked_size': unpacked_size,
|
||||
'packed_size': packed_size,
|
||||
'data_offset': data_offset,
|
||||
'fast_data_ptr': fast_ptr,
|
||||
'xor_size': xor_size,
|
||||
'sort_index': sort_index
|
||||
}
|
||||
|
||||
entries.append(entry)
|
||||
|
||||
return entries
|
||||
```
|
||||
|
||||
### Этап 3: Поиск файла по имени
|
||||
|
||||
```python
|
||||
def find_file(entries, filename):
|
||||
# Имена в архиве хранятся в UPPERCASE
|
||||
search_name = filename.upper()
|
||||
|
||||
# Используем бинарный поиск, так как каталог отсортирован
|
||||
# Сортировка по sort_index восстанавливает алфавитный порядок
|
||||
sorted_entries = sorted(entries, key=lambda e: e['sort_index'])
|
||||
|
||||
left, right = 0, len(sorted_entries) - 1
|
||||
|
||||
while left <= right:
|
||||
mid = (left + right) // 2
|
||||
mid_name = sorted_entries[mid]['name']
|
||||
|
||||
if mid_name == search_name:
|
||||
return sorted_entries[mid]
|
||||
elif mid_name < search_name:
|
||||
left = mid + 1
|
||||
else:
|
||||
right = mid - 1
|
||||
|
||||
return None
|
||||
```
|
||||
|
||||
### Этап 4: Извлечение данных файла
|
||||
|
||||
```python
|
||||
def extract_file(nres_file, entry):
|
||||
data = nres_file['data']
|
||||
|
||||
# 1. Получаем упакованные данные
|
||||
packed_data = data[entry['data_offset']:
|
||||
entry['data_offset'] + entry['packed_size']]
|
||||
|
||||
# 2. Определяем метод упаковки
|
||||
pack_method = entry['pack_method'] & 0x1E0
|
||||
|
||||
# 3. Распаковываем в зависимости от метода
|
||||
if pack_method == 0x000:
|
||||
# Без упаковки
|
||||
return unpack_none(packed_data)
|
||||
|
||||
elif pack_method == 0x020:
|
||||
# XOR-шифрование
|
||||
return unpack_xor(packed_data, entry['crc32'], entry['unpacked_size'])
|
||||
|
||||
elif pack_method == 0x040 or pack_method == 0x060:
|
||||
# FRES компрессия (может быть с XOR)
|
||||
if pack_method == 0x060:
|
||||
# Сначала XOR
|
||||
temp_data = unpack_xor(packed_data, entry['crc32'], entry['xor_size'])
|
||||
return unpack_fres(temp_data, entry['unpacked_size'])
|
||||
else:
|
||||
return unpack_fres(packed_data, entry['unpacked_size'])
|
||||
|
||||
elif pack_method == 0x0E0:
|
||||
# Huffman кодирование
|
||||
return unpack_huffman(packed_data, entry['unpacked_size'])
|
||||
|
||||
else:
|
||||
raise ValueError(f"Неподдерживаемый метод упаковки: 0x{pack_method:X}")
|
||||
```
|
||||
|
||||
### Этап 5: Реализация алгоритмов распаковки
|
||||
|
||||
```python
|
||||
def unpack_none(data):
|
||||
"""Без упаковки - просто возвращаем данные"""
|
||||
return data
|
||||
|
||||
def unpack_xor(data, crc32, size):
|
||||
"""XOR-дешифрование с изменяющимся ключом"""
|
||||
result = bytearray(size)
|
||||
key = crc32 & 0xFFFF # Берем младшие 16 бит
|
||||
|
||||
for i in range(min(size, len(data))):
|
||||
byte = data[i]
|
||||
|
||||
# XOR операция
|
||||
high_byte = (key >> 8) & 0xFF
|
||||
shifted = (key << 1) & 0xFFFF
|
||||
result[i] = byte ^ high_byte ^ (shifted & 0xFF)
|
||||
|
||||
# Обновление ключа
|
||||
new_byte = high_byte ^ (key << 1)
|
||||
key = (new_byte ^ (high_byte >> 1)) | ((new_byte & 0xFF) << 8)
|
||||
key &= 0xFFFF
|
||||
|
||||
return bytes(result)
|
||||
|
||||
def unpack_fres(data, unpacked_size):
|
||||
"""
|
||||
FRES декомпрессия - гибридный RLE+LZ77 алгоритм
|
||||
Полная реализация в nres_decompression.py (класс FRESDecoder)
|
||||
"""
|
||||
from nres_decompression import FRESDecoder
|
||||
decoder = FRESDecoder()
|
||||
return decoder.decompress(data, unpacked_size)
|
||||
|
||||
def unpack_huffman(data, unpacked_size):
|
||||
"""
|
||||
Huffman декодирование (DEFLATE-подобный)
|
||||
Полная реализация в nres_decompression.py (класс HuffmanDecoder)
|
||||
"""
|
||||
from nres_decompression import HuffmanDecoder
|
||||
decoder = HuffmanDecoder()
|
||||
return decoder.decompress(data, unpacked_size)
|
||||
```
|
||||
|
||||
### Этап 6: Извлечение всех файлов
|
||||
|
||||
```python
|
||||
def extract_all(nres_filepath, output_dir):
|
||||
import os
|
||||
|
||||
# 1. Открываем NRes файл
|
||||
nres_file = open_nres_file(nres_filepath)
|
||||
|
||||
# 2. Читаем каталог
|
||||
entries = read_directory(nres_file)
|
||||
|
||||
# 3. Создаем выходную директорию
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# 4. Извлекаем каждый файл
|
||||
for entry in entries:
|
||||
print(f"Извлечение: {entry['name']}")
|
||||
|
||||
try:
|
||||
# Извлекаем данные
|
||||
unpacked_data = extract_file(nres_file, entry)
|
||||
|
||||
# Сохраняем в файл
|
||||
output_path = os.path.join(output_dir, entry['name'])
|
||||
with open(output_path, 'wb') as f:
|
||||
f.write(unpacked_data)
|
||||
|
||||
print(f" ✓ Успешно ({len(unpacked_data)} байт)")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ✗ Ошибка: {e}")
|
||||
```
|
||||
|
||||
## Особенности и важные замечания
|
||||
|
||||
### 1. Порядок байт (Endianness)
|
||||
|
||||
- **Все многобайтовые значения хранятся в Little-Endian порядке**
|
||||
- При чтении используйте `struct.unpack('<...')`
|
||||
|
||||
### 2. Сортировка каталога
|
||||
|
||||
- Каталог файлов **отсортирован по имени файла** (алфавитный порядок)
|
||||
- Поле `sortIndex` хранит оригинальный индекс до сортировки
|
||||
- Это позволяет использовать бинарный поиск
|
||||
|
||||
### 3. Регистр символов
|
||||
|
||||
- Все имена файлов конвертируются в **UPPERCASE** (заглавные буквы)
|
||||
- При поиске используйте регистронезависимое сравнение
|
||||
|
||||
### 4. Memory Mapping
|
||||
|
||||
- Оригинальный код использует `MapViewOfFile` для эффективной работы с большими файлами
|
||||
- Рекомендуется использовать memory-mapped файлы для больших архивов
|
||||
|
||||
### 5. Валидация данных
|
||||
|
||||
- **Всегда проверяйте сигнатуру** перед обработкой
|
||||
- **Проверяйте версию** формата
|
||||
- **Проверяйте CRC32** после распаковки
|
||||
- **Проверяйте размеры** (unpacked_size должен совпадать с результатом)
|
||||
|
||||
### 6. Обработка ошибок
|
||||
|
||||
- Файл может быть поврежден
|
||||
- Метод упаковки может быть неподдерживаемым
|
||||
- Данные могут быть частично зашифрованы
|
||||
|
||||
### 7. Производительность
|
||||
|
||||
- Для несжатых файлов (`packMethod & 0x1E0 == 0`) можно использовать прямое чтение
|
||||
- Поле `fastDataPtr` может содержать кешированный указатель
|
||||
- Используйте буферизацию при последовательном чтении
|
||||
|
||||
### 8. Выравнивание данных
|
||||
|
||||
- **Все данные файлов выравниваются по 8 байт**
|
||||
- После каждого файла может быть 0-7 байт нулевого padding
|
||||
- `dataOffset` следующего файла всегда кратен 8
|
||||
- При чтении используйте `packedSize` из записи, не вычисляйте выравнивание
|
||||
- При создании архива добавляйте padding: `padding = ((size + 7) & ~7) - size`
|
||||
|
||||
## Пример использования
|
||||
|
||||
```python
|
||||
# Открыть архив
|
||||
nres = open_nres_file("resources.nres")
|
||||
|
||||
# Прочитать каталог
|
||||
entries = read_directory(nres)
|
||||
|
||||
# Вывести список файлов
|
||||
for entry in entries:
|
||||
print(f"{entry['name']:20s} - {entry['unpacked_size']:8d} байт")
|
||||
|
||||
# Найти конкретный файл
|
||||
entry = find_file(entries, "texture.bmp")
|
||||
if entry:
|
||||
data = extract_file(nres, entry)
|
||||
with open("extracted_texture.bmp", "wb") as f:
|
||||
f.write(data)
|
||||
|
||||
# Извлечь все файлы
|
||||
extract_all("resources.nres", "./extracted/")
|
||||
```
|
||||
|
||||
## Дополнительные функции
|
||||
|
||||
### Проверка формата файла
|
||||
|
||||
```python
|
||||
def is_nres_file(filepath):
|
||||
try:
|
||||
with open(filepath, 'rb') as f:
|
||||
signature = struct.unpack('<I', f.read(4))[0]
|
||||
return signature == 0x7365526E
|
||||
except:
|
||||
return False
|
||||
```
|
||||
|
||||
### Получение информации о файле
|
||||
|
||||
```python
|
||||
def get_file_info(entry):
|
||||
pack_names = {
|
||||
0x000: "Без сжатия",
|
||||
0x020: "XOR",
|
||||
0x040: "FRES",
|
||||
0x060: "FRES+XOR",
|
||||
0x080: "Zlib",
|
||||
0x0A0: "Zlib+XOR",
|
||||
0x0E0: "Huffman"
|
||||
}
|
||||
|
||||
pack_method = entry['pack_method'] & 0x1E0
|
||||
pack_name = pack_names.get(pack_method, f"Неизвестный (0x{pack_method:X})")
|
||||
|
||||
ratio = 100.0 * entry['packed_size'] / entry['unpacked_size'] if entry['unpacked_size'] > 0 else 0
|
||||
|
||||
return {
|
||||
'name': entry['name'],
|
||||
'size': entry['unpacked_size'],
|
||||
'packed': entry['packed_size'],
|
||||
'compression': pack_name,
|
||||
'ratio': f"{ratio:.1f}%",
|
||||
'crc32': f"0x{entry['crc32']:08X}"
|
||||
}
|
||||
```
|
||||
|
||||
## Заключение
|
||||
|
||||
Формат NRes представляет собой эффективный архив с поддержкой множества методов сжатия.
|
||||
Reference in New Issue
Block a user