summaryrefslogtreecommitdiff
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;
+}