summary refs log tree commit diff
path: root/src/loader.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/loader.c')
-rw-r--r--src/loader.c368
1 files changed, 368 insertions, 0 deletions
diff --git a/src/loader.c b/src/loader.c
new file mode 100644
index 0000000..aa599bd
--- /dev/null
+++ b/src/loader.c
@@ -0,0 +1,368 @@
+#include <stdlib.h> // malloc
+#include <stdio.h> // fprintf, stderr
+#include <string.h> // memcmp
+#include <zlib.h>
+#include <stdbool.h>
+
+#include <SDL2/SDL.h>
+
+#include "libplum.h"
+#include "zip.h"
+#include "loader.h"
+#include "util.h"
+#include "main.h"
+
+static void *inflateWrapped(void *const restrict data, uint32_t const outsize);
+
+typedef enum {
+	FILE_EXT_UNKNOWN,
+	FILE_EXT_TEXTURE,
+	FILE_EXT_MAP,
+} ext_T;
+
+ext_T get_extension_type(char *extension, int length);
+
+struct chaos { // why "chaos"? idk, naming variables is hard
+	name_T name;
+	ext_T extension;
+	void *data;
+	size_t size;
+};
+
+struct blob_collection {
+	size_t count;
+	struct blob_item {
+		struct blob blob;
+		name_T name;
+	} items[];
+} *textures = NULL, *maps = NULL;
+
+static int name_compare(name_T a, name_T b) {
+	return strcmp(a, b);
+	// return (a > b) - (a < b);
+}
+
+// like binary search, but its made for finding where to insert a new member
+void *bsearchinsertposition(const void *key, void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *), int *found) {
+	int L = 0, R = nmemb - 1, result, member;
+	if (nmemb == 0) {
+		if (found != NULL) {
+			*found = 0;
+		}
+		return base;
+	}
+	while (L != R) {
+		member = (L + R + 1) / 2;
+		result = compar(((char *) base) + member * size, key);
+		if (result > 0) {
+			R = member - 1;
+		} else {
+			L = member;
+		}
+	}
+	result = compar(((char *) base) + L * size, key);
+	if (found != NULL) {
+		*found = !result;
+	}
+	if (result < 0) {
+		return ((char *) base) + (L + 1) * size;
+	} else {
+		return ((char *) base) + L * size;
+	}
+}
+
+static struct blob_collection *res_init_blobs(void) {
+	struct blob_collection *collection = malloc(sizeof (struct blob_collection));
+	collection->count = 0;
+	return collection;
+}
+
+void res_init_texture(void) {
+	textures = res_init_blobs();
+}
+
+void res_init_map(void) {
+	maps = res_init_blobs();
+}
+
+void res_free_texture(void) {
+	for (size_t i = 0; i < textures->count; i++) {
+		SDL_DestroyTexture(textures->items[i].blob.data);
+		free(textures->items[i].name);
+	}
+	free(textures);
+	textures = NULL;
+}
+
+static void res_free_blobs(struct blob_collection *collection) {
+	for (size_t i = 0; i < collection->count; i++) {
+		free(collection->items[i].blob.data);
+		free(collection->items[i].name);
+	}
+	free(collection);
+}
+
+void res_free_map(void) {
+	res_free_blobs(maps);
+	maps = NULL;
+}
+
+static int res_compare_blobs(void const *a, void const *b) {
+	struct blob_item const *aa = a, *bb = b;
+	return name_compare(aa->name, bb->name);
+}
+
+static struct blob res_get_blob(name_T const name, struct blob_collection *collection) {
+	struct blob_item new = {.name = name};
+	struct blob_item *dst = bsearch(&new, collection->items, collection->count, sizeof (struct blob_item), res_compare_blobs);
+	#if 0
+	fprintf(stderr, "== chunk %zu ==\n", dst - collection->items);
+	for (int i = 0; i < collection->count; i++) {
+		fprintf(stderr, "= %s, %p\n", collection->items[i].name, collection->items[i].blob.data);
+	}
+	if (dst != NULL)
+		fprintf(stderr, "== value %p ==\n", dst->blob.data);
+	else
+		fprintf(stderr, "== not found ==\n");
+	#endif
+	if (dst == NULL) {
+		return (struct blob) {.data = NULL, .size = 0};
+	}
+	return dst->blob;
+}
+
+struct blob res_get_texture(name_T const name) {
+	return res_get_blob(name, textures);
+}
+
+struct blob res_get_map(name_T const name) {
+	return res_get_blob(name, maps);
+}
+
+static struct blob_collection *res_push_blob(struct blob *blob, name_T name, struct blob_collection *collection, void (*freeFunc)(void *)) {
+	int found;
+	struct blob_item new = {.blob = *blob, .name = name};
+	struct blob_item *dst = bsearchinsertposition(&new, collection->items, collection->count, sizeof (struct blob_item), res_compare_blobs, &found);
+	if (found) {
+		fprintf(stderr, "warn: name collision: %s\n", new.name);
+		freeFunc(dst);
+		memcpy(dst, &new, sizeof (struct blob_item));
+	} else {
+		size_t index = dst - collection->items; // hack
+		collection->count++;
+		collection = realloc(collection, sizeof (struct blob_collection) + sizeof (struct blob_item) * collection->count);
+		dst = collection->items + index; // hack for the struct having been reallocated
+		#if 0
+		fprintf(stderr, "info: no collision: %s\n", new.name);
+		#endif
+		memmove(dst + 1, dst, sizeof (struct blob_item) * (collection->count - 1 - (dst - collection->items)));
+		memcpy(dst, &new, sizeof (struct blob_item));
+	}
+	return collection;
+}
+
+static void texture_free_func(void *ptr) {
+	struct blob_item *a = ptr;
+	SDL_DestroyTexture(a->blob.data);
+	free(a->name);
+}
+
+void res_push_texture(struct blob *blob, name_T name) {
+	textures = res_push_blob(blob, name, textures, texture_free_func);
+}
+
+static void blob_free_func(void *ptr) {
+	struct blob_item *a = ptr;
+	free(a->blob.data);
+	free(a->name);
+}
+
+void res_push_map(struct blob *blob, name_T name) {
+	maps = res_push_blob(blob, name, maps, blob_free_func);
+}
+
+int loadResources(char *filename) {
+	// open the file
+	FILE *zip = fopen(filename, "rb");
+	if (zip == NULL) {
+		perror("fopen");
+		return EXIT_FAILURE;
+	}
+
+	// load it into memory
+	size_t size;
+	char *file = util_loadFile(zip, &size);
+	if (file == NULL) {
+		perror("reading file"); // "opening file: Succeeded"
+		return EXIT_FAILURE; // "process returned with status 1" id love to see that happen
+	}
+	fclose(zip);
+
+	// index the archive
+	int error;
+	struct zip_file *headers = zip_index(file, size, &error);
+	if (headers == NULL) {
+		fprintf(stderr, "%s\n", zip_error(error));
+		return EXIT_FAILURE;
+	}
+
+	size_t const file_count = headers->file_count;
+	struct chaos *files = malloc(sizeof (struct chaos) * file_count);
+	
+	for (size_t i = 0; i < file_count; i++) {
+		struct zip_index const *zip_file = headers->files + i;
+		struct chaos *chaos_file = files + i;
+		
+		size_t j = zip_file->filename_length;
+		for (; j > 0 && zip_file->filename[j] != '.'; j--)
+			;
+		if (j == 0) {
+			continue;
+		}
+		
+		chaos_file->name = malloc(j + 1);
+		memcpy(chaos_file->name, zip_file->filename, j);
+		chaos_file->name[j] = 0;
+		
+		chaos_file->extension = get_extension_type(zip_file->filename + j + 1, zip_file->filename_length - j - 1);
+		
+		#if 0
+		fprintf(stderr, "%s: %i\n", chaos_file->name, chaos_file->extension);
+		#endif
+		
+		switch (zip_file->method) {
+			case 0: // STORE
+				chaos_file->data = malloc(zip_file->original_size);
+				memcpy(chaos_file->data, zip_file->data, zip_file->original_size);
+				chaos_file->size = zip_file->original_size;
+				break;
+			
+			case 8: // DEFLATE
+				chaos_file->data = inflateWrapped(zip_file->data, zip_file->original_size);
+				if (chaos_file->data == NULL)
+					return EXIT_FAILURE;
+				chaos_file->size = zip_file->original_size;
+				break;
+			
+			default:
+				return EXIT_FAILURE;
+		}
+		
+		switch (chaos_file->extension) {
+			struct blob blob;
+			
+			case FILE_EXT_TEXTURE:;
+				unsigned error;
+				struct plum_image *image = plum_load_image(chaos_file->data, chaos_file->size, PLUM_COLOR_32 | PLUM_ALPHA_INVERT, &error);
+				free(chaos_file->data);
+				if (image == NULL) {
+					free(chaos_file->name);
+					fprintf(stderr, "error: libplum: %s\n", plum_get_error_text(error));
+					break;
+				}
+				SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(image->data, image->width, image->height, 32, image->width * 4, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
+				if (surface == NULL) {
+					free(chaos_file->name);
+					plum_destroy_image(image);
+					fprintf(stderr, "error: SDL2: %s\n", SDL_GetError());
+					break;
+				}
+				SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
+				SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
+				SDL_FreeSurface(surface);
+				plum_destroy_image(image);
+				if (texture == NULL) {
+					free(chaos_file->name);
+					fprintf(stderr, "error: SDL2: %s\n", SDL_GetError());
+					break;
+				}
+				
+				blob = (struct blob) {.data = texture, .size = 0};
+				res_push_texture(&blob, chaos_file->name);
+				break;
+			
+			case FILE_EXT_MAP:
+				blob = (struct blob) {.data = chaos_file->data, .size = chaos_file->size};
+				res_push_map(&blob, chaos_file->name);
+				break;
+			
+			default:
+				free(chaos_file->data), chaos_file->data = NULL;
+				free(chaos_file->name), chaos_file->name = NULL;
+				break;
+		}
+	}
+	
+	free(file);
+	free(headers);
+	
+	free(files);
+
+	return EXIT_SUCCESS;
+}
+
+ext_T get_extension_type(char *extension, int length) {
+	#define ext(str, en) \
+		if (length == sizeof (str) - 1 && \
+		memcmp(extension, str, sizeof (str) - 1) == 0) \
+		return en
+	ext("png", FILE_EXT_TEXTURE);
+	ext("jpg", FILE_EXT_TEXTURE);
+	ext("gif", FILE_EXT_TEXTURE);
+	ext("map", FILE_EXT_MAP);
+	return FILE_EXT_UNKNOWN;
+	#undef ext
+}
+
+static void *inflateWrapped(void *const restrict data, uint32_t const outsize) {
+	z_stream stream = {
+		.zalloc = Z_NULL,
+		.zfree = Z_NULL,
+		.opaque = Z_NULL,
+	}; // zlib docs require you to set these 3 to Z_NULL, apparently
+	stream.avail_in = outsize; // hope itll work! hehe
+	stream.next_in = data;
+	int ret = inflateInit2(&stream, -15); // -15 is raw DEFLATE, we dont want zlib + DEFLATE :p
+	if (ret != Z_OK) {
+		return NULL;
+	}
+	unsigned char *buf = malloc(outsize);
+	if (buf == NULL) {
+		perror("malloc");
+		goto finally; // would it hurt to change it to error? free(NULL) is defined to be a no-op
+	}
+	stream.avail_out = outsize;
+	stream.next_out = buf;
+	
+	switch ((ret = inflate(&stream, Z_SYNC_FLUSH))) {
+		case Z_NEED_DICT:
+		case Z_DATA_ERROR:
+			//fputs("inflate: data error\n", stderr);
+			goto error;
+		case Z_MEM_ERROR:
+			//fputs("inflate: memory error\n", stderr);
+			goto error;
+		case Z_STREAM_ERROR:
+			//fputs("inflate: stream error\n", stderr);
+			goto error;
+		case Z_OK:
+			//fputs("inflate: stream didnt end", stderr);
+			goto error;
+		
+		case Z_STREAM_END:
+			goto finally;
+		
+		default:
+			//fprintf(stderr, "unrecognised return value %u\n", ret);
+			goto error;
+	}
+	
+	error: // all cases share the error handling code
+	free(buf);
+	buf = NULL; // return value
+	
+	finally:
+	(void) inflateEnd(&stream);
+	//fprintf(stderr, "processed %lu (0x%lx) bytes total, exit status %d\n", stream.total_out, stream.total_out, ret);
+	return buf;
+}