Переглянути джерело

[Spine-C] retain hash and version strings

jpoag 11 роки тому
батько
коміт
eb1e303017
2 змінених файлів з 912 додано та 908 видалено
  1. 126 122
      spine-c/src/spine/SkeletonData.c
  2. 786 786
      spine-c/src/spine/SkeletonJson.c

+ 126 - 122
spine-c/src/spine/SkeletonData.c

@@ -1,122 +1,126 @@
-/******************************************************************************
- * Spine Runtimes Software License
- * Version 2.1
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software (typically granted by licensing Spine), you
- * may not (a) modify, translate, adapt or otherwise create derivative works,
- * improvements of the Software or develop new applications using the Software
- * or (b) remove, delete, alter or obscure any trademarks or any copyright,
- * trademark, patent or other intellectual property or proprietary rights
- * notices on or in the Software, including any copy thereof. Redistributions
- * in binary or source form must include this license and terms.
- * 
- * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
- * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-#include <spine/SkeletonData.h>
-#include <string.h>
-#include <spine/extension.h>
-
-spSkeletonData* spSkeletonData_create () {
-	return NEW(spSkeletonData);
-}
-
-void spSkeletonData_dispose (spSkeletonData* self) {
-	int i;
-	for (i = 0; i < self->bonesCount; ++i)
-		spBoneData_dispose(self->bones[i]);
-	FREE(self->bones);
-
-	for (i = 0; i < self->slotsCount; ++i)
-		spSlotData_dispose(self->slots[i]);
-	FREE(self->slots);
-
-	for (i = 0; i < self->skinsCount; ++i)
-		spSkin_dispose(self->skins[i]);
-	FREE(self->skins);
-
-	for (i = 0; i < self->eventsCount; ++i)
-		spEventData_dispose(self->events[i]);
-	FREE(self->events);
-
-	for (i = 0; i < self->animationsCount; ++i)
-		spAnimation_dispose(self->animations[i]);
-	FREE(self->animations);
-
-	for (i = 0; i < self->ikConstraintsCount; ++i)
-		spIkConstraintData_dispose(self->ikConstraints[i]);
-	FREE(self->ikConstraints);
-
-	FREE(self);
-}
-
-spBoneData* spSkeletonData_findBone (const spSkeletonData* self, const char* boneName) {
-	int i;
-	for (i = 0; i < self->bonesCount; ++i)
-		if (strcmp(self->bones[i]->name, boneName) == 0) return self->bones[i];
-	return 0;
-}
-
-int spSkeletonData_findBoneIndex (const spSkeletonData* self, const char* boneName) {
-	int i;
-	for (i = 0; i < self->bonesCount; ++i)
-		if (strcmp(self->bones[i]->name, boneName) == 0) return i;
-	return -1;
-}
-
-spSlotData* spSkeletonData_findSlot (const spSkeletonData* self, const char* slotName) {
-	int i;
-	for (i = 0; i < self->slotsCount; ++i)
-		if (strcmp(self->slots[i]->name, slotName) == 0) return self->slots[i];
-	return 0;
-}
-
-int spSkeletonData_findSlotIndex (const spSkeletonData* self, const char* slotName) {
-	int i;
-	for (i = 0; i < self->slotsCount; ++i)
-		if (strcmp(self->slots[i]->name, slotName) == 0) return i;
-	return -1;
-}
-
-spSkin* spSkeletonData_findSkin (const spSkeletonData* self, const char* skinName) {
-	int i;
-	for (i = 0; i < self->skinsCount; ++i)
-		if (strcmp(self->skins[i]->name, skinName) == 0) return self->skins[i];
-	return 0;
-}
-
-spEventData* spSkeletonData_findEvent (const spSkeletonData* self, const char* eventName) {
-	int i;
-	for (i = 0; i < self->eventsCount; ++i)
-		if (strcmp(self->events[i]->name, eventName) == 0) return self->events[i];
-	return 0;
-}
-
-spAnimation* spSkeletonData_findAnimation (const spSkeletonData* self, const char* animationName) {
-	int i;
-	for (i = 0; i < self->animationsCount; ++i)
-		if (strcmp(self->animations[i]->name, animationName) == 0) return self->animations[i];
-	return 0;
-}
-
-spIkConstraintData* spSkeletonData_findIkConstraint (const spSkeletonData* self, const char* ikConstraintName) {
-	int i;
-	for (i = 0; i < self->ikConstraintsCount; ++i)
-		if (strcmp(self->ikConstraints[i]->name, ikConstraintName) == 0) return self->ikConstraints[i];
-	return 0;
-}
+/******************************************************************************
+ * Spine Runtimes Software License
+ * Version 2.1
+ * 
+ * Copyright (c) 2013, Esoteric Software
+ * All rights reserved.
+ * 
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to install, execute and perform the Spine Runtimes
+ * Software (the "Software") solely for internal use. Without the written
+ * permission of Esoteric Software (typically granted by licensing Spine), you
+ * may not (a) modify, translate, adapt or otherwise create derivative works,
+ * improvements of the Software or develop new applications using the Software
+ * or (b) remove, delete, alter or obscure any trademarks or any copyright,
+ * trademark, patent or other intellectual property or proprietary rights
+ * notices on or in the Software, including any copy thereof. Redistributions
+ * in binary or source form must include this license and terms.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#include <spine/SkeletonData.h>
+#include <string.h>
+#include <spine/extension.h>
+
+spSkeletonData* spSkeletonData_create () {
+	return NEW(spSkeletonData);
+}
+
+void spSkeletonData_dispose (spSkeletonData* self) {
+	int i;
+
+	for (i = 0; i < self->bonesCount; ++i)
+		spBoneData_dispose(self->bones[i]);
+	FREE(self->bones);
+
+	for (i = 0; i < self->slotsCount; ++i)
+		spSlotData_dispose(self->slots[i]);
+	FREE(self->slots);
+
+	for (i = 0; i < self->skinsCount; ++i)
+		spSkin_dispose(self->skins[i]);
+	FREE(self->skins);
+
+	for (i = 0; i < self->eventsCount; ++i)
+		spEventData_dispose(self->events[i]);
+	FREE(self->events);
+
+	for (i = 0; i < self->animationsCount; ++i)
+		spAnimation_dispose(self->animations[i]);
+	FREE(self->animations);
+
+	for (i = 0; i < self->ikConstraintsCount; ++i)
+		spIkConstraintData_dispose(self->ikConstraints[i]);
+	FREE(self->ikConstraints);
+
+	FREE(self->hash);
+	FREE(self->version);
+
+	FREE(self);
+}
+
+spBoneData* spSkeletonData_findBone (const spSkeletonData* self, const char* boneName) {
+	int i;
+	for (i = 0; i < self->bonesCount; ++i)
+		if (strcmp(self->bones[i]->name, boneName) == 0) return self->bones[i];
+	return 0;
+}
+
+int spSkeletonData_findBoneIndex (const spSkeletonData* self, const char* boneName) {
+	int i;
+	for (i = 0; i < self->bonesCount; ++i)
+		if (strcmp(self->bones[i]->name, boneName) == 0) return i;
+	return -1;
+}
+
+spSlotData* spSkeletonData_findSlot (const spSkeletonData* self, const char* slotName) {
+	int i;
+	for (i = 0; i < self->slotsCount; ++i)
+		if (strcmp(self->slots[i]->name, slotName) == 0) return self->slots[i];
+	return 0;
+}
+
+int spSkeletonData_findSlotIndex (const spSkeletonData* self, const char* slotName) {
+	int i;
+	for (i = 0; i < self->slotsCount; ++i)
+		if (strcmp(self->slots[i]->name, slotName) == 0) return i;
+	return -1;
+}
+
+spSkin* spSkeletonData_findSkin (const spSkeletonData* self, const char* skinName) {
+	int i;
+	for (i = 0; i < self->skinsCount; ++i)
+		if (strcmp(self->skins[i]->name, skinName) == 0) return self->skins[i];
+	return 0;
+}
+
+spEventData* spSkeletonData_findEvent (const spSkeletonData* self, const char* eventName) {
+	int i;
+	for (i = 0; i < self->eventsCount; ++i)
+		if (strcmp(self->events[i]->name, eventName) == 0) return self->events[i];
+	return 0;
+}
+
+spAnimation* spSkeletonData_findAnimation (const spSkeletonData* self, const char* animationName) {
+	int i;
+	for (i = 0; i < self->animationsCount; ++i)
+		if (strcmp(self->animations[i]->name, animationName) == 0) return self->animations[i];
+	return 0;
+}
+
+spIkConstraintData* spSkeletonData_findIkConstraint (const spSkeletonData* self, const char* ikConstraintName) {
+	int i;
+	for (i = 0; i < self->ikConstraintsCount; ++i)
+		if (strcmp(self->ikConstraints[i]->name, ikConstraintName) == 0) return self->ikConstraints[i];
+	return 0;
+}

+ 786 - 786
spine-c/src/spine/SkeletonJson.c

@@ -1,786 +1,786 @@
-/******************************************************************************
- * Spine Runtimes Software License
- * Version 2.1
- * 
- * Copyright (c) 2013, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to install, execute and perform the Spine Runtimes
- * Software (the "Software") solely for internal use. Without the written
- * permission of Esoteric Software (typically granted by licensing Spine), you
- * may not (a) modify, translate, adapt or otherwise create derivative works,
- * improvements of the Software or develop new applications using the Software
- * or (b) remove, delete, alter or obscure any trademarks or any copyright,
- * trademark, patent or other intellectual property or proprietary rights
- * notices on or in the Software, including any copy thereof. Redistributions
- * in binary or source form must include this license and terms.
- * 
- * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
- * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-#include <spine/SkeletonJson.h>
-#include <stdio.h>
-#include "Json.h"
-#include <spine/extension.h>
-#include <spine/AtlasAttachmentLoader.h>
-
-typedef struct {
-	spSkeletonJson super;
-	int ownsLoader;
-} _spSkeletonJson;
-
-spSkeletonJson* spSkeletonJson_createWithLoader (spAttachmentLoader* attachmentLoader) {
-	spSkeletonJson* self = SUPER(NEW(_spSkeletonJson));
-	self->scale = 1;
-	self->attachmentLoader = attachmentLoader;
-	return self;
-}
-
-spSkeletonJson* spSkeletonJson_create (spAtlas* atlas) {
-	spAtlasAttachmentLoader* attachmentLoader = spAtlasAttachmentLoader_create(atlas);
-	spSkeletonJson* self = spSkeletonJson_createWithLoader(SUPER(attachmentLoader));
-	SUB_CAST(_spSkeletonJson, self)->ownsLoader = 1;
-	return self;
-}
-
-void spSkeletonJson_dispose (spSkeletonJson* self) {
-	if (SUB_CAST(_spSkeletonJson, self)->ownsLoader) spAttachmentLoader_dispose(self->attachmentLoader);
-	FREE(self->error);
-	FREE(self);
-}
-
-void _spSkeletonJson_setError (spSkeletonJson* self, Json* root, const char* value1, const char* value2) {
-	char message[256];
-	int length;
-	FREE(self->error);
-	strcpy(message, value1);
-	length = (int)strlen(value1);
-	if (value2) strncat(message + length, value2, 256 - length);
-	MALLOC_STR(self->error, message);
-	if (root) Json_dispose(root);
-}
-
-static float toColor (const char* value, int index) {
-	char digits[3];
-	char *error;
-	int color;
-
-	if (strlen(value) != 8) return -1;
-	value += index * 2;
-
-	digits[0] = *value;
-	digits[1] = *(value + 1);
-	digits[2] = '\0';
-	color = (int)strtoul(digits, &error, 16);
-	if (*error != 0) return -1;
-	return color / (float)255;
-}
-
-static void readCurve (spCurveTimeline* timeline, int frameIndex, Json* frame) {
-	Json* curve = Json_getItem(frame, "curve");
-	if (!curve) return;
-	if (curve->type == Json_String && strcmp(curve->valueString, "stepped") == 0)
-		spCurveTimeline_setStepped(timeline, frameIndex);
-	else if (curve->type == Json_Array) {
-		Json* child0 = curve->child;
-		Json* child1 = child0->next;
-		Json* child2 = child1->next;
-		Json* child3 = child2->next;
-		spCurveTimeline_setCurve(timeline, frameIndex, child0->valueFloat, child1->valueFloat, child2->valueFloat,
-				child3->valueFloat);
-	}
-}
-
-static spAnimation* _spSkeletonJson_readAnimation (spSkeletonJson* self, Json* root, spSkeletonData *skeletonData) {
-	int i;
-	spAnimation* animation;
-	Json* frame;
-	float duration;
-	int timelinesCount = 0;
-
-	Json* bones = Json_getItem(root, "bones");
-	Json* slots = Json_getItem(root, "slots");
-	Json* ik = Json_getItem(root, "ik");
-	Json* ffd = Json_getItem(root, "ffd");
-	Json* drawOrder = Json_getItem(root, "drawOrder");
-	Json* events = Json_getItem(root, "events");
-	Json* flipX = Json_getItem(root, "flipx");
-	Json* flipY = Json_getItem(root, "flipy");
-	Json *boneMap, *slotMap, *ikMap, *ffdMap;
-	if (!drawOrder) drawOrder = Json_getItem(root, "draworder");
-
-	for (boneMap = bones ? bones->child : 0; boneMap; boneMap = boneMap->next)
-		timelinesCount += boneMap->size;
-	for (slotMap = slots ? slots->child : 0; slotMap; slotMap = slotMap->next)
-		timelinesCount += slotMap->size;
-	timelinesCount += ik ? ik->size : 0;
-	for (ffdMap = ffd ? ffd->child : 0; ffdMap; ffdMap = ffdMap->next)
-		for (slotMap = ffdMap->child; slotMap; slotMap = slotMap->next)
-			timelinesCount += slotMap->size;
-	if (drawOrder) ++timelinesCount;
-	if (events) ++timelinesCount;
-	if (flipX) ++timelinesCount;
-	if (flipY) ++timelinesCount;
-
-	animation = spAnimation_create(root->name, timelinesCount);
-	animation->timelinesCount = 0;
-	skeletonData->animations[skeletonData->animationsCount++] = animation;
-
-	/* Slot timelines. */
-	for (slotMap = slots ? slots->child : 0; slotMap; slotMap = slotMap->next) {
-		Json *timelineArray;
-
-		int slotIndex = spSkeletonData_findSlotIndex(skeletonData, slotMap->name);
-		if (slotIndex == -1) {
-			spAnimation_dispose(animation);
-			_spSkeletonJson_setError(self, root, "Slot not found: ", slotMap->name);
-			return 0;
-		}
-
-		for (timelineArray = slotMap->child; timelineArray; timelineArray = timelineArray->next) {
-			if (strcmp(timelineArray->name, "color") == 0) {
-				spColorTimeline *timeline = spColorTimeline_create(timelineArray->size);
-				timeline->slotIndex = slotIndex;
-				for (frame = timelineArray->child, i = 0; frame; frame = frame->next, ++i) {
-					const char* s = Json_getString(frame, "color", 0);
-					spColorTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), toColor(s, 0), toColor(s, 1), toColor(s, 2),
-							toColor(s, 3));
-					readCurve(SUPER(timeline), i, frame);
-				}
-				animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
-				duration = timeline->frames[timelineArray->size * 5 - 5];
-				if (duration > animation->duration) animation->duration = duration;
-
-			} else if (strcmp(timelineArray->name, "attachment") == 0) {
-				spAttachmentTimeline *timeline = spAttachmentTimeline_create(timelineArray->size);
-				timeline->slotIndex = slotIndex;
-				for (frame = timelineArray->child, i = 0; frame; frame = frame->next, ++i) {
-					Json* name = Json_getItem(frame, "name");
-					spAttachmentTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0),
-							name->type == Json_NULL ? 0 : name->valueString);
-				}
-				animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
-				duration = timeline->frames[timelineArray->size - 1];
-				if (duration > animation->duration) animation->duration = duration;
-
-			} else {
-				spAnimation_dispose(animation);
-				_spSkeletonJson_setError(self, 0, "Invalid timeline type for a slot: ", timelineArray->name);
-				return 0;
-			}
-		}
-	}
-
-	/* Bone timelines. */
-	for (boneMap = bones ? bones->child : 0; boneMap; boneMap = boneMap->next) {
-		Json *timelineArray;
-
-		int boneIndex = spSkeletonData_findBoneIndex(skeletonData, boneMap->name);
-		if (boneIndex == -1) {
-			spAnimation_dispose(animation);
-			_spSkeletonJson_setError(self, root, "Bone not found: ", boneMap->name);
-			return 0;
-		}
-
-		for (timelineArray = boneMap->child; timelineArray; timelineArray = timelineArray->next) {
-			if (strcmp(timelineArray->name, "rotate") == 0) {
-				spRotateTimeline *timeline = spRotateTimeline_create(timelineArray->size);
-				timeline->boneIndex = boneIndex;
-				for (frame = timelineArray->child, i = 0; frame; frame = frame->next, ++i) {
-					spRotateTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), Json_getFloat(frame, "angle", 0));
-					readCurve(SUPER(timeline), i, frame);
-				}
-				animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
-				duration = timeline->frames[timelineArray->size * 2 - 2];
-				if (duration > animation->duration) animation->duration = duration;
-
-			} else {
-				int isScale = strcmp(timelineArray->name, "scale") == 0;
-				if (isScale || strcmp(timelineArray->name, "translate") == 0) {
-					float scale = isScale ? 1 : self->scale;
-					spTranslateTimeline *timeline =
-							isScale ? spScaleTimeline_create(timelineArray->size) : spTranslateTimeline_create(timelineArray->size);
-					timeline->boneIndex = boneIndex;
-					for (frame = timelineArray->child, i = 0; frame; frame = frame->next, ++i) {
-						spTranslateTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), Json_getFloat(frame, "x", 0) * scale,
-								Json_getFloat(frame, "y", 0) * scale);
-						readCurve(SUPER(timeline), i, frame);
-					}
-					animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
-					duration = timeline->frames[timelineArray->size * 3 - 3];
-					if (duration > animation->duration) animation->duration = duration;
-				} else if (strcmp(timelineArray->name, "flipX") == 0 || strcmp(timelineArray->name, "flipY") == 0) {
-					int x = strcmp(timelineArray->name, "flipX") == 0;
-					char* field = x ? "x" : "y";
-					spFlipTimeline *timeline = spFlipTimeline_create(timelineArray->size, x);
-					timeline->boneIndex = boneIndex;
-					for (frame = timelineArray->child, i = 0; frame; frame = frame->next, ++i)
-						spFlipTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), Json_getInt(frame, field, 0));
-					animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
-					duration = timeline->frames[timelineArray->size * 2 - 2];
-					if (duration > animation->duration) animation->duration = duration;
-
-				} else {
-					spAnimation_dispose(animation);
-					_spSkeletonJson_setError(self, 0, "Invalid timeline type for a bone: ", timelineArray->name);
-					return 0;
-				}
-			}
-		}
-	}
-
-	/* IK timelines. */
-	for (ikMap = ik ? ik->child : 0; ikMap; ikMap = ikMap->next) {
-		spIkConstraintData* ikConstraint = spSkeletonData_findIkConstraint(skeletonData, ikMap->name);
-		spIkConstraintTimeline* timeline = spIkConstraintTimeline_create(ikMap->size);
-		for (i = 0; i < skeletonData->ikConstraintsCount; ++i) {
-			if (ikConstraint == skeletonData->ikConstraints[i]) {
-				timeline->ikConstraintIndex = i;
-				break;
-			}
-		}
-		for (frame = ikMap->child, i = 0; frame; frame = frame->next, ++i) {
-			spIkConstraintTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), Json_getFloat(frame, "mix", 0),
-					Json_getInt(frame, "bendPositive", 1) ? 1 : -1);
-			readCurve(SUPER(timeline), i, frame);
-		}
-		animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
-		duration = timeline->frames[ikMap->size * 3 - 3];
-		if (duration > animation->duration) animation->duration = duration;
-	}
-
-	/* FFD timelines. */
-	for (ffdMap = ffd ? ffd->child : 0; ffdMap; ffdMap = ffdMap->next) {
-		spSkin* skin = spSkeletonData_findSkin(skeletonData, ffdMap->name);
-		for (slotMap = ffdMap->child; slotMap; slotMap = slotMap->next) {
-			int slotIndex = spSkeletonData_findSlotIndex(skeletonData, slotMap->name);
-			Json* timelineArray;
-			for (timelineArray = slotMap->child; timelineArray; timelineArray = timelineArray->next) {
-				Json* frame;
-				int verticesCount = 0;
-				float* tempVertices;
-				spFFDTimeline *timeline;
-
-				spAttachment* attachment = spSkin_getAttachment(skin, slotIndex, timelineArray->name);
-				if (!attachment) {
-					spAnimation_dispose(animation);
-					_spSkeletonJson_setError(self, 0, "Attachment not found: ", timelineArray->name);
-					return 0;
-				}
-				if (attachment->type == SP_ATTACHMENT_MESH)
-					verticesCount = SUB_CAST(spMeshAttachment, attachment)->verticesCount;
-				else if (attachment->type == SP_ATTACHMENT_SKINNED_MESH)
-					verticesCount = SUB_CAST(spSkinnedMeshAttachment, attachment)->weightsCount / 3 * 2;
-
-				timeline = spFFDTimeline_create(timelineArray->size, verticesCount);
-				timeline->slotIndex = slotIndex;
-				timeline->attachment = attachment;
-
-				tempVertices = MALLOC(float, verticesCount);
-				for (frame = timelineArray->child, i = 0; frame; frame = frame->next, ++i) {
-					Json* vertices = Json_getItem(frame, "vertices");
-					float* frameVertices;
-					if (!vertices) {
-						if (attachment->type == SP_ATTACHMENT_MESH)
-							frameVertices = SUB_CAST(spMeshAttachment, attachment)->vertices;
-						else {
-							frameVertices = tempVertices;
-							memset(frameVertices, 0, sizeof(float) * verticesCount);
-						}
-					} else {
-						int v, start = Json_getInt(frame, "offset", 0);
-						Json* vertex;
-						frameVertices = tempVertices;
-						memset(frameVertices, 0, sizeof(float) * start);
-						if (self->scale == 1) {
-							for (vertex = vertices->child, v = start; vertex; vertex = vertex->next, ++v)
-								frameVertices[v] = vertex->valueFloat;
-						} else {
-							for (vertex = vertices->child, v = start; vertex; vertex = vertex->next, ++v)
-								frameVertices[v] = vertex->valueFloat * self->scale;
-						}
-						memset(frameVertices + v, 0, sizeof(float) * (verticesCount - v));
-						if (attachment->type == SP_ATTACHMENT_MESH) {
-							float* meshVertices = SUB_CAST(spMeshAttachment, attachment)->vertices;
-							for (v = 0; v < verticesCount; ++v)
-								frameVertices[v] += meshVertices[v];
-						}
-					}
-					spFFDTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), frameVertices);
-					readCurve(SUPER(timeline), i, frame);
-				}
-				FREE(tempVertices);
-
-				animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
-				duration = timeline->frames[timelineArray->size - 1];
-				if (duration > animation->duration) animation->duration = duration;
-			}
-		}
-	}
-
-	/* Draw order timeline. */
-	if (drawOrder) {
-		spDrawOrderTimeline* timeline = spDrawOrderTimeline_create(drawOrder->size, skeletonData->slotsCount);
-		for (frame = drawOrder->child, i = 0; frame; frame = frame->next, ++i) {
-			int ii;
-			int* drawOrder = 0;
-			Json* offsets = Json_getItem(frame, "offsets");
-			if (offsets) {
-				Json* offsetMap;
-				int* unchanged = MALLOC(int, skeletonData->slotsCount - offsets->size);
-				int originalIndex = 0, unchangedIndex = 0;
-
-				drawOrder = MALLOC(int, skeletonData->slotsCount);
-				for (ii = skeletonData->slotsCount - 1; ii >= 0; --ii)
-					drawOrder[ii] = -1;
-
-				for (offsetMap = offsets->child; offsetMap; offsetMap = offsetMap->next) {
-					int slotIndex = spSkeletonData_findSlotIndex(skeletonData, Json_getString(offsetMap, "slot", 0));
-					if (slotIndex == -1) {
-						spAnimation_dispose(animation);
-						_spSkeletonJson_setError(self, 0, "Slot not found: ", Json_getString(offsetMap, "slot", 0));
-						return 0;
-					}
-					/* Collect unchanged items. */
-					while (originalIndex != slotIndex)
-						unchanged[unchangedIndex++] = originalIndex++;
-					/* Set changed items. */
-					drawOrder[originalIndex + Json_getInt(offsetMap, "offset", 0)] = originalIndex;
-					originalIndex++;
-				}
-				/* Collect remaining unchanged items. */
-				while (originalIndex < skeletonData->slotsCount)
-					unchanged[unchangedIndex++] = originalIndex++;
-				/* Fill in unchanged items. */
-				for (ii = skeletonData->slotsCount - 1; ii >= 0; ii--)
-					if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex];
-				FREE(unchanged);
-			}
-			spDrawOrderTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), drawOrder);
-			FREE(drawOrder);
-		}
-		animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
-		duration = timeline->frames[drawOrder->size - 1];
-		if (duration > animation->duration) animation->duration = duration;
-	}
-
-	/* Event timeline. */
-	if (events) {
-		Json* frame;
-
-		spEventTimeline* timeline = spEventTimeline_create(events->size);
-		for (frame = events->child, i = 0; frame; frame = frame->next, ++i) {
-			spEvent* event;
-			const char* stringValue;
-			spEventData* eventData = spSkeletonData_findEvent(skeletonData, Json_getString(frame, "name", 0));
-			if (!eventData) {
-				spAnimation_dispose(animation);
-				_spSkeletonJson_setError(self, 0, "Event not found: ", Json_getString(frame, "name", 0));
-				return 0;
-			}
-			event = spEvent_create(eventData);
-			event->intValue = Json_getInt(frame, "int", eventData->intValue);
-			event->floatValue = Json_getFloat(frame, "float", eventData->floatValue);
-			stringValue = Json_getString(frame, "string", eventData->stringValue);
-			if (stringValue) MALLOC_STR(event->stringValue, stringValue);
-			spEventTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), event);
-		}
-		animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
-		duration = timeline->frames[events->size - 1];
-		if (duration > animation->duration) animation->duration = duration;
-	}
-
-	return animation;
-}
-
-spSkeletonData* spSkeletonJson_readSkeletonDataFile (spSkeletonJson* self, const char* path) {
-	int length;
-	spSkeletonData* skeletonData;
-	const char* json = _spUtil_readFile(path, &length);
-	if (!json) {
-		_spSkeletonJson_setError(self, 0, "Unable to read skeleton file: ", path);
-		return 0;
-	}
-	skeletonData = spSkeletonJson_readSkeletonData(self, json);
-	FREE(json);
-	return skeletonData;
-}
-
-spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const char* json) {
-	int i, ii;
-	spSkeletonData* skeletonData;
-	Json *root, *skeleton, *bones, *boneMap, *ik, *slots, *skins, *animations, *events;
-
-	FREE(self->error);
-	CONST_CAST(char*, self->error) = 0;
-
-	root = Json_create(json);
-	if (!root) {
-		_spSkeletonJson_setError(self, 0, "Invalid skeleton JSON: ", Json_getError());
-		return 0;
-	}
-
-	skeletonData = spSkeletonData_create();
-
-	skeleton = Json_getItem(root, "skeleton");
-	if (skeleton) {
-		skeletonData->hash = Json_getString(skeleton, "hash", 0);
-		skeletonData->version = Json_getString(skeleton, "spine", 0);
-		skeletonData->width = Json_getFloat(skeleton, "width", 0);
-		skeletonData->height = Json_getFloat(skeleton, "height", 0);
-	}
-
-	/* Bones. */
-	bones = Json_getItem(root, "bones");
-	skeletonData->bones = MALLOC(spBoneData*, bones->size);
-	for (boneMap = bones->child, i = 0; boneMap; boneMap = boneMap->next, ++i) {
-		spBoneData* boneData;
-
-		spBoneData* parent = 0;
-		const char* parentName = Json_getString(boneMap, "parent", 0);
-		if (parentName) {
-			parent = spSkeletonData_findBone(skeletonData, parentName);
-			if (!parent) {
-				spSkeletonData_dispose(skeletonData);
-				_spSkeletonJson_setError(self, root, "Parent bone not found: ", parentName);
-				return 0;
-			}
-		}
-
-		boneData = spBoneData_create(Json_getString(boneMap, "name", 0), parent);
-		boneData->length = Json_getFloat(boneMap, "length", 0) * self->scale;
-		boneData->x = Json_getFloat(boneMap, "x", 0) * self->scale;
-		boneData->y = Json_getFloat(boneMap, "y", 0) * self->scale;
-		boneData->rotation = Json_getFloat(boneMap, "rotation", 0);
-		boneData->scaleX = Json_getFloat(boneMap, "scaleX", 1);
-		boneData->scaleY = Json_getFloat(boneMap, "scaleY", 1);
-		boneData->inheritScale = Json_getInt(boneMap, "inheritScale", 1);
-		boneData->inheritRotation = Json_getInt(boneMap, "inheritRotation", 1);
-
-		skeletonData->bones[i] = boneData;
-		skeletonData->bonesCount++;
-	}
-
-	/* IK constraints. */
-	ik = Json_getItem(root, "ik");
-	if (ik) {
-		Json *ikMap;
-		skeletonData->ikConstraintsCount = ik->size;
-		skeletonData->ikConstraints = MALLOC(spIkConstraintData*, ik->size);
-		for (ikMap = ik->child, i = 0; ikMap; ikMap = ikMap->next, ++i) {
-			const char* targetName;
-
-			spIkConstraintData* ikConstraintData = spIkConstraintData_create(Json_getString(ikMap, "name", 0));
-			boneMap = Json_getItem(ikMap, "bones");
-			ikConstraintData->bonesCount = boneMap->size;
-			ikConstraintData->bones = MALLOC(spBoneData*, boneMap->size);
-			for (boneMap = boneMap->child, ii = 0; boneMap; boneMap = boneMap->next, ++ii) {
-				ikConstraintData->bones[ii] = spSkeletonData_findBone(skeletonData, boneMap->valueString);
-				if (!ikConstraintData->bones[ii]) {
-					spSkeletonData_dispose(skeletonData);
-					_spSkeletonJson_setError(self, root, "IK bone not found: ", boneMap->valueString);
-					return 0;
-				}
-			}
-
-			targetName = Json_getString(ikMap, "target", 0);
-			ikConstraintData->target = spSkeletonData_findBone(skeletonData, targetName);
-			if (!ikConstraintData->target) {
-				spSkeletonData_dispose(skeletonData);
-				_spSkeletonJson_setError(self, root, "Target bone not found: ", boneMap->name);
-				return 0;
-			}
-
-			ikConstraintData->bendDirection = Json_getInt(ikMap, "bendPositive", 1) ? 1 : -1;
-			ikConstraintData->mix = Json_getFloat(ikMap, "mix", 1);
-
-			skeletonData->ikConstraints[i] = ikConstraintData;
-		}
-	}
-
-	/* Slots. */
-	slots = Json_getItem(root, "slots");
-	if (slots) {
-		Json *slotMap;
-		skeletonData->slotsCount = slots->size;
-		skeletonData->slots = MALLOC(spSlotData*, slots->size);
-		for (slotMap = slots->child, i = 0; slotMap; slotMap = slotMap->next, ++i) {
-			spSlotData* slotData;
-			const char* color;
-			Json *attachmentItem;
-
-			const char* boneName = Json_getString(slotMap, "bone", 0);
-			spBoneData* boneData = spSkeletonData_findBone(skeletonData, boneName);
-			if (!boneData) {
-				spSkeletonData_dispose(skeletonData);
-				_spSkeletonJson_setError(self, root, "Slot bone not found: ", boneName);
-				return 0;
-			}
-
-			slotData = spSlotData_create(Json_getString(slotMap, "name", 0), boneData);
-
-			color = Json_getString(slotMap, "color", 0);
-			if (color) {
-				slotData->r = toColor(color, 0);
-				slotData->g = toColor(color, 1);
-				slotData->b = toColor(color, 2);
-				slotData->a = toColor(color, 3);
-			}
-
-			attachmentItem = Json_getItem(slotMap, "attachment");
-			if (attachmentItem) spSlotData_setAttachmentName(slotData, attachmentItem->valueString);
-
-			slotData->additiveBlending = Json_getInt(slotMap, "additive", 0);
-
-			skeletonData->slots[i] = slotData;
-		}
-	}
-
-	/* Skins. */
-	skins = Json_getItem(root, "skins");
-	if (skins) {
-		Json *slotMap;
-		skeletonData->skinsCount = skins->size;
-		skeletonData->skins = MALLOC(spSkin*, skins->size);
-		for (slotMap = skins->child, i = 0; slotMap; slotMap = slotMap->next, ++i) {
-			Json *attachmentsMap;
-			spSkin *skin = spSkin_create(slotMap->name);
-
-			skeletonData->skins[i] = skin;
-			if (strcmp(slotMap->name, "default") == 0) skeletonData->defaultSkin = skin;
-
-			for (attachmentsMap = slotMap->child; attachmentsMap; attachmentsMap = attachmentsMap->next) {
-				int slotIndex = spSkeletonData_findSlotIndex(skeletonData, attachmentsMap->name);
-				Json *attachmentMap;
-
-				for (attachmentMap = attachmentsMap->child; attachmentMap; attachmentMap = attachmentMap->next) {
-					spAttachment* attachment;
-					const char* skinAttachmentName = attachmentMap->name;
-					const char* attachmentName = Json_getString(attachmentMap, "name", skinAttachmentName);
-					const char* path = Json_getString(attachmentMap, "path", attachmentName);
-					const char* color;
-					int i;
-					Json* entry;
-
-					const char* typeString = Json_getString(attachmentMap, "type", "region");
-					spAttachmentType type;
-					if (strcmp(typeString, "region") == 0)
-						type = SP_ATTACHMENT_REGION;
-					else if (strcmp(typeString, "mesh") == 0)
-						type = SP_ATTACHMENT_MESH;
-					else if (strcmp(typeString, "skinnedmesh") == 0)
-						type = SP_ATTACHMENT_SKINNED_MESH;
-					else if (strcmp(typeString, "boundingbox") == 0)
-						type = SP_ATTACHMENT_BOUNDING_BOX;
-					else {
-						spSkeletonData_dispose(skeletonData);
-						_spSkeletonJson_setError(self, root, "Unknown attachment type: ", typeString);
-						return 0;
-					}
-
-					attachment = spAttachmentLoader_newAttachment(self->attachmentLoader, skin, type, attachmentName, path);
-					if (!attachment) {
-						if (self->attachmentLoader->error1) {
-							spSkeletonData_dispose(skeletonData);
-							_spSkeletonJson_setError(self, root, self->attachmentLoader->error1, self->attachmentLoader->error2);
-							return 0;
-						}
-						continue;
-					}
-
-					switch (attachment->type) {
-					case SP_ATTACHMENT_REGION: {
-						spRegionAttachment* region = SUB_CAST(spRegionAttachment, attachment);
-						if (path) MALLOC_STR(region->path, path);
-						region->x = Json_getFloat(attachmentMap, "x", 0) * self->scale;
-						region->y = Json_getFloat(attachmentMap, "y", 0) * self->scale;
-						region->scaleX = Json_getFloat(attachmentMap, "scaleX", 1);
-						region->scaleY = Json_getFloat(attachmentMap, "scaleY", 1);
-						region->rotation = Json_getFloat(attachmentMap, "rotation", 0);
-						region->width = Json_getFloat(attachmentMap, "width", 32) * self->scale;
-						region->height = Json_getFloat(attachmentMap, "height", 32) * self->scale;
-
-						color = Json_getString(attachmentMap, "color", 0);
-						if (color) {
-							region->r = toColor(color, 0);
-							region->g = toColor(color, 1);
-							region->b = toColor(color, 2);
-							region->a = toColor(color, 3);
-						}
-
-						spRegionAttachment_updateOffset(region);
-						break;
-					}
-					case SP_ATTACHMENT_MESH: {
-						spMeshAttachment* mesh = SUB_CAST(spMeshAttachment, attachment);
-
-						MALLOC_STR(mesh->path, path);
-
-						entry = Json_getItem(attachmentMap, "vertices");
-						mesh->verticesCount = entry->size;
-						mesh->vertices = MALLOC(float, entry->size);
-						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
-							mesh->vertices[i] = entry->valueFloat * self->scale;
-
-						entry = Json_getItem(attachmentMap, "triangles");
-						mesh->trianglesCount = entry->size;
-						mesh->triangles = MALLOC(int, entry->size);
-						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
-							mesh->triangles[i] = entry->valueInt;
-
-						entry = Json_getItem(attachmentMap, "uvs");
-						mesh->regionUVs = MALLOC(float, entry->size);
-						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
-							mesh->regionUVs[i] = entry->valueFloat;
-
-						spMeshAttachment_updateUVs(mesh);
-
-						color = Json_getString(attachmentMap, "color", 0);
-						if (color) {
-							mesh->r = toColor(color, 0);
-							mesh->g = toColor(color, 1);
-							mesh->b = toColor(color, 2);
-							mesh->a = toColor(color, 3);
-						}
-
-						mesh->hullLength = Json_getInt(attachmentMap, "hull", 0);
-
-						entry = Json_getItem(attachmentMap, "edges");
-						if (entry) {
-							mesh->edgesCount = entry->size;
-							mesh->edges = MALLOC(int, entry->size);
-							for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
-								mesh->edges[i] = entry->valueInt;
-						}
-
-						mesh->width = Json_getFloat(attachmentMap, "width", 32) * self->scale;
-						mesh->height = Json_getFloat(attachmentMap, "height", 32) * self->scale;
-						break;
-					}
-					case SP_ATTACHMENT_SKINNED_MESH: {
-						spSkinnedMeshAttachment* mesh = SUB_CAST(spSkinnedMeshAttachment, attachment);
-						int verticesCount, b, w, nn;
-						float* vertices;
-
-						MALLOC_STR(mesh->path, path);
-
-						entry = Json_getItem(attachmentMap, "uvs");
-						mesh->uvsCount = entry->size;
-						mesh->regionUVs = MALLOC(float, entry->size);
-						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
-							mesh->regionUVs[i] = entry->valueFloat;
-
-						entry = Json_getItem(attachmentMap, "vertices");
-						verticesCount = entry->size;
-						vertices = MALLOC(float, entry->size);
-						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
-							vertices[i] = entry->valueFloat;
-
-						for (i = 0; i < verticesCount;) {
-							int bonesCount = (int)vertices[i];
-							mesh->bonesCount += bonesCount + 1;
-							mesh->weightsCount += bonesCount * 3;
-							i += 1 + bonesCount * 4;
-						}
-						mesh->bones = MALLOC(int, mesh->bonesCount);
-						mesh->weights = MALLOC(float, mesh->weightsCount);
-
-						for (i = 0, b = 0, w = 0; i < verticesCount;) {
-							int bonesCount = (int)vertices[i++];
-							mesh->bones[b++] = bonesCount;
-							for (nn = i + bonesCount * 4; i < nn; i += 4, ++b, w += 3) {
-								mesh->bones[b] = (int)vertices[i];
-								mesh->weights[w] = vertices[i + 1] * self->scale;
-								mesh->weights[w + 1] = vertices[i + 2] * self->scale;
-								mesh->weights[w + 2] = vertices[i + 3];
-							}
-						}
-
-						FREE(vertices);
-
-						entry = Json_getItem(attachmentMap, "triangles");
-						mesh->trianglesCount = entry->size;
-						mesh->triangles = MALLOC(int, entry->size);
-						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
-							mesh->triangles[i] = entry->valueInt;
-
-						spSkinnedMeshAttachment_updateUVs(mesh);
-
-						color = Json_getString(attachmentMap, "color", 0);
-						if (color) {
-							mesh->r = toColor(color, 0);
-							mesh->g = toColor(color, 1);
-							mesh->b = toColor(color, 2);
-							mesh->a = toColor(color, 3);
-						}
-
-						mesh->hullLength = Json_getInt(attachmentMap, "hull", 0);
-
-						entry = Json_getItem(attachmentMap, "edges");
-						if (entry) {
-							mesh->edgesCount = entry->size;
-							mesh->edges = MALLOC(int, entry->size);
-							for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
-								mesh->edges[i] = entry->valueInt;
-						}
-
-						mesh->width = Json_getFloat(attachmentMap, "width", 32) * self->scale;
-						mesh->height = Json_getFloat(attachmentMap, "height", 32) * self->scale;
-						break;
-					}
-					case SP_ATTACHMENT_BOUNDING_BOX: {
-						spBoundingBoxAttachment* box = SUB_CAST(spBoundingBoxAttachment, attachment);
-						entry = Json_getItem(attachmentMap, "vertices");
-						box->verticesCount = entry->size;
-						box->vertices = MALLOC(float, entry->size);
-						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
-							box->vertices[i] = entry->valueFloat * self->scale;
-						break;
-					}
-					}
-
-					spSkin_addAttachment(skin, slotIndex, skinAttachmentName, attachment);
-				}
-			}
-		}
-	}
-
-	/* Events. */
-	events = Json_getItem(root, "events");
-	if (events) {
-		Json *eventMap;
-		const char* stringValue;
-		skeletonData->eventsCount = events->size;
-		skeletonData->events = MALLOC(spEventData*, events->size);
-		for (eventMap = events->child, i = 0; eventMap; eventMap = eventMap->next, ++i) {
-			spEventData* eventData = spEventData_create(eventMap->name);
-			eventData->intValue = Json_getInt(eventMap, "int", 0);
-			eventData->floatValue = Json_getFloat(eventMap, "float", 0);
-			stringValue = Json_getString(eventMap, "string", 0);
-			if (stringValue) MALLOC_STR(eventData->stringValue, stringValue);
-			skeletonData->events[i] = eventData;
-		}
-	}
-
-	/* Animations. */
-	animations = Json_getItem(root, "animations");
-	if (animations) {
-		Json *animationMap;
-		skeletonData->animations = MALLOC(spAnimation*, animations->size);
-		for (animationMap = animations->child; animationMap; animationMap = animationMap->next)
-			_spSkeletonJson_readAnimation(self, animationMap, skeletonData);
-	}
-
-	Json_dispose(root);
-	return skeletonData;
-}
+/******************************************************************************
+ * Spine Runtimes Software License
+ * Version 2.1
+ * 
+ * Copyright (c) 2013, Esoteric Software
+ * All rights reserved.
+ * 
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to install, execute and perform the Spine Runtimes
+ * Software (the "Software") solely for internal use. Without the written
+ * permission of Esoteric Software (typically granted by licensing Spine), you
+ * may not (a) modify, translate, adapt or otherwise create derivative works,
+ * improvements of the Software or develop new applications using the Software
+ * or (b) remove, delete, alter or obscure any trademarks or any copyright,
+ * trademark, patent or other intellectual property or proprietary rights
+ * notices on or in the Software, including any copy thereof. Redistributions
+ * in binary or source form must include this license and terms.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#include <spine/SkeletonJson.h>
+#include <stdio.h>
+#include "Json.h"
+#include <spine/extension.h>
+#include <spine/AtlasAttachmentLoader.h>
+
+typedef struct {
+	spSkeletonJson super;
+	int ownsLoader;
+} _spSkeletonJson;
+
+spSkeletonJson* spSkeletonJson_createWithLoader (spAttachmentLoader* attachmentLoader) {
+	spSkeletonJson* self = SUPER(NEW(_spSkeletonJson));
+	self->scale = 1;
+	self->attachmentLoader = attachmentLoader;
+	return self;
+}
+
+spSkeletonJson* spSkeletonJson_create (spAtlas* atlas) {
+	spAtlasAttachmentLoader* attachmentLoader = spAtlasAttachmentLoader_create(atlas);
+	spSkeletonJson* self = spSkeletonJson_createWithLoader(SUPER(attachmentLoader));
+	SUB_CAST(_spSkeletonJson, self)->ownsLoader = 1;
+	return self;
+}
+
+void spSkeletonJson_dispose (spSkeletonJson* self) {
+	if (SUB_CAST(_spSkeletonJson, self)->ownsLoader) spAttachmentLoader_dispose(self->attachmentLoader);
+	FREE(self->error);
+	FREE(self);
+}
+
+void _spSkeletonJson_setError (spSkeletonJson* self, Json* root, const char* value1, const char* value2) {
+	char message[256];
+	int length;
+	FREE(self->error);
+	strcpy(message, value1);
+	length = (int)strlen(value1);
+	if (value2) strncat(message + length, value2, 256 - length);
+	MALLOC_STR(self->error, message);
+	if (root) Json_dispose(root);
+}
+
+static float toColor (const char* value, int index) {
+	char digits[3];
+	char *error;
+	int color;
+
+	if (strlen(value) != 8) return -1;
+	value += index * 2;
+
+	digits[0] = *value;
+	digits[1] = *(value + 1);
+	digits[2] = '\0';
+	color = (int)strtoul(digits, &error, 16);
+	if (*error != 0) return -1;
+	return color / (float)255;
+}
+
+static void readCurve (spCurveTimeline* timeline, int frameIndex, Json* frame) {
+	Json* curve = Json_getItem(frame, "curve");
+	if (!curve) return;
+	if (curve->type == Json_String && strcmp(curve->valueString, "stepped") == 0)
+		spCurveTimeline_setStepped(timeline, frameIndex);
+	else if (curve->type == Json_Array) {
+		Json* child0 = curve->child;
+		Json* child1 = child0->next;
+		Json* child2 = child1->next;
+		Json* child3 = child2->next;
+		spCurveTimeline_setCurve(timeline, frameIndex, child0->valueFloat, child1->valueFloat, child2->valueFloat,
+				child3->valueFloat);
+	}
+}
+
+static spAnimation* _spSkeletonJson_readAnimation (spSkeletonJson* self, Json* root, spSkeletonData *skeletonData) {
+	int i;
+	spAnimation* animation;
+	Json* frame;
+	float duration;
+	int timelinesCount = 0;
+
+	Json* bones = Json_getItem(root, "bones");
+	Json* slots = Json_getItem(root, "slots");
+	Json* ik = Json_getItem(root, "ik");
+	Json* ffd = Json_getItem(root, "ffd");
+	Json* drawOrder = Json_getItem(root, "drawOrder");
+	Json* events = Json_getItem(root, "events");
+	Json* flipX = Json_getItem(root, "flipx");
+	Json* flipY = Json_getItem(root, "flipy");
+	Json *boneMap, *slotMap, *ikMap, *ffdMap;
+	if (!drawOrder) drawOrder = Json_getItem(root, "draworder");
+
+	for (boneMap = bones ? bones->child : 0; boneMap; boneMap = boneMap->next)
+		timelinesCount += boneMap->size;
+	for (slotMap = slots ? slots->child : 0; slotMap; slotMap = slotMap->next)
+		timelinesCount += slotMap->size;
+	timelinesCount += ik ? ik->size : 0;
+	for (ffdMap = ffd ? ffd->child : 0; ffdMap; ffdMap = ffdMap->next)
+		for (slotMap = ffdMap->child; slotMap; slotMap = slotMap->next)
+			timelinesCount += slotMap->size;
+	if (drawOrder) ++timelinesCount;
+	if (events) ++timelinesCount;
+	if (flipX) ++timelinesCount;
+	if (flipY) ++timelinesCount;
+
+	animation = spAnimation_create(root->name, timelinesCount);
+	animation->timelinesCount = 0;
+	skeletonData->animations[skeletonData->animationsCount++] = animation;
+
+	/* Slot timelines. */
+	for (slotMap = slots ? slots->child : 0; slotMap; slotMap = slotMap->next) {
+		Json *timelineArray;
+
+		int slotIndex = spSkeletonData_findSlotIndex(skeletonData, slotMap->name);
+		if (slotIndex == -1) {
+			spAnimation_dispose(animation);
+			_spSkeletonJson_setError(self, root, "Slot not found: ", slotMap->name);
+			return 0;
+		}
+
+		for (timelineArray = slotMap->child; timelineArray; timelineArray = timelineArray->next) {
+			if (strcmp(timelineArray->name, "color") == 0) {
+				spColorTimeline *timeline = spColorTimeline_create(timelineArray->size);
+				timeline->slotIndex = slotIndex;
+				for (frame = timelineArray->child, i = 0; frame; frame = frame->next, ++i) {
+					const char* s = Json_getString(frame, "color", 0);
+					spColorTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), toColor(s, 0), toColor(s, 1), toColor(s, 2),
+							toColor(s, 3));
+					readCurve(SUPER(timeline), i, frame);
+				}
+				animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
+				duration = timeline->frames[timelineArray->size * 5 - 5];
+				if (duration > animation->duration) animation->duration = duration;
+
+			} else if (strcmp(timelineArray->name, "attachment") == 0) {
+				spAttachmentTimeline *timeline = spAttachmentTimeline_create(timelineArray->size);
+				timeline->slotIndex = slotIndex;
+				for (frame = timelineArray->child, i = 0; frame; frame = frame->next, ++i) {
+					Json* name = Json_getItem(frame, "name");
+					spAttachmentTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0),
+							name->type == Json_NULL ? 0 : name->valueString);
+				}
+				animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
+				duration = timeline->frames[timelineArray->size - 1];
+				if (duration > animation->duration) animation->duration = duration;
+
+			} else {
+				spAnimation_dispose(animation);
+				_spSkeletonJson_setError(self, 0, "Invalid timeline type for a slot: ", timelineArray->name);
+				return 0;
+			}
+		}
+	}
+
+	/* Bone timelines. */
+	for (boneMap = bones ? bones->child : 0; boneMap; boneMap = boneMap->next) {
+		Json *timelineArray;
+
+		int boneIndex = spSkeletonData_findBoneIndex(skeletonData, boneMap->name);
+		if (boneIndex == -1) {
+			spAnimation_dispose(animation);
+			_spSkeletonJson_setError(self, root, "Bone not found: ", boneMap->name);
+			return 0;
+		}
+
+		for (timelineArray = boneMap->child; timelineArray; timelineArray = timelineArray->next) {
+			if (strcmp(timelineArray->name, "rotate") == 0) {
+				spRotateTimeline *timeline = spRotateTimeline_create(timelineArray->size);
+				timeline->boneIndex = boneIndex;
+				for (frame = timelineArray->child, i = 0; frame; frame = frame->next, ++i) {
+					spRotateTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), Json_getFloat(frame, "angle", 0));
+					readCurve(SUPER(timeline), i, frame);
+				}
+				animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
+				duration = timeline->frames[timelineArray->size * 2 - 2];
+				if (duration > animation->duration) animation->duration = duration;
+
+			} else {
+				int isScale = strcmp(timelineArray->name, "scale") == 0;
+				if (isScale || strcmp(timelineArray->name, "translate") == 0) {
+					float scale = isScale ? 1 : self->scale;
+					spTranslateTimeline *timeline =
+							isScale ? spScaleTimeline_create(timelineArray->size) : spTranslateTimeline_create(timelineArray->size);
+					timeline->boneIndex = boneIndex;
+					for (frame = timelineArray->child, i = 0; frame; frame = frame->next, ++i) {
+						spTranslateTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), Json_getFloat(frame, "x", 0) * scale,
+								Json_getFloat(frame, "y", 0) * scale);
+						readCurve(SUPER(timeline), i, frame);
+					}
+					animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
+					duration = timeline->frames[timelineArray->size * 3 - 3];
+					if (duration > animation->duration) animation->duration = duration;
+				} else if (strcmp(timelineArray->name, "flipX") == 0 || strcmp(timelineArray->name, "flipY") == 0) {
+					int x = strcmp(timelineArray->name, "flipX") == 0;
+					char* field = x ? "x" : "y";
+					spFlipTimeline *timeline = spFlipTimeline_create(timelineArray->size, x);
+					timeline->boneIndex = boneIndex;
+					for (frame = timelineArray->child, i = 0; frame; frame = frame->next, ++i)
+						spFlipTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), Json_getInt(frame, field, 0));
+					animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
+					duration = timeline->frames[timelineArray->size * 2 - 2];
+					if (duration > animation->duration) animation->duration = duration;
+
+				} else {
+					spAnimation_dispose(animation);
+					_spSkeletonJson_setError(self, 0, "Invalid timeline type for a bone: ", timelineArray->name);
+					return 0;
+				}
+			}
+		}
+	}
+
+	/* IK timelines. */
+	for (ikMap = ik ? ik->child : 0; ikMap; ikMap = ikMap->next) {
+		spIkConstraintData* ikConstraint = spSkeletonData_findIkConstraint(skeletonData, ikMap->name);
+		spIkConstraintTimeline* timeline = spIkConstraintTimeline_create(ikMap->size);
+		for (i = 0; i < skeletonData->ikConstraintsCount; ++i) {
+			if (ikConstraint == skeletonData->ikConstraints[i]) {
+				timeline->ikConstraintIndex = i;
+				break;
+			}
+		}
+		for (frame = ikMap->child, i = 0; frame; frame = frame->next, ++i) {
+			spIkConstraintTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), Json_getFloat(frame, "mix", 0),
+					Json_getInt(frame, "bendPositive", 1) ? 1 : -1);
+			readCurve(SUPER(timeline), i, frame);
+		}
+		animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
+		duration = timeline->frames[ikMap->size * 3 - 3];
+		if (duration > animation->duration) animation->duration = duration;
+	}
+
+	/* FFD timelines. */
+	for (ffdMap = ffd ? ffd->child : 0; ffdMap; ffdMap = ffdMap->next) {
+		spSkin* skin = spSkeletonData_findSkin(skeletonData, ffdMap->name);
+		for (slotMap = ffdMap->child; slotMap; slotMap = slotMap->next) {
+			int slotIndex = spSkeletonData_findSlotIndex(skeletonData, slotMap->name);
+			Json* timelineArray;
+			for (timelineArray = slotMap->child; timelineArray; timelineArray = timelineArray->next) {
+				Json* frame;
+				int verticesCount = 0;
+				float* tempVertices;
+				spFFDTimeline *timeline;
+
+				spAttachment* attachment = spSkin_getAttachment(skin, slotIndex, timelineArray->name);
+				if (!attachment) {
+					spAnimation_dispose(animation);
+					_spSkeletonJson_setError(self, 0, "Attachment not found: ", timelineArray->name);
+					return 0;
+				}
+				if (attachment->type == SP_ATTACHMENT_MESH)
+					verticesCount = SUB_CAST(spMeshAttachment, attachment)->verticesCount;
+				else if (attachment->type == SP_ATTACHMENT_SKINNED_MESH)
+					verticesCount = SUB_CAST(spSkinnedMeshAttachment, attachment)->weightsCount / 3 * 2;
+
+				timeline = spFFDTimeline_create(timelineArray->size, verticesCount);
+				timeline->slotIndex = slotIndex;
+				timeline->attachment = attachment;
+
+				tempVertices = MALLOC(float, verticesCount);
+				for (frame = timelineArray->child, i = 0; frame; frame = frame->next, ++i) {
+					Json* vertices = Json_getItem(frame, "vertices");
+					float* frameVertices;
+					if (!vertices) {
+						if (attachment->type == SP_ATTACHMENT_MESH)
+							frameVertices = SUB_CAST(spMeshAttachment, attachment)->vertices;
+						else {
+							frameVertices = tempVertices;
+							memset(frameVertices, 0, sizeof(float) * verticesCount);
+						}
+					} else {
+						int v, start = Json_getInt(frame, "offset", 0);
+						Json* vertex;
+						frameVertices = tempVertices;
+						memset(frameVertices, 0, sizeof(float) * start);
+						if (self->scale == 1) {
+							for (vertex = vertices->child, v = start; vertex; vertex = vertex->next, ++v)
+								frameVertices[v] = vertex->valueFloat;
+						} else {
+							for (vertex = vertices->child, v = start; vertex; vertex = vertex->next, ++v)
+								frameVertices[v] = vertex->valueFloat * self->scale;
+						}
+						memset(frameVertices + v, 0, sizeof(float) * (verticesCount - v));
+						if (attachment->type == SP_ATTACHMENT_MESH) {
+							float* meshVertices = SUB_CAST(spMeshAttachment, attachment)->vertices;
+							for (v = 0; v < verticesCount; ++v)
+								frameVertices[v] += meshVertices[v];
+						}
+					}
+					spFFDTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), frameVertices);
+					readCurve(SUPER(timeline), i, frame);
+				}
+				FREE(tempVertices);
+
+				animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
+				duration = timeline->frames[timelineArray->size - 1];
+				if (duration > animation->duration) animation->duration = duration;
+			}
+		}
+	}
+
+	/* Draw order timeline. */
+	if (drawOrder) {
+		spDrawOrderTimeline* timeline = spDrawOrderTimeline_create(drawOrder->size, skeletonData->slotsCount);
+		for (frame = drawOrder->child, i = 0; frame; frame = frame->next, ++i) {
+			int ii;
+			int* drawOrder = 0;
+			Json* offsets = Json_getItem(frame, "offsets");
+			if (offsets) {
+				Json* offsetMap;
+				int* unchanged = MALLOC(int, skeletonData->slotsCount - offsets->size);
+				int originalIndex = 0, unchangedIndex = 0;
+
+				drawOrder = MALLOC(int, skeletonData->slotsCount);
+				for (ii = skeletonData->slotsCount - 1; ii >= 0; --ii)
+					drawOrder[ii] = -1;
+
+				for (offsetMap = offsets->child; offsetMap; offsetMap = offsetMap->next) {
+					int slotIndex = spSkeletonData_findSlotIndex(skeletonData, Json_getString(offsetMap, "slot", 0));
+					if (slotIndex == -1) {
+						spAnimation_dispose(animation);
+						_spSkeletonJson_setError(self, 0, "Slot not found: ", Json_getString(offsetMap, "slot", 0));
+						return 0;
+					}
+					/* Collect unchanged items. */
+					while (originalIndex != slotIndex)
+						unchanged[unchangedIndex++] = originalIndex++;
+					/* Set changed items. */
+					drawOrder[originalIndex + Json_getInt(offsetMap, "offset", 0)] = originalIndex;
+					originalIndex++;
+				}
+				/* Collect remaining unchanged items. */
+				while (originalIndex < skeletonData->slotsCount)
+					unchanged[unchangedIndex++] = originalIndex++;
+				/* Fill in unchanged items. */
+				for (ii = skeletonData->slotsCount - 1; ii >= 0; ii--)
+					if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex];
+				FREE(unchanged);
+			}
+			spDrawOrderTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), drawOrder);
+			FREE(drawOrder);
+		}
+		animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
+		duration = timeline->frames[drawOrder->size - 1];
+		if (duration > animation->duration) animation->duration = duration;
+	}
+
+	/* Event timeline. */
+	if (events) {
+		Json* frame;
+
+		spEventTimeline* timeline = spEventTimeline_create(events->size);
+		for (frame = events->child, i = 0; frame; frame = frame->next, ++i) {
+			spEvent* event;
+			const char* stringValue;
+			spEventData* eventData = spSkeletonData_findEvent(skeletonData, Json_getString(frame, "name", 0));
+			if (!eventData) {
+				spAnimation_dispose(animation);
+				_spSkeletonJson_setError(self, 0, "Event not found: ", Json_getString(frame, "name", 0));
+				return 0;
+			}
+			event = spEvent_create(eventData);
+			event->intValue = Json_getInt(frame, "int", eventData->intValue);
+			event->floatValue = Json_getFloat(frame, "float", eventData->floatValue);
+			stringValue = Json_getString(frame, "string", eventData->stringValue);
+			if (stringValue) MALLOC_STR(event->stringValue, stringValue);
+			spEventTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), event);
+		}
+		animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
+		duration = timeline->frames[events->size - 1];
+		if (duration > animation->duration) animation->duration = duration;
+	}
+
+	return animation;
+}
+
+spSkeletonData* spSkeletonJson_readSkeletonDataFile (spSkeletonJson* self, const char* path) {
+	int length;
+	spSkeletonData* skeletonData;
+	const char* json = _spUtil_readFile(path, &length);
+	if (!json) {
+		_spSkeletonJson_setError(self, 0, "Unable to read skeleton file: ", path);
+		return 0;
+	}
+	skeletonData = spSkeletonJson_readSkeletonData(self, json);
+	FREE(json);
+	return skeletonData;
+}
+
+spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const char* json) {
+	int i, ii;
+	spSkeletonData* skeletonData;
+	Json *root, *skeleton, *bones, *boneMap, *ik, *slots, *skins, *animations, *events;
+
+	FREE(self->error);
+	CONST_CAST(char*, self->error) = 0;
+
+	root = Json_create(json);
+	if (!root) {
+		_spSkeletonJson_setError(self, 0, "Invalid skeleton JSON: ", Json_getError());
+		return 0;
+	}
+
+	skeletonData = spSkeletonData_create();
+
+	skeleton = Json_getItem(root, "skeleton");
+	if (skeleton) {
+		MALLOC_STR(skeletonData->hash, Json_getString(skeleton, "hash", 0));
+		MALLOC_STR(skeletonData->version,  Json_getString(skeleton, "spine", 0));
+		skeletonData->width = Json_getFloat(skeleton, "width", 0);
+		skeletonData->height = Json_getFloat(skeleton, "height", 0);
+	}
+
+	/* Bones. */
+	bones = Json_getItem(root, "bones");
+	skeletonData->bones = MALLOC(spBoneData*, bones->size);
+	for (boneMap = bones->child, i = 0; boneMap; boneMap = boneMap->next, ++i) {
+		spBoneData* boneData;
+
+		spBoneData* parent = 0;
+		const char* parentName = Json_getString(boneMap, "parent", 0);
+		if (parentName) {
+			parent = spSkeletonData_findBone(skeletonData, parentName);
+			if (!parent) {
+				spSkeletonData_dispose(skeletonData);
+				_spSkeletonJson_setError(self, root, "Parent bone not found: ", parentName);
+				return 0;
+			}
+		}
+
+		boneData = spBoneData_create(Json_getString(boneMap, "name", 0), parent);
+		boneData->length = Json_getFloat(boneMap, "length", 0) * self->scale;
+		boneData->x = Json_getFloat(boneMap, "x", 0) * self->scale;
+		boneData->y = Json_getFloat(boneMap, "y", 0) * self->scale;
+		boneData->rotation = Json_getFloat(boneMap, "rotation", 0);
+		boneData->scaleX = Json_getFloat(boneMap, "scaleX", 1);
+		boneData->scaleY = Json_getFloat(boneMap, "scaleY", 1);
+		boneData->inheritScale = Json_getInt(boneMap, "inheritScale", 1);
+		boneData->inheritRotation = Json_getInt(boneMap, "inheritRotation", 1);
+
+		skeletonData->bones[i] = boneData;
+		skeletonData->bonesCount++;
+	}
+
+	/* IK constraints. */
+	ik = Json_getItem(root, "ik");
+	if (ik) {
+		Json *ikMap;
+		skeletonData->ikConstraintsCount = ik->size;
+		skeletonData->ikConstraints = MALLOC(spIkConstraintData*, ik->size);
+		for (ikMap = ik->child, i = 0; ikMap; ikMap = ikMap->next, ++i) {
+			const char* targetName;
+
+			spIkConstraintData* ikConstraintData = spIkConstraintData_create(Json_getString(ikMap, "name", 0));
+			boneMap = Json_getItem(ikMap, "bones");
+			ikConstraintData->bonesCount = boneMap->size;
+			ikConstraintData->bones = MALLOC(spBoneData*, boneMap->size);
+			for (boneMap = boneMap->child, ii = 0; boneMap; boneMap = boneMap->next, ++ii) {
+				ikConstraintData->bones[ii] = spSkeletonData_findBone(skeletonData, boneMap->valueString);
+				if (!ikConstraintData->bones[ii]) {
+					spSkeletonData_dispose(skeletonData);
+					_spSkeletonJson_setError(self, root, "IK bone not found: ", boneMap->valueString);
+					return 0;
+				}
+			}
+
+			targetName = Json_getString(ikMap, "target", 0);
+			ikConstraintData->target = spSkeletonData_findBone(skeletonData, targetName);
+			if (!ikConstraintData->target) {
+				spSkeletonData_dispose(skeletonData);
+				_spSkeletonJson_setError(self, root, "Target bone not found: ", boneMap->name);
+				return 0;
+			}
+
+			ikConstraintData->bendDirection = Json_getInt(ikMap, "bendPositive", 1) ? 1 : -1;
+			ikConstraintData->mix = Json_getFloat(ikMap, "mix", 1);
+
+			skeletonData->ikConstraints[i] = ikConstraintData;
+		}
+	}
+
+	/* Slots. */
+	slots = Json_getItem(root, "slots");
+	if (slots) {
+		Json *slotMap;
+		skeletonData->slotsCount = slots->size;
+		skeletonData->slots = MALLOC(spSlotData*, slots->size);
+		for (slotMap = slots->child, i = 0; slotMap; slotMap = slotMap->next, ++i) {
+			spSlotData* slotData;
+			const char* color;
+			Json *attachmentItem;
+
+			const char* boneName = Json_getString(slotMap, "bone", 0);
+			spBoneData* boneData = spSkeletonData_findBone(skeletonData, boneName);
+			if (!boneData) {
+				spSkeletonData_dispose(skeletonData);
+				_spSkeletonJson_setError(self, root, "Slot bone not found: ", boneName);
+				return 0;
+			}
+
+			slotData = spSlotData_create(Json_getString(slotMap, "name", 0), boneData);
+
+			color = Json_getString(slotMap, "color", 0);
+			if (color) {
+				slotData->r = toColor(color, 0);
+				slotData->g = toColor(color, 1);
+				slotData->b = toColor(color, 2);
+				slotData->a = toColor(color, 3);
+			}
+
+			attachmentItem = Json_getItem(slotMap, "attachment");
+			if (attachmentItem) spSlotData_setAttachmentName(slotData, attachmentItem->valueString);
+
+			slotData->additiveBlending = Json_getInt(slotMap, "additive", 0);
+
+			skeletonData->slots[i] = slotData;
+		}
+	}
+
+	/* Skins. */
+	skins = Json_getItem(root, "skins");
+	if (skins) {
+		Json *slotMap;
+		skeletonData->skinsCount = skins->size;
+		skeletonData->skins = MALLOC(spSkin*, skins->size);
+		for (slotMap = skins->child, i = 0; slotMap; slotMap = slotMap->next, ++i) {
+			Json *attachmentsMap;
+			spSkin *skin = spSkin_create(slotMap->name);
+
+			skeletonData->skins[i] = skin;
+			if (strcmp(slotMap->name, "default") == 0) skeletonData->defaultSkin = skin;
+
+			for (attachmentsMap = slotMap->child; attachmentsMap; attachmentsMap = attachmentsMap->next) {
+				int slotIndex = spSkeletonData_findSlotIndex(skeletonData, attachmentsMap->name);
+				Json *attachmentMap;
+
+				for (attachmentMap = attachmentsMap->child; attachmentMap; attachmentMap = attachmentMap->next) {
+					spAttachment* attachment;
+					const char* skinAttachmentName = attachmentMap->name;
+					const char* attachmentName = Json_getString(attachmentMap, "name", skinAttachmentName);
+					const char* path = Json_getString(attachmentMap, "path", attachmentName);
+					const char* color;
+					int i;
+					Json* entry;
+
+					const char* typeString = Json_getString(attachmentMap, "type", "region");
+					spAttachmentType type;
+					if (strcmp(typeString, "region") == 0)
+						type = SP_ATTACHMENT_REGION;
+					else if (strcmp(typeString, "mesh") == 0)
+						type = SP_ATTACHMENT_MESH;
+					else if (strcmp(typeString, "skinnedmesh") == 0)
+						type = SP_ATTACHMENT_SKINNED_MESH;
+					else if (strcmp(typeString, "boundingbox") == 0)
+						type = SP_ATTACHMENT_BOUNDING_BOX;
+					else {
+						spSkeletonData_dispose(skeletonData);
+						_spSkeletonJson_setError(self, root, "Unknown attachment type: ", typeString);
+						return 0;
+					}
+
+					attachment = spAttachmentLoader_newAttachment(self->attachmentLoader, skin, type, attachmentName, path);
+					if (!attachment) {
+						if (self->attachmentLoader->error1) {
+							spSkeletonData_dispose(skeletonData);
+							_spSkeletonJson_setError(self, root, self->attachmentLoader->error1, self->attachmentLoader->error2);
+							return 0;
+						}
+						continue;
+					}
+
+					switch (attachment->type) {
+					case SP_ATTACHMENT_REGION: {
+						spRegionAttachment* region = SUB_CAST(spRegionAttachment, attachment);
+						if (path) MALLOC_STR(region->path, path);
+						region->x = Json_getFloat(attachmentMap, "x", 0) * self->scale;
+						region->y = Json_getFloat(attachmentMap, "y", 0) * self->scale;
+						region->scaleX = Json_getFloat(attachmentMap, "scaleX", 1);
+						region->scaleY = Json_getFloat(attachmentMap, "scaleY", 1);
+						region->rotation = Json_getFloat(attachmentMap, "rotation", 0);
+						region->width = Json_getFloat(attachmentMap, "width", 32) * self->scale;
+						region->height = Json_getFloat(attachmentMap, "height", 32) * self->scale;
+
+						color = Json_getString(attachmentMap, "color", 0);
+						if (color) {
+							region->r = toColor(color, 0);
+							region->g = toColor(color, 1);
+							region->b = toColor(color, 2);
+							region->a = toColor(color, 3);
+						}
+
+						spRegionAttachment_updateOffset(region);
+						break;
+					}
+					case SP_ATTACHMENT_MESH: {
+						spMeshAttachment* mesh = SUB_CAST(spMeshAttachment, attachment);
+
+						MALLOC_STR(mesh->path, path);
+
+						entry = Json_getItem(attachmentMap, "vertices");
+						mesh->verticesCount = entry->size;
+						mesh->vertices = MALLOC(float, entry->size);
+						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
+							mesh->vertices[i] = entry->valueFloat * self->scale;
+
+						entry = Json_getItem(attachmentMap, "triangles");
+						mesh->trianglesCount = entry->size;
+						mesh->triangles = MALLOC(int, entry->size);
+						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
+							mesh->triangles[i] = entry->valueInt;
+
+						entry = Json_getItem(attachmentMap, "uvs");
+						mesh->regionUVs = MALLOC(float, entry->size);
+						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
+							mesh->regionUVs[i] = entry->valueFloat;
+
+						spMeshAttachment_updateUVs(mesh);
+
+						color = Json_getString(attachmentMap, "color", 0);
+						if (color) {
+							mesh->r = toColor(color, 0);
+							mesh->g = toColor(color, 1);
+							mesh->b = toColor(color, 2);
+							mesh->a = toColor(color, 3);
+						}
+
+						mesh->hullLength = Json_getInt(attachmentMap, "hull", 0);
+
+						entry = Json_getItem(attachmentMap, "edges");
+						if (entry) {
+							mesh->edgesCount = entry->size;
+							mesh->edges = MALLOC(int, entry->size);
+							for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
+								mesh->edges[i] = entry->valueInt;
+						}
+
+						mesh->width = Json_getFloat(attachmentMap, "width", 32) * self->scale;
+						mesh->height = Json_getFloat(attachmentMap, "height", 32) * self->scale;
+						break;
+					}
+					case SP_ATTACHMENT_SKINNED_MESH: {
+						spSkinnedMeshAttachment* mesh = SUB_CAST(spSkinnedMeshAttachment, attachment);
+						int verticesCount, b, w, nn;
+						float* vertices;
+
+						MALLOC_STR(mesh->path, path);
+
+						entry = Json_getItem(attachmentMap, "uvs");
+						mesh->uvsCount = entry->size;
+						mesh->regionUVs = MALLOC(float, entry->size);
+						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
+							mesh->regionUVs[i] = entry->valueFloat;
+
+						entry = Json_getItem(attachmentMap, "vertices");
+						verticesCount = entry->size;
+						vertices = MALLOC(float, entry->size);
+						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
+							vertices[i] = entry->valueFloat;
+
+						for (i = 0; i < verticesCount;) {
+							int bonesCount = (int)vertices[i];
+							mesh->bonesCount += bonesCount + 1;
+							mesh->weightsCount += bonesCount * 3;
+							i += 1 + bonesCount * 4;
+						}
+						mesh->bones = MALLOC(int, mesh->bonesCount);
+						mesh->weights = MALLOC(float, mesh->weightsCount);
+
+						for (i = 0, b = 0, w = 0; i < verticesCount;) {
+							int bonesCount = (int)vertices[i++];
+							mesh->bones[b++] = bonesCount;
+							for (nn = i + bonesCount * 4; i < nn; i += 4, ++b, w += 3) {
+								mesh->bones[b] = (int)vertices[i];
+								mesh->weights[w] = vertices[i + 1] * self->scale;
+								mesh->weights[w + 1] = vertices[i + 2] * self->scale;
+								mesh->weights[w + 2] = vertices[i + 3];
+							}
+						}
+
+						FREE(vertices);
+
+						entry = Json_getItem(attachmentMap, "triangles");
+						mesh->trianglesCount = entry->size;
+						mesh->triangles = MALLOC(int, entry->size);
+						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
+							mesh->triangles[i] = entry->valueInt;
+
+						spSkinnedMeshAttachment_updateUVs(mesh);
+
+						color = Json_getString(attachmentMap, "color", 0);
+						if (color) {
+							mesh->r = toColor(color, 0);
+							mesh->g = toColor(color, 1);
+							mesh->b = toColor(color, 2);
+							mesh->a = toColor(color, 3);
+						}
+
+						mesh->hullLength = Json_getInt(attachmentMap, "hull", 0);
+
+						entry = Json_getItem(attachmentMap, "edges");
+						if (entry) {
+							mesh->edgesCount = entry->size;
+							mesh->edges = MALLOC(int, entry->size);
+							for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
+								mesh->edges[i] = entry->valueInt;
+						}
+
+						mesh->width = Json_getFloat(attachmentMap, "width", 32) * self->scale;
+						mesh->height = Json_getFloat(attachmentMap, "height", 32) * self->scale;
+						break;
+					}
+					case SP_ATTACHMENT_BOUNDING_BOX: {
+						spBoundingBoxAttachment* box = SUB_CAST(spBoundingBoxAttachment, attachment);
+						entry = Json_getItem(attachmentMap, "vertices");
+						box->verticesCount = entry->size;
+						box->vertices = MALLOC(float, entry->size);
+						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
+							box->vertices[i] = entry->valueFloat * self->scale;
+						break;
+					}
+					}
+
+					spSkin_addAttachment(skin, slotIndex, skinAttachmentName, attachment);
+				}
+			}
+		}
+	}
+
+	/* Events. */
+	events = Json_getItem(root, "events");
+	if (events) {
+		Json *eventMap;
+		const char* stringValue;
+		skeletonData->eventsCount = events->size;
+		skeletonData->events = MALLOC(spEventData*, events->size);
+		for (eventMap = events->child, i = 0; eventMap; eventMap = eventMap->next, ++i) {
+			spEventData* eventData = spEventData_create(eventMap->name);
+			eventData->intValue = Json_getInt(eventMap, "int", 0);
+			eventData->floatValue = Json_getFloat(eventMap, "float", 0);
+			stringValue = Json_getString(eventMap, "string", 0);
+			if (stringValue) MALLOC_STR(eventData->stringValue, stringValue);
+			skeletonData->events[i] = eventData;
+		}
+	}
+
+	/* Animations. */
+	animations = Json_getItem(root, "animations");
+	if (animations) {
+		Json *animationMap;
+		skeletonData->animations = MALLOC(spAnimation*, animations->size);
+		for (animationMap = animations->child; animationMap; animationMap = animationMap->next)
+			_spSkeletonJson_readAnimation(self, animationMap, skeletonData);
+	}
+
+	Json_dispose(root);
+	return skeletonData;
+}