summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorP. J. McDermott <pjm@nac.net>2013-02-14 18:03:09 (EST)
committer P. J. McDermott <pjm@nac.net>2013-02-14 18:03:09 (EST)
commit80191b41352ad20493fb62e8f3683d69133d0d24 (patch)
treecb77e93dfe476d9535ca48e732a98aae9a780ba4
downloadoverworld-rpg-80191b41352ad20493fb62e8f3683d69133d0d24.zip
overworld-rpg-80191b41352ad20493fb62e8f3683d69133d0d24.tar.gz
overworld-rpg-80191b41352ad20493fb62e8f3683d69133d0d24.tar.bz2
Initial commit.
-rw-r--r--.gitignore8
-rw-r--r--Makefile21
-rw-r--r--data/forest1.tmx63
-rw-r--r--data/tilesets/collision.pngbin0 -> 209 bytes
-rw-r--r--data/tilesets/collision.tsx87
-rw-r--r--data/tilesets/collision.xcfbin0 -> 1627 bytes
-rw-r--r--data/tilesets/mountain_landscape_19-16.pngbin0 -> 149736 bytes
-rw-r--r--src/base64.c95
-rw-r--r--src/base64.h8
-rw-r--r--src/compression.c226
-rw-r--r--src/compression.h6
-rw-r--r--src/image.c22
-rw-r--r--src/image.h6
-rw-r--r--src/init.c36
-rw-r--r--src/init.h11
-rw-r--r--src/layer.h20
-rw-r--r--src/logging.c43
-rw-r--r--src/logging.h8
-rw-r--r--src/main.c65
-rw-r--r--src/map.h16
-rw-r--r--src/tileset.h12
-rw-r--r--src/tmx.c370
-rw-r--r--src/tmx.h8
23 files changed, 1131 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9fdb10a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+*.o
+sdlex
+a.out
+
+*.s[a-w]?
+Session.vim
+*~
+*.orig
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3efdec5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,21 @@
+.SUFFIXES =
+.SUFFIXES = .c .h .o
+
+CC = gcc
+CFLAGS = -Wall -Wextra -Werror -g
+
+SRCS = src/main.c src/init.c src/logging.c src/image.c \
+ src/base64.c src/compression.c src/tmx.c
+OBJS = $(SRCS:.c=.o)
+LIBS = sdl SDL_image zlib expat
+
+all: sdlex
+
+sdlex: $(OBJS)
+ $(CC) $(LDFLAGS) -o sdlex $$(pkg-config --libs $(LIBS)) $(OBJS)
+
+.c.o:
+ $(CC) -c $(CFLAGS) -o $*.o $$(pkg-config --cflags $(LIBS)) $<
+
+clean:
+ rm -f $(OBJS) sdlex
diff --git a/data/forest1.tmx b/data/forest1.tmx
new file mode 100644
index 0000000..41c8241
--- /dev/null
+++ b/data/forest1.tmx
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" width="30" height="40" tilewidth="16" tileheight="16">
+ <tileset firstgid="1" name="mountain_landscape_19-16" tilewidth="16" tileheight="16">
+ <image source="tilesets/mountain_landscape_19-16.png" width="256" height="256"/>
+ </tileset>
+ <tileset firstgid="257" source="tilesets/collision.tsx"/>
+ <layer name="ground" width="30" height="40">
+ <data encoding="base64" compression="zlib">
+ eJztlsERAjEIRSlArUCtwLWVtQPtQDtwS18OHpwdIvwv7EFz+MMhkyEvgR9GEbmoRjDuVYeXjsT+q+pGxGVedD96zl/n3ai2hu7FvO9cLVXwRvNW8k6qwdC5mJfhyuBl3rHzdl6Et+VjlnaJvBE/+XRPrA+jeVu8qA97PvYM8qI+7NW5t876sFfn3jrbpxW8kT7tvN/zPsT3IG++YnhRZfEyeVnek9g9G5E1X0V5s2OUNzt23hre5f/t9XcFHzJvZL4nMm9k8k7i9/da9bsGb+Q/+zfeGUt8RgE=
+ </data>
+ </layer>
+ <layer name="obj-low" width="30" height="40">
+ <data encoding="base64" compression="zlib">
+ eJztmNEJwzAMRLX/Dh2pi6QbtASajxqMiFzd6VQo5MAIQs7yI7FkvJnZ4xgbGL1Q//MYLyJ6of55/fdP3F0czxXyvNHcypxm57wdfF6ed6gzp9k5r8/dsYZvvF3cK95ORbzdUvGidUrFy9Y7lJet396P8rL12/tH/lswovWyqvKyqn5fVhdvTr/mzezPzFwob2Z/ZuZS96PuehWJrVdVXbzr9/6RF+29s6fCi/be2aM+b2T96vNG1l/lRaXiRXXxrsX+v9E8bP9V8VZjxKW+z6ne77wB2seF1Q==
+ </data>
+ </layer>
+ <layer name="char-bot" width="30" height="40">
+ <data encoding="base64" compression="zlib">
+ eJzt1sEJAAAIAzH3H1qcQVAESSboo4+L4FpeDwAAaJhoF/3DJv8C+KcAmWED+Q==
+ </data>
+ </layer>
+ <layer name="obj-mid" width="30" height="40">
+ <data encoding="base64" compression="zlib">
+ eJzt1UEKACEMBMH8/wG+13sgiBBHGbthb0LtYcQI6mikz9XN3m+uKty987/t0N0lj27t5XW3+z7halyiE1X7rN7frl3jatzV/6hdIocmn4BVjQ==
+ </data>
+ </layer>
+ <layer name="char-top" width="30" height="40">
+ <data encoding="base64" compression="zlib">
+ eJzt1jENAAAIA0H8i0UDEwJIICx3Cjp0+Ahafg8AABjYaBf9wyX/AmBLAdpSA7k=
+ </data>
+ </layer>
+ <layer name="obj-high" width="30" height="40">
+ <data encoding="base64" compression="zlib">
+ eJztl0EKgDAMBPv/p9Uf6Gf0YKCXmN0kLUgzILm4DQO2qb21drR19Lff+TzXwr7ST/qvonzXUL4+JM/WqK/k2cr6oj5WnvVFfay819dL1NdL+WLs4ovsT2Qt1hfZn8ha2fNo9nmlMfu80ijf7/f+6MvO3jET8WVn75jJvm+g+ez7BpqP+rJk+bKU7zfe71dbxzt/s3yjVfOy/o+iVfOy/o928b0BRnVFrg==
+ </data>
+ </layer>
+ <layer name="collision" width="30" height="40">
+ <data encoding="base64" compression="zlib">
+ eJzt1cEKwCAMA1B38ej/f67XIStqm7oICZRdXJ7CcPUppTpmjKfjT3fseD+Rme0/K1/uCZ/pvHLlznqi9w+bG+1jcZsx1nveRM8rVy7CXVm/0nWLi9iXXLnM7u4/8EbXspD3D7O7G7mcrvc7YnWjk92Pcjt4pgtE
+ </data>
+ </layer>
+ <objectgroup name="exits" width="30" height="40">
+ <object type="exit" x="288" y="624" width="64" height="16">
+ <properties>
+ <property name="coords" value="19,1"/>
+ <property name="map" value="town1"/>
+ </properties>
+ </object>
+ <object type="exit" x="224" y="0" width="64" height="16">
+ <properties>
+ <property name="coords" value="15,28"/>
+ <property name="map" value="town2"/>
+ </properties>
+ </object>
+ </objectgroup>
+ <objectgroup name="spawns" width="30" height="40">
+ <object type="spawn" x="368" y="464" width="16" height="16">
+ <properties>
+ <property name="player" value="1"/>
+ </properties>
+ </object>
+ </objectgroup>
+</map>
diff --git a/data/tilesets/collision.png b/data/tilesets/collision.png
new file mode 100644
index 0000000..6d9a18b
--- /dev/null
+++ b/data/tilesets/collision.png
Binary files differ
diff --git a/data/tilesets/collision.tsx b/data/tilesets/collision.tsx
new file mode 100644
index 0000000..b38d47a
--- /dev/null
+++ b/data/tilesets/collision.tsx
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<tileset name="collision" tilewidth="16" tileheight="16">
+ <properties>
+ <property name="loadimage" value="0"/>
+ </properties>
+ <image source="tilesets/collision.png" trans="00ff00" width="64" height="64"/>
+ <tile id="0">
+ <properties>
+ <property name="collision" value="1,0,0,1"/>
+ </properties>
+ </tile>
+ <tile id="1">
+ <properties>
+ <property name="collision" value="1,0,0,0"/>
+ </properties>
+ </tile>
+ <tile id="2">
+ <properties>
+ <property name="collision" value="1,0,0,0"/>
+ </properties>
+ </tile>
+ <tile id="3">
+ <properties>
+ <property name="collision" value="1,1,0,0"/>
+ </properties>
+ </tile>
+ <tile id="4">
+ <properties>
+ <property name="collision" value="0,0,0,1"/>
+ </properties>
+ </tile>
+ <tile id="5">
+ <properties>
+ <property name="collision" value="1,1,1,1"/>
+ </properties>
+ </tile>
+ <tile id="6">
+ <properties>
+ <property name="collision" value="1,1,1,1"/>
+ </properties>
+ </tile>
+ <tile id="7">
+ <properties>
+ <property name="collision" value="0,1,0,0"/>
+ </properties>
+ </tile>
+ <tile id="8">
+ <properties>
+ <property name="collision" value="0,0,0,1"/>
+ </properties>
+ </tile>
+ <tile id="9">
+ <properties>
+ <property name="collision" value="1,1,1,1"/>
+ </properties>
+ </tile>
+ <tile id="10">
+ <properties>
+ <property name="collision" value="1,1,1,1"/>
+ </properties>
+ </tile>
+ <tile id="11">
+ <properties>
+ <property name="collision" value="0,1,0,0"/>
+ </properties>
+ </tile>
+ <tile id="12">
+ <properties>
+ <property name="collision" value="0,0,1,1"/>
+ </properties>
+ </tile>
+ <tile id="13">
+ <properties>
+ <property name="collision" value="0,0,1,0"/>
+ </properties>
+ </tile>
+ <tile id="14">
+ <properties>
+ <property name="collision" value="0,0,1,0"/>
+ </properties>
+ </tile>
+ <tile id="15">
+ <properties>
+ <property name="collision" value="0,1,1,0"/>
+ </properties>
+ </tile>
+</tileset>
diff --git a/data/tilesets/collision.xcf b/data/tilesets/collision.xcf
new file mode 100644
index 0000000..a873597
--- /dev/null
+++ b/data/tilesets/collision.xcf
Binary files differ
diff --git a/data/tilesets/mountain_landscape_19-16.png b/data/tilesets/mountain_landscape_19-16.png
new file mode 100644
index 0000000..54e619c
--- /dev/null
+++ b/data/tilesets/mountain_landscape_19-16.png
Binary files differ
diff --git a/src/base64.c b/src/base64.c
new file mode 100644
index 0000000..6a2c873
--- /dev/null
+++ b/src/base64.c
@@ -0,0 +1,95 @@
+#include "base64.h"
+
+static void
+base64_decode_block(const char *src, char *dest)
+{
+ int i;
+ char buf[4];
+
+ /* Convert base 64 characters into values. */
+ /* Supports ASCII and EBCDIC systems. */
+ for (i = 0; i < 4; ++i) {
+ if (src[i] >= 'A' && src[i] <= 'I') {
+ buf[i] = src[i] - 'A' + 0;
+ } else if (src[i] >= 'J' && src[i] <= 'R') {
+ buf[i] = src[i] - 'J' + 9;
+ } else if (src[i] >= 'S' && src[i] <= 'Z') {
+ buf[i] = src[i] - 'S' + 18;
+ } else if (src[i] >= 'a' && src[i] <= 'i') {
+ buf[i] = src[i] - 'a' + 26;
+ } else if (src[i] >= 'j' && src[i] <= 'r') {
+ buf[i] = src[i] - 'j' + 35;
+ } else if (src[i] >= 's' && src[i] <= 'z') {
+ buf[i] = src[i] - 's' + 44;
+ } else if (src[i] >= '0' && src[i] <= '9') {
+ buf[i] = src[i] - '0' + 52;
+ } else if (src[i] == '+') {
+ buf[i] = 62;
+ } else if (src[i] == '/') {
+ buf[i] = 63;
+ } else {
+ buf[i] = '\0';
+ }
+ }
+
+ /* Map four decoded value bytes into three output bytes. */
+ dest[0] = (buf[0] << 2) | (buf[1] >> 4);
+ dest[1] = (buf[1] << 4) | (buf[2] >> 2);
+ dest[2] = (buf[2] << 6) | (buf[3] >> 0);
+}
+
+void
+base64_decode(const char *src, char *dest, size_t dest_len)
+{
+ size_t i;
+
+ /* Decode the input one entire block at a time. */
+ for (i = 0; i < (dest_len - 1) / 3; ++i) {
+ if (src[i * 4] == '\0' ||
+ src[i * 4 + 1] == '\0' ||
+ src[i * 4 + 2] == '\0' ||
+ src[i * 4 + 3] == '\0') {
+ dest[i * 3] = '\0';
+ break;
+ }
+ base64_decode_block(src + i * 4, dest + i * 3);
+ }
+
+ /* Ensure NUL termination. */
+ dest[dest_len - 1] = '\0';
+}
+
+#if 0
+int
+main()
+{
+ char dest[1024];
+ base64_decode(
+ "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5v"
+ "dCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1"
+ "dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Np"
+ "b24gZnJvbSBvdGhlciBhbmltYWxzLCB3"
+ "aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1p"
+ "bmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFu"
+ "Y2Ugb2YgZGVsaWdodCBpbiB0aGUgY29u"
+ "dGludWVkIGFuZCBpbmRlZmF0aWdhYmxl"
+ "IGdlbmVyYXRpb24gb2Yga25vd2xlZGdl"
+ "LCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhl"
+ "bWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVh"
+ "c3VyZS4=", dest, 1024);
+ printf("Decoded string: \"%s\"\n", dest);
+ base64_decode("YW55IGNhcm5hbCBwbGVhc3VyZS4=", dest, 1024);
+ printf("Decoded string: \"%s\"\n", dest);
+ base64_decode("YW55IGNhcm5hbCBwbGVhc3VyZQ==", dest, 1024);
+ printf("Decoded string: \"%s\"\n", dest);
+ base64_decode("YW55IGNhcm5hbCBwbGVhc3Vy", dest, 1024);
+ printf("Decoded string: \"%s\"\n", dest);
+ base64_decode("YW55IGNhcm5hbCBwbGVhc3U=", dest, 1024);
+ printf("Decoded string: \"%s\"\n", dest);
+ base64_decode("YW55IGNhcm5hbCBwbGVhcw==", dest, 1024);
+ printf("Decoded string: \"%s\"\n", dest);
+ base64_decode("YW55IGNhcm5hbCBwbGVhcw==", dest, 4);
+ printf("Decoded string: \"%s\"\n", dest);
+ return 0;
+}
+#endif
diff --git a/src/base64.h b/src/base64.h
new file mode 100644
index 0000000..b56b9db
--- /dev/null
+++ b/src/base64.h
@@ -0,0 +1,8 @@
+#ifndef BASE64_H
+#define BASE64_H
+
+#include <stdlib.h>
+
+void base64_decode(const char *src, char *dest, size_t dest_len);
+
+#endif
diff --git a/src/compression.c b/src/compression.c
new file mode 100644
index 0000000..cf88de6
--- /dev/null
+++ b/src/compression.c
@@ -0,0 +1,226 @@
+#include <stdlib.h>
+#include <string.h>
+#include <zlib.h>
+#include "logging.h"
+
+static void
+zlib_err(int status)
+{
+ switch (status) {
+ case Z_OK:
+ case Z_STREAM_END:
+ break;
+ case Z_MEM_ERROR:
+ err(1, "zlib error: out of memory");
+ break;
+ case Z_VERSION_ERROR:
+ err(1, "zlib error: incompatible library version");
+ break;
+ case Z_STREAM_ERROR:
+ err(1, "zlib error: invalid stream");
+ break;
+ case Z_NEED_DICT:
+ err(1, "zlib error: need dictionary");
+ break;
+ case Z_DATA_ERROR:
+ err(1, "zlib error: corrupted data");
+ break;
+ case Z_BUF_ERROR:
+ err(1, "zlib error: buffer error");
+ break;
+ default:
+ err(1, "zlib error: unknown");
+ break;
+ }
+}
+
+void
+decompress(const char *src, size_t src_len, char *dest, size_t dest_len)
+{
+ z_stream d_stream;
+
+ d_stream.zalloc = Z_NULL;
+ d_stream.zfree = Z_NULL;
+ d_stream.opaque = Z_NULL;
+ d_stream.next_in = (Bytef *) src;
+ d_stream.avail_in = src_len;
+ d_stream.next_out = (Bytef *) dest;
+ d_stream.avail_out = dest_len;
+
+ debug("Initializing inflation stream...");
+ zlib_err(inflateInit2(&d_stream, 15 + 32));
+
+ debug("Inflating %d bytes into up to %d bytes...", src_len, dest_len);
+ zlib_err(inflate(&d_stream, Z_NO_FLUSH));
+
+ debug("Ending inflation stream...");
+ zlib_err(inflateEnd(&d_stream));
+}
+
+#if 0
+#include <stdio.h>
+#include "base64.h"
+
+int
+main()
+{
+ char decoded_data[4801], compressed_data[4801], decompressed_data[4801];
+
+ base64_decode(
+ "TwAAAFAAAABPAAAAUAAAAE8AAABQAAAATwAAAFAAAABPAAAA"
+ "UAAAAE8AAABQAAAATwAAAFAAAAAcAAAAHQAAAB0AAAAeAAAA"
+ "TwAAAFAAAABPAAAAUAAAAE8AAABQAAAATwAAAFAAAABPAAAA"
+ "UAAAAE8AAABQAAAAXwAAAGAAAABfAAAAYAAAAF8AAABgAAAA"
+ "XwAAAGAAAABfAAAAYAAAAF8AAABgAAAAXwAAAGAAAAAcAAAA"
+ "HQAAAB0AAAAeAAAAXwAAAGAAAABfAAAAYAAAAF8AAABgAAAA"
+ "XwAAAGAAAABfAAAAYAAAAF8AAABgAAAATwAAAFAAAABPAAAA"
+ "UAAAAE8AAABQAAAATwAAAFAAAABPAAAAUAAAAE8AAABQAAAA"
+ "TwAAAFAAAAAcAAAAHQAAAB0AAAAeAAAATwAAAFAAAABPAAAA"
+ "UAAAAE8AAABQAAAATwAAAFAAAABPAAAAUAAAAE8AAABQAAAA"
+ "XwAAAGAAAABfAAAAYAAAAF8AAABgAAAAXwAAAGAAAABfAAAA"
+ "YAAAAF8AAABgAAAAXwAAAGAAAAAcAAAAHQAAAB0AAAAeAAAA"
+ "XwAAAGAAAABfAAAAYAAAAF8AAABgAAAAXwAAAGAAAABfAAAA"
+ "YAAAAF8AAABgAAAATwAAAFAAAABPAAAAUAAAAE8AAABQAAAA"
+ "TwAAAFAAAAAMAAAADQAAAA0AAAANAAAADQAAAA0AAABvAAAA"
+ "HQAAAB0AAAAeAAAATwAAAFAAAABPAAAAUAAAAE8AAABQAAAA"
+ "TwAAAFAAAABPAAAAUAAAAE8AAABQAAAAXwAAAGAAAABfAAAA"
+ "YAAAAF8AAABgAAAAXwAAAGAAAAAcAAAAHQAAAB0AAAAdAAAA"
+ "HQAAAB0AAAAdAAAAHQAAAB0AAAAeAAAAXwAAAGAAAABfAAAA"
+ "YAAAAF8AAABgAAAAXwAAAGAAAABfAAAAYAAAAF8AAABgAAAA"
+ "TwAAAFAAAABPAAAAUAAAAE8AAABQAAAATwAAAFAAAAAcAAAA"
+ "HQAAAB0AAAAdAAAAHQAAAB0AAAAdAAAAHQAAAB0AAAAeAAAA"
+ "TwAAAFAAAABPAAAAUAAAAE8AAABQAAAATwAAAFAAAABPAAAA"
+ "UAAAAE8AAABQAAAAXwAAAGAAAABfAAAAYAAAAF8AAABgAAAA"
+ "XwAAAGAAAAAcAAAAHQAAAB0AAACAAAAALQAAAC0AAAAtAAAA"
+ "LQAAAC0AAAAuAAAAXwAAAGAAAABfAAAAYAAAAF8AAABgAAAA"
+ "XwAAAGAAAABfAAAAYAAAAF8AAABgAAAATwAAAFAAAABPAAAA"
+ "UAAAAE8AAABQAAAATwAAAFAAAAAcAAAAHQAAAB0AAAAeAAAA"
+ "TwAAAFAAAABPAAAAUAAAAE8AAABQAAAATwAAAFAAAABPAAAA"
+ "UAAAAE8AAABQAAAATwAAAFAAAABPAAAAUAAAAE8AAABQAAAA"
+ "XwAAAGAAAABfAAAAYAAAAF8AAABgAAAAXwAAAGAAAAAcAAAA"
+ "HQAAAB0AAAAeAAAAXwAAAGAAAABfAAAAYAAAAF8AAABgAAAA"
+ "XwAAAGAAAABfAAAAYAAAAF8AAABgAAAAXwAAAGAAAABfAAAA"
+ "YAAAAF8AAABgAAAATwAAAFAAAABPAAAAUAAAAE8AAABQAAAA"
+ "TwAAAFAAAAAcAAAAHQAAAB0AAAAeAAAATwAAAFAAAABPAAAA"
+ "UAAAAE8AAABQAAAATwAAAFAAAABPAAAAUAAAAE8AAABQAAAA"
+ "TwAAAFAAAABPAAAAUAAAAE8AAABQAAAAXwAAAGAAAABfAAAA"
+ "YAAAAF8AAABgAAAAXwAAAGAAAAAcAAAAHQAAAB0AAAAeAAAA"
+ "XwAAAGAAAABfAAAAYAAAAF8AAABgAAAAXwAAAGAAAABfAAAA"
+ "YAAAAF8AAABgAAAAXwAAAGAAAABfAAAAYAAAAF8AAABgAAAA"
+ "TwAAAFAAAABPAAAAUAAAAE8AAABQAAAATwAAAFAAAAAcAAAA"
+ "HQAAAB0AAAAeAAAATwAAAFAAAABPAAAAUAAAAE8AAABQAAAA"
+ "TwAAAFAAAABPAAAAUAAAAE8AAABQAAAATwAAAFAAAABPAAAA"
+ "UAAAAE8AAABQAAAAXwAAAGAAAABfAAAAYAAAAF8AAABgAAAA"
+ "XwAAAGAAAAAcAAAAHQAAAB0AAAAeAAAAXwAAAGAAAABfAAAA"
+ "YAAAAF8AAABgAAAAXwAAAGAAAABfAAAAYAAAAF8AAABgAAAA"
+ "XwAAAGAAAABfAAAAYAAAAF8AAABgAAAATwAAAFAAAABPAAAA"
+ "UAAAAE8AAABQAAAATwAAAFAAAAAcAAAAHQAAAB0AAAAeAAAA"
+ "TwAAAFAAAAAMAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAA"
+ "DQAAAA0AAAANAAAADQAAAA4AAABPAAAAUAAAAE8AAABQAAAA"
+ "XwAAAGAAAABfAAAAYAAAAF8AAABgAAAAXwAAAGAAAAAcAAAA"
+ "HQAAAB0AAAAeAAAAXwAAAGAAAAAcAAAAHQAAAB0AAAAdAAAA"
+ "HQAAAB0AAAAdAAAAHQAAAB0AAAAdAAAAHQAAAB4AAABfAAAA"
+ "YAAAAF8AAABgAAAATwAAAFAAAAAMAAAADQAAAA0AAAANAAAA"
+ "DQAAAA0AAABvAAAAHQAAAB0AAAAeAAAATwAAAFAAAAAcAAAA"
+ "HQAAAB0AAAAdAAAAHQAAAB0AAAAdAAAAHQAAAB0AAAAdAAAA"
+ "HQAAAB4AAABPAAAAUAAAAE8AAABQAAAAXwAAAGAAAAAcAAAA"
+ "HQAAAB0AAAAdAAAAHQAAAB0AAAAdAAAAHQAAAB0AAAAeAAAA"
+ "XwAAAGAAAAAcAAAAHQAAAB0AAACAAAAALQAAAC0AAAAtAAAA"
+ "LQAAAH8AAAAdAAAAHQAAAB4AAABfAAAAYAAAAF8AAABgAAAA"
+ "TwAAAFAAAAAcAAAAHQAAAB0AAAAdAAAAHQAAAB0AAAAdAAAA"
+ "HQAAAB0AAAAeAAAATwAAAFAAAAAcAAAAHQAAAB0AAAAeAAAA"
+ "TwAAAFAAAABPAAAAUAAAABwAAAAdAAAAHQAAAB4AAABPAAAA"
+ "UAAAAE8AAABQAAAAXwAAAGAAAAAcAAAAHQAAAB0AAACAAAAA"
+ "LQAAAC0AAAAtAAAALQAAAC0AAAAuAAAAXwAAAGAAAAAcAAAA"
+ "HQAAAB0AAAAeAAAAXwAAAGAAAABfAAAAYAAAABwAAAAdAAAA"
+ "HQAAAB4AAABfAAAAYAAAAF8AAABgAAAATwAAAFAAAAAcAAAA"
+ "HQAAAB0AAAAeAAAATwAAAFAAAABPAAAAUAAAAE8AAABQAAAA"
+ "TwAAAFAAAAAcAAAAHQAAAB0AAAAeAAAATwAAAFAAAABPAAAA"
+ "UAAAABwAAAAdAAAAHQAAAB4AAABPAAAAUAAAAE8AAABQAAAA"
+ "XwAAAGAAAAAcAAAAHQAAAB0AAAAeAAAAXwAAAGAAAABfAAAA"
+ "YAAAAF8AAABgAAAAXwAAAGAAAAAcAAAAHQAAAB0AAAAeAAAA"
+ "XwAAAGAAAABfAAAAYAAAABwAAAAdAAAAHQAAAB4AAABfAAAA"
+ "YAAAAF8AAABgAAAATwAAAFAAAAAcAAAAHQAAAB0AAAAeAAAA"
+ "TwAAAFAAAABPAAAAUAAAAE8AAABQAAAATwAAAFAAAAAcAAAA"
+ "HQAAAB0AAAAeAAAATwAAAFAAAABPAAAAUAAAABwAAAAdAAAA"
+ "HQAAAB4AAABPAAAAUAAAAE8AAABQAAAAXwAAAGAAAAAcAAAA"
+ "HQAAAB0AAAAeAAAAXwAAAGAAAABfAAAAYAAAAF8AAABgAAAA"
+ "XwAAAGAAAAAcAAAAHQAAAB0AAAAeAAAAXwAAAGAAAABfAAAA"
+ "YAAAABwAAAAdAAAAHQAAAB4AAABfAAAAYAAAAF8AAABgAAAA"
+ "TwAAAFAAAAAcAAAAHQAAAB0AAABwAAAADQAAAA0AAAANAAAA"
+ "DQAAAA0AAAANAAAADQAAAA0AAABvAAAAHQAAAB0AAAAeAAAA"
+ "TwAAAFAAAABPAAAAUAAAABwAAAAdAAAAHQAAAB4AAABPAAAA"
+ "UAAAAE8AAABQAAAAXwAAAGAAAAAcAAAAHQAAAB0AAAAdAAAA"
+ "HQAAAB0AAAAdAAAAHQAAAB0AAAAdAAAAHQAAAB0AAAAdAAAA"
+ "HQAAAB0AAAAeAAAAXwAAAGAAAABfAAAAYAAAABwAAAAdAAAA"
+ "HQAAAB4AAABfAAAAYAAAAF8AAABgAAAATwAAAFAAAAAcAAAA"
+ "HQAAAB0AAAAdAAAAHQAAAB0AAAAdAAAAHQAAAB0AAAAdAAAA"
+ "HQAAAB0AAAAdAAAAHQAAAB0AAAAeAAAATwAAAFAAAABPAAAA"
+ "UAAAABwAAAAdAAAAHQAAAB4AAABPAAAAUAAAAE8AAABQAAAA"
+ "XwAAAGAAAAAsAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAA"
+ "LQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAuAAAA"
+ "XwAAAGAAAABfAAAAYAAAABwAAAAdAAAAHQAAAB4AAABfAAAA"
+ "YAAAAF8AAABgAAAATwAAAFAAAABPAAAAUAAAAE8AAABQAAAA"
+ "TwAAAFAAAABPAAAAUAAAAE8AAABQAAAATwAAAFAAAABPAAAA"
+ "UAAAAE8AAABQAAAATwAAAFAAAABPAAAAUAAAABwAAAAdAAAA"
+ "HQAAAB4AAABPAAAAUAAAAE8AAABQAAAAXwAAAGAAAABfAAAA"
+ "YAAAAF8AAABgAAAAXwAAAGAAAABfAAAAYAAAAF8AAABgAAAA"
+ "XwAAAGAAAABfAAAAYAAAAF8AAABgAAAAXwAAAGAAAABfAAAA"
+ "YAAAABwAAAAdAAAAHQAAAB4AAABfAAAAYAAAAF8AAABgAAAA"
+ "TwAAAFAAAABPAAAAUAAAAE8AAABQAAAATwAAAFAAAABPAAAA"
+ "UAAAAE8AAABQAAAATwAAAFAAAABPAAAAUAAAAE8AAABQAAAA"
+ "TwAAAFAAAABPAAAAUAAAABwAAAAdAAAAHQAAAB4AAABPAAAA"
+ "UAAAAE8AAABQAAAAXwAAAGAAAABfAAAAYAAAAF8AAABgAAAA"
+ "XwAAAGAAAABfAAAAYAAAAF8AAABgAAAAXwAAAGAAAABfAAAA"
+ "YAAAAF8AAABgAAAAXwAAAGAAAABfAAAAYAAAABwAAAAdAAAA"
+ "HQAAAB4AAABfAAAAYAAAAF8AAABgAAAATwAAAFAAAABPAAAA"
+ "UAAAAE8AAABQAAAATwAAAFAAAABPAAAAUAAAAE8AAABQAAAA"
+ "TwAAAFAAAABPAAAAUAAAAE8AAABQAAAADAAAAA0AAAANAAAA"
+ "DQAAAG8AAAAdAAAAHQAAAB4AAABPAAAAUAAAAE8AAABQAAAA"
+ "XwAAAGAAAABfAAAAYAAAAF8AAABgAAAAXwAAAGAAAABfAAAA"
+ "YAAAAF8AAABgAAAAXwAAAGAAAABfAAAAYAAAAF8AAABgAAAA"
+ "HAAAAB0AAAAdAAAAHQAAAB0AAAAdAAAAHQAAAB4AAABfAAAA"
+ "YAAAAF8AAABgAAAATwAAAFAAAABPAAAAUAAAAE8AAABQAAAA"
+ "TwAAAFAAAABPAAAAUAAAAE8AAABQAAAATwAAAFAAAABPAAAA"
+ "UAAAAE8AAABQAAAAHAAAAB0AAAAdAAAAHQAAAB0AAAAdAAAA"
+ "HQAAAB4AAABPAAAAUAAAAE8AAABQAAAAXwAAAGAAAABfAAAA"
+ "YAAAAF8AAABgAAAAXwAAAGAAAABfAAAAYAAAAF8AAABgAAAA"
+ "XwAAAGAAAABfAAAAYAAAAF8AAABgAAAAHAAAAB0AAAAdAAAA"
+ "gAAAAC0AAAAtAAAALQAAAC4AAABfAAAAYAAAAF8AAABgAAAA"
+ "TwAAAFAAAABPAAAAUAAAAE8AAABQAAAATwAAAFAAAABPAAAA"
+ "UAAAAE8AAABQAAAATwAAAFAAAABPAAAAUAAAAE8AAABQAAAA"
+ "HAAAAB0AAAAdAAAAHgAAAE8AAABQAAAATwAAAFAAAABPAAAA"
+ "UAAAAE8AAABQAAAAXwAAAGAAAABfAAAAYAAAAF8AAABgAAAA"
+ "XwAAAGAAAABfAAAAYAAAAF8AAABgAAAAXwAAAGAAAABfAAAA"
+ "YAAAAF8AAABgAAAAHAAAAB0AAAAdAAAAHgAAAF8AAABgAAAA"
+ "XwAAAGAAAABfAAAAYAAAAF8AAABgAAAATwAAAFAAAABPAAAA"
+ "UAAAAE8AAABQAAAATwAAAFAAAABPAAAAUAAAAE8AAABQAAAA"
+ "TwAAAFAAAABPAAAAUAAAAE8AAABQAAAAHAAAAB0AAAAdAAAA"
+ "HgAAAE8AAABQAAAATwAAAFAAAABPAAAAUAAAAE8AAABQAAAA"
+ "XwAAAGAAAABfAAAAYAAAAF8AAABgAAAAXwAAAGAAAABfAAAA"
+ "YAAAAF8AAABgAAAAXwAAAGAAAABfAAAAYAAAAF8AAABgAAAA"
+ "HAAAAB0AAAAdAAAAHgAAAF8AAABgAAAAXwAAAGAAAABfAAAA"
+ "YAAAAF8AAABgAAAA",
+ decoded_data, 4801);
+ base64_decode(
+ "eJztlsERAjEIRSlArUCtwLWVtQPtQDtwS18OHpwdIvwv7EFz"
+ "+MMhkyEvgR9GEbmoRjDuVYeXjsT+q+pGxGVedD96zl/n3ai2"
+ "hu7FvO9cLVXwRvNW8k6qwdC5mJfhyuBl3rHzdl6Et+VjlnaJ"
+ "vBE/+XRPrA+jeVu8qA97PvYM8qI+7NW5t876sFfn3jrbpxW8"
+ "kT7tvN/zPsT3IG++YnhRZfEyeVnek9g9G5E1X0V5s2OUNzt2"
+ "3hre5f/t9XcFHzJvZL4nMm9k8k7i9/da9bsGb+Q/+zfeGUt8"
+ "RgE=",
+ compressed_data, 4801);
+ decompress(compressed_data, decompressed_data, 4801);
+ decoded_data[4800] = '\0';
+ compressed_data[4800] = '\0';
+ decompressed_data[4800] = '\0';
+ if (strncmp(decoded_data, decompressed_data, 4800) == 0) {
+ puts("Matched!");
+ } else {
+ puts("No match. :(");
+ }
+
+ return 0;
+}
+#endif
diff --git a/src/compression.h b/src/compression.h
new file mode 100644
index 0000000..387d634
--- /dev/null
+++ b/src/compression.h
@@ -0,0 +1,6 @@
+#ifndef COMPRESSION_H
+#define COMPRESSION_H
+
+void decompress(const char *src, size_t src_len, char *dest, size_t dest_len);
+
+#endif
diff --git a/src/image.c b/src/image.c
new file mode 100644
index 0000000..557e1de
--- /dev/null
+++ b/src/image.c
@@ -0,0 +1,22 @@
+#include <SDL.h>
+#include <SDL_image.h>
+#include "logging.h"
+#include "image.h"
+
+SDL_Surface *
+load_png(const char *path)
+{
+ SDL_RWops *rwops;
+ SDL_Surface *img;
+
+ debug("Loading PNG image \"%s\"...", path);
+
+ rwops = SDL_RWFromFile(path, "rb");
+ img = IMG_LoadPNG_RW(rwops);
+ if (!img) {
+ err(1, "Failed to load image \"%s\" (%s)",
+ path, IMG_GetError());
+ }
+
+ return img;
+}
diff --git a/src/image.h b/src/image.h
new file mode 100644
index 0000000..0de4083
--- /dev/null
+++ b/src/image.h
@@ -0,0 +1,6 @@
+#ifndef IMAGE_H
+#define IMAGE_H
+
+SDL_Surface *load_png(const char *path);
+
+#endif
diff --git a/src/init.c b/src/init.c
new file mode 100644
index 0000000..f893d1d
--- /dev/null
+++ b/src/init.c
@@ -0,0 +1,36 @@
+#include <SDL.h>
+#include <SDL_image.h>
+#include "init.h"
+#include "logging.h"
+
+void
+init(void)
+{
+ debug("Initializing SDL...");
+ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) == -1) {
+ err(1, "Failed to initialize SDL (%s)", SDL_GetError());
+ }
+
+ debug("Initializing SDL_Image...");
+ if (IMG_Init(IMG_INIT_PNG) != IMG_INIT_PNG) {
+ err(1, "Failed to initialize SDL_Image (%s)", IMG_GetError());
+ }
+
+ debug("Setting video mode...");
+ screen = SDL_SetVideoMode(240, 160, 8, SDL_SWSURFACE | SDL_ANYFORMAT);
+ if (screen == NULL) {
+ err(1, "Failed to set video mode (%s)", SDL_GetError());
+ }
+}
+
+void
+quit(int status)
+{
+ debug("Quitting SDL_Image...");
+ IMG_Quit();
+
+ debug("Quitting SDL...");
+ SDL_Quit();
+
+ exit(status);
+}
diff --git a/src/init.h b/src/init.h
new file mode 100644
index 0000000..f04b1fe
--- /dev/null
+++ b/src/init.h
@@ -0,0 +1,11 @@
+#ifndef INIT_H
+#define INIT_H
+
+#include <SDL.h>
+
+SDL_Surface *screen;
+
+void init(void);
+void quit(int status);
+
+#endif
diff --git a/src/layer.h b/src/layer.h
new file mode 100644
index 0000000..126c3d9
--- /dev/null
+++ b/src/layer.h
@@ -0,0 +1,20 @@
+#ifndef LAYER_H
+#define LAYER_H
+
+#include <SDL_stdinc.h>
+
+const int LAYER_GROUND = 0;
+const int LAYER_OBJ_LOW = 1;
+const int LAYER_CHAR_BOT = 2;
+const int LAYER_OBJ_MID = 3;
+const int LAYER_CHAR_TOP = 4;
+const int LAYER_OBJ_HIGH = 5;
+const int LAYER_WEATHER = 6;
+
+struct layer {
+ char *name;
+ Uint32 *tiles;
+ struct layer *next;
+};
+
+#endif
diff --git a/src/logging.c b/src/logging.c
new file mode 100644
index 0000000..be03528
--- /dev/null
+++ b/src/logging.c
@@ -0,0 +1,43 @@
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "init.h"
+#include "logging.h"
+
+void
+debug(const char *fmt, ...)
+{
+ va_list ap;
+
+ printf("Debug: ");
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+ putchar('\n');
+}
+
+void
+warn(const char *fmt, ...)
+{
+ va_list ap;
+
+ printf("Warning: ");
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+ putchar('\n');
+}
+
+void
+err(int status, const char *fmt, ...)
+{
+ va_list ap;
+
+ printf("Error: ");
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+ putchar('\n');
+
+ quit(status);
+}
diff --git a/src/logging.h b/src/logging.h
new file mode 100644
index 0000000..e03dad6
--- /dev/null
+++ b/src/logging.h
@@ -0,0 +1,8 @@
+#ifndef LOGGING_H
+#define LOGGING_H
+
+void debug(const char *fmt, ...);
+void warn(const char *fmt, ...);
+void err(int status, const char *fmt, ...);
+
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..b30d0b2
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,65 @@
+#include <SDL.h>
+#include "init.h"
+#include "logging.h"
+#include "image.h"
+#include "tmx.h"
+
+int
+main(void)
+{
+ SDL_Surface *img;
+ SDL_Rect imgrect, surfacerect;
+
+ init();
+
+ tmx_load("data/forest1.tmx");
+
+ img = load_png("../forest-6-layer-test_ground.png");
+ imgrect.w = 240;
+ imgrect.h = 160;
+ surfacerect.x = 0;
+ surfacerect.y = 0;
+ surfacerect.w = 240;
+ surfacerect.h = 160;
+
+ imgrect.x = 208;
+ imgrect.y = 480;
+ SDL_BlitSurface(img, &imgrect, screen, &surfacerect);
+ SDL_Flip(screen);
+ SDL_Delay(500);
+
+ imgrect.x = 208;
+ imgrect.y = 464;
+ SDL_BlitSurface(img, &imgrect, screen, &surfacerect);
+ SDL_Flip(screen);
+ SDL_Delay(500);
+
+ imgrect.x = 224;
+ imgrect.y = 464;
+ SDL_BlitSurface(img, &imgrect, screen, &surfacerect);
+ SDL_Flip(screen);
+ SDL_Delay(500);
+
+ imgrect.x = 240;
+ imgrect.y = 464;
+ SDL_BlitSurface(img, &imgrect, screen, &surfacerect);
+ SDL_Flip(screen);
+ SDL_Delay(500);
+
+ imgrect.x = 256;
+ imgrect.y = 464;
+ SDL_BlitSurface(img, &imgrect, screen, &surfacerect);
+ SDL_Flip(screen);
+ SDL_Delay(500);
+
+ imgrect.x = 256;
+ imgrect.y = 448;
+ SDL_BlitSurface(img, &imgrect, screen, &surfacerect);
+ SDL_Flip(screen);
+ SDL_Delay(500);
+
+ quit(0);
+
+ /* Control doesn't actually reach here. */
+ return 0;
+}
diff --git a/src/map.h b/src/map.h
new file mode 100644
index 0000000..554b428
--- /dev/null
+++ b/src/map.h
@@ -0,0 +1,16 @@
+#ifndef MAP_H
+#define MAP_H
+
+#include "tileset.h"
+#include "layer.h"
+
+struct map {
+ int width;
+ int height;
+ int tilewidth;
+ int tileheight;
+ struct tileset *tilesets;
+ struct layer *layers;
+};
+
+#endif
diff --git a/src/tileset.h b/src/tileset.h
new file mode 100644
index 0000000..2fd384a
--- /dev/null
+++ b/src/tileset.h
@@ -0,0 +1,12 @@
+#ifndef TILESET_H
+#define TILESET_H
+
+struct tileset {
+ int firstgid;
+ int tilewidth;
+ int tileheight;
+ SDL_Surface *image;
+ struct tileset *next;
+};
+
+#endif
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;
+}
diff --git a/src/tmx.h b/src/tmx.h
new file mode 100644
index 0000000..e2ab766
--- /dev/null
+++ b/src/tmx.h
@@ -0,0 +1,8 @@
+#ifndef TMX_H
+#define TMX_H
+
+#include "map.h"
+
+struct map *tmx_load(const char *path);
+
+#endif