From b6956dfde5305ed9a87478b3f0ebb17b82103fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sun, 17 Jul 2016 07:57:42 +0200 Subject: [PATCH] AMF3 reading, toString AMF3 writing stub TODO: Long strings, Externizable, finish writing --- .../flash/amf/amf3/AMF3InputStream.java | 477 ++++++++++++++++++ .../flash/amf/amf3/AMF3OutputStream.java | 79 +++ .../decompiler/flash/amf/amf3/AMF3Tools.java | 241 +++++++++ .../decompiler/flash/amf/amf3/Marker.java | 23 + .../amf/amf3/ObjectTypeSerializeHandler.java | 12 + .../jpexs/decompiler/flash/amf/amf3/Pair.java | 20 + .../decompiler/flash/amf/amf3/Traits.java | 41 ++ .../flash/amf/amf3/WithSubValues.java | 8 + 8 files changed, 901 insertions(+) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/AMF3InputStream.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/AMF3OutputStream.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/AMF3Tools.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Marker.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/ObjectTypeSerializeHandler.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Pair.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Traits.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/WithSubValues.java diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/AMF3InputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/AMF3InputStream.java new file mode 100644 index 000000000..368232a91 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/AMF3InputStream.java @@ -0,0 +1,477 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +import com.jpexs.decompiler.flash.amf.amf3.types.ArrayType; +import com.jpexs.decompiler.flash.amf.amf3.types.XmlType; +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.VectorObjectType; +import com.jpexs.decompiler.flash.amf.amf3.types.VectorIntType; +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.VectorDoubleType; +import com.jpexs.decompiler.flash.amf.amf3.types.DictionaryType; +import com.jpexs.decompiler.flash.amf.amf3.types.VectorUIntType; +import com.jpexs.decompiler.flash.amf.amf3.types.BasicType; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class AMF3InputStream extends InputStream { + + public final static Logger LOGGER = Logger.getLogger(AMF3InputStream.class.getName()); + private final InputStream is; + + public AMF3InputStream(InputStream is) { + this.is = is; + } + + public int readU8() throws IOException { + return safeRead(); + } + + public int readU16() throws IOException { + int b1 = safeRead(); + int b2 = safeRead(); + return (b1 << 8) + b2; + } + + public long readU32() throws IOException { + int b1 = safeRead(); + int b2 = safeRead(); + int b3 = safeRead(); + int b4 = safeRead(); + + return (b1 << 24) + (b2 << 16) + (b3 << 8) + b4; + } + + private long readLong() throws IOException { + byte[] readBuffer = new byte[8]; + for (int i = 0; i < 8; i++) { + readBuffer[i] = (byte) safeRead(); + } + return (((long) readBuffer[0] << 56) + + ((long) (readBuffer[1] & 0xff) << 48) + + ((long) (readBuffer[2] & 0xff) << 40) + + ((long) (readBuffer[3] & 0xff) << 32) + + ((long) (readBuffer[4] & 0xff) << 24) + + ((readBuffer[5] & 0xff) << 16) + + ((readBuffer[6] & 0xff) << 8) + + ((readBuffer[7] & 0xff))); + } + + public double readDouble() throws IOException { + long lval = readLong(); + double ret = Double.longBitsToDouble(lval); + return ret; + } + + public long readU29() throws IOException { + long val = 0; + for (int i = 1; i <= 4; i++) { + int b = safeRead(); + if (i == 4) { + val = ((val << 8) + b); + } else { + val = (val << 7) + (b & 0x7F); + if ((b & 0x80) != 0x80) { + break; + } + } + } + return val; + } + + private long signExtend(long val, int size) { + if (((val >> (size - 1)) & 1) == 1) { //has sign bit + long mask = (1 << size) - 1; // 111111...up to size + long positiveVal = (~(val - 1)) & mask; + long negativeVal = -positiveVal; + return negativeVal; + } + return val; + } + + private String readUtf8Char(int byteLength) throws IOException { + if (byteLength == 0) { + return ""; + } + byte buf[] = new byte[(int) byteLength]; //how about long strings(?), will the int length be enough? + int cnt = is.read(buf); + if (cnt < buf.length) { + throw new IOException("EOF"); + } + String retString = new String(buf, "UTF-8"); + return retString; + } + + public String readUtf8Vr(List stringTable) throws IOException { + long u = readU29(); + int stringNoRefFlag = (int) (u & 1); + if (stringNoRefFlag == 1) { + int byteLength = (int) (u >> 1); //TODO: long strings, int is not enough for them + String retString = readUtf8Char(byteLength); + stringTable.add(retString); + LOGGER.log(Level.FINE, "Read string: \"{0}\"", retString); + return retString; + } else { //flag==0 + int stringRefTableIndex = (int) (u >> 1); + + String retString = stringTable.get(stringRefTableIndex); + LOGGER.log(Level.FINE, "Read string: reference({0}):" + retString, stringRefTableIndex); + return retString; + + } + } + + private int safeRead() throws IOException { + int ret = read(); + if (ret == -1) { + throw new IOException("EOF"); + } + return ret; + } + + @Override + public int read() throws IOException { + return is.read(); + } + + public Object readValue() throws IOException { + return readValue(new HashMap<>()); + } + + public Object readValue(Map serializers) throws IOException { + return readValue(serializers, new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + } + + private Object readValue(Map serializers, + List objectTable, + List traitsTable, + List stringTable + ) throws IOException { + + int marker = readU8(); + switch (marker) { + case Marker.UNDEFINED: + LOGGER.log(Level.FINE, "Read value: undefined"); + return BasicType.UNDEFINED; + case Marker.NULL: + LOGGER.log(Level.FINE, "Read value: null"); + return BasicType.NULL; + case Marker.FALSE: + LOGGER.log(Level.FINE, "Read value: false"); + return Boolean.FALSE; + case Marker.TRUE: + LOGGER.log(Level.FINE, "Read value: true"); + return Boolean.TRUE; + case Marker.INTEGER: + LOGGER.log(Level.FINE, "Read value: integer"); + long ival = signExtend(readU29(), 29); + LOGGER.log(Level.FINER, "Integer value: {0}", ival); + return ival; + case Marker.DOUBLE: + LOGGER.log(Level.FINE, "Read value: double"); + double dval = readDouble(); + LOGGER.log(Level.FINER, "Double value: {0}", "" + dval); + return dval; + case Marker.STRING: + LOGGER.log(Level.FINE, "Read value: string"); + String sval = readUtf8Vr(stringTable); + LOGGER.log(Level.FINER, "String value: {0}", sval); + return sval; + case Marker.XML_DOC: + LOGGER.log(Level.FINE, "Read value: xml_doc"); + long xmlDocU29 = readU29(); + int xmlDocNoRefFlag = (int) (xmlDocU29 & 1); + if (xmlDocNoRefFlag == 1) { + long byteLength = xmlDocU29 >> 1; + String xval; + if (xmlDocU29 == 0) { + xval = ""; + } else { + byte buf[] = new byte[(int) byteLength]; //TODO: long strings, int is not enough for them + int cnt = is.read(buf); + if (cnt < buf.length) { + throw new IOException("EOF"); + } + xval = new String(buf, "UTF-8"); + } + LOGGER.log(Level.FINER, "XmlDoc value: {0}", xval); + XmlDocType retXmlDoc = new XmlDocType(xval); + objectTable.add(retXmlDoc); + return retXmlDoc; + } else { + int refIndexXmlDoc = (int) (xmlDocU29 >> 1); + LOGGER.log(Level.FINER, "XmlDoc value: reference({0})", refIndexXmlDoc); + return objectTable.get(refIndexXmlDoc); //What if it's not XmlRef? + } + case Marker.DATE: + LOGGER.log(Level.FINE, "Read value: date"); + long dateU29 = readU29(); + int dateNoRefFlag = (int) (dateU29 & 1); + if (dateNoRefFlag == 1) { + //remaining bits of dateU29 are not used + double dtval = readDouble(); + DateType retDate = new DateType(dtval); + LOGGER.log(Level.FINER, "Date value: {0}", retDate); + objectTable.add(retDate); + return retDate; + } else { + int refIndexDate = (int) (dateU29 >> 1); + LOGGER.log(Level.FINER, "Date value: reference({0})", refIndexDate); + return objectTable.get(refIndexDate); //What if it's not Date? + } + case Marker.ARRAY: + LOGGER.log(Level.FINE, "Read value: array"); + long arrayU29 = readU29(); + int arrayNoRefFlag = (int) (arrayU29 & 1); + if (arrayNoRefFlag == 1) { + int denseCount = (int) (arrayU29 >> 1); + LOGGER.log(Level.FINEST, "Array value: denseCount={0}", new Object[]{denseCount}); + List> assocPart = new ArrayList<>(); + List densePart = new ArrayList<>(); + ArrayType retArray = new ArrayType(densePart, assocPart); + objectTable.add(retArray); //add before processing elements which may reference this + + while (true) { + String key = readUtf8Vr(stringTable); + if (key.isEmpty()) { + break; + } else { + Object val = readValue(serializers, objectTable, traitsTable, stringTable); + assocPart.add(new Pair<>(key, val)); + } + } + LOGGER.log(Level.FINEST, "Array value: assocSize={0}", new Object[]{assocPart.size()}); + + for (int i = 0; i < denseCount; i++) { + densePart.add(readValue(serializers, objectTable, traitsTable, stringTable)); + } + LOGGER.log(Level.FINER, "Array value: dense_size={0},assocSize={1}", new Object[]{densePart.size(), assocPart.size()}); + return retArray; + + } else { + int refIndexArray = (int) (arrayU29 >> 1); + LOGGER.log(Level.FINER, "Array value: reference({0})", refIndexArray); + return objectTable.get(refIndexArray); //What if it's not Array? + } + + case Marker.OBJECT: + LOGGER.log(Level.FINE, "Read value: object"); + long objectU29 = readU29(); + int objectNoRefFlag = (int) (objectU29 & 1); + if (objectNoRefFlag == 1) { + Traits traits; + int objectTraitsNoRefFlag = (int) ((objectU29 >> 1) & 1); + if (objectTraitsNoRefFlag == 1) { + int objectTraitsExtFlag = (int) ((objectU29 >> 2) & 1); + ObjectType retObjectType; + if (objectTraitsExtFlag == 1) { + String className = readUtf8Vr(stringTable); + if (!serializers.containsKey(className)) { + throw new IOException("No (de)serializer for class " + className + " found"); + } + retObjectType = serializers.get(className).readObject(className, is); + LOGGER.log(Level.FINER, "Object/Traits value: customSerialized"); + traits = new Traits(className, true, new ArrayList<>());// FIXME??? + traitsTable.add(traits); + //TODO: verify this + } else { + int dynamicFlag = (int) ((objectU29 >> 3) & 1); + int numSealed = (int) (objectU29 >> 4); + LOGGER.log(Level.FINEST, "object dynamicFlag:{0}", dynamicFlag); + LOGGER.log(Level.FINEST, "object numSealed:{0}", numSealed); + String className = readUtf8Vr(stringTable); + LOGGER.log(Level.FINEST, "object className:{0}", className); + List sealedMemberNames = new ArrayList<>(); + for (int i = 0; i < numSealed; i++) { + sealedMemberNames.add(readUtf8Vr(stringTable)); + } + traits = new Traits(className, dynamicFlag == 1, sealedMemberNames); + } + + } else { + int refIndexTraits = (int) (objectU29 >> 2); + traits = traitsTable.get(refIndexTraits); + LOGGER.log(Level.FINER, "Traits value: reference({0}) - traitsize={1}", new Object[]{refIndexTraits, traits.getSealedMemberNames().size()}); + } + + if (objectTraitsNoRefFlag == 1) { + traitsTable.add(traits); + } + List> sealedMembers = new ArrayList<>(); + List> dynamicMembers = new ArrayList<>(); + + Object retObjectType = new ObjectType(traits.isDynamic(), sealedMembers, dynamicMembers, traits.getClassName()); + objectTable.add(retObjectType); //add it before any subvalue can reference it + List sealedMemberValues = new ArrayList<>(); + for (int i = 0; i < traits.getSealedMemberNames().size(); i++) { + sealedMemberValues.add(readValue(serializers, objectTable, traitsTable, stringTable)); + } + + for (int i = 0; i < traits.getSealedMemberNames().size(); i++) { + sealedMembers.add(new Pair<>(traits.getSealedMemberNames().get(i), sealedMemberValues.get(i))); + } + if (traits.isDynamic()) { + String dynamicMemberName; + while (!(dynamicMemberName = readUtf8Vr(stringTable)).isEmpty()) { + Object dynamicMemberValue = readValue(serializers, objectTable, traitsTable, stringTable); + dynamicMembers.add(new Pair<>(dynamicMemberName, dynamicMemberValue)); + } + } + + LOGGER.log(Level.FINER, "Object value: dynamic={0},className={1},sealedSize={2},dynamicSize={3}", new Object[]{traits.isDynamic(), traits.getClassName(), sealedMembers.size(), dynamicMembers.size()}); + return retObjectType; + } else { + int refIndexObject = (int) (objectU29 >> 1); + LOGGER.log(Level.FINER, "Object value: reference({0})", refIndexObject); + return objectTable.get(refIndexObject); + } + case Marker.XML: + LOGGER.log(Level.FINE, "Read value: xml"); + long xmlU29 = readU29(); + int xmlNoRefFlag = (int) (xmlU29 & 1); + if (xmlNoRefFlag == 1) { + int byteLength = (int) (xmlU29 >> 1); //TODO: long strings, int is not enough for them + String xString = readUtf8Char(byteLength); + XmlType retXmlType = new XmlType(xString); + LOGGER.log(Level.FINER, "Xml value: {0}", xString); + objectTable.add(retXmlType); + return retXmlType; + } else { + int refIndexXml = (int) (xmlU29 >> 1); + LOGGER.log(Level.FINER, "XML value: reference({0})", refIndexXml); + return objectTable.get(refIndexXml); + } + case Marker.BYTE_ARRAY: + LOGGER.log(Level.FINE, "Read value: bytearray"); + long byteArrayU29 = readU29(); + int byteArrayNoRefFlag = (int) (byteArrayU29 & 1); + if (byteArrayNoRefFlag == 1) { + int byteArrayLength = (int) (byteArrayU29 >> 1); + byte byteArrayBuf[] = new byte[byteArrayLength]; + if (is.read(byteArrayBuf) != byteArrayLength) { + throw new IOException("EOF"); + } + + LOGGER.log(Level.FINER, "ByteArray value: bytes[{0}]", byteArrayLength); + ByteArrayType retByteArrayType = new ByteArrayType(byteArrayBuf); + objectTable.add(retByteArrayType); + return retByteArrayType; + } else { + int refIndexByteArray = (int) (byteArrayU29 >> 1); + LOGGER.log(Level.FINER, "ByteArray value: reference({0})", refIndexByteArray); + return objectTable.get(refIndexByteArray); + } + case Marker.VECTOR_INT: + LOGGER.log(Level.FINE, "Read value: vector_int"); + long vectorIntU29 = readU29(); + int vectorIntNoRefFlag = (int) (vectorIntU29 & 1); + if (vectorIntNoRefFlag == 1) { + int vectorIntCountItems = (int) (vectorIntU29 >> 1); + int fixed = readU8(); + List vals = new ArrayList<>(); + for (int i = 0; i < vectorIntCountItems; i++) { + vals.add(readU32()); + } + VectorIntType retVectorInt = new VectorIntType(fixed == 1, vals); + LOGGER.log(Level.FINER, "Vector value: fixed={0}, size={1}]", new Object[]{fixed, vectorIntCountItems}); + objectTable.add(retVectorInt); + return retVectorInt; + } else { + int refIndexVectorInt = (int) (vectorIntU29 >> 1); + LOGGER.log(Level.FINER, "Vector value: reference({0})", refIndexVectorInt); + return objectTable.get(refIndexVectorInt); + } + case Marker.VECTOR_UINT: + LOGGER.log(Level.FINE, "Read value: vector_uint"); + long vectorUIntU29 = readU29(); + int vectorUIntNoRefFlag = (int) (vectorUIntU29 & 1); + if (vectorUIntNoRefFlag == 1) { + int vectorUIntCountItems = (int) (vectorUIntU29 >> 1); + int fixed = readU8(); + List vals = new ArrayList<>(); + for (int i = 0; i < vectorUIntCountItems; i++) { + vals.add(signExtend(readU32(), 32)); + } + VectorUIntType retVectorUInt = new VectorUIntType(fixed == 1, vals); + LOGGER.log(Level.FINER, "Vector value: fixed={0}, size={1}]", new Object[]{fixed, vectorUIntCountItems}); + objectTable.add(retVectorUInt); + return retVectorUInt; + } else { + int refIndexVectorUInt = (int) (vectorUIntU29 >> 1); + LOGGER.log(Level.FINER, "Vector value: reference({0})", refIndexVectorUInt); + return objectTable.get(refIndexVectorUInt); + } + case Marker.VECTOR_DOUBLE: + LOGGER.log(Level.FINE, "Read value: vector_double"); + long vectorDoubleU29 = readU29(); + int vectorDoubleNoRefFlag = (int) (vectorDoubleU29 & 1); + if (vectorDoubleNoRefFlag == 1) { + int vectorDoubleCountItems = (int) (vectorDoubleU29 >> 1); + int fixed = readU8(); + List vals = new ArrayList<>(); + for (int i = 0; i < vectorDoubleCountItems; i++) { + vals.add(readDouble()); + } + VectorDoubleType retVectorDouble = new VectorDoubleType(fixed == 1, vals); + LOGGER.log(Level.FINER, "Vector value: fixed={0}, size={1}]", new Object[]{fixed, vectorDoubleCountItems}); + objectTable.add(retVectorDouble); + return retVectorDouble; + } else { + int refIndexVectorDouble = (int) (vectorDoubleU29 >> 1); + LOGGER.log(Level.FINER, "Vector value: reference({0})", refIndexVectorDouble); + return objectTable.get(refIndexVectorDouble); + } + case Marker.VECTOR_OBJECT: + LOGGER.log(Level.FINE, "Read value: vector_object"); + long vectorObjectU29 = readU29(); + int vectorObjectNoRefFlag = (int) (vectorObjectU29 & 1); + if (vectorObjectNoRefFlag == 1) { + int vectorObjectCountItems = (int) (vectorObjectU29 >> 1); + int fixed = readU8(); + String objectTypeName = readUtf8Vr(stringTable); //uses "*" for any type + List vals = new ArrayList<>(); + for (int i = 0; i < vectorObjectCountItems; i++) { + vals.add(readValue(serializers, objectTable, traitsTable, stringTable)); + } + VectorObjectType retVectorObject = new VectorObjectType(fixed == 1, objectTypeName, vals); + LOGGER.log(Level.FINER, "Vector value: fixed={0}, size={1}, typeName:{2}]", new Object[]{fixed, vectorObjectCountItems, objectTypeName}); + objectTable.add(retVectorObject); + return retVectorObject; + } else { + int refIndexVectorObject = (int) (vectorObjectU29 >> 1); + LOGGER.log(Level.FINER, "Vector value: reference({0})", refIndexVectorObject); + return objectTable.get(refIndexVectorObject); + } + case Marker.DICTIONARY: + long dictionaryObjectU29 = readU29(); + int dictionaryNoRefFlag = (int) (dictionaryObjectU29 & 1); + if (dictionaryNoRefFlag == 1) { + int numEntries = (int) (dictionaryObjectU29 >> 1); + int weakKeys = readU8(); + List> data = new ArrayList<>(); + for (int i = 0; i < numEntries; i++) { + Object key = readValue(serializers, objectTable, traitsTable, stringTable); + Object val = readValue(serializers, objectTable, traitsTable, stringTable); + data.add(new Pair<>(key, val)); + } + DictionaryType retDictionary = new DictionaryType(weakKeys == 1, data); + objectTable.add(retDictionary); + return retDictionary; + } else { + int refIndexDictionary = (int) (dictionaryObjectU29 >> 1); + LOGGER.log(Level.FINER, "Dictionary value: reference({0})", refIndexDictionary); + return objectTable.get(refIndexDictionary); + } + default: + return null; + } + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/AMF3OutputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/AMF3OutputStream.java new file mode 100644 index 000000000..0c93ce193 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/AMF3OutputStream.java @@ -0,0 +1,79 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +import java.io.IOException; +import java.io.OutputStream; + +public class AMF3OutputStream extends OutputStream { + + private final OutputStream os; + + public AMF3OutputStream(OutputStream os) { + this.os = os; + } + + public void writeUI8(int v) throws IOException { + write(v); + } + + public void writeUI16(int v) throws IOException { + int b1 = (v >> 8) & 0xff; + int b2 = v & 0xff; + write(b1); + write(b2); + } + + public void writeUI32(long v) throws IOException { + int b1 = (int) ((v >> 24) & 0xff); + int b2 = (int) ((v >> 16) & 0xff); + int b3 = (int) ((v >> 8) & 0xff); + int b4 = (int) (v & 0xff); + + write(b1); + write(b2); + write(b3); + write(b4); + } + + public void writeDouble(double v) throws IOException { + writeUI32(Double.doubleToLongBits(v)); + } + + public void writeUI29(long v) throws IOException { + if (v <= 0x7F) { + write((int) v); + } else if (v <= 0x3FFF) { + int b1 = (int) ((v >> 7) & 0x7F); + int b2 = (int) (v & 0x7F); + write(b1); + write(b2); + } else if (v <= 0x1FFFFF) { + int b1 = (int) ((v >> 14) & 0x7F); + int b2 = (int) ((v >> 7) & 0x7F); + int b3 = (int) (v & 0x7F); + write(b1); + write(b2); + write(b3); + } else if (v <= 0x3FFFFFFF) { + int b1 = (int) ((v >> 21) & 0x7F); + int b2 = (int) ((v >> 14) & 0x7F); + int b3 = (int) ((v >> 7) & 0x7F); + int b4 = (int) (v & 0x7F); + write(b1); + write(b2); + write(b3); + write(b4); + } else { + throw new IllegalArgumentException("Value too long"); + } + } + + @Override + public void write(int v) throws IOException { + os.write(v); + } + + public void writeValue(Object object) throws IOException { + //TODO:!!! + throw new UnsupportedOperationException("Not implemented yet"); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/AMF3Tools.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/AMF3Tools.java new file mode 100644 index 000000000..1bf97de9a --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/AMF3Tools.java @@ -0,0 +1,241 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +import com.jpexs.decompiler.flash.amf.amf3.types.ArrayType; +import com.jpexs.decompiler.flash.amf.amf3.types.XmlType; +import com.jpexs.decompiler.flash.amf.amf3.types.ObjectType; +import com.jpexs.decompiler.flash.amf.amf3.types.AbstractVectorType; +import com.jpexs.decompiler.flash.amf.amf3.types.XmlDocType; +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.BasicType; +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 AMF3Tools { + + /** + * Converts AMF value to something human-readable. + * + * @param obj + * @return + */ + public static String amfToString(Object obj) { + Map refCount = new HashMap<>(); + List objectList = new ArrayList<>(); + Map objectAlias = new HashMap<>(); + populateObjects(obj, refCount, objectList, objectAlias); + return amfToString(new ArrayList<>(), 0, obj, 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 referenceCount, List objectList, Map objectAlias) { + if (((List) Arrays.asList(String.class, Long.class, Double.class, BasicType.class, Boolean.class)).contains(object.getClass())) { + 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 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(List processedObjects, int level, Object object, Map referenceCount, Map objectAlias) { + if (object instanceof String) { + return "\"" + Helper.escapeActionScriptString((String) object) + "\""; + } + if (((List) Arrays.asList(Long.class, Double.class, BasicType.class, Boolean.class)).contains(object.getClass())) { + 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) + "\"\r\n" : ""; + + if (object instanceof AbstractVectorType) { + AbstractVectorType avt = (AbstractVectorType) object; + ret.append("{\r\n"); + ret.append(indent(level + 1)).append("\"type\": \"Vector\",\r\n"); + 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("\"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("]\r\n"); + ret.append(indent(level)).append("}"); + } else if (object instanceof ObjectType) { + ObjectType ot = (ObjectType) object; + ret.append("{\r\n"); + ret.append(indent(level + 1) + "\"type\": \"Object\",\r\n"); + ret.append(addId); + ret.append(indent(level + 1) + "\"className\": " + amfToString(processedObjects, level, ot.getClassName(), referenceCount, objectAlias) + ",\r\n"); + ret.append(indent(level + 1) + "\"dynamic\": " + ot.isDynamic() + ",\r\n"); + if (!ot.getSealedMembers().isEmpty()) { + ret.append(indent(level + 1) + "\"sealedMembers\": {\r\n"); + for (int i = 0; i < ot.getSealedMembers().size(); i++) { + Pair member = ot.getSealedMembers().get(i); + ret.append(indent(level + 2) + amfToString(processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias) + ":" + amfToString(processedObjects, level + 1, member.getSecond(), referenceCount, objectAlias)); + if (i < ot.getSealedMembers().size() - 1) { + ret.append(",\r\n"); + } else { + ret.append("\r\n"); + } + } + ret.append(indent(level + 1) + "}"); + if (!ot.getDynamicMembers().isEmpty()) { + ret.append(","); + } + ret.append("\r\n"); + } + if (!ot.getDynamicMembers().isEmpty()) { + ret.append(indent(level + 1) + "\"dynamicMembers\": {\r\n"); + for (int i = 0; i < ot.getDynamicMembers().size(); i++) { + Pair member = ot.getDynamicMembers().get(i); + ret.append(indent(level + 2) + amfToString(processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias) + ":" + amfToString(processedObjects, level + 2, member.getSecond(), referenceCount, objectAlias)); + if (i < ot.getDynamicMembers().size() - 1) { + ret.append(","); + } + ret.append("\r\n"); + } + ret.append(indent(level + 1) + "}\r\n"); + } + ret.append(indent(level) + "}"); + } else if (object instanceof ArrayType) { + ArrayType at = (ArrayType) object; + ret.append("{\r\n"); + ret.append(indent(level + 1) + "\"type\": \"Array\",\r\n"); + ret.append(indent(level + 1) + "\"denseValues\": ["); + + for (int i = 0; i < at.getDenseValues().size(); i++) { + if (i > 0) { + ret.append(", "); + } + ret.append(amfToString(processedObjects, level + 2, at.getDenseValues().get(i), referenceCount, objectAlias)); + } + ret.append("],\r\n"); + ret.append(indent(level + 1) + "\"associativeValues\": ["); + if (!at.getAssociativeValues().isEmpty()) { + ret.append("\r\n"); + } + for (int i = 0; i < at.getAssociativeValues().size(); i++) { + Pair p = at.getAssociativeValues().get(i); + ret.append(indent(level + 2) + amfToString(processedObjects, level + 1, p.getFirst(), referenceCount, objectAlias) + " : " + amfToString(processedObjects, level + 1, p.getSecond(), referenceCount, objectAlias)); + if (i < at.getAssociativeValues().size() - 1) { + ret.append(","); + } + ret.append("\r\n"); + } + if (!at.getAssociativeValues().isEmpty()) { + ret.append(indent(level + 1)); + } + ret.append("]\r\n"); + ret.append(indent(level) + "}"); + } else if (object instanceof DictionaryType) { + DictionaryType dt = (DictionaryType) object; + ret.append("{\r\n"); + ret.append(indent(level + 1) + "\"type\": \"Dictionary\",\r\n"); + ret.append(addId); + ret.append(indent(level + 1) + "\"value\": {\r\n"); + for (int i = 0; i < dt.getPairs().size(); i++) { + Pair pair = dt.getPairs().get(i); + ret.append(indent(level + 1) + amfToString(processedObjects, level + 1, pair.getFirst(), referenceCount, objectAlias) + " : " + amfToString(processedObjects, level + 1, pair.getSecond(), referenceCount, objectAlias)); + if (i < dt.getPairs().size() - 1) { + ret.append(","); + } + ret.append("\r\n"); + } + ret.append(indent(level + 1) + "}\r\n"); + ret.append(indent(level) + "}"); + } else if (object instanceof ByteArrayType) { + ByteArrayType ba = (ByteArrayType) object; + byte data[] = ba.getData(); + return "{\r\n" + + indent(level + 1) + "\"type\": \"ByteArray\",\r\n" + + addId + + indent(level + 1) + "\"value\": 0x" + javax.xml.bind.DatatypeConverter.printHexBinary(data) + "\r\n" + + 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" + + addId + + indent(level + 1) + "\"value\": " + amfToString(processedObjects, level, sdf.format(new Date((long) dt.getVal())), referenceCount, objectAlias) + "\r\n" + + indent(level) + "}"; + + } else if (object instanceof XmlDocType) { + return "{\r\n" + + indent(level + 1) + "\"type\": \"XML\",\r\n" + + addId + + indent(level + 1) + "\"value\": " + amfToString(processedObjects, level, ((XmlDocType) object).getData(), referenceCount, objectAlias) + "\r\n" + + indent(level) + "}"; + } else if (object instanceof XmlType) { + return "{\r\n" + + indent(level + 1) + "\"type\": \"XMLDocument\",\r\n" + + addId + + indent(level + 1) + "\"value\": " + amfToString(processedObjects, level, ((XmlType) object).getData(), referenceCount, objectAlias) + "\r\n" + + indent(level) + "}"; + } else if (object == BasicType.NULL) { + return "null"; + } else if (object == BasicType.UNDEFINED) { + return "undefined"; + } else { + throw new IllegalArgumentException("Unsupported type: " + object.getClass()); + } + + return ret.toString(); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Marker.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Marker.java new file mode 100644 index 000000000..2ace21d0e --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Marker.java @@ -0,0 +1,23 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +public class Marker { + + public static final int UNDEFINED = 0x00; + public static final int NULL = 0x01; + public static final int FALSE = 0x02; + public static final int TRUE = 0x03; + public static final int INTEGER = 0x04; + public static final int DOUBLE = 0x05; + public static final int STRING = 0x06; + public static final int XML_DOC = 0x07; + public static final int DATE = 0x08; + public static final int ARRAY = 0x09; + public static final int OBJECT = 0x0A; + public static final int XML = 0x0B; + public static final int BYTE_ARRAY = 0x0C; + public static final int VECTOR_INT = 0x0D; + public static final int VECTOR_UINT = 0x0E; + public static final int VECTOR_DOUBLE = 0x0F; + public static final int VECTOR_OBJECT = 0x10; + public static final int DICTIONARY = 0x11; +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/ObjectTypeSerializeHandler.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/ObjectTypeSerializeHandler.java new file mode 100644 index 000000000..67fe64ac2 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/ObjectTypeSerializeHandler.java @@ -0,0 +1,12 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +import com.jpexs.decompiler.flash.amf.amf3.types.ObjectType; +import java.io.InputStream; +import java.io.OutputStream; + +public interface ObjectTypeSerializeHandler { + + public ObjectType readObject(String className, InputStream is); + + public void writeObject(ObjectType val, OutputStream os); +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Pair.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Pair.java new file mode 100644 index 000000000..021814ec2 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Pair.java @@ -0,0 +1,20 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +public class Pair { + + private final T1 first; + private final T2 second; + + public Pair(T1 first, T2 second) { + this.first = first; + this.second = second; + } + + public T1 getFirst() { + return first; + } + + public T2 getSecond() { + return second; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Traits.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Traits.java new file mode 100644 index 000000000..2ebd59327 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Traits.java @@ -0,0 +1,41 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +import java.util.List; + +public class Traits { + + private String className; + private boolean dynamic; + private List sealedMemberNames; + + public Traits(String className, boolean dynamic, List sealedMemberNames) { + this.className = className; + this.dynamic = dynamic; + this.sealedMemberNames = sealedMemberNames; + } + + public String getClassName() { + return className; + } + + public boolean isDynamic() { + return dynamic; + } + + public List getSealedMemberNames() { + return sealedMemberNames; + } + + public void setClassName(String className) { + this.className = className; + } + + public void setDynamic(boolean dynamic) { + this.dynamic = dynamic; + } + + public void setSealedMemberNames(List sealedMemberNames) { + this.sealedMemberNames = sealedMemberNames; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/WithSubValues.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/WithSubValues.java new file mode 100644 index 000000000..4baa7973c --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/WithSubValues.java @@ -0,0 +1,8 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +import java.util.List; + +public interface WithSubValues { + + public List getSubValues(); +}