AMF export/import fixes

XML import/export of AMF (+other Multiline) handling
This commit is contained in:
Jindra Petřík
2016-07-23 13:38:53 +02:00
parent e980325719
commit c3314b862c
4 changed files with 111 additions and 73 deletions
@@ -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;
}
}
@@ -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<Object, Integer> refCount = new HashMap<>();
List<Object> objectList = new ArrayList<>();
Map<Object, String> 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<Object> processedObjects, int level, Object object, Map<Object, Integer> referenceCount, Map<Object, String> objectAlias) {
private static String amfToString(String indentStr, String newLine, List<Object> processedObjects, int level, Object object, Map<Object, Integer> referenceCount, Map<Object, String> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<Object, Object> 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());
@@ -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<Field> 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);
}
@@ -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());