m3g/m3gcore11/src/m3g_loader.c
changeset 0 5d03bc08d59c
child 116 171fae344dd4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/m3g/m3gcore11/src/m3g_loader.c	Tue Feb 02 01:47:50 2010 +0200
@@ -0,0 +1,3056 @@
+/*
+* Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of the License "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description: Native implementation of the Loader class
+*
+*/
+
+
+/*!
+ * \internal
+ * \file
+ * \brief Native implementation of the Loader class
+ *
+*/
+
+#include "m3g_object.h"
+#include "m3g_array.h"
+
+/*----------------------------------------------------------------------
+ * Internal data types
+ *--------------------------------------------------------------------*/
+
+/*!
+ * \internal
+ * \brief Possible global states for the loader
+ */
+typedef enum {
+    /*! \internal \brief Loading not supported yet */
+    LOADSTATE_NOT_SUPPORTED = -2,
+    /*! \internal \brief Loading has terminated with an error */
+    LOADSTATE_ERROR         = -1,
+    /*! \internal \brief Loading has not started yet */
+    LOADSTATE_INITIAL       =  0,
+    /*! \internal \brief The identifier of the file is being read */
+    LOADSTATE_IDENTIFIER,
+    /*! \internal \brief The header of the section is being read */
+    LOADSTATE_SECTION,
+    /*! \internal \brief The header field of an object is being read */
+    LOADSTATE_OBJECT,
+    /*! \internal \brief Loading is finished */
+    LOADSTATE_DONE
+} LoaderState;
+
+/*!
+ * \internal
+ * \brief Possible local states for the loader
+ */
+typedef enum {
+    /*! \internal \brief Local state is entered */
+    LOADSTATE_ENTER,
+    /*! \internal \brief Local state is exited */
+    LOADSTATE_EXIT,
+    /*! \internal \brief Local state is section checksum */
+    LOADSTATE_CHECKSUM
+} LoaderLocalState;
+
+/*!
+ * \internal
+ * \brief Buffered byte stream class
+ */
+typedef struct
+{
+    M3Gubyte *allocatedData;
+    M3Gubyte *data;
+    M3Gsizei capacity, bytesAvailable, totalBytes;
+} BufferedStream;
+
+/*!
+ * \internal
+ * \brief User data for a loaded object
+ */
+typedef struct
+{
+    M3GObject object;
+    M3Gint numParams;
+    M3Gbyte **params;
+    M3Gsizei *paramLengths;
+    M3Gint *paramId;
+} UserData;
+
+/*!
+ * \internal
+ * \brief Loader instance data
+ */
+typedef struct M3GLoaderImpl
+{
+    Object object;
+
+    BufferedStream stream;
+    M3Gsizei bytesRequired;
+    M3Gsizei sectionBytesRequired;
+
+    PointerArray refArray;
+    PointerArray userDataArray;
+
+    /*!
+     * \internal
+     * \brief The global state the loader is in
+     *
+     * This is a rather ordinary state machine thing; basically the
+     * type of object being loaded, or one of the possible error
+     * conditions. In here, it also amounts to a particular coroutine
+     * execution context.
+     */
+    LoaderState state;
+
+    /*!
+     * \internal
+     * \brief The local state of the loader
+     *
+     * This is basically the line number within a particular coroutine
+     * function.
+     */
+    M3Gint localState;
+
+    /*!
+     * \internal
+     * \brief Object being loaded
+     */
+    M3Gint objectType;
+
+    /*!
+     * \internal
+     * \brief Loaded object
+     */
+    M3GObject loadedObject;
+
+    /*!
+     * \internal
+     * \brief Pointer to the beginning of an object
+     */
+    M3Gubyte *objectData;
+
+    /*!
+     * \internal
+     * \brief Pointer to the end of an object
+     */
+    M3Gubyte *objectDataEnd;
+
+    /*!
+     * \internal
+     * \brief Pointer to the context data for the current coroutine
+     * context
+     */
+    M3Gubyte *localData;
+
+    /*!
+     * \internal
+     * \brief Size of the current coroutine data
+     *
+     * This is grown dynamically as necessary, rather than trying to
+     * maintain a single size that fits all coroutines.
+     */
+    size_t localDataSize;
+
+    /* File information */
+    M3Gbool hasReferences;
+    M3Gsizei fileSize;
+    M3Gsizei contentSize;
+    M3Gint triCount;
+    M3Gint triConstraint;
+
+    /* Section information */
+    M3Gbool compressed;
+    M3Gint sectionLength;
+    M3Gint sectionNum;
+    M3Gint inflatedLength;
+    M3Gubyte *sectionData;
+    M3Gubyte *allocatedSectionData;
+    
+    /* Adler data */
+    M3Gint S12[2];
+} Loader;
+
+typedef struct {
+    const unsigned char *data;
+    int read;
+    int length;
+} compressedData;
+
+/* Type ID used for classifying objects derived from Node */
+#define ANY_NODE_CLASS ((M3GClass)(-1))
+
+#include <string.h>
+#define m3gCmp(s1, s2, len)  memcmp(s1, s2, len)
+
+/*----------------------------------------------------------------------
+ * Private constants
+ *--------------------------------------------------------------------*/
+
+#define M3G_MIN_OBJECT_SIZE     (1 + 4)
+#define M3G_MIN_SECTION_SIZE    (1 + 4 + 4)
+#define M3G_CHECKSUM_SIZE       4
+
+#define M3G_ADLER_CONST         65521;
+
+static const M3Gubyte M3G_FILE_IDENTIFIER[] = {
+    0xAB, 0x4A, 0x53, 0x52, 0x31, 0x38, 0x34, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
+};
+
+static const M3Gubyte PNG_FILE_IDENTIFIER[] = {
+    0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a
+};
+
+static void m3gCleanupLoader(M3GLoader loader);
+
+/*----------------------------------------------------------------------
+ * Platform-specific "inflate" decompression code
+ *--------------------------------------------------------------------*/
+
+/*!
+ * \internal
+ * \brief Decompresses a block of data into an output buffer
+ *
+ * \param srcLength number of bytes in the input (compressed) buffer
+ * \param src       pointer to the input buffer
+ * \param dstLength number of bytes allocated in the output buffer
+ * \param dst       pointer to the output buffer
+ * \return the number of bytes written to \c dst
+ */
+static M3Gsizei m3gInflateBlock(M3Gsizei srcLength, const M3Gubyte *src,
+                                M3Gsizei dstLength, M3Gubyte *dst);
+                           
+/* Include the platform-dependent implementation */
+#if !defined(M3G_TARGET_GENERIC)
+#   include "m3g_loader_inflate.inl"
+#endif
+
+/*----------------------------------------------------------------------
+ * Private functions
+ *--------------------------------------------------------------------*/
+
+/*!
+ * \internal
+ * \brief Destructor
+ */
+static void m3gDestroyLoader(Object *obj)
+{
+    Loader* loader = (Loader *) obj;
+    M3G_VALIDATE_OBJECT(loader);
+    {
+        Interface *m3g = M3G_INTERFACE(loader);
+        M3Gint n, i;
+
+        m3gCleanupLoader(loader);
+        m3gDestroyArray(&loader->refArray, m3g);
+        n = m3gArraySize(&loader->userDataArray);
+        for (i = 0; i < n; ++i)
+        {
+            UserData *data = (UserData *)m3gGetArrayElement(&loader->userDataArray, i);
+            m3gFree(m3g, data->params);
+            m3gFree(m3g, data->paramLengths);
+            m3gFree(m3g, data->paramId);
+            m3gFree(m3g, data);
+        }
+        m3gDestroyArray(&loader->userDataArray, m3g);
+        m3gFree(m3g, loader->stream.allocatedData);
+        m3gFree(m3g, loader->allocatedSectionData);
+    }
+    m3gDestroyObject(obj);
+}
+
+/*!
+ * \internal
+ * \brief Stores new data in the stream buffer of this loader
+ */
+static M3Gbool m3gBufferData( M3GInterface m3g,
+                           BufferedStream *stream,
+                           M3Gsizei bytes,
+                           const M3Gubyte *data)
+{
+    M3Gsizei used;
+
+    /* Allocate initial buffer */
+    if (stream->allocatedData == NULL) {
+        stream->capacity = bytes + 512;
+        stream->allocatedData = m3gAllocZ(m3g, stream->capacity);
+        if (!stream->allocatedData) {
+            return M3G_FALSE;
+        }
+        stream->data = stream->allocatedData;
+        stream->bytesAvailable = 0;
+        stream->totalBytes = 0;
+    }
+
+    /* First skip used bytes */
+    used = stream->data - stream->allocatedData;
+    if (used > 0) {
+        m3gMove(stream->allocatedData, stream->data, stream->bytesAvailable);
+        stream->data = stream->allocatedData;
+    }
+
+    /* Check if new data fits in current buffer */
+    if ((stream->capacity - stream->bytesAvailable) < bytes) {
+        M3Gubyte *newData;
+        stream->capacity = stream->capacity + bytes + 512;
+        newData = m3gAllocZ(m3g, stream->capacity);
+        if (!newData) {
+            m3gFree(m3g, stream->allocatedData);
+            stream->allocatedData = NULL;
+            return M3G_FALSE;
+        }
+        m3gCopy(newData, stream->data, stream->bytesAvailable);
+        m3gFree(m3g, stream->allocatedData);
+        stream->allocatedData = newData;
+        stream->data = stream->allocatedData;
+    }
+
+    m3gCopy(stream->data + stream->bytesAvailable, data, bytes);
+    stream->bytesAvailable += bytes;
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Resets buffered data
+ */
+static void m3gResetBufferedData(BufferedStream *stream)
+{
+    stream->bytesAvailable = 0;
+    stream->data = stream->allocatedData;
+    stream->totalBytes = 0;
+}
+
+/*!
+ * \internal
+ * \brief Gets buffered data pointer
+ */
+static M3Gubyte *m3gGetBufferedDataPtr(BufferedStream *stream, M3Gint length)
+{
+    if (stream->bytesAvailable >= length) {
+        return stream->data;
+    }
+    else {
+        return NULL;
+    }
+}
+
+/*!
+ * \internal
+ * \brief Advances buffered data pointer
+ */
+static void m3gAdvanceBufferedData(BufferedStream *stream, M3Gint length)
+{
+    stream->data += length;
+    stream->bytesAvailable -= length;
+    stream->totalBytes += length;
+}
+
+/*!
+ * \internal
+ * \brief Verify a boolean
+ */
+static M3Gbool m3gVerifyBool(M3Gubyte *data)
+{
+    return (*data == 0 || *data == 1);
+}
+
+/*!
+ * \internal
+ * \brief Loads ARGB color from data array
+ */
+static M3Guint m3gLoadARGB(M3Gubyte *data)
+{
+    M3Guint v = data[3];
+    v <<= 8;
+    v |=  data[0];
+    v <<= 8;
+    v |=  data[1];
+    v <<= 8;
+    v |=  data[2];
+
+    return v;
+}
+
+/*!
+ * \internal
+ * \brief Loads RGB color from data array
+ */
+static M3Guint m3gLoadRGB(M3Gubyte *data)
+{
+    M3Guint v = data[0];
+    v <<= 8;
+    v |=  data[1];
+    v <<= 8;
+    v |=  data[2];
+
+    return v;
+}
+
+/*!
+ * \internal
+ * \brief Loads short from data array
+ */
+static M3Gshort m3gLoadShort(M3Gubyte *data)
+{
+    M3Gshort v = data[1];
+    v <<= 8;
+    v |=  data[0];
+
+    return v;
+}
+
+/*!
+ * \internal
+ * \brief Loads integer from data array
+ */
+static M3Gint m3gLoadInt(M3Gubyte *data)
+{
+    M3Gint v = data[3];
+    v <<= 8;
+    v |=  data[2];
+    v <<= 8;
+    v |=  data[1];
+    v <<= 8;
+    v |=  data[0];
+
+    return v;
+}
+
+/*!
+ * \internal
+ * \brief Loads integer from data array
+ */
+static M3Gbool m3gLoadFloat(M3Gubyte *data, M3Gfloat *res)
+{
+    M3Guint v = data[3];
+    v <<= 8;
+    v |=  data[2];
+    v <<= 8;
+    v |=  data[1];
+    v <<= 8;
+    v |=  data[0];
+
+    *res = (*(M3Gfloat*)&(v));
+    if ((v & 0x7f800000) ==  0x7f800000 ||
+        v == 0x80000000 || // negative zero
+        ((v & 0x007FFFFF ) != 0 && ( v & 0x7F800000 ) == 0))
+        return M3G_FALSE;
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads 4 * 4 matrix from data array
+ */
+static M3Gbool m3gLoadMatrix(Matrix *m, M3Gubyte *data)
+{
+    M3Gint i;
+    M3Gfloat array[16];
+
+    for (i = 0; i < 16; i++) {
+        if (!m3gLoadFloat(data + 4 * i, &array[i]))
+            return M3G_FALSE;
+    }
+
+    m3gSetMatrixRows(m, array);
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Inflates a section
+ */
+static M3Gubyte *m3gInflateSection(Loader *loader,
+                                   M3Gubyte *compressed,
+                                   M3Gint cLength, M3Gint iLength)
+{
+    M3Gubyte *inflated = m3gAllocZ(M3G_INTERFACE(loader), iLength);
+    if (inflated && !m3gInflateBlock(cLength, compressed, iLength, inflated)) {
+        m3gFree(M3G_INTERFACE(loader), inflated);
+        return NULL;
+    }
+
+    return inflated;
+}
+
+/*!
+ * \internal
+ * \brief Loads file identifier
+ */
+static LoaderState m3gLoadIdentifier(Loader *loader)
+{
+    M3Gubyte *data = m3gGetBufferedDataPtr(&loader->stream, loader->bytesRequired);
+
+    if (loader->localState == LOADSTATE_ENTER) {
+        if (m3gCmp(data, PNG_FILE_IDENTIFIER, sizeof(PNG_FILE_IDENTIFIER)) == 0) {
+            m3gAdvanceBufferedData(&loader->stream, loader->bytesRequired);
+            return LOADSTATE_NOT_SUPPORTED;
+        }
+        else {
+            loader->localState = LOADSTATE_EXIT;
+            loader->bytesRequired = sizeof(M3G_FILE_IDENTIFIER);
+            return LOADSTATE_IDENTIFIER;
+        }
+    }
+    else {
+        if (m3gCmp(data, M3G_FILE_IDENTIFIER, sizeof(M3G_FILE_IDENTIFIER)) == 0) {
+            m3gAdvanceBufferedData(&loader->stream, loader->bytesRequired);
+            loader->localState = LOADSTATE_ENTER;
+            loader->bytesRequired = M3G_MIN_SECTION_SIZE;
+            return LOADSTATE_SECTION;
+        }
+
+        loader->bytesRequired = 0;
+        return LOADSTATE_ERROR;
+    }
+}
+
+/*!
+ * \internal
+ * \brief Adler helper functions
+ */
+
+static void m3gInitAdler(M3Gint *S12)
+{
+    S12[0] = 1; S12[1] = 0;
+}
+
+static void m3gUpdateAdler(M3Gint *S12, M3Gubyte *data, M3Gint length)
+{
+    int i;
+    for (i = 0; i < length; i++) {
+        S12[0] = (S12[0] + data[i]) % M3G_ADLER_CONST;
+        S12[1] = (S12[1] + S12[0])  % M3G_ADLER_CONST;
+    }
+}
+
+static M3Gint m3gGetAdler(M3Gint *S12)
+{
+    return (S12[1] << 16) | S12[0];
+}
+
+/*!
+ * \internal
+ * \brief Loads a section
+ */
+static LoaderState m3gLoadSection(Loader *loader)
+{
+    M3Gubyte *data = m3gGetBufferedDataPtr(&loader->stream, loader->bytesRequired);
+
+    if (data == NULL) {
+        m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
+        return LOADSTATE_ERROR;
+    }
+
+    switch(loader->localState) {
+    case LOADSTATE_ENTER:
+        m3gAdvanceBufferedData(&loader->stream, loader->bytesRequired);
+        m3gInitAdler(loader->S12);
+        m3gUpdateAdler(loader->S12, data, loader->bytesRequired);
+
+        if (*data > 1)
+        {
+            m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
+            return LOADSTATE_ERROR;
+        }
+        loader->compressed = data[0];
+        loader->sectionLength = m3gLoadInt(data + 1);
+        loader->inflatedLength = m3gLoadInt(data + 5);
+
+        loader->localState = LOADSTATE_EXIT;
+        loader->bytesRequired = loader->sectionLength - loader->bytesRequired;
+        if (!loader->compressed && loader->inflatedLength != (loader->bytesRequired - M3G_CHECKSUM_SIZE))
+        {
+            m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
+            return LOADSTATE_ERROR;
+        }
+
+        loader->sectionNum++;
+
+        /* Special case for empty sections */
+        if (loader->bytesRequired == M3G_CHECKSUM_SIZE) {
+            loader->localData = data + loader->sectionLength - M3G_CHECKSUM_SIZE;
+            loader->sectionData = loader->localData;
+            loader->compressed = M3G_FALSE;
+            loader->localState = LOADSTATE_CHECKSUM;
+        }
+        return LOADSTATE_SECTION;
+
+    case LOADSTATE_EXIT:
+    default:
+        m3gUpdateAdler(loader->S12, data, loader->bytesRequired - M3G_CHECKSUM_SIZE);
+
+        if (loader->compressed) {
+            if (loader->inflatedLength > 0) {
+                m3gFree(M3G_INTERFACE(loader), loader->allocatedSectionData);
+                loader->sectionData = m3gInflateSection(loader, data, loader->bytesRequired, loader->inflatedLength);
+                loader->allocatedSectionData = loader->sectionData;
+
+                if (!loader->sectionData) {
+                    if (m3gErrorRaised(M3G_INTERFACE(loader)) == M3G_NO_ERROR)
+                        m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
+                    return LOADSTATE_ERROR;
+                }
+            }
+            else {
+                loader->sectionData = NULL;
+            }
+        }
+        else {
+            loader->sectionData = data;
+        }
+
+        loader->localData = loader->sectionData;
+        loader->sectionBytesRequired = M3G_MIN_OBJECT_SIZE;
+        loader->localState = LOADSTATE_ENTER;
+        return LOADSTATE_OBJECT;
+
+    case LOADSTATE_CHECKSUM:
+        if (loader->localData != loader->sectionData + loader->inflatedLength || /* Length */
+            m3gLoadInt(data + loader->bytesRequired - M3G_CHECKSUM_SIZE) != m3gGetAdler(loader->S12))
+        {
+            m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
+            m3gFree(M3G_INTERFACE(loader), loader->allocatedSectionData);
+            loader->allocatedSectionData = NULL;
+            return LOADSTATE_ERROR;
+        }
+        m3gAdvanceBufferedData(&loader->stream, loader->bytesRequired);
+
+        m3gFree(M3G_INTERFACE(loader), loader->allocatedSectionData);
+        loader->allocatedSectionData = NULL;
+
+        loader->localState = LOADSTATE_ENTER;
+        loader->bytesRequired = M3G_MIN_SECTION_SIZE;
+        return LOADSTATE_SECTION;
+    }
+}
+
+/*!
+ * \internal
+ * \brief Resets section data pointer to the beginning of an object
+ */
+static void m3gRewindObject(Loader *loader)
+{
+    loader->localData = loader->objectData;
+}
+
+/*!
+ * \internal
+ * \brief Resets section data pointer to the end of an object
+ */
+static void m3gSkipObject(Loader *loader)
+{
+    loader->localData = loader->objectDataEnd;
+}
+
+/*!
+ * \internal
+ * \brief Marks object to begin
+ */
+static void m3gBeginObject(Loader *loader)
+{
+    loader->objectData = loader->localData;
+}
+
+/*!
+ * \internal
+ * \brief Marks object to end
+ */
+static void m3gEndObject(Loader *loader)
+{
+    loader->objectDataEnd = loader->localData;
+}
+
+/*!
+ * \internal
+ * \brief Gets section data pointer
+ */
+static M3Gubyte *m3gGetSectionDataPtr(Loader *loader, M3Gint length)
+{
+    if ((loader->localData + length) <= (loader->sectionData + loader->inflatedLength)) {
+        return loader->localData;
+    }
+    else {
+        return NULL;
+    }
+}
+
+/*!
+ * \internal
+ * \brief Advances section data pointer
+ */
+static void m3gAdvanceSectionData(Loader *loader, M3Gint length)
+{
+    loader->localData += length;
+}
+
+/*!
+ * \internal
+ * \brief Check length of the available section data
+ */
+static M3Gbool m3gCheckSectionDataLength(Loader *loader, const M3Gubyte *data, M3Gsizei length)
+{
+    if (data + length < data) return M3G_FALSE; /* Check for overflow */
+    return ((data + length) <= (loader->sectionData + loader->inflatedLength));
+}
+
+/*!
+ * \internal
+ * \brief References an object in the object array
+ *
+ * \note Uses lowest bit of the pointer to mark a reference
+ */
+static void m3gReferenceLoaded(PointerArray *array, M3Gint idx)
+{
+    M3Guint ptr = (M3Guint)m3gGetArrayElement(array, idx);
+    ptr |= 1;
+    m3gSetArrayElement(array, idx, (void *)ptr);
+}
+
+/*!
+ * \internal
+ * \brief Gets an object in the object array and
+ * returns reference status
+ */
+static M3GObject m3gGetLoadedPtr(PointerArray *array, M3Gint idx, M3Gbool *referenced)
+{
+    M3Guint ptr = (M3Guint)m3gGetArrayElement(array, idx);
+    if (referenced != NULL) {
+        *referenced = ptr & 1;
+    }
+    return (M3GObject)(ptr & (~1));
+}
+
+/*!
+ * \internal
+ * \brief Gets a loaded object and marks it referenced
+ */
+static M3GObject m3gGetLoaded(Loader *loader, M3Gint idx, M3GClass classID)
+{
+    M3GObject obj;
+    M3GClass objClassID;
+    M3Gbool isCompatible;
+
+    if (idx == 0) return NULL;
+    idx -= 2;
+
+    if (idx < 0 || idx >= m3gArraySize(&loader->refArray)) {
+        /* Error, not loaded */
+        m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
+        return NULL;
+    }
+
+    obj = m3gGetLoadedPtr(&loader->refArray, idx, NULL);
+    objClassID = M3G_CLASS(obj);
+
+    /* Class type check; handle nodes as a special case */
+    
+    if (classID == ANY_NODE_CLASS) {
+        switch (objClassID) {
+        case M3G_CLASS_CAMERA:
+        case M3G_CLASS_GROUP:
+        case M3G_CLASS_LIGHT:
+        case M3G_CLASS_MESH:
+        case M3G_CLASS_MORPHING_MESH:
+        case M3G_CLASS_SKINNED_MESH:
+        case M3G_CLASS_SPRITE:
+        case M3G_CLASS_WORLD:
+            isCompatible = M3G_TRUE;
+            break;
+        default:
+            isCompatible = M3G_FALSE;
+        }
+    }
+    else {
+        switch (classID) {
+        case M3G_ABSTRACT_CLASS:
+            M3G_ASSERT(M3G_FALSE);
+            isCompatible = M3G_FALSE;
+            break;
+        case M3G_CLASS_MESH:
+            isCompatible = (classID == M3G_CLASS_MESH)
+                || (classID == M3G_CLASS_MORPHING_MESH)
+                || (classID == M3G_CLASS_SKINNED_MESH);
+            break;
+        default:
+            isCompatible = (classID == objClassID);
+        }
+    }
+
+    if (!isCompatible) {
+        /* Error, class mismatch */
+        m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
+        return NULL;
+    }
+
+    /* Mark object as referenced */
+    m3gReferenceLoaded(&loader->refArray, idx);
+    return obj;
+}
+
+/*!
+ * \internal
+ * \brief Loads Object3D data
+ */
+static M3Gbool m3gLoadObject3DData(Loader *loader, M3GObject obj)
+{
+    M3Guint animTracks, i, userParams, paramId, paramLength;
+    UserData *userData = NULL;
+    M3Gubyte *data = m3gGetSectionDataPtr(loader, 8);
+    if (data == NULL) return M3G_FALSE;
+
+    m3gSetUserID(obj, m3gLoadInt(data));
+    data += 4;
+
+    animTracks = m3gLoadInt(data);
+    data += 4;
+
+    /* Overflow? */
+    if (animTracks >= 0x1fffffff) {
+        return M3G_FALSE;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, animTracks * 4 + 4) == M3G_FALSE) return M3G_FALSE;
+    for (i = 0; i < animTracks; i++) {
+        M3GAnimationTrack at = (M3GAnimationTrack)m3gGetLoaded(loader, m3gLoadInt(data),M3G_CLASS_ANIMATION_TRACK);
+        if (at == NULL || m3gAddAnimationTrack(obj, at) == -1) {
+            return M3G_FALSE;
+        }
+        data += 4;
+    }
+
+    userParams = m3gLoadInt(data);
+    data += 4;
+
+    if (userParams != 0) {
+        /* Overflow? */
+        if (userParams >= 0x10000000) {
+            return M3G_FALSE;
+        }
+
+        if (m3gCheckSectionDataLength(loader, data, userParams * 8) == M3G_FALSE)
+            return M3G_FALSE; /* Check the minimum size to avoid useless allocation */
+        userData = (UserData *)m3gAllocZ(M3G_INTERFACE(loader), sizeof(UserData));
+        if (userData == NULL)
+            return M3G_FALSE;
+        userData->object = obj;
+        userData->numParams = userParams;
+        userData->params = (M3Gbyte **)m3gAllocZ(M3G_INTERFACE(loader), userParams*sizeof(M3Gbyte *));
+        userData->paramLengths = (M3Gsizei *)m3gAlloc(M3G_INTERFACE(loader), userParams*sizeof(M3Gsizei));
+        userData->paramId = (M3Gint *)m3gAlloc(M3G_INTERFACE(loader), userParams*sizeof(M3Gint));
+        if (userData->params == NULL ||
+            userData->paramLengths == NULL ||
+            userData->paramId == NULL ||
+            m3gArrayAppend(&loader->userDataArray, userData, M3G_INTERFACE(loader)) == -1) {
+            m3gFree(M3G_INTERFACE(loader), userData->params);
+            m3gFree(M3G_INTERFACE(loader), userData->paramLengths);
+            m3gFree(M3G_INTERFACE(loader), userData->paramId);
+            m3gFree(M3G_INTERFACE(loader), userData);
+            return M3G_FALSE;
+        }
+
+        for (i = 0; i < userParams; i++) {
+            if (m3gCheckSectionDataLength(loader, data, 8) == M3G_FALSE) return M3G_FALSE;
+            paramId = m3gLoadInt(data);
+            data += 4;
+            paramLength = m3gLoadInt(data);
+            data += 4;
+            userData->paramId[i] = paramId;
+            userData->paramLengths[i] = paramLength;
+            if (m3gCheckSectionDataLength(loader, data, paramLength) == M3G_FALSE) return M3G_FALSE;
+            userData->params[i] = (M3Gbyte *)m3gAlloc(M3G_INTERFACE(loader), paramLength*sizeof(M3Gbyte));
+            if (userData->params[i] == NULL)
+                return M3G_FALSE;
+            m3gCopy(userData->params[i], data, paramLength);
+            data += paramLength;
+        }
+    }
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Skips Object3D data
+ */
+static M3Gbool m3gSkipObject3DData(Loader *loader)
+{
+    M3Guint animTracks, i, userParams, paramLength;
+    M3Gubyte *data = m3gGetSectionDataPtr(loader, 8);
+    if (data == NULL) return M3G_FALSE;
+
+    data += 4;
+    animTracks = m3gLoadInt(data);
+    data += 4;
+
+    /* Overflow? */
+    if (animTracks >= 0x1fffffff) {
+        return M3G_FALSE;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, animTracks * 4 + 4) == M3G_FALSE) return M3G_FALSE;
+    for (i = 0; i < animTracks; i++) {
+        data += 4;
+    }
+
+    userParams = m3gLoadInt(data);
+    data += 4;
+
+    for (i = 0; i < userParams; i++) {
+        if (m3gCheckSectionDataLength(loader, data, 8) == M3G_FALSE) return M3G_FALSE;
+        data += 4;
+        paramLength = m3gLoadInt(data);
+        if (m3gCheckSectionDataLength(loader, data, paramLength) == M3G_FALSE) return M3G_FALSE;
+        data += 4 + paramLength;
+    }
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads transformable data
+ */
+static M3Gbool m3gLoadTransformableData(Loader *loader, M3GTransformable obj)
+{
+    M3Gfloat f1, f2, f3, f4;
+    M3Gubyte *data;
+    if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
+        return M3G_FALSE;
+    }
+
+    data = m3gGetSectionDataPtr(loader, 1);
+    if (data == NULL) return M3G_FALSE;
+
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    /* Component transform ? */
+    if (*data++) {
+        if (m3gCheckSectionDataLength(loader, data, 40) == M3G_FALSE) return M3G_FALSE;
+        if (!m3gLoadFloat(data + 0, &f1) ||
+            !m3gLoadFloat(data + 4, &f2) ||
+            !m3gLoadFloat(data + 8, &f3))
+            return M3G_FALSE;
+        m3gSetTranslation(obj, f1, f2, f3);
+        if (!m3gLoadFloat(data + 12, &f1) ||
+            !m3gLoadFloat(data + 16, &f2) ||
+            !m3gLoadFloat(data + 20, &f3))
+            return M3G_FALSE;
+        m3gSetScale(obj, f1, f2, f3);
+        if (!m3gLoadFloat(data + 24, &f1) ||
+            !m3gLoadFloat(data + 28, &f2) ||
+            !m3gLoadFloat(data + 32, &f3) ||
+            !m3gLoadFloat(data + 36, &f4))
+            return M3G_FALSE;
+        m3gSetOrientation(obj, f1, f2, f3, f4);
+        data += 40;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, 1) == M3G_FALSE) return M3G_FALSE;
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    /* Generic transform */
+    if (*data++) {
+        Matrix m;
+        if (m3gCheckSectionDataLength(loader, data, 64) == M3G_FALSE) return M3G_FALSE;
+        if (!m3gLoadMatrix(&m, data)) return M3G_FALSE;
+        m3gSetTransform(obj, &m);
+        data += 64;
+    }
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Skips transformable data
+ */
+static M3Gbool m3gSkipTransformableData(Loader *loader)
+{
+    M3Gubyte *data;
+    if (!m3gSkipObject3DData(loader)) return M3G_FALSE;
+
+    data = m3gGetSectionDataPtr(loader, 1);
+    if (data == NULL) return M3G_FALSE;
+
+    /* Component transform ? */
+    if (*data++) {
+        if (m3gCheckSectionDataLength(loader, data, 40) == M3G_FALSE) return M3G_FALSE;
+        data += 40;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, 1) == M3G_FALSE) return M3G_FALSE;
+    /* Generic transform */
+    if (*data++) {
+        if (m3gCheckSectionDataLength(loader, data, 64) == M3G_FALSE) return M3G_FALSE;
+        data += 64;
+    }
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads node data
+ */
+static M3Gbool m3gLoadNodeData(Loader *loader, M3GNode obj)
+{
+    M3Gubyte *data;
+    if (!m3gLoadTransformableData(loader, (M3GTransformable)obj)) {
+        return M3G_FALSE;
+    }
+
+    data = m3gGetSectionDataPtr(loader, 8);
+    if (data == NULL) return M3G_FALSE;
+
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    m3gEnable(obj, 0, *data++);
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    m3gEnable(obj, 1, *data++);
+    {
+        unsigned a = *data++; 
+        m3gSetAlphaFactor(obj, m3gDivif(a, 255));
+    }
+    m3gSetScope(obj, m3gLoadInt(data));
+    data += 4;
+
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    if (*data++) {
+        M3Gubyte zt, yt;
+        M3Gint zr, yr;
+        if (m3gCheckSectionDataLength(loader, data, 10) == M3G_FALSE) return M3G_FALSE;
+        zt = *data++;
+        yt = *data++;
+        zr = m3gLoadInt(data);
+        yr = m3gLoadInt(data + 4);
+        m3gSetAlignment(obj,    (M3GNode)m3gGetLoaded(loader, zr, ANY_NODE_CLASS),
+                                zt,
+                                (M3GNode)m3gGetLoaded(loader, yr, ANY_NODE_CLASS),
+                                yt);
+        data += 8;
+    }
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Skips node data
+ */
+static M3Gbool m3gSkipNodeData(Loader *loader)
+{
+    M3Gubyte *data;
+    if (!m3gSkipTransformableData(loader)) return M3G_FALSE;
+
+    data = m3gGetSectionDataPtr(loader, 8);
+    if (data == NULL) return M3G_FALSE;
+    data += 7;
+
+    /* Alignment? */
+    if (*data++) {
+        if (m3gCheckSectionDataLength(loader, data, 10) == M3G_FALSE) return M3G_FALSE;
+        data += 10;
+    }
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a camera
+ */
+static M3Gbool m3gLoadCamera(Loader *loader)
+{
+    M3Gfloat f1, f2, f3, f4;
+    M3Gubyte *data;
+    M3GCamera obj = m3gCreateCamera(M3G_INTERFACE(loader));
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    if (!m3gLoadNodeData(loader, (M3GNode)obj)) {
+        return M3G_FALSE;
+    }
+
+    data = m3gGetSectionDataPtr(loader, 1);
+    if (data == NULL) return M3G_FALSE;
+
+    switch(*data++) {
+    case M3G_GENERIC:
+        {
+            Matrix m;
+            if (m3gCheckSectionDataLength(loader, data, 64) == M3G_FALSE) return M3G_FALSE;
+            if (!m3gLoadMatrix(&m, data)) return M3G_FALSE;
+            m3gSetProjectionMatrix(obj, &m);
+            data += 64;
+        }
+        break;
+    case M3G_PERSPECTIVE:
+        if (m3gCheckSectionDataLength(loader, data, 16) == M3G_FALSE) return M3G_FALSE;
+        if (!m3gLoadFloat(data + 0, &f1) ||
+            !m3gLoadFloat(data + 4, &f2) ||
+            !m3gLoadFloat(data + 8, &f3) ||
+            !m3gLoadFloat(data + 12, &f4))
+            return M3G_FALSE;
+        m3gSetPerspective(obj, f1, f2, f3, f4);
+        data += 16;
+        break;
+    case M3G_PARALLEL:
+        if (m3gCheckSectionDataLength(loader, data, 16) == M3G_FALSE) return M3G_FALSE;
+        if (!m3gLoadFloat(data + 0, &f1) ||
+            !m3gLoadFloat(data + 4, &f2) ||
+            !m3gLoadFloat(data + 8, &f3) ||
+            !m3gLoadFloat(data + 12, &f4))
+            return M3G_FALSE;
+        m3gSetParallel(obj, f1, f2, f3, f4);
+        data += 16;
+        break;
+    default:
+        /* Error */
+        break;
+    }
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a background
+ */
+static M3Gbool m3gLoadBackground(Loader *loader)
+{
+    M3Gubyte *data;
+    M3GBackground obj = m3gCreateBackground(M3G_INTERFACE(loader));
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
+        return M3G_FALSE;
+    }
+
+    data = m3gGetSectionDataPtr(loader, 28);
+    if (data == NULL) return M3G_FALSE;
+
+    m3gSetBgColor(obj, m3gLoadARGB(data));
+    data += 4;
+    m3gSetBgImage(obj, (M3GImage)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_IMAGE));
+    data += 4;
+    m3gSetBgMode(obj, data[0], data[1]);
+    data += 2;
+    m3gSetBgCrop(obj,   m3gLoadInt(data),
+                        m3gLoadInt(data + 4),
+                        m3gLoadInt(data + 8),
+                        m3gLoadInt(data + 12) );
+    data += 16;
+
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    m3gSetBgEnable(obj, 0, *data++);
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    m3gSetBgEnable(obj, 1, *data++);
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a vertex array
+ */
+static M3Gbool m3gLoadVertexArray(Loader *loader)
+{
+    M3Gint i, j;
+    M3Guint size;
+    M3Gushort vertices;
+    M3Gubyte *data, components, encoding;
+    M3Gdatatype componentSize;
+    M3GVertexArray obj;
+
+    m3gBeginObject(loader);
+    if (!m3gSkipObject3DData(loader)) return M3G_FALSE;
+
+    data = m3gGetSectionDataPtr(loader, 5);
+    if (data == NULL) return M3G_FALSE;
+
+    if (*data != 1 && *data != 2) return M3G_FALSE;
+    componentSize = (*data++ == 1) ? M3G_BYTE : M3G_SHORT;
+    components    = *data++;
+    encoding      = *data++;
+    vertices      = m3gLoadShort(data);
+    data += 2;
+
+    size = vertices * components * (componentSize == M3G_BYTE ? 1 : 2);
+
+    /* Overflow? */
+    if (size < vertices) {
+        return M3G_FALSE;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, size) == M3G_FALSE) return M3G_FALSE;
+    obj = m3gCreateVertexArray(M3G_INTERFACE(loader), vertices, components, componentSize);
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    if (componentSize == M3G_BYTE) {
+        M3Gbyte previousValues[4];
+        m3gZero(previousValues, sizeof(previousValues));
+
+        for (i = 0; i < vertices; i++) {
+            for (j = 0; j < components; j++) {
+                if (encoding == 0) {
+                    previousValues[j] = *data++;
+                }
+                else {
+                    previousValues[j] = (M3Gbyte) (previousValues[j] + *data++);
+                }
+            }
+            m3gSetVertexArrayElements(obj, i, 1, sizeof(previousValues), componentSize, previousValues);
+        }
+    }
+    else {
+        M3Gshort previousValues[4];
+        m3gZero(previousValues, sizeof(previousValues));
+
+        for (i = 0; i < vertices; i++) {
+            for (j = 0; j < components; j++) {
+                if (encoding == 0) {
+                    previousValues[j] = m3gLoadShort(data);
+                }
+                else {
+                    previousValues[j] = (M3Gshort) (previousValues[j] + m3gLoadShort(data));
+                }
+                data += 2;
+            }
+            m3gSetVertexArrayElements(obj, i, 1, sizeof(previousValues), componentSize, previousValues);
+        }
+    }
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    m3gEndObject(loader);
+    m3gRewindObject(loader);
+    if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
+        return M3G_FALSE;
+    }
+    m3gSkipObject(loader);
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a vertex buffer
+ */
+static M3Gbool m3gLoadVertexBuffer(Loader *loader)
+{
+    M3Gubyte *data;
+    M3GVertexArray va;
+    M3Gfloat bias[3], scale;
+    M3Guint i, taCount;
+    M3GVertexBuffer obj = m3gCreateVertexBuffer(M3G_INTERFACE(loader));
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
+        return M3G_FALSE;
+    }
+
+    data = m3gGetSectionDataPtr(loader, 36);
+    if (data == NULL) return M3G_FALSE;
+
+    m3gSetVertexDefaultColor(obj, m3gLoadARGB(data));
+    data += 4;
+
+    /* Positions */
+    va = (M3GVertexArray)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_VERTEX_ARRAY);
+    data += 4;
+    if (!m3gLoadFloat(data + 0, &bias[0])) return M3G_FALSE;
+    if (!m3gLoadFloat(data + 4, &bias[1])) return M3G_FALSE;
+    if (!m3gLoadFloat(data + 8, &bias[2])) return M3G_FALSE;
+    if (!m3gLoadFloat(data + 12, &scale)) return M3G_FALSE;
+    if (va != NULL) {
+        m3gSetVertexArray(obj, va, scale, bias, 3);
+    }
+    data += 16;
+
+    /* Normals */
+    va = (M3GVertexArray)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_VERTEX_ARRAY);
+    data += 4;
+    if (va != NULL) {
+        m3gSetNormalArray(obj, va);
+    }
+
+    /* Colors */
+    va = (M3GVertexArray)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_VERTEX_ARRAY);
+    data += 4;
+    if (va != NULL) {
+        m3gSetColorArray(obj, va);
+    }
+
+    /* Texture coordinates */
+    taCount = m3gLoadInt(data);
+    data += 4;
+
+    /* Overflow? */
+    if (taCount >= 0x0ccccccc) {
+        return M3G_FALSE;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, taCount * 20) == M3G_FALSE) return M3G_FALSE;
+    for (i = 0; i < taCount; i++) {
+        va = (M3GVertexArray)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_VERTEX_ARRAY);
+        data += 4;
+        if (!m3gLoadFloat(data + 0, &bias[0])) return M3G_FALSE;
+        if (!m3gLoadFloat(data + 4, &bias[1])) return M3G_FALSE;
+        if (!m3gLoadFloat(data + 8, &bias[2])) return M3G_FALSE;
+        if (!m3gLoadFloat(data + 12, &scale)) return M3G_FALSE;
+        if (va != NULL) {
+            m3gSetTexCoordArray(obj, i, va, scale, bias, 3);
+        }
+        else {
+            return M3G_FALSE;
+        }
+        data += 16;
+    }
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    return M3G_TRUE;
+}
+
+
+/*!
+ * \internal
+ * \brief Loads a triangle strip array
+ */
+static M3Gbool m3gLoadTsa(Loader *loader)
+{
+    M3Gubyte *data;
+    M3GIndexBuffer obj = 0;
+    M3Gubyte encoding;
+    M3Guint startIndex = 0, indicesCount = 0, *indices = NULL;
+    M3Guint lengthCount;
+    M3Gint *lengths = NULL;
+    M3Guint i;
+
+    m3gBeginObject(loader);
+    if (!m3gSkipObject3DData(loader)) return M3G_FALSE;
+
+    data = m3gGetSectionDataPtr(loader, 1);
+    if (data == NULL) return M3G_FALSE;
+
+    encoding = *data++;
+
+    switch(encoding) {
+    case 0:
+        if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) return M3G_FALSE;
+        startIndex = m3gLoadInt(data);
+        data += 4;
+        break;
+
+    case 1:
+        if (m3gCheckSectionDataLength(loader, data, 1) == M3G_FALSE) return M3G_FALSE;
+        startIndex = *data++;
+        break;
+
+    case 2:
+        if (m3gCheckSectionDataLength(loader, data, 2) == M3G_FALSE) return M3G_FALSE;
+        startIndex = (M3Gushort) m3gLoadShort(data);
+        data += 2;
+        break;
+
+    case 128:
+        if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) return M3G_FALSE;
+        indicesCount = m3gLoadInt(data);
+        data += 4;
+
+        /* Overflow? */
+        if (indicesCount >= 0x20000000) {
+            return M3G_FALSE;
+        }
+
+        if (m3gCheckSectionDataLength(loader, data, indicesCount * 4) == M3G_FALSE) return M3G_FALSE;
+        indices = m3gAllocZ(M3G_INTERFACE(loader), sizeof(M3Gint) * indicesCount);
+        if (!indices) return M3G_FALSE;
+        for (i = 0; i < indicesCount; i++ ) {
+            indices[i] = m3gLoadInt(data);
+            data += 4;
+        }
+        break;
+
+    case 129:
+        if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) return M3G_FALSE;
+        indicesCount = m3gLoadInt(data);
+        data += 4;
+        if (m3gCheckSectionDataLength(loader, data, indicesCount) == M3G_FALSE) return M3G_FALSE;
+        indices = m3gAllocZ(M3G_INTERFACE(loader), sizeof(M3Gint) * indicesCount);
+        if (!indices) return M3G_FALSE;
+        for (i = 0; i < indicesCount; i++ ) {
+            indices[i] = *data++;
+        }
+        break;
+
+    case 130:
+        if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) return M3G_FALSE;
+        indicesCount = m3gLoadInt(data);
+        data += 4;
+
+        /* Overflow? */
+        if (indicesCount >= 0x40000000) {
+            return M3G_FALSE;
+        }
+
+        if (m3gCheckSectionDataLength(loader, data, indicesCount * 2) == M3G_FALSE) return M3G_FALSE;
+        indices = m3gAllocZ(M3G_INTERFACE(loader), sizeof(M3Gint) * indicesCount);
+        if (!indices) return M3G_FALSE;
+        for (i = 0; i < indicesCount; i++) {
+            indices[i] = (M3Gushort)m3gLoadShort(data);
+            data += 2;
+        }
+        break;
+
+    default:
+        return M3G_FALSE;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) goto cleanup;
+    lengthCount = m3gLoadInt(data);
+    data += 4;
+
+    /* Overflow? */
+    if (lengthCount >= 0x20000000) {
+        goto cleanup;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, lengthCount * 4) == M3G_FALSE) goto cleanup;
+    lengths = m3gAllocZ(M3G_INTERFACE(loader), sizeof(M3Gint) * lengthCount);
+    if (!lengths) goto cleanup;
+
+    for (i = 0; i < lengthCount; i++) {
+        lengths[i] = m3gLoadInt(data);
+        data += 4;
+    }
+
+    if (encoding == 0 || encoding == 1 || encoding == 2) {
+        obj = m3gCreateImplicitStripBuffer( M3G_INTERFACE(loader),
+                                            lengthCount,
+                                            lengths,
+                                            startIndex);
+    }
+    else {
+        obj = m3gCreateStripBuffer( M3G_INTERFACE(loader),
+                                    M3G_TRIANGLE_STRIPS,
+                                    lengthCount,
+                                    lengths,
+                                    M3G_INT,
+                                    indicesCount,
+                                    indices);
+    }
+
+cleanup:
+    m3gFree(M3G_INTERFACE(loader), indices);
+    m3gFree(M3G_INTERFACE(loader), lengths);
+
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    m3gEndObject(loader);
+    m3gRewindObject(loader);
+    if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
+        return M3G_FALSE;
+    }
+    m3gSkipObject(loader);
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a compositing mode
+ */
+static M3Gbool m3gLoadCompositingMode(Loader *loader)
+{
+    M3Gfloat f1, f2;
+    M3Gubyte *data;
+    M3GCompositingMode obj = m3gCreateCompositingMode(M3G_INTERFACE(loader));
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
+        return M3G_FALSE;
+    }
+
+    data = m3gGetSectionDataPtr(loader, 14);
+    if (data == NULL) return M3G_FALSE;
+
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    m3gEnableDepthTest      (obj, *data++);
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    m3gEnableDepthWrite     (obj, *data++);
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    m3gEnableColorWrite     (obj, *data++);
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    m3gSetAlphaWriteEnable  (obj, *data++);
+    m3gSetBlending          (obj, *data++);
+    {
+        unsigned a = *data++; 
+        m3gSetAlphaThreshold(obj, m3gDivif(a, 255));
+    }
+    if (!m3gLoadFloat(data, &f1) || !m3gLoadFloat(data + 4, &f2)) return M3G_FALSE;
+    m3gSetDepthOffset       (obj, f1, f2);
+    data += 8;
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a polygon mode
+ */
+static M3Gbool m3gLoadPolygonMode(Loader *loader)
+{
+    M3Gubyte *data;
+    M3GPolygonMode obj = m3gCreatePolygonMode(M3G_INTERFACE(loader));
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
+        return M3G_FALSE;
+    }
+
+    data = m3gGetSectionDataPtr(loader, 6);
+    if (data == NULL) return M3G_FALSE;
+
+    m3gSetCulling                       (obj, *data++);
+    m3gSetShading                       (obj, *data++);
+    m3gSetWinding                       (obj, *data++);
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    m3gSetTwoSidedLightingEnable        (obj, *data++);
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    m3gSetLocalCameraLightingEnable     (obj, *data++);
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    m3gSetPerspectiveCorrectionEnable   (obj, *data++);
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads an appearance
+ */
+static M3Gbool m3gLoadAppearance(Loader *loader)
+{
+    M3Guint textures, i;
+    M3Gubyte *data;
+    M3GAppearance obj = m3gCreateAppearance(M3G_INTERFACE(loader));
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
+        return M3G_FALSE;
+    }
+
+    data = m3gGetSectionDataPtr(loader, 21);
+    if (data == NULL) return M3G_FALSE;
+
+    m3gSetLayer(obj, (M3Gbyte)*data++);
+    m3gSetCompositingMode(obj, (M3GCompositingMode)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_COMPOSITING_MODE));
+    data += 4;
+    m3gSetFog(obj, (M3GFog)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_FOG));
+    data += 4;
+    m3gSetPolygonMode(obj, (M3GPolygonMode)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_POLYGON_MODE));
+    data += 4;
+    m3gSetMaterial(obj, (M3GMaterial)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_MATERIAL));
+    data += 4;
+
+    textures = m3gLoadInt(data);
+    data += 4;
+
+    /* Overflow? */
+    if (textures >= 0x20000000) {
+        return M3G_FALSE;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, textures * 4) == M3G_FALSE) return M3G_FALSE;
+    for (i = 0; i < textures; i++) {
+        M3GTexture tex = (M3GTexture)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_TEXTURE);
+        if (!tex) {
+            return M3G_FALSE;
+        }
+        m3gSetTexture(obj, i, tex);
+        data += 4;
+    }
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a mesh
+ */
+static M3Gbool m3gLoadMesh(Loader *loader)
+{
+    M3Guint subMeshCount, i;
+    M3GVertexBuffer vb;
+    M3GIndexBuffer *ib = NULL;
+    M3GAppearance *ap = NULL;
+    M3Gubyte *data;
+    M3GMesh obj = NULL;
+
+    m3gBeginObject(loader);
+    if (!m3gSkipNodeData(loader)) return M3G_FALSE;
+
+    data = m3gGetSectionDataPtr(loader, 8);
+    if (data == NULL) return M3G_FALSE;
+
+    vb = (M3GVertexBuffer)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_VERTEX_BUFFER);
+    if (vb == NULL) return M3G_FALSE;
+    data += 4;
+    subMeshCount = m3gLoadInt(data);
+    data += 4;
+
+    /* Overflow? */
+    if (subMeshCount >= 0x10000000) {
+        return M3G_FALSE;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, subMeshCount * 8) == M3G_FALSE) return M3G_FALSE;
+    ib = m3gAllocZ(M3G_INTERFACE(loader), sizeof(*ib) * subMeshCount);
+    ap = m3gAllocZ(M3G_INTERFACE(loader), sizeof(*ap) * subMeshCount);
+    if (!ib || !ap) goto cleanup;
+
+    for (i = 0; i < subMeshCount; i++) {
+        M3GIndexBuffer indexBuffer;
+        indexBuffer = (M3GIndexBuffer)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_INDEX_BUFFER);
+        if (indexBuffer != NULL && loader->triConstraint != 0) {
+            loader->triCount += indexBuffer->indexCount;
+            if (loader->triCount > loader->triConstraint) goto cleanup;
+        }
+        ib[i] = indexBuffer;
+        data += 4;
+        ap[i] = (M3GAppearance)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_APPEARANCE);
+        data += 4;
+    }
+
+    obj = m3gCreateMesh(    M3G_INTERFACE(loader),
+                            vb,
+                            ib,
+                            ap,
+                            subMeshCount);
+
+cleanup:
+    m3gFree(M3G_INTERFACE(loader), ib);
+    m3gFree(M3G_INTERFACE(loader), ap);
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    m3gEndObject(loader);
+    m3gRewindObject(loader);
+    if (!m3gLoadNodeData(loader, (M3GNode)obj)) {
+        return M3G_FALSE;
+    }
+    m3gSkipObject(loader);
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads group data
+ */
+static M3Gbool m3gLoadGroupData(Loader *loader, M3GGroup obj)
+{
+    M3Guint childCount, i;
+    M3Gubyte *data;
+
+    if (!m3gLoadNodeData(loader, (M3GNode)obj)) {
+        return M3G_FALSE;
+    }
+
+    data = m3gGetSectionDataPtr(loader, 4);
+    if (data == NULL) return M3G_FALSE;
+
+    childCount = m3gLoadInt(data);
+    data += 4;
+
+    /* Overflow? */
+    if (childCount >= 0x20000000) {
+        return M3G_FALSE;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, childCount * 4) == M3G_FALSE) return M3G_FALSE;
+    for (i = 0; i < childCount; i++) {
+        m3gAddChild(obj, (M3GNode)m3gGetLoaded(loader, m3gLoadInt(data), ANY_NODE_CLASS));
+        data += 4;
+    }
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a group
+ */
+static M3Gbool m3gLoadGroup(Loader *loader)
+{
+    M3GGroup obj = m3gCreateGroup(M3G_INTERFACE(loader));
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    if (!m3gLoadGroupData(loader, obj)) {
+        return M3G_FALSE;
+    }
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a world
+ */
+static M3Gbool m3gLoadWorld(Loader *loader)
+{
+    M3GCamera cam;
+    M3Gubyte *data;
+    M3GWorld obj = m3gCreateWorld(M3G_INTERFACE(loader));
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    if (!m3gLoadGroupData(loader, (M3GGroup)obj)) {
+        return M3G_FALSE;
+    }
+
+    data = m3gGetSectionDataPtr(loader, 8);
+    if (data == NULL) return M3G_FALSE;
+
+    cam = (M3GCamera)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_CAMERA);
+    data += 4;
+    if (cam != NULL) {
+        m3gSetActiveCamera(obj, cam);
+    }
+    m3gSetBackground(obj, (M3GBackground)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_BACKGROUND));
+    data += 4;
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a light
+ */
+static M3Gbool m3gLoadLight(Loader *loader)
+{
+    M3Gfloat f1, f2, f3;
+    M3Gubyte *data;
+    M3GLight obj = m3gCreateLight(M3G_INTERFACE(loader));
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    if (!m3gLoadNodeData(loader, (M3GNode)obj)) {
+        return M3G_FALSE;
+    }
+
+    data = m3gGetSectionDataPtr(loader, 28);
+    if (data == NULL) return M3G_FALSE;
+
+    if (!m3gLoadFloat(data, &f1) ||
+        !m3gLoadFloat(data + 4, &f2) ||
+        !m3gLoadFloat(data + 8, &f3)) return M3G_FALSE;
+    m3gSetAttenuation   (obj, f1, f2, f3);
+    data += 12;
+    m3gSetLightColor    (obj, m3gLoadRGB(data));
+    data += 3;
+    m3gSetLightMode     (obj, *data++);
+    if (!m3gLoadFloat(data, &f1)) return M3G_FALSE;
+    m3gSetIntensity     (obj, f1);
+    data += 4;
+    if (!m3gLoadFloat(data, &f1)) return M3G_FALSE;
+    m3gSetSpotAngle     (obj, f1);
+    data += 4;
+    if (!m3gLoadFloat(data, &f1)) return M3G_FALSE;
+    m3gSetSpotExponent  (obj, f1);
+    data += 4;
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a keyframe sequence
+ */
+static M3Gbool m3gLoadKeyframeSequence(Loader *loader)
+{
+    M3Guint i, j, interpolation, repeatMode, encoding, duration,
+            rangeFirst, rangeLast, components, keyFrames, size;
+    M3Gfloat *values;
+    M3Gubyte *data;
+    M3GKeyframeSequence obj;
+
+    m3gBeginObject(loader);
+    if (!m3gSkipObject3DData(loader)) return M3G_FALSE;
+
+    data = m3gGetSectionDataPtr(loader, 23);
+    if (data == NULL) return M3G_FALSE;
+
+    interpolation = *data++;
+    repeatMode    = *data++;
+    encoding      = *data++;
+    duration      = m3gLoadInt(data);
+    data += 4;
+    rangeFirst    = m3gLoadInt(data);
+    data += 4;
+    rangeLast     = m3gLoadInt(data);
+    data += 4;
+    components    = m3gLoadInt(data);
+    data += 4;
+    keyFrames     = m3gLoadInt(data);
+    data += 4;
+
+    if (encoding == 0) {
+        size = keyFrames * (4 + components * 4);
+    }
+    else {
+        size = components * 8 + keyFrames * (4 + components * (encoding == 1 ? 1 : 2));
+    }
+
+    /* Overflow? */
+    if (size < keyFrames) {
+        return M3G_FALSE;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, size) == M3G_FALSE) return M3G_FALSE;
+
+    obj = m3gCreateKeyframeSequence(M3G_INTERFACE(loader), keyFrames, components, interpolation);
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    m3gSetRepeatMode(obj, repeatMode);
+    m3gSetDuration(obj, duration);
+    m3gSetValidRange(obj, rangeFirst, rangeLast);
+
+    values = m3gAllocZ(M3G_INTERFACE(loader), sizeof(M3Gfloat) * components);
+    if (!values) return M3G_FALSE;
+
+    if (encoding == 0) {
+        for (i = 0; i < keyFrames; i++ ) {
+            M3Gint time = m3gLoadInt(data);
+            data += 4;
+
+            for (j = 0; j < components; j++ ) {
+                if (!m3gLoadFloat(data, &values[j])) {
+                    m3gFree(M3G_INTERFACE(loader), values);
+                    return M3G_FALSE;
+                }
+                data += 4;
+            }
+
+            m3gSetKeyframe(obj, i, time, components, values);
+        }
+    }
+    else {
+        M3Gfloat *vectorBiasScale = m3gAllocZ(M3G_INTERFACE(loader), sizeof(M3Gfloat) * components * 2);
+        if (!vectorBiasScale) {
+            m3gFree(M3G_INTERFACE(loader), values);
+            return M3G_FALSE;
+        }
+
+        for (i = 0; i < components; i++ ) {
+            if (!m3gLoadFloat(data, &vectorBiasScale[i])) {
+                m3gFree(M3G_INTERFACE(loader), vectorBiasScale);
+                m3gFree(M3G_INTERFACE(loader), values);
+                return M3G_FALSE;
+            }
+            data += 4;
+        }
+        for (i = 0; i < components; i++ ) {
+            if (!m3gLoadFloat(data, &vectorBiasScale[i + components])) {
+                m3gFree(M3G_INTERFACE(loader), vectorBiasScale);
+                m3gFree(M3G_INTERFACE(loader), values);
+                return M3G_FALSE;
+            }
+            data += 4;
+        }
+
+        for (i = 0; i < keyFrames; i++ ) {
+            M3Gint time;
+            time = m3gLoadInt(data);
+            data += 4;
+
+            if (encoding == 1) {
+                for (j = 0; j < components; j++ ) {
+                    M3Gubyte v = *data++;
+                    values[j] = vectorBiasScale[j] + ((vectorBiasScale[j + components] * v ) / 255.0f);
+                }
+            }
+            else {
+                for (j = 0; j < components; j++ ) {
+                    M3Gushort v = m3gLoadShort(data);
+                    data += 2;
+                    values[j] = vectorBiasScale[j] + ((vectorBiasScale[j + components] * v) / 65535.0f);
+                }
+            }
+
+            m3gSetKeyframe(obj, i, time, components, values);
+        }
+
+        m3gFree(M3G_INTERFACE(loader), vectorBiasScale);
+    }
+
+    m3gFree(M3G_INTERFACE(loader), values);
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    m3gEndObject(loader);
+    m3gRewindObject(loader);
+    if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
+        return M3G_FALSE;
+    }
+    m3gSkipObject(loader);
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads an animation controller
+ */
+static M3Gbool m3gLoadAnimationController(Loader *loader)
+{
+    M3Gfloat speed, weight, referenceSeqTime;
+    M3Gint referenceWorldTime;
+    M3Gubyte *data;
+    M3GAnimationController obj = m3gCreateAnimationController(M3G_INTERFACE(loader));
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
+        return M3G_FALSE;
+    }
+
+    data = m3gGetSectionDataPtr(loader, 24);
+    if (data == NULL) return M3G_FALSE;
+
+    if (!m3gLoadFloat(data, &speed)) return M3G_FALSE;
+    data += 4;
+    if (!m3gLoadFloat(data, &weight)) return M3G_FALSE;
+    data += 4;
+    m3gSetActiveInterval(obj, m3gLoadInt(data), m3gLoadInt(data + 4));
+    data += 8;
+    if (!m3gLoadFloat(data, &referenceSeqTime)) return M3G_FALSE;
+    data += 4;
+    referenceWorldTime = m3gLoadInt(data);
+    data += 4;
+
+    m3gSetPosition(obj, referenceSeqTime, referenceWorldTime);
+    m3gSetSpeed(obj, speed, referenceWorldTime);
+    m3gSetWeight(obj, weight);
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads an animation track
+ */
+static M3Gbool m3gLoadAnimationTrack(Loader *loader)
+{
+    M3Gint property;
+    M3GKeyframeSequence ks;
+    M3GAnimationController ac;
+    M3Gubyte *data;
+    M3GAnimationTrack obj;
+
+    m3gBeginObject(loader);
+    if (!m3gSkipObject3DData(loader)) return M3G_FALSE;
+
+    data = m3gGetSectionDataPtr(loader, 12);
+    if (data == NULL) return M3G_FALSE;
+
+    ks = (M3GKeyframeSequence)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_KEYFRAME_SEQUENCE);
+    data += 4;
+    ac = (M3GAnimationController)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_ANIMATION_CONTROLLER);
+    data += 4;
+    property = m3gLoadInt(data);
+    data += 4;
+
+    obj = m3gCreateAnimationTrack(M3G_INTERFACE(loader), ks, property);
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    m3gSetController(obj, ac);
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    m3gEndObject(loader);
+    m3gRewindObject(loader);
+    if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
+        return M3G_FALSE;
+    }
+    m3gSkipObject(loader);
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a material
+ */
+static M3Gbool m3gLoadMaterial(Loader *loader)
+{
+    M3Gfloat f1;
+    M3Gubyte *data;
+    M3GMaterial obj = m3gCreateMaterial(M3G_INTERFACE(loader));
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
+        return M3G_FALSE;
+    }
+
+    data = m3gGetSectionDataPtr(loader, 18);
+    if (data == NULL) return M3G_FALSE;
+
+    m3gSetColor(obj, M3G_AMBIENT_BIT, m3gLoadRGB(data));
+    data += 3;
+    m3gSetColor(obj, M3G_DIFFUSE_BIT, m3gLoadARGB(data));
+    data += 4;
+    m3gSetColor(obj, M3G_EMISSIVE_BIT, m3gLoadRGB(data));
+    data += 3;
+    m3gSetColor(obj, M3G_SPECULAR_BIT, m3gLoadRGB(data));
+    data += 3;
+    if (!m3gLoadFloat(data, &f1)) return M3G_FALSE;
+    m3gSetShininess(obj, f1);
+    data += 4;
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    m3gSetVertexColorTrackingEnable(obj, *data++);
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a fog
+ */
+static M3Gbool m3gLoadFog(Loader *loader)
+{
+    M3Gfloat f1, f2;
+    M3Gubyte *data;
+    M3GFog obj = m3gCreateFog(M3G_INTERFACE(loader));
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
+        return M3G_FALSE;
+    }
+
+    data = m3gGetSectionDataPtr(loader, 4);
+    if (data == NULL) return M3G_FALSE;
+
+    m3gSetFogColor(obj, m3gLoadRGB(data));
+    data += 3;
+    m3gSetFogMode(obj, *data);
+
+    if (*data++ == M3G_EXPONENTIAL_FOG) {
+        if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) return M3G_FALSE;
+        if (!m3gLoadFloat(data, &f1)) return M3G_FALSE;
+        m3gSetFogDensity(obj, f1);
+        data += 4;
+    }
+    else {
+        if (m3gCheckSectionDataLength(loader, data, 8) == M3G_FALSE) return M3G_FALSE;
+        if (!m3gLoadFloat(data, &f1) || !m3gLoadFloat(data + 4, &f2)) return M3G_FALSE;
+        m3gSetFogLinear(obj, f1, f2);
+        data += 8;
+    }
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads an image
+ */
+static M3Gbool m3gLoadImage(Loader *loader)
+{
+    M3GImageFormat format;
+    M3Guint width, height;
+    M3Gbyte isMutable;
+    M3Gubyte *data;
+    M3GImage obj;
+
+    m3gBeginObject(loader);
+    if (!m3gSkipObject3DData(loader)) return M3G_FALSE;
+
+    data = m3gGetSectionDataPtr(loader, 10);
+    if (data == NULL) return M3G_FALSE;
+
+    format = (M3GImageFormat)*data++;
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    isMutable = *data++;
+    width = m3gLoadInt(data);
+    data += 4;
+    height = m3gLoadInt(data);
+    data += 4;
+
+    if (isMutable) {
+        obj = m3gCreateImage(M3G_INTERFACE(loader), format,
+                                                    width, height,
+                                                    M3G_RENDERING_TARGET);
+    }
+    else {
+        M3Gubyte *palette = NULL, *pixels = NULL;
+        M3Gint paletteLength, pixelsLength, bpp;
+
+        switch(format) {
+        case M3G_ALPHA:             bpp = 1; break;
+        case M3G_LUMINANCE:         bpp = 1; break;
+        case M3G_LUMINANCE_ALPHA:   bpp = 2; break;
+        case M3G_RGB:               bpp = 3; break;
+        case M3G_RGBA:              bpp = 4; break;
+        default:                    return M3G_FALSE;
+        }
+
+        if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) return M3G_FALSE;
+        paletteLength = m3gLoadInt(data);
+        data += 4;
+
+        if (paletteLength > 0) {
+            if (m3gCheckSectionDataLength(loader, data, paletteLength) == M3G_FALSE) return M3G_FALSE;
+            palette = data;
+            data += paletteLength;
+        }
+
+        if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) return M3G_FALSE;
+        pixelsLength = m3gLoadInt(data);
+        data += 4;
+        if (m3gCheckSectionDataLength(loader, data, pixelsLength) == M3G_FALSE) return M3G_FALSE;
+        pixels = data;
+        data += pixelsLength;
+
+        if (palette != NULL) {
+            obj = m3gCreateImage(M3G_INTERFACE(loader), format,
+                                                        width, height,
+                                                        M3G_PALETTED);
+            if (obj != NULL) {
+                M3Gint numEntries = paletteLength / bpp;
+                if (numEntries > 256) {
+                    numEntries = 256;
+                }
+                m3gSetImage(obj, pixels);
+                m3gSetImagePalette(obj, numEntries, palette);
+                m3gCommitImage(obj);
+            }
+        }
+        else {
+            obj = m3gCreateImage(M3G_INTERFACE(loader), format,
+                                                        width, height,
+                                                        0);
+            if (obj != NULL) {
+                m3gSetImage(obj, pixels);
+                m3gCommitImage(obj);
+            }
+        }
+    }
+
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    m3gEndObject(loader);
+    m3gRewindObject(loader);
+    if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
+        return M3G_FALSE;
+    }
+    m3gSkipObject(loader);
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a texture
+ */
+static M3Gbool m3gLoadTexture(Loader *loader)
+{
+    M3GImage image;
+    M3Gubyte *data;
+    M3GTexture obj;
+
+    m3gBeginObject(loader);
+    if (!m3gSkipTransformableData(loader)) return M3G_FALSE;
+
+    data = m3gGetSectionDataPtr(loader, 12);
+    if (data == NULL) return M3G_FALSE;
+
+    image = (M3GImage)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_IMAGE);
+    data += 4;
+
+    obj = m3gCreateTexture(M3G_INTERFACE(loader), image);
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    m3gSetBlendColor(obj, m3gLoadRGB(data));
+    data += 3;
+    m3gTextureSetBlending(obj, *data++);
+    m3gSetWrapping(obj, data[0], data[1]);
+    data += 2;
+    m3gSetFiltering(obj, data[0], data[1]);
+    data += 2;
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    m3gEndObject(loader);
+    m3gRewindObject(loader);
+    if (!m3gLoadTransformableData(loader, (M3GTransformable)obj)) {
+        return M3G_FALSE;
+    }
+    m3gSkipObject(loader);
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a skinned mesh
+ */
+static M3Gbool m3gLoadSkinnedMesh(Loader *loader)
+{
+    M3GVertexBuffer vb;
+    M3Guint i, subMeshCount, transformReferenceCount, firstVertex, vertexCount;
+    M3Gint weight;
+    M3GIndexBuffer *ib = NULL;
+    M3GAppearance *ap = NULL;
+    M3GGroup skeleton;
+    M3GNode bone;
+    M3Gubyte *data;
+    M3GSkinnedMesh obj = NULL;
+
+    m3gBeginObject(loader);
+    if (!m3gSkipNodeData(loader)) return M3G_FALSE;
+
+    data = m3gGetSectionDataPtr(loader, 8);
+    if (data == NULL) return M3G_FALSE;
+
+    vb = (M3GVertexBuffer)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_VERTEX_BUFFER);
+    if (vb == NULL) return M3G_FALSE;
+    data += 4;
+    subMeshCount = m3gLoadInt(data);
+    data += 4;
+
+    /* Overflow? */
+    if (subMeshCount >= 0x10000000) {
+        return M3G_FALSE;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, subMeshCount * 8) == M3G_FALSE) return M3G_FALSE;
+    ib = m3gAllocZ(M3G_INTERFACE(loader), sizeof(*ib) * subMeshCount);
+    ap = m3gAllocZ(M3G_INTERFACE(loader), sizeof(*ap) * subMeshCount);
+    if (!ib || !ap) goto cleanup;
+
+    for (i = 0; i < subMeshCount; i++) {
+        ib[i] = (M3GIndexBuffer)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_INDEX_BUFFER);
+        data += 4;
+        ap[i] = (M3GAppearance)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_APPEARANCE);
+        data += 4;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, 8) == M3G_FALSE) goto cleanup;
+    skeleton = (M3GGroup)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_GROUP);
+    data += 4;
+
+    obj = m3gCreateSkinnedMesh( M3G_INTERFACE(loader),
+                                vb,
+                                ib,
+                                ap,
+                                subMeshCount,
+                                skeleton);
+
+cleanup:
+    m3gFree(M3G_INTERFACE(loader), ib);
+    m3gFree(M3G_INTERFACE(loader), ap);
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    transformReferenceCount = m3gLoadInt(data);
+    data += 4;
+
+    /* Overflow? */
+    if (transformReferenceCount >= 0x08000000) {
+        return M3G_FALSE;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, transformReferenceCount * 16) == M3G_FALSE) return M3G_FALSE;
+    for (i = 0; i < transformReferenceCount; i++) {
+        bone        = (M3GNode)m3gGetLoaded(loader, m3gLoadInt(data), ANY_NODE_CLASS);
+        data += 4;
+        firstVertex = m3gLoadInt(data);
+        data += 4;
+        vertexCount = m3gLoadInt(data);
+        data += 4;
+        weight      = m3gLoadInt(data);
+        data += 4;
+        m3gAddTransform(obj, bone, weight, firstVertex, vertexCount);
+    }
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    m3gEndObject(loader);
+    m3gRewindObject(loader);
+    if (!m3gLoadNodeData(loader, (M3GNode)obj)) {
+        return M3G_FALSE;
+    }
+    m3gSkipObject(loader);
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a morphing mesh
+ */
+static M3Gbool m3gLoadMorphingMesh(Loader *loader)
+{
+    M3GVertexBuffer vb, *targets = NULL;
+    M3Guint i, subMeshCount, targetCount;
+    M3Gfloat *weights = NULL;
+    M3GIndexBuffer *ib = NULL;
+    M3GAppearance *ap = NULL;
+    M3Gubyte *data;
+    M3GMorphingMesh obj = NULL;
+
+    m3gBeginObject(loader);
+    if (!m3gSkipNodeData(loader)) return M3G_FALSE;
+
+    data = m3gGetSectionDataPtr(loader, 8);
+    if (data == NULL) return M3G_FALSE;
+
+    vb = (M3GVertexBuffer)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_VERTEX_BUFFER);
+    if (vb == NULL) return M3G_FALSE;
+    data += 4;
+    subMeshCount = m3gLoadInt(data);
+    data += 4;
+
+    /* Overflow? */
+    if (subMeshCount >= 0x10000000) {
+        return M3G_FALSE;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, subMeshCount * 8) == M3G_FALSE) return M3G_FALSE;
+    ib = m3gAllocZ(M3G_INTERFACE(loader), sizeof(*ib) * subMeshCount);
+    ap = m3gAllocZ(M3G_INTERFACE(loader), sizeof(*ap) * subMeshCount);
+    if (!ib || !ap) goto cleanup;
+
+    for (i = 0; i < subMeshCount; i++) {
+        ib[i] = (M3GIndexBuffer)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_INDEX_BUFFER);
+        data += 4;
+        ap[i] = (M3GAppearance)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_APPEARANCE);
+        data += 4;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) goto cleanup;
+    targetCount = m3gLoadInt(data);
+    data += 4;
+
+    /* Overflow? */
+    if (targetCount >= 0x10000000) {
+        goto cleanup;
+    }
+
+    if (m3gCheckSectionDataLength(loader, data, targetCount * 8) == M3G_FALSE) goto cleanup;
+    weights = m3gAllocZ(M3G_INTERFACE(loader), sizeof(M3Gfloat) * targetCount);
+    targets = m3gAllocZ(M3G_INTERFACE(loader), sizeof(*targets) * targetCount);
+    if (!weights || !targets) goto cleanup;
+
+    for (i = 0; i < targetCount; i++) {
+        targets[i] = (M3GVertexBuffer)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_VERTEX_BUFFER);
+        data += 4;
+        if (!m3gLoadFloat(data, &weights[i])) goto cleanup;
+        data += 4;
+    }
+
+    obj = m3gCreateMorphingMesh(    M3G_INTERFACE(loader),
+                                    vb,
+                                    targets,
+                                    ib,
+                                    ap,
+                                    subMeshCount,
+                                    targetCount);
+
+cleanup:
+    m3gFree(M3G_INTERFACE(loader), ib);
+    m3gFree(M3G_INTERFACE(loader), ap);
+    m3gFree(M3G_INTERFACE(loader), targets);
+    m3gFree(M3G_INTERFACE(loader), weights);
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    m3gEndObject(loader);
+    m3gRewindObject(loader);
+    if (!m3gLoadNodeData(loader, (M3GNode)obj)) {
+        return M3G_FALSE;
+    }
+    m3gSkipObject(loader);
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a sprite
+ */
+static M3Gbool m3gLoadSprite(Loader *loader)
+{
+    M3GImage image;
+    M3GAppearance appearance;
+    M3Gubyte *data;
+    M3GSprite obj;
+
+    m3gBeginObject(loader);
+    if (!m3gSkipNodeData(loader)) return M3G_FALSE;
+
+    data = m3gGetSectionDataPtr(loader, 25);
+    if (data == NULL) return M3G_FALSE;
+
+    image = (M3GImage)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_IMAGE);
+    data += 4;
+    appearance = (M3GAppearance)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_APPEARANCE);
+    data += 4;
+
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    obj = m3gCreateSprite(  M3G_INTERFACE(loader),
+                            *data++,
+                            image,
+                            appearance);
+    loader->loadedObject = (M3GObject)obj;
+    if (!obj) return M3G_FALSE;
+
+    m3gSetCrop(obj, m3gLoadInt(data),
+                    m3gLoadInt(data + 4),
+                    m3gLoadInt(data + 8),
+                    m3gLoadInt(data + 12));
+    data += 16;
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    m3gEndObject(loader);
+    m3gRewindObject(loader);
+    if (!m3gLoadNodeData(loader, (M3GNode)obj)) {
+        return M3G_FALSE;
+    }
+    m3gSkipObject(loader);
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads a M3G file header
+ */
+static M3Gbool m3gLoadHeader(Loader *loader)
+{
+    M3Gubyte *data;
+    data = m3gGetSectionDataPtr(loader, 12);
+    if (data == NULL) return M3G_FALSE;
+
+    /* Check version */
+    if (data[0] != 1 || data[1] != 0 || loader->sectionNum != 0) {
+        return M3G_FALSE;
+    }
+    data += 2;
+
+    if (!m3gVerifyBool(data)) return M3G_FALSE;
+    loader->hasReferences = *data++;
+    loader->fileSize = m3gLoadInt(data);
+    data += 4;
+    loader->contentSize = m3gLoadInt(data);
+    data += 4;
+
+    /* Skip authoring field */
+    while(*data++)
+        if (m3gCheckSectionDataLength(loader, data, 1) == M3G_FALSE) return M3G_FALSE;
+
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Skips external reference
+ */
+static M3Gbool m3gLoadExternalReference(Loader *loader)
+{
+    M3Gubyte *data;
+    if (loader->sectionNum != 1 || !loader->hasReferences)
+        return M3G_FALSE;
+    data = m3gGetSectionDataPtr(loader, 1);
+    while(*data++) { /* Skip string */
+        if (m3gCheckSectionDataLength(loader, data, 1) == M3G_FALSE)
+            return M3G_FALSE;
+    }
+    m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Loads an object
+ */
+static LoaderState m3gLoadObject(Loader *loader)
+{
+    M3Gubyte *data = m3gGetSectionDataPtr(loader, loader->sectionBytesRequired);
+
+    if (data == NULL) {
+        loader->localState = LOADSTATE_CHECKSUM;
+        return LOADSTATE_SECTION;
+    }
+
+    m3gAdvanceSectionData(loader, loader->sectionBytesRequired);
+
+    if (loader->localState == LOADSTATE_ENTER) {
+        M3Gbool status = M3G_TRUE;
+
+        loader->objectType = *data++;
+        loader->loadedObject = NULL;
+        loader->localState = LOADSTATE_EXIT;
+        loader->sectionBytesRequired = m3gLoadInt(data);
+        data += 4;
+        if (loader->sectionNum == 0 && loader->objectType != 0) {
+            m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
+            return LOADSTATE_ERROR;
+        }
+
+        switch(loader->objectType) {
+        case 0: /* Header Object */
+            status = m3gLoadHeader(loader);
+            break;
+        case 1: /* AnimationController */
+            status = m3gLoadAnimationController(loader);
+            break;
+        case 2: /* AnimationTrack */
+            status = m3gLoadAnimationTrack(loader);
+            break;
+        case 3: /* Appearance */
+            status = m3gLoadAppearance(loader);
+            break;
+        case 4: /* Background */
+            status = m3gLoadBackground(loader);
+            break;
+        case 5: /* Camera */
+            status = m3gLoadCamera(loader);
+            break;
+        case 6: /* CompositingMode */
+            status = m3gLoadCompositingMode(loader);
+            break;
+        case 7: /* Fog */
+            status = m3gLoadFog(loader);
+            break;
+        case 8: /* PolygonMode */
+            status = m3gLoadPolygonMode(loader);
+            break;
+        case 9: /* Group */
+            status = m3gLoadGroup(loader);
+            break;
+        case 10: /* Image2D */
+            status = m3gLoadImage(loader);
+            break;
+        case 11: /* TriangleStripArray */
+            status = m3gLoadTsa(loader);
+            break;
+        case 12: /* Light */
+            status = m3gLoadLight(loader);
+            break;
+        case 13: /* Material */
+            status = m3gLoadMaterial(loader);
+            break;
+        case 14: /* Mesh */
+            status = m3gLoadMesh(loader);
+            break;
+        case 15: /* MorphingMesh */
+            status = m3gLoadMorphingMesh(loader);
+            break;
+        case 16: /* SkinnedMesh */
+            status = m3gLoadSkinnedMesh(loader);
+            break;
+        case 17: /* Texture2D */
+            status = m3gLoadTexture(loader);
+            break;
+        case 18: /* Sprite */
+            status = m3gLoadSprite(loader);
+            break;
+        case 19: /* KeyframeSequence */
+            status = m3gLoadKeyframeSequence(loader);
+            break;
+        case 20: /* VertexArray */
+            status = m3gLoadVertexArray(loader);
+            break;
+        case 21: /* VertexBuffer */
+            status = m3gLoadVertexBuffer(loader);
+            break;
+        case 22: /* World */
+            status = m3gLoadWorld(loader);
+            break;
+        case 255: /* External Reference */
+            status = m3gLoadExternalReference(loader);
+            break;
+        default:  /* 23 ... 254 Reserved for use in future versions of the file format */
+            status = M3G_FALSE;
+            break;
+        }
+
+        /* Check if object loading caused an error */
+        if (m3gErrorRaised(M3G_INTERFACE(loader))) {
+            m3gDeleteObject(loader->loadedObject);
+            return LOADSTATE_ERROR;
+        }
+        if (!status || 
+            m3gGetSectionDataPtr(loader, 0) != data + loader->sectionBytesRequired) {
+            m3gDeleteObject(loader->loadedObject);
+            m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
+            return LOADSTATE_ERROR;
+        }
+        loader->sectionBytesRequired = 0;
+        
+        /* Add object to loaded objects array */
+        if (loader->loadedObject != NULL) {
+            if (m3gArrayAppend(&loader->refArray, loader->loadedObject, M3G_INTERFACE(loader)) == -1) {
+                /* OOM */
+                m3gDeleteObject(loader->loadedObject);
+                return LOADSTATE_ERROR;
+            }
+            m3gAddRef(loader->loadedObject);
+        }
+    }
+    else {
+        loader->sectionBytesRequired = M3G_MIN_OBJECT_SIZE;
+        loader->localState = LOADSTATE_ENTER;
+    }
+
+    return LOADSTATE_OBJECT;
+}
+
+/*!
+ * \internal
+ * \brief Handles branching to different subroutines upon re-entry
+ *
+ * When sufficient data is available in internal buffers, this
+ * function gets called and directs execution into the coroutine
+ * matching the current global state.
+ */
+static LoaderState m3gLoaderMain(Loader *loader)
+{
+    M3G_VALIDATE_OBJECT(loader);
+    M3G_ASSERT(loader->bytesRequired > 0);
+    M3G_ASSERT(loader->bytesRequired <= loader->stream.bytesAvailable);
+
+    switch (loader->state) {
+    case LOADSTATE_INITIAL:
+        loader->stream.totalBytes = 0;
+        loader->localState = LOADSTATE_ENTER;
+        loader->fileSize = 0x00ffffff;
+        loader->bytesRequired = sizeof(PNG_FILE_IDENTIFIER);
+        return LOADSTATE_IDENTIFIER;
+    case LOADSTATE_IDENTIFIER:
+        return m3gLoadIdentifier(loader);
+    case LOADSTATE_SECTION:
+        return m3gLoadSection(loader);
+    case LOADSTATE_OBJECT:
+        return m3gLoadObject(loader);
+    default:
+        loader->bytesRequired = 0;
+        return LOADSTATE_ERROR;
+    }
+}
+
+/*!
+ * \brief Deletes all unreferenced objects
+ */
+static void m3gCleanupLoader(M3GLoader loader)
+{
+    M3Gint i, j, n;
+    PointerArray *refs;
+    M3Gbool referenced;
+    M3GObject obj;    
+
+    refs = &loader->refArray;
+    n = m3gArraySize(refs);
+
+    /* All unreferenced objects will be deleted, as their ref count becomes 0 */
+    for (i = 0; i < n; ++i) {
+        obj = m3gGetLoadedPtr(refs, i, &referenced);
+        m3gDeleteRef(obj);
+    }
+    m3gClearArray(&loader->refArray);
+
+    n = m3gArraySize(&loader->userDataArray);
+    for (i = 0; i < n; ++i)
+    {
+        UserData *data = (UserData *)m3gGetArrayElement(&loader->userDataArray, i);
+        for (j = 0; j < data->numParams; ++j)
+            m3gFree(M3G_INTERFACE(loader), data->params[j]);
+        m3gFree(M3G_INTERFACE(loader), data->params);
+        m3gFree(M3G_INTERFACE(loader), data->paramLengths);
+        m3gFree(M3G_INTERFACE(loader), data->paramId);
+        m3gFree(M3G_INTERFACE(loader), data);
+    }
+    m3gClearArray(&loader->userDataArray);
+
+    m3gFree(M3G_INTERFACE(loader), loader->allocatedSectionData);
+    loader->allocatedSectionData = NULL;
+}
+
+/*!
+ * \internal
+ * \brief Resets the loader
+ */
+static void m3gResetLoader(Loader *loader)
+{
+    /* Reset loader state */
+    loader->state = LOADSTATE_INITIAL;
+    loader->bytesRequired = sizeof(PNG_FILE_IDENTIFIER);
+
+    m3gCleanupLoader(loader);
+    m3gResetBufferedData(&loader->stream);
+}
+
+/*----------------------------------------------------------------------
+ * Virtual function table
+ *--------------------------------------------------------------------*/
+
+static const ObjectVFTable m3gvf_Loader = {
+    NULL, /* ApplyAnimation */
+    NULL, /* IsCompatible */
+    NULL, /* UpdateProperty */
+    NULL, /* DoGetReferences */
+    NULL, /* FindID */
+    NULL, /* Duplicate */
+    m3gDestroyLoader
+};
+    
+
+/*----------------------------------------------------------------------
+ * Public interface
+ *--------------------------------------------------------------------*/
+
+/*!
+ * \brief Creates a new loader instance
+ */
+M3G_API M3GLoader m3gCreateLoader(M3GInterface m3g)
+{
+    Loader *loader;
+    M3G_VALIDATE_INTERFACE(m3g);
+
+    loader = m3gAllocZ(m3g, sizeof(*loader));
+    if (loader != NULL) {
+        m3gInitObject(&loader->object, m3g, M3G_CLASS_LOADER);
+        m3gInitArray(&loader->refArray);
+        m3gInitArray(&loader->userDataArray);
+        loader->bytesRequired = sizeof(PNG_FILE_IDENTIFIER);
+        loader->state = LOADSTATE_INITIAL;
+        loader->sectionNum = -1;
+    }
+    return loader;
+}
+
+/*!
+ * \brief Import a set of objects into the reference table of a loader
+ *
+ * This is intended for passing in external references, decoded in
+ * Java
+ */
+M3G_API void m3gImportObjects(M3GLoader loader, M3Gint n, M3GObject *refs)
+{
+    int i;
+    M3G_VALIDATE_OBJECT(loader);
+
+    if (loader->state == LOADSTATE_DONE)
+        m3gResetLoader(loader);
+    for (i = 0; i < n; ++i) {
+        /* For loop is interrupted in case of OOM */
+        if (m3gArrayAppend(&loader->refArray,
+                           refs[i],
+                           M3G_INTERFACE(loader)) == -1) {
+            break;
+        }
+        m3gAddRef(refs[i]);
+    }
+}
+
+/*!
+ * \brief Return the complete reference table for this loader instance
+ *
+ * The reference table will contain the handle of each object that has
+ * been loaded so far.
+ *
+ * \param loader loader instance
+ * \param buffer destination buffer, or NULL to just get
+ *               the number of unreferenced objects
+ * \return number of unreferenced objects
+ */
+M3G_API M3Gint m3gGetLoadedObjects(M3GLoader loader, M3GObject *buffer)
+{
+    PointerArray *refs;
+    int i, n, unref = 0;
+    M3Gbool referenced;
+    M3GObject obj, *dst = buffer;
+    M3G_VALIDATE_OBJECT(loader);
+
+    /* If error in decoding, reset and return 0 objects */
+    if (loader->state < LOADSTATE_INITIAL) {
+        return 0;
+    }
+
+    refs = &loader->refArray;
+    n = m3gArraySize(refs);
+
+    /* Scan unreferenced objects */
+    for (i = 0; i < n; ++i) {
+        obj = m3gGetLoadedPtr(refs, i, &referenced);
+        if (!referenced) {
+            unref++;
+            if (dst != NULL) {
+                *dst++ = obj;
+            }
+        }
+    }
+
+    return unref;
+}
+
+/*!
+ * \brief Submits data to the loader for processing
+ *
+ * Upon data input, the loader will either read more objects from the
+ * stream or buffer the data for processing later on. The return value
+ * will indicate how many bytes of data are required before the next
+ * data element can be loaded, but that data can still be submitted in
+ * smaller blocks.
+ *
+ * \param loader the loader instance
+ * \param bytes  the number of bytes in the data
+ * \param data   pointer to the data
+ *
+ * \return the number of bytes required to load the next data element,
+ * or zero to indicate that loading has finished
+ */
+
+#ifdef M3G_ENABLE_PROFILING
+static M3Gsizei m3gDecodeDataInternal(M3GLoader loader,
+                               M3Gsizei bytes,
+                               const M3Gubyte *data);
+
+M3G_API M3Gsizei m3gDecodeData(M3GLoader loader,
+                               M3Gsizei bytes,
+                               const M3Gubyte *data)
+{
+    M3Gsizei bytesReq;
+    M3G_BEGIN_PROFILE(M3G_INTERFACE(loader), M3G_PROFILE_LOADER_DECODE);
+    bytesReq = m3gDecodeDataInternal(loader, bytes, data);
+    M3G_END_PROFILE(M3G_INTERFACE(loader), M3G_PROFILE_LOADER_DECODE);
+    return bytesReq;
+}
+
+static M3Gsizei m3gDecodeDataInternal(M3GLoader loader,
+                               M3Gsizei bytes,
+                               const M3Gubyte *data)
+
+#else
+M3G_API M3Gsizei m3gDecodeData(M3GLoader loader,
+                               M3Gsizei bytes,
+                               const M3Gubyte *data)
+
+#endif
+{
+    m3gErrorHandler *errorHandler;
+    Interface *m3g = M3G_INTERFACE(loader);
+
+    M3G_VALIDATE_OBJECT(loader);
+    M3G_VALIDATE_INTERFACE(m3g);
+
+    /* Check for errors */
+    if (bytes <= 0 || data == NULL) {
+        m3gRaiseError(m3g, M3G_INVALID_VALUE);
+        return 0;
+    }
+
+    if (loader->state == LOADSTATE_DONE)
+        m3gResetLoader(loader);
+
+    /* Submit data, then load until we run out of data again or are
+     * finished */
+
+    if (!m3gBufferData(m3g, &loader->stream, bytes, data)) {
+        return 0;
+    }
+
+    /* Disable error handler */
+    errorHandler = m3gSetErrorHandler(m3g, NULL);
+
+    /* Continue loading if sufficient data has arrived */
+    while (loader->bytesRequired > 0
+           && loader->bytesRequired <= loader->stream.bytesAvailable) {
+        loader->state = m3gLoaderMain(loader);
+    }
+
+    /* Restore error handler */
+    m3gSetErrorHandler(m3g, errorHandler); 
+
+    /* Check if error was raised */
+    if (m3gErrorRaised(m3g) != M3G_NO_ERROR) {
+        /* Need to free all loaded objects */
+        m3gResetLoader(loader);
+
+        /* Raise again with original error handler in place */
+        if (m3gErrorRaised(m3g) == M3G_OUT_OF_MEMORY)
+            m3gRaiseError(m3g, M3G_OUT_OF_MEMORY);
+        else
+            m3gRaiseError(m3g, M3G_IO_ERROR);
+        return 0;
+    }
+
+    /* Return the number of bytes we need for loading to proceed
+     * further; clamp to zero in case we're exiting due to an error,
+     * or just have been fed excess data */
+    {
+        M3Gsizei bytesReq =
+            loader->bytesRequired - loader->stream.bytesAvailable;
+
+        /* Check if whole file is done */
+        if (loader->stream.totalBytes >= loader->fileSize) {
+            loader->state = LOADSTATE_DONE;
+            bytesReq = 0;
+        }
+
+        return (bytesReq >= 0) ? bytesReq : 0;
+    }
+}
+
+/*!
+ * \brief Return all loaded objects with user parameters
+ *
+ * \param loader  the loader instance
+ * \param objects an array for objects with user parameters,
+ *                or null to return the number of objects
+ *
+ * \return Number of objects with user parameters
+ */
+M3G_API M3Gint m3gGetObjectsWithUserParameters(M3GLoader loader, M3GObject *objects) {
+    const Loader *ldr = (const Loader *) loader;
+    M3G_VALIDATE_OBJECT(ldr);
+    {
+        M3Gint i, n;
+        n = m3gArraySize(&ldr->userDataArray);
+    
+        if (objects != NULL)
+            for (i = 0; i < n; ++i)
+            {
+                const UserData *data = (const UserData *)m3gGetArrayElement(&ldr->userDataArray, i);
+                objects[i] = data->object;
+            }
+        return n;
+    }
+}
+
+/*!
+ * \brief Return the number of user parameters loaded for an object
+ */
+M3G_API M3Gint m3gGetNumUserParameters(M3GLoader loader, M3Gint object)
+{
+    const Loader *ldr = (const Loader *) loader;
+    M3G_VALIDATE_OBJECT(ldr);
+    {
+        const UserData *data = (const UserData *)m3gGetArrayElement(&ldr->userDataArray, object);
+        return (data != NULL) ? data->numParams : 0;
+    }
+}
+
+/*!
+ * \brief Set constraints for loading.
+ * \param triConstraint maximum triangle count
+ */
+M3G_API void m3gSetConstraints(M3GLoader loader, M3Gint triConstraint)
+{
+    M3G_VALIDATE_OBJECT(loader);
+    loader->triConstraint = triConstraint;
+}
+
+/*!
+ * \brief Return the given user parameter for an object
+ *
+ * \param loader the loader instance
+ * \param object the object to query
+ * \param index  index of the string to query
+ * \param buffer buffer to copy the data into,
+ *               or NULL to just query the length
+ *
+ * \return id of the parameter, or the length of the string if data was NULL
+ */
+M3G_API M3Gsizei m3gGetUserParameter(M3GLoader loader,
+                                     M3Gint object,
+                                     M3Gint index,
+                                     M3Gbyte *buffer)
+{
+    const Loader *ldr = (const Loader *) loader;
+    M3G_VALIDATE_OBJECT(ldr);
+    {
+        const UserData *data = (const UserData *)m3gGetArrayElement(&ldr->userDataArray, object);
+        if (data != NULL && m3gInRange(index, 0, data->numParams - 1)) {
+            const char *src = (const char *)data->params[index];
+            M3Gsizei len = data->paramLengths[index];
+            if (buffer != NULL) {
+                m3gCopy(buffer, src, len);
+                return data->paramId[index];
+            }
+            else
+                return len;
+        }
+        else
+            return 0;
+    }
+}
+
+#undef ANY_NODE_CLASS
+