summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/entity.h2
-rw-r--r--src/funsinit.c7
-rw-r--r--src/loader.c106
-rw-r--r--src/loader.h11
-rw-r--r--src/main.c52
-rw-r--r--src/player.c2
-rw-r--r--utl/json2map/common.c21
-rw-r--r--utl/json2map/common.h4
-rw-r--r--utl/json2map/main.c266
9 files changed, 395 insertions, 76 deletions
diff --git a/src/entity.h b/src/entity.h
index 52a2eac..354fbc1 100644
--- a/src/entity.h
+++ b/src/entity.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "loader.h"
+#include <SDL2/SDL.h>
 
 #define from_fixed(a) (a / 16)
 #define to_fixed(a) (a * 16)
diff --git a/src/funsinit.c b/src/funsinit.c
new file mode 100644
index 0000000..5d15cc4
--- /dev/null
+++ b/src/funsinit.c
@@ -0,0 +1,7 @@
+#include "entity.h"
+#include "loader.h"
+#include "main.h"
+
+
+void funs_init(void) {
+}
diff --git a/src/loader.c b/src/loader.c
index 6a50341..3b39bca 100644
--- a/src/loader.c
+++ b/src/loader.c
@@ -39,6 +39,22 @@ struct blob_collection {
 	} items[];
 } *textures = NULL, *maps = NULL, *collisions = NULL;
 
+struct newfun_collection {
+	size_t count;
+	struct newfun_item {
+		struct entity *(*fun)(void);
+		name_T name;
+	} *items;
+} newfuns = {0, NULL};
+
+struct setfun_collection {
+	size_t count;
+	struct setfun_item {
+		int (*fun)(struct entity *const restrict self, char const *const restrict key, char const *const restrict value);
+		name_T name;
+	} *items;
+} setfuns = {0, NULL};
+
 static int name_compare(name_T a, name_T b) {
 	return strcmp(a, b);
 	// return (a > b) - (a < b);
@@ -124,24 +140,40 @@ void res_free_collision(void) {
 	collisions = NULL;
 }
 
+void res_free_newfun(void) {
+	for (size_t i = 0; i < newfuns.count; i++) {
+		free(newfuns.items[i].name);
+	}
+	free(newfuns.items);
+	newfuns.count = 0;
+}
+
+void res_free_setfun(void) {
+	for (size_t i = 0; i < setfuns.count; i++) {
+		free(setfuns.items[i].name);
+	}
+	free(setfuns.items);
+	setfuns.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_newfuns(void const *a, void const *b) {
+	struct newfun_item const *aa = a, *bb = b;
+	return name_compare(aa->name, bb->name);
+}
+
+static int res_compare_setfuns(void const *a, void const *b) {
+	struct setfun_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};
 	}
@@ -160,6 +192,24 @@ struct blob res_get_collision(name_T const name) {
 	return res_get_blob(name, collisions);
 }
 
+struct entity *(*res_get_newfun(name_T const name))(void) {
+	struct newfun_item new = {.name = name};
+	struct newfun_item *dst = bsearch(&new, newfuns.items, newfuns.count, sizeof (struct newfun_item), res_compare_newfuns);
+	if (dst == NULL) {
+		return NULL;
+	}
+	return dst->fun;
+}
+
+int (*res_get_setfun(name_T const name))(struct entity *const restrict self, char const *const restrict key, char const *const restrict value) {
+	struct setfun_item new = {.name = name};
+	struct setfun_item *dst = bsearch(&new, setfuns.items, setfuns.count, sizeof (struct setfun_item), res_compare_setfuns);
+	if (dst == NULL) {
+		return NULL;
+	}
+	return dst->fun;
+}
+
 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};
@@ -206,6 +256,42 @@ void res_push_collision(struct blob *blob, name_T name) {
 	collisions = res_push_blob(blob, name, collisions, blob_free_func);
 }
 
+void res_push_newfun(struct entity *(*newfun)(void), name_T name) {
+	int found;
+	struct newfun_item new = {.fun = newfun, .name = name};
+	struct newfun_item *dst = bsearchinsertposition(&new, newfuns.items, newfuns.count, sizeof (struct newfun_item), res_compare_newfuns, &found);
+	if (found) {
+		fprintf(stderr, "warn: name collision: %s\n", new.name);
+		free(dst->name);
+		memcpy(dst, &new, sizeof (struct newfun_item));
+	} else {
+		size_t index = dst - newfuns.items; // hack
+		newfuns.count++;
+		newfuns.items = realloc(newfuns.items, sizeof (struct newfun_item) * newfuns.count);
+		dst = newfuns.items + index; // hack for the struct having been reallocated
+		memmove(dst + 1, dst, sizeof (struct newfun_item) * (newfuns.count - 1 - (dst - newfuns.items)));
+		memcpy(dst, &new, sizeof (struct newfun_item));
+	}
+}
+
+void res_push_setfun(int (*setfun)(struct entity *const restrict self, char const *const restrict key, char const *const restrict value), name_T name) {
+	int found;
+	struct setfun_item new = {.fun = setfun, .name = name};
+	struct setfun_item *dst = bsearchinsertposition(&new, setfuns.items, setfuns.count, sizeof (struct setfun_item), res_compare_setfuns, &found);
+	if (found) {
+		fprintf(stderr, "warn: name collision: %s\n", new.name);
+		free(dst->name);
+		memcpy(dst, &new, sizeof (struct setfun_item));
+	} else {
+		size_t index = dst - setfuns.items; // hack
+		setfuns.count++;
+		setfuns.items = realloc(setfuns.items, sizeof (struct setfun_item) * setfuns.count);
+		dst = setfuns.items + index; // hack for the struct having been reallocated
+		memmove(dst + 1, dst, sizeof (struct setfun_item) * (setfuns.count - 1 - (dst - setfuns.items)));
+		memcpy(dst, &new, sizeof (struct setfun_item));
+	}
+}
+
 int loadResources(char *filename) {
 	// open the file
 	FILE *zip = fopen(filename, "rb");
diff --git a/src/loader.h b/src/loader.h
index b1563bb..016b8b2 100644
--- a/src/loader.h
+++ b/src/loader.h
@@ -1,5 +1,8 @@
 #pragma once
 
+#include <stddef.h>
+#include "entity.h"
+
 typedef char * name_T;
 
 struct blob {
@@ -21,4 +24,12 @@ void res_init_collision(void);
 void res_free_collision(void);
 struct blob res_get_collision(name_T const name);
 
+void res_free_newfun(void);
+void res_push_newfun(struct entity *(*newfun)(void), name_T name);
+struct entity *(*res_get_newfun(name_T const name))(void);
+
+void res_free_setfun(void);
+void res_push_setfun(int (*setfun)(struct entity *const restrict self, char const *const restrict key, char const *const restrict value), name_T name);
+int (*res_get_setfun(name_T const name))(struct entity *const restrict self, char const *const restrict key, char const *const restrict value);
+
 int loadResources(char *filename);
diff --git a/src/main.c b/src/main.c
index 6b195b4..8fc400f 100644
--- a/src/main.c
+++ b/src/main.c
@@ -39,6 +39,8 @@ struct entity player[1] = {0};
 struct entity *player_new(void);
 int player_property(struct entity *const restrict entity, char const *const restrict property, char const *const restrict value);
 
+void funs_init(void);
+
 int main(int const argc, char *const *const argv) {
 	struct option opts[] = {
 		{"help",       0, NULL, 'h'},
@@ -156,6 +158,7 @@ int main(int const argc, char *const *const argv) {
 	
 	{
 		res_init();
+		funs_init();
 		if (optind == argc) {
 			void *a = util_executableRelativePath("assets.res", *argv, 0);
 			puts(a);
@@ -175,6 +178,55 @@ int main(int const argc, char *const *const argv) {
 		}
 		struct blob map = res_get_map("untitled");
 		tilemap = tilemap_load(map.data, map.size);
+		char *a = (char *) map.data + tilemap->input_bytes;
+		map.size -= tilemap->input_bytes;
+		while (map.size) {
+			size_t len;
+			if ((len = strnlen(a, map.size)) == map.size) {
+				return 1; // hack
+			}
+			char *name = a;
+			puts(name);
+			struct entity *(*fun)(void) = res_get_newfun(name);
+			a += len + 1;
+			map.size -= len + 1;
+			if (fun) {
+				struct entity *entity = fun();
+				while (1) {
+					if ((len = strnlen(a, map.size)) == map.size) {
+						return 1; // hack
+					} else if (len == 0) {
+						a++;
+						map.size--;
+						break;
+					}
+					char *key = a;
+					a += len + 1;
+					map.size -= len + 1;
+					if ((len = strnlen(a, map.size)) == map.size) {
+						return 1; // hack
+					}
+					char *value = a;
+					a += len + 1;
+					map.size -= len + 1;
+					if (res_get_setfun(name)(entity, key, value)) {
+						return 1; // hack
+					}
+				}
+			} else {
+				while (1) { // hack
+					if ((len = strnlen(a, map.size)) == map.size) { // hack
+						return 1; // hack
+					} else if (len == 0) {
+						a++;
+						map.size--;
+						break;
+					}
+					a += len + 1;
+					map.size -= len + 1;
+				}
+			}
+		}
 		//memmem(map.data + tilemap->byte_count, map.size - tilemap->byte_count, (char []) {0, 0}, 2);
 		printf("load_tilemap %p\n", (void *) tilemap);
 		SDL_SetRenderTarget(renderer, NULL);
diff --git a/src/player.c b/src/player.c
index 9efd0ac..b23b2f0 100644
--- a/src/player.c
+++ b/src/player.c
@@ -1,5 +1,6 @@
 #include "main.h"
 #include "entity.h"
+#include "loader.h"
 #include "input.h"
 #include "tilemap.h"
 #include <stdbool.h>
@@ -119,6 +120,7 @@ void anim(struct entity *self, unsigned anim) {
 
 static int player_update(struct entity *self) {
 	if (self->hp <= 0) {
+		self->state = 0;
 		return 1;
 	}
 	switch (self->state) {
diff --git a/utl/json2map/common.c b/utl/json2map/common.c
index c401fc1..8c2c4c2 100644
--- a/utl/json2map/common.c
+++ b/utl/json2map/common.c
@@ -1,7 +1,28 @@
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include "common.h"
 
+struct blob blob_new(void) {
+	return (struct blob) {.data = NULL, .size = 0};
+}
+
+void blob_append(struct blob *const blob, void const *const data, size_t const size) {
+	char *new = realloc(blob->data, blob->size + size);
+	if (new == NULL) {
+		abort();
+	}
+	memcpy(new + blob->size, data, size);
+	blob->data = new;
+	blob->size += size;
+}
+
+void blob_free(struct blob *const blob) {
+	free(blob->data);
+	blob->data = NULL;
+	blob->size = 0;
+}
+
 struct blob load_file(char const *const name) {
 	const size_t START_SIZE = 8192;
 	FILE *file = fopen(name, "rb");
diff --git a/utl/json2map/common.h b/utl/json2map/common.h
index 73e3240..e5a7590 100644
--- a/utl/json2map/common.h
+++ b/utl/json2map/common.h
@@ -5,4 +5,8 @@ struct blob {
 	size_t size;
 };
 
+struct blob blob_new(void);
+void blob_append(struct blob *const blob, void const *const data, size_t const size);
+void blob_free(struct blob *const blob);
+
 struct blob load_file(char const *const name);
diff --git a/utl/json2map/main.c b/utl/json2map/main.c
index 684e0ed..9ffa9b9 100644
--- a/utl/json2map/main.c
+++ b/utl/json2map/main.c
@@ -21,7 +21,7 @@ struct PACKED sets {
 };
 
 struct PACKED map {
-	struct {
+	struct color {
 		uint8_t r, g, b, a;
 	} backdrop;
 	uint32_t width;
@@ -91,6 +91,56 @@ size_t safe_write(void const *restrict buf, size_t size, FILE *restrict stream)
 	return fwrite(buf, 1, size, stream);
 }
 
+int string_to_color(struct color *const restrict color, char const *const restrict string) {
+	if (string[0] != '#') {
+		fprintf(stderr, "color %s: missing a hash\n", string);
+		return 1;
+	}
+	char *end = NULL;
+	unsigned const value = strtoul(string + 1, &end, 16);
+	size_t const len = strlen(string);
+	if (end != string + len) {
+		fprintf(stderr, "color %s: not a color string\n", string);
+		return 1;
+	}
+	switch (len) {
+		case 9:
+			color->a = value >> 24;
+			break;
+		case 7:
+			color->a = 0xff;
+			break;
+		default:
+			fprintf(stderr, "color %s: not a color string\n", string);
+			return 1;
+	}
+	color->r = value >> 16;
+	color->g = value >> 8;
+	color->b = value >> 0;
+	return 0;
+}
+
+// will turn, say, example/file.name into just file
+// or another/example/file.with.multiple.dots into file.with.multiple
+struct blob strip_filename(char *str) {
+	size_t length = strlen(str);
+	char *temp, *start = str;
+	while ((temp = memchr(start, '/', length))) {
+		temp++;
+		length -= temp - start;
+		start = temp;
+	}
+	char *data = start;
+	char *end = data + length;
+	while ((temp = memchr(data, '.', length))) {
+		end = temp;
+		temp++;
+		length -= temp - data;
+		data = temp;
+	}
+	return (struct blob) {.data = start, .size = end - start};
+}
+
 int main(int argc, char **argv) {
 	if (argc != 2 && argc != 3) {
 		return EXIT_FAILURE;
@@ -124,8 +174,7 @@ int main(int argc, char **argv) {
 
 	char *wang_tileset = NULL;
 	uint32_t wang_height;
-	char *tilesets_data = NULL;
-	size_t tilesets_size = 0;
+	struct blob tilesets_blob = blob_new();
 	struct sets counts = {MAGIC, 0, 0};
 	
 	cJSON_ArrayForEach(tileset, tilesets) {
@@ -163,28 +212,13 @@ int main(int argc, char **argv) {
 		}
 		cJSON const *image = cJSON_GetObjectItemCaseSensitive(tileset, "image");
 		{
-			char const *const str = image->valuestring;
-			size_t start = 0, length = strlen(str);
-			for (size_t i = 0; i < length; i++) {
-				if (str[i] == '/') {
-					start = i + 1;
-				}
-			}
-			length -= start;
-			for (size_t i = length; i > 0; i--) {
-				if (str[start + i] == '.') {
-					length = i;
-					break;
-				}
-			}
+			struct blob fragment = strip_filename(image->valuestring);
 			//printf("%.*s\n", (int) length, str + start);
 			uint32_t height = cJSON_GetObjectItemCaseSensitive(tileset, "imageheight")->valueint;
-			tilesets_data = realloc(tilesets_data, tilesets_size + length + sizeof (uint32_t) + 1);
-			memcpy(tilesets_data + tilesets_size, &height, sizeof (uint32_t));
-			memcpy(tilesets_data + tilesets_size + sizeof (uint32_t), str + start, length);
-			tilesets_data[tilesets_size + sizeof (uint32_t) + length] = 0;
+			blob_append(&tilesets_blob, &height, sizeof (uint32_t));
+			blob_append(&tilesets_blob, fragment.data, fragment.size);
+			blob_append(&tilesets_blob, &(char) {0}, 1);
 			//puts(tilesets_data + tilesets_size + sizeof (uint32_t));
-			tilesets_size += length + sizeof (uint32_t) + 1;
 			counts.tilesets_count++;
 		}
 		//puts(image->valuestring);
@@ -196,24 +230,11 @@ int main(int argc, char **argv) {
 		wang_continue:;
 	}
 	if (counts.wang_count == 1) {
-		char const *const str = wang_tileset;
-		size_t start = 0, length = strlen(str);
-		for (size_t i = 0; i < length; i++) {
-			if (str[i] == '/') {
-				start = i + 1;
-			}
-		}
-		length -= start;
-		for (size_t i = length; i > 0; i--) {
-			if (str[start + i] == '.') {
-				length = i;
-				break;
-			}
-		}
+		struct blob fragment = strip_filename(wang_tileset);
 		//printf("%.*s\n", (int) length, str + start);
-		wang_tileset = malloc(length + 1);
-		memcpy(wang_tileset, str + start, length);
-		wang_tileset[length] = 0;
+		wang_tileset = malloc(fragment.size + 1);
+		memcpy(wang_tileset, fragment.data, fragment.size);
+		wang_tileset[fragment.size] = 0;
 		//puts(wang_tileset);
 	}
 
@@ -235,29 +256,14 @@ int main(int argc, char **argv) {
 
 	cJSON const *backdrop = cJSON_GetObjectItemCaseSensitive(json, "backgroundcolor");
 	if (cJSON_IsString(backdrop)) {
-		switch (strlen(backdrop->valuestring)) {
-			unsigned value;
-			case 9:
-				value = strtoul(backdrop->valuestring + 1, NULL, 16);
-				map.backdrop.r = value >> 16;
-				map.backdrop.g = value >> 8;
-				map.backdrop.b = value >> 0;
-				map.backdrop.a = value >> 24;
-				break;
-			case 7:
-				value = strtoul(backdrop->valuestring + 1, NULL, 16);
-				map.backdrop.r = value >> 16;
-				map.backdrop.g = value >> 8;
-				map.backdrop.b = value >> 0;
-				map.backdrop.a = 0xff;
-				break;
-			default:
-				fputs("incorrect backdrop color format\n", stderr);
-				return 1;
+		if (string_to_color(&map.backdrop, backdrop->valuestring)) {
+			return 1;
 		}
 	}
 	//printf("sky: # %02x %02x %02x %02x\n", map.backdrop.r, map.backdrop.g, map.backdrop.b, map.backdrop.a);
 	
+	struct blob entities = blob_new();
+	
 	cJSON const *layers = cJSON_GetObjectItemCaseSensitive(json, "layers");
 	cJSON const *layer = NULL;
 	cJSON_ArrayForEach (layer, layers) {
@@ -365,6 +371,127 @@ int main(int argc, char **argv) {
 			puts(str);
 			free(str);
 			#endif
+		} else if (strcmp(type, "object")) {
+			// object layer
+			cJSON const *object = NULL;
+			cJSON_ArrayForEach(object, cJSON_GetObjectItemCaseSensitive(layer, "objects")) {
+				struct blob entity = blob_new();
+				
+				cJSON const *property = cJSON_GetObjectItemCaseSensitive(object, "type");
+				blob_append(&entity, property->valuestring, strlen(property->valuestring) + 1);
+				//printf("%s:", property->valuestring);
+				
+				char const *const *default_props = (char const *const[]) {"x", "y", "width", "height", NULL};
+				if (cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(object, "point"))) {
+					default_props = (char const *const[]) {"x", "y", NULL};
+				}
+				for (int i = 0; default_props[i] != NULL; i++) {
+					property = cJSON_GetObjectItemCaseSensitive(object, default_props[i]);
+					if (property == NULL) {
+						continue;
+					}
+					
+					blob_append(&entity, default_props[i], strlen(default_props[i]) + 1);
+					if (cJSON_IsNumber(property)) {
+						char buf[32];
+						sprintf(buf, "%i", property->valueint);
+						blob_append(&entity, buf, strlen(buf) + 1);
+						//printf(" %s=%i", default_props[i], property->valueint);
+					} else if (cJSON_IsString(property)) {
+						blob_append(&entity, property->valuestring, strlen(property->valuestring) + 1);
+						//printf(" %s=%s", default_props[i], property->valuestring);
+					} else {
+						char *str = cJSON_Print(property);
+						puts(str);
+						free(str);
+						return 1;
+					}
+				}
+				cJSON_ArrayForEach(property, cJSON_GetObjectItemCaseSensitive(object, "properties")) {
+					char const *const name = cJSON_GetObjectItemCaseSensitive(property, "name")->valuestring;
+					char const *const type = cJSON_GetObjectItemCaseSensitive(property, "type")->valuestring;
+					cJSON const *value_obj = cJSON_GetObjectItemCaseSensitive(property, "value");
+
+					blob_append(&entity, name, strlen(name) + 1);
+					if (strcmp(type, "string") == 0) {
+						if (!cJSON_IsString(value_obj)) {
+							fprintf(stderr, "%s: not actually a %s\n", name, type);
+							return 1;
+						}
+						char const *const value = value_obj->valuestring;
+						blob_append(&entity, value, strlen(value) + 1);
+						//printf(" %s='%s'", name, value);
+					} else if (strcmp(type, "file") == 0) {
+						if (!cJSON_IsString(value_obj)) {
+							fprintf(stderr, "%s: not actually a %s\n", name, type);
+							return 1;
+						}
+						struct blob const fragment = strip_filename(value_obj->valuestring);
+						blob_append(&entity, fragment.data, fragment.size);
+						blob_append(&entity, &(char) {0}, 1);
+						//printf(" %s='%.*s'", name, fragment.size, fragment.data);
+					} else if (strcmp(type, "bool") == 0) {
+						if (!cJSON_IsBool(value_obj)) {
+							fprintf(stderr, "%s: not actually a %s\n", name, type);
+							return 1;
+						}
+						if (cJSON_IsTrue(value_obj)) {
+							blob_append(&entity, "true", sizeof ("true"));
+							//printf(" %s=true", name);
+						} else {
+							blob_append(&entity, "false", sizeof ("false"));
+							//printf(" %s=false", name);
+						}
+					} else if (strcmp(type, "color") == 0) {
+						if (!cJSON_IsString(value_obj)) {
+							fprintf(stderr, "%s: not actually a %s\n", name, type);
+							return 1;
+						}
+						struct color color;
+						char const *const value = value_obj->valuestring;
+						string_to_color(&color, value);
+						char buf[10];
+						sprintf(buf, "#%02x%02x%02x%02x", color.r, color.g, color.b, color.a);
+						blob_append(&entity, buf, strlen(buf) + 1);
+						//printf(" %s='%s'", name, buf);
+					} else if (strcmp(type, "int") == 0) {
+						if (!cJSON_IsNumber(value_obj)) {
+							fprintf(stderr, "%s: not actually a %s\n", name, type);
+							return 1;
+						}
+					
+						char buf[32];
+						sprintf(buf, "%i", value_obj->valueint);
+						blob_append(&entity, buf, strlen(buf) + 1);
+						//printf(" %s=%s", name, buf);
+					} else if (strcmp(type, "float") == 0) {
+						if (!cJSON_IsNumber(value_obj)) {
+							fprintf(stderr, "%s: not actually a %s\n", name, type);
+							return 1;
+						}
+					
+						char buf[32];
+						sprintf(buf, "%a", value_obj->valuedouble);
+						blob_append(&entity, buf, strlen(buf) + 1);
+						//printf(" %s=%s", name, buf);
+					} else if (strcmp(type, "object") == 0) {
+						fputs("object properties are not supported\n", stderr);
+						return 1;
+					} else {
+						fprintf(stderr, "%s: dont know what '%s' is\n", name, type);
+						return 1;
+					}
+				}
+				blob_append(&entity, &(char) {0}, 1);
+				//putchar('\n');
+				#if 0
+				char *str = cJSON_Print(property);
+				puts(str);
+				free(str);
+				#endif
+				blob_append(&entities, entity.data, entity.size);
+				blob_free(&entity);
+			}
 		} else {
 			// ??? layer
 		}
@@ -384,17 +511,26 @@ int main(int argc, char **argv) {
 		}
 	}
 	
-	fwrite(&counts, 1, sizeof (counts), outfile);
-	fwrite(tilesets_data, 1, tilesets_size, outfile);
+	safe_write(&counts, sizeof (counts), outfile);
+	safe_write(tilesets_blob.data, tilesets_blob.size, outfile);
+	blob_free(&tilesets_blob);
+	
 	if (wang_tileset != NULL) {
-		fwrite(&wang_height, 1, sizeof (uint32_t), outfile);
-		fwrite(wang_tileset, 1, strlen(wang_tileset) + 1, outfile);
+		safe_write(&wang_height, sizeof (uint32_t), outfile);
+		safe_write(wang_tileset, strlen(wang_tileset) + 1, outfile);
+		free(wang_tileset);
 	}
-	fwrite(&map, 1, sizeof (map), outfile);
-	fwrite(tile_data, 1, map.width * map.height * map.layers, outfile);
-	fwrite(parallaxes, 1, sizeof (float) * 2 * map.layers, outfile);
 	
+	safe_write(&map, sizeof (map), outfile);
+	safe_write(tile_data, map.width * map.height * map.layers, outfile);
 	free(tile_data);
+	
+	safe_write(parallaxes, sizeof (float) * 2 * map.layers, outfile);
+	free(parallaxes);
+	
+	safe_write(entities.data, entities.size, outfile);
+	blob_free(&entities);
+	
 	end:
 	if (outfile != NULL) {
 		fclose(outfile);