Для начала в качестве примера и в дополнение к коду ниже вырезка из PAYLOAD блока,
что лежит в начале .manifest-а 71_778444776426820105.manifest - Half-Life Base Content (Sep 05 2013)
Код: Выделить всё
d0 17 f6 71 - PAYLOAD id
ae 05 00 00 - размер PAYLOAD (не закодирован)
0a 73 0a - размер текущего блока информации о файле (между двумя 0x0a)
18 - длина строки
76 61 6c 76 65 5c 63 6c 5f 64 6c 6c 73 5c 63 6c 69 65 6e 74 2e 64 6c 6c - строка "valve\cl_dlls\client.dll"
10 - байт конца строки
80 bc 26 - закодированный размер файла. Его можно получить по формуле в коде ниже
18 - разделитель
00 - флаг типа файла. В данном случае "обычный файл"
22 - разделитель
14 - предположительно размер sha1. Всегда равен 20 (в десятичом виде)
b1 9f 9b 59 5d af 67 3f 05 6d 81 af 24 ce 09 5a 3b 99 f4 b4 - SHA1 строки "valve\cl_dlls\client.dll"
2a 14 - разделитель
e8 76 2b 8d ac 01 15 fb e0 73 46 3a 9c b4 06 38 df 0b 07 7d - SHA1 файла. Если папка, то заполнен нулями
прочие данные
32 25 0a 14 e8 76 2b 8d ac 01
15 fb e0 73 46 3a 9c b4 06 38 df 0b 07 7d 15 85
39 10 2c 18 00 20 80 bc 26 28 80 9d 0f 0a 75 0a
1a 76 61 6c 76 65 5c 63 6c 5f 64 6c 6c 73 5c 63
6c 69 65 6e 74 2e 64 79 6c 69 62 10 b4 93 38 18
00 22 14 ad 12 c2 d3 3d bc 52 42 47 2c 77 a8 91
66 b7 c1 dc af 3f 10 2a 14 98 ce a8 c0 e2 d9 a5
aa 22 f2 fb f6 cd 8f db 7f cb db 3a b4 32 25 0a
14 98 ce a8 c0 e2 d9 a5 aa 22 f2 fb f6 cd 8f db
7f cb db 3a b4 15 ba 91 9e 82 18 00 20 b4 93 38
28 c0 9b 12
Код: Выделить всё
be 12 48 1f - METADATA id
2c 00 00 00 - размер METADATA
08 - разделитель
47 - depot id = 71 из "71_778444776426820105"
10 - разделитель
89 a4 b7 c1 e5 ec e5 e6 0a - depot version = 778444776426820105 из "71_778444776426820105"
18 - разделитель
a7 ad 9e 91 05 - last update - Unix время
20 - разделитель
00 - булев значение filenames encrypted
28 - разделитель
прочие данные
9d f3 b1 07 30 d0 c6 e2 02 38 13 40 fb e9 f1 a0 02 48 c8
f0 b0 fc 04
рекомендуемый порядок чтения комментариев и алгоритма:
load_content_manifest(...)
encode_payload(...)
encode_metadata(...)
"content_manifest.h"
[syntax lang="c" lines="100"]
/*
* загрузка .manifest файла
* Автор: wowks ( memberlist.php?mode=viewprofile&u=59383 )
* Благодаря информации из
* https://wiki.singul4rity.com/steam:file ... s:manifest
* http://cs.rin.ru/forum/viewtopic.php?f= ... =.manifest
*
* и Pr0Ger ( memberlist.php?mode=viewprofile&u=63369 )
*/
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#ifndef ___MANIFET___
#define ___MANIFET___
#define MANIFEST_CHUNK_PAYLOAD_ID 0x71F617D0
#define MANIFEST_CHUNK_METADATA_ID 0x1F4812BE
#define MANIFEST_CHUNK_SIGNATURE_ID 0x1B81B817
// MANIFEST_PAYLOAD_FILE_INFO.file_type:
#define MANIFEST_PAYLOAD_REGULAR_FILE 0
#define MANIFEST_PAYLOAD_USER_CONFIG 1
#define MANIFEST_PAYLOAD_VERSIONED_USER_CONFIG 2
#define MANIFEST_PAYLOAD_ENCRYPTED 4
#define MANIFEST_PAYLOAD_READ_ONLY 8
#define MANIFEST_PAYLOAD_HIDDEN 16
#define MANIFEST_PAYLOAD_EXECUTABLE 32
#define MANIFEST_PAYLOAD_DIRECTORY 64
//
#define SHA1_SIZE 20
typedef struct {
char filename[MAX_PATH];
int32_t filesize;
uint8_t file_type;
uint8_t sha1_filename[SHA1_SIZE];
uint8_t sha1_filedata[SHA1_SIZE];
} MANIFEST_PAYLOAD_FILE_INFO;
typedef struct {
int32_t depot_id;
int64_t depot_version;
time_t last_updated;
bool filenames_encrypted;
} MANIFEST_METADATA;
typedef void (* ___content_manifest_load_callback_fn) (const uint32_t manifest_chunk_id, const void * data, bool * cancel_load,
const void * optional_ptr);
// возвращаемые значения load_content_manifest
#define LOAD_CONTENT_MANIFEST___SUCCESS 0
#define LOAD_CONTENT_MANIFEST___FILE_ERROR 1
#define LOAD_CONTENT_MANIFEST___LOAD_CANCELED 2
//
int load_content_manifest(const wchar_t * manifest_filename, ___content_manifest_load_callback_fn callback,
const void * optional_ptr = NULL);
#endif
[/syntax]
"content_manifest.cpp"
[syntax lang="c" lines="100"]
#include "content_manifest.h"
#define ___varint_to_int(varint, varint_len, result_int) \
result_int = 0; \
for (register uintptr_t x = (varint_len - 1); x >= 1; x--) { \
result_int += (varint[x] - 1); \
result_int *= 128; \
} \
result_int += varint[0]; \
#define ___get_varint_len(varint, result_len) \
result_len = 0; \
do { \
result_len++; \
} while (varint[result_len - 1] & 0x80); \
typedef struct {
uint32_t id;
uint32_t size;
} CONTENT_MANIFEST_CHUNK;
static inline void encode_payload(const void * payload, MANIFEST_PAYLOAD_FILE_INFO * file_info ) {
memset(file_info, 0, sizeof(MANIFEST_PAYLOAD_FILE_INFO));
register uint8_t * ptr = (uint8_t *) payload;
register size_t varint_len;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ptr += 1; // пропуск закрывающего байта 0x0A
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 1. строка относительного пути файла.
uint8_t filename_len = *ptr; // cледущий байт после закрывающего 0x0A содержит длину стоки. А после него идёт сама строка. Считываем этот байт
ptr += 1; // пропуск байта с длиной стоки
strncpy(file_info->filename, (char const *) ptr, filename_len);
file_info->filename[filename_len] = '\0';
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ptr += filename_len; // пропуск строки
ptr += 1; // после строки всегда идёт байт 0x10, а после него размер файла. Пропускаем этот байт
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 2. размер файла.
// Он может быть длиной от 1 до нескольких байт. Чтобы не выводить формулы, объясню на примерах
// (значения в десятичной системе)
// 1 байт
// всё просто - этот байт содержит сам размер.
// ////////////////////////////////////////////////////////////////////////////////////////
// 2 байта {248, 20}
// 20 - 1 = 19
// размер = 248 + (19 * 128)
// ////////////////////////////////////////////////////////////////////////////////////////
// 3 байта {128, 188, 38}
// 188 - 1= 187 38 - 1= 37
// 37 * 128 = 4736
// (187 + 4736) * 128 = 630144
// размер = 128 + 630144
// ////////////////////////////////////////////////////////////////////////////////////////
// 4 байта {216, 211, 148, 2}
// 211 - 1 = 210 148 - 1 = 147 2 - 1 = 1
// 1 * 128 = 128
// (147 + 128) * 128 = 35200
// (210 + 35200) * 128 = 4532480
// размер = 216 + 4532480
//
// Возможно, есть и 5 и более байт для размера, но я с ними не сталкивался в пределах манифестов для игр
// Valve, на основе анализа которых я писал этот код.
___get_varint_len(ptr, varint_len);
___varint_to_int(ptr, varint_len, file_info->filesize);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ptr += varint_len; // пропуск filesize
ptr += 1; // пропуск 0x18
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 3. тип файла
file_info->file_type = *ptr;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ptr += 1; // пропуск типа файла
ptr += 2; // пропуск 0x22 и 0x14
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 4. sha1 относительного пути
memcpy(&file_info->sha1_filename[0], ptr, SHA1_SIZE);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ptr += SHA1_SIZE; // пропуск sha1 относительного пути
ptr += 2; // пропуск 0x2A, 0x14
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 5. sha1 файла
// для папки этот sha1 по понятным причинам заполнен нулями и копировать его нет смысла
if ((file_info->file_type & MANIFEST_PAYLOAD_DIRECTORY) != MANIFEST_PAYLOAD_DIRECTORY) {
memcpy(&file_info->sha1_filedata[0], ptr, SHA1_SIZE);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ptr += SHA1_SIZE; // пропуск sha1 файла
// пропуск чего-то ещё
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 5. далее могут идти какие-то данные, и иногда много, но их оставляю
// ...
return;
}
static inline void encode_metadata(const void * metadata, MANIFEST_METADATA * manifest_info ) {
memset(manifest_info, 0, sizeof(MANIFEST_METADATA));
register uint8_t * ptr = (uint8_t *) metadata;
register size_t varint_len;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ptr += 1; // пропуск 0x08
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// пример имени "depotcache\[depot_id]_[depot_version].manifest"
// струтура начала такая:
// 0x08
// varint [depot_id]
// 0x10
// varint [depot_version]
// 0x18
// varint [last_updated]
// 0x20
// (bool)uint8_t [filenames_encrypted]
// 0x28
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 1. depot_id
___get_varint_len(ptr, varint_len);
___varint_to_int(ptr, varint_len, manifest_info->depot_id);
////////////////////////////////////////////////////////////////////
ptr += varint_len; // пропуск depot_id
ptr += 1; // пропуск 0x10
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 2. depot_version
___get_varint_len(ptr, varint_len);
___varint_to_int(ptr, varint_len, manifest_info->depot_version);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ptr += varint_len; // пропуск байт depot_version
ptr += 1; // прпуск 0x18
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 3. last_updated
___get_varint_len(ptr, varint_len);
___varint_to_int(ptr, varint_len, manifest_info->last_updated); //
//////////////////////////////////////////////////////////
ptr += varint_len; // пропуск байт last_updated
ptr += 1; // пропуск 0x20
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 4. filenames_encrypted
manifest_info->filenames_encrypted = *ptr;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ptr += 1; // пропуск filenames_encrypted
// ptr += 1; // пропуск 0x28
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// дальше должны идти:
// varint [cb_disk_original]
// 0x30
// varint [cb_disk_compressed]
// 0x38
// varint [unique_chunks]
// 0x40
// varint [crc_encrypted]
// 0x48
// varint [crc_clear]
// MANIFEST_CHUNK_SIGNATURE_ID
// но на мой взгляд эти данные не важны
return;
}
int load_content_manifest(const wchar_t * manifest_filename, ___content_manifest_load_callback_fn callback,
const void * optional_ptr) {
int res = LOAD_CONTENT_MANIFEST___FILE_ERROR;
bool cancel_load = false;
FILE * f = _wfopen(manifest_filename, L"rb");
if (f != NULL) {
CONTENT_MANIFEST_CHUNK manifest_chunk;
// PAYLOAD
if (fread(&manifest_chunk, sizeof(CONTENT_MANIFEST_CHUNK), 1, f) == 1) {
if (manifest_chunk.id == MANIFEST_CHUNK_PAYLOAD_ID) {
if (manifest_chunk.size > 0) {
void * payload = malloc(manifest_chunk.size);
if (payload != NULL) {
if (fread(payload, manifest_chunk.size, 1, f) == 1) {
register uint8_t * ptr = (uint8_t *) payload;
MANIFEST_PAYLOAD_FILE_INFO file_info;
do {
// получаем размер участка информации о файле (file_info_size)
// это varint!
// он экранируется 2-мя байтами со значением 0x0A. Пример для 1 и 2 байтной цепочки:
// 1 байт
// [0x0A, размер, 0x0A]
// размер участка = размер
// 2 байта
// [0x0A, размер, инкремент_размера, 0x0A]
// размер участка = размер + ((инкремент_размера - 1) * 128)
//
ptr += 1; // пропуск 0x0A
manifest_chunk.size -= 1;
// узнать длину цепочки байт varint можно в цикле, проверяя выставлен ли самый младший (8-й) бит в значение 1.
// Для этого есть макрос ___get_varint_len(varint, result_len)
size_t varint_len;
___get_varint_len(ptr, varint_len);
// Для получения значения из varint по его длине есть другой макрос ___varint_to_int(varint, varint_len, result_int)
size_t file_info_size;
___varint_to_int(ptr, varint_len, file_info_size);
// блок данных начинается с закрывающего 0x0A включительно
// перематываем до него
ptr += varint_len;
manifest_chunk.size -= varint_len;
encode_payload(ptr, &file_info);
callback(MANIFEST_CHUNK_PAYLOAD_ID, &file_info, &cancel_load, optional_ptr);
if (cancel_load) {
res = LOAD_CONTENT_MANIFEST___LOAD_CANCELED;
break;
}
ptr += file_info_size;
manifest_chunk.size -= file_info_size;
} while (manifest_chunk.size > 0);
free(payload);
if ( ! cancel_load) {
goto load_metadata_chunk;
}
} else {
free(payload);
}
}
}
}
}
goto finish;
// METADATA
load_metadata_chunk:
if (fread(&manifest_chunk, sizeof(CONTENT_MANIFEST_CHUNK), 1, f) == 1) {
if (manifest_chunk.id == MANIFEST_CHUNK_METADATA_ID) {
if (manifest_chunk.size > 0) {
void * metadata = malloc(manifest_chunk.size);
if (metadata != NULL) {
if (fread(metadata, manifest_chunk.size, 1, f) == 1) {
MANIFEST_METADATA manifest_info;
encode_metadata(metadata, &manifest_info);
callback(MANIFEST_CHUNK_METADATA_ID, &manifest_info, &cancel_load, optional_ptr);
if (cancel_load) {
res = LOAD_CONTENT_MANIFEST___LOAD_CANCELED;
}
free(metadata);
if ( ! cancel_load) {
goto load_signature_chunk;
}
} else {
free(metadata);
}
}
}
}
}
goto finish;
// SIGNATURE (есть не всегда)
load_signature_chunk:
/*
// не встречал манифесты, гле этот блок содержит данные, и не знаю важны ли они
if (fread(&manifest_chunk, sizeof(CONTENT_MANIFEST_CHUNK), 1, f) == 1) {
if (manifest_chunk.id == MANIFEST_CHUNK_SIGNATURE_ID) {
if (manifest_chunk.size > 0) {
void * signature = malloc(manifest_chunk.size);
if (signature != NULL) {
if (fread(signature, manifest_chunk.size, 1, f) == 1) {
//
// ...
//
free(signature);
} else {
free(signature);
goto finish;
}
}
}
}
}
*/
// и после SIGNATURE лежит uint32_t со значением 0x32C415AB, означающим конец
res = LOAD_CONTENT_MANIFEST___SUCCESS;
finish:
fclose(f);
}
return res;
}
[/syntax]
"main.cpp" (пример)
[syntax lang="c" lines="100"]
///////////////////// пример ////////////////////////////
#include "content_manifest.h"
#include <time.h>
typedef struct {
const char * root_path;
uint32_t file_nr;
} MY_CUSTOM_DATA;
void ManifestCallback(const uint32_t manifest_chunk_id, const void * data, bool * calcel_load,
const void * optional_ptr) {
if (manifest_chunk_id == MANIFEST_CHUNK_PAYLOAD_ID) {
MANIFEST_PAYLOAD_FILE_INFO * file_info = (MANIFEST_PAYLOAD_FILE_INFO *) data;
MY_CUSTOM_DATA * custom_data = (MY_CUSTOM_DATA *) optional_ptr;
custom_data->file_nr++;
printf("%4d) File: \"%s\\%s\"\n",
custom_data->file_nr,
(char *) custom_data->root_path,
file_info->filename);
printf(" Size: %d\n", file_info->filesize);
printf(" Type: %s\n", ((file_info->file_type & MANIFEST_PAYLOAD_DIRECTORY) ? "directory" : "file"));
#define ___print_sha1(pre_str, sha1_ptr, post_str) \
printf("%s", pre_str); \
for (uintptr_t x = 0; x < 20; x++) { \
printf("%x", sha1_ptr[x]); \
} \
printf("%s", post_str); \
___print_sha1(" SHA1 File Name:", file_info->sha1_filename, "\n");
___print_sha1(" SHA1 File Data:", file_info->sha1_filedata, "\n");
return;
}
if (manifest_chunk_id == MANIFEST_CHUNK_METADATA_ID) {
MANIFEST_METADATA * manifest_info = (MANIFEST_METADATA *) data;
printf("\n======================================================================\n");
printf("Depot Id: %d\n", manifest_info->depot_id);
printf("Depot Version: %llu\n", manifest_info->depot_version);
printf("Last Updated: %s", ctime(&manifest_info->last_updated));
printf("FileNames Encrypted: %s\n", (manifest_info->filenames_encrypted ? "TRUE" : "FALSE"));
return;
}
return;
}
int main() {
MY_CUSTOM_DATA custom_data;
custom_data.root_path = "Steam\\SteamApps\\common\\Portal 2";
custom_data.file_nr = 0;
switch (load_content_manifest(L"C:\\621_1861956698869753646.manifest", ManifestCallback, &custom_data)) {
case LOAD_CONTENT_MANIFEST___SUCCESS: MessageBoxA(NULL, "OK", "", MB_OK);
break;
case LOAD_CONTENT_MANIFEST___LOAD_CANCELED: MessageBoxA(NULL, "CANCELED", "", MB_OK);
break;
case LOAD_CONTENT_MANIFEST___FILE_ERROR: MessageBoxA(NULL, "FILE ERROR", "", MB_OK);
break;
default:
break;
}
return 0;
}
[/syntax]
*архив с исходником (это проект для CLion): https://mega.nz/#!94A3hBLC!R9MUOiMfTcn7 ... 3ITm8LGLpw
этой информации достаточно для:
- проверки целостности кеша, и сравнения с другиим кешем
- создание патчей
- если файлы игр однго движка свалены в одну папку можно по списку из manifest отделить их (игры) и их части (например озвучку) друг от друга
- по depot_id можно понять что это такое. Например в данном случае 71 - Half-Life Base Content