public static ASEFile Parse(byte[] bytes)
        {
            var stream = new MemoryStream(bytes);

            using (var reader = new BinaryReader(stream)) {
                var file = new ASEFile();

                reader.ReadDWord(); // File size
                _CheckMagicNumber(reader.ReadWord(), 0xA5E0);

                var frameCount = reader.ReadWord();

                file.width  = reader.ReadWord();
                file.height = reader.ReadWord();

                var colorDepth = reader.ReadWord();

                if (colorDepth != 32)
                {
                    _Error("Non RGBA color mode isn't supported yet");
                }

                reader.ReadDWord(); // Flags
                reader.ReadWord();  // Deprecated speed
                _CheckMagicNumber(reader.ReadDWord(), 0);
                _CheckMagicNumber(reader.ReadDWord(), 0);

                reader.ReadBytes(4);
                reader.ReadWord();
                reader.ReadBytes(2);
                reader.ReadBytes(92);
                int readLayerIndex = 0;

                UserDataAcceptor lastUserdataAcceptor = null;

                for (int i = 0; i < frameCount; ++i)
                {
                    var frame = new Frame();
                    frame.frameID = i;

                    reader.ReadDWord(); // frameBytes
                    _CheckMagicNumber(reader.ReadWord(), 0xF1FA);

                    var chunkCount = reader.ReadWord();

                    frame.duration = reader.ReadWord();

                    reader.ReadBytes(6);

                    for (int j = 0; j < chunkCount; ++j)
                    {
                        var chunkBytes = reader.ReadDWord(); // 4
                        var chunkType  = reader.ReadWord();  // 2

                        switch (chunkType)
                        {
                        case CHUNK_LAYER: {
                            var layer = new Layer();
                            var flags = reader.ReadWord();

                            layer.visible = (flags & 0x1) != 0;

                            var layerType = reader.ReadWord();
                            reader.ReadWord(); // childLevel

                            reader.ReadWord();
                            reader.ReadWord();

                            layer.blendMode = (BlendMode)reader.ReadWord();
                            layer.opacity   = reader.ReadByte() / 255.0f;
                            reader.ReadBytes(3);

                            layer.layerName = reader.ReadUTF8();

                            if (layerType == 0 && layer.visible && !layer.layerName.StartsWith("//"))
                            {
                                layer.index = readLayerIndex;
                                layer.type  = layer.layerName.StartsWith("@") ? LayerType.Meta : LayerType.Content;
                                if (layer.type == LayerType.Meta)
                                {
                                    MetaLayerParser.Parse(layer);
                                }

                                file.layers.Add(layer);
                            }

                            ++readLayerIndex;

                            lastUserdataAcceptor = layer;
                        } break;

                        case CHUNK_CEL: {
                            var cel = new Cel();

                            cel.layerIndex = reader.ReadWord();          // 2
                            cel.x          = reader.ReadInt16();         // 2
                            cel.y          = reader.ReadInt16();         // 2
                            cel.opacity    = reader.ReadByte() / 255.0f; // 1
                            cel.type       = (CelType)reader.ReadWord(); // 2
                            reader.ReadBytes(7);                         // 7

                            switch (cel.type)
                            {
                            case CelType.Raw: {
                                cel.width       = reader.ReadWord(); // 2
                                cel.height      = reader.ReadWord(); // 2
                                cel.colorBuffer = ToColorBufferRGBA(reader.ReadBytes(chunkBytes - 6 - 16 - 4));

                                _Assert(cel.width * cel.height == cel.colorBuffer.Length, "Color buffer size incorrect");
                            } break;

                            case CelType.Linked: {
                                cel.linkedCel = reader.ReadWord();
                            } break;

                            case CelType.Compressed: {
                                cel.width       = reader.ReadWord();
                                cel.height      = reader.ReadWord();
                                cel.colorBuffer = ToColorBufferRGBA(
                                    reader.ReadCompressedBytes(chunkBytes - 6 - 16 - 4));
                                _Assert(cel.width * cel.height == cel.colorBuffer.Length, "Color buffer size incorrect");
                            } break;
                            }

                            if (file.FindLayer(cel.layerIndex) != null)
                            {
                                frame.cels.Add(cel.layerIndex, cel);
                            }

                            lastUserdataAcceptor = cel;
                        } break;

                        case CHUNK_FRAME_TAGS: {
                            var count = reader.ReadWord();
                            reader.ReadBytes(8);

                            for (int c = 0; c < count; ++c)
                            {
                                var frameTag = new FrameTag();

                                frameTag.from = reader.ReadWord();
                                frameTag.to   = reader.ReadWord();
                                reader.ReadByte();
                                reader.ReadBytes(8);
                                reader.ReadBytes(3);
                                reader.ReadByte();

                                frameTag.name = reader.ReadUTF8();

                                if (frameTag.name.StartsWith("//")) // Commented tags are ignored
                                {
                                    continue;
                                }

                                var originalName = frameTag.name;

                                var tagIdx      = frameTag.name.IndexOf('#');
                                var nameInvalid = false;
                                if (tagIdx != -1)
                                {
                                    frameTag.name = frameTag.name.Substring(0, tagIdx).Trim();
                                    var possibleProperties = originalName.Substring(tagIdx).Split(' ');
                                    foreach (var possibleProperty in possibleProperties)
                                    {
                                        if (possibleProperty.Length > 1 && possibleProperty[0] == '#')
                                        {
                                            frameTag.properties.Add(possibleProperty.Substring(1));
                                        }
                                        else
                                        {
                                            nameInvalid = true;
                                        }
                                    }
                                }

                                if (nameInvalid)
                                {
                                    Debug.LogWarning("Invalid name: " + originalName);
                                }

                                file.frameTags.Add(frameTag);
                            }
                        } break;

                        case CHUNK_USERDATA: {
                            var flags    = reader.ReadDWord();
                            var hasText  = (flags & 0x01) != 0;
                            var hasColor = (flags & 0x02) != 0;

                            if (hasText)
                            {
                                lastUserdataAcceptor.userData = reader.ReadUTF8();
                            }

                            if (hasColor)
                            {
                                reader.ReadBytes(4);
                            }
                        } break;

                        default: {
                            reader.ReadBytes(chunkBytes - 6);
                        } break;
                        }
                    }

                    file.frames.Add(frame);
                }

                // Post process: calculate pixel alpha
                for (int f = 0; f < file.frames.Count; ++f)
                {
                    var frame = file.frames[f];
                    foreach (var cel in frame.cels.Values)
                    {
                        if (cel.type != CelType.Linked)
                        {
                            for (int i = 0; i < cel.colorBuffer.Length; ++i)
                            {
                                cel.colorBuffer[i].a *= cel.opacity * file.FindLayer(cel.layerIndex).opacity;
                            }
                        }
                    }
                }

                // Post process: eliminate reference cels
                for (int f = 0; f < file.frames.Count; ++f)
                {
                    var frame = file.frames[f];
                    foreach (var pair in frame.cels)
                    {
                        var layerID = pair.Key;
                        var cel     = pair.Value;
                        if (cel.type == CelType.Linked)
                        {
                            cel.type = CelType.Raw;

                            var src = file.frames[cel.linkedCel].cels[layerID];

                            cel.x           = src.x;
                            cel.y           = src.y;
                            cel.width       = src.width;
                            cel.height      = src.height;
                            cel.colorBuffer = src.colorBuffer;
                            cel.opacity     = src.opacity;
                            cel.userData    = src.userData;
                        }
                    }
                }

                return(file);
            }
        }