瀏覽代碼

add reading pakfile lumps, fix #19

Ivan Avdeev 6 年之前
父節點
當前提交
1f49bfdc2b
共有 6 個文件被更改,包括 301 次插入4 次删除
  1. 13 2
      src/bsp.c
  2. 220 0
      src/collection.c
  3. 1 0
      src/collection.h
  4. 7 1
      src/material.c
  5. 0 1
      src/vpk.h
  6. 60 0
      src/zip.h

+ 13 - 2
src/bsp.c

@@ -863,6 +863,7 @@ enum BSPLoadResult bspLoadWorldspawn(struct BSPLoadModelContext context, const c
 	}
 
 	void *tmp_cursor = stackGetCursor(context.tmp);
+	struct ICollection *pakfile = NULL;
 
 	struct VBSPHeader vbsp_header;
 	size_t bytes = file->read(file, 0, sizeof vbsp_header, &vbsp_header);
@@ -899,7 +900,14 @@ enum BSPLoadResult bspLoadWorldspawn(struct BSPLoadModelContext context, const c
 	LIST_LUMPS
 #undef BSPLUMP
 
-	result = bspLoadModel(context.collection, context.model, context.persistent, context.tmp, &lumps, 0);
+	if (lumps.pakfile.n > 0) {
+		struct Memories memories = { context.tmp, context.tmp };
+		pakfile = collectionCreatePakfile(&memories, lumps.pakfile.p, lumps.pakfile.n);
+		if (pakfile)
+			pakfile->next = context.collection;
+	}
+
+	result = bspLoadModel(pakfile ? pakfile : context.collection, context.model, context.persistent, context.tmp, &lumps, 0);
 	if (result != BSPLoadResult_Success) {
 		PRINTF("Error: bspLoadModel() => %s", R2S(result));
 		goto exit;
@@ -907,9 +915,12 @@ enum BSPLoadResult bspLoadWorldspawn(struct BSPLoadModelContext context, const c
 
 	result = bspReadEntities(&context, lumps.entities.p, lumps.entities.n);
 	if (result != BSPLoadResult_Success)
-		PRINTF("Errro: bspReadEntities() => %s", R2S(result));
+		PRINTF("Error: bspReadEntities() => %s", R2S(result));
 
 exit:
+	if (pakfile)
+		pakfile->close(pakfile);
+
 	stackFreeUpToPosition(context.tmp, tmp_cursor);
 	if (file) file->close(file);
 	return result;

+ 220 - 0
src/collection.c

@@ -1,6 +1,7 @@
 #include "collection.h"
 #include "common.h"
 #include "vpk.h"
+#include "zip.h"
 #include <alloca.h>
 
 enum CollectionOpenResult collectionChainOpen(struct ICollection *collection,
@@ -444,3 +445,222 @@ struct ICollection *collectionCreateVPK(struct Memories *mem, const char *dirfil
 
 	return &collection->head;
 }
+
+struct PakfileFileMetadata {
+	struct StringView filename;
+	const void *data;
+	uint32_t size;
+};
+
+struct PakfileCollection {
+	struct ICollection head;
+	struct Memories mem;
+	struct PakfileFileMetadata *files;
+	int files_count;
+};
+
+struct PakfileCollectionFile {
+	struct IFile head;
+	const struct PakfileFileMetadata *metadata;
+	struct Stack *stack;
+};
+
+static void pakfileCollectionClose(struct ICollection *collection) {
+	struct PakfileCollection *pc = (void*)collection;
+	stackFreeUpToPosition(pc->mem.persistent, pc->files);
+}
+
+static size_t pakfileCollectionFileRead(struct IFile *file, size_t offset, size_t size, void *buffer) {
+	struct PakfileCollectionFile *f = (struct PakfileCollectionFile*)file;
+	const struct PakfileFileMetadata *meta = f->metadata;
+
+	if (offset > meta->size)
+		return 0;
+	if (offset + size > meta->size)
+		size = meta->size - offset;
+	memcpy(buffer, (const char*)meta->data + offset, size);
+	return size;
+}
+
+static void pakfileCollectionFileClose(struct IFile *file) {
+	struct PakfileCollectionFile *f = (void*)file;
+	stackFreeUpToPosition(f->stack, f);
+}
+
+static enum CollectionOpenResult pakfileCollectionFileOpen(struct ICollection *collection,
+		const char *name, enum FileType type, struct IFile **out_file) {
+	struct PakfileCollection *pakfilec = (struct PakfileCollection*)collection;
+
+	*out_file = NULL;
+
+	struct PakfileCollectionFile *file = stackAlloc(pakfilec->mem.temp, sizeof(*file));
+	char *filename = makeResourceFilename(pakfilec->mem.temp, NULL, name, type);
+
+	if (!file || !filename) {
+		PRINTF("Not enough memory for file %s", name);
+		return CollectionOpen_NotEnoughMemory;
+	}
+
+	// binary search
+	{
+		const struct PakfileFileMetadata *begin = pakfilec->files;
+		int count = pakfilec->files_count;
+
+		while (count > 0) {
+			int item = count / 2;
+
+			const struct PakfileFileMetadata *meta = begin + item;
+			const int comparison = strncmp(filename, meta->filename.s, meta->filename.len);
+			if (comparison == 0) {
+				file->metadata = meta;
+				file->stack = pakfilec->mem.temp;
+				file->head.size = meta->size;
+				file->head.read = pakfileCollectionFileRead;
+				file->head.close = pakfileCollectionFileClose;
+				*out_file = &file->head;
+				stackFreeUpToPosition(pakfilec->mem.temp, filename);
+				return CollectionOpen_Success;
+			}
+
+			if (comparison < 0) {
+				count = item;
+			} else {
+				count = count - item - 1;
+				begin += item + 1;
+			}
+		}
+	}
+
+	stackFreeUpToPosition(pakfilec->mem.temp, file);
+	return CollectionOpen_NotFound;
+}
+
+static int pakfileMetadataCompare(const void *a, const void *b) {
+	const struct PakfileFileMetadata *am = a, *bm = b;
+	return strncmp(am->filename.s, bm->filename.s,
+			am->filename.len < bm->filename.len ?
+			am->filename.len : bm->filename.len);
+}
+
+struct ICollection *collectionCreatePakfile(struct Memories *mem, const void *pakfile, uint32_t size) {
+
+	(void)mem;
+
+	// 1. need to find zip end of directory 
+	if (size < (int)sizeof(struct ZipEndOfDirectory)) {
+		PRINT("Invalid pakfile size");
+		return NULL;
+	}
+
+	int eod_offset = size - sizeof(struct ZipEndOfDirectory);
+	const struct ZipEndOfDirectory *eod;
+	for (;;) {
+		eod = (void*)((const char*)pakfile + eod_offset);
+		// TODO what if comment contain signature?
+		if (eod->signature == ZipEndOfDirectory_Signature)
+			break;
+
+		--eod_offset;
+	}
+
+	if (!eod) {
+		PRINT("End-of-directory not found");
+		return NULL;
+	}
+
+	if (eod->dir_offset > size || eod->dir_size > size || eod->dir_size + eod->dir_offset > size) {
+		PRINTF("Wrong pakfile directory sizes; size=%d, dir_offset=%d, dir_size=%d",
+				size, eod->dir_offset, eod->dir_size);
+		return NULL;
+	}
+
+	struct PakfileFileMetadata *metadata_start = stackGetCursor(mem->persistent);
+	int files_count = 0;
+
+	const char *dir = (const char*)pakfile + eod->dir_offset, *dir_end = dir + eod->dir_size;
+	for (;;) {
+		if (dir == dir_end)
+			break;
+
+		if (dir_end - dir < (long)sizeof(struct ZipFileHeader)) {
+			PRINT("Corrupted directory");
+			break;
+		}
+
+		const struct ZipFileHeader *fileheader = (void*)dir;
+		if (fileheader->signature != ZipFileHeader_Signature) {
+			PRINTF("Wrong file header signature: %08x", fileheader->signature);
+			break;
+		}
+
+		// TODO overflow
+		const char *next_dir = dir + sizeof(struct ZipFileHeader) + fileheader->filename_length + fileheader->extra_field_length + fileheader->file_comment_length;
+		if (dir > dir_end) {
+			PRINT("Corrupted directory");
+			break;
+		}
+
+		if (fileheader->compression != 0 || fileheader->uncompressed_size != fileheader->compressed_size) {
+			PRINTF("Compression method %d is not supported", fileheader->compression);
+		} else {
+			const char *filename = dir + sizeof(struct ZipFileHeader);
+
+			const char *local = (const char*)pakfile + fileheader->local_offset;
+			if (size - (local - (const char*)pakfile) < (long)sizeof(struct ZipLocalFileHeader)) {
+				PRINT("Local file header OOB");
+				break;
+			}
+
+			const struct ZipLocalFileHeader *localheader = (void*)local;
+			if (localheader->signature != ZipLocalFileHeader_Signature) {
+				PRINTF("Invalid local file header signature %08x", localheader->signature);
+				break;
+			}
+
+			// TODO overflow
+			local += sizeof(*localheader) + localheader->filename_length + localheader->extra_field_length;
+
+			if ((local - (const char*)pakfile) + fileheader->compressed_size > size) {
+				PRINT("File data OOB");
+				break;
+			}
+
+			struct PakfileFileMetadata *metadata = stackAlloc(mem->persistent, sizeof(*metadata));
+			if (!metadata) {
+				PRINT("Not enough memory");
+				exit(-1);
+			}
+
+			metadata->data = local;
+			metadata->size = fileheader->uncompressed_size;
+			metadata->filename.s = filename;
+			metadata->filename.len = fileheader->filename_length;
+			++files_count;
+
+			/*
+			PRINTF("FILE \""PRI_SV"\" size=%d offset=%d",
+				PASS_SV(metadata->filename), fileheader->uncompressed_size, fileheader->local_offset);
+			*/
+		}
+
+		dir = next_dir;
+	}
+
+	if (dir != dir_end) {
+		PRINT("Something went wrong");
+		exit(-1);
+	}
+
+	// sort
+	qsort(metadata_start, files_count, sizeof(*metadata_start), pakfileMetadataCompare);
+
+	struct PakfileCollection *collection = stackAlloc(mem->persistent, sizeof(*collection));
+	collection->mem = *mem;
+	collection->files = metadata_start;
+	collection->files_count = files_count;
+	collection->head.open = pakfileCollectionFileOpen;
+	collection->head.close = pakfileCollectionClose;
+
+	return &collection->head;
+}
+

+ 1 - 0
src/collection.h

@@ -40,4 +40,5 @@ enum CollectionOpenResult collectionChainOpen(struct ICollection *collection,
 
 struct ICollection *collectionCreateFilesystem(struct Memories *mem, const char *dir);
 struct ICollection *collectionCreateVPK(struct Memories *mem, const char *dir_filename);
+struct ICollection *collectionCreatePakfile(struct Memories *mem, const void *pakfile, uint32_t size);
 

+ 7 - 1
src/material.c

@@ -122,6 +122,12 @@ static int materialLoad(struct IFile *file, struct ICollection *coll, struct Mat
 		} else if (strncasecmp("$fogcolor", ctx.key, ctx.key_length) == 0) {
 		} else if (strncasecmp("$alphatest", ctx.key, ctx.key_length) == 0) {
 		} else if (strncasecmp("$translucent", ctx.key, ctx.key_length) == 0) {
+		} else if (strncasecmp("include", ctx.key, ctx.key_length) == 0) {
+			char *vmt = strstr(ctx.value, ".vmt");
+			if (vmt)
+				*vmt = '\0';
+			if (strstr(ctx.value, "materials/") == ctx.value)
+				*output = *materialGet(ctx.value + 10, coll, tmp);
 		} else {
 			PRINTF("Material shader:%.*s, unknown param %.*s = %s",
 					shader_length, shader, ctx.key_length, ctx.key, ctx.value);
@@ -144,7 +150,7 @@ const struct Material *materialGet(const char *name, struct ICollection *collect
 
 	struct IFile *matfile;
 	if (CollectionOpen_Success != collectionChainOpen(collection, name, File_Material, &matfile)) {
-		//PRINTF("Material \"%s\" not found", name);
+		PRINTF("Material \"%s\" not found", name);
 		return cacheGetMaterial("opensource/placeholder");
 	}
 

+ 0 - 1
src/vpk.h

@@ -1,5 +1,4 @@
 #pragma once
-
 #include <stdint.h>
 
 #pragma pack(1)

+ 60 - 0
src/zip.h

@@ -0,0 +1,60 @@
+#pragma once
+#include <stdint.h>
+
+#pragma pack(1)
+
+#define ZipEndOfDirectory_Signature 0x06054b50ul
+#define ZipFileHeader_Signature 0x02014b50ul
+#define ZipLocalFileHeader_Signature 0x04034b50ul
+
+struct ZipEndOfDirectory {
+	uint32_t signature;
+	uint16_t disk;
+	uint16_t dir_disk;
+	uint16_t dir_records_num;
+	uint16_t dir_records_num_total;
+	uint32_t dir_size;
+	uint32_t dir_offset;
+	uint16_t comment_size;
+};
+
+struct ZipFileHeader {
+	uint32_t signature;
+	uint16_t version;
+	uint16_t extract_version;
+	uint16_t flags;
+	uint16_t compression;
+	uint16_t mod_time, mod_date;
+	uint32_t crc32;
+	uint32_t compressed_size;
+	uint32_t uncompressed_size;
+	uint16_t filename_length;
+	uint16_t extra_field_length;
+	uint16_t file_comment_length;
+	uint16_t disk;
+	uint16_t file_attribs;
+	uint32_t ext_file_attribs;
+	uint32_t local_offset;
+	/*
+	 * - [filename_length] file name
+	 * - [extra_field_length] extra field
+	 * - [file_comment_length] file comments
+	 */
+};
+
+struct ZipLocalFileHeader {
+	uint32_t signature;
+	uint16_t extract_version;
+	uint16_t flags;
+	uint16_t compression;
+	uint16_t mod_time;
+	uint16_t mod_date;
+	uint32_t crc32;
+	uint32_t compressed_size, uncompressed_size;
+	uint16_t filename_length, extra_field_length;
+	/* - [filename_length] file name
+	 * - [extra_field_length] extra field
+	 */
+};
+
+#pragma pack()