Формат файлов GCF/NCF

Для тех, кто хочет сделать мир лучше.
Сообщение
Автор
Аватара пользователя
andreil
Разработчик
Разработчик
Сообщения: 781
Зарегистрирован: 14.08.2006
Откуда: Светлогорск, Беларусь
Поблагодарили: 2 раза
Контактная информация:

#1 Сообщение 20.06.2010, 17:44

Формат файлов GCF (Game Cache File)

В данном документе описана только 6-ая версия формата.

В качестве исходного документа была использована статья с форума http://cs.rin.ru/forum

Структура файлов:
  1. Заголовок файла;
  2. Таблица размещения блоков:
    1. Заголовок;
    2. Записи;
  3. Таблица размещения файлов:
    1. Заголовок;
    2. Записи;
  4. Манифест:
    1. Заголовок;
    2. Записи (Узлы);
    3. Таблица имен;
    4. Хэш-таблица;
    5. Минимально-необходимые файлы;
    6. Файлы пользовательской конфигурации;
  5. Карта манифеста:
    1. Заголовок;
    2. Записи;
  6. Контрольные суммы:
    1. Заголовок;
    2. Заголовок таблицы;
    3. Таблица записей;
    4. Записи контрольных сумм;
    5. Сигнатура;
    6. Последняя версия приложения;
  7. Секция данных;
  8. Формат NCF-файлов.
    1. Заголовок файла
    Структура

    Код: Выделить всё

    struct FileHeader
    {
        uint32_t HeaderVersion;
        uint32_t CacheType;
        uint32_t FormatVersion;
        uint32_t CacheID;
        uint32_t CacheVersion; 
        uint32_t IsMounted;
        uint32_t Dummy0;
        uint32_t FileSize;
        uint32_t ClusterSize;
        uint32_t ClusterCount;
        uint32_t Checksum;
    }

    Код: Выделить всё

      TGCFHeader = record
          HeaderVersion,
          CacheType, 
          FormatVersion,
          CacheID,
          CacheVersion,
          IsMounted, 
          Dummy0,
          FileSize
          ClusterSize
          ClusterCount,        
          Checksum:  ulong;
        end;
    Поля:
    • HeaderVersion – всегда 0x00000001. Вероятно, это номер версии для этой структуры;
    • CacheType – всегда 0x00000001 (для NCF равен 0x00000002);
    • FormatVersion – версия формата файла. Последняя версия – 6;
    • CacheID – идентификатор файла. Список всех идентификаторов содержится в файле ClientRegistry.blob внутри записи «Content Description Record»;
    • CacheVersion – номер версии файла;
    • IsMounted – имеет значение 0x00000001, если файл в настоящее время смонтирован, иначе будет иметь значение 0x00000000 (непроверенно);
    • Dummy0 – всегда 0x00000000;
    • FileSize – полный размер файла;
    • ClusterSize – размер одного кластера в байтах;
    • ClusterCount – общее количество кластеров в файле. (Не количество файлов!);
    • Checksum – используется для проверки заголовка. Рассчитывается путем сложения всех байт заголовка (за исключением самой контрольной суммы).
Код для расчета контрольной суммы

Код: Выделить всё

// This is not a safe implementation, it is merely a basic example of
// how the algorithm works.
uint32_t FileHeaderChecksum(const uint8_t *headerData)
{
    uint32_t checksum = 0;
    for (size_t i = 0; i < (sizeof(FileHeader) - sizeof(uint32_t)); i++)
        checksum += headerData[i];
    return checksum;
}

Код: Выделить всё

function HeaderChecksum(HeaderData: pByte; Length: integer): ulong;
var
  checksum: ulong;
  i: integer;
begin
  checksum:=0;
  for i:=0 to Length-1 do
  begin
    inc(checksum, headerData^);
    inc(Headerdata);
  end;
  result:=checksum;
end;

2. Таблица размещения блоков

В данной таблице можно пометить файлы, которые еще не были распакованы или расшифрованы. Одному файлу может принадлежать несколько блоков, особенно если он не был до конца распакован или расшифрован.
2.a Заголовок
Структура

Код: Выделить всё

struct BlockAllocationTableHeader
{
    uint32_t BlockCount;
    uint32_t BlocksUsed;
    uint32_t LastUsedBlock;
    uint32_t Dummy0;
    uint32_t Dummy1;
    uint32_t Dummy2;
    uint32_t Dummy3;
    uint32_t Checksum;
}

Код: Выделить всё

  TGCFBlockAllocationTableHeader = record
      BlockCount,
      BlocksUsed,
      LastUsedBlock,
      Dummy0,
      Dummy1,
      Dummy2,
      Dummy3,
      Checksum: uint;
    end;
Поля:
  • BlockCount – число блоков в файле. Может быть равно FileHeader.ClusterCount;
  • BlocksUsed – число используемых блоков;
  • LastUsedBlock – индекс последнего используемого блока;
  • Checksum – проверка заголовка. Равняется сумме всех остальных полей.
2.b Записи

Данный массив имеет размер, указанный в BlockAllocationTableHeader.BlockCount.
Структура

Код: Выделить всё

struct BlockAllocationTableEntry
{
    uint16_t Flags;
    uint16_t Dummy0;
    uint32_t FileDataOffset;
    uint32_t FileDataSize;
    uint32_t FirstClusterIndex;
    uint32_t NextBlockIndex;
    uint32_t PreviousBlockIndex;
    uint32_t ManifestIndex;
}

Код: Выделить всё

  TGCFBlockEntry = record
      Flags: Word;
      Dummy0: word;
      FileDataOffset,
      FileDataSize,
      FirstClusterIndex,
      NextBlockEntryIndex,
      PreviousBlockEntryIndex,
      ManifestIndex: uint;
    end;
Поля:
  • Flags – тип блока. Возможные значения (в виде флагов):
    • 0x8000 – используется;
    • 0x4000 – локальная копия имеет приоритет над сохраненной копией (непроверенно);
    • 0x0004 – зашифровано (непроверенно);
    • 0x0002 – зашифровано и сжато (непроверенно);
    • 0x0001 – RAW.
  • Dummy0 – неизвестно;
  • FileDataOffset – смещение в извлекаемом файле, по которому располагаются данные блока;
  • FileDataSize – длина данных для данного блока;
  • FirstClusterIndex – индекс первого кластера, содержащего данные блока;
  • NextBlockIndex – индекс следующего блока. Если значение равно
  • BlockAllocationTableHeader.BlockCount, тогда следующего блока нет;
  • PreviousBlockIndex – индекс предыдущего блока. Если значение равно BlockAllocationTableHeader.BlockCount, тогда следующего блока нет;
  • ManifestIndex – индекс файла, которому принадлежит этот блок. Равен 0xFFFFFFFF, если не принадлежит.

3. Таблица размещения файлов

Представляет собой простой способ расположения частей файлов, даже если они не могут быть сохранены вместе в кэше.
3.a Заголовок
Структура

Код: Выделить всё

struct FileAllocationTableHeader
{
    uint32_t ClusterCount;
    uint32_t FirstUnusedEntry;
    uint32_t IsLongTerminator;
    uint32_t Checksum;
}

Код: Выделить всё

  TGCFFileAllocationTableHeader = record
      ClusterCount,
      FirstUnusedEntry,
      IsLongTerminator,
      Checksum: ulong;
    end;
Поля:
  • ClusterCount – количество кластеров в файле. Должно совпадать с
  • FileHeader.ClusterCount;
  • FirstUnusedEntry – индекс первой неиспользованной записи;
  • IsLongTerminator – определяет границу блока. Если равен 0, то это 0x0000FFFF, если 1 - 0xFFFFFFFF;
  • Checksum - проверка заголовка. Равняется сумме всех остальных полей.
3.b Записи

Представляет собой массив длиной FileAllocationTableHeader.ClusterCount элементов. В массиве хранятся индексы следующего кластера для файла (типа uint32_t). Если значение равно граничному (см. FileAllocationTableHeader.IsLongTerminator), значит в файле больше нет секторов.
4. Манифест

Здесь находятся все мета-данные для работы с файлами. Ранее использовалось название «каталог», однако сейчас используется «манифест».
4.a Заголовок
Структура

Код: Выделить всё

struct ManifestHeader
{
    uint32_t HeaderVersion;
    uint32_t CacheID;
    uint32_t CacheVersion;
    uint32_t NodeCount;
    uint32_t FileCount;
    uint32_t CompressionBlockSize;
    uint32_t BinarySize;
    uint32_t NameSize;
    uint32_t HashTableKeyCount;
    uint32_t NumOfMinimumFootprintFiles;
    uint32_t NumOfUserConfigFiles;
    uint32_t Bitmask;
    uint32_t Fingerprint;
    uint32_t Checksum;
}

Код: Выделить всё

  TGCFManifestHeader = record
      HeaderVersion, 
      CacheID,
      CacheVersion, 
      NodeCount, 
      FileCount,
      CompressionBlockSize,
      BinarySize,
      NameSize,
      HashTableKeyCount,
      NumOfMinimumFootprintFiles,
      NumOfUserConfigFiles,
      Bitmask,
      Fingerprint,
      Checksum: ulong;
    end;
Поля:
  • 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) – они обнуляются при пересчете.
Пример вычисления контрольной суммы

Код: Выделить всё

#define OFFSET_OF(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER) 
uint32_t ManifestChecksum(const uint8_t *manifestData, uint32_t manifestSize)
{
    ManifestHeader *manifest = (ManifestHeader*)manifestData;
    ASSERT(manifest->BinarySize <= manifestSize);
 
    // Temporary "replacement" bytes for non-calculated fields.
    static const uint32_t nullFields[2] = {
        0, // Fingerprint
        0  // Checksum
    };
 
    // Calculate checksum in a 3-part process.
    uint32_t checksum = 0;
    checksum = adler32(checksum, manifest, OFFSET_OF(ManifestHeader, Fingerprint));
    checksum = adler32(checksum, nullFields, sizeof(nullFields));
    checksum = adler32(checksum, manifest + sizeof(CacheManifestHeader),
                       manifest->BinarySize - sizeof(CacheManifestHeader));
 
    // Return the checksum.
    return checksum;
}
4.b Записи (узлы)

Представляет собой массив длиной ManifestHeader.NodeCount элементов.
Структура

Код: Выделить всё

struct ManifestNode
{
    uint32_t NameOffset;
    uint32_t CountOrSize;
    uint32_t FileId;
    uint32_t Attributes;
    uint32_t ParentIndex;
    uint32_t NextIndex;
    uint32_t ChildIndex;
}

Код: Выделить всё

 TGCFManifestNode = record 
      NameOffset, 
      CountOrSize,
      FileId,
      Attributes,
      ParentIndex, 
      NextIndex,
      ChildIndex: ulong;
    end;
Поля:
  • 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.
Первым узел (с индексом 0) всегда должен быть корневой узел, не имеющий ни родителей, ни следующих/предыдущих элементов. Его имя должно быть пустой строкой (“\0” в таблице имен).
4.c Таблица имен

Простой массив символов размером ManifestHeader.NameSize байт.
4.d Хэш-таблица

Данная хэш-таблица отличается от остальных тем, что состоит из 2-х таблиц – HashTableKeys и HashTableIndices.
HashTableKeys содержит ManifestHeader. HashTableKeyCount записей. В ней находятся значения хэш-функции для имен элементов (только имен, без пути; имя должно быть только в нижнем регистре).
HashTableIndices содержит ManifestHeader. NodeCount записей. В ней находятся индексы элементов, на которые ссылается предыдущая таблица.
Данная таблица служит для быстрого получения необходимых элементов по их имени. Для этого необходимо сделать следующее:
  • Привести искомое имя к нижнему регистру и вычислить значение хэш-функции по алгоритму jenkinsLookupHash2;
  • Взять значение из таблицы HashTableKeys из ячейки с номером, равным остатку от целочисленного деления полученного ранее значения хэш-функции на значение ManifestHeader. HashTableKeysCount и уменьшаем его на ManifestHeader. HashTableKeysCount. Полученное число будет ссылаться на ячейку в таблице HashTableIndices;
  • Берем значение из таблицы HashTableIndices и объединяем с маской 0x7FFFFFFF. Результатом будет индекс узла, в котором вероятно и находится искомый элемент;
  • Если требуемый узел не найден, то переходим к следующей ячейке таблицы HashTableIndices. Если значение текущей ячейки этой таблицы совпадает с маской 0x80000000, то мы достигли конца участка для данного значения хэш-функции.
Пример поиска файлов с использованием хэш-таблицы

Код: Выделить всё

uint32_t getFileIDFast(Manifest *manifest, const char *path)
{
    const char *fileName = getFileName(path);
    char *fileNameLower = (char *)malloc(strlen(fileName) + 1);
    ASSERT(fileNameLower != NULL);
    strcpy(fileNameLower, fileName);
    strlwr(fileNameLower);
    // Compute the hash of the file name.
    uint32_t hash = jenkinsLookupHash2((uint8_t *)fileNameLower, strlen(fileNameLower), 1);
    // Compute HashTableKeys index.
    // This could also be implemented (theoretically faster) using:
    //       hashIndex = hash & (manifest->HashTableKeysCount - 1);
    uint32_t hashIndex = hash % manifest->HashTableKeysCount;
 
    // Get the HashTableIndexes index.
    uint32_t hashedFileIndex = manifest->HashTableKeys[hashIndex];
    if (hashedFileIndex == -1)
    {
        // Clean up.
        free(fileNameLower);
 
        // No files found with the given hash.
        return 0;
    }
    hashedFileIndex -= manifest->HashTableKeysCount;
    int stop;
    do
    {
        uint32_t hashedValue = manifest->HashTableIndices[hashedFileIndex];
        uint32_t fileId = hashedValue & 0x7FFFFFFF;
        stop = (hashedValue & 0x80000000) != 0; 
        filePath = getManifestFilePath(manifest, fileId);
        if (filePath == NULL)
            continue;
        // Compare the given file path to the one from the hash table
        // results. (This should be a case insensitive and path
        // separator (slash) insensitive comparison.)
        if (!pathEquals(filePath, path))
        {
            // Cleanup.
            free(filePath);
 
            // File found.
            return fileId;
        } 
        // Cleanup.
        free(filePath);
    } while (!stop && fileIDsCount < maxFileIDs); 
    // Clean up.
    free(fileNameLower); 
    // Return the number of entries found.
    return 0xFFFFFFFF;
}

Код: Выделить всё

function TGCF.GetItemByPath(Path: AnsiString): integer;
var
  end_block: boolean;
  Hash, HashIdx, HashValue: ulong;
  FileID, HashFileIdx: integer;
  PathEx: AnsiString;
begin
  result:=-1;
  PathEx:=ExtractFileName(LowerCase(Path));
  Hash:=jenkinsLookupHash2(pAnsiChar(PathEx), Length(PathEx), 1);
  HashIdx:=Hash mod fHeader.pDirectoryHeader.HashTableKeyCount;
  HashFileIdx:=fHeader.lpHashTableKeys[HashIdx];
  if HashFileIdx=-1 then
    Exit;
  dec(HashFileIdx, fHeader.pDirectoryHeader.HashTableKeyCount);
  repeat
    HashValue:=fHeader.lpHashTableIndices[HashFileIdx];
    FileID:=HashValue and $7FFFFFFF;
    end_block:= (HashValue and $80000000 = $80000000);
    if StrComp_NoCase(PAnsiChar(GetItemPath(FileID)), pAnsiChar(Path))=0 then
    begin
      result:=FileID;
      Exit;
    end;
    inc(HashFileIdx);
  until end_block;
end;
4.e Минимально-необходимые файлы

Данный массив имеет размер ManifestHeader.NumOfMinimumFootprintFiles. Все элементы, имеющиеся в данном массиве, должны быть извлечены на диск. Если эти записи были обновлены или изменены (или на диске или в файле кэша), то их необходимо переписать заново. В массиве хранятся индексы в таблице узлов (типа uint32_t).
4.f Файлы пользовательской конфигурации

Данный массив имеет размер ManifestHeader.NumOfUserConfigFiles. Все элементы, имеющиеся в данном массиве, должны быть извлечены на диск, если они не были извлечены ранее. Версия на диске должна иметь больший приоритет перед файлом в кэше, независимо от любых изменений. В массиве хранятся индексы в таблице узлов (типа uint32_t).
5. Карта манифеста

В данной таблице содержатся связанные цепочки индексов кластеров.
5.a Заголовок
Структура

Код: Выделить всё

struct ManifestMapHeader
{
    uint32_t HeaderVersion;
    uint32_t Dummy0;
}

Код: Выделить всё

  TGCFManifestMapHeader = record
      Dummy0,       
      Dummy1: ulong
    end;
Поля:
  • HeaderVersion – всегда 0x00000001. Версия заголовка;
  • Dummy0 – всегда 0x00000000.
5.b Записи

Данный массив имеет размер ManifestHeader.NodeCount. Значения массива (типа uint32_t) являются индексами первого блока (в таблице размещения блоков) для файла. Если значение равно BlockAllocationTableHeader.BlockCount, то элемент не сохранен в кэше или является каталогом.
6. Контрольные суммы

Данная запись в значительной мере избыточна, так как содержит много неиспользованных и дублированных сумм. Тем не менее, они используются, но только для других версий кэша (вплоть до LatestApplicationVersion).
6.a Заголовок
Структура

Код: Выделить всё

struct ChecksumDataContainer
{
    uint32_t HeaderVersion;
    uint32_t ChecksumSize;
}

Код: Выделить всё

  TGCFChecksumDataContainer = record
      HeaderVersion, 
      ChecksumSize: ulong;
    end;
Поля:
  • HeaderVersion – всегда 0x00000001. Версия заголовка;
  • ChecksumSize – количество байт в данной структуре (от ChecksumDataContainer и до LatestApplicationVersion).
6. b Заголовок таблицы
Структура

Код: Выделить всё

struct FileIdChecksumTableHeader
{
    uint32_t FormatCode;
    uint32_t Dummy0;
    uint32_t FileIdCount;
    uint32_t ChecksumCount;
}

Код: Выделить всё

[code]  TGCFFileIdChecksumTableHeader = record
      FormatCode
      Dummy0
      FileIdCount, 
      ChecksumCount: 
    end;
[/code]
Поля:
  • FormatCode – всегда 0x14893721;
  • Dummy0 – всегда 0x00000001;
  • FileIdCount – количество записей;
  • ChecksumCount – общее количество контрольных сумм.
6.c Таблица записей

Данный массив имеет размер FileIdChecksumTableHeader.FileIdCount. Он содержит идентификаторы файлов для всех версий данного кэш-файла. Таким образом для получения контрольной суммы для файла необходимо взять значение из массива, используя идентификатор файла как индекс.
Структура

Код: Выделить всё

struct FileIdChecksumTableEntry
{
    uint32_t ChecksumCount;
    uint32_t FirstChecksumIndex;
}

Код: Выделить всё

  TGCFFileIdChecksumTableEntry = record
      ChecksumCount,
      FirstChecksumIndex: ulong;
    end;
Поля:
  • ChecksumCount – определяет, сколько контрольных сумм принадлежит данной секции;
  • FirstChecksumIndex – индекс первого значения ChecksumEntry для данной секции.
6.d Записи контрольных сумм

Данный массив имеет размер FileIdChecksumTableHeader.ChecksumCount и содержит контрольные суммы для всех сегментов. В каждом блоке может быть несколько записей, следующих друг за другом.
Значение контрольной суммы рассчитывается для частей файлов размером ManifestHeader.CompressionBlockSize. Если размер сегмента файла меньше размера блока, то контрольная сумма вычисляется только для оставшегося размера файла.
Расчет контрольной суммы

Код: Выделить всё

// You need to use to use the zlib library <http://www.zlib.net/>
// or something with equivalent checksum algorithms.
uint32_t FileChecksum(const uint8_t *data, uint32_t length)
{
    return adler32(0, data, length) ^ crc32(0, data, length);
}

Код: Выделить всё

function Checksum(lpBuffer: pByte; uiBufferSize: uint): ulong;
begin
  result:=Adler32(0, lpBuffer, uiBufferSize) xor CRC32(lpBuffer, uiBufferSize);
end;
6.e Сигнатура

Представляет собой 0x80 байт RSA-сигнатуры (используя SHA-1 и RSASSA-PKCS1-v1_5) от секции контрольных сумм. RSA в качестве публичного ключа использует объявленный в Content Description Record, соответствующий ID файла кэша.
6.f Последняя версия приложения

Данная структура не существует, если ChecksumDataContainer.HeaderVersion имеет значение 0. Представляет собой значение типа uint32_t, хранящее последнюю версию контрольных сумм файла. Должно быть равно FileHeader. CacheID. Если это значение меньше чем FileHeader. CacheID, то данные контрольные суммы являются устаревшими и не должны использоваться.
7 Секция данных
Структура

Код: Выделить всё

struct DataHeader
{
    uint32_t ClusterCount;
    uint32_t ClusterSize;
    uint32_t FirstClusterOffset;
    uint32_t ClustersUsed;
    uint32_t Checksum;
}

Код: Выделить всё

  TGCFDataBlockHeader = record
      ClusterCount,
      PhysicalSectorSize,
      FirstSectorOffset,
      ClustersUsed,
      Checksum : ulong;
    end;
Поля:
  • ClusterCount – количество кластеров в файле. Должно совпадать с
  • FileHeader.ClusterCount;
  • ClusterSize – размер одного кластера в байтах. Должен совпадать с
  • FileHeader.ClusterSize;
  • FirstClusterOffset – смещение (относительно начала файла) для первого кластера;
  • ClustersUsed – количество использованных кластеров;
  • Checksum – для проверки заголовка. Рассчитывается путем сложения всех предыдущих полей.


Формат NCF (Not-Cache File)
  1. Структура данных файлов схожа с GCF, за исключением следующих пунктов:
  2. FileHeader. CacheType равен 0x00000002;
  3. Отсутствуют следующие поля:
    • таблица размещения блоков;
    • таблица размещения файлов;
    • секция данных;
  4. Файлы располагаются не в файле кэша, а на диске в папке “common\папка_игры”.

О поддержке файлов >4Гб

Для получения точного размера файла кэша существует два пути:
  1. из FileHeader.FileSize. Если полученный и записанный размеры не совпадают, то сверить с 0x100000000+FileHeader.FileSize - именно так вычисляется размер для файлов кэша >4Гб);
  2. вычислив значение DataHeader.FirstClusterOffset + DataHeader.ClusterCount*DataHeader.ClusterSize.
Добавлено спустя 2 минуты:
По сравнению с исходным документом есть некоторые отличия - добавлены описания заголовков/функций для языка Delphi (object Pascal), некоторые места я сократил, оставив из описания только необходимое (без ущерба информативности ).

PS: Переводил целый день и еще две недели (без нескольких дней) ждал открытия форума для публикации.

PSS: Абсолютно все положения (кроме сигнатуры контрольных сумм) проверенны мною на практических примерах (проверка производилась в программах CFToolBox и GCFScape).

PSSS: Все, написанное в данном документе, переводилось для использования в Universal Steam Extractor (rev.3), где уже вовсю идут работы ).
Последний раз редактировалось andreil 20.06.2010, 23:10, всего редактировалось 1 раз.
[url=svn://forum.csmania.ru/andreil]Репозиторий с моими проектами[/url]
Занимаюсь переносом всех своих библиотек на С++, а так же созданием их кроссплатформенных версий.
В команду переводчиков манги "Ah! My Goddess!" требуются переводчики с английского и тайперы (последних можем обучить, главное - желание).

Аватара пользователя
Darth Revan
Майор
Майор
Сообщения: 784
Зарегистрирован: 30.09.2008
Поблагодарили: 1 раз
Контактная информация:

#2 Сообщение 20.06.2010, 18:17

Очень впечатляет. Столько работы...
Теперь осталось создать создавалку GCF :-).

Аватара пользователя
andreil
Разработчик
Разработчик
Сообщения: 781
Зарегистрирован: 14.08.2006
Откуда: Светлогорск, Беларусь
Поблагодарили: 2 раза
Контактная информация:

#3 Сообщение 20.06.2010, 18:28

Вообще-то работы уже идут ;)

Добавлено спустя 45 секунд:
Если кто заметит ошибки/опечатки в тексте - сообщите, исправлю.
[url=svn://forum.csmania.ru/andreil]Репозиторий с моими проектами[/url]
Занимаюсь переносом всех своих библиотек на С++, а так же созданием их кроссплатформенных версий.
В команду переводчиков манги "Ah! My Goddess!" требуются переводчики с английского и тайперы (последних можем обучить, главное - желание).

Аватара пользователя
bugme666
Эксперты no-Steam
Эксперты no-Steam
Сообщения: 1168
Зарегистрирован: 29.01.2009
Откуда: MOSCOШ
Благодарил (а): 882 раза
Поблагодарили: 358 раз
Контактная информация:

#4 Сообщение 20.06.2010, 20:56

andreil
гы, круто
единственная ошибка, что я заметил - это вместо PSSS должно быть PPPS (постпостскриптум, а не постскриптумскриптум)

Аватара пользователя
SavicH
Майор
Майор
Сообщения: 770
Зарегистрирован: 05.11.2008
Откуда: Воронеж
Благодарил (а): 1 раз
Контактная информация:

#5 Сообщение 20.06.2010, 20:59

bugme, можно и так (post sub scriptum)

Аватара пользователя
bugme666
Эксперты no-Steam
Эксперты no-Steam
Сообщения: 1168
Зарегистрирован: 29.01.2009
Откуда: MOSCOШ
Благодарил (а): 882 раза
Поблагодарили: 358 раз
Контактная информация:

#6 Сообщение 20.06.2010, 21:01

SavicH, буду знать %)

Аватара пользователя
Schum
Модератор
Модератор
Сообщения: 1286
Зарегистрирован: 09.12.2007
Поблагодарили: 14 раз
Контактная информация:

#7 Сообщение 20.06.2010, 21:22

проихводилась
Браво, проделана большая работа

Аватара пользователя
[email protected]!c_V()1D
Разработчик
Разработчик
Сообщения: 2638
Зарегистрирован: 06.12.2007
Благодарил (а): 10 раз
Поблагодарили: 29 раз

#8 Сообщение 20.06.2010, 21:30

Не понимаю, зачем в глобальных?
По сабжу: так это ж всё итак было известно. Андрейка перевёл и свёл всю информацию из исходников в читаемый для обывателя вид. Ничего ж революционного. Ну, а за проделанную работу всё ж стоит вручить печеньку.

Аватара пользователя
DoktorAkcel666
Модератор
Модератор
Сообщения: 2818
Зарегистрирован: 30.03.2008
Благодарил (а): 367 раз
Поблагодарили: 1095 раз
Контактная информация:

#9 Сообщение 20.06.2010, 22:37

И также можно понять, насколько формат GCF сложнее NCF. Так сказать, все познается в сравнении
Every day in Africa a gazelle wakes up.
It knows it must run faster than the fastest lion or it will be killed.
Every morning a lion wakes up. It knows that it must outrun the gazelle or it will starve to death.
It doesn’t matter whether you are a lion or a gazelle.
When the sun comes up, you better be running.

old_liquid
Лейтенант
Лейтенант
Сообщения: 122
Зарегистрирован: 27.08.2009
Благодарил (а): 5 раз
Поблагодарили: 4 раза
Контактная информация:

#10 Сообщение 20.06.2010, 22:52

Зато ГЦФ устойчив к внешним воздействиям и неподвластен вирусам и прочим бякам. Ну и плюс отсутствие фрагментации, и всего один файл вместо нескольких сотен или тысяч

Аватара пользователя
bugme666
Эксперты no-Steam
Эксперты no-Steam
Сообщения: 1168
Зарегистрирован: 29.01.2009
Откуда: MOSCOШ
Благодарил (а): 882 раза
Поблагодарили: 358 раз
Контактная информация:

#11 Сообщение 20.06.2010, 22:57

andreil, common\папка_игры

Аватара пользователя
DoktorAkcel666
Модератор
Модератор
Сообщения: 2818
Зарегистрирован: 30.03.2008
Благодарил (а): 367 раз
Поблагодарили: 1095 раз
Контактная информация:

#12 Сообщение 20.06.2010, 22:57

Но в последнее время Valve что-то перегнули палку... Столько файлов наделать на пустом месте.
Every day in Africa a gazelle wakes up.
It knows it must run faster than the fastest lion or it will be killed.
Every morning a lion wakes up. It knows that it must outrun the gazelle or it will starve to death.
It doesn’t matter whether you are a lion or a gazelle.
When the sun comes up, you better be running.

Аватара пользователя
andreil
Разработчик
Разработчик
Сообщения: 781
Зарегистрирован: 14.08.2006
Откуда: Светлогорск, Беларусь
Поблагодарили: 2 раза
Контактная информация:

#13 Сообщение 20.06.2010, 23:11

Исправил вроде оба замечания :)

Насчет востребованности - многие просили "а где найти?", "а ее нету?", вот и решил опубликовать ;)
[url=svn://forum.csmania.ru/andreil]Репозиторий с моими проектами[/url]
Занимаюсь переносом всех своих библиотек на С++, а так же созданием их кроссплатформенных версий.
В команду переводчиков манги "Ah! My Goddess!" требуются переводчики с английского и тайперы (последних можем обучить, главное - желание).

Siemens31
Нович0к
Нович0к
Сообщения: 1
Зарегистрирован: 20.06.2011

#14 Сообщение 01.07.2011, 20:58

andreil писал(а):Исправил вроде оба замечания :)

Насчет востребованности - многие просили "а где найти?", "а ее нету?", вот и решил опубликовать ;)
Репазиторий не робит.

MOZGIII
Разработчик
Разработчик
Сообщения: 910
Зарегистрирован: 09.01.2009
Откуда: Переезжаю в /dev/null
Благодарил (а): 7 раз
Поблагодарили: 65 раз
Контактная информация:

#15 Сообщение 01.07.2011, 22:23

Siemens31
Репозиторий работает, возможно вы неправильно им пользуетесь. К тому-же все репозиторий andreil'а запаролен.

Ответить