summaryrefslogtreecommitdiffstats
path: root/src/tmx.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tmx.c')
-rw-r--r--src/tmx.c370
1 files changed, 370 insertions, 0 deletions
diff --git a/src/tmx.c b/src/tmx.c
new file mode 100644
index 0000000..0e401b3
--- /dev/null
+++ b/src/tmx.c
@@ -0,0 +1,370 @@
+#include <stdio.h>
+#include <string.h>
+#include <libgen.h>
+#include <expat.h>
+#include <SDL.h>
+#include "logging.h"
+#include "base64.h"
+#include "compression.h"
+#include "map.h"
+#include "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");
+ }
+
+ foreach_tmx_attr (attr) {
+ tmx_get_string_attr(attr, "source", source);
+ }
+
+ /* 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 = load_png(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);
+ }
+
+ 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;
+}