//////////////////////////////////////////////////////////////////////////////// // // ADOBE SYSTEMS INCORPORATED // Copyright 2003-2007 Adobe Systems Incorporated // All Rights Reserved. // // NOTICE: Adobe permits you to use, modify, and distribute this file // in accordance with the terms of the license agreement accompanying it. // //////////////////////////////////////////////////////////////////////////////// package flash.swf.tools; import macromedia.abc.AbcParser; import macromedia.asc.embedding.CompilerHandler; import macromedia.asc.embedding.avmplus.ActionBlockEmitter; import macromedia.asc.parser.ProgramNode; import macromedia.asc.semantics.ObjectValue; import macromedia.asc.semantics.TypeValue; import macromedia.asc.util.Context; import macromedia.asc.util.ContextStatics; import macromedia.asc.util.StringPrintWriter; import flash.swf.ActionDecoder; import flash.swf.Dictionary; import flash.swf.Header; import flash.swf.SwfDecoder; import flash.swf.Tag; import flash.swf.TagDecoder; import flash.swf.TagEncoder; import flash.swf.TagHandler; import flash.swf.TagValues; import flash.swf.tags.*; import flash.swf.types.ActionList; import flash.swf.types.ButtonCondAction; import flash.swf.types.ButtonRecord; import flash.swf.types.ClipActionRecord; import flash.swf.types.CurvedEdgeRecord; import flash.swf.types.EdgeRecord; import flash.swf.types.FillStyle; import flash.swf.types.Filter; import flash.swf.types.GlyphEntry; import flash.swf.types.GradRecord; import flash.swf.types.ImportRecord; import flash.swf.types.LineStyle; import flash.swf.types.MorphFillStyle; import flash.swf.types.MorphGradRecord; import flash.swf.types.MorphLineStyle; import flash.swf.types.Shape; import flash.swf.types.ShapeRecord; import flash.swf.types.ShapeWithStyle; import flash.swf.types.SoundInfo; import flash.swf.types.StraightEdgeRecord; import flash.swf.types.StyleChangeRecord; import flash.swf.types.TextRecord; import flash.swf.types.FocalGradient; import flash.swf.types.KerningRecord; import flash.util.Base64; import flash.util.FileUtils; import flash.util.SwfImageUtils; import flash.util.Trace; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.DataInputStream; 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.PrintWriter; import java.io.StringWriter; import java.net.URL; import java.net.URLConnection; import java.text.DateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** * @author Clement Wong * @author Edwin Smith */ public final class SwfxPrinter extends TagHandler { /** * this value should get set after the header is parsed */ private Integer swfVersion = null; private boolean abc = false; private boolean showActions = true; private boolean showOffset = false; private boolean showDebugSource = false; private boolean glyphs = true; private boolean external = false; private String externalPrefix = null; private String externalDirectory = null; private boolean decompile; private boolean defunc; private int indent = 0; private boolean tabbedGlyphs = false; static { TypeValue.init(); ObjectValue.init(); } public SwfxPrinter(PrintWriter out) { this.out = out; } 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); } 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 header(Header h) { swfVersion = new Integer(h.version); out.println(""); out.println(""); indent++; indent(); out.println(""); } public void productInfo(ProductInfo productInfo) { open(productInfo); out.print(" product='" + productInfo.getProductString() + "'"); out.print(" edition='" + productInfo.getEditionString() + "'"); 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 metadata(Metadata tag) { open(tag); end(); indent(); out.println(tag.xml); close(tag); } public void fileAttributes(FileAttributes tag) { open(tag); out.print(" hasMetadata='" + tag.hasMetadata + "'"); out.print(" actionScript3='" + tag.actionScript3 + "'"); out.print(" suppressCrossDomainCaching='" + tag.suppressCrossDomainCaching + "'"); out.print(" swfRelativeUrls='" + tag.swfRelativeUrls + "'"); out.print(" useNetwork='" + tag.useNetwork + "'"); close(); } private final PrintWriter out; private Dictionary dict; public void setDecoderDictionary(Dictionary dict) { this.dict = dict; } 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(""); } } private void open(Tag tag) { indent(); out.print("<" + TagValues.names[tag.code]); } private void end() { out.println(">"); indent++; } private void openCDATA() { indent(); out.println(""); } private void close() { out.println("/>"); } private void close(Tag tag) { indent--; indent(); out.println(""); } public void error(String s) { indent(); out.println(""); } public void unknown(GenericTag tag) { indent(); out.println(""); } public void showFrame(ShowFrame tag) { open(tag); close(); } public void defineShape(DefineShape tag) { printDefineShape(tag, false); } private void printDefineShape(DefineShape tag, boolean alpha) { open(tag); out.print(" id='" + id(tag) + "'"); out.print(" bounds='" + tag.bounds + "'"); if (tag.code == Tag.stagDefineShape6) { out.print(" edgebounds='" + tag.edgeBounds + "'"); out.print(" usesNonScalingStrokes='" + tag.usesNonScalingStrokes + "'"); out.print(" usesScalingStrokes='" + tag.usesScalingStrokes + "'"); } end(); printShapeWithStyles(tag.shapeWithStyle, alpha); close(tag); } private String id(DefineTag tag) { final int id = dict.getId(tag); return String.valueOf(id); } static final char[] digits = new char[]{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; /** * @param rgb as an integer, 0x00RRGGBB * @return string formatted as #RRGGBB */ public String printRGB(int rgb) { StringBuffer b = new StringBuffer(); b.append('#'); int red = (rgb >> 16) & 255; b.append(digits[(red >> 4) & 15]); b.append(digits[red & 15]); int green = (rgb >> 8) & 255; b.append(digits[(green >> 4) & 15]); b.append(digits[green & 15]); int blue = rgb & 255; b.append(digits[(blue >> 4) & 15]); b.append(digits[blue & 15]); return b.toString(); } /** * @param rgb as an integer, 0xAARRGGBB * @return string formatted as #RRGGBBAA */ public String printRGBA(int rgb) { StringBuffer b = new StringBuffer(); b.append('#'); int red = (rgb >> 16) & 255; b.append(digits[(red >> 4) & 15]); b.append(digits[red & 15]); int green = (rgb >> 8) & 255; b.append(digits[(green >> 4) & 15]); b.append(digits[green & 15]); int blue = rgb & 255; b.append(digits[(blue >> 4) & 15]); b.append(digits[blue & 15]); int alpha = (rgb >> 24) & 255; b.append(digits[(alpha >> 4) & 15]); b.append(digits[alpha & 15]); return b.toString(); } public void placeObject(PlaceObject tag) { open(tag); out.print(" idref='" + idRef(tag.ref) + "'"); out.print(" depth='" + tag.depth + "'"); out.print(" matrix='" + tag.matrix + "'"); if (tag.colorTransform != null) out.print(" colorXform='" + tag.colorTransform + "'"); close(); } public void removeObject(RemoveObject tag) { open(tag); out.print(" idref='" + idRef(tag.ref) + "'"); 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()); } //private byte[] jpegTable = null; public void defineBits(DefineBits tag) { if (tag.jpegTables == null) { out.println(""); } open(tag); out.print(" id='" + id(tag) + "'"); if (external) { String path = externalDirectory + externalPrefix + "image" + dict.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(""); } } else { out.print(" encoding='base64'"); end(); outputBase64(tag.data); close(tag); } } public void defineButton(DefineButton tag) { open(tag); out.print(" id='" + id(tag) + "'"); end(); if (showActions) { openCDATA(); // todo print button records printActions(tag.condActions[0].actionList); closeCDATA(); } else { out.println(""); } close(tag); } public void jpegTables(GenericTag tag) { open(tag); out.print(" encoding='base64'"); end(); outputBase64(tag.data); close(tag); } public void setBackgroundColor(SetBackgroundColor tag) { open(tag); out.print(" color='" + printRGB(tag.color) + "'"); close(); } public void defineFont(DefineFont1 tag) { open(tag); out.print(" id='" + id(tag) + "'"); end(); if (glyphs) { for (int i = 0; i < tag.glyphShapeTable.length; i++) { indent(); out.println(""); Shape shape = tag.glyphShapeTable[i]; indent++; printShapeWithTabs(shape); indent--; indent(); out.println(""); } } close(tag); } public void defineText(DefineText tag) { open(tag); out.print(" id='" + id(tag) + "'"); out.print(" bounds='" + tag.bounds + "'"); out.print(" matrix='" + tag.matrix + "'"); end(); Iterator it = tag.records.iterator(); while (it.hasNext()) { TextRecord tr = (TextRecord)it.next(); printTextRecord(tr, tag.code); } close(tag); } public void doAction(DoAction tag) { open(tag); end(); if (showActions) { openCDATA(); printActions(tag.actionList); closeCDATA(); } else { out.println(""); } close(tag); } public void defineFontInfo(DefineFontInfo tag) { open(tag); out.print(" idref='" + idRef(tag.font) + "'"); out.print(" ansi='" + tag.ansi + "'"); out.print(" italic='" + tag.italic + "'"); out.print(" bold='" + tag.bold + "'"); out.print(" wideCodes='" + tag.wideCodes + "'"); out.print(" langCold='" + tag.langCode + "'"); out.print(" name='" + tag.name + "'"); out.print(" shiftJIS='" + tag.shiftJIS + "'"); end(); indent(); for (int i = 0; i < tag.codeTable.length; i++) { out.print((int)tag.codeTable[i]); if ((i + 1) % 16 == 0) { out.println(); indent(); } else { out.print(' '); } } if (tag.codeTable.length % 16 != 0) { out.println(); indent(); } close(tag); } public void defineSound(DefineSound tag) { open(tag); out.print(" id='" + id(tag) + "'"); out.print(" format='" + tag.format + "'"); out.print(" rate='" + tag.rate + "'"); out.print(" size='" + tag.size + "'"); out.print(" type='" + tag.type + "'"); out.print(" sampleCount='" + tag.sampleCount + "'"); out.print(" soundDataSize='" + tag.data.length + "'"); end(); openCDATA(); outputBase64(tag.data); closeCDATA(); close(tag); } public void startSound(StartSound tag) { open(tag); out.print(" soundid='" + idRef(tag.sound) + "'"); printSoundInfo(tag.soundInfo); close(tag); } private void printSoundInfo(SoundInfo info) { out.print(" syncStop='" + info.syncStop + "'"); out.print(" syncNoMultiple='" + info.syncNoMultiple + "'"); if (info.inPoint != SoundInfo.UNINITIALIZED) { out.print(" inPoint='" + info.inPoint + "'"); } if (info.outPoint != SoundInfo.UNINITIALIZED) { out.print(" outPoint='" + info.outPoint + "'"); } if (info.loopCount != SoundInfo.UNINITIALIZED) { out.print(" loopCount='" + info.loopCount + "'"); } end(); if (info.records != null && info.records.length > 0) { openCDATA(); for (int i = 0; i < info.records.length; i++) { out.println(info.records[i]); } closeCDATA(); } } public void defineButtonSound(DefineButtonSound tag) { open(tag); out.print(" buttonId='" + idRef(tag.button) + "'"); close(); } public void soundStreamHead(SoundStreamHead tag) { open(tag); close(); } public void soundStreamBlock(GenericTag tag) { open(tag); close(); } public void defineBinaryData(DefineBinaryData tag) { open(tag); out.println(" id='" + id(tag) + "' length='" + tag.data.length + "' />" ); } public void defineBitsLossless(DefineBitsLossless tag) { open(tag); out.print(" id='" + id(tag) + "' width='" + tag.width + "' height='" + tag.height + "'"); if (external) { String path = externalDirectory + externalPrefix + "image" + dict.getId(tag) + ".bitmap"; out.println(" src='" + path + "' />"); try { FileOutputStream image = new FileOutputStream(path, false); image.write(tag.data); image.close(); } catch (IOException e) { out.println(""); } } else { out.print(" encoding='base64'"); end(); outputBase64(tag.data); close(tag); } } public void defineBitsJPEG2(DefineBits tag) { open(tag); out.print(" id='" + id(tag) + "'"); if (external) { String path = externalDirectory + externalPrefix + "image" + dict.getId(tag) + ".jpg"; out.println(" src='" + path + "' />"); try { FileOutputStream image = new FileOutputStream(path, false); image.write(tag.data); image.close(); } catch (IOException e) { out.println(""); } } else { out.print(" encoding='base64'"); end(); outputBase64(tag.data); close(tag); } } public void defineShape2(DefineShape tag) { printDefineShape(tag, false); } public void defineButtonCxform(DefineButtonCxform tag) { open(tag); out.print(" buttonId='" + idRef(tag.button) + "'"); close(); } public void protect(GenericTag tag) { open(tag); if (tag.data != null) out.print(" password='" + hexify(tag.data) + "'"); close(); } public void placeObject2(PlaceObject tag) { placeObject23(tag); } public void placeObject3(PlaceObject tag) { placeObject23(tag); } public void placeObject23(PlaceObject tag) { if (tag.hasCharID()) { if (tag.ref.name != null) { indent(); out.println(""); } } open(tag); if (tag.hasClassName()) out.print(" className='" + tag.className + "'"); if (tag.hasImage()) out.print(" hasImage='true' "); if (tag.hasCharID()) out.print(" idref='" + idRef(tag.ref) + "'"); if (tag.hasName()) out.print(" name='" + tag.name + "'"); out.print(" depth='" + tag.depth + "'"); if (tag.hasClipDepth()) out.print(" clipDepth='" + tag.clipDepth + "'"); if (tag.hasRatio()) out.print(" ratio='" + tag.ratio + "'"); if (tag.hasCxform()) out.print(" cxform='" + tag.colorTransform + "'"); if (tag.hasMatrix()) out.print(" matrix='" + tag.matrix + "'"); if (tag.hasBlendMode()) out.print(" blendmode='" + tag.blendMode + "'"); if (tag.hasFilterList()) { // todo - pretty print this once we actually care out.print(" filters='"); for (Iterator it = tag.filters.iterator(); it.hasNext();) { out.print( (((Filter) it.next()).getID() ) + " "); } out.print("'"); } if (tag.hasClipAction()) { end(); Iterator it = tag.clipActions.clipActionRecords.iterator(); openCDATA(); while (it.hasNext()) { 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 removeObject2(RemoveObject tag) { open(tag); out.print(" depth='" + tag.depth + "'"); close(); } public void defineShape3(DefineShape tag) { printDefineShape(tag, true); } public void defineShape6(DefineShape tag) { printDefineShape(tag, true); } private void printShapeWithStyles(ShapeWithStyle shapes, boolean alpha) { printFillStyles(shapes.fillstyles, alpha); printLineStyles(shapes.linestyles, alpha); printShape(shapes, alpha); } private void printMorphLineStyles(MorphLineStyle[] lineStyles) { for (int i = 0; i < lineStyles.length; i++) { MorphLineStyle lineStyle = lineStyles[i]; indent(); out.print(""); } } private void printLineStyles(ArrayList linestyles, boolean alpha) { Iterator it = linestyles.iterator(); while (it.hasNext()) { LineStyle lineStyle = (LineStyle)it.next(); indent(); out.print(""); } } private void printFillStyles(ArrayList fillstyles, boolean alpha) { Iterator it = fillstyles.iterator(); while (it.hasNext()) { FillStyle fillStyle = (FillStyle)it.next(); indent(); out.print(""); } } private void printMorphFillStyles(MorphFillStyle[] fillStyles) { for (int i = 0; i < fillStyles.length; i++) { MorphFillStyle fillStyle = fillStyles[i]; indent(); out.print(""); } } private String formatGradient(GradRecord[] records, boolean alpha) { StringBuffer b = new StringBuffer(); for (int i = 0; i < records.length; i++) { b.append(records[i].ratio); b.append(' '); b.append(alpha ? printRGBA(records[i].color) : printRGB(records[i].color)); if (i + 1 < records.length) b.append(' '); } return b.toString(); } private String formatMorphGradient(MorphGradRecord[] records) { StringBuffer b = new StringBuffer(); for (int i = 0; i < records.length; i++) { b.append(records[i].startRatio); b.append(','); b.append(records[i].endRatio); b.append(' '); b.append(printRGBA(records[i].startColor)); b.append(','); b.append(printRGBA(records[i].endColor)); if (i + 1 < records.length) b.append(' '); } return b.toString(); } private void printShape(Shape shapes, boolean alpha) { Iterator it = shapes.shapeRecords.iterator(); while (it.hasNext()) { indent(); ShapeRecord shape = (ShapeRecord)it.next(); if (shape instanceof StyleChangeRecord) { StyleChangeRecord styleChange = (StyleChangeRecord)shape; out.print(""); indent++; printFillStyles(styleChange.fillstyles, alpha); printLineStyles(styleChange.linestyles, alpha); indent--; indent(); out.println(""); } else { out.println("/>"); } } else { EdgeRecord edge = (EdgeRecord)shape; if (edge instanceof StraightEdgeRecord) { StraightEdgeRecord straightEdge = (StraightEdgeRecord)edge; out.println(""); } else { CurvedEdgeRecord curvedEdge = (CurvedEdgeRecord)edge; out.print(""); } } } } private void printShapeWithTabs(Shape shapes) { Iterator it = shapes.shapeRecords.iterator(); int startX = 0; int startY = 0; int x = 0; int y = 0; while (it.hasNext()) { indent(); ShapeRecord shape = (ShapeRecord)it.next(); if (shape instanceof StyleChangeRecord) { StyleChangeRecord styleChange = (StyleChangeRecord)shape; out.print("SSCR" + styleChange.nMoveBits() + "\t"); if (styleChange.stateMoveTo) { out.print(styleChange.moveDeltaX + "\t" + styleChange.moveDeltaY); if (startX == 0 && startY == 0) { startX = styleChange.moveDeltaX; startY = styleChange.moveDeltaY; } x = styleChange.moveDeltaX; y = styleChange.moveDeltaY; out.print("\t\t"); } } else { EdgeRecord edge = (EdgeRecord)shape; if (edge instanceof StraightEdgeRecord) { StraightEdgeRecord straightEdge = (StraightEdgeRecord)edge; out.print("SER" + "\t"); out.print(straightEdge.deltaX + "\t" + straightEdge.deltaY); x += straightEdge.deltaX; y += straightEdge.deltaY; out.print("\t\t"); } else { CurvedEdgeRecord curvedEdge = (CurvedEdgeRecord)edge; out.print("CER" + "\t"); out.print(curvedEdge.controlDeltaX + "\t" + curvedEdge.controlDeltaY + "\t"); out.print(curvedEdge.anchorDeltaX + "\t" + curvedEdge.anchorDeltaY); x += (curvedEdge.controlDeltaX + curvedEdge.anchorDeltaX); y += (curvedEdge.controlDeltaY + curvedEdge.anchorDeltaY); } } out.println("\t\t" + x + "\t" + y); } } private String printClipEventFlags(int flags) { StringBuffer b = new StringBuffer(); 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(DefineText tag) { open(tag); out.print(" id='" + id(tag) + "'"); end(); Iterator it = tag.records.iterator(); while (it.hasNext()) { TextRecord tr = (TextRecord)it.next(); printTextRecord(tr, tag.code); } close(tag); } public void printTextRecord(TextRecord tr, int tagCode) { indent(); out.print(""); indent++; printGlyphEntries(tr); indent--; indent(); out.println(""); } private void printGlyphEntries(TextRecord tr) { indent(); for (int i = 0; i < tr.entries.length; i++) { GlyphEntry ge = tr.entries[i]; out.print(ge.getIndex()); if (ge.advance >= 0) out.print('+'); out.print(ge.advance); out.print(' '); if ((i + 1) % 10 == 0) { out.println(); indent(); } } if (tr.entries.length % 10 != 0) out.println(); } public void defineButton2(DefineButton tag) { open(tag); out.print(" id='" + id(tag) + "'"); out.print(" trackAsMenu='" + tag.trackAsMenu + "'"); end(); for (int i = 0; i < tag.buttonRecords.length; i++) { ButtonRecord record = tag.buttonRecords[i]; indent(); out.println(""); // todo print optional cxforma } // print conditional actions if (tag.condActions.length > 0 && showActions) { indent(); out.println(""); 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(""); } close(tag); } public void defineBitsJPEG3(DefineBitsJPEG3 tag) { // We don't support // FIXME open(tag); out.print(" id='" + id(tag) + "'"); close(); } public void defineBitsLossless2(DefineBitsLossless tag) { open(tag); out.print(" id='" + id(tag) + "'"); if (external) { String path = externalDirectory + externalPrefix + "image" + dict.getId(tag) + ".bitmap"; out.println(" src='" + path + "' />"); try { FileOutputStream image = new FileOutputStream(path, false); image.write(tag.data); image.close(); } catch (IOException e) { out.println(""); } } else { out.print(" encoding='base64'"); end(); outputBase64(tag.data); close(tag); } } String escape(String s) { if (s == null) return null; StringBuffer b = new StringBuffer(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; } } return b.toString(); } public void defineEditText(DefineEditText tag) { open(tag); out.print(" id='" + id(tag) + "'"); if (tag.hasText) out.print(" text='" + escape(tag.initialText) + "'"); if (tag.hasFont) { out.print(" fontId='" + id(tag.font) + "'"); out.print(" fontName='" + tag.font.getFontName() + "'"); out.print(" fontHeight='" + tag.height + "'"); } out.print(" bounds='" + tag.bounds + "'"); if (tag.hasTextColor) out.print(" color='" + printRGBA(tag.color) + "'"); out.print(" html='" + tag.html + "'"); out.print(" autoSize='" + tag.autoSize + "'"); out.print(" border='" + tag.border + "'"); if (tag.hasMaxLength) out.print(" maxLength='" + tag.maxLength + "'"); out.print(" multiline='" + tag.multiline + "'"); out.print(" noSelect='" + tag.noSelect + "'"); out.print(" password='" + tag.password + "'"); out.print(" readOnly='" + tag.readOnly + "'"); out.print(" useOutlines='" + tag.useOutlines + "'"); out.print(" varName='" + tag.varName + "'"); out.print(" wordWrap='" + tag.wordWrap + "'"); if (tag.hasLayout) { out.print(" align='" + tag.align + "'"); out.print(" indent='" + tag.ident + "'"); out.print(" leading='" + tag.leading + "'"); out.print(" leftMargin='" + tag.leftMargin + "'"); out.print(" rightMargin='" + tag.rightMargin + "'"); } close(); } public void defineSprite(DefineSprite tag) { open(tag); out.print(" id='" + id(tag) + "'"); end(); indent(); out.println(""); tag.tagList.visitTags(this); close(tag); } public void finish() { --indent; indent(); out.println(""); } public void frameLabel(FrameLabel tag) { open(tag); out.print(" label='" + tag.label + "'"); if (tag.anchor) out.print(" anchor='" + "true" + "'"); close(); } public void soundStreamHead2(SoundStreamHead tag) { open(tag); out.print(" playbackRate='" + tag.playbackRate + "'"); out.print(" playbackSize='" + tag.playbackSize + "'"); out.print(" playbackType='" + tag.playbackType + "'"); out.print(" compression='" + tag.compression + "'"); out.print(" streamRate='" + tag.streamRate + "'"); out.print(" streamSize='" + tag.streamSize + "'"); out.print(" streamType='" + tag.streamType + "'"); out.print(" streamSampleCount='" + tag.streamSampleCount + "'"); if (tag.compression == 2) { out.print(" latencySeek='" + tag.latencySeek + "'"); } close(); } public void defineScalingGrid(DefineScalingGrid tag) { open(tag); out.print(" idref='" + id(tag.scalingTarget) + "'"); out.print( " grid='" + tag.rect + "'" ); close(); } public void defineMorphShape(DefineMorphShape tag) { defineMorphShape2(tag); } public void defineMorphShape2(DefineMorphShape tag) { open(tag); out.print(" id='" + id(tag) + "'"); out.print(" startBounds='" + tag.startBounds + "'"); out.print(" endBounds='" + tag.endBounds + "'"); if (tag.code == TagValues.stagDefineMorphShape2) { out.print(" startEdgeBounds='" + tag.startEdgeBounds + "'"); out.print(" endEdgeBounds='" + tag.endEdgeBounds + "'"); out.print(" usesNonScalingStrokes='" + tag.usesNonScalingStrokes + "'"); out.print(" usesScalingStrokes='" + tag.usesScalingStrokes + "'"); } end(); printMorphLineStyles(tag.lineStyles); printMorphFillStyles(tag.fillStyles); indent(); out.println(""); indent++; printShape(tag.startEdges, true); indent--; indent(); out.println(""); indent(); out.println(""); indent++; printShape(tag.endEdges, true); indent--; indent(); out.println(""); close(tag); } public void defineFont2(DefineFont2 tag) { open(tag); out.print(" id='" + id(tag) + "'"); out.print(" font='" + tag.fontName + "'"); out.print(" numGlyphs='" + tag.glyphShapeTable.length + "'"); out.print(" italic='" + tag.italic + "'"); out.print(" bold='" + tag.bold + "'"); out.print(" ansi='" + tag.ansi + "'"); out.print(" wideOffsets='" + tag.wideOffsets + "'"); out.print(" wideCodes='" + tag.wideCodes + "'"); out.print(" shiftJIS='" + tag.shiftJIS + "'"); out.print(" langCode='" + tag.langCode + "'"); out.print(" hasLayout='" + tag.hasLayout + "'"); out.print(" ascent='" + tag.ascent + "'"); out.print(" descent='" + tag.descent + "'"); out.print(" leading='" + tag.leading + "'"); out.print(" kerningCount='" + tag.kerningCount + "'"); out.print(" codepointCount='" + tag.codeTable.length + "'"); if (tag.hasLayout) { out.print(" advanceCount='" + tag.advanceTable.length + "'"); out.print(" boundsCount='" + tag.boundsTable.length + "'"); } end(); if (glyphs) { for (int i=0; i < tag.kerningCount; i++) { KerningRecord rec = tag.kerningTable[i]; indent(); out.println(""); } for (int i = 0; i < tag.glyphShapeTable.length; i++) { indent(); out.print(""); Shape shape = tag.glyphShapeTable[i]; indent++; if (tabbedGlyphs) printShapeWithTabs(shape); else printShape(shape, true); indent--; indent(); out.println(""); } } close(tag); } public void defineFont3(DefineFont3 tag) { defineFont2(tag); } public void defineFont4(DefineFont4 tag) { open(tag); out.print(" id='" + id(tag) + "'"); out.print(" font='" + tag.fontName + "'"); out.print(" hasFontData='" + tag.hasFontData + "'"); out.print(" smallText='" + tag.smallText + "'"); out.print(" italic='" + tag.italic + "'"); out.print(" bold='" + tag.bold + "'"); out.print(" langCode='" + tag.langCode + "'"); end(); if (tag.hasFontData) { outputBase64(tag.data); } close(tag); } public void defineFontAlignZones(DefineFontAlignZones tag) { open(tag); if (tag.name != null) out.print(" id='" + id(tag) + "'"); out.print(" fontID='" + id(tag.font) + "'"); out.print(" CSMTableHint='" + tag.csmTableHint + "'"); out.println(">"); indent++; indent(); out.println(""); indent++; if (glyphs) { for (int i = 0; i < tag.zoneTable.length; i++) { ZoneRecord record = tag.zoneTable[i]; indent(); out.print(""); for (int j = 0; j < record.zoneData.length; j++) { out.print(record.zoneData[j] + " "); } out.println(""); } } indent--; indent(); out.println(""); close(tag); } public void csmTextSettings(CSMTextSettings tag) { open(tag); if (tag.name != null) out.print(" id='" + id(tag) + "'"); String textID = tag.textReference == null ? "0" : id(tag.textReference); out.print(" textID='" + textID + "'"); out.print(" styleFlagsUseSaffron='" + tag.styleFlagsUseSaffron + "'"); out.print(" gridFitType='" + tag.gridFitType + "'"); out.print(" thickness='" + tag.thickness + "'"); out.print(" sharpness='" + tag.sharpness + "'"); close(); } public void defineFontName(DefineFontName tag) { open(tag); if (tag.name != null) out.print(" id='" + id(tag) + "'"); out.print(" fontID='" + id(tag.font) + "'"); if (tag.fontName != null) { out.print(" name='" + tag.fontName + "'"); } if (tag.copyright != null) { out.print(" copyright='" + tag.copyright + "'"); } close(); } private boolean isPrintable(char c) { int i = c & 0xFFFF; if (i < ' ' || i == '<' || i == '&' || i == '\'') return false; else return true; } public void exportAssets(ExportAssets tag) { open(tag); end(); Iterator it = tag.exports.iterator(); while (it.hasNext()) { DefineTag ref = (DefineTag)it.next(); indent(); out.println(""); } close(tag); } public void symbolClass(SymbolClass tag) { open(tag); end(); Iterator it = tag.class2tag.entrySet().iterator(); while (it.hasNext()) { Map.Entry e = (Map.Entry)it.next(); String className = (String)e.getKey(); DefineTag ref = (DefineTag)e.getValue(); indent(); out.println(""); } if (tag.topLevelClass != null) { indent(); out.println(""); } close(tag); } public void importAssets(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(""); } close(tag); } public void importAssets2(ImportAssets tag) { // TODO: add support for tag.downloadNow and SHA1... importAssets(tag); } public void enableDebugger(EnableDebugger tag) { open(tag); out.print(" password='" + tag.password + "'"); close(); } public void doInitAction(DoInitAction tag) { if (tag.sprite != null && tag.sprite.name != null) { indent(); out.println(""); } open(tag); if (tag.sprite != null) out.print(" idref='" + idRef(tag.sprite) + "'"); end(); if (showActions) { openCDATA(); printActions(tag.actionList); closeCDATA(); } else { indent(); out.println(""); } close(tag); } private String idRef(DefineTag 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 (tag.name == null) { // just print the character id since no name was exported return String.valueOf(dict.getId(tag)); } else { return tag.name; } } public void defineVideoStream(DefineVideoStream tag) { open(tag); out.print(" id='" + id(tag) + "'"); close(); } public void videoFrame(VideoFrame tag) { open(tag); out.print(" streamId='" + idRef(tag.stream) + "'"); out.print(" frame='" + tag.frameNum + "'"); close(); } public void defineFontInfo2(DefineFontInfo tag) { defineFontInfo(tag); } public void enableDebugger2(EnableDebugger tag) { open(tag); out.print(" password='" + tag.password + "'"); out.print(" reserved='0x" + Integer.toHexString(tag.reserved) + "'"); close(); } public void debugID(DebugID tag) { open(tag); out.print(" uuid='" + tag.uuid + "'"); close(); } public void scriptLimits(ScriptLimits tag) { open(tag); out.print(" scriptRecursionLimit='" + tag.scriptRecursionLimit + "'" + " scriptTimeLimit='" + tag.scriptTimeLimit + "'"); close(); } public void setTabIndex(SetTabIndex tag) { open(tag); out.print(" depth='" + tag.depth + "'"); out.print(" index='" + tag.index + "'"); close(); } public void doABC(DoABC tag) { if (abc) { open(tag); end(); AbcPrinter abcPrinter = new AbcPrinter(tag.abc, out, showOffset, indent); abcPrinter.print(); close(tag); } else if (showActions) { open(tag); if (tag.code == TagValues.stagDoABC2) out.print( " name='" + tag.name + "'"); end(); ContextStatics contextStatics = new ContextStatics(); contextStatics.use_static_semantics = true; contextStatics.dialect = 9; assert swfVersion != null : "header should have been parsed already, but wasn't"; contextStatics.setAbcVersion(ContextStatics.getTargetAVM(swfVersion.intValue())); contextStatics.use_namespaces.addAll(ContextStatics.getRequiredUseNamespaces(swfVersion.intValue())); Context context = new Context(contextStatics); context.setHandler(new CompilerHandler()); AbcParser abcParser = new AbcParser(context, tag.abc); context.setEmitter(new ActionBlockEmitter(context, tag.name, new StringPrintWriter(), new StringPrintWriter(), false, false, false, false)); ProgramNode programNode = abcParser.parseAbc(); if (programNode == null) { out.println(""); } else if (decompile) { // PrettyPrinter prettyPrinter = new PrettyPrinter(out); // programNode.evaluate(context, prettyPrinter); } else { SyntaxTreeDumper syntaxTreeDumper = new SyntaxTreeDumper(out, indent); programNode.evaluate(context, syntaxTreeDumper); } close(tag); } else { open(tag); close(); } } private String hexify(byte[] id) { StringBuffer b = new StringBuffer(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 showDebugSourceOption = false; static boolean glyphsOption = true; static boolean externalOption = false; static boolean decompileOption = true; static boolean defuncOption = true; static boolean saveOption = false; static boolean tabbedGlyphsOption = true; /** * swfdump usage: swfdump [-encode] [-noactions] [-showoffset] files ... * -encode ? * -noactions don't output ActionScript byte code * -showoffset output an XML comment line in the output before each * tag, displaying the tag's byte offset and size in the file *

* Swfdump will dump a SWF file as XML. Swf tags are shown as XML tags. Swf Actions are shown * commented out assembly language. If a SWD file is found that matches this SWF file, then * we will show intermixed source code and assembly language. *

* The format of the output (swfx) is according to the SWFX doctype. The optional -dtd flag will * include the doctype declaration before the actual content. This format can be edited in any * text or xml editor, and then converted back into SWF using the Swfxc utility. */ public static void main(String[] args) throws IOException { if (args.length == 0) { System.err.println("Usage: java tools.SwfxPrinter [-encode] [-asm] [-abc] [-noactions] [-showdebugsource] [-showoffset] [-noglyphs] [-external] [-save file.swf] [-nofunctions] [-out file.swfx] 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("-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("-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(new FileOutputStream(outfile, false)); } 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(System.out, true); File f = new File(args[index]); URL[] urls; if (!f.exists()) { urls = new URL[]{new URL(args[index])}; } else { if (f.isDirectory()) { File[] list = FileUtils.listFiles(f); urls = new URL[list.length]; for (int i = 0; i < list.length; i++) { urls[i] = FileUtils.toURL(list[i]); } } else { urls = new URL[]{FileUtils.toURL(f)}; } } 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 (isSwf(url)) { dumpSwf(out, url, outfile); } else if (isZip(url) && !url.toString().endsWith(".abj")) { dumpZip(out, url, outfile); } else { out.println(""); // we have no way of knowing the swf version, so assume latest URLConnection connection = url.openConnection(); ActionDecoder actionDecoder = new ActionDecoder(new SwfDecoder(connection.getInputStream(), 7)); actionDecoder.setKeepOffsets(true); ActionList actions = actionDecoder.decode(connection.getContentLength()); SwfxPrinter printer = new SwfxPrinter(out); printer.decompile = decompileOption; printer.defunc = defuncOption; printer.printActions(actions); } 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); } } } private static void dumpZip(PrintWriter out, URL url, String outfile) throws IOException { InputStream in = new BufferedInputStream(url.openStream()); try { ZipInputStream zipIn = new ZipInputStream(in); ZipEntry zipEntry = zipIn.getNextEntry(); while ((zipEntry != null)) { URL fileUrl = new URL("jar:" + url.toString() + "!/" + zipEntry.getName()); if (isSwf(fileUrl)) dumpSwf(out, fileUrl, outfile); zipEntry = zipIn.getNextEntry(); } } finally { in.close(); } } private static void dumpSwf(PrintWriter out, URL url, String outfile) throws IOException { out.println(""); InputStream in; SwfxPrinter debugPrinter = new SwfxPrinter(out); debugPrinter.showActions = showActionsOption; debugPrinter.showOffset = showOffsetOption; debugPrinter.showDebugSource = showDebugSourceOption; debugPrinter.glyphs = glyphsOption; debugPrinter.setExternal(externalOption, outfile); debugPrinter.decompile = decompileOption; debugPrinter.abc = abcOption; debugPrinter.defunc = defuncOption; debugPrinter.tabbedGlyphs = tabbedGlyphsOption; if (encodeOption) { // decode -> encode -> decode -> print TagEncoder encoder = new TagEncoder(); in = url.openStream(); new TagDecoder(in, url).parse(encoder); encoder.finish(); in = new ByteArrayInputStream(encoder.toByteArray()); } else { // decode -> print in = url.openStream(); } TagDecoder t = new TagDecoder(in, url); t.setKeepOffsets(debugPrinter.showOffset); t.parse(debugPrinter); } private static boolean isSwf(URL url) throws IOException { InputStream in = new BufferedInputStream(url.openStream()); try { return isSwf(in); } finally { in.close(); } } public static boolean isSwf(InputStream in) { try { DataInputStream data = new DataInputStream(in); byte[] b = new byte[3]; data.mark(b.length); data.readFully(b); if (b[0] == 'C' && b[1] == 'W' && b[2] == 'S' || b[0] == 'F' && b[1] == 'W' && b[2] == 'S') { data.reset(); return true; } else { data.reset(); return false; } } catch (IOException e) { return false; } } private static boolean isZip(URL url) throws IOException { InputStream in = new BufferedInputStream(url.openStream()); try { return isZip(in); } finally { in.close(); } } public static boolean isZip(InputStream in) { try { ZipInputStream swcZipInputStream = new ZipInputStream(in); swcZipInputStream.getNextEntry(); return true; } catch (IOException e) { return false; } } // Handy dandy for dumping an action list during debugging public static String actionListToString(ActionList al, String[] args) { // cut and paste arg code from main() could be better but it works boolean showActions = true; boolean showOffset = false; boolean showDebugSource = false; boolean decompile = false; boolean defunc = true; boolean tabbedGlyphs = true; int index = 0; while (args != null && (index < args.length) && (args[index].startsWith("-"))) { if (args[index].equals("-decompile")) { decompile = true; ++index; } else if (args[index].equals("-nofunctions")) { defunc = false; ++index; } else if (args[index].equals("-asm")) { decompile = false; ++index; } else if (args[index].equals("-noactions")) { showActions = false; ++index; } else if (args[index].equals("-showoffset")) { showOffset = true; ++index; } else if (args[index].equals("-showdebugsource")) { showDebugSource = true; ++index; } else if (args[index].equalsIgnoreCase("-tabbedGlyphs")) { tabbedGlyphs = true; ++index; } } StringWriter sw = new StringWriter(); PrintWriter out = new PrintWriter(sw); SwfxPrinter printer = new SwfxPrinter(out); printer.showActions = showActions; printer.showOffset = showOffset; printer.showDebugSource = showDebugSource; printer.decompile = decompile; printer.defunc = defunc; printer.tabbedGlyphs = tabbedGlyphs; printer.printActions(al); out.flush(); return sw.toString(); } }