AMF3 reading, toString

AMF3 writing stub
TODO: Long strings, Externizable, finish writing
This commit is contained in:
Jindra Petřík
2016-07-17 07:57:42 +02:00
parent 5ccbc424f6
commit b6956dfde5
8 changed files with 901 additions and 0 deletions

View File

@@ -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<String> 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<String, ObjectTypeSerializeHandler> serializers) throws IOException {
return readValue(serializers, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
}
private Object readValue(Map<String, ObjectTypeSerializeHandler> serializers,
List<Object> objectTable,
List<Traits> traitsTable,
List<String> 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<Pair<String, Object>> assocPart = new ArrayList<>();
List<Object> 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<String> 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<Pair<String, Object>> sealedMembers = new ArrayList<>();
List<Pair<String, Object>> 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<Object> 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<Long> 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<int> 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<int> 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<Long> 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<uint> 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<uint> 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<Double> 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<double> 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<double> 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<Object> 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<Object> 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<Object> 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<Pair<Object, Object>> 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;
}
}
}

View File

@@ -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");
}
}

View File

@@ -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<Object, Integer> refCount = new HashMap<>();
List<Object> objectList = new ArrayList<>();
Map<Object, String> 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<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;
}
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(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, 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<String, Object> 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<String, Object> 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<String, Object> 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<Object, Object> 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();
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -0,0 +1,20 @@
package com.jpexs.decompiler.flash.amf.amf3;
public class Pair<T1, T2> {
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;
}
}

View File

@@ -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<String> sealedMemberNames;
public Traits(String className, boolean dynamic, List<String> sealedMemberNames) {
this.className = className;
this.dynamic = dynamic;
this.sealedMemberNames = sealedMemberNames;
}
public String getClassName() {
return className;
}
public boolean isDynamic() {
return dynamic;
}
public List<String> getSealedMemberNames() {
return sealedMemberNames;
}
public void setClassName(String className) {
this.className = className;
}
public void setDynamic(boolean dynamic) {
this.dynamic = dynamic;
}
public void setSealedMemberNames(List<String> sealedMemberNames) {
this.sealedMemberNames = sealedMemberNames;
}
}

View File

@@ -0,0 +1,8 @@
package com.jpexs.decompiler.flash.amf.amf3;
import java.util.List;
public interface WithSubValues {
public List<Object> getSubValues();
}