From c3314b862ca16c316e92481d12b0daacb3f01a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sat, 23 Jul 2016 13:38:53 +0200 Subject: [PATCH] AMF export/import fixes XML import/export of AMF (+other Multiline) handling --- .../decompiler/flash/amf/amf3/Amf3Value.java | 24 +++- .../exporters/amf/amf3/Amf3Exporter.java | 129 ++++++++++-------- .../flash/exporters/swf/SwfXmlExporter.java | 23 +++- .../flash/importers/SwfXmlImporter.java | 8 +- 4 files changed, 111 insertions(+), 73 deletions(-) diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Amf3Value.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Amf3Value.java index 9da5c62a2..724816ef8 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Amf3Value.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Amf3Value.java @@ -1,11 +1,19 @@ package com.jpexs.decompiler.flash.amf.amf3; import com.jpexs.decompiler.flash.amf.amf3.types.Amf3ValueType; +import com.jpexs.decompiler.flash.amf.amf3.types.BasicType; import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter; +import com.jpexs.decompiler.flash.importers.amf.amf3.Amf3Importer; +import com.jpexs.decompiler.flash.importers.amf.amf3.Amf3ParseException; +import com.jpexs.decompiler.flash.types.annotations.Multiline; +import com.jpexs.decompiler.flash.types.annotations.SWFField; +import java.io.IOException; public class Amf3Value { - private Object value; + @SWFField + @Multiline + private String data = null; public Amf3Value() { setValue(null); @@ -19,7 +27,7 @@ public class Amf3Value { if (!isValueValid(value)) { throw new IllegalArgumentException("Invalid Amf value: " + value.getClass().getSimpleName()); } - this.value = value; + this.data = value == null ? "" : Amf3Exporter.amfToString(value, " ", "\n"); } public static boolean isValueValid(Object value) { @@ -45,14 +53,16 @@ public class Amf3Value { } public Object getValue() { - return value; + Amf3Importer imp = new Amf3Importer(); + try { + return imp.stringToAmf(data); + } catch (IOException | Amf3ParseException ex) { + return BasicType.UNKNOWN; + } } @Override public String toString() { - if (value == null) { - return ""; - } - return Amf3Exporter.amfToString(value); + return data; } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/amf/amf3/Amf3Exporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/amf/amf3/Amf3Exporter.java index afe62a3c2..b88876e27 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/amf/amf3/Amf3Exporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/amf/amf3/Amf3Exporter.java @@ -24,17 +24,30 @@ import java.util.Map; public class Amf3Exporter { /** - * Converts AMF value to something human-readable. + * Converts AMF value to something human-readable with indentStr " " and + * CRLF newlines * * @param amfValue * @return */ public static String amfToString(Object amfValue) { + return amfToString(amfValue, " ", "\r\n"); + } + + /** + * Converts AMF value to something human-readable. + * + * @param amfValue + * @param indentStr + * @param newLine + * @return + */ + public static String amfToString(Object amfValue, String indentStr, String newLine) { Map refCount = new HashMap<>(); List objectList = new ArrayList<>(); Map objectAlias = new HashMap<>(); populateObjects(amfValue, refCount, objectList, objectAlias); - return amfToString(new ArrayList<>(), 0, amfValue, refCount, objectAlias); + return amfToString(indentStr, newLine, new ArrayList<>(), 0, amfValue, refCount, objectAlias); } /** @@ -87,7 +100,7 @@ public class Amf3Exporter { * @param objectAlias * @return */ - private static String amfToString(List processedObjects, int level, Object object, Map referenceCount, Map objectAlias) { + private static String amfToString(String indentStr, String newLine, List processedObjects, int level, Object object, Map referenceCount, Map objectAlias) { if (object instanceof String) { return "\"" + Helper.escapeActionScriptString((String) object) + "\""; } @@ -108,90 +121,90 @@ public class Amf3Exporter { } processedObjects.add(object); - String addId = refCount > 1 ? indent(level + 1) + "\"id\": \"" + objectAlias.get(object) + "\",\r\n" : ""; + String addId = refCount > 1 ? indent(level + 1) + "\"id\": \"" + objectAlias.get(object) + "\"," + newLine : ""; if (object instanceof AbstractVectorType) { AbstractVectorType avt = (AbstractVectorType) object; - ret.append("{\r\n"); - ret.append(indent(level + 1)).append("\"type\": \"Vector\",\r\n"); + ret.append("{").append(newLine); + ret.append(indent(level + 1)).append("\"type\": \"Vector\",").append(newLine); ret.append(addId); - ret.append(indent(level + 1)).append("\"fixed\": ").append(avt.isFixed()).append(",\r\n"); - ret.append(indent(level + 1)).append("\"subtype\": ").append(amfToString(processedObjects, level, avt.getTypeName(), referenceCount, objectAlias)).append(",\r\n"); + ret.append(indent(level + 1)).append("\"fixed\": ").append(avt.isFixed()).append(",").append(newLine); + ret.append(indent(level + 1)).append("\"subtype\": ").append(amfToString(indentStr, newLine, processedObjects, level, avt.getTypeName(), referenceCount, objectAlias)).append(",").append(newLine); ret.append(indent(level + 1)).append("\"values\": ["); for (int i = 0; i < avt.getValues().size(); i++) { if (i > 0) { ret.append(", "); } - ret.append(amfToString(processedObjects, level + 1, avt.getValues().get(i), referenceCount, objectAlias)); + ret.append(amfToString(indentStr, newLine, processedObjects, level + 1, avt.getValues().get(i), referenceCount, objectAlias)); } - ret.append("]\r\n"); + ret.append("]").append(newLine); ret.append(indent(level)).append("}"); } else if (object instanceof ObjectType) { ObjectType ot = (ObjectType) object; - ret.append("{\r\n"); - ret.append(indent(level + 1)).append("\"type\": \"Object\",\r\n"); + ret.append("{").append(newLine); + ret.append(indent(level + 1)).append("\"type\": \"Object\",").append(newLine); ret.append(addId); - ret.append(indent(level + 1)).append("\"className\": ").append(amfToString(processedObjects, level, ot.getClassName(), referenceCount, objectAlias)).append(",\r\n"); + ret.append(indent(level + 1)).append("\"className\": ").append(amfToString(indentStr, newLine, processedObjects, level, ot.getClassName(), referenceCount, objectAlias)).append(",").append(newLine); if (ot.isSerialized()) { byte[] serData = ot.getSerializedData(); if (serData == null) { - ret.append(indent(level + 1)).append("\"serialized\": unknown\r\n"); + ret.append(indent(level + 1)).append("\"serialized\": unknown").append(newLine); } else { - ret.append(indent(level + 1)).append("\"serialized\": \"").append(javax.xml.bind.DatatypeConverter.printHexBinary(serData)).append("\",\r\n"); + ret.append(indent(level + 1)).append("\"serialized\": \"").append(javax.xml.bind.DatatypeConverter.printHexBinary(serData)).append("\",").append(newLine); if (!ot.getSerializedMembers().isEmpty()) { - ret.append(indent(level + 1)).append("\"unserializedMembers\": {\r\n"); + ret.append(indent(level + 1)).append("\"unserializedMembers\": {").append(newLine); for (int i = 0; i < ot.getSerializedMembers().size(); i++) { Pair member = ot.getSerializedMembers().get(i); - ret.append(indent(level + 2)).append(amfToString(processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias)).append(":").append(amfToString(processedObjects, level + 1, member.getSecond(), referenceCount, objectAlias)); + ret.append(indent(level + 2)).append(amfToString(indentStr, newLine, processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias)).append(":").append(amfToString(indentStr, newLine, processedObjects, level + 1, member.getSecond(), referenceCount, objectAlias)); if (i < ot.getSerializedMembers().size() - 1) { - ret.append(",\r\n"); + ret.append(",").append(newLine); } else { - ret.append("\r\n"); + ret.append(newLine); } } ret.append(indent(level + 1)).append("}"); - ret.append("\r\n"); + ret.append(newLine); } } } else { - ret.append(indent(level + 1)).append("\"dynamic\": ").append(ot.isDynamic()).append(",\r\n"); + ret.append(indent(level + 1)).append("\"dynamic\": ").append(ot.isDynamic()).append(",").append(newLine); //if (!ot.getSealedMembers().isEmpty()) { - ret.append(indent(level + 1)).append("\"sealedMembers\": {\r\n"); + ret.append(indent(level + 1)).append("\"sealedMembers\": {").append(newLine); for (int i = 0; i < ot.getSealedMembers().size(); i++) { Pair member = ot.getSealedMembers().get(i); - ret.append(indent(level + 2)).append(amfToString(processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias)).append(":").append(amfToString(processedObjects, level + 1, member.getSecond(), referenceCount, objectAlias)); + ret.append(indent(level + 2)).append(amfToString(indentStr, newLine, processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias)).append(":").append(amfToString(indentStr, newLine, processedObjects, level + 1, member.getSecond(), referenceCount, objectAlias)); if (i < ot.getSealedMembers().size() - 1) { - ret.append(",\r\n"); + ret.append(",").append(newLine); } else { - ret.append("\r\n"); + ret.append(newLine); } } ret.append(indent(level + 1)).append("}"); //if (!ot.getDynamicMembers().isEmpty()) { ret.append(","); //} - ret.append("\r\n"); + ret.append(newLine); //} //if (!ot.getDynamicMembers().isEmpty()) { - ret.append(indent(level + 1)).append("\"dynamicMembers\": {\r\n"); + ret.append(indent(level + 1)).append("\"dynamicMembers\": {").append(newLine); for (int i = 0; i < ot.getDynamicMembers().size(); i++) { Pair member = ot.getDynamicMembers().get(i); - ret.append(indent(level + 2)).append(amfToString(processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias)); + ret.append(indent(level + 2)).append(amfToString(indentStr, newLine, processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias)); ret.append(":"); - ret.append(amfToString(processedObjects, level + 2, member.getSecond(), referenceCount, objectAlias)); + ret.append(amfToString(indentStr, newLine, processedObjects, level + 2, member.getSecond(), referenceCount, objectAlias)); if (i < ot.getDynamicMembers().size() - 1) { ret.append(","); } - ret.append("\r\n"); + ret.append(newLine); } - ret.append(indent(level + 1)).append("}\r\n"); + ret.append(indent(level + 1)).append("}").append(newLine); //} } ret.append(indent(level)).append("}"); } else if (object instanceof ArrayType) { ArrayType at = (ArrayType) object; - ret.append("{\r\n"); - ret.append(indent(level + 1)).append("\"type\": \"Array\",\r\n"); + ret.append("{").append(newLine); + ret.append(indent(level + 1)).append("\"type\": \"Array\",").append(newLine); ret.append(addId); ret.append(indent(level + 1)).append("\"denseValues\": ["); @@ -199,71 +212,71 @@ public class Amf3Exporter { if (i > 0) { ret.append(", "); } - ret.append(amfToString(processedObjects, level + 2, at.getDenseValues().get(i), referenceCount, objectAlias)); + ret.append(amfToString(indentStr, newLine, processedObjects, level + 2, at.getDenseValues().get(i), referenceCount, objectAlias)); } - ret.append("],\r\n"); + ret.append("],").append(newLine); ret.append(indent(level + 1)).append("\"associativeValues\": {"); if (!at.getAssociativeValues().isEmpty()) { - ret.append("\r\n"); + ret.append(newLine); } for (int i = 0; i < at.getAssociativeValues().size(); i++) { Pair p = at.getAssociativeValues().get(i); - ret.append(indent(level + 2)).append(amfToString(processedObjects, level + 1, p.getFirst(), referenceCount, objectAlias)).append(" : ").append(amfToString(processedObjects, level + 1, p.getSecond(), referenceCount, objectAlias)); + ret.append(indent(level + 2)).append(amfToString(indentStr, newLine, processedObjects, level + 1, p.getFirst(), referenceCount, objectAlias)).append(" : ").append(amfToString(indentStr, newLine, processedObjects, level + 1, p.getSecond(), referenceCount, objectAlias)); if (i < at.getAssociativeValues().size() - 1) { ret.append(","); } - ret.append("\r\n"); + ret.append(newLine); } if (!at.getAssociativeValues().isEmpty()) { ret.append(indent(level + 1)); } - ret.append("}\r\n"); + ret.append("}").append(newLine); ret.append(indent(level)).append("}"); } else if (object instanceof DictionaryType) { DictionaryType dt = (DictionaryType) object; - ret.append("{\r\n"); - ret.append(indent(level + 1)).append("\"type\": \"Dictionary\",\r\n"); + ret.append("{").append(newLine); + ret.append(indent(level + 1)).append("\"type\": \"Dictionary\",").append(newLine); ret.append(addId); - ret.append(indent(level + 1)).append("\"weakKeys\": ").append(dt.hasWeakKeys()).append(",\r\n"); - ret.append(indent(level + 1)).append("\"entries\": {\r\n"); + ret.append(indent(level + 1)).append("\"weakKeys\": ").append(dt.hasWeakKeys()).append(",").append(newLine); + ret.append(indent(level + 1)).append("\"entries\": {").append(newLine); for (int i = 0; i < dt.getPairs().size(); i++) { Pair pair = dt.getPairs().get(i); - ret.append(indent(level + 1)).append(amfToString(processedObjects, level + 1, pair.getFirst(), referenceCount, objectAlias)).append(" : ").append(amfToString(processedObjects, level + 1, pair.getSecond(), referenceCount, objectAlias)); + ret.append(indent(level + 1)).append(amfToString(indentStr, newLine, processedObjects, level + 1, pair.getFirst(), referenceCount, objectAlias)).append(" : ").append(amfToString(indentStr, newLine, processedObjects, level + 1, pair.getSecond(), referenceCount, objectAlias)); if (i < dt.getPairs().size() - 1) { ret.append(","); } - ret.append("\r\n"); + ret.append(newLine); } - ret.append(indent(level + 1)).append("}\r\n"); + ret.append(indent(level + 1)).append("}").append(newLine); ret.append(indent(level)).append("}"); } else if (object instanceof ByteArrayType) { ByteArrayType ba = (ByteArrayType) object; byte data[] = ba.getData(); - return "{\r\n" - + indent(level + 1) + "\"type\": \"ByteArray\",\r\n" + return "{" + newLine + + indent(level + 1) + "\"type\": \"ByteArray\"," + newLine + addId - + indent(level + 1) + "\"value\": \"" + javax.xml.bind.DatatypeConverter.printHexBinary(data) + "\"\r\n" + + indent(level + 1) + "\"value\": \"" + javax.xml.bind.DatatypeConverter.printHexBinary(data) + "\"" + newLine + indent(level) + "}"; } else if (object instanceof DateType) { DateType dt = (DateType) object; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SS"); - return "{\r\n" - + indent(level + 1) + "\"type\": \"Date\",\r\n" + return "{" + newLine + + indent(level + 1) + "\"type\": \"Date\"," + newLine + addId - + indent(level + 1) + "\"value\": " + amfToString(processedObjects, level, sdf.format(new Date((long) dt.getVal())), referenceCount, objectAlias) + "\r\n" + + indent(level + 1) + "\"value\": " + amfToString(indentStr, newLine, processedObjects, level, sdf.format(new Date((long) dt.getVal())), referenceCount, objectAlias) + newLine + indent(level) + "}"; } else if (object instanceof XmlDocType) { - return "{\r\n" - + indent(level + 1) + "\"type\": \"XMLDocument\",\r\n" + return "{" + newLine + + indent(level + 1) + "\"type\": \"XMLDocument\"," + newLine + addId - + indent(level + 1) + "\"value\": " + amfToString(processedObjects, level, ((XmlDocType) object).getData(), referenceCount, objectAlias) + "\r\n" + + indent(level + 1) + "\"value\": " + amfToString(indentStr, newLine, processedObjects, level, ((XmlDocType) object).getData(), referenceCount, objectAlias) + newLine + indent(level) + "}"; } else if (object instanceof XmlType) { - return "{\r\n" - + indent(level + 1) + "\"type\": \"XML\",\r\n" + return "{" + newLine + + indent(level + 1) + "\"type\": \"XML\"," + newLine + addId - + indent(level + 1) + "\"value\": " + amfToString(processedObjects, level, ((XmlType) object).getData(), referenceCount, objectAlias) + "\r\n" + + indent(level + 1) + "\"value\": " + amfToString(indentStr, newLine, processedObjects, level, ((XmlType) object).getData(), referenceCount, objectAlias) + newLine + indent(level) + "}"; } else { throw new IllegalArgumentException("Unsupported type: " + object.getClass()); 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 index 8b1c54337..413f619af 100644 --- 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 @@ -21,6 +21,7 @@ import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.helpers.InternalClass; import com.jpexs.decompiler.flash.helpers.LazyObject; import com.jpexs.decompiler.flash.types.annotations.Internal; +import com.jpexs.decompiler.flash.types.annotations.Multiline; import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.Helper; import com.jpexs.helpers.ReflectionTools; @@ -49,6 +50,7 @@ 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.CDATASection; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -98,7 +100,7 @@ public class SwfXmlExporter { } public void exportXml(SWF swf, Document doc, Node node) throws IOException { - generateXml(doc, node, "swf", swf, false, 0); + generateXml(doc, node, "swf", swf, false, 0, false); } public List getSwfFieldsCached(Class cls) { @@ -111,10 +113,18 @@ public class SwfXmlExporter { return result; } - private void generateXml(Document doc, Node node, String name, Object obj, boolean isListItem, int level) { + private void generateXml(Document doc, Node node, String name, Object obj, boolean isListItem, int level, boolean needsCData) { Class cls = obj != null ? obj.getClass() : null; - if (obj != null && (cls == Byte.class || cls == byte.class + if (obj != null && needsCData && cls == String.class) { + + Element objNode = doc.createElement(name); + objNode.setAttribute("type", "String"); + CDATASection cdataNode = doc.createCDATASection((String) obj); + objNode.appendChild(cdataNode); + + node.appendChild(objNode); + } else if (obj != null && (cls == Byte.class || cls == byte.class || cls == Short.class || cls == short.class || cls == Integer.class || cls == int.class || cls == Long.class || cls == long.class @@ -149,7 +159,7 @@ public class SwfXmlExporter { Element listNode = doc.createElement(name); node.appendChild(listNode); for (int i = 0; i < list.size(); i++) { - generateXml(doc, listNode, "item", list.get(i), true, level + 1); + generateXml(doc, listNode, "item", list.get(i), true, level + 1, false); } } else if (cls != null && cls.isArray()) { Class arrayType = cls.getComponentType(); @@ -157,7 +167,7 @@ public class SwfXmlExporter { node.appendChild(arrayNode); int length = Array.getLength(obj); for (int i = 0; i < length; i++) { - generateXml(doc, arrayNode, "item", Array.get(obj, i), true, level + 1); + generateXml(doc, arrayNode, "item", Array.get(obj, i), true, level + 1, false); } } else if (obj != null) { if (obj instanceof LazyObject) { @@ -189,10 +199,11 @@ public class SwfXmlExporter { if (inter != null) { continue; } + Multiline multilineA = f.getAnnotation(Multiline.class); try { f.setAccessible(true); - generateXml(doc, objNode, f.getName(), f.get(obj), false, level + 1); + generateXml(doc, objNode, f.getName(), f.get(obj), false, level + 1, multilineA != null); } catch (IllegalArgumentException | IllegalAccessException ex) { logger.log(Level.SEVERE, null, ex); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SwfXmlImporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SwfXmlImporter.java index 22de25ad6..691ef5c7a 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SwfXmlImporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SwfXmlImporter.java @@ -37,6 +37,7 @@ import com.jpexs.decompiler.flash.abc.types.traits.TraitFunction; import com.jpexs.decompiler.flash.abc.types.traits.TraitMethodGetterSetter; import com.jpexs.decompiler.flash.abc.types.traits.TraitSlotConst; import com.jpexs.decompiler.flash.abc.types.traits.Traits; +import com.jpexs.decompiler.flash.amf.amf3.Amf3Value; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.TagTypeInfo; import com.jpexs.decompiler.flash.types.ALPHABITMAPDATA; @@ -119,6 +120,7 @@ import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -249,7 +251,9 @@ public class SwfXmlImporter { private Object processObject(Element element, Class requiredType, SWF swf, Tag tag) throws IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException { String type = element.getAttribute("type"); - if (type != null && !type.isEmpty()) { + if ("String".equals(type)) { + return element.getTextContent(); + } else if (type != null && !type.isEmpty()) { Object childObj = createObject(type, swf, tag); if (childObj instanceof Tag) { tag = (Tag) childObj; @@ -304,7 +308,7 @@ public class SwfXmlImporter { AVM2ConstantPool.class, Decimal.class, Namespace.class, NamespaceSet.class, Multiname.class, MethodInfo.class, MetadataInfo.class, ValueKind.class, InstanceInfo.class, Traits.class, TraitClass.class, TraitFunction.class, TraitMethodGetterSetter.class, TraitSlotConst.class, ClassInfo.class, ScriptInfo.class, MethodBody.class, - ABCException.class, ABCVersion.class}; + ABCException.class, ABCVersion.class, Amf3Value.class}; for (Class cls2 : knownObjects) { if (!ReflectionTools.canInstantiateDefaultConstructor(cls2)) { System.err.println("Can't instantiate: " + cls2.getName());