Структура файлов:
- Заголовок файла;
- Таблица размещения блоков:
- Заголовок;
- Записи;
- Таблица размещения файлов:
- Заголовок;
- Записи;
- Манифест:
- Заголовок;
- Записи (Узлы);
- Таблица имен;
- Хэш-таблица;
- Минимально-необходимые файлы;
- Файлы пользовательской конфигурации;
- Карта манифеста:
- Заголовок;
- Записи;
- Контрольные суммы:
- Заголовок;
- Заголовок таблицы;
- Таблица записей;
- Записи контрольных сумм;
- Сигнатура;
- Последняя версия приложения;
- Секция данных;
- Формат NCF-файлов.
1. Заголовок файла Поля:Структура- HeaderVersion – всегда 0x00000001. Вероятно, это номер версии для этой структуры;
- CacheType – всегда 0x00000001 (для NCF равен 0x00000002);
- FormatVersion – версия формата файла. Последняя версия – 6;
- CacheID – идентификатор файла. Список всех идентификаторов содержится в файле ClientRegistry.blob внутри записи «Content Description Record»;
- CacheVersion – номер версии файла;
- IsMounted – имеет значение 0x00000001, если файл в настоящее время смонтирован, иначе будет иметь значение 0x00000000 (непроверенно);
- Dummy0 – всегда 0x00000000;
- FileSize – полный размер файла;
- ClusterSize – размер одного кластера в байтах;
- ClusterCount – общее количество кластеров в файле. (Не количество файлов!);
- Checksum – используется для проверки заголовка. Рассчитывается путем сложения всех байт заголовка (за исключением самой контрольной суммы).
В данной таблице можно пометить файлы, которые еще не были распакованы или расшифрованы. Одному файлу может принадлежать несколько блоков, особенно если он не был до конца распакован или расшифрован.
- BlockCount – число блоков в файле. Может быть равно FileHeader.ClusterCount;
- BlocksUsed – число используемых блоков;
- LastUsedBlock – индекс последнего используемого блока;
- Checksum – проверка заголовка. Равняется сумме всех остальных полей.
Данный массив имеет размер, указанный в BlockAllocationTableHeader.BlockCount.
- Flags – тип блока. Возможные значения (в виде флагов):
- 0x8000 – используется;
- 0x4000 – локальная копия имеет приоритет над сохраненной копией (непроверенно);
- 0x0004 – зашифровано (непроверенно);
- 0x0002 – зашифровано и сжато (непроверенно);
- 0x0001 – RAW.
- Dummy0 – неизвестно;
- FileDataOffset – смещение в извлекаемом файле, по которому располагаются данные блока;
- FileDataSize – длина данных для данного блока;
- FirstClusterIndex – индекс первого кластера, содержащего данные блока;
- NextBlockIndex – индекс следующего блока. Если значение равно
- BlockAllocationTableHeader.BlockCount, тогда следующего блока нет;
- PreviousBlockIndex – индекс предыдущего блока. Если значение равно BlockAllocationTableHeader.BlockCount, тогда следующего блока нет;
- ManifestIndex – индекс файла, которому принадлежит этот блок. Равен 0xFFFFFFFF, если не принадлежит.
Представляет собой простой способ расположения частей файлов, даже если они не могут быть сохранены вместе в кэше.
- ClusterCount – количество кластеров в файле. Должно совпадать с
- FileHeader.ClusterCount;
- FirstUnusedEntry – индекс первой неиспользованной записи;
- IsLongTerminator – определяет границу блока. Если равен 0, то это 0x0000FFFF, если 1 - 0xFFFFFFFF;
- Checksum - проверка заголовка. Равняется сумме всех остальных полей.
Представляет собой массив длиной FileAllocationTableHeader.ClusterCount элементов. В массиве хранятся индексы следующего кластера для файла (типа uint32_t). Если значение равно граничному (см. FileAllocationTableHeader.IsLongTerminator), значит в файле больше нет секторов.
Здесь находятся все мета-данные для работы с файлами. Ранее использовалось название «каталог», однако сейчас используется «манифест».
- HeaderVersion – всегда 0x00000004. Это текущая версия манифеста;
- CacheID – идентификатор кэша (должен совпадать с FileHeader.ApplicationID);
- CacheVersion – версия файла (должна совпадать с FileHeader.ApplicationVersion);
- NodeCount – количество элементов манифеста;
- FileCount – количество файлов в кэше;
- CompressionBlockSize – определяет, сколько байт используется в контрольной сумме / сжатом файле;
- BinarySize – размер манифеста в байтах (включая данную структуру);
- NameSize – размер таблицы имен в байтах;
- HashTableKeyCount – количество ключей в хэш-таблице;
- NumOfMinimumFootprintFiles – количество минимально-необходимых файлов;
- NumOfUserConfigFiles – количество файлов пользовательской конфигурации;
- Bitmask – битовая маска флагов. Известные маски:
- 0x00000001 - Build Mode (назначение неизвестно);
- 0x00000002 – чистая цепочка (назначение неизвестно);
- 0x00000004 – длинная цепочка (назначение неизвестно; может быть, это связано с языковыми файлами);
- Fingerprint – скорее всего генерируется каждый раз при обновлении. Может использоваться для быстрого определения необходимости обновить файл;
- Checksum – контрольная сумма, вычисленная по алгоритму adler32 для всех полей записи за исключением двух (Fingerprint и Checksum) – они обнуляются при пересчете.
Представляет собой массив длиной ManifestHeader.NodeCount элементов.
- NameOffset – смещение в таблице имен, начиная с которого находится имя узла. Имя представляет собой строку C (null-терминантная строка, оканчивается символом ‘\0’);
- CountOrSize – размер узла. Для файла равен размеру файла в байтах, для каталога – количеству дочерних узлов;
- FileId – ID файла. Если узел является папкой, то имеет значение 0xFFFFFFFF;
Attributes – различные флаги узла. Известные значения:- 0x00004000 – узел является файлом;
- 0x00000800 – узел является исполняемым файлом (непроверенно);
- 0x00000400 – узел является скрытым файлом (непроверенно);
- 0x00000200 – узел является файлом только для чтения (непроверенно);
- 0x00000100 – файл зашифрован;
- 0x00000080 - The node is a purge file (непроверенно);
- 0x00000040 – файл необходимо скопировать перед перезаписью (непроверенно);
- 0x00000020 – узел является no-cache file (непроверенно);
- 0x00000008 – узел является заблокированным файлом;
- 0x00000002 – узел является запускаемым файлом;
- 0x00000001 – узел является файлом пользовательской конфигурации. Данный файл не переписывается при его существовании;
- ParentIndex – индекс родительского узла. Если узел находится в корне, то имеет значение 0xFFFFFFFF. Всегда должен являться ссылкой на каталог;
- NextIndex – индекс следующего узла на данном уровне дерева. Если далее узлы отсутствуют, то имеет значение 0x00000000. Может ссылаться только на те узла, у которых ParentIndex одинаков с ParentIndex текущего элемента;
- ChildIndex – индекс первого дочернего элемента. Если дочерних узлов нет, то имеет значение 0x00000000.
Простой массив символов размером ManifestHeader.NameSize байт.
Данная хэш-таблица отличается от остальных тем, что состоит из 2-х таблиц – HashTableKeys и HashTableIndices.
HashTableKeys содержит ManifestHeader. HashTableKeyCount записей. В ней находятся значения хэш-функции для имен элементов (только имен, без пути; имя должно быть только в нижнем регистре).
HashTableIndices содержит ManifestHeader. NodeCount записей. В ней находятся индексы элементов, на которые ссылается предыдущая таблица.
Данная таблица служит для быстрого получения необходимых элементов по их имени. Для этого необходимо сделать следующее:
- Привести искомое имя к нижнему регистру и вычислить значение хэш-функции по алгоритму jenkinsLookupHash2;
- Взять значение из таблицы HashTableKeys из ячейки с номером, равным остатку от целочисленного деления полученного ранее значения хэш-функции на значение ManifestHeader. HashTableKeysCount и уменьшаем его на ManifestHeader. HashTableKeysCount. Полученное число будет ссылаться на ячейку в таблице HashTableIndices;
- Берем значение из таблицы HashTableIndices и объединяем с маской 0x7FFFFFFF. Результатом будет индекс узла, в котором вероятно и находится искомый элемент;
- Если требуемый узел не найден, то переходим к следующей ячейке таблицы HashTableIndices. Если значение текущей ячейки этой таблицы совпадает с маской 0x80000000, то мы достигли конца участка для данного значения хэш-функции.
Данный массив имеет размер ManifestHeader.NumOfMinimumFootprintFiles. Все элементы, имеющиеся в данном массиве, должны быть извлечены на диск. Если эти записи были обновлены или изменены (или на диске или в файле кэша), то их необходимо переписать заново. В массиве хранятся индексы в таблице узлов (типа uint32_t).
Данный массив имеет размер ManifestHeader.NumOfUserConfigFiles. Все элементы, имеющиеся в данном массиве, должны быть извлечены на диск, если они не были извлечены ранее. Версия на диске должна иметь больший приоритет перед файлом в кэше, независимо от любых изменений. В массиве хранятся индексы в таблице узлов (типа uint32_t).
В данной таблице содержатся связанные цепочки индексов кластеров.
- HeaderVersion – всегда 0x00000001. Версия заголовка;
- Dummy0 – всегда 0x00000000.
Данный массив имеет размер ManifestHeader.NodeCount. Значения массива (типа uint32_t) являются индексами первого блока (в таблице размещения блоков) для файла. Если значение равно BlockAllocationTableHeader.BlockCount, то элемент не сохранен в кэше или является каталогом.
Данная запись в значительной мере избыточна, так как содержит много неиспользованных и дублированных сумм. Тем не менее, они используются, но только для других версий кэша (вплоть до LatestApplicationVersion).
- HeaderVersion – всегда 0x00000001. Версия заголовка;
- ChecksumSize – количество байт в данной структуре (от ChecksumDataContainer и до LatestApplicationVersion).
- FormatCode – всегда 0x14893721;
- Dummy0 – всегда 0x00000001;
- FileIdCount – количество записей;
- ChecksumCount – общее количество контрольных сумм.
Данный массив имеет размер FileIdChecksumTableHeader.FileIdCount. Он содержит идентификаторы файлов для всех версий данного кэш-файла. Таким образом для получения контрольной суммы для файла необходимо взять значение из массива, используя идентификатор файла как индекс.
- ChecksumCount – определяет, сколько контрольных сумм принадлежит данной секции;
- FirstChecksumIndex – индекс первого значения ChecksumEntry для данной секции.
Данный массив имеет размер FileIdChecksumTableHeader.ChecksumCount и содержит контрольные суммы для всех сегментов. В каждом блоке может быть несколько записей, следующих друг за другом.
Значение контрольной суммы рассчитывается для частей файлов размером ManifestHeader.CompressionBlockSize. Если размер сегмента файла меньше размера блока, то контрольная сумма вычисляется только для оставшегося размера файла.
Представляет собой 0x80 байт RSA-сигнатуры (используя SHA-1 и RSASSA-PKCS1-v1_5) от секции контрольных сумм. RSA в качестве публичного ключа использует объявленный в Content Description Record, соответствующий ID файла кэша.
Данная структура не существует, если ChecksumDataContainer.HeaderVersion имеет значение 0. Представляет собой значение типа uint32_t, хранящее последнюю версию контрольных сумм файла. Должно быть равно FileHeader. CacheID. Если это значение меньше чем FileHeader. CacheID, то данные контрольные суммы являются устаревшими и не должны использоваться.
- ClusterCount – количество кластеров в файле. Должно совпадать с
- FileHeader.ClusterCount;
- ClusterSize – размер одного кластера в байтах. Должен совпадать с
- FileHeader.ClusterSize;
- FirstClusterOffset – смещение (относительно начала файла) для первого кластера;
- ClustersUsed – количество использованных кластеров;
- Checksum – для проверки заголовка. Рассчитывается путем сложения всех предыдущих полей.
- Структура данных файлов схожа с GCF, за исключением следующих пунктов:
- FileHeader. CacheType равен 0x00000002;
- Отсутствуют следующие поля:
- таблица размещения блоков;
- таблица размещения файлов;
- секция данных;
- Файлы располагаются не в файле кэша, а на диске в папке “common\папка_игры”.
Для получения точного размера файла кэша существует два пути:
- из FileHeader.FileSize. Если полученный и записанный размеры не совпадают, то сверить с 0x100000000+FileHeader.FileSize - именно так вычисляется размер для файлов кэша >4Гб);
- вычислив значение DataHeader.FirstClusterOffset + DataHeader.ClusterCount*DataHeader.ClusterSize.
По сравнению с исходным документом есть некоторые отличия - добавлены описания заголовков/функций для языка Delphi (object Pascal), некоторые места я сократил, оставив из описания только необходимое (без ущерба информативности ).
PS: Переводил целый день и еще две недели (без нескольких дней) ждал открытия форума для публикации.
PSS: Абсолютно все положения (кроме сигнатуры контрольных сумм) проверенны мною на практических примерах (проверка производилась в программах CFToolBox и GCFScape).
PSSS: Все, написанное в данном документе, переводилось для использования в Universal Steam Extractor (rev.3), где уже вовсю идут работы ).