#include #include #include #include #include #include "logging.h" #include "base64.h" #include "compression.h" #include "map.h" #include "resources/image.h" struct tmx { char *dirname; struct map *map; enum { TMX_PARSING_NONE = 0, TMX_PARSING_MAP, TMX_PARSING_TILESET, TMX_PARSING_IMAGE, TMX_PARSING_LAYER, TMX_PARSING_DATA } parsing; struct tileset *cur_tileset; struct layer *cur_layer; enum { TMX_DATA_ENC_NONE = 0, TMX_DATA_ENC_BASE64, TMX_DATA_ENC_CSV } data_encoding; enum { TMX_DATA_COMP_NONE = 0, TMX_DATA_COMP_GZIP, TMX_DATA_COMP_ZLIB } data_compression; char *layer_data; }; #define foreach_tmx_attr(attr) \ for (; (attr)[0] != NULL; (attr) += 2) #define tmx_get_int_attr(attr, name, p) \ do { \ if (strcmp((attr)[0], (name)) == 0) { \ if (sscanf((attr)[1], "%d", (p)) != 1) { \ err(1, "Invalid \"%s\" attribute", (name)); \ } \ } \ } while (0); #define tmx_get_string_attr(attr, name, p) \ do { \ if (strcmp((attr)[0], (name)) == 0) { \ (p) = strdup((attr)[1]); \ } \ } while (0); static void tmx_start_map(struct tmx *cur_tmx, const char **attr) { if (cur_tmx->map != NULL) { err(1, "Found multiple maps in TMX file"); } cur_tmx->map = malloc(sizeof(*cur_tmx->map)); if (cur_tmx->map == NULL) { err(1, "Failed to allocate map"); } memset(cur_tmx->map, 0, sizeof(*cur_tmx->map)); foreach_tmx_attr (attr) { tmx_get_int_attr(attr, "width", &cur_tmx->map->width); tmx_get_int_attr(attr, "height", &cur_tmx->map->height); tmx_get_int_attr(attr, "tilewidth", &cur_tmx->map->tilewidth); tmx_get_int_attr(attr, "tileheight", &cur_tmx->map->tileheight); } } static void tmx_start_tileset(struct tmx *cur_tmx, const char **attr) { struct tileset *new_tileset; if (cur_tmx->map == NULL) { err(1, "Malformed TMX file"); } new_tileset = malloc(sizeof(*new_tileset)); if (new_tileset == NULL) { err(1, "Failed to allocate tileset"); } memset(new_tileset, 0, sizeof(*new_tileset)); if (cur_tmx->cur_tileset == NULL) { cur_tmx->map->tilesets = new_tileset; } else { cur_tmx->cur_tileset->next = new_tileset; } cur_tmx->cur_tileset = new_tileset; foreach_tmx_attr (attr) { tmx_get_int_attr(attr, "firstgid", &cur_tmx->cur_tileset->firstgid); tmx_get_int_attr(attr, "tilewidth", &cur_tmx->cur_tileset->tilewidth); tmx_get_int_attr(attr, "tileheight", &cur_tmx->cur_tileset->tileheight); } /* TODO: Parse tilesets referenced in "source" attributes. */ } static void tmx_start_image(struct tmx *cur_tmx, const char **attr) { char *source; char *path; if (cur_tmx->cur_tileset == NULL) { err(1, "Malformed TMX file"); } source = NULL; foreach_tmx_attr (attr) { tmx_get_string_attr(attr, "source", source); } if (source == NULL) { err(1, "No \"source\" attribute found in \"image\" tag"); } /* TODO: Move to end tag handler and check for "loadimage" property. */ path = malloc(strlen(cur_tmx->dirname) + strlen(source) + 2); if (path == NULL) { err(1, "Failed to allocate resource path string"); } sprintf(path, "%s/%s", cur_tmx->dirname, source); cur_tmx->cur_tileset->image = img_png_get(path); free(path); debug(" Found tileset with firstgid %d and source \"%s\"", cur_tmx->cur_tileset->firstgid, source); free(source); } static void tmx_start_layer(struct tmx *cur_tmx, const char **attr) { struct layer *new_layer; if (cur_tmx->map == NULL) { err(1, "Malformed TMX file"); } new_layer = malloc(sizeof(*new_layer)); if (new_layer == NULL) { err(1, "Failed to allocate layer"); } memset(new_layer, 0, sizeof(*new_layer)); if (cur_tmx->cur_layer == NULL) { cur_tmx->map->layers = new_layer; } else { cur_tmx->cur_layer->next = new_layer; } cur_tmx->cur_layer = new_layer; foreach_tmx_attr (attr) { tmx_get_string_attr(attr, "name", cur_tmx->cur_layer->name); } debug(" Found layer with name \"%s\"", cur_tmx->cur_layer->name); } static void tmx_start_data(struct tmx *cur_tmx, const char **attr) { if (cur_tmx->cur_layer == NULL) { err(1, "Malformed TMX file"); } foreach_tmx_attr (attr) { if (strcmp(attr[0], "encoding") == 0) { if (strcmp(attr[1], "base64") == 0) { cur_tmx->data_encoding = TMX_DATA_ENC_BASE64; } else if (strcmp(attr[1], "csv") == 0) { cur_tmx->data_encoding = TMX_DATA_ENC_CSV; } else { err(1, "Unsupported data encoding in TMX file"); } } else if (strcmp(attr[0], "compression") == 0) { if (strcmp(attr[1], "gzip") == 0) { cur_tmx->data_compression = TMX_DATA_COMP_GZIP; } else if (strcmp(attr[1], "zlib") == 0) { cur_tmx->data_compression = TMX_DATA_COMP_ZLIB; } else { err(1, "Unsupported compression in TMX file"); } } } } static void XMLCALL tmx_start(void *data, const char *el, const char **attr) { struct tmx *cur_tmx; cur_tmx = (struct tmx *) data; if (strcmp(el, "map") == 0) { tmx_start_map(cur_tmx, attr); cur_tmx->parsing = TMX_PARSING_MAP; } else if (strcmp(el, "tileset") == 0) { tmx_start_tileset(cur_tmx, attr); cur_tmx->parsing = TMX_PARSING_TILESET; } else if (strcmp(el, "image") == 0) { tmx_start_image(cur_tmx, attr); cur_tmx->parsing = TMX_PARSING_IMAGE; } else if (strcmp(el, "layer") == 0) { tmx_start_layer(cur_tmx, attr); cur_tmx->parsing = TMX_PARSING_LAYER; } else if (strcmp(el, "data") == 0) { tmx_start_data(cur_tmx, attr); cur_tmx->parsing = TMX_PARSING_DATA; } /* TODO: Handle objectgroup, object, properties, and property tags. */ } static void tmx_end_data(struct tmx *cur_tmx) { size_t decoded_len; size_t decomp_len; char *decoded_buf; char *decomp_buf; decoded_len = strlen(cur_tmx->layer_data); decomp_len = 4 * cur_tmx->map->width * cur_tmx->map->height; debug(" Expected map data size: %d", decomp_len); decoded_buf = malloc(decoded_len + 1); if (decoded_buf == NULL) { err(1, "Failed to allocate map layer data buffer"); } if (cur_tmx->data_encoding == TMX_DATA_ENC_BASE64) { debug(" Decoding base 64 layer data..."); base64_decode(cur_tmx->layer_data, decoded_buf, decoded_len + 1); } if (cur_tmx->data_compression == TMX_DATA_COMP_NONE) { debug(" Layer data already decompressed"); decomp_buf = decoded_buf; } else if (cur_tmx->data_compression == TMX_DATA_COMP_ZLIB) { debug(" Decompressing layer data with zlib..."); decomp_buf = malloc(decomp_len); if (decomp_buf == NULL) { err(1, "Failed to allocate map layer data buffer"); } decompress(decoded_buf, decoded_len, decomp_buf, decomp_len); free(decoded_buf); } else if (cur_tmx->data_compression == TMX_DATA_COMP_GZIP) { debug(" Decompressing layer data with gzip..."); decomp_buf = malloc(decomp_len); if (decomp_buf == NULL) { err(1, "Failed to allocate map layer data buffer"); } decompress(decoded_buf, decoded_len, decomp_buf, decomp_len); free(decoded_buf); } else { /* This should never happen. This branch exists only to silence * GCC's maybe-uninitialized warning on decomp_buf below. */ return; } free(cur_tmx->layer_data); cur_tmx->cur_layer->tiles = (Uint32 *) decomp_buf; } static void XMLCALL tmx_end(void *data, const char *el) { struct tmx *cur_tmx; cur_tmx = (struct tmx *) data; if (strcmp(el, "data") == 0) { tmx_end_data(cur_tmx); } cur_tmx->parsing = TMX_PARSING_NONE; } static void XMLCALL tmx_data(void *data, const char *s, int len) { struct tmx *cur_tmx; char *s_z; char *s_z_trimmed, *s_z_trimmed_end; cur_tmx = (struct tmx *) data; if (cur_tmx->parsing != TMX_PARSING_DATA) { return; } s_z = s_z_trimmed = strndup(s, len); while(isspace(*s_z_trimmed)) { ++s_z_trimmed; --len; } if (*s_z_trimmed == '\0') { return; } s_z_trimmed_end = s_z_trimmed + len - 1; while (s_z_trimmed_end > s_z_trimmed && isspace(*s_z_trimmed_end)) { --s_z_trimmed_end; } *(s_z_trimmed_end + 1) = '\0'; cur_tmx->layer_data = strdup(s_z_trimmed); free(s_z); } struct map * tmx_load(const char *path) { XML_Parser p; struct tmx cur_tmx; char *path_dup; FILE *tmx_fp; void *tmx_buf; size_t len; debug("Parsing TMX file \"%s\"...", path); p = XML_ParserCreate(NULL); if (p == NULL) { err(1, "Failed to create TMX parser"); } XML_SetElementHandler(p, tmx_start, tmx_end); XML_SetCharacterDataHandler(p, tmx_data); memset(&cur_tmx, 0, sizeof(cur_tmx)); path_dup = strdup(path); cur_tmx.dirname = dirname(path_dup); XML_SetUserData(p, &cur_tmx); tmx_fp = fopen(path, "rb"); if (tmx_fp == NULL) { err(1, "Failed to open TMX file"); } tmx_buf = XML_GetBuffer(p, 8192); if (tmx_buf == NULL) { err(1, "Failed to create TMX parse buffer"); } while (!feof(tmx_fp)) { len = fread(tmx_buf, 1, 8192, tmx_fp); if (XML_ParseBuffer(p, len, feof(tmx_fp)) == XML_STATUS_ERROR) { err(1, "Failed to parse TMX file (%s)", XML_ErrorString(XML_GetErrorCode(p))); } } fclose(tmx_fp); free(path_dup); return cur_tmx.map; }