Приложение 5: Декомпрессия сжатых блоков DBPF2
Статьи по теме
- библиотека компонентов для работы с файлами TS3 – DBPF2: описание формата, на русском языке;
- описание формата – Sims3: DBPF, на английском языке;
- описание сжатия данных – Sims3: DBPF/Compression, на английском языке;
- описание сжатия данных и алгоритм декомпрессии – Sims2: DBPF/Compression, на английском языке;
Замечание В оригинальном руководстве есть несколько моментов, например, порядок записей в заголовке, которые не совсем (а на самом деле вообще никак) ни согласуются с реальными данными.
Краткий обзор
Идея, положенная в сжатие данных – повторное использование предварительно декодированных данных. Так, например, если слово "heureka" встречается дважды в файле, то использование вместо второго вхождения ссылки на первое позволяет сократить занимаемое место.
Сжатие выполняется с использованием определенных управляющих символов, которые задают три вещи:
- сколько байт должны быть непосредственно перенесены к распакованным данным;
- сколько байт должны быть считаны из уже распакованных данных и дописаны к ним в конец;
- откуда читаются уже распакованные данные при их копировании;
Описание алгоритма
Алгоритм декомпрессии данных состоит в следующем:
1. Читаем заголовок записи (1), он имеет следующий формат:
Или в виде типа данных (0):ComprType: BYTE – тип сжатого блока, определяет используемый алгоритм сжатия, при ComprType = $10, $40 параметр PackedSizeBuf имеет размер 3 байта, при $80 – 4 байта, что позволяет хранить сжитые данные объемом до 16 Мб;
Id: BYTE – идентификатор сжатого блока, должен быть $FB;
PackedSize: DWORD – размер сжатых данных, из-за того, что байты размера данных в заголовке идут в "прямом" порядке (т.е. младший байт по младшему адресу), а в PC наоборот и размер может меняться, то удобнее читать его в виде массива байт PackedSizeBuf: packed array [0..3] of byte (2);
2. После заголовка записи (со смещением 5 или 6 байт, в зависимости от типа сжатого блока) лежат непосредственно данные, соответственно их размер (len) на 5 или 6 байт меньше полного размера блока. Далее в цикле, пока не все данные распакованы (3):
- читаем первый байт управляющего символа (4);
- в зависимости от того, какой это управляющий символ, читаем дополнительно его 0..3 байта (5);
- определяем какие данные и откуда должны копироваться (6);
- копируем 0..n байт данных, которые должны быть непосредственно перенесены к распакованным данным (7);
- копируем 0..n байт данных, которые должны быть считаны из уже распакованных данных и дописаны к ним в конец (8);
Управляющие символы
Существует четыре типа управляющих символов. Они используются с различными ограничениями на то, сколько байт данных читается и как далеко от конца потока они могут читаться.
Следующие термины будут применяться в их описании:
- первичный поток – поток содержащий файл, с которым ведется работа в данный момент, вторичный поток – поток, содержащий декодированную запись; процедура декодирования собственно и состоит в выборке данных из первичного потока по определенным правилам (управляющим символам) и заполнении ими (добавлении в конец) вторичного потока.
- numplain (Num plain text) – число байт, которые непосредственно копируются из конца первичного потока во вторичный;
- numcopy (Num to copy) – число уже декодированных байт, которые копируются с определенной позиции вторичного потока в его конец;
- offset (Copy offset) – смещение во вторичном потоке откуда копируются данные с конца потока, смещение 0 соответствует последнему декодированному байту, 1 – байте перед ним;
- cc[N] – N-ный байт заголовка блока с управляющими символами (нумерация начинается с 0), соответственно cc[0] – первый байт; - биты управляющего символа:
* p – numplain;
* c – numcopy
* o – offset;
* i – идентификатор символов, в отдельности не существует, т.к. представляет собой диапазон значений первого байта заголовка блока;
Замечание Иногда может случиться, что управляющий символ потребует, например, что необходимо скопировать 10 символов 5 с конца потока. Ясно, что невозможно прочитать более чем 5 символов до того как будет достигнут конца буфера. Решение – читать и записывать по одному символу. Каждый раз, когда вы читаете символ, вы копируете это в конец таким образом увеличивая размер потока. Таким образом, даже offset = 0 корректен и приводит бы к дублированию последнего символа несколько раз – это используется, например, при сжатии строк имитирующих линии и состоящих из повторяющихся символов тире.
CC[0] - $00 .. $7F – копирует 0..3 байта данных из первичного потока во вторичный, затем копирует 3..10 байт из вторичного потока во вторичный
CC[0] - $80 .. $BF – копирует 0..3 байта данных из первичного потока во вторичный, затем копирует 4..67 байт из вторичного потока во вторичный
CC[0] - $C0 .. $DF – копирует 0..3 байта данных из первичного потока во вторичный, затем копирует 5..1028 байт из вторичного потока во вторичный
CC[0] - $E0 .. $FB – копирует 4..112 байт данных из первичного потока во вторичный
CC[0] - $FC .. $FF – копирует 0..3 байта данных из первичного потока во вторичный, блок сжатых данных должен заканчиваться этим управляющим символом, при отсутствии данных последним символом должен быть $FC. Отсутствие завершающей последовательность привод к краху игры при попытки загрузки неверного блока.
Реализация на Pascal
Реализация декомпрессии записей формата DBPF2 содержится в файле siS3DBPF2Decoder.pas
Метод Decode вызывается при получении данных записи в виде потока: