collection.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. #include "collection.h"
  2. #include "common.h"
  3. #include "vpk.h"
  4. #include "zip.h"
  5. #ifndef _WIN32
  6. #include <alloca.h>
  7. #else
  8. #define alloca _alloca
  9. #endif
  10. enum CollectionOpenResult collectionChainOpen(struct ICollection *collection,
  11. const char *name, enum FileType type, struct IFile **out_file) {
  12. while (collection) {
  13. enum CollectionOpenResult result = collection->open(collection, name, type, out_file);
  14. if (result == CollectionOpen_Success) return result;
  15. if (result == CollectionOpen_NotFound) {
  16. collection = collection->next;
  17. } else {
  18. /* TODO print error */
  19. return result;
  20. }
  21. }
  22. return CollectionOpen_NotFound;
  23. }
  24. struct FilesystemCollectionFile {
  25. struct IFile head;
  26. struct AFile file;
  27. struct Stack *temp;
  28. };
  29. struct FilesystemCollection {
  30. struct ICollection head;
  31. struct Memories mem;
  32. char *prefix;
  33. };
  34. static size_t filesystemCollectionFile_Read(struct IFile *file, size_t offset, size_t size, void *buffer) {
  35. struct FilesystemCollectionFile *f = (void*)file;
  36. const size_t result = aFileReadAtOffset(&f->file, offset, size, buffer);
  37. return result != AFileError ? result : 0;
  38. }
  39. static void filesystemCollectionFile_Close(struct IFile *file) {
  40. struct FilesystemCollectionFile *f = (void*)file;
  41. aFileClose(&f->file);
  42. stackFreeUpToPosition(f->temp, f);
  43. }
  44. static void filesystemCollectionClose(struct ICollection *collection) {
  45. (void)(collection);
  46. /* TODO ensure all files are closed */
  47. /* TODO free memory */
  48. }
  49. static char *makeResourceFilename(struct Stack *temp, const char *prefix, const char *name, enum FileType type) {
  50. const char *subdir = NULL;
  51. const char *suffix = NULL;
  52. switch (type) {
  53. case File_Map: subdir = "maps/"; suffix = ".bsp"; break;
  54. case File_Material: subdir = "materials/"; suffix = ".vmt"; break;
  55. case File_Texture: subdir = "materials/"; suffix = ".vtf"; break;
  56. case File_Model: subdir = "models/"; suffix = ".mdl"; break;
  57. }
  58. const int prefix_len = prefix ? strlen(prefix) : 0;
  59. const int subdir_len = strlen(subdir);
  60. const int name_len = strlen(name);
  61. const int suffix_len = strlen(suffix);
  62. const int name_length = prefix_len + subdir_len + name_len + suffix_len + 1;
  63. char *output = stackAlloc(temp, name_length);
  64. if (!output) return NULL;
  65. char *c = output;
  66. if (prefix)
  67. for (int i = 0; i < prefix_len; ++i)
  68. *c++ = prefix[i];
  69. for (int i = 0; i < subdir_len; ++i)
  70. *c++ = subdir[i];
  71. for (int i = 0; i < name_len; ++i) {
  72. char C = tolower(name[i]);
  73. *c++ = (C == '\\') ? '/' : C;
  74. }
  75. for (int i = 0; i < suffix_len; ++i)
  76. *c++ = suffix[i];
  77. *c = '\0';
  78. return output;
  79. }
  80. static enum CollectionOpenResult filesystemCollectionOpen(struct ICollection *collection,
  81. const char *name, enum FileType type, struct IFile **out_file) {
  82. struct FilesystemCollection *fsc = (struct FilesystemCollection*)collection;
  83. *out_file = NULL;
  84. struct FilesystemCollectionFile *file = stackAlloc(fsc->mem.temp, sizeof(*file));
  85. char *filename = makeResourceFilename(fsc->mem.temp, fsc->prefix, name, type);
  86. if (!file || !filename) {
  87. PRINTF("Not enough memory for file %s", name);
  88. return CollectionOpen_NotEnoughMemory;
  89. }
  90. if (aFileOpen(&file->file, filename) != AFile_Success) {
  91. if (type == File_Map)
  92. PRINTF("Cannot open map %s", filename);
  93. stackFreeUpToPosition(fsc->mem.temp, file);
  94. return CollectionOpen_NotFound;
  95. }
  96. file->head.size = file->file.size;
  97. file->head.read = filesystemCollectionFile_Read;
  98. file->head.close = filesystemCollectionFile_Close;
  99. file->temp = fsc->mem.temp;
  100. *out_file = &file->head;
  101. stackFreeUpToPosition(fsc->mem.temp, filename);
  102. return CollectionOpen_Success;
  103. }
  104. struct ICollection *collectionCreateFilesystem(struct Memories *mem, const char *dir) {
  105. int dir_len = strlen(dir);
  106. struct FilesystemCollection *collection = stackAlloc(mem->persistent, sizeof(*collection) + dir_len + 2);
  107. if (!collection)
  108. return NULL;
  109. memset(collection, 0, sizeof *collection);
  110. collection->mem = *mem;
  111. collection->prefix = (char*)(collection + 1);
  112. memcpy(collection->prefix, dir, dir_len);
  113. collection->prefix[dir_len] = '/';
  114. collection->prefix[dir_len+1] = '\0';
  115. collection->head.open = filesystemCollectionOpen;
  116. collection->head.close = filesystemCollectionClose;
  117. return &collection->head;
  118. }
  119. struct StringView {
  120. const char *s;
  121. int len;
  122. };
  123. #define PRI_SV "%.*s"
  124. #define PASS_SV(sv) sv.len, sv.s
  125. #define PASS_PSV(sv) sv->len, sv->s
  126. static struct StringView readString(const char **c, const char *end) {
  127. struct StringView ret = { *c, 0 };
  128. while (*c < end && **c != '\0') ++(*c);
  129. ret.len = *c - ret.s;
  130. ++(*c);
  131. return ret;
  132. }
  133. struct VPKFileMetadata {
  134. struct StringView filename;
  135. int archive;
  136. struct {
  137. size_t off, size;
  138. } dir, arc;
  139. };
  140. #define MAX_VPK_ARCHIVES 32
  141. struct VPKCollection {
  142. struct ICollection head;
  143. struct Memories mem;
  144. struct AFileMap directory;
  145. struct AFile archives[MAX_VPK_ARCHIVES];
  146. struct VPKFileMetadata *files;
  147. int files_count;
  148. };
  149. struct VPKCollectionFile {
  150. struct IFile head;
  151. const struct VPKFileMetadata *metadata;
  152. struct VPKCollection *collection;
  153. };
  154. static void vpkCollectionClose(struct ICollection *collection) {
  155. (void)(collection);
  156. /* FIXME close handles and free memory */
  157. }
  158. static size_t vpkCollectionFileRead(struct IFile *file, size_t offset, size_t size, void *buffer) {
  159. struct VPKCollectionFile *f = (struct VPKCollectionFile*)file;
  160. const struct VPKFileMetadata *meta = f->metadata;
  161. size_t size_read = 0;
  162. if (offset < meta->dir.size) {
  163. const void *begin = ((char*)f->collection->directory.map) + offset + meta->dir.off;
  164. const size_t dir_size_left = meta->dir.size - offset;
  165. if (size < dir_size_left) {
  166. memcpy(buffer, begin, size);
  167. return size;
  168. }
  169. const size_t size_to_read = size - dir_size_left;
  170. memcpy(buffer, begin, size_to_read);
  171. buffer = ((char*)buffer) + size_to_read;
  172. offset += size_to_read;
  173. size -= size_to_read;
  174. size_read += size_to_read;
  175. }
  176. offset -= meta->dir.size;
  177. if (offset < meta->arc.size)
  178. size_read += aFileReadAtOffset(&f->collection->archives[meta->archive], meta->arc.off + offset, size, buffer);
  179. return size_read;
  180. }
  181. static void vpkCollectionFileClose(struct IFile *file) {
  182. struct VPKCollectionFile *f = (void*)file;
  183. stackFreeUpToPosition(f->collection->mem.temp, f);
  184. }
  185. static enum CollectionOpenResult vpkCollectionFileOpen(struct ICollection *collection,
  186. const char *name, enum FileType type, struct IFile **out_file) {
  187. struct VPKCollection *vpkc = (struct VPKCollection*)collection;
  188. *out_file = NULL;
  189. struct VPKCollectionFile *file = stackAlloc(vpkc->mem.temp, sizeof(*file));
  190. char *filename = makeResourceFilename(vpkc->mem.temp, NULL, name, type);
  191. if (!file || !filename) {
  192. PRINTF("Not enough memory for file %s", name);
  193. return CollectionOpen_NotEnoughMemory;
  194. }
  195. // binary search
  196. {
  197. const struct VPKFileMetadata *begin = vpkc->files;
  198. int count = vpkc->files_count;
  199. while (count > 0) {
  200. int item = count / 2;
  201. const struct VPKFileMetadata *meta = begin + item;
  202. const int comparison = strcmp(filename, meta->filename.s);
  203. if (comparison == 0) {
  204. file->metadata = meta;
  205. file->collection = vpkc;
  206. file->head.size = meta->arc.size + meta->dir.size;
  207. file->head.read = vpkCollectionFileRead;
  208. file->head.close = vpkCollectionFileClose;
  209. *out_file = &file->head;
  210. stackFreeUpToPosition(vpkc->mem.temp, filename);
  211. return CollectionOpen_Success;
  212. }
  213. if (comparison < 0) {
  214. count = item;
  215. } else {
  216. count = count - item - 1;
  217. begin += item + 1;
  218. }
  219. }
  220. }
  221. stackFreeUpToPosition(vpkc->mem.temp, file);
  222. return CollectionOpen_NotFound;
  223. }
  224. static int vpkMetadataCompare(const void *a, const void *b) {
  225. const struct VPKFileMetadata *am = a, *bm = b;
  226. return strcmp(am->filename.s, bm->filename.s);
  227. }
  228. struct ICollection *collectionCreateVPK(struct Memories *mem, const char *dirfile) {
  229. PRINTF("Opening collection %s", dirfile);
  230. struct VPKCollection *collection = stackAlloc(mem->persistent, sizeof(*collection));
  231. if (!collection)
  232. return NULL;
  233. memset(collection, 0, sizeof *collection);
  234. collection->mem = *mem;
  235. void *const temp_stack_top = stackGetCursor(mem->temp);
  236. collection->directory = aFileMapOpen(dirfile);
  237. if (!collection->directory.map) {
  238. PRINTF("Cannot open %s", dirfile);
  239. exit(-1);
  240. }
  241. const char *dir = collection->directory.map;
  242. const size_t size = collection->directory.size;
  243. if (size <= sizeof(struct VPK2Header)) {
  244. PRINT("VPK header is too small");
  245. exit(-1);
  246. }
  247. const struct VPK2Header *header = collection->directory.map;
  248. if (header->signature != VPK_SIGNATURE) {
  249. PRINTF("Wrong VPK signature %08x", header->signature);
  250. exit(-1);
  251. }
  252. if (header->version != 2) {
  253. PRINTF("VPK version %d is not supported", header->version);
  254. exit(-1);
  255. }
  256. struct VPKFileMetadata *files_begin = stackGetCursor(mem->persistent), *files_end = files_begin;
  257. int max_archives = -1;
  258. const char *const end = dir + size;
  259. const char *c = dir + sizeof(struct VPK2Header);
  260. for (;;) {
  261. // read extension
  262. const struct StringView ext = readString(&c, end);
  263. if (ext.len == 0)
  264. break;
  265. for (;;) {
  266. // read path
  267. const struct StringView path = readString(&c, end);
  268. if (path.len == 0)
  269. break;
  270. for (;;) {
  271. // read filename
  272. const struct StringView filename = readString(&c, end);
  273. if (filename.len == 0)
  274. break;
  275. if ((unsigned long)(end - c) < sizeof(struct VPKTreeEntry)) {
  276. PRINT("Incomplete VPKTreeEntry struct");
  277. exit(-1);
  278. }
  279. const struct VPKTreeEntry *const entry = (const struct VPKTreeEntry*)c;
  280. c += sizeof(struct VPKTreeEntry);
  281. if (entry->terminator != VPK_TERMINATOR) {
  282. PRINTF("Wrong terminator: %04x", entry->terminator);
  283. exit(-1);
  284. }
  285. const int filename_len = 3 + path.len + filename.len + ext.len;
  286. char *filename_temp = stackAlloc(mem->temp, filename_len);
  287. if (!filename_temp) {
  288. PRINT("Not enough temp memory");
  289. exit(-1);
  290. }
  291. memcpy(filename_temp, path.s, path.len);
  292. filename_temp[path.len] = '/';
  293. memcpy(filename_temp + path.len + 1, filename.s, filename.len);
  294. filename_temp[path.len + 1 + filename.len] = '.';
  295. memcpy(filename_temp + path.len + 1 + filename.len + 1, ext.s, ext.len);
  296. filename_temp[filename_len-1] = '\0';
  297. /*
  298. PRINTF("%s crc=%08x pre=%d arc=%d(%04x) off=%d len=%d",
  299. filename_temp,
  300. entry->crc,
  301. entry->preloadBytes, entry->archive, entry->archive,
  302. entry->archiveOffset, entry->archiveLength);
  303. */
  304. struct VPKFileMetadata *file = stackAlloc(mem->persistent, sizeof(struct VPKFileMetadata));
  305. if (!file) {
  306. PRINT("Not enough persistent memory");
  307. exit(-1);
  308. }
  309. memset(file, 0, sizeof(*file));
  310. file->filename.s = filename_temp;
  311. file->filename.len = filename_len - 1;
  312. if (entry->preloadBytes) {
  313. file->dir.off = c - (char*)dir;
  314. file->dir.size = entry->preloadBytes;
  315. }
  316. if (entry->archiveLength) {
  317. file->archive = entry->archive != 0x7fff ? entry->archive : -1;
  318. file->arc.off = entry->archiveOffset;
  319. file->arc.size = entry->archiveLength;
  320. }
  321. if (file->archive > max_archives)
  322. max_archives = file->archive;
  323. files_end = file + 1;
  324. ++collection->files_count;
  325. c += entry->preloadBytes;
  326. } // for filenames
  327. } // for paths
  328. } // for extensions
  329. // sort
  330. qsort(files_begin, files_end - files_begin, sizeof(*files_begin), vpkMetadataCompare);
  331. // store filenames in persistent memory
  332. for (struct VPKFileMetadata *file = files_begin; file != files_end; ++file) {
  333. char *string = stackAlloc(mem->persistent, file->filename.len + 1);
  334. if (!string) {
  335. PRINT("Not enough persistent memory");
  336. exit(-1);
  337. }
  338. memcpy(string, file->filename.s, file->filename.len + 1);
  339. file->filename.s = string;
  340. }
  341. // open archives
  342. if (max_archives >= MAX_VPK_ARCHIVES) {
  343. PRINTF("Too many archives: %d", max_archives);
  344. exit(-1);
  345. }
  346. const int dirfile_len = strlen(dirfile) + 1;
  347. char *arcname = alloca(dirfile_len);
  348. if (!arcname || dirfile_len < 8) {
  349. PRINT("WTF");
  350. exit(-1);
  351. }
  352. memcpy(arcname, dirfile, dirfile_len);
  353. for (int i = 0; i <= max_archives; ++i) {
  354. sprintf(arcname + dirfile_len - 8, "%03d.vpk", i);
  355. if (AFile_Success != aFileOpen(collection->archives+i, arcname)) {
  356. PRINTF("Cannot open archive %s", arcname);
  357. exit(-1);
  358. }
  359. }
  360. stackFreeUpToPosition(mem->temp, temp_stack_top);
  361. collection->head.open = vpkCollectionFileOpen;
  362. collection->head.close = vpkCollectionClose;
  363. collection->files = files_begin;
  364. return &collection->head;
  365. }
  366. struct PakfileFileMetadata {
  367. struct StringView filename;
  368. const void *data;
  369. uint32_t size;
  370. };
  371. struct PakfileCollection {
  372. struct ICollection head;
  373. struct Memories mem;
  374. struct PakfileFileMetadata *files;
  375. int files_count;
  376. };
  377. struct PakfileCollectionFile {
  378. struct IFile head;
  379. const struct PakfileFileMetadata *metadata;
  380. struct Stack *stack;
  381. };
  382. static void pakfileCollectionClose(struct ICollection *collection) {
  383. struct PakfileCollection *pc = (void*)collection;
  384. stackFreeUpToPosition(pc->mem.persistent, pc->files);
  385. }
  386. static size_t pakfileCollectionFileRead(struct IFile *file, size_t offset, size_t size, void *buffer) {
  387. struct PakfileCollectionFile *f = (struct PakfileCollectionFile*)file;
  388. const struct PakfileFileMetadata *meta = f->metadata;
  389. if (offset > meta->size)
  390. return 0;
  391. if (offset + size > meta->size)
  392. size = meta->size - offset;
  393. memcpy(buffer, (const char*)meta->data + offset, size);
  394. return size;
  395. }
  396. static void pakfileCollectionFileClose(struct IFile *file) {
  397. struct PakfileCollectionFile *f = (void*)file;
  398. stackFreeUpToPosition(f->stack, f);
  399. }
  400. static enum CollectionOpenResult pakfileCollectionFileOpen(struct ICollection *collection,
  401. const char *name, enum FileType type, struct IFile **out_file) {
  402. struct PakfileCollection *pakfilec = (struct PakfileCollection*)collection;
  403. *out_file = NULL;
  404. struct PakfileCollectionFile *file = stackAlloc(pakfilec->mem.temp, sizeof(*file));
  405. char *filename = makeResourceFilename(pakfilec->mem.temp, NULL, name, type);
  406. if (!file || !filename) {
  407. PRINTF("Not enough memory for file %s", name);
  408. return CollectionOpen_NotEnoughMemory;
  409. }
  410. // binary search
  411. {
  412. const struct PakfileFileMetadata *begin = pakfilec->files;
  413. int count = pakfilec->files_count;
  414. while (count > 0) {
  415. int item = count / 2;
  416. const struct PakfileFileMetadata *meta = begin + item;
  417. const int comparison = strncmp(filename, meta->filename.s, meta->filename.len);
  418. if (comparison == 0) {
  419. file->metadata = meta;
  420. file->stack = pakfilec->mem.temp;
  421. file->head.size = meta->size;
  422. file->head.read = pakfileCollectionFileRead;
  423. file->head.close = pakfileCollectionFileClose;
  424. *out_file = &file->head;
  425. stackFreeUpToPosition(pakfilec->mem.temp, filename);
  426. return CollectionOpen_Success;
  427. }
  428. if (comparison < 0) {
  429. count = item;
  430. } else {
  431. count = count - item - 1;
  432. begin += item + 1;
  433. }
  434. }
  435. }
  436. stackFreeUpToPosition(pakfilec->mem.temp, file);
  437. return CollectionOpen_NotFound;
  438. }
  439. static int pakfileMetadataCompare(const void *a, const void *b) {
  440. const struct PakfileFileMetadata *am = a, *bm = b;
  441. return strncmp(am->filename.s, bm->filename.s,
  442. am->filename.len < bm->filename.len ?
  443. am->filename.len : bm->filename.len);
  444. }
  445. struct ICollection *collectionCreatePakfile(struct Memories *mem, const void *pakfile, uint32_t size) {
  446. (void)mem;
  447. // 1. need to find zip end of directory
  448. if (size < (int)sizeof(struct ZipEndOfDirectory)) {
  449. PRINT("Invalid pakfile size");
  450. return NULL;
  451. }
  452. int eod_offset = size - sizeof(struct ZipEndOfDirectory);
  453. const struct ZipEndOfDirectory *eod;
  454. for (;;) {
  455. eod = (void*)((const char*)pakfile + eod_offset);
  456. // TODO what if comment contain signature?
  457. if (eod->signature == ZipEndOfDirectory_Signature)
  458. break;
  459. --eod_offset;
  460. }
  461. if (!eod) {
  462. PRINT("End-of-directory not found");
  463. return NULL;
  464. }
  465. if (eod->dir_offset > size || eod->dir_size > size || eod->dir_size + eod->dir_offset > size) {
  466. PRINTF("Wrong pakfile directory sizes; size=%d, dir_offset=%d, dir_size=%d",
  467. size, eod->dir_offset, eod->dir_size);
  468. return NULL;
  469. }
  470. struct PakfileFileMetadata *metadata_start = stackGetCursor(mem->persistent);
  471. int files_count = 0;
  472. const char *dir = (const char*)pakfile + eod->dir_offset, *dir_end = dir + eod->dir_size;
  473. for (;;) {
  474. if (dir == dir_end)
  475. break;
  476. if (dir_end - dir < (long)sizeof(struct ZipFileHeader)) {
  477. PRINT("Corrupted directory");
  478. break;
  479. }
  480. const struct ZipFileHeader *fileheader = (void*)dir;
  481. if (fileheader->signature != ZipFileHeader_Signature) {
  482. PRINTF("Wrong file header signature: %08x", fileheader->signature);
  483. break;
  484. }
  485. // TODO overflow
  486. const char *next_dir = dir + sizeof(struct ZipFileHeader) + fileheader->filename_length + fileheader->extra_field_length + fileheader->file_comment_length;
  487. if (dir > dir_end) {
  488. PRINT("Corrupted directory");
  489. break;
  490. }
  491. if (fileheader->compression != 0 || fileheader->uncompressed_size != fileheader->compressed_size) {
  492. PRINTF("Compression method %d is not supported", fileheader->compression);
  493. } else {
  494. const char *filename = dir + sizeof(struct ZipFileHeader);
  495. const char *local = (const char*)pakfile + fileheader->local_offset;
  496. if (size - (local - (const char*)pakfile) < (long)sizeof(struct ZipLocalFileHeader)) {
  497. PRINT("Local file header OOB");
  498. break;
  499. }
  500. const struct ZipLocalFileHeader *localheader = (void*)local;
  501. if (localheader->signature != ZipLocalFileHeader_Signature) {
  502. PRINTF("Invalid local file header signature %08x", localheader->signature);
  503. break;
  504. }
  505. // TODO overflow
  506. local += sizeof(*localheader) + localheader->filename_length + localheader->extra_field_length;
  507. if ((local - (const char*)pakfile) + fileheader->compressed_size > size) {
  508. PRINT("File data OOB");
  509. break;
  510. }
  511. struct PakfileFileMetadata *metadata = stackAlloc(mem->persistent, sizeof(*metadata));
  512. if (!metadata) {
  513. PRINT("Not enough memory");
  514. exit(-1);
  515. }
  516. metadata->data = local;
  517. metadata->size = fileheader->uncompressed_size;
  518. metadata->filename.s = filename;
  519. metadata->filename.len = fileheader->filename_length;
  520. ++files_count;
  521. /*
  522. PRINTF("FILE \""PRI_SV"\" size=%d offset=%d",
  523. PASS_SV(metadata->filename), fileheader->uncompressed_size, fileheader->local_offset);
  524. */
  525. }
  526. dir = next_dir;
  527. }
  528. if (dir != dir_end) {
  529. PRINT("Something went wrong");
  530. exit(-1);
  531. }
  532. // sort
  533. qsort(metadata_start, files_count, sizeof(*metadata_start), pakfileMetadataCompare);
  534. struct PakfileCollection *collection = stackAlloc(mem->persistent, sizeof(*collection));
  535. collection->mem = *mem;
  536. collection->files = metadata_start;
  537. collection->files_count = files_count;
  538. collection->head.open = pakfileCollectionFileOpen;
  539. collection->head.close = pakfileCollectionClose;
  540. return &collection->head;
  541. }