package com.jpexs.decompiler.flash.exporters.amf.amf3; import com.jpexs.decompiler.flash.amf.amf3.WithSubValues; import com.jpexs.decompiler.flash.amf.amf3.types.AbstractVectorType; import com.jpexs.decompiler.flash.amf.amf3.types.ArrayType; import com.jpexs.decompiler.flash.amf.amf3.types.BasicType; import com.jpexs.decompiler.flash.amf.amf3.types.ByteArrayType; import com.jpexs.decompiler.flash.amf.amf3.types.DateType; import com.jpexs.decompiler.flash.amf.amf3.types.DictionaryType; import com.jpexs.decompiler.flash.amf.amf3.types.ObjectType; import com.jpexs.decompiler.flash.amf.amf3.types.XmlDocType; import com.jpexs.decompiler.flash.amf.amf3.types.XmlType; import com.jpexs.decompiler.flash.ecma.EcmaScript; import com.jpexs.helpers.Helper; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; public class Amf3Exporter { /** * 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(indentStr, newLine, new ArrayList<>(), 0, amfValue, refCount, objectAlias); } /** * Populates all object instances and their references and generates aliases * * @param object Object to be populated * @param referenceCount Result: Map of reference number * @param objectList Result: List of all found object instances * @param objectAlias Result: Map of assigned object names */ public static void populateObjects(Object object, Map<Object, Integer> referenceCount, List<Object> objectList, Map<Object, String> objectAlias) { if (((List<? extends Class>) Arrays.asList(String.class, Long.class, Double.class, BasicType.class, Boolean.class)).contains(object.getClass())) { return; } if (object instanceof BasicType) { return; } int prevRef = 0; if (referenceCount.containsKey(object)) { prevRef = referenceCount.get(object); } referenceCount.put(object, prevRef + 1); if (prevRef == 0) { if (object instanceof WithSubValues) { List<Object> subvalues = ((WithSubValues) object).getSubValues(); for (Object o : subvalues) { populateObjects(o, referenceCount, objectList, objectAlias); } } objectList.add(object); objectAlias.put(object, "obj" + objectList.size()); } } private static String indent(int level) { String na = ""; for (int i = 0; i < level; i++) { na += " "; } return na; } /** * Processes one level of object and converts it to string * * @param processedObjects * @param level * @param object * @param referenceCount * @param objectAlias * @return */ 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) + "\""; } if (((List<? extends Class>) Arrays.asList(Long.class, Double.class, Boolean.class)).contains(object.getClass())) { return EcmaScript.toString(object); } if (object instanceof BasicType) { return object.toString(); } StringBuilder ret = new StringBuilder(); Integer refCount = referenceCount.get(object); if (refCount > 1 && processedObjects.contains(object)) { ret.append("#").append(objectAlias.get(object)); return ret.toString(); } processedObjects.add(object); String addId = refCount > 1 ? indent(level + 1) + "\"id\": \"" + objectAlias.get(object) + "\"," + newLine : ""; if (object instanceof AbstractVectorType) { AbstractVectorType avt = (AbstractVectorType) object; 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(",").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(indentStr, newLine, processedObjects, level + 1, avt.getValues().get(i), referenceCount, objectAlias)); } ret.append("]").append(newLine); ret.append(indent(level)).append("}"); } else if (object instanceof ObjectType) { ObjectType ot = (ObjectType) object; 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(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").append(newLine); } else { 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\": {").append(newLine); { int i = 0; for (String key : ot.sealedMembersKeySet()) { Object val = ot.getSealedMember(key); ret.append(indent(level + 2)).append(amfToString(indentStr, newLine, processedObjects, level + 2, key, referenceCount, objectAlias)).append(":").append(amfToString(indentStr, newLine, processedObjects, level + 1, val, referenceCount, objectAlias)); if (i < ot.serializedMembersSize() - 1) { ret.append(",").append(newLine); } else { ret.append(newLine); } i++; } } ret.append(indent(level + 1)).append("}"); ret.append(newLine); } } } else { ret.append(indent(level + 1)).append("\"dynamic\": ").append(ot.isDynamic()).append(",").append(newLine); //if (!ot.getSealedMembers().isEmpty()) { ret.append(indent(level + 1)).append("\"sealedMembers\": {").append(newLine); { int i = 0; for (String key : ot.sealedMembersKeySet()) { Object val = ot.getSealedMember(key); ret.append(indent(level + 2)).append(amfToString(indentStr, newLine, processedObjects, level + 2, key, referenceCount, objectAlias)).append(":").append(amfToString(indentStr, newLine, processedObjects, level + 1, val, referenceCount, objectAlias)); if (i < ot.sealedMembersSize() - 1) { ret.append(",").append(newLine); } else { ret.append(newLine); } i++; } } ret.append(indent(level + 1)).append("}"); //if (!ot.getDynamicMembers().isEmpty()) { ret.append(","); //} ret.append(newLine); //} //if (!ot.getDynamicMembers().isEmpty()) { ret.append(indent(level + 1)).append("\"dynamicMembers\": {").append(newLine); { int i = 0; for (String key : ot.dynamicMembersKeySet()) { Object val = ot.getDynamicMember(key); ret.append(indent(level + 2)).append(amfToString(indentStr, newLine, processedObjects, level + 2, key, referenceCount, objectAlias)); ret.append(":"); ret.append(amfToString(indentStr, newLine, processedObjects, level + 2, val, referenceCount, objectAlias)); if (i < ot.dynamicMembersSize() - 1) { ret.append(","); } ret.append(newLine); i++; } } ret.append(indent(level + 1)).append("}").append(newLine); //} } ret.append(indent(level)).append("}"); } else if (object instanceof ArrayType) { ArrayType at = (ArrayType) object; ret.append("{").append(newLine); ret.append(indent(level + 1)).append("\"type\": \"Array\",").append(newLine); ret.append(addId); ret.append(indent(level + 1)).append("\"denseValues\": ["); for (int i = 0; i < at.getDenseValues().size(); i++) { if (i > 0) { ret.append(", "); } ret.append(amfToString(indentStr, newLine, processedObjects, level + 2, at.getDenseValues().get(i), referenceCount, objectAlias)); } ret.append("],").append(newLine); ret.append(indent(level + 1)).append("\"associativeValues\": {"); if (!at.getAssociativeValues().isEmpty()) { ret.append(newLine); } { int i = 0; for (String key : at.associativeKeySet()) { Object val = at.getAssociative(key); ret.append(indent(level + 2)).append(amfToString(indentStr, newLine, processedObjects, level + 1, key, referenceCount, objectAlias)).append(" : ").append(amfToString(indentStr, newLine, processedObjects, level + 1, val, referenceCount, objectAlias)); if (i < at.getAssociativeValues().size() - 1) { ret.append(","); } ret.append(newLine); i++; } } if (!at.getAssociativeValues().isEmpty()) { ret.append(indent(level + 1)); } ret.append("}").append(newLine); ret.append(indent(level)).append("}"); } else if (object instanceof DictionaryType) { DictionaryType dt = (DictionaryType) object; 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(",").append(newLine); ret.append(indent(level + 1)).append("\"entries\": {").append(newLine); { int i = 0; for (Object key : dt.keySet()) { Object val = dt.get(key); ret.append(indent(level + 1)).append(amfToString(indentStr, newLine, processedObjects, level + 1, key, referenceCount, objectAlias)).append(" : ").append(amfToString(indentStr, newLine, processedObjects, level + 1, val, referenceCount, objectAlias)); if (i < dt.size() - 1) { ret.append(","); } ret.append(newLine); i++; } } 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 "{" + newLine + indent(level + 1) + "\"type\": \"ByteArray\"," + newLine + addId + 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 "{" + newLine + indent(level + 1) + "\"type\": \"Date\"," + newLine + addId + 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 "{" + newLine + indent(level + 1) + "\"type\": \"XMLDocument\"," + newLine + addId + indent(level + 1) + "\"value\": " + amfToString(indentStr, newLine, processedObjects, level, ((XmlDocType) object).getData(), referenceCount, objectAlias) + newLine + indent(level) + "}"; } else if (object instanceof XmlType) { return "{" + newLine + indent(level + 1) + "\"type\": \"XML\"," + newLine + addId + 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()); } return ret.toString(); } }