#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; }