diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java index bc344c736..368175e49 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -219,6 +219,7 @@ public final class SWF implements SWFContainerItem, Timelined { * Tags inside of file */ public List tags = new ArrayList<>(); + @Internal public boolean hasEndTag; /** * ExportRectangle for the display @@ -695,8 +696,9 @@ public final class SWF implements SWFContainerItem, Timelined { frameCount = sis.readUI16("frameCount"); List tags = sis.readTagList(this, 0, parallelRead, true, !checkOnly); if (tags.get(tags.size() - 1).getId() == EndTag.ID) { - hasEndTag = true; tags.remove(tags.size() - 1); + } else { + hasEndTag = false; } this.tags = tags; if (!checkOnly) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/IndenetedStringBuilder.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/IndentedStringBuilder.java similarity index 93% rename from libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/IndenetedStringBuilder.java rename to libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/IndentedStringBuilder.java index 5f42bf00f..b4ffa827c 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/IndenetedStringBuilder.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/IndentedStringBuilder.java @@ -22,13 +22,13 @@ import com.jpexs.helpers.Helper; * * @author JPEXS */ -public class IndenetedStringBuilder { +public class IndentedStringBuilder { private final StringBuilder builder = new StringBuilder(); private final String indentString; private int indent; - public IndenetedStringBuilder(String indentString) { + public IndentedStringBuilder(String indentString) { super(); this.indentString = indentString; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfJavaExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfJavaExporter.java index bc9cd3dae..4dd0eb139 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfJavaExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfJavaExporter.java @@ -89,7 +89,7 @@ public class SwfJavaExporter { writer.append("public class SwfFile {").newLine(); writer.newLine(); writer.indent(); - IndenetedStringBuilder sb = new IndenetedStringBuilder(javaIndentString); + IndentedStringBuilder sb = new IndentedStringBuilder(javaIndentString); generateJavaCode(writer, sb, objectNames, swf, 0); writer.unindent(); writer.append(" public SWF getSwf() {").newLine(); @@ -128,7 +128,7 @@ public class SwfJavaExporter { return sb.toString(); } - private static Object generateJavaCode(GraphTextWriter writer, IndenetedStringBuilder sb, Map objectNames, Object obj, int level){ + private static Object generateJavaCode(GraphTextWriter writer, IndentedStringBuilder sb, Map objectNames, Object obj, int level){ if (obj == null) { return null; } @@ -216,7 +216,7 @@ public class SwfJavaExporter { boolean isSwf = level == 0; String resultName = isSwf ? "swf" : "result"; String tagObjName = isSwf ? "swf" : getNextId(objectNames, "obj" + className); - IndenetedStringBuilder sb2 = new IndenetedStringBuilder(javaIndentString); + IndentedStringBuilder sb2 = new IndentedStringBuilder(javaIndentString); sb2.indent(); sb2.indent(); String indent = getIndent(writer.getIndent() + 1, javaIndentString); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfXmlExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfXmlExporter.java new file mode 100644 index 000000000..2d84a9f34 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfXmlExporter.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2010-2015 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.exporters.swf; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.helpers.LazyObject; +import com.jpexs.decompiler.flash.types.annotations.Internal; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.utf8.Utf8OutputStreamWriter; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +/** + * + * @author JPEXS + */ +public class SwfXmlExporter { + + public List exportXml(SWF swf, String outdir) throws IOException { + final File file = new File(outdir + File.separator + Helper.makeFileName("swf.xml")); + + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + try { + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + Document xmlDoc = docBuilder.newDocument(); + xmlDoc.appendChild(xmlDoc.createComment("WARNING: The structure of this XML is not final. In later versions of FFDec it can be changed.")); + exportXml(swf, xmlDoc, xmlDoc); + try (Writer writer = new BufferedWriter(new Utf8OutputStreamWriter(new FileOutputStream(file)))) { + writer.append(getXml(xmlDoc)); + } + } catch (ParserConfigurationException ex) { + Logger.getLogger(SwfXmlExporter.class.getName()).log(Level.SEVERE, null, ex); + } + + List ret = new ArrayList<>(); + ret.add(file); + return ret; + } + + private String getXml(Document xml) { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + StringWriter writer = new StringWriter(); + try { + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + DOMSource source = new DOMSource(xml); + StreamResult result = new StreamResult(writer); + transformer.transform(source, result); + } catch (TransformerException ex) { + Logger.getLogger(SwfXmlExporter.class.getName()).log(Level.SEVERE, null, ex); + } + return writer.toString(); + } + + public void exportXml(SWF swf, Document doc, Node node) throws IOException { + generateXml(doc, node, "swf", swf, 0); + } + + private static void generateXml(Document doc, Node node, String name, Object obj, int level){ + if (obj == null) { + return; + } + + Class cls = obj.getClass(); + Object value = null; + + if (cls == Byte.class || cls == byte.class || + cls == Short.class || cls == short.class || + cls == Integer.class || cls == int.class || + cls == Long.class || cls == long.class || + cls == Float.class || cls == float.class || + cls == Double.class || cls == double.class || + cls == Boolean.class || cls == boolean.class || + cls == Character.class || cls == char.class || + cls == String.class) { + value = obj; + if (value instanceof String) { + value = Helper.escapeXML((String) value); + } + + ((Element) node).setAttribute(name, value.toString()); + } else if (cls.isEnum()) { + ((Element) node).setAttribute(name, obj.toString()); + } else if (obj instanceof ByteArrayRange) { + ByteArrayRange range = (ByteArrayRange) obj; + byte[] data = range.getRangeData(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < data.length; i++) { + sb.append(String.format("%02x", data[i])); + } + + ((Element) node).setAttribute(name, sb.toString()); + } else if (List.class.isAssignableFrom(cls)) { + List list = (List) obj; + Element listNode = doc.createElement(name); + node.appendChild(listNode); + for (int i = 0; i < list.size(); i++) { + generateXml(doc, listNode, "item", list.get(i), level + 1); + } + } else if (cls.isArray()) { + String arrayType = cls.getComponentType().getSimpleName(); + Element arrayNode = doc.createElement(name); + node.appendChild(arrayNode); + int length = Array.getLength(obj); + for (int i = 0; i < length; i++) { + generateXml(doc, arrayNode, "item", Array.get(obj, i), level + 1); + } + } else { + if (obj instanceof LazyObject) { + ((LazyObject) obj).load(); + } + + String className = obj.getClass().getSimpleName(); + Field fields[] = obj.getClass().getFields(); + Element objNode = doc.createElement(name); + objNode.setAttribute("type", className); + node.appendChild(objNode); + + for (Field f : fields) { + if (Modifier.isStatic(f.getModifiers())) { + continue; + } + + Internal inter = f.getAnnotation(Internal.class); + if (inter != null) { + continue; + } + + try { + f.setAccessible(true); + generateXml(doc, objNode, f.getName(), f.get(obj), level + 1); + } catch (IllegalArgumentException | IllegalAccessException ex) { + Logger.getLogger(SwfXmlExporter.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java b/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java index 29761c690..c9919b71b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java @@ -869,6 +869,18 @@ public class Helper { return text; } + public static String escapeXML(String text) { + StringBuilder sb = new StringBuilder(text.length()); + for (int i = 0; i < text.length(); i++) { + char ch = text.charAt(i); + if (i > 31 || i == 9 || i == 10 || i == 13) { + sb.append(ch); + } + } + + return escapeHTML(sb.toString()); + } + public static Shape imageToShape(BufferedImage image) { Area area = new Area(); Rectangle rectangle = new Rectangle(); diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java index 55b6e5e50..d85152914 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java @@ -95,6 +95,7 @@ public class MainFrameRibbonMenu extends MainFrameMenu implements ActionListener private static final String ACTION_EXPORT_FLA = "EXPORTFLA"; public static final String ACTION_EXPORT_SEL = "EXPORTSEL"; public static final String ACTION_EXPORT_JAVA_SOURCE = "EXPORTJAVASOURCE"; + public static final String ACTION_EXPORT_SWF_XML = "EXPORTSWFXML"; private static final String ACTION_EXPORT = "EXPORT"; private static final String ACTION_IMPORT_TEXT = "IMPORTTEXT"; private static final String ACTION_CHECK_UPDATES = "CHECKUPDATES"; diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index cf7cbad13..440bfe885 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -58,6 +58,7 @@ import com.jpexs.decompiler.flash.exporters.settings.ShapeExportSettings; import com.jpexs.decompiler.flash.exporters.settings.SoundExportSettings; import com.jpexs.decompiler.flash.exporters.settings.TextExportSettings; import com.jpexs.decompiler.flash.exporters.swf.SwfJavaExporter; +import com.jpexs.decompiler.flash.exporters.swf.SwfXmlExporter; import com.jpexs.decompiler.flash.gui.abc.ABCPanel; import com.jpexs.decompiler.flash.gui.abc.ClassesListTreeModel; import com.jpexs.decompiler.flash.gui.abc.DeobfuscationDialog; @@ -1643,6 +1644,25 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec if (selFile != null) { try { new SwfJavaExporter().exportJavaCode(swf, selFile); + Main.stopWork(); + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + } + } + } + + public void exportSwfXml() { + List sel = tagTree.getSelected(tagTree); + for (TreeItem item : sel) { + if (item instanceof SWF) { + SWF swf = (SWF) item; + final String selFile = selectExportDir(); + if (selFile != null) { + try { + new SwfXmlExporter().exportXml(swf, selFile); + Main.stopWork(); } catch (IOException ex) { logger.log(Level.SEVERE, null, ex); } @@ -2000,9 +2020,12 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec switch (e.getActionCommand()) { - case MainFrameRibbonMenu.ACTION_EXPORT_JAVA_SOURCE: { + case MainFrameRibbonMenu.ACTION_EXPORT_JAVA_SOURCE: exportJavaSource(); - } + break; + case MainFrameRibbonMenu.ACTION_EXPORT_SWF_XML: + exportSwfXml(); + break; case MainFrameRibbonMenu.ACTION_EXPORT_SEL: export(true); break; diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index cb8d06b24..650e87e60 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -550,3 +550,4 @@ message.confirm.close = There are unsaved changes. Do you really want to close { message.confirm.closeAll = There are unsaved changes. Do you really want to close all SWFs? contextmenu.exportJavaSource = Export Java Source +contextmenu.exportSwfXml = Export SWF as XML diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_hu.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_hu.properties index ec6757983..5de306c15 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_hu.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_hu.properties @@ -550,3 +550,4 @@ message.confirm.close = Mentetlen v\u00e1ltoz\u00e1sok vannak. Szeretm\u00e9 m\u message.confirm.closeAll = Mentetlen v\u00e1ltoz\u00e1sok vannak. Szeretn\u00e9 bez\u00e1rni az \u00f6sszes SWF-t ? contextmenu.exportJavaSource = Java k\u00f3d export\u00e1l\u00e1s +contextmenu.exportSwfXml = SWF export\u00e1l\u00e1sa XML-k\u00e9nt diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java index 71a9823e1..12b1b7942 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -77,6 +77,7 @@ public class TagTreeContextMenu extends JPopupMenu implements ActionListener { private JMenuItem rawEditMenuItem; private JMenuItem jumpToCharacterMenuItem; private JMenuItem exportJavaSourceMenuItem; + private JMenuItem exportSwfXmlMenuItem; private JMenuItem closeMenuItem; private JMenu addTagMenu; private JMenu moveTagMenu; @@ -128,6 +129,11 @@ public class TagTreeContextMenu extends JPopupMenu implements ActionListener { exportJavaSourceMenuItem.addActionListener(mainPanel); add(exportJavaSourceMenuItem); + exportSwfXmlMenuItem = new JMenuItem(mainPanel.translate("contextmenu.exportSwfXml")); + exportSwfXmlMenuItem.setActionCommand(MainFrameRibbonMenu.ACTION_EXPORT_SWF_XML); + exportSwfXmlMenuItem.addActionListener(mainPanel); + add(exportSwfXmlMenuItem); + closeMenuItem = new JMenuItem(mainPanel.translate("contextmenu.closeSwf")); closeMenuItem.setActionCommand(ACTION_CLOSE_SWF); closeMenuItem.addActionListener(this); @@ -469,6 +475,7 @@ public class TagTreeContextMenu extends JPopupMenu implements ActionListener { } } } + break; } } }