private void Parse(Stream stream) { var reader = new BinaryReader(stream); // wrote these to match the documentation names so it's easier (for me, anyway) to parse byte BYTE() => reader.ReadByte(); ushort WORD() => reader.ReadUInt16(); short SHORT() => reader.ReadInt16(); uint DWORD() => reader.ReadUInt32(); long LONG() => reader.ReadInt32(); string STRING() => Encoding.UTF8.GetString(BYTES(WORD())); byte[] BYTES(int number) => reader.ReadBytes(number); void SEEK(int number) => reader.BaseStream.Position += number; // Header { // file size DWORD(); // Magic number (0xA5E0) var magic = WORD(); if (magic != 0xA5E0) { throw new Exception("File is not in .ase format"); } // Frames / Width / Height / Color Mode frameCount = WORD(); Width = WORD(); Height = WORD(); Mode = (Modes)(WORD() / 8); // Other Info, Ignored DWORD(); // Flags WORD(); // Speed (deprecated) DWORD(); // Set be 0 DWORD(); // Set be 0 BYTE(); // Palette entry SEEK(3); // Ignore these bytes WORD(); // Number of colors (0 means 256 for old sprites) BYTE(); // Pixel width BYTE(); // Pixel height SEEK(92); // For Future } // temporary variables var temp = new byte[Width * Height * (int)Mode]; var palette = new Color[256]; IUserData?last = null; // Frames for (int i = 0; i < frameCount; i++) { var frame = new Frame(this); Frames.Add(frame); long frameStart, frameEnd; int chunkCount; // frame header { frameStart = reader.BaseStream.Position; frameEnd = frameStart + DWORD(); WORD(); // Magic number (always 0xF1FA) chunkCount = WORD(); // Number of "chunks" in this frame frame.Duration = WORD(); // Frame duration (in milliseconds) SEEK(6); // For future (set to zero) } // chunks for (int j = 0; j < chunkCount; j++) { long chunkStart, chunkEnd; Chunks chunkType; // chunk header { chunkStart = reader.BaseStream.Position; chunkEnd = chunkStart + DWORD(); chunkType = (Chunks)WORD(); } // LAYER CHUNK if (chunkType == Chunks.Layer) { // create layer var layer = new Layer(); // get layer data layer.Flag = (Layer.Flags)WORD(); layer.Type = (Layer.Types)WORD(); layer.ChildLevel = WORD(); WORD(); // width (unused) WORD(); // height (unused) layer.BlendMode = WORD(); layer.Alpha = (BYTE() / 255f); SEEK(3); // for future layer.Name = STRING(); last = layer; Layers.Add(layer); } // CEL CHUNK else if (chunkType == Chunks.Cel) { var layer = Layers[WORD()]; var x = SHORT(); var y = SHORT(); var alpha = BYTE() / 255f; var celType = WORD(); var width = 0; var height = 0; Color[]? pixels = null; Cel?link = null; SEEK(7); // RAW or DEFLATE if (celType == 0 || celType == 2) { width = WORD(); height = WORD(); var count = width * height * (int)Mode; if (count > temp.Length) { temp = new byte[count]; } // RAW if (celType == 0) { reader.Read(temp, 0, width * height * (int)Mode); } // DEFLATE else { SEEK(2); using var deflate = new DeflateStream(reader.BaseStream, CompressionMode.Decompress, true); deflate.Read(temp, 0, count); } // get pixel data pixels = new Color[width * height]; BytesToPixels(temp, pixels, Mode, palette); } // REFERENCE else if (celType == 1) { var linkFrame = Frames[WORD()]; var linkCel = linkFrame.Cels[frame.Cels.Count]; width = linkCel.Width; height = linkCel.Height; pixels = linkCel.Pixels; link = linkCel; } else { throw new NotImplementedException(); } var cel = new Cel(layer, pixels) { X = x, Y = y, Width = width, Height = height, Alpha = alpha, Link = link }; // draw to frame if visible if (cel.Layer.Visible) { CelToFrame(frame, cel); } last = cel; frame.Cels.Add(cel); } // PALETTE CHUNK else if (chunkType == Chunks.Palette) { var size = DWORD(); var start = DWORD(); var end = DWORD(); SEEK(8); // for future for (int p = 0; p < (end - start) + 1; p++) { var hasName = WORD(); palette[start + p] = new Color(BYTE(), BYTE(), BYTE(), BYTE()).Premultiply(); if (Calc.IsBitSet(hasName, 0)) { STRING(); } } } // USERDATA else if (chunkType == Chunks.UserData) { if (last != null) { var flags = (int)DWORD(); // has text if (Calc.IsBitSet(flags, 0)) { last.UserDataText = STRING(); } // has color if (Calc.IsBitSet(flags, 1)) { last.UserDataColor = new Color(BYTE(), BYTE(), BYTE(), BYTE()).Premultiply(); } } } // TAG else if (chunkType == Chunks.FrameTags) { var count = WORD(); SEEK(8); for (int t = 0; t < count; t++) { var tag = new Tag(); tag.From = WORD(); tag.To = WORD(); tag.LoopDirection = (Tag.LoopDirections)BYTE(); SEEK(8); tag.Color = new Color(BYTE(), BYTE(), BYTE(), (byte)255).Premultiply(); SEEK(1); tag.Name = STRING(); Tags.Add(tag); } } // SLICE else if (chunkType == Chunks.Slice) { var count = DWORD(); var flags = (int)DWORD(); DWORD(); // reserved var name = STRING(); for (int s = 0; s < count; s++) { var slice = new Slice { Name = name, Frame = (int)DWORD(), OriginX = (int)LONG(), OriginY = (int)LONG(), Width = (int)DWORD(), Height = (int)DWORD() }; // 9 slice (ignored atm) if (Calc.IsBitSet(flags, 0)) { slice.NineSlice = new RectInt( (int)LONG(), (int)LONG(), (int)DWORD(), (int)DWORD()); } // pivot point if (Calc.IsBitSet(flags, 1)) { slice.Pivot = new Point2((int)DWORD(), (int)DWORD()); } last = slice; Slices.Add(slice); } } reader.BaseStream.Position = chunkEnd; } reader.BaseStream.Position = frameEnd; } }