427 lines
14 KiB
Markdown
427 lines
14 KiB
Markdown
|
|
# FRES Декомпрессия
|
|||
|
|
|
|||
|
|
## Обзор
|
|||
|
|
|
|||
|
|
FRES — это гибридный алгоритм сжатия, использующий комбинацию RLE (Run-Length Encoding) и LZ77-подобного сжатия со скользящим окном. Существуют два режима работы: **adaptive Huffman** (флаг `a1 < 0`) и **простой битовый** (флаг `a1 >= 0`).
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
char __stdcall sub_1001B22E(
|
|||
|
|
char a1, // Флаг режима (< 0 = Huffman, >= 0 = простой)
|
|||
|
|
int a2, // Ключ/seed (не используется напрямую)
|
|||
|
|
_BYTE *a3, // Выходной буфер
|
|||
|
|
int a4, // Размер выходного буфера
|
|||
|
|
_BYTE *a5, // Входные сжатые данные
|
|||
|
|
int a6 // Размер входных данных
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Структуры данных
|
|||
|
|
|
|||
|
|
### Глобальные переменные
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
byte_1003A910[4096] // Циклический буфер скользящего окна (12 бит адрес)
|
|||
|
|
dword_1003E09C // Указатель на конец выходного буфера
|
|||
|
|
dword_1003E0A0 // Текущая позиция в циклическом буфере
|
|||
|
|
dword_1003E098 // Состояние Huffman дерева
|
|||
|
|
dword_1003E0A4 // Длина повтора для LZ77
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Константы
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
#define WINDOW_SIZE 4096 // Размер скользящего окна (0x1000)
|
|||
|
|
#define WINDOW_MASK 0x0FFF // Маска для циклического буфера
|
|||
|
|
#define INIT_POS_NEG 4078 // Начальная позиция для Huffman режима
|
|||
|
|
#define INIT_POS_POS 4036 // Начальная позиция для простого режима
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Режим 1: Простой битовый режим (a1 >= 0)
|
|||
|
|
|
|||
|
|
Это более простой режим без Huffman кодирования. Работает следующим образом:
|
|||
|
|
|
|||
|
|
### Алгоритм
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Инициализация:
|
|||
|
|
position = 4036
|
|||
|
|
flags = 0
|
|||
|
|
flagBits = 0
|
|||
|
|
|
|||
|
|
Цикл декомпрессии:
|
|||
|
|
Пока есть входные данные и выходной буфер не заполнен:
|
|||
|
|
|
|||
|
|
1. Прочитать бит флага:
|
|||
|
|
if (flagBits высокий бит == 0):
|
|||
|
|
flags = *input++
|
|||
|
|
flagBits = 127 (0x7F)
|
|||
|
|
|
|||
|
|
flag_bit = flags & 1
|
|||
|
|
flags >>= 1
|
|||
|
|
|
|||
|
|
2. Прочитать второй бит:
|
|||
|
|
if (flagBits низкий бит == 0):
|
|||
|
|
загрузить новый байт флагов
|
|||
|
|
|
|||
|
|
second_bit = flags & 1
|
|||
|
|
flags >>= 1
|
|||
|
|
|
|||
|
|
3. Выбор действия по битам:
|
|||
|
|
|
|||
|
|
a) Если оба бита == 0:
|
|||
|
|
// Литерал - копировать один байт
|
|||
|
|
byte = *input++
|
|||
|
|
window[position] = byte
|
|||
|
|
*output++ = byte
|
|||
|
|
position = (position + 1) & 0xFFF
|
|||
|
|
|
|||
|
|
b) Если второй бит == 0 (первый == 1):
|
|||
|
|
// LZ77 копирование
|
|||
|
|
word = *(uint16*)input
|
|||
|
|
input += 2
|
|||
|
|
|
|||
|
|
offset = (word >> 4) & 0xFFF // 12 бит offset
|
|||
|
|
length = (word & 0xF) + 3 // 4 бита длины + 3
|
|||
|
|
|
|||
|
|
src_pos = offset
|
|||
|
|
Повторить length раз:
|
|||
|
|
byte = window[src_pos]
|
|||
|
|
window[position] = byte
|
|||
|
|
*output++ = byte
|
|||
|
|
src_pos = (src_pos + 1) & 0xFFF
|
|||
|
|
position = (position + 1) & 0xFFF
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Формат сжатых данных (простой режим)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Битовый поток:
|
|||
|
|
|
|||
|
|
[FLAG_BIT] [SECOND_BIT] [DATA]
|
|||
|
|
|
|||
|
|
Где:
|
|||
|
|
FLAG_BIT = 0, SECOND_BIT = 0: → Литерал (1 байт следует)
|
|||
|
|
FLAG_BIT = 1, SECOND_BIT = 0: → LZ77 копирование (2 байта следуют)
|
|||
|
|
FLAG_BIT = любой, SECOND_BIT = 1: → Литерал (1 байт следует)
|
|||
|
|
|
|||
|
|
Формат LZ77 копирования (2 байта, little-endian):
|
|||
|
|
Байт 0: offset_low (биты 0-7)
|
|||
|
|
Байт 1: [length:4][offset_high:4]
|
|||
|
|
|
|||
|
|
offset = (byte1 >> 4) | (byte0 << 4) // 12 бит
|
|||
|
|
length = (byte1 & 0x0F) + 3 // 4 бита + 3 = 3-18 байт
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Режим 2: Adaptive Huffman режим (a1 < 0)
|
|||
|
|
|
|||
|
|
Более сложный режим с динамическим Huffman деревом.
|
|||
|
|
|
|||
|
|
### Инициализация Huffman
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
Инициализация таблиц:
|
|||
|
|
1. Создание таблицы быстрого декодирования (dword_1003B94C[256])
|
|||
|
|
2. Инициализация длин кодов (byte_1003BD4C[256])
|
|||
|
|
3. Построение начального дерева (627 узлов)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Алгоритм декодирования
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Инициализация:
|
|||
|
|
position = 4078
|
|||
|
|
bit_buffer = 0
|
|||
|
|
bit_count = 8
|
|||
|
|
|
|||
|
|
Инициализировать окно значением 0x20 (пробел):
|
|||
|
|
for i in range(2039):
|
|||
|
|
window[i] = 0x20
|
|||
|
|
|
|||
|
|
Цикл декомпрессии:
|
|||
|
|
Пока не конец выходного буфера:
|
|||
|
|
|
|||
|
|
1. Декодировать символ через Huffman дерево:
|
|||
|
|
|
|||
|
|
tree_index = dword_1003E098 // начальный узел
|
|||
|
|
|
|||
|
|
Пока tree_index < 627: // внутренний узел
|
|||
|
|
bit = прочитать_бит()
|
|||
|
|
tree_index = tree[tree_index + bit]
|
|||
|
|
|
|||
|
|
symbol = tree_index - 627 // лист дерева
|
|||
|
|
|
|||
|
|
Обновить дерево (sub_1001B0AE)
|
|||
|
|
|
|||
|
|
2. Обработать символ:
|
|||
|
|
|
|||
|
|
if (symbol < 256):
|
|||
|
|
// Литерал
|
|||
|
|
window[position] = symbol
|
|||
|
|
*output++ = symbol
|
|||
|
|
position = (position + 1) & 0xFFF
|
|||
|
|
|
|||
|
|
else:
|
|||
|
|
// LZ77 копирование
|
|||
|
|
length = symbol - 253
|
|||
|
|
|
|||
|
|
// Прочитать offset (закодирован отдельно)
|
|||
|
|
offset_bits = прочитать_биты(таблица длин)
|
|||
|
|
offset = декодировать(offset_bits)
|
|||
|
|
|
|||
|
|
src_pos = (position - 1 - offset) & 0xFFF
|
|||
|
|
|
|||
|
|
Повторить length раз:
|
|||
|
|
byte = window[src_pos]
|
|||
|
|
window[position] = byte
|
|||
|
|
*output++ = byte
|
|||
|
|
src_pos = (src_pos + 1) & 0xFFF
|
|||
|
|
position = (position + 1) & 0xFFF
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Обновление дерева
|
|||
|
|
|
|||
|
|
Адаптивное Huffman дерево обновляется после каждого декодированного символа:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Алгоритм обновления:
|
|||
|
|
1. Увеличить счетчик частоты символа
|
|||
|
|
2. Если частота превысила порог:
|
|||
|
|
Перестроить узлы дерева (swapping)
|
|||
|
|
3. Если счетчик достиг 0x8000:
|
|||
|
|
Пересчитать все частоты (разделить на 2)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Псевдокод полной реализации
|
|||
|
|
|
|||
|
|
### Декодер (простой режим)
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
def fres_decompress_simple(input_data, output_size):
|
|||
|
|
"""
|
|||
|
|
FRES декомпрессия в простом режиме
|
|||
|
|
"""
|
|||
|
|
# Инициализация
|
|||
|
|
window = bytearray(4096)
|
|||
|
|
position = 4036
|
|||
|
|
output = bytearray()
|
|||
|
|
|
|||
|
|
input_pos = 0
|
|||
|
|
flags = 0
|
|||
|
|
flag_bits_high = 0
|
|||
|
|
flag_bits_low = 0
|
|||
|
|
|
|||
|
|
while len(output) < output_size and input_pos < len(input_data):
|
|||
|
|
# Читаем флаг высокого бита
|
|||
|
|
if (flag_bits_high & 1) == 0:
|
|||
|
|
if input_pos >= len(input_data):
|
|||
|
|
break
|
|||
|
|
flags = input_data[input_pos]
|
|||
|
|
input_pos += 1
|
|||
|
|
flag_bits_high = 127 # 0x7F
|
|||
|
|
|
|||
|
|
flag_high = flag_bits_high & 1
|
|||
|
|
flag_bits_high >>= 1
|
|||
|
|
|
|||
|
|
# Читаем флаг низкого бита
|
|||
|
|
if input_pos >= len(input_data):
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
if (flag_bits_low & 1) == 0:
|
|||
|
|
flags = input_data[input_pos]
|
|||
|
|
input_pos += 1
|
|||
|
|
flag_bits_low = 127
|
|||
|
|
|
|||
|
|
flag_low = flags & 1
|
|||
|
|
flags >>= 1
|
|||
|
|
|
|||
|
|
# Обработка по флагам
|
|||
|
|
if not flag_low: # Второй бит == 0
|
|||
|
|
if not flag_high: # Оба бита == 0
|
|||
|
|
# Литерал
|
|||
|
|
if input_pos >= len(input_data):
|
|||
|
|
break
|
|||
|
|
byte = input_data[input_pos]
|
|||
|
|
input_pos += 1
|
|||
|
|
|
|||
|
|
window[position] = byte
|
|||
|
|
output.append(byte)
|
|||
|
|
position = (position + 1) & 0xFFF
|
|||
|
|
|
|||
|
|
else: # Первый == 1, второй == 0
|
|||
|
|
# LZ77 копирование
|
|||
|
|
if input_pos + 1 >= len(input_data):
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
word = input_data[input_pos] | (input_data[input_pos + 1] << 8)
|
|||
|
|
input_pos += 2
|
|||
|
|
|
|||
|
|
offset = (word >> 4) & 0xFFF
|
|||
|
|
length = (word & 0xF) + 3
|
|||
|
|
|
|||
|
|
for _ in range(length):
|
|||
|
|
if len(output) >= output_size:
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
byte = window[offset]
|
|||
|
|
window[position] = byte
|
|||
|
|
output.append(byte)
|
|||
|
|
|
|||
|
|
offset = (offset + 1) & 0xFFF
|
|||
|
|
position = (position + 1) & 0xFFF
|
|||
|
|
|
|||
|
|
else: # Второй бит == 1
|
|||
|
|
# Литерал
|
|||
|
|
if input_pos >= len(input_data):
|
|||
|
|
break
|
|||
|
|
byte = input_data[input_pos]
|
|||
|
|
input_pos += 1
|
|||
|
|
|
|||
|
|
window[position] = byte
|
|||
|
|
output.append(byte)
|
|||
|
|
position = (position + 1) & 0xFFF
|
|||
|
|
|
|||
|
|
return bytes(output[:output_size])
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Вспомогательные функции
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class BitReader:
|
|||
|
|
"""Класс для побитового чтения"""
|
|||
|
|
|
|||
|
|
def __init__(self, data):
|
|||
|
|
self.data = data
|
|||
|
|
self.pos = 0
|
|||
|
|
self.bit_buffer = 0
|
|||
|
|
self.bits_available = 0
|
|||
|
|
|
|||
|
|
def read_bit(self):
|
|||
|
|
"""Прочитать один бит"""
|
|||
|
|
if self.bits_available == 0:
|
|||
|
|
if self.pos >= len(self.data):
|
|||
|
|
return 0
|
|||
|
|
self.bit_buffer = self.data[self.pos]
|
|||
|
|
self.pos += 1
|
|||
|
|
self.bits_available = 8
|
|||
|
|
|
|||
|
|
bit = self.bit_buffer & 1
|
|||
|
|
self.bit_buffer >>= 1
|
|||
|
|
self.bits_available -= 1
|
|||
|
|
return bit
|
|||
|
|
|
|||
|
|
def read_bits(self, count):
|
|||
|
|
"""Прочитать несколько бит"""
|
|||
|
|
result = 0
|
|||
|
|
for i in range(count):
|
|||
|
|
result |= self.read_bit() << i
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
|
|||
|
|
def initialize_window():
|
|||
|
|
"""Инициализация окна для Huffman режима"""
|
|||
|
|
window = bytearray(4096)
|
|||
|
|
# Заполняем начальным значением
|
|||
|
|
for i in range(4078):
|
|||
|
|
window[i] = 0x20 # Пробел
|
|||
|
|
return window
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Ключевые особенности
|
|||
|
|
|
|||
|
|
### 1. Циклический буфер
|
|||
|
|
|
|||
|
|
- Размер: 4096 байт (12 бит адресации)
|
|||
|
|
- Маска: `0xFFF` для циклического доступа
|
|||
|
|
- Начальная позиция зависит от режима
|
|||
|
|
|
|||
|
|
### 2. Dual-режимы
|
|||
|
|
|
|||
|
|
- **Простой**: Быстрее, меньше сжатие, для данных с низкой энтропией
|
|||
|
|
- **Huffman**: Медленнее, лучше сжатие, для данных с высокой энтропией
|
|||
|
|
|
|||
|
|
### 3. LZ77 кодирование
|
|||
|
|
|
|||
|
|
- Offset: 12 бит (0-4095)
|
|||
|
|
- Length: 4 бита + 3 (3-18 байт)
|
|||
|
|
- Максимальное копирование: 18 байт
|
|||
|
|
|
|||
|
|
### 4. Битовые флаги
|
|||
|
|
|
|||
|
|
Используется двойная система флагов для определения типа следующих данных
|
|||
|
|
|
|||
|
|
## Проблемы реализации
|
|||
|
|
|
|||
|
|
### 1. Битовый порядок
|
|||
|
|
|
|||
|
|
Биты читаются справа налево (LSB first), что может вызвать путаницу
|
|||
|
|
|
|||
|
|
### 2. Huffman дерево
|
|||
|
|
|
|||
|
|
Адаптивное дерево требует точного отслеживания частот и правильной перестройки
|
|||
|
|
|
|||
|
|
### 3. Граничные условия
|
|||
|
|
|
|||
|
|
Необходимо тщательно проверять границы буферов
|
|||
|
|
|
|||
|
|
## Примеры данных
|
|||
|
|
|
|||
|
|
### Пример 1: Литералы (простой режим)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Входные биты: 00 00 00 ...
|
|||
|
|
Выход: Последовательность литералов
|
|||
|
|
|
|||
|
|
Пример:
|
|||
|
|
Flags: 0x00 (00000000)
|
|||
|
|
Data: 0x41 ('A'), 0x42 ('B'), 0x43 ('C'), ...
|
|||
|
|
Выход: "ABC..."
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Пример 2: LZ77 копирование
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Входные биты: 10 ...
|
|||
|
|
Выход: Копирование из окна
|
|||
|
|
|
|||
|
|
Пример:
|
|||
|
|
Flags: 0x01 (00000001) - первый бит = 1
|
|||
|
|
Word: 0x1234
|
|||
|
|
|
|||
|
|
Разбор:
|
|||
|
|
offset = (0x34 << 4) | (0x12 >> 4) = 0x341
|
|||
|
|
length = (0x12 & 0xF) + 3 = 5
|
|||
|
|
|
|||
|
|
Действие: Скопировать 5 байт с позиции offset
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Отладка
|
|||
|
|
|
|||
|
|
Для отладки рекомендуется:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
def debug_fres_decompress(input_data, output_size):
|
|||
|
|
"""Версия с отладочным выводом"""
|
|||
|
|
print(f"Input size: {len(input_data)}")
|
|||
|
|
print(f"Output size: {output_size}")
|
|||
|
|
|
|||
|
|
# ... реализация с print на каждом шаге
|
|||
|
|
|
|||
|
|
print(f"Flag: {flag_high}{flag_low}")
|
|||
|
|
if is_literal:
|
|||
|
|
print(f" Literal: 0x{byte:02X}")
|
|||
|
|
else:
|
|||
|
|
print(f" LZ77: offset={offset}, length={length}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Заключение
|
|||
|
|
|
|||
|
|
FRES — это эффективный гибридный алгоритм, сочетающий:
|
|||
|
|
|
|||
|
|
- RLE для повторяющихся данных
|
|||
|
|
- LZ77 для ссылок на предыдущие данные
|
|||
|
|
- Опциональный Huffman для символов
|
|||
|
|
|
|||
|
|
**Сложность декомпрессии:** O(n) где n — размер выходных данных
|
|||
|
|
|
|||
|
|
**Размер окна:** 4 КБ — хороший баланс между памятью и степенью сжатия
|