#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" #include "collision.h" static void *inflateWrapped(void *const restrict data, uint32_t const outsize); typedef enum { FILE_EXT_UNKNOWN, FILE_EXT_TEXTURE, FILE_EXT_MAP, FILE_EXT_COLLISION, } 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, *collisions = NULL; struct fun_collection { size_t count; struct fun_item { struct funs funs; name_T name; } *items; } funs = {0, 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_init_collision(void) { collisions = res_init_blobs(); } void res_init(void) { res_init_texture(); res_init_map(); res_init_collision(); } 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; } void res_free_collision(void) { res_free_blobs(maps); collisions = NULL; } void res_free_fun(void) { for (size_t i = 0; i < funs.count; i++) { free(funs.items[i].name); } free(funs.items); funs.count = 0; } 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 int res_compare_funs(void const *a, void const *b) { struct fun_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 (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); } struct blob res_get_collision(name_T const name) { return res_get_blob(name, collisions); } struct funs res_get_fun(name_T const name) { struct fun_item new = {.name = name}; struct fun_item *dst = bsearch(&new, funs.items, funs.count, sizeof (struct fun_item), res_compare_funs); if (dst == NULL) { return (struct funs) {.newfun = NULL, .setfun = NULL}; } return dst->funs; } 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); } void res_push_collision(struct blob *blob, name_T name) { collisions = res_push_blob(blob, name, collisions, blob_free_func); } void res_push_fun(struct entity *(*newfun)(void), int (*setfun)(struct entity *const restrict self, char const *const restrict key, char const *const restrict value), name_T name) { int found; struct fun_item new = {.funs = {.newfun = newfun, .setfun = setfun}, .name = name}; struct fun_item *dst = bsearchinsertposition(&new, funs.items, funs.count, sizeof (struct fun_item), res_compare_funs, &found); if (found) { fprintf(stderr, "warn: name collision: %s\n", new.name); free(dst->name); memcpy(dst, &new, sizeof (struct fun_item)); } else { size_t index = dst - funs.items; // hack funs.count++; funs.items = realloc(funs.items, sizeof (struct fun_item) * funs.count); dst = funs.items + index; // hack for the struct having been reallocated memmove(dst + 1, dst, sizeof (struct fun_item) * (funs.count - 1 - (dst - funs.items))); memcpy(dst, &new, sizeof (struct fun_item)); } } 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((char *) 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; case FILE_EXT_COLLISION:; char const *props = chaos_file->data; size_t remaining = chaos_file->size; if (props[remaining - 1] != '\n') { fprintf(stderr, "warn: will not load %s.col (missing trailing newline)\n", chaos_file->name); free(chaos_file->name); free(chaos_file->data); break; } int tiles = 0; for (size_t i = 0; i < remaining; i++) { if (props[i] == '\n') { tiles++; } } //fprintf(stderr, "allocating %u tiles\n", tiles); collision_T *const data = calloc(tiles, sizeof (collision_T)); for (int tile = 0; tile < tiles; tile++) { char const *tmp = memchr(props, '\n', remaining); size_t tile_remaining = tmp - props; //fprintf(stderr, "tile %u (%zu):", tile, tile_remaining); char const *tile_props = props; props += tile_remaining + 1; while (true) { char const *tile_prop = memchr(tile_props, ',', tile_remaining); char const *const str = tile_props; size_t len; if (tile_prop == NULL) { len = tile_remaining; //fprintf(stderr, " '%.*s'", len, tile_props); //fprintf(stderr, " '%u'", len); } else { len = tile_prop - tile_props; //fprintf(stderr, " '%.*s'", len, tile_props); //fprintf(stderr, " '%u'", len); tile_props += len + 1; tile_remaining -= len + 1; } if (len == 0) { break; } #define t(a, b)\ else if (len == sizeof (a) - 1 && memcmp(str, a, sizeof (a) - 1) == 0) {\ data[tile] |= b;\ } t("solid", 1 << COLLISION_SOLID | 1 << COLLISION_FLOOR) t("hazard", 1 << COLLISION_HAZARD) t("semisolid", 1 << COLLISION_FLOOR) #undef t #if 0 if (len == 5 && memcmp(str, "solid", 5) == 0) { data[tile] |= 1 << COLLISION_SOLID | 1 << COLLISION_FLOOR; } else if (len == 6 && memcmp(str, "hazard", 6) == 0) { data[tile] |= 1 << COLLISION_HAZARD; } else if (len == 9 && memcmp(str, "semisolid", 9) == 0) { data[tile] |= 1 << COLLISION_FLOOR; } #endif else { fprintf(stderr, "warn: %s: unrecognized property '%.*s'\n", chaos_file->name, (int) len, str); } if (tile_prop == NULL) { break; } } //fprintf(stderr, " %b\n", data[tile]); } free(chaos_file->data); blob = (struct blob) {.data = data, .size = tiles}; res_push_collision(&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); ext("col", FILE_EXT_COLLISION); 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; }