#include #include #include #include #include "cJSON.h" #include "common.h" struct tilemap_T { unsigned width, height; int *tilemap; }; #define PACKED __attribute__((__packed__)) #define MAGIC "TMv1" struct PACKED sets { uint8_t magic[4]; uint32_t tilesets_count; uint32_t wang_count; }; struct PACKED map { struct color { uint8_t r, g, b, a; } backdrop; uint32_t width; uint32_t height; uint32_t layers; uint32_t middleground; uint8_t tiles[]; }; struct tilemap_T make_tilemap(cJSON const *const layer, int const wangStart, int const wangSize) { cJSON const *width_obj = cJSON_GetObjectItemCaseSensitive(layer, "width"); cJSON const *height_obj = cJSON_GetObjectItemCaseSensitive(layer, "height"); if (!cJSON_IsNumber(width_obj) || !cJSON_IsNumber(height_obj)) { fputs("missing tile layer size\n", stderr); return (struct tilemap_T) {0, 0, NULL}; } int const width = width_obj->valueint; int const height = height_obj->valueint; cJSON const *data = cJSON_GetObjectItemCaseSensitive(layer, "data"); int const size = cJSON_GetArraySize(data); if (width * height != size) { fputs("tile data size mismatch", stderr); return (struct tilemap_T) {0, 0, NULL}; } struct tilemap_T in = {.tilemap = NULL, .width = width, .height = height}; in.tilemap = malloc(size * sizeof (int)); cJSON const *tile = NULL; size_t i = 0; cJSON_ArrayForEach (tile, data) { in.tilemap[i] = tile->valueint; i++; } struct tilemap_T out = {.tilemap = NULL, .width = width - 1, .height = height - 1}; out.tilemap = malloc(out.width * out.height * sizeof (int)); //printf("=== tilemap (%d tiles) ===\n", size); for (int y = 0; y < out.height; y++) { for (int x = 0; x < out.width; x++) { int a = in.tilemap[x + y * in.width]; if (a == 0) { out.tilemap[x + y * out.width] = 0; } else if (a < wangStart) { out.tilemap[x + y * out.width] = a - 1; } else if (a > wangStart + wangSize) { out.tilemap[x + y * out.width] = a - wangSize - 1; } else { int b = (in.tilemap[x + y * in.width] - wangStart) >> 4; int c = (in.tilemap[(x + 1) + y * in.width] - wangStart) >> 4; int d = (in.tilemap[x + (y + 1) * in.width] - wangStart) >> 4; int e = (in.tilemap[(x + 1) + (y + 1) * in.width] - wangStart) >> 4; int f = b == c && b == d && b == e; if (f && ((a - wangStart) & 0x8)) { out.tilemap[x + y * out.width] = b + 0xf0; } else { out.tilemap[x + y * out.width] = 0; } } } } free(in.tilemap); return out; } size_t safe_write(void const *restrict buf, size_t size, FILE *restrict stream) { if (stream == NULL) { return 0; } 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; } struct blob file = load_file(argv[1]); cJSON *json = cJSON_ParseWithLength(file.data, file.size); free(file.data); FILE *outfile = NULL; if (argc == 3) { outfile = fopen(argv[2], "wb"); } int status = EXIT_SUCCESS; if (json == NULL) { puts("null"); status = EXIT_FAILURE; goto end; } cJSON const *tilewidth = cJSON_GetObjectItemCaseSensitive(json, "tilewidth"); cJSON const *tileheight = cJSON_GetObjectItemCaseSensitive(json, "tileheight"); if (!cJSON_IsNumber(tilewidth) || !cJSON_IsNumber(tileheight) || tilewidth->valueint != 8 || tileheight->valueint != 8) { status = EXIT_FAILURE; goto end; } cJSON const *tilesets = cJSON_GetObjectItemCaseSensitive(json, "tilesets"); cJSON const *tileset = NULL; int wangStart = 0; int wangSize = 0; char *wang_tileset = NULL; uint32_t wang_height; struct blob tilesets_blob = blob_new(); struct sets counts = {MAGIC, 0, 0}; cJSON_ArrayForEach(tileset, tilesets) { //printf("=== tileset (%% tiles) ===\n"); cJSON const *wangs = cJSON_GetObjectItemCaseSensitive(tileset, "wangsets"); if (wangs != NULL) { cJSON const *wang = NULL; cJSON_ArrayForEach(wang, wangs) { //puts("=== wang ==="); cJSON const *type = cJSON_GetObjectItemCaseSensitive(wang, "type"); if (strcmp("corner", type->valuestring) == 0) { if (wangStart != 0) { fputs("two or more corner sets\n", stderr); status = EXIT_FAILURE; goto end; } wangStart = cJSON_GetObjectItemCaseSensitive(tileset, "firstgid")->valueint; wangSize = cJSON_GetObjectItemCaseSensitive(tileset, "tilecount")->valueint; wang_tileset = cJSON_GetObjectItemCaseSensitive(tileset, "image")->valuestring; switch (cJSON_GetObjectItemCaseSensitive(tileset, "imagewidth")->valueint) { case 32: wang_height = cJSON_GetObjectItemCaseSensitive(tileset, "imageheight")->valueint / 4; break; case 128: wang_height = cJSON_GetObjectItemCaseSensitive(tileset, "imageheight")->valueint; break; default: goto end; } //printf("=== wang %u-%u ===\n", wangStart, wangStart + wangSize); counts.wang_count++; goto wang_continue; } } } cJSON const *image = cJSON_GetObjectItemCaseSensitive(tileset, "image"); { struct blob fragment = strip_filename(image->valuestring); //printf("%.*s\n", (int) length, str + start); uint32_t height = cJSON_GetObjectItemCaseSensitive(tileset, "imageheight")->valueint; 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)); counts.tilesets_count++; } //puts(image->valuestring); #if 0 char *str = cJSON_Print(tileset); puts(str); free(str); #endif wang_continue:; } if (counts.wang_count == 1) { struct blob fragment = strip_filename(wang_tileset); //printf("%.*s\n", (int) length, str + start); wang_tileset = malloc(fragment.size + 1); memcpy(wang_tileset, fragment.data, fragment.size); wang_tileset[fragment.size] = 0; //puts(wang_tileset); } cJSON const *width_obj = cJSON_GetObjectItemCaseSensitive(json, "width"); cJSON const *height_obj = cJSON_GetObjectItemCaseSensitive(json, "height"); if (!cJSON_IsNumber(width_obj) || !cJSON_IsNumber(height_obj)) { fputs("missing tilemap size\n", stderr); return 1; } struct map map = { .backdrop = {0x9f, 0x9f, 0x9f, 0xff}, .width = width_obj->valueint - 1, .height = height_obj->valueint - 1, .layers = 0, .middleground = 0, }; uint8_t *tile_data = NULL; float *parallaxes = NULL; cJSON const *backdrop = cJSON_GetObjectItemCaseSensitive(json, "backgroundcolor"); if (cJSON_IsString(backdrop)) { 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) { cJSON const *type_obj = cJSON_GetObjectItemCaseSensitive(layer, "type"); if (!cJSON_IsString(type_obj)) { status = EXIT_FAILURE; goto end; } char const *type = type_obj->valuestring; //puts(type); if (strcmp(type, "group") == 0) { // group layer cJSON const *sublayers = cJSON_GetObjectItemCaseSensitive(layer, "layers"); cJSON const *sublayer = NULL; struct tilemap_T layert = {.tilemap = NULL, .width = 0, .height = 0}; cJSON_ArrayForEach (sublayer, sublayers) { struct tilemap_T tilemap = make_tilemap(sublayer, wangStart, wangSize); if (tilemap.tilemap == NULL) { status = EXIT_FAILURE; goto end; } if (layert.tilemap == NULL) { layert.width = tilemap.width; layert.height = tilemap.height; layert.tilemap = malloc(layert.width * layert.height * sizeof (int)); memcpy(layert.tilemap, tilemap.tilemap, layert.width * layert.height * sizeof (int)); } else { for (size_t i = 0; i < layert.width * layert.height; i++) { if (tilemap.tilemap[i] != 0) { layert.tilemap[i] = tilemap.tilemap[i]; } } } free(tilemap.tilemap); } for (int y = 0; y < layert.height; y++) { for (int x = 0; x < layert.width; x++) { //printf("%2x,", layert.tilemap[x + y * layert.width]); } //fputc('\n', stdout); } size_t const layer_size = map.width * map.height; tile_data = realloc(tile_data, layer_size * (map.layers + 1)); for (size_t i = 0; i < layer_size; i++) { tile_data[layer_size * map.layers + i] = layert.tilemap[i]; } parallaxes = realloc(parallaxes, sizeof (float) * 2 * (map.layers + 1)); float px = 1.0f, py = 1.0f; cJSON const *parallax; parallax = cJSON_GetObjectItemCaseSensitive(layer, "parallaxx"); if (cJSON_IsNumber(parallax)) { px = parallax->valuedouble; } parallax = cJSON_GetObjectItemCaseSensitive(layer, "parallaxy"); if (cJSON_IsNumber(parallax)) { py = parallax->valuedouble; } //printf("parallax: %f %f\n", px, py); parallaxes[map.layers * 2 + 0] = px; parallaxes[map.layers * 2 + 1] = py; map.layers++; free(layert.tilemap); } else if (strcmp(type, "tilelayer") == 0) { // tile layer struct tilemap_T tilemap = make_tilemap(layer, wangStart, wangSize); if (tilemap.tilemap == NULL) { status = EXIT_FAILURE; goto end; } for (int y = 0; y < tilemap.height; y++) { for (int x = 0; x < tilemap.width; x++) { //printf("%2x,", tilemap.tilemap[x + y * tilemap.width]); } //fputc('\n', stdout); } size_t const layer_size = map.width * map.height; tile_data = realloc(tile_data, layer_size * (map.layers + 1)); for (size_t i = 0; i < layer_size; i++) { tile_data[layer_size * map.layers + i] = tilemap.tilemap[i]; } parallaxes = realloc(parallaxes, sizeof (float) * 2 * (map.layers + 1)); float px = 1.0f, py = 1.0f; cJSON const *parallax; parallax = cJSON_GetObjectItemCaseSensitive(layer, "parallaxx"); if (cJSON_IsNumber(parallax)) { px = parallax->valuedouble; } parallax = cJSON_GetObjectItemCaseSensitive(layer, "parallaxy"); if (cJSON_IsNumber(parallax)) { py = parallax->valuedouble; } //printf("parallax: %fx%f\n", px, py); parallaxes[map.layers * 2 + 0] = px; parallaxes[map.layers * 2 + 1] = py; map.layers++; free(tilemap.tilemap); #if 0 char *str = cJSON_Print(layer); 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")) || cJSON_GetObjectItemCaseSensitive(object, "polygon") || cJSON_GetObjectItemCaseSensitive(object, "polyline")) { 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 const *path = cJSON_GetObjectItemCaseSensitive(object, "polygon"); if (path) { fputs("polygon ", stdout); cJSON const *node; struct blob nodes = blob_new(); blob_append(&nodes, "path", strlen("path") + 1); cJSON_ArrayForEach(node, path) { char buf[64]; sprintf(buf, "%i,%i ", cJSON_GetObjectItemCaseSensitive(node, "x")->valueint, cJSON_GetObjectItemCaseSensitive(node, "y")->valueint); blob_append(&nodes, buf, strlen(buf)); } blob_append(&entity, nodes.data, nodes.size - 1); blob_append(&entity, &(char) {0}, 1); fwrite(nodes.data, 1, nodes.size - 1, stdout); blob_free(&nodes); putchar('\n'); } path = cJSON_GetObjectItemCaseSensitive(object, "polyline"); if (path) { //fputs("polyline ", stdout); char **node_strings = malloc(cJSON_GetArraySize(path) * sizeof (char *)); int node_strings_index = 0; cJSON const *node; struct blob nodes = blob_new(); blob_append(&nodes, "path", strlen("path") + 1); cJSON_ArrayForEach(node, path) { char buf[64]; sprintf(buf, "%i,%i ", cJSON_GetObjectItemCaseSensitive(node, "x")->valueint, cJSON_GetObjectItemCaseSensitive(node, "y")->valueint); blob_append(&nodes, buf, strlen(buf)); node_strings[node_strings_index++] = strdup(buf); } free(node_strings[--node_strings_index]); while (node_strings_index > 1) { node_strings_index--; blob_append(&nodes, node_strings[node_strings_index], strlen(node_strings[node_strings_index])); free(node_strings[node_strings_index]); } free(node_strings[--node_strings_index]); free(node_strings); blob_append(&entity, nodes.data, nodes.size - 1); blob_append(&entity, &(char) {0}, 1); //fwrite(nodes.data, 1, nodes.size - 1, stdout); blob_free(&nodes); //putchar('\n'); } 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 } cJSON const *properties = cJSON_GetObjectItemCaseSensitive(layer, "properties"); cJSON const *property = NULL; cJSON_ArrayForEach (property, properties) { char const *const name = cJSON_GetObjectItemCaseSensitive(property, "name")->valuestring; if (strcmp(name, "middleground") == 0) { if (cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(property, "value"))) { map.middleground = map.layers; } } else { printf("unknown property '%s'\n", name); return 1; } } } safe_write(&counts, sizeof (counts), outfile); safe_write(tilesets_blob.data, tilesets_blob.size, outfile); blob_free(&tilesets_blob); if (wang_tileset != NULL) { safe_write(&wang_height, sizeof (uint32_t), outfile); safe_write(wang_tileset, strlen(wang_tileset) + 1, outfile); free(wang_tileset); } 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); } cJSON_Delete(json); return status; }