Metroid Prime 3: Corruption/PAK
PAK представляет собой контейнер (архив) для хранения файлов с возможностью использования сжатия. В качестве алгоритма сжатия используется LZO.
Каждый блок данных в файле выровнен по размеру «слова» 64 байта.
Содержание
Структура файла
Размер (без учёта выравнивания) | Данные |
---|---|
24 байта | Заголовок |
4 байта + количество секций × размер записи таблицы секций | Таблица секций |
Суммарный размер секций | Секции |
Секции
В архиве имеется несколько типов секций:
Имя | Назначение | Описание |
---|---|---|
STRG | Секция имён | В ней описаны имена, которые задаются некоторым файлам. Может быть пустой, но всегда присутствует. |
RSHD | Секция файлов | В ней хранится вся информация о файлах в архиве. |
DATA | Секция данных | В ней хранятся непосредственно данные файлов. |
Перед началом таблицы секции указано количество записей в ней. В игре всегда используется три секции.
Запись таблицы секций выглядит так:
Смещение | Размер | Описание |
---|---|---|
0x00 | 4 байта | Имя секции (четыре символа) |
0x04 | 4 байта | Размер секции |
Секции следуют друг за другом, их размеры округляются до 64-х байт из-за выравнивания. Смещение секции можно вычислить по сумме размеров заголовка, таблицы секций и предыдущих секций с учётом выравнивания.
Секция STRG
В начале секции находится 4 байта, в которых хранится количество заданных имён. Далее следует соответствующее количество записей вида:
Размер | Описание |
---|---|
До нуль-терминатора (включительно) | Имя файла |
4 байта | Тип файла, 4 символа [1] |
8 байтов | Хеш имени файла |
- ↑ Удобно использовать в качестве префикса или расширения имени файла.
Имена подавляющего большинства файлов в архивах не указаны, вместо этого для обращения к ним используется хеш имени. Хеш-функция не известна.
Секция RSHD
В начале секции находится 4 байта, в которых хранится количество файлов в контейнере. Далее следует соответствующее количество записей вида:
Смещение | Размер | Описание |
---|---|---|
0x00 | 4 байта | Флаг компрессии [1] |
0x04 | 4 байта | Тип файла, аналогичен типам файлов в секции STRG |
0x08 | 8 байов | Хеш имени файла [2] |
0x10 | 4 байта | Размер файла [3] |
0x14 | 4 байта | Смещение файла в секции данных |
- ↑ Если true (не равен нулю) - то сжатие используется, в противном случае - нет.
- ↑ Можно рассматривать ещё и как ключ для связи с записями в секции имён.
- ↑ Указан размер данных в том виде, в котором они хранятся в контейнере. Если файл сжат, то размер исходных данных указан в заголовке сжатых данных.
Секция DATA
В секции DATA хранятся непосредственно данные файлов в запакованном или исходном виде, в зависимости от того, указана ли компрессия в записи файла. Как и все данные, данные файлов выравниваются по размеру 64 байта.
Сжатые файлы
Заголовок сжатого файла имеет следующий вид:
Смещение | Размер | Описание |
---|---|---|
0x00 | 4 байта | Сигнатура "CMPD" (вероятно, от слова "compressed") |
0x04 | 4 байта | Тип архива |
Заголовок потока сжатых данных выглядит таким образом:
Смещение | Размер | Описание |
---|---|---|
0x00 | 1 байт | Флаги |
0x01 | 3 байта | Размер потока сжатых данных |
0x04 | 4 байта | Размер файла |
Сами сжатые файлы встречаются двух типов, при чём второй в игре используется только для текстур. Если в качестве типа архива указан 1, то используется первый, если 2 - то второй.
Тип 1
Структура сжатого файла первого типа выглядит так:
Размер | Описание |
---|---|
8 байтов | Заголовок |
8 байтов | Заголовок потока сжатых данных |
Указан в заголовке | Поток сжатых данных |
Тип 2
Второй тип сжатых файлов позволяет хранить некоторое количество данных из начала файла в несжатом виде. Таким образом можно, например, считывать заголовки файлов не прибегая к распаковке.
Структура сжатого файла второго типа выглядит так:
Размер | Описание |
---|---|
8 байтов | Заголовок |
4 байта | Неизвестно (всегда 12) [1] |
4 байта | Неизвестно (всегда 12) [2] |
Встречался только 12 байтов [3] | Несжатые данные из начала файла |
8 байтов | Заголовок потока сжатых данных |
Указан в заголовке | Поток сжатых данных |
- ↑ Исходя из того, что первые 12 байтов не запакованы, то можно предположить, что это как раз и есть размер этих данных.
- ↑ Скорей всего, это смещение в файле, с которого начинается сжатие данных. Как ещё один вариант - это отступ до заголовка потока запакованных данных.
- ↑ Если предыдущие предположения верны - то он указан в одном из предыдущих неизвестных значений.
Флаги
Бит | Маска | Назначение | Описание |
---|---|---|---|
0 | 0x80 | Флаг сжатия | Если не установлен, то при распаковке поток сжатых данных напрямую копируется в выходной поток. |
1 | 0x40 | Неизвестно | - |
2 | 0x20 | Неизвестно | - |
3 | 0x10 | Неизвестно | - |
4 | 0x08 | Неизвестно | - |
5 | 0x04 | Неизвестно | - |
6 | 0x02 | Неизвестно | - |
7 | 0x01 | Неизвестно | - |
Поток сжатых данных
Поток сжатых данных состоит из множества блоков вида:
Размер | Описание |
---|---|
2 байта | Размер порции |
Указан в начале | Порция сжатых LZO данных |
Структуру можно изобразить так:
Блок данных 1 |
Блок данных 2 |
... |
Блок данных N |
Блоки данных кончаются тогда, когда суммарный размер содержащихся в них сжатых данных дойдёт до размера, указанного в заголовке потока сжатых данных. Как вариант - когда размер распакованных данных дойдёт до указанного в заголовке размера, но первый вариант предпочтительней и безопасней.
Пример кода распаковки потока сжатых данных на C++:
while(stream_size > 0)
{
in->read(&chunk_size, 2);
stream_size -= 2;
in->read(buf, chunk_size);
stream_size -= chunk_size;
lzo_decompress(in_buf, chunk_size, out_buf, &size);
out->write(out_buf, size);
}