/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.flex.swf.io; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.MalformedURLException; import java.net.URL; import java.text.DateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import org.apache.commons.io.IOUtils; import org.apache.flex.abc.ABCParser; import org.apache.flex.abc.PoolingABCVisitor; import org.apache.flex.abc.print.ABCDumpVisitor; import org.apache.flex.compiler.clients.problems.CompilerProblemCategorizer; import org.apache.flex.compiler.clients.problems.ProblemFormatter; import org.apache.flex.compiler.clients.problems.ProblemPrinter; import org.apache.flex.compiler.clients.problems.ProblemQuery; import org.apache.flex.compiler.clients.problems.WorkspaceProblemFormatter; import org.apache.flex.compiler.common.VersionInfo; import org.apache.flex.compiler.internal.workspaces.Workspace; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.swf.Header; import org.apache.flex.swf.SWF; import org.apache.flex.swf.SWFFrame; import org.apache.flex.swf.TagType; import org.apache.flex.swf.tags.*; import org.apache.flex.swf.types.ButtonRecord; import org.apache.flex.swf.types.CurvedEdgeRecord; import org.apache.flex.swf.types.EdgeRecord; import org.apache.flex.swf.types.FillStyle; import org.apache.flex.swf.types.FillStyleArray; import org.apache.flex.swf.types.Filter; import org.apache.flex.swf.types.FocalGradient; import org.apache.flex.swf.types.GlyphEntry; import org.apache.flex.swf.types.GradRecord; import org.apache.flex.swf.types.IFillStyle; import org.apache.flex.swf.types.ILineStyle; import org.apache.flex.swf.types.KerningRecord; import org.apache.flex.swf.types.LineStyle; import org.apache.flex.swf.types.LineStyle2; import org.apache.flex.swf.types.LineStyleArray; import org.apache.flex.swf.types.MorphFillStyle; import org.apache.flex.swf.types.MorphGradRecord; import org.apache.flex.swf.types.MorphLineStyle; import org.apache.flex.swf.types.RGB; import org.apache.flex.swf.types.RGBA; import org.apache.flex.swf.types.Shape; import org.apache.flex.swf.types.ShapeRecord; import org.apache.flex.swf.types.ShapeWithStyle; import org.apache.flex.swf.types.SoundEnvelope; import org.apache.flex.swf.types.SoundInfo; import org.apache.flex.swf.types.StraightEdgeRecord; import org.apache.flex.swf.types.StyleChangeRecord; import org.apache.flex.swf.types.Styles; import org.apache.flex.swf.types.TextRecord; import org.apache.flex.swf.types.ZoneRecord; import org.apache.flex.utils.Base64; import org.apache.flex.utils.Trace; import com.google.common.collect.ImmutableList; /** * Dump a SWF model to XML. Usage: swfdump [-abc] file1.swf */ public final class SWFDump { /** * Dump a SWF at a given URL. * * @param url URL of the SWF to dump. * @throws IOException Any IO error ;-) */ public void dump(URL url) throws IOException { final SWFReader swfReader = new SWFReader(); final String path = url.getPath(); try { swf = (SWF)swfReader.readFrom( new BufferedInputStream(url.openStream()), path); ProblemQuery problemQuery = new ProblemQuery(); problemQuery.addAll(swfReader.getProblems()); if (!problemQuery.hasErrors()) { dumpHeader(swf.getHeader()); final ImmutableList<ITag> tags = ImmutableList.copyOf(swfReader.iterator()); currentFrameIndex = 0; if (swf.getFrameCount() > 0) currentFrame = swf.getFrameAt(0); HashMap<String, ITag> abcTags = new HashMap<String, ITag>(); for (ITag tag : tags) { if (sortOption) { if (tag.getTagType() == TagType.DoABC) { DoABCTag abcTag = (DoABCTag)tag; abcTags.put(abcTag.getName(), tag); } else if (tag.getTagType() == TagType.ShowFrame) { if (abcTags.size() > 0) { ArrayList<String> nameList = new ArrayList<String>(); nameList.addAll(abcTags.keySet()); Collections.sort(nameList); for (String name : nameList) { ITag abcTag = abcTags.get(name); dumpTag(abcTag); } } dumpTag(tag); abcTags = new HashMap<String, ITag>(); } else dumpTag(tag); } else { dumpTag(tag); } if (tag.getTagType() == TagType.ShowFrame) { currentFrameIndex++; if (currentFrameIndex < swf.getFrameCount()) currentFrame = swf.getFrameAt(currentFrameIndex); } } finish(); } printProblems(swfReader.getProblems()); } finally { IOUtils.closeQuietly(swfReader); } } /** * Print out problems to standard output. * * @param problems */ private void printProblems(Collection<ICompilerProblem> problems) { CompilerProblemCategorizer categorizer = new CompilerProblemCategorizer(); ProblemFormatter formatter = new WorkspaceProblemFormatter(new Workspace(), categorizer); ProblemPrinter printer = new ProblemPrinter(formatter, System.err); printer.printProblems(problems); } /** * Dump a tag. * * @param tag the tag to dump. */ private void dumpTag(ITag tag) { TagType type = tag.getTagType(); switch (type) { case CSMTextSettings: dumpCSMTextSettings((CSMTextSettingsTag)tag); break; case DoABC: dumpDoABC((DoABCTag)tag); break; case DefineBinaryData: dumpDefineBinaryData((DefineBinaryDataTag)tag); break; case DefineBits: dumpDefineBits((DefineBitsTag)tag); break; case DefineBitsJPEG2: dumpDefineBitsJPEG2((DefineBitsTag)tag); break; case DefineBitsJPEG3: dumpDefineBitsJPEG3((DefineBitsJPEG3Tag)tag); break; case DefineBitsLossless: dumpDefineBitsLossless((DefineBitsLosslessTag)tag); break; case DefineBitsLossless2: dumpDefineBitsLossless2((DefineBitsLossless2Tag)tag); break; case DefineScalingGrid: dumpDefineScalingGrid((DefineScalingGridTag)tag); break; case DefineShape: dumpDefineShape((DefineShapeTag)tag); break; case DefineShape2: dumpDefineShape2((DefineShapeTag)tag); break; case DefineShape3: dumpDefineShape3((DefineShape3Tag)tag); break; case DefineShape4: dumpDefineShape4((DefineShape4Tag)tag); break; case DefineSprite: dumpDefineSprite((DefineSpriteTag)tag); break; case DefineSound: dumpDefineSound((DefineSoundTag)tag); break; case StartSound: dumpStartSound((StartSoundTag)tag); break; case StartSound2: // TODO StartSound2 // dumpStartSound2(); break; case SoundStreamHead: dumpSoundStreamHead((SoundStreamHeadTag)tag); break; case SoundStreamHead2: dumpSoundStreamHead2((SoundStreamHeadTag)tag); break; case SoundStreamBlock: dumpSoundStreamBlock((SoundStreamBlockTag)tag); break; case DefineMorphShape: dumpDefineMorphShape((DefineMorphShapeTag)tag); break; case DefineMorphShape2: dumpDefineMorphShape2((DefineMorphShapeTag)tag); break; case DefineSceneAndFrameLabelData: // TODO: no dump routine for this tag. //dumpDefineSceneAndFrameLabelData(); break; case DefineFont: dumpDefineFont((DefineFontTag)tag); break; case DefineFontInfo: dumpDefineFontInfo((DefineFontInfoTag)tag); break; case DefineFont2: dumpDefineFont2((DefineFont2Tag)tag); break; case DefineFont3: dumpDefineFont3((DefineFont3Tag)tag); break; case DefineFont4: dumpDefineFont4((DefineFont4Tag)tag); break; case DefineFontAlignZones: dumpDefineFontAlignZones((DefineFontAlignZonesTag)tag); break; case DefineFontName: dumpDefineFontName((DefineFontNameTag)tag); break; case DefineText: dumpDefineText((DefineTextTag)tag); break; case DefineText2: dumpDefineText((DefineTextTag)tag); break; case DefineEditText: dumpDefineEditText((DefineEditTextTag)tag); break; case DefineButton: dumpDefineButton((DefineButtonTag)tag); break; case DefineButton2: dumpDefineButton2((DefineButton2Tag)tag); break; case DefineButtonSound: dumpDefineButtonSound((DefineButtonSoundTag)tag); break; case DefineVideoStream: dumpDefineVideoStream((DefineVideoStreamTag)tag); break; case VideoFrame: dumpVideoFrame((VideoFrameTag)tag); break; case End: break; case EnableDebugger2: dumpEnableDebugger2((EnableDebugger2Tag)tag); break; case ExportAssets: dumpExportAssets((ExportAssetsTag)tag); break; case FileAttributes: dumpFileAttributes((FileAttributesTag)tag); break; case FrameLabel: dumpFrameLabel((FrameLabelTag)tag); break; case JPEGTables: // TODO: handle this tag. //dumpJPEGTables(); break; case Metadata: dumpMetadata((MetadataTag)tag); break; case ProductInfo: dumpProductInfo((ProductInfoTag)tag); break; case PlaceObject: dumpPlaceObject((PlaceObjectTag)tag); break; case PlaceObject2: dumpPlaceObject2((PlaceObjectTag)tag); break; case PlaceObject3: dumpPlaceObject3((PlaceObjectTag)tag); break; case RemoveObject: dumpRemoveObject((RemoveObjectTag)tag); break; case RemoveObject2: dumpRemoveObject2((RemoveObject2Tag)tag); break; case ScriptLimits: dumpScriptLimits((ScriptLimitsTag)tag); break; case SetBackgroundColor: dumpSetBackgroundColor((SetBackgroundColorTag)tag); break; case SetTabIndex: dumpSetTabIndex((SetTabIndexTag)tag); break; case ShowFrame: dumpShowFrame((ShowFrameTag)tag); break; case SymbolClass: dumpSymbolClass((SymbolClassTag)tag); break; case EnableTelemetry: dumpEnableTelemetry((EnableTelemetryTag) tag); break; default: assert (tag instanceof RawTag); if (tag instanceof RawTag) { dumpRawTag((RawTag) tag); } break; } } /** * this value should get set after the header is parsed */ @SuppressWarnings("unused") private Integer swfVersion = null; private boolean abc = false; private boolean showActions = true; private boolean showOffset = false; @SuppressWarnings("unused") private boolean showByteCode = false; @SuppressWarnings("unused") private boolean showDebugSource = false; private boolean glyphs = true; private boolean external = false; private String externalPrefix = null; private String externalDirectory = null; @SuppressWarnings("unused") private boolean decompile; @SuppressWarnings("unused") private boolean defunc; private int indent = 0; private boolean tabbedGlyphs = true; private SWF swf; /** * Constructor. * * @param out The output stream. */ public SWFDump(PrintWriter out) { this.out = out; } // TODO: We currently don't decode actions. // private void printActions(ActionList list) // { // if (decompile) // { // /* // AsNode node; // try // { // node = new Decompiler(defunc).decompile(list); // new PrettyPrinter(out, indent).list(node); // return; // } // catch (Exception e) // { // indent(); // out.println("// error while decompiling. falling back to disassembler"); // } // */ // } // // Disassembler disassembler = new Disassembler(out, showOffset, indent); // if (showDebugSource) // { // disassembler.setShowDebugSource(showDebugSource); // disassembler.setComment("// "); // } // list.visitAll(disassembler); // } @SuppressWarnings("unused") private void setExternal(boolean b, String path) { external = b; if (external) { if (path != null) { externalPrefix = baseName(path); externalDirectory = dirName(path); } if (externalPrefix == null) externalPrefix = ""; else externalPrefix += "-"; if (externalDirectory == null) externalDirectory = ""; } } private void indent() { for (int i = 0; i < indent; i++) { out.print(" "); } } public void dumpHeader(Header h) { swfVersion = h.getVersion(); out.println("<swf xmlns=\"http://macromedia/2003/swfx\"" + " version=\"" + h.getVersion() + "\"" + " framerate=\"" + h.getFrameRate() + "\"" + " size=\"" + h.getFrameSize().getWidth() + "x" + h.getFrameSize().getHeight() + "\"" + " compressed=\"" + (h.getCompression() != Header.Compression.NONE) + "\"" + " >"); indent++; indent(); if (sortOption) out.println("<!-- framecount=" + h.getFrameCount() + " length might be different because of debugfile paths -->"); else out.println("<!-- framecount=" + h.getFrameCount() + " length=" + h.getLength() + " -->"); } public void dumpProductInfo(ProductInfoTag productInfo) { open(productInfo); out.print(" product=\"" + productInfo.getProduct() + "\""); out.print(" edition=\"" + productInfo.getEdition() + "\""); out.print(" version=\"" + productInfo.getMajorVersion() + "." + productInfo.getMinorVersion() + "\""); out.print(" build=\"" + productInfo.getBuild() + "\""); out.print(" compileDate=\"" + DateFormat.getInstance().format(new Date(productInfo.getCompileDate())) + "\""); close(); } public void dumpMetadata(MetadataTag tag) { open(tag); end(); indent(); openCDATA(); String metaData = tag.getMetadata(); if (sortOption) { metaData = metaData.replaceAll("build=\".*\"", ""); } out.println(metaData); closeCDATA(); close(tag); } public void dumpFileAttributes(FileAttributesTag tag) { open(tag); out.print(" useDirectBlit=\"" + tag.isUseDirectBlit() + "\""); out.print(" useGPU=\"" + tag.isUseGPU() + "\""); out.print(" hasMetadata=\"" + tag.isHasMetadata() + "\""); out.print(" actionScript3=\"" + tag.isAS3() + "\""); out.print(" suppressCrossDomainCaching=\"" + tag.isSuppressCrossDomainCaching() + "\""); out.print(" swfRelativeUrls=\"" + tag.isSWFRelativeURLs() + "\""); out.print(" useNetwork=\"" + tag.isUseNetwork() + "\""); close(); } private final PrintWriter out; private SWFFrame currentFrame; private int currentFrameIndex; /** * Get the symbol name of a class. * * @param tag tag to look up. * @return Name of tag in the symbol class table, null if not found. */ public String id(ICharacterTag tag) { return String.valueOf(tag.getCharacterID()); } public void setOffsetAndSize(int offset, int size) { // Note: 'size' includes the size of the tag's header // so it is either length + 2 or length + 6. if (showOffset) { indent(); out.println("<!--" + " offset=" + offset + " size=" + size + " -->"); } } /** * Output the start of XML output for a SWF tag. * * @param tag the tag to output. */ private void open(ITag tag) { indent(); out.print("<" + tag.getTagType().toString()); } /** * Output the end of XML output for a SWF tag. * * @param tag the tag to output. */ private void end() { out.println(">"); indent++; } private void openCDATA() { indent(); out.print("<![CDATA["); } private void closeCDATA() { out.println("]]>"); } private void close() { out.println("/>"); } private void close(Tag tag) { indent--; indent(); out.println("</" + tag.getTagType().toString() + ">"); } public void error(String s) { indent(); out.println("<!-- error: " + s + " -->"); } public void dumpRawTag(RawTag tag) { indent(); out.println("<!-- unknown tag=" + tag.getTagType().getValue() + " length=" + (tag.getTagBody() != null ? tag.getTagBody().length : 0) + " -->"); } public void dumpShowFrame(ShowFrameTag tag) { open(tag); close(); } public void dumpDefineShape(DefineShapeTag tag) { printDefineShape(tag); } private void printDefineShape(DefineShapeTag tag) { open(tag); out.print(" id=\"" + id(tag) + "\""); out.print(" bounds=\"" + tag.getShapeBounds() + "\""); if (tag.getTagType() == TagType.DefineShape4) { DefineShape4Tag shape4Tag = (DefineShape4Tag)tag; out.print(" edgebounds=\"" + shape4Tag.getEdgeBounds() + "\""); out.print(" usesNonScalingStrokes=\"" + shape4Tag.isUsesNonScalingStrokes() + "\""); out.print(" usesScalingStrokes=\"" + shape4Tag.isUsesScalingStrokes() + "\""); } end(); printShapeWithStyles(tag.getShapes()); close(tag); } static final char[] digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; /** * @param color to print. * @return string formatted as #RRGGBB */ public String printColor(RGB color) { StringBuilder b = new StringBuilder(); b.append('#'); int red = color.getRed(); b.append(digits[(red >> 4) & 15]); b.append(digits[red & 15]); int green = color.getGreen(); b.append(digits[(green >> 4) & 15]); b.append(digits[green & 15]); int blue = color.getBlue(); b.append(digits[(blue >> 4) & 15]); b.append(digits[blue & 15]); if (color instanceof RGBA) { int alpha = ((RGBA)color).getAlpha(); b.append(digits[(alpha >> 4) & 15]); b.append(digits[alpha & 15]); } return b.toString(); } public void dumpPlaceObject(PlaceObjectTag tag) { open(tag); out.print(" idref=\"" + idRef(tag.getCharacter()) + "\""); out.print(" depth=\"" + tag.getDepth() + "\""); out.print(" matrix=\"" + tag.getMatrix() + "\""); if (tag.getColorTransform() != null) out.print(" colorXform=\"" + tag.getColorTransform() + "\""); close(); } public void dumpRemoveObject(RemoveObjectTag tag) { open(tag); out.print(" idref=\"" + idRef(tag.getCharacter()) + "\""); close(); } public void outputBase64(byte[] data) { Base64.Encoder e = new Base64.Encoder(1024); indent(); int remain = data.length; while (remain > 0) { int block = 1024; if (block > remain) block = remain; e.encode(data, data.length - remain, block); out.print(e.drain()); remain -= block; } out.println(e.flush()); } public void dumpDefineBits(DefineBitsTag tag) { if (tag.getData() == null) { out.println("<!-- warning: no JPEG table tag found. -->"); } open(tag); out.print(" id=\"" + id(tag) + "\""); // if (external) // { // String path = externalDirectory // + externalPrefix // + "image" // + dictionary.getId(tag) // + ".jpg"; // // out.println(" src=\"" + path + "\" />"); // try // { // FileOutputStream image = new FileOutputStream(path, false); // SwfImageUtils.JPEG jpeg = new SwfImageUtils.JPEG(tag.jpegTables.data, tag.data); // jpeg.write(image); // image.close(); // } // catch (IOException e) // { // out.println("<!-- error: unable to write external asset file " + path + "-->"); // } // } // else { out.print(" encoding=\"base64\""); end(); outputBase64(tag.getData()); close(tag); } } public void dumpDefineButton(DefineButtonTag tag) { open(tag); out.print(" id=\"" + id(tag) + "\""); end(); if (showActions) { openCDATA(); // todo print button records outputBase64(tag.getActions()); closeCDATA(); } else { // TODO: dump out raw action bytes // out.println("<!-- " + tag.condActions[0].actionList.size() + " action(s) elided -->"); } close(tag); } public void dumpSetBackgroundColor(SetBackgroundColorTag tag) { open(tag); out.print(" color=\"" + printColor(tag.getColor()) + "\""); close(); } public void dumpDefineFont(DefineFontTag tag) { open(tag); out.print(" id=\"" + id(tag) + "\""); end(); if (glyphs) { for (int i = 0; i < tag.getGlyphShapeTable().length; i++) { indent(); out.println("<glyph>"); Shape shape = tag.getGlyphShapeTable()[i]; indent++; printShapeWithTabs(shape); indent--; indent(); out.println("</glyph>"); } } close(tag); } public void dumpDefineText(DefineTextTag tag) { open(tag); out.print(" id=\"" + id(tag) + "\""); out.print(" bounds=\"" + tag.getTextBounds() + "\""); out.print(" matrix=\"" + tag.getTextMatrix() + "\""); end(); for (TextRecord tr : tag.getTextRecords()) { printTextRecord(tr, tag.getTagType().getValue()); } close(tag); } // TODO: decode actions // public void doAction(DoAction tag) // { // open(tag); // end(); // // if (showActions) // { // openCDATA(); // printActions(tag.actionList); // closeCDATA(); // } // else // { // out.println("<!-- " + tag.actionList.size() + " action(s) elided -->"); // } // close(tag); // } public void dumpDefineFontInfo(DefineFontInfoTag tag) { open(tag); out.print(" idref=\"" + idRef(tag.getFontTag()) + "\""); out.print(" ansi=\"" + tag.isFontFlagsANSI() + "\""); out.print(" italic=\"" + tag.isFontFlagsItalic() + "\""); out.print(" bold=\"" + tag.isFontFlagsBold() + "\""); out.print(" wideCodes=\"" + tag.isFontFlagsWideCodes() + "\""); out.print(" smallText=\"" + tag.isFontFlagsSmallText() + "\""); out.print(" name=\"" + escape(tag.getFontName()) + "\""); out.print(" shiftJIS=\"" + tag.isFontFlagsShiftJIS() + "\""); end(); indent(); int[] codeTable = tag.getCodeTable(); for (int i = 0; i < codeTable.length; i++) { out.print((int)codeTable[i]); if ((i + 1) % 16 == 0) { out.println(); indent(); } else { out.print(' '); } } if (codeTable.length % 16 != 0) { out.println(); indent(); } close(tag); } public void dumpDefineSound(DefineSoundTag tag) { open(tag); out.print(" id=\"" + id(tag) + "\""); out.print(" format=\"" + tag.getSoundFormat() + "\""); out.print(" rate=\"" + tag.getSoundRate() + "\""); out.print(" size=\"" + tag.getSoundSize() + "\""); out.print(" type=\"" + tag.getSoundType() + "\""); out.print(" sampleCount=\"" + tag.getSoundSampleCount() + "\""); out.print(" soundDataSize=\"" + tag.getSoundData().length + "\""); end(); openCDATA(); outputBase64(tag.getSoundData()); closeCDATA(); close(tag); } public void dumpStartSound(StartSoundTag tag) { open(tag); out.print(" soundid=\"" + idRef(tag.getSoundTag()) + "\""); printSoundInfo(tag.getSoundInfo()); close(tag); } private void printSoundInfo(SoundInfo info) { out.print(" syncStop=\"" + info.isSyncStop() + "\""); out.print(" syncNoMultiple=\"" + info.isSyncNoMultiple() + "\""); if (info.getInPoint() != 0) { out.print(" inPoint=\"" + info.getInPoint() + "\""); } if (info.getOutPoint() != 0) { out.print(" outPoint=\"" + info.getOutPoint() + "\""); } if (info.getLoopCount() != 0) { out.print(" loopCount=\"" + info.getLoopCount() + "\""); } end(); SoundEnvelope[] envelopes = info.getEnvelopeRecords(); if (envelopes != null && envelopes.length > 0) { openCDATA(); for (int i = 0; i < envelopes.length; i++) { out.println("pos44 =\"" + envelopes[i].getPos44() + "\""); out.println("left level =\"" + envelopes[i].getLeftLevel() + "\""); out.println("right level=\"" + envelopes[i].getRightLevel() + "\""); } closeCDATA(); } } public void dumpDefineButtonSound(DefineButtonSoundTag tag) { open(tag); out.print(" buttonId=\"" + idRef(tag.getButtonTag()) + "\""); close(); } public void dumpSoundStreamHead(SoundStreamHeadTag tag) { open(tag); close(); } public void dumpSoundStreamBlock(SoundStreamBlockTag tag) { open(tag); close(); } public void dumpDefineBinaryData(DefineBinaryDataTag tag) { open(tag); out.println(" id=\"" + id(tag) + "\" length=\"" + tag.getData().length + "\" />"); } public void dumpDefineBitsLossless(DefineBitsLosslessTag tag) { open(tag); out.print(" id=\"" + id(tag) + "\" width=\"" + tag.getBitmapWidth() + "\" height=\"" + tag.getBitmapHeight() + "\""); if (external) { String path = externalDirectory + externalPrefix + "image" + id(tag) + ".bitmap"; out.println(" src=\"" + path + "\" />"); try { FileOutputStream image = new FileOutputStream(path, false); image.write(tag.getData()); image.close(); } catch (IOException e) { out.println("<!-- error: unable to write external asset file " + path + "-->"); } } else { out.print(" encoding=\"base64\""); end(); outputBase64(tag.getData()); close(tag); } } public void dumpDefineBitsJPEG2(DefineBitsTag tag) { open(tag); out.print(" id=\"" + id(tag) + "\""); if (external) { String path = externalDirectory + externalPrefix + "image" + id(tag) + ".jpg"; out.println(" src=\"" + path + "\" />"); try { FileOutputStream image = new FileOutputStream(path, false); image.write(tag.getData()); image.close(); } catch (IOException e) { out.println("<!-- error: unable to write external asset file " + path + "-->"); } } else { out.print(" encoding=\"base64\""); end(); outputBase64(tag.getData()); close(tag); } } public void dumpDefineShape2(DefineShapeTag tag) { printDefineShape(tag); } public void dumpDefineButtonCxform(DefineButtonCxformTag tag) { open(tag); out.print(" buttonId=\"" + idRef(tag.getButtonTag()) + "\""); close(); } // TODO: Handle the Protect Tag // public void protect(GenericTag tag) // { // open(tag); // if (tag.data != null) // out.print(" password=\"" + hexify(tag.data) + "\""); // close(); // } public void dumpPlaceObject2(PlaceObjectTag tag) { dumpPlaceObject23(tag); } public void dumpPlaceObject3(PlaceObjectTag tag) { dumpPlaceObject23(tag); } public void dumpPlaceObject23(PlaceObjectTag tag) { PlaceObject2Tag tag2 = tag instanceof PlaceObject2Tag ? (PlaceObject2Tag)tag : null; PlaceObject3Tag tag3 = tag instanceof PlaceObject3Tag ? (PlaceObject3Tag)tag : null; if (tag != null || (tag2 != null && tag2.isHasCharacter())) { if (tag.getCharacter() != null && currentFrame != null && currentFrame.getSymbolName(tag.getCharacter()) != null) { indent(); out.println("<!-- instance of " + idRef(tag.getCharacter()) + " -->"); } } open(tag); if (tag3 != null && tag3.isHasClassName()) out.print(" className=\"" + tag3.getClassName() + "\""); if (tag3 != null && tag3.isHasImage()) out.print(" hasImage=\"true\" "); if (tag != null || (tag2 != null && tag2.isHasCharacter())) out.print(" idref=\"" + idRef(tag.getCharacter()) + "\""); if (tag2 != null && tag2.isHasName()) out.print(" name=\"" + tag2.getName() + "\""); out.print(" depth=\"" + tag.getDepth() + "\""); if (tag2 != null && tag2.isHasClipDepth()) out.print(" clipDepth=\"" + tag2.getClipDepth() + "\""); if (tag3 != null && tag3.isHasCacheAsBitmap()) out.print(" cacheAsBitmap=\"true\""); if (tag2 != null && tag2.isHasRatio()) out.print(" ratio=\"" + tag2.getRatio() + "\""); if (tag2 != null && tag2.isHasColorTransform()) out.print(" cxform=\"" + tag2.getColorTransform() + "\""); else if (tag.getColorTransform() != null) out.print(" cxform=\"" + tag.getColorTransform() + "\""); if (tag.getMatrix() != null || (tag2 != null && tag2.isHasMatrix())) out.print(" matrix=\"" + tag.getMatrix() + "\""); if (tag3 != null && tag3.isHasBlendMode()) out.print(" blendmode=\"" + tag3.getBlendMode() + "\""); if (tag3 != null && tag3.isHasFilterList()) { // todo - pretty print this once we actually care out.print(" filters=\""); for (Filter filter : tag3.getSurfaceFilterList()) { out.print(filter.getFilterID() + " "); } out.print("\""); } // TODO: decode clip actions // if (tag3 != null && tag3.isHasClipActions()) // { // end(); // Iterator it = tag.clipActions.clipActionRecords.iterator(); // // openCDATA(); // for (ClipActions ) // { // ClipActionRecord record = (ClipActionRecord)it.next(); // indent(); // out.println("onClipEvent(" + printClipEventFlags(record.eventFlags) + // (record.hasKeyPress() ? "<" + record.keyCode + ">" : "") + // ") {"); // indent++; // if (showActions) // { // printActions(record.actionList); // } // else // { // indent(); // out.println("// " + record.actionList.size() + " action(s) elided"); // } // indent--; // indent(); // out.println("}"); // } // closeCDATA(); // close(tag); // } // else { close(); } } public void dumpRemoveObject2(RemoveObject2Tag tag) { open(tag); out.print(" depth=\"" + tag.getDepth() + "\""); close(); } public void dumpDefineShape3(DefineShape3Tag tag) { printDefineShape(tag); } public void dumpDefineShape4(DefineShape4Tag tag) { printDefineShape(tag); } private void printShapeWithStyles(ShapeWithStyle shapes) { printFillStyles(shapes.getFillStyles()); printLineStyles(shapes.getLineStyles()); printShape(shapes); } @SuppressWarnings("unused") private void printMorphLineStyles(MorphLineStyle[] lineStyles) { for (int i = 0; i < lineStyles.length; i++) { MorphLineStyle lineStyle = lineStyles[i]; indent(); out.print("<linestyle "); out.print("startColor=\"" + printColor(lineStyle.getStartColor()) + "\" "); out.print("endColor=\"" + printColor(lineStyle.getStartColor()) + "\" "); out.print("startWidth=\"" + lineStyle.getStartWidth() + "\" "); out.print("endWidth=\"" + lineStyle.getEndWidth() + "\" "); out.println("/>"); } } private void printLineStyles(LineStyleArray linestyles) { for (ILineStyle lineStyle : linestyles) { indent(); out.print("<linestyle "); if (lineStyle instanceof LineStyle) { LineStyle ls = (LineStyle)lineStyle; String color = printColor(ls.getColor()); out.print("color=\"" + color + "\" "); out.print("width=\"" + ls.getWidth() + "\" "); } if (lineStyle instanceof LineStyle2) { LineStyle2 lineStyle2 = (LineStyle2)lineStyle; if (lineStyle2.getJoinStyle() == LineStyle2.JS_MITER_JOIN) { out.print("miterLimit=\"" + lineStyle2.getMiterLimitFactor() + "\" "); } if (lineStyle2.isHasFillFlag()) { out.println(">"); indent(); FillStyleArray fillStyles = new FillStyleArray(1); fillStyles.add(lineStyle2.getFillType()); printFillStyles(fillStyles); indent(); out.println("</linestyle>"); } else { out.println("/>"); } } else { out.println("/>"); } } } private void printFillStyles(FillStyleArray fillstyles) { for (IFillStyle iFillStyle : fillstyles) { indent(); out.print("<fillstyle"); int fillStyleType; FillStyle fillStyle = (FillStyle)iFillStyle; fillStyleType = fillStyle.getFillStyleType(); out.print(" type=\"" + fillStyleType + "\""); if (fillStyleType == FillStyle.SOLID_FILL) { out.print(" color=\"" + printColor(fillStyle.getColor()) + "\""); } if ((fillStyleType & FillStyle.LINEAR_GRADIENT_FILL) != 0) { if (fillStyleType == FillStyle.RADIAL_GRADIENT_FILL) out.print(" typeName=\"radial\""); else if (fillStyleType == FillStyle.FOCAL_RADIAL_GRADIENT_FILL) out.print(" typeName=\"focal\" focalPoint=\"" + ((FocalGradient)fillStyle.getGradient()).getFocalPoint() + "\""); // todo print linear or radial or focal out.print(" gradient=\"" + formatGradient(fillStyle.getGradient().getGradientRecords()) + "\""); out.print(" matrix=\"" + fillStyle.getGradientMatrix() + "\""); } if ((fillStyleType & FillStyle.REPEATING_BITMAP_FILL) != 0) { // todo print tiled or clipped out.print(" idref=\"" + idRef(fillStyle.getBitmapCharacter()) + "\""); out.print(" matrix=\"" + fillStyle.getBitmapMatrix() + "\""); } out.println(" />"); } } @SuppressWarnings("unused") private void printMorphFillStyles(MorphFillStyle[] fillStyles) { for (int i = 0; i < fillStyles.length; i++) { MorphFillStyle fillStyle = fillStyles[i]; indent(); out.print("<fillstyle"); out.print(" type=\"" + fillStyle.getFillStyleType() + "\""); if (fillStyle.getFillStyleType() == FillStyle.SOLID_FILL) { out.print(" startColor=\"" + printColor(fillStyle.getStartColor()) + "\""); out.print(" endColor=\"" + printColor(fillStyle.getEndColor()) + "\""); } if ((fillStyle.getFillStyleType() & FillStyle.LINEAR_GRADIENT_FILL) != 0) { // todo print linear or radial out.print(" gradient=\"" + formatMorphGradient( fillStyle.getGradient().toArray(new MorphGradRecord[0])) + "\""); out.print(" startMatrix=\"" + fillStyle.getStartGradientMatrix() + "\""); out.print(" endMatrix=\"" + fillStyle.getEndGradientMatrix() + "\""); if (fillStyle.getFillStyleType() == FillStyle.FOCAL_RADIAL_GRADIENT_FILL) { out.print(" ratio1 =\"" + fillStyle.getRatio1()); out.print(" ratio2 =\"" + fillStyle.getRatio2()); } } if ((fillStyle.getFillStyleType() & FillStyle.REPEATING_BITMAP_FILL) != 0) { // todo print tiled or clipped out.print(" idref=\"" + idRef(fillStyle.getBitmap()) + "\""); out.print(" startMatrix=\"" + fillStyle.getStartBitmapMatrix() + "\""); out.print(" endMatrix=\"" + fillStyle.getEndBitmapMatrix() + "\""); } out.println(" />"); } } private String formatGradient(List<GradRecord> records) { StringBuilder b = new StringBuilder(); int i = 0; for (GradRecord record : records) { b.append(record.getRatio()); b.append(' '); b.append(printColor(record.getColor())); if (i + 1 < records.size()) b.append(' '); i++; } return b.toString(); } private String formatMorphGradient(MorphGradRecord[] records) { StringBuilder b = new StringBuilder(); for (int i = 0; i < records.length; i++) { b.append(records[i].getStartRatio()); b.append(','); b.append(records[i].getEndRatio()); b.append(' '); b.append(printColor(records[i].getStartColor())); b.append(','); b.append(printColor(records[i].getEndColor())); if (i + 1 < records.length) b.append(' '); } return b.toString(); } private void printShape(Shape shapes) { if (shapes == null) return; Styles styles = null; if (shapes instanceof ShapeWithStyle) { styles = new Styles(((ShapeWithStyle)shapes).getFillStyles(), ((ShapeWithStyle)shapes).getLineStyles()); } for (ShapeRecord shape : shapes.getShapeRecords()) { indent(); if (shape instanceof StyleChangeRecord) { StyleChangeRecord styleChange = (StyleChangeRecord)shape; out.print("<styleChange "); if (styleChange.isStateMoveTo()) { out.print("dx=\"" + styleChange.getMoveDeltaX() + "\" dy=\"" + styleChange.getMoveDeltaY() + "\" "); } if (styleChange.isStateFillStyle0()) { out.print("fillStyle0=\"" + (styles.getFillStyles().indexOf(styleChange.getFillstyle0()) + 1) + "\" "); } if (styleChange.isStateFillStyle1()) { out.print("fillStyle1=\"" + (styles.getFillStyles().indexOf(styleChange.getFillstyle1()) + 1) + "\" "); } if (styleChange.isStateLineStyle()) { out.print("lineStyle=\"" + (styles.getFillStyles().indexOf(styleChange.getLinestyle()) + 1) + "\" "); } if (styleChange.isStateNewStyles()) { out.println(">"); indent++; printFillStyles(styleChange.getStyles().getFillStyles()); printLineStyles(styleChange.getStyles().getLineStyles()); indent--; indent(); out.println("</styleChange>"); styles = styleChange.getStyles(); } else { out.println("/>"); } } else { EdgeRecord edge = (EdgeRecord)shape; if (edge instanceof StraightEdgeRecord) { StraightEdgeRecord straightEdge = (StraightEdgeRecord)edge; out.println("<line dx=\"" + straightEdge.getDeltaX() + "\" dy=\"" + straightEdge.getDeltaY() + "\" />"); } else { CurvedEdgeRecord curvedEdge = (CurvedEdgeRecord)edge; out.print("<curve "); out.print("cdx=\"" + curvedEdge.getControlDeltaX() + "\" cdy=\"" + curvedEdge.getControlDeltaY() + "\" "); out.print("dx=\"" + curvedEdge.getAnchorDeltaX() + "\" dy=\"" + curvedEdge.getAnchorDeltaY() + "\" "); out.println("/>"); } } } } private void printShapeWithTabs(Shape shapes) { if (shapes == null) return; int startX = 0; int startY = 0; int x = 0; int y = 0; for (ShapeRecord shape : shapes.getShapeRecords()) { indent(); if (shape instanceof StyleChangeRecord) { StyleChangeRecord styleChange = (StyleChangeRecord)shape; // No longer print out number of bits in MoveDeltaX and // MoveDeltaY. The StyleChangeRecord record does not preserve // this information. out.print("SCR\t"); if (styleChange.isStateMoveTo()) { out.print(styleChange.getMoveDeltaX() + "\t" + styleChange.getMoveDeltaY()); if (startX == 0 && startY == 0) { startX = styleChange.getMoveDeltaX(); startY = styleChange.getMoveDeltaY(); } x = styleChange.getMoveDeltaX(); y = styleChange.getMoveDeltaY(); out.print("\t\t"); } } else { EdgeRecord edge = (EdgeRecord)shape; if (edge instanceof StraightEdgeRecord) { StraightEdgeRecord straightEdge = (StraightEdgeRecord)edge; out.print("SER" + "\t"); out.print(straightEdge.getDeltaX() + "\t" + straightEdge.getDeltaY()); x += straightEdge.getDeltaX(); y += straightEdge.getDeltaY(); out.print("\t\t"); } else { CurvedEdgeRecord curvedEdge = (CurvedEdgeRecord)edge; out.print("CER" + "\t"); out.print(curvedEdge.getControlDeltaX() + "\t" + curvedEdge.getControlDeltaY() + "\t"); out.print(curvedEdge.getAnchorDeltaX() + "\t" + curvedEdge.getAnchorDeltaY()); x += (curvedEdge.getControlDeltaX() + curvedEdge.getAnchorDeltaX()); y += (curvedEdge.getControlDeltaY() + curvedEdge.getAnchorDeltaY()); } } out.println("\t\t" + x + "\t" + y); } } // TODO: decode ClipActionRecords // private String printClipEventFlags(int flags) // { // StringBuilder b = new StringBuilder(); // // if ((flags & ClipActionRecord.unused31) != 0) b.append("res31,"); // if ((flags & ClipActionRecord.unused30) != 0) b.append("res30,"); // if ((flags & ClipActionRecord.unused29) != 0) b.append("res29,"); // if ((flags & ClipActionRecord.unused28) != 0) b.append("res28,"); // if ((flags & ClipActionRecord.unused27) != 0) b.append("res27,"); // if ((flags & ClipActionRecord.unused26) != 0) b.append("res26,"); // if ((flags & ClipActionRecord.unused25) != 0) b.append("res25,"); // if ((flags & ClipActionRecord.unused24) != 0) b.append("res24,"); // // if ((flags & ClipActionRecord.unused23) != 0) b.append("res23,"); // if ((flags & ClipActionRecord.unused22) != 0) b.append("res22,"); // if ((flags & ClipActionRecord.unused21) != 0) b.append("res21,"); // if ((flags & ClipActionRecord.unused20) != 0) b.append("res20,"); // if ((flags & ClipActionRecord.unused19) != 0) b.append("res19,"); // if ((flags & ClipActionRecord.construct) != 0) b.append("construct,"); // if ((flags & ClipActionRecord.keyPress) != 0) b.append("keyPress,"); // if ((flags & ClipActionRecord.dragOut) != 0) b.append("dragOut,"); // // if ((flags & ClipActionRecord.dragOver) != 0) b.append("dragOver,"); // if ((flags & ClipActionRecord.rollOut) != 0) b.append("rollOut,"); // if ((flags & ClipActionRecord.rollOver) != 0) b.append("rollOver,"); // if ((flags & ClipActionRecord.releaseOutside) != 0) b.append("releaseOutside,"); // if ((flags & ClipActionRecord.release) != 0) b.append("release,"); // if ((flags & ClipActionRecord.press) != 0) b.append("press,"); // if ((flags & ClipActionRecord.initialize) != 0) b.append("initialize,"); // if ((flags & ClipActionRecord.data) != 0) b.append("data,"); // // if ((flags & ClipActionRecord.keyUp) != 0) b.append("keyUp,"); // if ((flags & ClipActionRecord.keyDown) != 0) b.append("keyDown,"); // if ((flags & ClipActionRecord.mouseUp) != 0) b.append("mouseUp,"); // if ((flags & ClipActionRecord.mouseDown) != 0) b.append("mouseDown,"); // if ((flags & ClipActionRecord.mouseMove) != 0) b.append("moseMove,"); // if ((flags & ClipActionRecord.unload) != 0) b.append("unload,"); // if ((flags & ClipActionRecord.enterFrame) != 0) b.append("enterFrame,"); // if ((flags & ClipActionRecord.load) != 0) b.append("load,"); // if (b.length() > 1) // { // b.setLength(b.length() - 1); // } // return b.toString(); // } public void defineText2(DefineTextTag tag) { open(tag); out.print(" id=\"" + id(tag) + "\""); end(); for (TextRecord tr : tag.getTextRecords()) { printTextRecord(tr, tag.getTagType().getValue()); } close(tag); } public void printTextRecord(TextRecord tr, int tagCode) { indent(); out.print("<textRecord "); if (tr.isStyleFlagsHasFont()) { out.print(" font=\"" + id(tr.getFontTag()) + "\""); out.print(" height=\"" + tr.getTextHeight() + "\""); } if (tr.isStyleFlagsHasXOffset()) out.print(" xOffset=\"" + tr.getxOffset() + "\""); if (tr.isStyleFlagsHasYOffset()) out.print(" yOffset=\"" + tr.getyOffset() + "\""); if (tr.isStyleFlagsHasColor()) out.print(" color=\"" + printColor(tr.getTextColor()) + "\""); out.println(">"); indent++; printGlyphEntries(tr); indent--; indent(); out.println("</textRecord>"); } private void printGlyphEntries(TextRecord tr) { indent(); GlyphEntry[] entries = tr.getGlyphEntries(); for (int i = 0; i < entries.length; i++) { GlyphEntry ge = entries[i]; out.print(ge.getGlyphIndex()); if (ge.getGlyphAdvance() >= 0) out.print('+'); out.print(ge.getGlyphAdvance()); out.print(' '); if ((i + 1) % 10 == 0) { out.println(); indent(); } } if (entries.length % 10 != 0) out.println(); } public void dumpDefineButton2(DefineButton2Tag tag) { open(tag); out.print(" id=\"" + id(tag) + "\""); out.print(" trackAsMenu=\"" + tag.isTrackAsMenu() + "\""); end(); for (int i = 0; i < tag.getCharacters().length; i++) { ButtonRecord record = tag.getCharacters()[i]; indent(); out.println("<buttonRecord " + "idref=\"" + record.getCharacterID() + "\" " + //TODO: print symbol name "depth=\"" + record.getPlaceDepth() + "\" " + "matrix=\"" + record.getPlaceMatrix() + "\" " + "stateHitTest=\"" + record.isStateHitTest() + "\" " + "stateDown=\"" + record.isStateDown() + "\" " + "stateOver=\"" + record.isStateOver() + "\" " + "stateUp=\"" + record.isStateUp() + "\" " + "/>"); // todo print optional cxforma } // print conditional actions // TODO: print actions // if (tag.condActions.length > 0 && showActions) // { // indent(); // out.println("<buttonCondAction>"); // openCDATA(); // for (int i = 0; i < tag.condActions.length; i++) // { // ButtonCondAction cond = tag.condActions[i]; // indent(); // out.println("on(" + cond + ") {"); // indent++; // printActions(cond.actionList); // indent--; // indent(); // out.println("}"); // } // closeCDATA(); // indent(); // out.println("</buttonCondAction>"); // } close(tag); } public void dumpDefineBitsJPEG3(DefineBitsJPEG3Tag tag) { open(tag); out.print(" id=\"" + id(tag) + "\""); // TODO: We don't have a JEB encoder // if (external) // { // String path = externalDirectory // + externalPrefix // + "image" // + id(tag) // + ".jpg"; // // out.println(" src=\"" + path + "\" />"); // // try // { // FileOutputStream image = new FileOutputStream(path, false); // SwfImageUtils.JPEG jpeg = null; // // if (tag.jpegTables != null) // { // jpeg = new SwfImageUtils.JPEG(tag.jpegTables.data, tag.data); // } // else // { // jpeg = new SwfImageUtils.JPEG(tag.data, true); // } // // jpeg.write(image); // image.close(); // } // catch (IOException e) // { // out.println("<!-- error: unable to write external asset file " + path + "-->"); // } // } // else { out.print(" encoding=\"base64\""); end(); outputBase64(tag.getData()); close(tag); } } public void dumpDefineBitsLossless2(DefineBitsLossless2Tag tag) { open(tag); out.print(" id=\"" + id(tag) + "\""); if (external) { String path = externalDirectory + externalPrefix + "image" + id(tag) + ".bitmap"; out.println(" src=\"" + path + "\" />"); try { FileOutputStream image = new FileOutputStream(path, false); image.write(tag.getData()); image.close(); } catch (IOException e) { out.println("<!-- error: unable to write external asset file " + path + "-->"); } } else { out.print(" encoding=\"base64\""); end(); outputBase64(tag.getData()); close(tag); } } String escape(String s) { if (s == null) return null; StringBuilder b = new StringBuilder(s.length()); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); switch (c) { case '<': b.append("<"); break; case '>': b.append(">"); break; case '&': b.append("&"); break; case '"': b.append("""); break; default: { // Non-printable low ASCII characters are invalid in XML if ((c >= 0 && c <= 8) || (c >= 11 && c <= 12) || (c >= 14 && c <= 31)) { b.append("?"); } else { b.append(c); } } } } return b.toString(); } public void dumpDefineEditText(DefineEditTextTag tag) { open(tag); out.print(" id=\"" + id(tag) + "\""); if (tag.isHasFont()) { out.print(" fontId=\"" + id(tag.getFontTag()) + "\""); out.print(" fontName=\"" + escape(idRef(tag.getFontTag())) + "\""); out.print(" fontHeight=\"" + tag.getFontHeight() + "\""); } else if (tag.isHasFontClass()) { out.print(" fontClass=\"" + tag.getFontClass() + "\""); out.print(" fontHeight=\"" + tag.getFontHeight() + "\""); } out.print(" bounds=\"" + tag.getBounds() + "\""); if (tag.isHasTextColor()) out.print(" color=\"" + printColor(tag.getTextColor()) + "\""); out.print(" html=\"" + tag.isHtml() + "\""); out.print(" autoSize=\"" + tag.isAutoSize() + "\""); out.print(" border=\"" + tag.isBorder() + "\""); if (tag.isHasMaxLength()) out.print(" maxLength=\"" + tag.getMaxLength() + "\""); out.print(" multiline=\"" + tag.isMultiline() + "\""); out.print(" noSelect=\"" + tag.isNoSelect() + "\""); out.print(" password=\"" + tag.isPassword() + "\""); out.print(" readOnly=\"" + tag.isReadOnly() + "\""); out.print(" useOutlines=\"" + tag.isUseOutlines() + "\""); out.print(" varName=\"" + escape(tag.getVariableName()) + "\""); out.print(" wordWrap=\"" + tag.isWordWrap() + "\""); if (tag.isHasLayout()) { out.print(" align=\"" + tag.getAlign() + "\""); out.print(" indent=\"" + tag.getIndent() + "\""); out.print(" leading=\"" + tag.getLeading() + "\""); out.print(" leftMargin=\"" + tag.getLeftMargin() + "\""); out.print(" rightMargin=\"" + tag.getRightMargin() + "\""); } end(); if (tag.isHasText()) { indent(); out.println("<text>"); openCDATA(); out.print(tag.getInitialText()); closeCDATA(); indent(); out.println("</text>"); } close(tag); } public void dumpDefineSprite(DefineSpriteTag tag) { open(tag); out.print(" id=\"" + id(tag) + "\""); end(); indent(); out.println("<!-- sprite framecount=" + tag.getFrameCount() + " -->"); // Dump list of nested tags in the sprite. for (ITag controlTag : tag.getControlTags()) dumpTag(controlTag); close(tag); } public void finish() { --indent; indent(); out.println("</swf>"); } public void dumpFrameLabel(FrameLabelTag tag) { open(tag); out.print(" label=\"" + tag.getName() + "\""); if (tag.isNamedAnchorTag()) out.print(" anchor=\"" + "true" + "\""); close(); } public void dumpSoundStreamHead2(SoundStreamHeadTag tag) { open(tag); out.print(" playbackRate=\"" + tag.getPlaybackSoundRate() + "\""); out.print(" playbackSize=\"" + tag.getPlaybackSoundSize() + "\""); out.print(" playbackType=\"" + tag.getPlaybackSoundType() + "\""); out.print(" compression=\"" + tag.getStreamSoundCompression() + "\""); out.print(" streamRate=\"" + tag.getStreamSoundRate() + "\""); out.print(" streamSize=\"" + tag.getStreamSoundSize() + "\""); out.print(" streamType=\"" + tag.getStreamSoundRate() + "\""); out.print(" streamSampleCount=\"" + tag.getStreamSoundSampleCount() + "\""); if (tag.getStreamSoundCompression() == 2) { out.print(" latencySeek=\"" + tag.getLatencySeek() + "\""); } close(); } public void dumpDefineScalingGrid(DefineScalingGridTag tag) { open(tag); out.print(" idref=\"" + id(tag.getCharacter()) + "\""); out.print(" grid=\"" + tag.getSplitter() + "\""); close(); } public void dumpDefineMorphShape(DefineMorphShapeTag tag) { dumpDefineMorphShape2(tag); } public void dumpDefineMorphShape2(DefineMorphShapeTag tag) { open(tag); out.print(" id=\"" + id(tag) + "\""); out.print(" startBounds=\"" + tag.getStartBounds() + "\""); out.print(" endBounds=\"" + tag.getEndBounds() + "\""); if (tag.getTagType() == TagType.DefineMorphShape2) { DefineMorphShape2Tag tag2 = (DefineMorphShape2Tag)tag; out.print(" startEdgeBounds=\"" + tag2.getStartEdgeBounds() + "\""); out.print(" endEdgeBounds=\"" + tag2.getEndEdgeBounds() + "\""); out.print(" usesNonScalingStrokes=\"" + tag2.isUsesNonScalingStrokes() + "\""); out.print(" usesScalingStrokes=\"" + tag2.isUsesScalingStrokes() + "\""); } end(); // TODO: dump line styles and fill styles // printMorphLineStyles(tag.lineStyles); // printMorphFillStyles(tag.fillStyles); indent(); out.println("<start>"); indent++; printShape(tag.getStartEdges()); indent--; indent(); out.println("</start>"); indent(); out.println("<end>"); indent++; printShape(tag.getEndEdges()); indent--; indent(); out.println("</end>"); close(tag); } public void dumpDefineFont2(DefineFont2Tag tag) { open(tag); out.print(" id=\"" + id(tag) + "\""); out.print(" font=\"" + escape(tag.getFontName()) + "\""); out.print(" numGlyphs=\"" + tag.getNumGlyphs() + "\""); out.print(" italic=\"" + tag.isFontFlagsItalic() + "\""); out.print(" bold=\"" + tag.isFontFlagsBold() + "\""); out.print(" ansi=\"" + tag.isFontFlagsANSI() + "\""); out.print(" wideOffsets=\"" + tag.isFontFlagsWideCodes() + "\""); out.print(" wideCodes=\"" + tag.isFontFlagsWideCodes() + "\""); out.print(" shiftJIS=\"" + tag.isFontFlagsShiftJIS() + "\""); out.print(" langCode=\"" + tag.getLanguageCode() + "\""); out.print(" hasLayout=\"" + tag.isFontFlagsHasLayout() + "\""); out.print(" ascent=\"" + tag.getFontAscent() + "\""); out.print(" descent=\"" + tag.getFontDescent() + "\""); out.print(" leading=\"" + tag.getFontLeading() + "\""); out.print(" kerningCount=\"" + tag.getKerningCount() + "\""); out.print(" codepointCount=\"" + tag.getCodeTable().length + "\""); if (tag.isFontFlagsHasLayout()) { out.print(" advanceCount=\"" + tag.getFontAdvanceTable().length + "\""); out.print(" boundsCount=\"" + tag.getFontBoundsTable().length + "\""); } end(); if (glyphs && tag.isFontFlagsHasLayout()) { for (int i = 0; i < tag.getFontKerningTable().length; i++) { KerningRecord rec = tag.getFontKerningTable()[i]; indent(); out.println("<kerningRecord adjustment=\"" + rec.getAdjustment() + "\" code1=\"" + rec.getCode1() + "\" code2=\"" + rec.getCode2() + "\" />"); } for (int i = 0; i < tag.getGlyphShapeTable().length; i++) { indent(); out.print("<glyph"); out.print(" codepoint=\"" + (tag.getCodeTable()[i]) + (isPrintable((char)tag.getCodeTable()[i]) ? escape(("(" + (char)tag.getCodeTable()[i] + ")")) : "(?)") + "\""); if (tag.isFontFlagsHasLayout()) { out.print(" advance=\"" + tag.getFontAdvanceTable()[i] + "\""); out.print(" bounds=\"" + tag.getFontBoundsTable()[i] + "\""); } out.println(">"); Shape shape = tag.getGlyphShapeTable()[i]; indent++; if (tabbedGlyphs) printShapeWithTabs(shape); else printShape(shape); indent--; indent(); out.println("</glyph>"); } } close(tag); } public void dumpDefineFont3(DefineFont3Tag tag) { dumpDefineFont2(tag); } public void dumpDefineFont4(DefineFont4Tag tag) { open(tag); out.print(" id=\"" + id(tag) + "\""); out.print(" font=\"" + escape(tag.getFontName()) + "\""); out.print(" hasFontData=\"" + tag.isFontFlagsHasFontData() + "\""); out.print(" italic=\"" + tag.isFontFlagsItalic() + "\""); out.print(" bold=\"" + tag.isFontFlagsBold() + "\""); end(); if (glyphs && tag.isFontFlagsHasFontData()) { outputBase64(tag.getFontData()); } close(tag); } public void dumpDefineFontAlignZones(DefineFontAlignZonesTag tag) { open(tag); out.print(" fontID=\"" + id(tag.getFontTag()) + "\""); out.print(" CSMTableHint=\"" + tag.getCsmTableHint() + "\""); out.println(">"); indent++; indent(); out.println("<ZoneTable length=\"" + tag.getZoneTable().length + "\">"); indent++; if (glyphs) { for (int i = 0; i < tag.getZoneTable().length; i++) { ZoneRecord record = tag.getZoneTable()[i]; indent(); out.print("<ZoneRecord num=\"" + record.getNumZoneData() + "\" maskX=\"" + record.isZoneMaskX() + "\" maskY=\"" + record.isZoneMaskY() + "\">"); out.print(record.getZoneData0().getData() + " " + record.getZoneData1().getData() + " "); out.println("</ZoneRecord>"); } } indent--; indent(); out.println("</ZoneTable>"); close(tag); } public void dumpCSMTextSettings(CSMTextSettingsTag tag) { open(tag); String textID = tag.getTextTag() == null ? "0" : id(tag.getTextTag()); out.print(" textID=\"" + textID + "\""); out.print(" useFlashType=\"" + tag.getUseFlashType() + "\""); out.print(" gridFitType=\"" + tag.getGridFit() + "\""); out.print(" thickness=\"" + tag.getThickness() + "\""); out.print(" sharpness=\"" + tag.getSharpness() + "\""); close(); } public void dumpDefineFontName(DefineFontNameTag tag) { open(tag); out.print(" fontID=\"" + id(tag.getFontTag()) + "\""); if (tag.getFontName() != null) { out.print(" name=\"" + tag.getFontName() + "\""); } if (tag.getFontCopyright() != null) { out.print(" copyright=\"" + escape(tag.getFontCopyright()) + "\""); } close(); } private boolean isPrintable(char c) { int i = c & 0xFFFF; if (i < ' ' || i == '<' || i == '&' || i == '\'') return false; else return true; } public void dumpExportAssets(ExportAssetsTag tag) { open(tag); end(); for (String name : tag.getCharacterNames()) { indent(); out.println("<Export idref=\"" + tag.getCharacterTagByName(name).getCharacterID() + "\" name=\"" + name + "\" />"); } close(tag); } public void dumpSymbolClass(SymbolClassTag tag) { open(tag); end(); for (String symbolName : tag.getSymbolNames()) { indent(); out.println("<Symbol idref=\"" + tag.getSymbol(symbolName).getCharacterID() + "\" className=\"" + symbolName + "\" />"); } if (currentFrameIndex == 0 && swf.getTopLevelClass() != null) { indent(); out.println("<Symbol idref=\"0\" className=\"" + swf.getTopLevelClass() + "\" />"); } close(tag); } // TODO: handle ImportAssets tag. // public void dumpImportAssets(ImportAssets tag) // { // open(tag); // out.print(" url=\"" + tag.url + "\""); // end(); // // Iterator it = tag.importRecords.iterator(); // while (it.hasNext()) // { // ImportRecord record = (ImportRecord)it.next(); // indent(); // out.println("<Import name=\"" + record.name + "\" id=\"" + dictionary.getId(record) + "\" />"); // } // // close(tag); // } // // public void dumpImportAssets2(ImportAssets tag) // { // // TODO: add support for tag.downloadNow and SHA1... // importAssets(tag); // } public void dumpEnableDebugger(EnableDebugger2Tag tag) { open(tag); out.print(" password=\"" + tag.getPassword() + "\""); close(); } // TODO Decode actions // public void dumpDoInitAction(DoInitAction tag) // { // if (tag.sprite != null && tag.sprite.name != null) // { // indent(); // out.println("<!-- init " + tag.sprite.name + " " + dictionary.getId(tag.sprite) + " -->"); // } // // open(tag); // if (tag.sprite != null) // out.print(" idref=\"" + idRef(tag.sprite) + "\""); // end(); // // if (showActions) // { // openCDATA(); // printActions(tag.actionList); // closeCDATA(); // } // else // { // indent(); // out.println("<!-- " + tag.actionList.size() + " action(s) elided -->"); // } // close(tag); // } public void dumpEnableTelemetry(EnableTelemetryTag tag) { open(tag); out.print(" password=\"" + tag.getPassword() + "\""); close(); } private String idRef(ICharacterTag tag) { if (tag == null) { // if tag is null then it isn't in the dict -- the SWF is invalid. // lets be lax and print something; Matador generates invalid SWF sometimes. return "-1"; } else if (currentFrame == null || currentFrame.getSymbolName(tag) == null) { // just print the character id since no name was exported return String.valueOf(id(tag)); } else { return currentFrame.getSymbolName(tag); } } public void dumpDefineVideoStream(DefineVideoStreamTag tag) { open(tag); out.print(" id=\"" + id(tag) + "\""); close(); } public void dumpVideoFrame(VideoFrameTag tag) { open(tag); out.print(" streamId=\"" + idRef(tag.getStreamTag()) + "\""); out.print(" frame=\"" + tag.getFrameNum() + "\""); close(); } public void dumpDefineFontInfo2(DefineFontInfoTag tag) { dumpDefineFontInfo(tag); } public void dumpEnableDebugger2(EnableDebugger2Tag tag) { open(tag); out.print(" password=\"" + tag.getPassword() + "\""); out.print(" reserved=\"0x" + Integer.toHexString(EnableDebugger2Tag.RESERVED_FIELD_VALUE) + "\""); close(); } // TODO: handle DebugID tag // public void dumpDebugID(DebugID tag) // { // open(tag); // out.print(" uuid=\"" + tag.uuid + "\""); // close(); // } public void dumpScriptLimits(ScriptLimitsTag tag) { open(tag); out.print(" scriptRecursionLimit=\"" + tag.getMaxRecursionDepth() + "\"" + " scriptTimeLimit=\"" + tag.getScriptTimeoutSeconds() + "\""); close(); } public void dumpSetTabIndex(SetTabIndexTag tag) { open(tag); out.print(" depth=\"" + tag.getDepth() + "\""); out.print(" index=\"" + tag.getTabIndex() + "\""); close(); } public void dumpDoABC(DoABCTag tag) { if (abc) { open(tag); end(); ABCParser parser = new ABCParser(tag.getABCData()); PoolingABCVisitor printer = new ABCDumpVisitor(out, sortOption); parser.parseABC(printer); close(tag); } else { open(tag); if (tag.getTagType() == TagType.DoABC) out.print(" name=\"" + tag.getName() + "\""); close(); } } @SuppressWarnings("unused") private String hexify(byte[] id) { StringBuilder b = new StringBuilder(id.length * 2); for (int i = 0; i < id.length; i++) { b.append(Character.forDigit((id[i] >> 4) & 15, 16)); b.append(Character.forDigit(id[i] & 15, 16)); } return b.toString().toUpperCase(); } public static String baseName(String path) { int start = path.lastIndexOf(File.separatorChar); if (File.separatorChar != '/') { // some of us are grouchy about unix paths not being // parsed since they are totally legit at the system // level of win32. int altstart = path.lastIndexOf('/'); if ((start == -1) || (altstart > start)) start = altstart; } if (start == -1) start = 0; else ++start; int end = path.lastIndexOf('.'); if (end == -1) end = path.length(); if (start > end) end = path.length(); return path.substring(start, end); } public static String dirName(String path) { int end = path.lastIndexOf(File.pathSeparatorChar); if (File.pathSeparatorChar != '/') { // some of us are grouchy about unix paths not being // parsed since they are totally legit at the system // level of win32. int altend = path.lastIndexOf('/'); if ((end == -1) || (altend < end)) end = altend; } if (end == -1) return ""; else ++end; return path.substring(0, end); } // options static boolean abcOption = false; static boolean encodeOption = false; static boolean showActionsOption = true; static boolean showOffsetOption = false; static boolean showByteCodeOption = false; static boolean showDebugSourceOption = false; static boolean glyphsOption = true; static boolean externalOption = false; static boolean decompileOption = true; static boolean defuncOption = true; static boolean saveOption = false; static boolean sortOption = false; static boolean tabbedGlyphsOption = true; static boolean uncompressOption = false; /** * SWFDump will dump a SWF file as XML. * * Usage: swfdump [-abc] file1.swf */ public static void main(String[] args) throws IOException { // This message should not be localized. System.err.println("Apache Flex SWF Dump Utility"); System.err.println(VersionInfo.buildMessage()); System.err.println(""); if (args.length == 0) { // TODO: decide which options to implement. // System.err.println("Usage: swfdump [-encode] [-asm] [-abc] [-showbytecode] [-showdebugsource] [-showoffset] [-noglyphs] [-save file.swf] [-nofunctions] [-out file.swfx] file1.swf ..."); System.err.println("Usage: swfdump [-abc] file1.swf"); System.exit(1); } int index = 0; PrintWriter out = null; String outfile = null; while ((index < args.length) && (args[index].startsWith("-"))) { if (args[index].equals("-encode")) { encodeOption = true; ++index; } else if (args[index].equals("-save")) { ++index; saveOption = true; outfile = args[index++]; } else if (args[index].equals("-sort")) { // Try to sort output by alpha-order of identifiers // so compare of two dumps compare better. // There is some randomness in the order of output // of some scripts ++index; sortOption = true; } else if (args[index].equals("-uncompress")) { ++index; uncompressOption = true; outfile = args[index++]; } else if (args[index].equals("-decompile")) { decompileOption = true; ++index; } else if (args[index].equals("-nofunctions")) { defuncOption = false; ++index; } else if (args[index].equals("-asm")) { decompileOption = false; ++index; } else if (args[index].equals("-abc")) { abcOption = true; ++index; } else if (args[index].equals("-noactions")) { showActionsOption = false; ++index; } else if (args[index].equals("-showoffset")) { showOffsetOption = true; ++index; } else if (args[index].equals("-showbytecode")) { showByteCodeOption = true; ++index; } else if (args[index].equals("-showdebugsource")) { showDebugSourceOption = true; ++index; } else if (args[index].equals("-noglyphs")) { glyphsOption = false; ++index; } else if (args[index].equals("-out")) { if (index + 1 == args.length) { System.err.println("-out requires a filename or - for stdout"); System.exit(1); } if (!args[index + 1].equals("-")) { outfile = args[index + 1]; out = new PrintWriter(outfile, "UTF-8"); } index += 2; } else if (args[index].equals("-external")) { externalOption = true; ++index; } else if (args[index].equalsIgnoreCase("-tabbedGlyphs")) { tabbedGlyphsOption = true; ++index; } else { System.err.println("unknown argument " + args[index]); ++index; } } if (out == null) out = new PrintWriter(new OutputStreamWriter(System.out, "UTF-8"), true); File f = new File(args[index]); URL[] urls = new URL[0]; File currentFile = f; try { if (!f.exists()) { urls = new URL[] {new URL(args[index])}; } else { if (f.isDirectory()) { File[] list = listFiles(f); urls = new URL[list.length]; for (int i = 0; i < list.length; i++) { currentFile = list[i]; urls[i] = toURL(list[i]); } } else { urls = new URL[] {toURL(f)}; } } } catch (MalformedURLException e) { System.err.println("Unable to open " + currentFile); } for (int i = 0; i < urls.length; i++) { try { URL url = urls[i]; if (saveOption) { InputStream in = new BufferedInputStream(url.openStream()); try { OutputStream fileOut = new BufferedOutputStream(new FileOutputStream(outfile)); try { int c; while ((c = in.read()) != -1) { fileOut.write(c); } } finally { fileOut.close(); } } finally { in.close(); } } if (uncompressOption) { final SWFReader swfReader = new SWFReader(); final String path = url.getPath(); try { SWF swf = (SWF)swfReader.readFrom( new BufferedInputStream(url.openStream()), path); ProblemQuery problemQuery = new ProblemQuery(); problemQuery.addAll(swfReader.getProblems()); if (!problemQuery.hasErrors()) { OutputStream fileOut = new BufferedOutputStream(new FileOutputStream(outfile)); SWFWriter swfWriter = new SWFWriter(swf, Header.Compression.NONE); swfWriter.writeTo(fileOut); swfWriter.close(); } } finally { IOUtils.closeQuietly(swfReader); } } if (!uncompressOption) dumpSwf(out, url, outfile); out.flush(); } catch (Error e) { if (Trace.error) e.printStackTrace(); System.err.println(""); System.err.println("An unrecoverable error occurred. The given file " + urls[i] + " may not be"); System.err.println("a valid swf."); } catch (FileNotFoundException e) { System.err.println("Error: " + e.getMessage()); System.exit(1); } } } public static void dumpSwf(PrintWriter out, URL url, String outfile) throws IOException { out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); if (!sortOption) out.println("<!-- Parsing swf " + url + " -->"); SWFDump swfDump = new SWFDump(out); // TODO: Disable options // swfDump.showActions = showActionsOption; // swfDump.showOffset = showOffsetOption; // swfDump.showByteCode = showByteCodeOption; // swfDump.showDebugSource = showDebugSourceOption; // swfDump.glyphs = glyphsOption; // swfDump.setExternal(externalOption, outfile); // swfDump.decompile = decompileOption; swfDump.abc = abcOption; // swfDump.defunc = defuncOption; // swfDump.tabbedGlyphs = tabbedGlyphsOption; swfDump.dump(url); } public static URL toURL(File f) throws MalformedURLException { String s = f.getAbsolutePath(); if (File.separatorChar != '/') { s = s.replace(File.separatorChar, '/'); } if (!s.startsWith("/")) { s = "/" + s; } if (!s.endsWith("/") && f.isDirectory()) { s = s + "/"; } return new URL("file", "", s); } public static File[] listFiles(File dir) { String[] fileNames = dir.list(); if (fileNames == null) { return null; } File[] fileList = new File[fileNames.length]; for (int i = 0; i < fileNames.length; i++) { fileList[i] = new File(dir.getPath(), fileNames[i]); } return fileList; } }