From b23a3ab831f91553d34a48f51370ed9525de07ac Mon Sep 17 00:00:00 2001 From: zlago Date: Tue, 24 Sep 2024 20:54:48 +0200 Subject: initial commit --- src/loader.c | 368 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 368 insertions(+) create mode 100644 src/loader.c (limited to 'src/loader.c') 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 // malloc +#include // fprintf, stderr +#include // memcmp +#include +#include + +#include + +#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; +} -- cgit 1.4.1-2-gfad0