Example #1
0
        private void WriteSprite(Sprite s, ListSet <Timeline> unboundClasses)
        {
            foreach (ICharacterReference cr in s.CharacterRefs)
            {
                this.WriteCharacter(cr.Character, unboundClasses);
            }

            if (s.HasClass && !(s.Class is AdobeClass) && !unboundClasses.Contains(s))
            {
                unboundClasses.Add(s);
            }

            int         id        = this.characterMarshal.GetIDFor(s);
            WriteBuffer tagWriter = this.OpenTag(Tag.DefineSprite, s.ToString() + ";id=" + id.ToString());

            tagWriter.WriteUI16((uint)id);
            tagWriter.WriteUI16(s.FrameCount);
#if DEBUG
            this.LogMessage("char id=" + id);
#endif

            foreach (Frame f in s.Frames)
            {
                if (f.HasLabel)
                {
#if DEBUG
                    this.LogMessage("frame label=" + f.Label);
#endif
                    WriteBuffer labelWriter = this.OpenTag(Tag.FrameLabel);
                    labelWriter.WriteString(f.Label);
                    this.CloseTag();
                }

                foreach (IDisplayListItem dli in f.DisplayList)
                {
                    switch (dli.Type)
                    {
                    case DisplayListItemType.PlaceObjectX:
                        this.WritePlaceObjectTag((PlaceObject)dli);
                        break;

                    case DisplayListItemType.RemoveObjectX:
                        this.WriteRemoveObjectTag((RemoveObject)dli);
                        break;

                    default:
                        /* ISSUE 73 */
                        throw new SWFModellerException(
                                  SWFModellerError.UnimplementedFeature,
                                  "Unsupported tag in SWF sprite writer: " + dli.GetType().ToString());
                    }
                }

                this.WriteBodylessTag(Tag.ShowFrame);
            }

            this.WriteBodylessTag(Tag.End, id.ToString());

            this.CloseTag(); /* DefineSprite */
        }
Example #2
0
        private void WriteRemoveObjectTag(RemoveObject ro)
        {
            WriteBuffer removeBuf = this.OpenTag(ro.HasCharacter ? Tag.RemoveObject : Tag.RemoveObject2);

            if (ro.HasCharacter)
            {
                int cid = this.characterMarshal.GetIDFor(ro.Character);
                removeBuf.WriteUI16((uint)cid);
            }

            removeBuf.WriteUI16((uint)ro.LayerIndex);
            this.CloseTag();
        }
Example #3
0
        private WriteBuffer CloseTag()
        {
            if (this.writers.Count <= 1)
            {
                throw new SWFModellerException(
                          SWFModellerError.Internal,
                          "Call to CloseTag when no tag was open.");
            }

            WriteBuffer tagWriter = this.writers.Peek();

            tagWriter.Align8();
            byte[] tagData = tagWriter.Data;
            int    tagCode = (int)tagWriter.Tag;

#if DEBUG
            this.LogMessage("Body length of " + tagWriter.Tag.ToString() + " " + tagData.Length + " bytes on " + tagWriter.LogData, true);
#endif
            this.writers.Pop();

            tagWriter = this.writers.Peek();
            tagWriter.Align8();

            if (tagData.Length >= 63)
            {
                /* Long record header */
                int hdr = (tagCode << 6) | 0x3f;
                tagWriter.WriteUI16((uint)hdr);
                tagWriter.WriteSI32(tagData.Length);
            }
            else
            {
                /* Short record header */
                int hdr = (tagCode << 6) | tagData.Length;
                tagWriter.WriteUI16((uint)hdr);
            }

            tagWriter.Write(tagData, 0, tagData.Length);

            return(tagWriter);
        }
Example #4
0
        private void WriteImage(IImage image)
        {
            if (characterMarshal.HasMarshalled(image))
            {
                /* Been there, done that. */
                return;
            }

            ImageBlob blob = image as ImageBlob;

            if (blob == null)
            {
                throw new SWFModellerException(SWFModellerError.Internal, "Can't write " + image.ToString());
            }

            switch (blob.DataFormat)
            {
            case Tag.DefineBitsJPEG2:
            case Tag.DefineBitsLossless:
            case Tag.DefineBitsLossless2:
                WriteBuffer blobBuffer = OpenTag(blob.DataFormat);
                blobBuffer.WriteUI16((uint)characterMarshal.GetIDFor(image));
                blobBuffer.WriteBytes(blob.FormattedBytes);
                CloseTag();
                break;

            case Tag.DefineBits:
                if (blob.JPEGTable != null)
                {
                    if (writtenJPEGTable != null && writtenJPEGTable != blob.JPEGTable)
                    {
                        /* ISSUE 16 */
                        throw new SWFModellerException(SWFModellerError.UnimplementedFeature,
                                                       "Can't process multiple JPEG encoding tables yet.");
                    }

                    WriteBuffer tables = OpenTag(Tag.JPEGTables);
                    tables.WriteBytes(blob.JPEGTable.TableData);
                    CloseTag();

                    writtenJPEGTable = blob.JPEGTable;
                }

                WriteBuffer bits = OpenTag(Tag.DefineBits);
                bits.WriteUI16((uint)characterMarshal.GetIDFor(image));
                bits.WriteBytes(blob.FormattedBytes);
                CloseTag();
                break;

            default:
                throw new SWFModellerException(SWFModellerError.Internal, "Can't write image format " + blob.DataFormat.ToString());;
            }
        }
Example #5
0
        private void BindClasses(ListSet <Timeline> unboundClasses)
        {
            if (unboundClasses.Count > 0)
            {
                WriteBuffer symbolBuf = this.OpenTag(Tag.SymbolClass);

                symbolBuf.WriteUI16((uint)unboundClasses.Count);

                foreach (Timeline t in unboundClasses)
                {
                    symbolBuf.WriteUI16((uint)this.characterMarshal.GetIDFor((ICharacter)t));
                    symbolBuf.WriteString(t.Class.QualifiedName);
                    this.LogMessage(
                        "ID:" +
                        (uint)this.characterMarshal.GetIDFor((ICharacter)t) +
                        " => " +
                        t.Class.QualifiedName);
                }
                this.CloseTag();

                unboundClasses.Clear();
            }
        }
Example #6
0
        /// <summary>
        /// Note that if you're fixing this method, it shares functionality with
        /// CloseTag, which may also need the same fix. Whatever it is you're fixing.
        /// </summary>
        private void WriteBodylessTag(Tag tag, string log = null)
        {
#if DEBUG
            this.LogTag(tag, log);
#endif
            WriteBuffer tagWriter = this.writers.Peek();
            int         tagCode   = (int)tag;

#if DEBUG
            this.LogMessage("Bodyless tag " + tag.ToString(), true);
#endif

            /* Short record header */
            int hdr = (tagCode << 6);
            tagWriter.WriteUI16((uint)hdr);
        }
Example #7
0
        private void WriteFont(SWFFont font, int fid)
        {
            WriteBuffer fontTag = this.OpenTag(Tag.DefineFont3, font.Name + "; id=" + fid);

            char[] codes = font.CodePoints;

            /* Tag.DefineFont3 */
            {
                fontTag.WriteUI16((uint)fid);

                fontTag.WriteBit(font.HasLayout);

                fontTag.WriteBit(false); /* ISSUE 50: ShiftJIS support */
                fontTag.WriteBit(font.IsSmall);
                fontTag.WriteBit(false); /* ISSUE 51: ANSI support, though I think this might never be false. */

                fontTag.WriteBit(true);  /* ISSUE 52: We always write wide offsets. This is because we're too lazy to measure our table. */
                fontTag.WriteBit(true);  /* Spec says must be true. */

                fontTag.WriteBit(font.IsItalic);
                fontTag.WriteBit(font.IsBold);

                fontTag.WriteUI8((uint)font.LanguageCode);

                fontTag.WriteString(font.Name, true);

                fontTag.WriteUI16((uint)font.GlyphCount);

                byte[][] shapeData       = new byte[font.GlyphCount][];
                int      totalShapeBytes = 0;
                for (int i = 0; i < font.GlyphCount; i++)
                {
                    Tag format;
                    shapeData[i] = ShapeWriter.ShapeToBytes(font.GetGlyphShape(codes[i]), out format);

                    if (format != Tag.DefineFont3)
                    {
                        throw new SWFModellerException(SWFModellerError.Internal, "Can't write non-font shapes as glyphs");
                    }

                    totalShapeBytes += shapeData[i].Length;
                }

                int startOffset = font.GlyphCount * 4 + 4; /* 4 bytes per offset (wide offsets) + 4 for the code table offset */
                int nextOffset  = startOffset;
                foreach (byte[] shapeBytes in shapeData)
                {
                    fontTag.WriteUI32((uint)nextOffset);
                    nextOffset += shapeBytes.Length;
                }

                fontTag.WriteUI32((uint)(startOffset + totalShapeBytes));

                foreach (byte[] shapeBytes in shapeData)
                {
                    fontTag.WriteBytes(shapeBytes);
                }

                foreach (char code in codes)
                {
                    fontTag.WriteUI16((uint)code);
                }

                if (font.HasLayout)
                {
                    fontTag.WriteSI16(font.Ascent.Value);
                    fontTag.WriteSI16(font.Descent.Value);
                    fontTag.WriteSI16(font.Leading.Value);

                    Rect[] bounds    = new Rect[font.GlyphCount];
                    int    boundsPos = 0;
                    foreach (char c in codes)
                    {
                        GlyphLayout gl = font.GetLayout(c);
                        fontTag.WriteSI16(gl.Advance);
                        bounds[boundsPos++] = gl.Bounds;
                    }

                    foreach (Rect bound in bounds)
                    {
                        fontTag.WriteRect(bound);
                        fontTag.Align8();
                    }

                    fontTag.WriteUI16((uint)font.KerningTable.Length);
                    foreach (KerningPair kern in font.KerningTable)
                    {
                        fontTag.WriteUI16(kern.LeftChar);
                        fontTag.WriteUI16(kern.RightChar);
                        fontTag.WriteSI16(kern.Adjustment);
                    }
                }
            }

            this.CloseTag();

            if (font.HasPixelAlignment)
            {
                WriteBuffer zonesTag = this.OpenTag(Tag.DefineFontAlignZones, font.Name + "; id=" + fid);

                zonesTag.WriteUI16((uint)fid);

                if (font.ThicknessHint == null)
                {
                    throw new SWFModellerException(SWFModellerError.Internal, "Can't have pixel aligmnent without a font thickness hint.");
                }

                zonesTag.WriteUBits((uint)font.ThicknessHint, 2);
                zonesTag.WriteUBits(0, 6); /* Reserved */

                foreach (char c in codes)
                {
                    PixelAlignment pa = font.GetPixelAligment(c);

                    if (pa.ZoneInfo.Length != 2)
                    {
                        throw new SWFModellerException(SWFModellerError.Internal, "Pixel aligment should always have 2 zones.");
                    }

                    zonesTag.WriteUI8((uint)pa.ZoneInfo.Length);

                    foreach (PixelAlignment.ZoneData zi in pa.ZoneInfo)
                    {
                        /* These int values are just unparsed 16-bit floats. */
                        zonesTag.WriteUI16((uint)zi.AlignmentCoord);
                        zonesTag.WriteUI16((uint)zi.Range);
                    }

                    zonesTag.WriteUBits(0, 6); /* Reserved */
                    zonesTag.WriteBit(pa.HasY);
                    zonesTag.WriteBit(pa.HasX);
                }

                this.CloseTag();
            }

            if (font.HasExtraNameInfo)
            {
                WriteBuffer nameTag = this.OpenTag(Tag.DefineFontName, font.FullName + "; id=" + fid);

                nameTag.WriteUI16((uint)fid);
                nameTag.WriteString(font.FullName);
                nameTag.WriteString(font.Copyright);

                this.CloseTag();
            }
        }
Example #8
0
        /// <param name="fontID">Pass -1 if this has no font.</param>
        private void WriteEditText(EditText text, int fontID)
        {
            int cid = characterMarshal.GetIDFor(text);

            WriteBuffer textTag = this.OpenTag(Tag.DefineEditText, "; id=" + cid);

            /* Tag.DefineEditText */
            {
                textTag.WriteUI16((uint)cid);

                textTag.WriteRect(text.Bounds);
                textTag.Align8();

                textTag.WriteBit(text.HasText);
                textTag.WriteBit(text.WordWrapEnabled);
                textTag.WriteBit(text.IsMultiline);
                textTag.WriteBit(text.IsPassword);
                textTag.WriteBit(text.IsReadOnly);
                textTag.WriteBit(text.HasTextColor);
                textTag.WriteBit(text.HasMaxLength);
                textTag.WriteBit(text.HasFont);
                textTag.WriteBit(text.HasFontClass);
                textTag.WriteBit(text.IsAutoSized);
                textTag.WriteBit(text.HasLayout);
                textTag.WriteBit(text.IsNonSelectable);
                textTag.WriteBit(text.HasBorder);
                textTag.WriteBit(text.IsStatic);
                textTag.WriteBit(text.IsHTML);
                textTag.WriteBit(text.UseOutlines);

                if (text.HasFont)
                {
                    textTag.WriteUI16((uint)fontID);
                }

                if (text.HasFontClass)
                {
                    /* ISSUE 14 */
                    throw new SWFModellerException(SWFModellerError.UnimplementedFeature, "Font classes can't be written.");
                }

                if (text.HasFont)
                {
                    textTag.WriteUI16((uint)text.FontHeight);
                }

                if (text.HasTextColor)
                {
                    textTag.WriteRGBA(text.Color.ToArgb());
                }

                if (text.HasMaxLength)
                {
                    textTag.WriteUI16((uint)text.MaxLength.Value);
                }

                if (text.HasLayout)
                {
                    EditText.Layout layout = text.LayoutInfo;

                    textTag.WriteUI8((uint)layout.Align);
                    textTag.WriteUI16((uint)layout.LeftMargin);
                    textTag.WriteUI16((uint)layout.RightMargin);
                    textTag.WriteUI16((uint)layout.Indent);
                    textTag.WriteSI16(layout.Leading);
                }

                textTag.WriteString(text.VarName);

                if (text.HasText)
                {
                    textTag.WriteString(text.Text);
                }
            }

            CloseTag();
        }
Example #9
0
        private void WriteStaticText(StaticText text)
        {
            int cid = characterMarshal.GetIDFor(text);

            bool hasAlpha = text.HasAlpha;

            WriteBuffer textTag = this.OpenTag(hasAlpha ? Tag.DefineText2 : Tag.DefineText, "; id=" + cid);

            /* Tag.DefineText(2) */
            {
                textTag.WriteUI16((uint)cid);

                textTag.WriteRect(text.Bounds);
                textTag.Align8();

                textTag.WriteMatrix(text.Position);
                textTag.Align8();

                /* ISSUE 49: We're lazy here. We max out the bits for text and advances coz we can't
                 * yet calculate them. Fix this attrocity. */

                int glyphBits   = 16;
                int advanceBits = 16;

                textTag.WriteUI8((uint)glyphBits);
                textTag.WriteUI8((uint)advanceBits);

                foreach (TextRecord tr in text.Records)
                {
                    Dictionary <char, int> glyphIDX = null;

                    uint flags = 0x80;

                    if (tr.HasFont)
                    {
                        flags |= 0x08;
                    }

                    if (tr.HasColour)
                    {
                        flags |= 0x04;
                    }

                    if (tr.HasYOffset)
                    {
                        flags |= 0x02;
                    }

                    if (tr.HasXOffset)
                    {
                        flags |= 0x01;
                    }

                    textTag.WriteUI8(flags);

                    if (tr.HasFont)
                    {
                        textTag.WriteUI16((uint)this.characterMarshal.GetExistingIDFor(tr.Font));
                    }

                    if (tr.HasColour)
                    {
                        if (hasAlpha)
                        {
                            textTag.WriteRGBA(tr.Colour.ToArgb());
                        }
                        else
                        {
                            textTag.WriteRGB(tr.Colour.ToArgb());
                        }
                    }

                    if (tr.HasXOffset)
                    {
                        textTag.WriteSI16(tr.XOffset);
                    }

                    if (tr.HasYOffset)
                    {
                        textTag.WriteSI16(tr.YOffset);
                    }

                    if (tr.HasFont)
                    {
                        textTag.WriteUI16((uint)tr.FontHeight);

                        glyphIDX = tr.Font.IndexMap;
                    }

                    char[] chars = tr.Text.ToCharArray();
                    if (chars.Length > 255)
                    {
                        throw new SWFModellerException(SWFModellerError.Internal, "String too long. This should be split across text records.");
                    }

                    textTag.WriteUI8((uint)chars.Length);
                    for (int i = 0; i < tr.Advances.Length; i++)
                    {
                        textTag.WriteUBits((uint)glyphIDX[chars[i]], glyphBits);
                        textTag.WriteSBits(tr.Advances[i], advanceBits);
                    }

                    textTag.Align8();
                }

                textTag.WriteUI8(0); /* End record */
            } /* End of tag code. */

            this.CloseTag();
        }
Example #10
0
        private void WriteCharacter(ICharacter ch, ListSet <Timeline> unboundClasses)
        {
            int cid;

            if (ch == null)
            {
                return;
            }

            if (this.characterMarshal.HasMarshalled(ch))
            {
                return;
            }

            int fontID = -1;

            if (ch is IFontUserProcessor)
            {
                IFontUserProcessor fup = (IFontUserProcessor)ch;
                fup.FontUserProc(delegate(IFontUser fu)
                {
                    if (fu.HasFont && !characterMarshal.HasMarshalled(fu.Font))
                    {
                        fontID = characterMarshal.GetIDFor(fu.Font);
                        this.WriteFont(fu.Font, fontID);
                    }
                    else
                    {
                        fontID = characterMarshal.GetExistingIDFor(fu.Font);
                    }
                });
            }

            if (ch is IShape)
            {
                IImage[] images = ((IShape)ch).GetImages();

                if (images != null)
                {
                    foreach (IImage image in images)
                    {
                        this.WriteImage(image);
                    }
                }

                Tag    format;
                byte[] shapeBytes = ShapeWriter.ShapeToBytes((IShape)ch, out format);

                WriteBuffer shapeTag = this.OpenTag(format);
                cid = this.characterMarshal.GetIDFor(ch);
                shapeTag.WriteUI16((uint)cid);
                shapeTag.WriteBytes(shapeBytes);
#if DEBUG
                this.LogMessage("char id=" + cid);
#endif
                this.CloseTag();
            }
            else if (ch is Sprite)
            {
                this.WriteSprite((Sprite)ch, unboundClasses);
            }
            else if (ch is EditText)
            {
                this.WriteEditText((EditText)ch, fontID);
            }
            else if (ch is StaticText)
            {
                this.WriteStaticText((StaticText)ch);
            }
            else
            {
                /* ISSUE 73 */
                throw new SWFModellerException(
                          SWFModellerError.UnimplementedFeature,
                          "Character of type " + ch.GetType().ToString() + " not currently supported in writer");
            }

            if (ch is Timeline)
            {
                Timeline tl = (Timeline)ch;
                if (tl.HasClass && !(tl.Class is AdobeClass) && !unboundClasses.Contains(tl))
                {
                    unboundClasses.Add(tl);
                }
            }
        }
Example #11
0
        private void WritePlaceObjectTag(PlaceObject po)
        {
            Tag         placeTag  = this.TagForPlaceObject(po);
            WriteBuffer tagWriter = this.OpenTag(placeTag);
            int         cid;

            switch (placeTag)
            {
            case Tag.PlaceObject:
                if (!po.HasCharacter)
                {
                    throw new SWFModellerException(
                              SWFModellerError.Internal,
                              "A PlaceObject display list item must have a character unless it is a move instruction.");
                }
#if DEBUG
                if (!this.characterMarshal.HasMarshalled(po.Character))
                {
                    throw new SWFModellerException(
                              SWFModellerError.Internal,
                              "Can't place object that hasn't been written to stream yet.");
                }
#endif
                cid = this.characterMarshal.GetIDFor(po.Character);
                tagWriter.WriteUI16((uint)cid);
#if DEBUG
                this.LogMessage("po cid =" + cid);
#endif
                tagWriter.WriteUI16((uint)po.LayerIndex);
                if (!po.HasMatrix)
                {
                    throw new SWFModellerException(
                              SWFModellerError.Internal,
                              "A PlaceObject display list item must have a Matrix, unless it's a PlaceObject2 tag. See spec for info, I can't work it out.");
                }
                tagWriter.WriteMatrix(po.Matrix);
                if (po.HasColorTransform)
                {
                    tagWriter.WriteColorTransform(po.CXForm, false);
                }
                break;


            case Tag.PlaceObject2:
                tagWriter.WriteBit(po.HasClipActions);
                tagWriter.WriteBit(po.HasClipDepth);
                tagWriter.WriteBit(po.HasName);
                tagWriter.WriteBit(po.HasRatio);
                tagWriter.WriteBit(po.HasColorTransform);
                tagWriter.WriteBit(po.HasMatrix);
                tagWriter.WriteBit(po.HasCharacter);
                tagWriter.WriteBit(po.IsMove);
                tagWriter.WriteUI16((uint)po.LayerIndex);

                if (po.HasCharacter)
                {
#if DEBUG
                    if (!this.characterMarshal.HasMarshalled(po.Character))
                    {
                        throw new SWFModellerException(
                                  SWFModellerError.Internal,
                                  "Can't place object that hasn't been written to stream yet.");
                    }
#endif
                    cid = this.characterMarshal.GetIDFor(po.Character);
                    tagWriter.WriteUI16((uint)cid);
#if DEBUG
                    this.LogMessage("po cid =" + cid);
#endif
                }

                if (po.HasMatrix)
                {
                    tagWriter.WriteMatrix(po.Matrix);
                }

                if (po.HasColorTransform)
                {
                    tagWriter.WriteColorTransform(po.CXForm, true);
                }

                if (po.HasRatio)
                {
                    tagWriter.WriteUI16((uint)po.Ratio);
                }

                if (po.HasName)
                {
#if DEBUG
                    this.LogMessage("name=" + po.Name);
#endif
                    tagWriter.WriteString(po.Name);
                }

                if (po.HasClipDepth)
                {
                    tagWriter.WriteUI16((uint)po.ClipDepth);
                }

                if (po.HasClipActions)
                {
                    throw new SWFModellerException(
                              SWFModellerError.Internal,
                              "Clips cannot have actions in the target SWF version.");
                }
                break;

            default:
                /* ISSUE 73 */
                throw new SWFModellerException(
                          SWFModellerError.UnimplementedFeature,
                          "Unsupported PlaceObject tag: " + placeTag.ToString());
            }

#if DEBUG
            this.LogMessage("po char =" + po.Character);
#endif

            this.CloseTag();
        }
Example #12
0
        /// <summary>
        /// Does the grunt-work of writing all the objects in the SWF file, tagging
        /// each of them with a record header.
        /// </summary>
        private void WriteTags()
        {
            /* Start with a file attributes tag */
            this.WriteFileAttributesTag();
            /* Despite background color being specified in the header, flash always puts this in too. */
            this.WriteBGColorTag();

            if (swf.ProtectHash != null)
            {
                /* ISSUE 45: This should be an option of some kind. */
                WriteBuffer protectTag = this.OpenTag(Tag.Protect);
                protectTag.WriteUI16(0); /* Reserved, always 0 */
                protectTag.WriteString(swf.ProtectHash);
                this.CloseTag();
            }

            if (this.options.EnableDebugger)
            {
                WriteBuffer dbugTag = this.OpenTag(Tag.EnableDebugger2);
                dbugTag.WriteUI16(0);                                /* Reserved, always 0 */
                dbugTag.WriteString("$1$ZH$B14iwyCzzcXcqLaJz0Mif0"); /* MD5-encoded password "abc"; http://devadraco.blogspot.com/2009/06/guide-to-cracking-enabledebugger2.html */
                this.CloseTag();
            }

            /* ISSUE 46: Write DefineSceneAndFrameLabelData tag */
            foreach (DoABC abc in this.swf.Scripts)
            {
                WriteBuffer abcOut = this.OpenTag(Tag.DoABC);

                abcOut.WriteUI32((uint)(abc.IsLazilyInitialized ? ABCValues.AbcFlagLazyInitialize : 0));
                abcOut.WriteString(abc.Name);

                AbcWriter abcWriter = new AbcWriter();
                abcWriter.AssembleIfNecessary(
                    abc,
                    this.options.EnableDebugger,
                    this.swf.Class == null ? null : this.swf.Class.QualifiedName,
                    this.abcWriteLog);

                abcOut.WriteBytes(abc.Bytecode);

                this.CloseTag();
            }

            ListSet <Timeline> writtenSymbolClasses = new ListSet <Timeline>();
            ListSet <Timeline> unboundClasses       = new ListSet <Timeline>();

            foreach (Sprite exported in this.swf.ExportOnFirstFrame)
            {
                this.WriteSprite(exported, unboundClasses);
            }

            this.BindClasses(unboundClasses);

            if (this.swf.FrameCount > 0)
            {
                int writtenFrames = 0;

                if (this.swf.HasClass)
                {
                    WriteBuffer scbuf = this.OpenTag(Tag.SymbolClass);
                    scbuf.WriteUI16(1);                              /* Count */
                    scbuf.WriteUI16(0);                              /* Character ref */
                    scbuf.WriteString(this.swf.Class.QualifiedName); /* Name */
                    this.LogMessage(this.swf.Class.QualifiedName);
                    this.CloseTag();
                }

                foreach (Frame f in this.swf.Frames)
                {
                    if (f.HasLabel)
                    {
#if DEBUG
                        this.LogMessage("frame label=" + f.Label);
#endif
                        WriteBuffer labelWriter = this.OpenTag(Tag.FrameLabel);
                        labelWriter.WriteString(f.Label);
                        this.CloseTag();
                    }

                    foreach (IDisplayListItem dli in f.DisplayList)
                    {
                        switch (dli.Type)
                        {
                        case DisplayListItemType.PlaceObjectX:
                            this.WriteCharacter(((ICharacterReference)dli).Character, unboundClasses);
                            this.WritePlaceObjectTag((PlaceObject)dli);
                            break;

                        case DisplayListItemType.RemoveObjectX:
                        default:
                            this.WriteRemoveObjectTag((RemoveObject)dli);
                            break;
                        }
                    }

                    this.BindClasses(unboundClasses);

                    this.WriteBodylessTag(Tag.ShowFrame);

                    writtenFrames++;

                    List <SymbolClass> symbolClasses = new List <SymbolClass>();
                }
            }
            else
            {
                /* No SWF should be frameless. Awwww. */
                this.WriteBodylessTag(Tag.ShowFrame);
            }

            /* Finish with an end tag */
            this.WriteBodylessTag(Tag.End);
        }