mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-06-17 17:41:52 +00:00
AMF3 reading, toString
AMF3 writing stub TODO: Long strings, Externizable, finish writing
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.jpexs.decompiler.flash.amf.amf3;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface WithSubValues {
|
||||
|
||||
public List<Object> getSubValues();
|
||||
}
|
||||
Reference in New Issue
Block a user