Merge pull request #35 from jindrapetrik/amf

Amf
This commit is contained in:
Jindra Petřík
2016-07-23 10:26:15 +02:00
committed by GitHub
73 changed files with 6738 additions and 1510 deletions

View File

@@ -118,6 +118,9 @@ import com.jpexs.decompiler.flash.action.swf7.ActionExtends;
import com.jpexs.decompiler.flash.action.swf7.ActionImplementsOp;
import com.jpexs.decompiler.flash.action.swf7.ActionThrow;
import com.jpexs.decompiler.flash.action.swf7.ActionTry;
import com.jpexs.decompiler.flash.amf.amf3.Amf3InputStream;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Value;
import com.jpexs.decompiler.flash.amf.amf3.NoSerializerExistsException;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.dumpview.DumpInfo;
import com.jpexs.decompiler.flash.tags.CSMTextSettingsTag;
@@ -758,6 +761,23 @@ public class SWFInputStream implements AutoCloseable {
return ret;
}
/**
* Reads AMF3 encoded value from the stream
*
* @param name
* @return
* @throws IOException
*/
public Amf3Value readAmf3Object(String name) throws IOException {
Amf3InputStream ai = new Amf3InputStream(is);
ai.dumpInfo = this.dumpInfo;
try {
return new Amf3Value(ai.readValue("amfData"));
} catch (NoSerializerExistsException nse) {
return new Amf3Value(nse.getIncompleteData());
}
}
/**
* Reads byte range from the stream
*

View File

@@ -16,6 +16,10 @@
*/
package com.jpexs.decompiler.flash;
import com.jpexs.decompiler.flash.amf.amf3.Amf3OutputStream;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Value;
import com.jpexs.decompiler.flash.amf.amf3.NoSerializerExistsException;
import com.jpexs.decompiler.flash.amf.amf3.ObjectTypeSerializeHandler;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.tags.DefineBitsLosslessTag;
import com.jpexs.decompiler.flash.tags.Tag;
@@ -81,7 +85,9 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.Deflater;
@@ -1946,4 +1952,31 @@ public class SWFOutputStream extends OutputStream {
writeUB(5, (value >> 11) & 0xff);
writeUB(5, (value >> 3) & 0xff);
}
/**
* Writes AMF3 encoded value. Warning: Correct serializer needs to be passed
* as second parameter when using IExternalizable
*
* @param value
* @param serializers Map className=>Serializer for classes implementing
* IExternalizable
* @throws IOException
* @throws NoSerializerExistsException
*/
public void writeAmf3Object(Amf3Value value, Map<String, ObjectTypeSerializeHandler> serializers) throws IOException, NoSerializerExistsException {
Amf3OutputStream ao = new Amf3OutputStream(os);
ao.writeValue(value.getValue(), serializers);
}
/**
* Writes AMF3 encoded value. Warning: When the object implements
* IExternalizable, you need to pass serializer as second parameter
*
* @param value
* @throws IOException
* @throws NoSerializerExistsException
*/
public void writeAmf3Object(Amf3Value value) throws IOException, NoSerializerExistsException {
writeAmf3Object(value, new HashMap<>());
}
}

View File

@@ -0,0 +1,867 @@
package com.jpexs.decompiler.flash.amf.amf3;
import com.jpexs.decompiler.flash.EndOfStreamException;
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 com.jpexs.decompiler.flash.dumpview.DumpInfo;
import com.jpexs.decompiler.flash.ecma.EcmaScript;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.MemoryInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
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 MemoryInputStream is;
public DumpInfo dumpInfo;
private static final String NO_REFERENCE_BIT_TEXT = "not reference";
private static final String OBJECT_INDEX_TEXT = "object index";
private static final String STRING_INDEX_TEXT = "string index";
private static final String TRAIT_INDEX_TEXT = "trait index";
public Amf3InputStream(MemoryInputStream is) {
this.is = is;
}
public DumpInfo newDumpLevel(String name, String type) {
if (dumpInfo != null) {
long startByte = is.getPos();
DumpInfo di = new DumpInfo(name, type, null, startByte, 0, 0, 0);
di.parent = dumpInfo;
dumpInfo.getChildInfos().add(di);
dumpInfo = di;
}
return dumpInfo;
}
public void endDumpLevel() {
endDumpLevel(null);
}
public void endDumpLevel(Object value) {
if (dumpInfo != null) {
dumpInfo.lengthBytes = is.getPos() - dumpInfo.startByte;
dumpInfo.previewValue = value;
dumpInfo = dumpInfo.parent;
}
}
public void endDumpLevelUntil(DumpInfo di) {
if (di != null) {
while (dumpInfo != null && dumpInfo != di) {
endDumpLevel();
}
}
}
public int readU8(String name) throws IOException {
newDumpLevel(name, "U8");
int ret = readInternal();
endDumpLevel(ret);
return ret;
}
public int readU16(String name) throws IOException {
newDumpLevel(name, "U16");
int b1 = readInternal();
int b2 = readInternal();
int ret = (b1 << 8) + b2;
endDumpLevel(ret);
return ret;
}
public long readU32(String name) throws IOException {
newDumpLevel(name, "U32");
long ret = readU32Internal();
endDumpLevel(ret);
return ret;
}
private long readU32Internal() throws IOException {
int b1 = readInternal();
int b2 = readInternal();
int b3 = readInternal();
int b4 = readInternal();
return ((b1 << 24) + (b2 << 16) + (b3 << 8) + b4) & 0xffffffff;
}
public long readS32(String name) throws IOException {
newDumpLevel(name, "S32");
long ret = signExtend(readU32Internal(), 32);
endDumpLevel(ret);
return ret;
}
private long readLong() throws IOException {
byte[] readBuffer = new byte[8];
for (int i = 0; i < 8; i++) {
readBuffer[i] = (byte) readInternal();
}
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(String name) throws IOException {
newDumpLevel(name, "DOUBLE");
long lval = readLong();
double ret = Double.longBitsToDouble(lval);
endDumpLevel(EcmaScript.toString(ret));
return ret;
}
public long readU29(String name) throws IOException {
newDumpLevel(name, "U29");
long val = readU29Internal();
endDumpLevel(val);
return val;
}
private void renameU29O_ref() {
renameU29("U29O-ref", NO_REFERENCE_BIT_TEXT, OBJECT_INDEX_TEXT);
}
private void renameU29S_ref() {
renameU29("U29S-ref", NO_REFERENCE_BIT_TEXT, STRING_INDEX_TEXT);
}
private void renameU29Traits_ref() {
renameU29("U29O-traits-ref", NO_REFERENCE_BIT_TEXT, "trait reference", TRAIT_INDEX_TEXT);
}
private void renameLastDump(String wholeName) {
if (dumpInfo != null) {
if (!dumpInfo.getChildInfos().isEmpty()) {
DumpInfo u29DumpInfo = dumpInfo.getChildInfos().get(dumpInfo.getChildInfos().size() - 1);
u29DumpInfo.name = wholeName;
}
}
}
private void setDumpInfoType(String type) {
if (dumpInfo != null) {
dumpInfo.type = type;
}
}
private void renameU29(String wholeName, String name1, String... names) {
List<String> bitNames = new ArrayList<>();
bitNames.add(name1);
bitNames.addAll(Arrays.asList(names));
String restName = bitNames.remove(bitNames.size() - 1);
if (bitNames.size() > 6) {
throw new RuntimeException("Renaming more than 6 bits in U29 is not supported");
}
if (dumpInfo != null) {
if (!dumpInfo.getChildInfos().isEmpty()) {
DumpInfo u29DumpInfo = dumpInfo.getChildInfos().get(dumpInfo.getChildInfos().size() - 1);
u29DumpInfo.name = wholeName;
long lastBytePos = u29DumpInfo.startByte + u29DumpInfo.lengthBytes - 1; //last byte of U29 is least significant (U29 is big endian)
int remainingBitLength = ((int) u29DumpInfo.lengthBytes) * 8 - bitNames.size();
long u29val = ((long) (Long) u29DumpInfo.previewValue);
DumpInfo restDumpInfo = new DumpInfo(restName, "UB(" + remainingBitLength + ")", u29val >> bitNames.size(), lastBytePos, 0, u29DumpInfo.lengthBytes, remainingBitLength);
restDumpInfo.parent = u29DumpInfo;
u29DumpInfo.getChildInfos().add(restDumpInfo);
for (int i = bitNames.size() - 1; i >= 0; i--) {
int bitVal = (int) ((u29val >> i) & 1);
DumpInfo bitDumpInfo = new DumpInfo(bitNames.get(i), "bit", bitVal, lastBytePos, 7 - i, 1, 1);
bitDumpInfo.parent = u29DumpInfo;
u29DumpInfo.getChildInfos().add(bitDumpInfo);
}
}
}
}
public long readS29(String name) throws IOException {
newDumpLevel(name, "S29");
long val = signExtend(readU29Internal(), 29);
endDumpLevel(val);
return val;
}
public long readU29Internal() throws IOException {
long val = 0;
for (int i = 1; i <= 4; i++) {
int b = readInternal();
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 = size == 32 ? 0xFFFFFFFF : (1 << size) - 1; // 111111...up to size
long positiveVal = (~(val - 1)) & mask;
long negativeVal = -positiveVal;
return negativeVal;
}
return val;
}
private String readUtf8Char(String name, long byteLength) throws IOException {
if (byteLength == 0) {
return "";
}
newDumpLevel(name, "UTF8-char");
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 EndOfStreamException();
}
String retString = new String(buf, "UTF-8");
endDumpLevel("\"" + Helper.escapeActionScriptString(retString) + "\"");
return retString;
}
public String readUtf8Vr(String name, List<String> stringTable) throws IOException {
newDumpLevel(name, "UTF-8-vr");
long u = readU29("U29S");
int stringNoRefFlag = (int) (u & 1);
String retString;
if (stringNoRefFlag == 1) {
renameU29("U29S-value", NO_REFERENCE_BIT_TEXT, "byte length");
long byteLength = u >> 1; //TODO: long strings, int is not enough for them
retString = readUtf8Char("characters", byteLength);
if (byteLength > 0) {
stringTable.add(retString);
}
LOGGER.log(Level.FINE, "Read string: \"{0}\"", retString);
} else { //flag==0
renameU29S_ref();
int stringRefTableIndex = (int) (u >> 1);
retString = stringTable.get(stringRefTableIndex);
LOGGER.log(Level.FINE, "Read string: reference({0}):" + retString, stringRefTableIndex);
}
endDumpLevel("\"" + Helper.escapeActionScriptString(retString) + "\"");
return retString;
}
private int readInternal() throws IOException {
int ret = read();
if (ret == -1) {
throw new EndOfStreamException();
}
return ret;
}
@Override
public int read() throws IOException {
return is.read();
}
public Object readValue(String name) throws IOException, NoSerializerExistsException {
return readValue(name, new HashMap<>());
}
public Object readValue(String name, Map<String, ObjectTypeSerializeHandler> serializers) throws IOException, NoSerializerExistsException {
return readValue(name, serializers, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
}
private Object readValue(String name, Map<String, ObjectTypeSerializeHandler> serializers,
List<Object> objectTable,
List<Traits> traitsTable,
List<String> stringTable
) throws IOException, NoSerializerExistsException {
newDumpLevel(name, "value-type");
Object result;
try {
int marker = readU8("marker");
markerswitch:
switch (marker) {
case Marker.UNDEFINED:
renameLastDump("undefined-marker");
setDumpInfoType("undefined-type");
LOGGER.log(Level.FINE, "Read value: undefined");
result = BasicType.UNDEFINED;
break;
case Marker.NULL:
renameLastDump("null-marker");
setDumpInfoType("null-type");
LOGGER.log(Level.FINE, "Read value: null");
result = BasicType.NULL;
break;
case Marker.FALSE:
renameLastDump("false-marker");
setDumpInfoType("false-type");
LOGGER.log(Level.FINE, "Read value: false");
result = Boolean.FALSE;
break;
case Marker.TRUE:
renameLastDump("true-marker");
setDumpInfoType("true-type");
LOGGER.log(Level.FINE, "Read value: true");
result = Boolean.TRUE;
break;
case Marker.INTEGER:
renameLastDump("integer-marker");
setDumpInfoType("integer-type");
LOGGER.log(Level.FINE, "Read value: integer");
long ival = readS29("intValue");
LOGGER.log(Level.FINER, "Integer value: {0}", ival);
result = ival;
break;
case Marker.DOUBLE:
renameLastDump("double-marker");
setDumpInfoType("double-type");
LOGGER.log(Level.FINE, "Read value: double");
double dval = readDouble("doubleValue");
LOGGER.log(Level.FINER, "Double value: {0}", "" + dval);
result = dval;
break;
case Marker.STRING:
renameLastDump("string-marker");
setDumpInfoType("string-type");
LOGGER.log(Level.FINE, "Read value: string");
String sval = readUtf8Vr("stringValue", stringTable);
LOGGER.log(Level.FINER, "String value: {0}", sval);
result = sval;
break;
case Marker.XML_DOC:
renameLastDump("xml-doc-marker");
setDumpInfoType("xml-doc-type");
LOGGER.log(Level.FINE, "Read value: xml_doc");
long xmlDocU29 = readU29("U29");
int xmlDocNoRefFlag = (int) (xmlDocU29 & 1);
if (xmlDocNoRefFlag == 1) {
renameU29("U29X-value", NO_REFERENCE_BIT_TEXT, "byte length");
long byteLength = xmlDocU29 >> 1;
String xval = readUtf8Char("characters", byteLength);
LOGGER.log(Level.FINER, "XmlDoc value: {0}", xval);
XmlDocType retXmlDoc = new XmlDocType(xval);
objectTable.add(retXmlDoc);
result = retXmlDoc;
} else {
renameU29O_ref();
int refIndexXmlDoc = (int) (xmlDocU29 >> 1);
LOGGER.log(Level.FINER, "XmlDoc value: reference({0})", refIndexXmlDoc);
result = objectTable.get(refIndexXmlDoc); //What if it's not XmlRef?
}
break;
case Marker.DATE:
renameLastDump("date-marker");
setDumpInfoType("date-type");
LOGGER.log(Level.FINE, "Read value: date");
long dateU29 = readU29("U29");
int dateNoRefFlag = (int) (dateU29 & 1);
if (dateNoRefFlag == 1) {
renameU29("U29D-value", NO_REFERENCE_BIT_TEXT, "unused");
//remaining bits of dateU29 are not used
double dtval = readDouble("date-time");
DateType retDate = new DateType(dtval);
LOGGER.log(Level.FINER, "Date value: {0}", retDate);
objectTable.add(retDate);
result = retDate;
} else {
renameU29O_ref();
int refIndexDate = (int) (dateU29 >> 1);
LOGGER.log(Level.FINER, "Date value: reference({0})", refIndexDate);
result = objectTable.get(refIndexDate); //What if it's not Date?
}
break;
case Marker.ARRAY:
renameLastDump("array-marker");
setDumpInfoType("array-type");
LOGGER.log(Level.FINE, "Read value: array");
long arrayU29 = readU29("U29");
int arrayNoRefFlag = (int) (arrayU29 & 1);
if (arrayNoRefFlag == 1) {
renameU29("U29A-value", NO_REFERENCE_BIT_TEXT, "dense count");
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
newDumpLevel("associativeValues", "assoc-value");
while (true) {
String key = readUtf8Vr("key", stringTable);
if (key.isEmpty()) {
renameLastDump("UTF-8-empty");
break;
} else {
try {
Object val = readValue("value", serializers, objectTable, traitsTable, stringTable);
assocPart.add(new Pair<>(key, val));
} catch (NoSerializerExistsException nse) {
assocPart.add(new Pair<>(key, nse.getIncompleteData()));
throw new NoSerializerExistsException(nse.getClassName(), retArray, nse);
}
}
}
endDumpLevel();
LOGGER.log(Level.FINEST, "Array value: assocSize={0}", new Object[]{assocPart.size()});
newDumpLevel("denseValues", "value-type[]");
for (int i = 0; i < denseCount; i++) {
try {
densePart.add(readValue("denseValue", serializers, objectTable, traitsTable, stringTable));
} catch (NoSerializerExistsException nse) {
densePart.add(nse.getIncompleteData());
for (int j = i + 1; j < denseCount; j++) {
densePart.add(BasicType.UNKNOWN);
}
throw new NoSerializerExistsException(nse.getClassName(), retArray, nse);
}
}
endDumpLevel();
LOGGER.log(Level.FINER, "Array value: dense_size={0},assocSize={1}", new Object[]{densePart.size(), assocPart.size()});
result = retArray;
} else {
renameU29O_ref();
int refIndexArray = (int) (arrayU29 >> 1);
LOGGER.log(Level.FINER, "Array value: reference({0})", refIndexArray);
result = objectTable.get(refIndexArray); //What if it's not Array?
}
break;
case Marker.OBJECT:
renameLastDump("object-marker");
setDumpInfoType("object-type");
LOGGER.log(Level.FINE, "Read value: object");
long objectU29 = readU29("U29");
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) {
renameU29("U29O-traits-ext", NO_REFERENCE_BIT_TEXT, "not trait reference", "externalized traits", "unused");
String className = readUtf8Vr("className", stringTable);
if (!serializers.containsKey(className)) {
throw new NoSerializerExistsException(className, new ObjectType(new Traits(className, false, new ArrayList<>()), (byte[]) null, new ArrayList<>()), null);
}
newDumpLevel("serializedData", "U8[]");
MonitoredInputStream mis = new MonitoredInputStream(is);
List<Pair<String, Object>> serMembers = serializers.get(className).readObject(className, mis);
byte serData[] = mis.getReadData();
endDumpLevel();
Traits unserTraits = new Traits(className, false, new ArrayList<>());
retObjectType = new ObjectType(unserTraits, serData, serMembers);
LOGGER.log(Level.FINER, "Object/Traits value: customSerialized");
objectTable.add(retObjectType);
result = retObjectType;
break markerswitch;
} else {
renameU29("U29O-traits", NO_REFERENCE_BIT_TEXT, "not trait reference", "externalized traits", "dynamic", "sealed count");
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("className", stringTable);
LOGGER.log(Level.FINEST, "object className:{0}", className);
List<String> sealedMemberNames = new ArrayList<>();
if (numSealed > 0) {
newDumpLevel("sealedMemberNames", "UTF-8-vr[]");
for (int i = 0; i < numSealed; i++) {
sealedMemberNames.add(readUtf8Vr("sealedMemberName", stringTable));
}
endDumpLevel();
}
traits = new Traits(className, dynamicFlag == 1, sealedMemberNames);
traitsTable.add(traits);
}
} else {
renameU29Traits_ref();
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()});
}
List<Pair<String, Object>> sealedMembers = new ArrayList<>();
List<Pair<String, Object>> dynamicMembers = new ArrayList<>();
Object retObjectType = new ObjectType(traits, sealedMembers, dynamicMembers);
objectTable.add(retObjectType); //add it before any subvalue can reference it
List<Object> sealedMemberValues = new ArrayList<>();
NoSerializerExistsException error = null;
if (!traits.getSealedMemberNames().isEmpty()) {
newDumpLevel("sealedMemberValues", "value-type[]");
for (int i = 0; i < traits.getSealedMemberNames().size(); i++) {
try {
sealedMemberValues.add(readValue("sealedMemberValue", serializers, objectTable, traitsTable, stringTable));
} catch (NoSerializerExistsException nse) {
sealedMemberValues.add(nse.getIncompleteData());
for (int j = i + 1; j < traits.getSealedMemberNames().size(); j++) {
sealedMemberValues.add(BasicType.UNKNOWN);
}
error = nse;
break;
}
}
endDumpLevel();
}
for (int i = 0; i < traits.getSealedMemberNames().size(); i++) {
sealedMembers.add(new Pair<>(traits.getSealedMemberNames().get(i), sealedMemberValues.get(i)));
}
if (traits.isDynamic()) {
newDumpLevel("dynamicMembers", "dynamic-member[]");
String dynamicMemberName;
while (!(dynamicMemberName = readUtf8Vr("name", stringTable)).isEmpty()) {
try {
Object dynamicMemberValue = readValue("value", serializers, objectTable, traitsTable, stringTable);
dynamicMembers.add(new Pair<>(dynamicMemberName, dynamicMemberValue));
} catch (NoSerializerExistsException nse) {
dynamicMembers.add(new Pair<>(dynamicMemberName, nse.getIncompleteData()));
throw new NoSerializerExistsException(nse.getClassName(), retObjectType, nse);
} finally {
//group dumpInfo to one sub "dynamic-member"
if (dumpInfo != null) {
DumpInfo valueDumpInfo = dumpInfo.getChildInfos().remove(dumpInfo.getChildInfos().size() - 1);
DumpInfo nameDumpInfo = dumpInfo.getChildInfos().remove(dumpInfo.getChildInfos().size() - 1);
DumpInfo memberDumpInfo = new DumpInfo("member", "dynamic-member", "", nameDumpInfo.startByte, nameDumpInfo.lengthBytes + valueDumpInfo.lengthBytes);
memberDumpInfo.getChildInfos().add(nameDumpInfo);
memberDumpInfo.getChildInfos().add(valueDumpInfo);
memberDumpInfo.parent = dumpInfo;
nameDumpInfo.parent = memberDumpInfo;
valueDumpInfo.parent = memberDumpInfo;
memberDumpInfo.previewValue = "" + nameDumpInfo.previewValue + (valueDumpInfo.previewValue != null ? " : " + valueDumpInfo.previewValue : "");
dumpInfo.getChildInfos().add(memberDumpInfo);
}
}
}
renameLastDump("UTF-8-empty");
endDumpLevel();
}
LOGGER.log(Level.FINER, "Object value: dynamic={0},className={1},sealedSize={2},dynamicSize={3}", new Object[]{traits.isDynamic(), traits.getClassName(), sealedMembers.size(), dynamicMembers.size()});
result = retObjectType;
} else {
renameU29O_ref();
int refIndexObject = (int) (objectU29 >> 1);
LOGGER.log(Level.FINER, "Object value: reference({0})", refIndexObject);
result = objectTable.get(refIndexObject);
}
break;
case Marker.XML:
renameLastDump("xml-marker");
setDumpInfoType("xml-type");
LOGGER.log(Level.FINE, "Read value: xml");
long xmlU29 = readU29("U29");
int xmlNoRefFlag = (int) (xmlU29 & 1);
if (xmlNoRefFlag == 1) {
renameU29("U29X-value", NO_REFERENCE_BIT_TEXT, "byte length");
long byteLength = (xmlU29 >> 1);
String xString = readUtf8Char("characters", byteLength);
XmlType retXmlType = new XmlType(xString);
LOGGER.log(Level.FINER, "Xml value: {0}", xString);
objectTable.add(retXmlType);
result = retXmlType;
} else {
renameU29O_ref();
int refIndexXml = (int) (xmlU29 >> 1);
LOGGER.log(Level.FINER, "XML value: reference({0})", refIndexXml);
result = objectTable.get(refIndexXml);
}
break;
case Marker.BYTE_ARRAY:
renameLastDump("byte-array-marker");
setDumpInfoType("bytearray-type");
LOGGER.log(Level.FINE, "Read value: bytearray");
long byteArrayU29 = readU29("U29");
int byteArrayNoRefFlag = (int) (byteArrayU29 & 1);
if (byteArrayNoRefFlag == 1) {
renameU29("U29B-value", NO_REFERENCE_BIT_TEXT, "byte array length");
int byteArrayLength = (int) (byteArrayU29 >> 1);
newDumpLevel("bytes", "U8[]");
byte byteArrayBuf[] = new byte[byteArrayLength];
if (is.read(byteArrayBuf) != byteArrayLength) {
throw new EndOfStreamException();
}
endDumpLevel();
LOGGER.log(Level.FINER, "ByteArray value: bytes[{0}]", byteArrayLength);
ByteArrayType retByteArrayType = new ByteArrayType(byteArrayBuf);
objectTable.add(retByteArrayType);
result = retByteArrayType;
} else {
renameU29O_ref();
int refIndexByteArray = (int) (byteArrayU29 >> 1);
LOGGER.log(Level.FINER, "ByteArray value: reference({0})", refIndexByteArray);
result = objectTable.get(refIndexByteArray);
}
break;
case Marker.VECTOR_INT:
renameLastDump("vector-int-marker");
setDumpInfoType("vector-int-type");
LOGGER.log(Level.FINE, "Read value: vector_int");
long vectorIntU29 = readU29("U29");
int vectorIntNoRefFlag = (int) (vectorIntU29 & 1);
if (vectorIntNoRefFlag == 1) {
renameU29("U29V-value", NO_REFERENCE_BIT_TEXT, "item count");
int vectorIntCountItems = (int) (vectorIntU29 >> 1);
int fixed = readU8("fixed");
List<Long> vals = new ArrayList<>();
newDumpLevel("items", "S32[]");
for (int i = 0; i < vectorIntCountItems; i++) {
vals.add(readS32("intValue"));
}
endDumpLevel();
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);
result = retVectorInt;
} else {
renameU29O_ref();
int refIndexVectorInt = (int) (vectorIntU29 >> 1);
LOGGER.log(Level.FINER, "Vector<int> value: reference({0})", refIndexVectorInt);
result = objectTable.get(refIndexVectorInt);
}
break;
case Marker.VECTOR_UINT:
renameLastDump("vector-uint-marker");
setDumpInfoType("vector-uint-type");
LOGGER.log(Level.FINE, "Read value: vector_uint");
long vectorUIntU29 = readU29("U29");
int vectorUIntNoRefFlag = (int) (vectorUIntU29 & 1);
if (vectorUIntNoRefFlag == 1) {
renameU29("U29V-value", NO_REFERENCE_BIT_TEXT, "item count");
int vectorUIntCountItems = (int) (vectorUIntU29 >> 1);
int fixed = readU8("fixed");
List<Long> vals = new ArrayList<>();
newDumpLevel("items", "U32[]");
for (int i = 0; i < vectorUIntCountItems; i++) {
vals.add(readU32("uintValue"));
}
endDumpLevel();
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);
result = retVectorUInt;
} else {
renameU29O_ref();
int refIndexVectorUInt = (int) (vectorUIntU29 >> 1);
LOGGER.log(Level.FINER, "Vector<uint> value: reference({0})", refIndexVectorUInt);
result = objectTable.get(refIndexVectorUInt);
}
break;
case Marker.VECTOR_DOUBLE:
renameLastDump("vector-double-marker");
setDumpInfoType("vector-double-type");
LOGGER.log(Level.FINE, "Read value: vector_double");
long vectorDoubleU29 = readU29("U29");
int vectorDoubleNoRefFlag = (int) (vectorDoubleU29 & 1);
if (vectorDoubleNoRefFlag == 1) {
renameU29("U29V-value", NO_REFERENCE_BIT_TEXT, "item count");
int vectorDoubleCountItems = (int) (vectorDoubleU29 >> 1);
int fixed = readU8("fixed");
List<Double> vals = new ArrayList<>();
newDumpLevel("items", "DOUBLE[]");
for (int i = 0; i < vectorDoubleCountItems; i++) {
vals.add(readDouble("doubleValue"));
}
endDumpLevel();
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);
result = retVectorDouble;
} else {
renameU29O_ref();
int refIndexVectorDouble = (int) (vectorDoubleU29 >> 1);
LOGGER.log(Level.FINER, "Vector<double> value: reference({0})", refIndexVectorDouble);
result = objectTable.get(refIndexVectorDouble);
}
break;
case Marker.VECTOR_OBJECT:
renameLastDump("vector-object-marker");
setDumpInfoType("vector-object-type");
LOGGER.log(Level.FINE, "Read value: vector_object");
long vectorObjectU29 = readU29("U29");
int vectorObjectNoRefFlag = (int) (vectorObjectU29 & 1);
if (vectorObjectNoRefFlag == 1) {
renameU29("U29V-value", NO_REFERENCE_BIT_TEXT, "item count");
int vectorObjectCountItems = (int) (vectorObjectU29 >> 1);
int fixed = readU8("fixed");
String objectTypeName = readUtf8Vr("object-type-name", stringTable); //uses "*" for any type
List<Object> vals = new ArrayList<>();
NoSerializerExistsException error = null;
newDumpLevel("items", "value_type[]");
for (int i = 0; i < vectorObjectCountItems; i++) {
try {
vals.add(readValue("value", serializers, objectTable, traitsTable, stringTable));
} catch (NoSerializerExistsException nse) {
vals.add(nse.getIncompleteData());
for (int j = i + 1; j < vectorObjectCountItems; j++) {
vals.add(BasicType.UNKNOWN);
}
error = nse;
break;
}
}
endDumpLevel();
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);
if (error != null) {
throw new NoSerializerExistsException(error.getClassName(), retVectorObject, error);
}
result = retVectorObject;
} else {
renameU29O_ref();
int refIndexVectorObject = (int) (vectorObjectU29 >> 1);
LOGGER.log(Level.FINER, "Vector<Object> value: reference({0})", refIndexVectorObject);
result = objectTable.get(refIndexVectorObject);
}
break;
case Marker.DICTIONARY:
renameLastDump("dictionary-marker");
setDumpInfoType("dictionary-type");
long dictionaryObjectU29 = readU29("U29");
int dictionaryNoRefFlag = (int) (dictionaryObjectU29 & 1);
if (dictionaryNoRefFlag == 1) {
renameU29("U29Dict-value", NO_REFERENCE_BIT_TEXT, "entries count");
int numEntries = (int) (dictionaryObjectU29 >> 1);
int weakKeys = readU8("weak keys");
List<Pair<Object, Object>> data = new ArrayList<>();
DictionaryType retDictionary = new DictionaryType(weakKeys == 1, data);
objectTable.add(retDictionary);
NoSerializerExistsException error = null;
newDumpLevel("entries", "");
for (int i = 0; i < numEntries; i++) {
Object key;
Object val;
try {
key = readValue("entry-key", serializers, objectTable, traitsTable, stringTable);
try {
val = readValue("entry-value", serializers, objectTable, traitsTable, stringTable);
} catch (NoSerializerExistsException nse) {
error = nse;
val = BasicType.UNKNOWN;
}
} catch (NoSerializerExistsException nse) {
error = nse;
key = BasicType.UNKNOWN;
val = BasicType.UNKNOWN;
}
data.add(new Pair<>(key, val));
if (error != null) {
for (int j = i + 1; j < numEntries; j++) {
data.add(new Pair<>(BasicType.UNKNOWN, BasicType.UNKNOWN));
}
break;
}
}
endDumpLevel();
if (error != null) {
throw new NoSerializerExistsException(error.getClassName(), retDictionary, error);
}
result = retDictionary;
} else {
renameU29O_ref();
int refIndexDictionary = (int) (dictionaryObjectU29 >> 1);
LOGGER.log(Level.FINER, "Dictionary value: reference({0})", refIndexDictionary);
result = objectTable.get(refIndexDictionary);
}
break;
default:
throw new UnsupportedValueTypeException(marker);
}
} finally {
endDumpLevel();
}
return result;
}
private static String valToPreviewString(Object v) {
if (v instanceof ObjectType) {
return "{...}";
}
if (v instanceof ArrayType) {
return "[...]";
}
if (v instanceof DictionaryType) {
return "";
}
if (v instanceof BasicType) {
return "";
}
if (v instanceof DateType) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SS");
return sdf.format(((DateType) v).toDate());
}
if (v instanceof String) {
return "\"" + Helper.escapeActionScriptString((String) v) + "\"";
}
return EcmaScript.toString(v);
}
private class MonitoredInputStream extends InputStream {
private final InputStream is;
private ByteArrayOutputStream baos;
public MonitoredInputStream(InputStream is) {
this.is = is;
this.baos = new ByteArrayOutputStream();
}
@Override
public int read() throws IOException {
int ret = is.read();
if (ret > -1) {
baos.write(ret);
}
return ret;
}
public byte[] getReadData() {
return baos.toByteArray();
}
}
}

View File

@@ -0,0 +1,365 @@
package com.jpexs.decompiler.flash.amf.amf3;
import com.jpexs.decompiler.flash.amf.amf3.types.ArrayType;
import com.jpexs.decompiler.flash.amf.amf3.types.BasicType;
import com.jpexs.decompiler.flash.amf.amf3.types.ByteArrayType;
import com.jpexs.decompiler.flash.amf.amf3.types.DateType;
import com.jpexs.decompiler.flash.amf.amf3.types.DictionaryType;
import com.jpexs.decompiler.flash.amf.amf3.types.ObjectType;
import com.jpexs.decompiler.flash.amf.amf3.types.VectorDoubleType;
import com.jpexs.decompiler.flash.amf.amf3.types.VectorIntType;
import com.jpexs.decompiler.flash.amf.amf3.types.VectorObjectType;
import com.jpexs.decompiler.flash.amf.amf3.types.VectorUIntType;
import com.jpexs.decompiler.flash.amf.amf3.types.XmlDocType;
import com.jpexs.decompiler.flash.amf.amf3.types.XmlType;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
public class Amf3OutputStream extends OutputStream {
public final static Logger LOGGER = Logger.getLogger(Amf3OutputStream.class.getName());
private final OutputStream os;
private static final int NO_REFERENCE_FLAG = 1;
private static final int NO_TRAIT_REFERENCE_FLAG = 2;
private static final int TRAIT_EXT_FLAG = 4;
private static final int DYNAMIC_FLAG = 8;
public Amf3OutputStream(OutputStream os) {
this.os = os;
}
public void writeU8(int v) throws IOException {
write(v);
}
public void writeU16(int v) throws IOException {
int b1 = (v >> 8) & 0xff;
int b2 = v & 0xff;
write(b1);
write(b2);
}
private void writeLong(long value) throws IOException {
byte[] writeBuffer = new byte[8];
writeBuffer[0] = (byte) (value >>> 56);
writeBuffer[1] = (byte) (value >>> 48);
writeBuffer[2] = (byte) (value >>> 40);
writeBuffer[3] = (byte) (value >>> 32);
writeBuffer[4] = (byte) (value >>> 24);
writeBuffer[5] = (byte) (value >>> 16);
writeBuffer[6] = (byte) (value >>> 8);
writeBuffer[7] = (byte) (value);
write(writeBuffer);
}
public void writeU32(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 {
writeLong(Double.doubleToLongBits(v));
}
public void writeBytes(byte[] data) throws IOException {
os.write(data);
}
public void writeU29(long v) throws IOException {
v = v & 0x3FFFFFFF; //make unsigned
final int USE_NEXT_BYTE_FLAG = 0x80;
final int SEVEN_BITS_MASK = 0x7f;
final int EIGHT_BITS_MASK = 0xff;
if (v <= 0x7F) {
write((int) v);
} else if (v <= 0x3FFF) {
int b1 = (int) ((v >> 7) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG;
int b2 = (int) (v & SEVEN_BITS_MASK);
write(b1);
write(b2);
} else if (v <= 0x1FFFFF) {
int b1 = (int) ((v >> 14) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG;
int b2 = (int) ((v >> 7) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG;
int b3 = (int) (v & SEVEN_BITS_MASK);
write(b1);
write(b2);
write(b3);
} else if (v <= 0x3FFFFFFF) {
int b1 = (int) ((v >> 21) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG;
int b2 = (int) ((v >> 14) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG;
int b3 = (int) ((v >> 7) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG;
int b4 = (int) (v & EIGHT_BITS_MASK);
write(b1);
write(b2);
write(b3);
write(b4);
} else {
throw new IllegalArgumentException("Value too long");
}
}
private void writeUtf8Vr(String val, List<String> stringTable) throws IOException {
int stringIndex = stringTable.indexOf(val);
if (stringIndex == -1) {
if (!val.isEmpty()) {
stringTable.add(val);
}
byte data[] = val.getBytes("UTF-8");
writeU29((data.length << 1) | NO_REFERENCE_FLAG);
writeBytes(data);
} else {
writeU29((stringIndex << 1));
}
}
private void writeByteArray(ByteArrayType val, List<Object> objectTable) throws IOException {
int objectIndex = objectTable.indexOf(val);
if (objectIndex == -1) {
objectTable.add(val);
byte data[] = val.getData();
writeU29((data.length << 1) | NO_REFERENCE_FLAG);
writeBytes(data);
} else {
writeU29((objectIndex << 1));
}
}
private void writeXmlDoc(XmlDocType val, List<Object> objectTable) throws IOException {
int objectIndex = objectTable.indexOf(val);
if (objectIndex == -1) {
objectTable.add(val);
byte data[] = val.getData().getBytes("UTF-8");
writeU29((data.length << 1) | NO_REFERENCE_FLAG);
writeBytes(data);
} else {
writeU29((objectIndex << 1));
}
}
private void writeXml(XmlType val, List<Object> objectTable) throws IOException {
int objectIndex = objectTable.indexOf(val);
if (objectIndex == -1) {
objectTable.add(val);
byte data[] = val.getData().getBytes("UTF-8");
writeU29((data.length << 1) | NO_REFERENCE_FLAG);
writeBytes(data);
} else {
writeU29((objectIndex << 1));
}
}
@Override
public void write(int v) throws IOException {
os.write(v);
}
private void writeArray(ArrayType val, Map<String, ObjectTypeSerializeHandler> serializers, List<String> stringTable, List<Traits> traitsTable, List<Object> objectTable) throws IOException, NoSerializerExistsException {
int objectIndex = objectTable.indexOf(val);
if (objectIndex == -1) {
objectTable.add(val);
writeU29((val.getDenseValues().size() << 1) | NO_REFERENCE_FLAG);
for (Pair<String, Object> p : val.getAssociativeValues()) {
writeUtf8Vr(p.getFirst(), stringTable);
writeValue(p.getSecond(), serializers, stringTable, traitsTable, objectTable);
}
writeUtf8Vr("", stringTable);
for (Object v : val.getDenseValues()) {
writeValue(v, serializers, stringTable, traitsTable, objectTable);
}
} else {
writeU29((objectIndex << 1));
}
}
private void writeObject(ObjectType val, Map<String, ObjectTypeSerializeHandler> serializers, List<String> stringTable, List<Traits> traitsTable, List<Object> objectTable) throws IOException, NoSerializerExistsException {
int objectIndex = objectTable.indexOf(val);
if (objectIndex == -1) {
objectTable.add(val);
Traits traits = val.getTraits();
int traitsIndex = traitsTable.indexOf(traits);
if (traitsIndex == -1) {
if (val.isSerialized()) {
writeU29(NO_REFERENCE_FLAG | NO_TRAIT_REFERENCE_FLAG | TRAIT_EXT_FLAG);
writeUtf8Vr(val.getClassName(), stringTable);
if (serializers.containsKey(val.getClassName())) {
serializers.get(val.getClassName()).writeObject(val.getSerializedMembers(), os);
} else if (val.getSerializedData() != null) {
writeBytes(val.getSerializedData());
} else {
throw new NoSerializerExistsException(val.getClassName(), null, null);
}
} else {
traitsTable.add(traits);
writeU29((val.getSealedMembers().size() << 4) | NO_REFERENCE_FLAG | NO_TRAIT_REFERENCE_FLAG | (traits.isDynamic() ? DYNAMIC_FLAG : 0));
writeUtf8Vr(val.getClassName(), stringTable);
for (Pair<String, Object> v : val.getSealedMembers()) {
writeUtf8Vr(v.getFirst(), stringTable);
}
}
} else {
writeU29((traitsIndex << 2) | NO_REFERENCE_FLAG);
}
for (Pair<String, Object> v : val.getSealedMembers()) {
writeValue(v.getSecond(), serializers, stringTable, traitsTable, objectTable);
}
if (traits.isDynamic()) {
for (Pair<String, Object> v : val.getDynamicMembers()) {
writeUtf8Vr(v.getFirst(), stringTable);
writeValue(v.getSecond(), serializers, stringTable, traitsTable, objectTable);
}
writeUtf8Vr("", stringTable);
}
} else {
writeU29((objectIndex << 1));
}
}
public void writeValue(Object object) throws IOException, NoSerializerExistsException {
writeValue(object, new HashMap<>());
}
public void writeValue(Object object, Map<String, ObjectTypeSerializeHandler> serializers) throws IOException, NoSerializerExistsException {
writeValue(object, serializers, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
}
private void writeValue(Object object, Map<String, ObjectTypeSerializeHandler> serializers, List<String> stringTable, List<Traits> traitsTable, List<Object> objectTable) throws IOException, NoSerializerExistsException {
if (object == BasicType.UNDEFINED) {
writeU8(Marker.UNDEFINED);
} else if (object == BasicType.NULL) {
writeU8(Marker.NULL);
} else if (object == Boolean.FALSE) {
writeU8(Marker.FALSE);
} else if (object == Boolean.TRUE) {
writeU8(Marker.TRUE);
} else if (object == BasicType.UNKNOWN) {
//Nothing
} else if (object instanceof Long) {
writeU8(Marker.INTEGER);
writeU29((Long) object);
} else if (object instanceof Double) {
writeU8(Marker.DOUBLE);
writeDouble((Double) object);
} else if (object instanceof String) {
writeU8(Marker.STRING);
writeUtf8Vr((String) object, stringTable);
} else if (object instanceof XmlDocType) {
writeU8(Marker.XML_DOC);
writeXmlDoc((XmlDocType) object, objectTable);
} else if (object instanceof DateType) {
writeU8(Marker.DATE);
int dateIndex = objectTable.indexOf(object);
DateType val = (DateType) object;
if (dateIndex == -1) {
objectTable.add(val);
writeU29(NO_REFERENCE_FLAG);
writeDouble(val.getVal());
} else {
writeU29(dateIndex << 1);
}
} else if (object instanceof ArrayType) {
writeU8(Marker.ARRAY);
writeArray((ArrayType) object, serializers, stringTable, traitsTable, objectTable);
} else if (object instanceof ObjectType) {
writeU8(Marker.OBJECT);
writeObject((ObjectType) object, serializers, stringTable, traitsTable, objectTable);
} else if (object instanceof XmlType) {
writeU8(Marker.XML);
writeXml((XmlType) object, objectTable);
} else if (object instanceof ByteArrayType) {
writeU8(Marker.BYTE_ARRAY);
writeByteArray((ByteArrayType) object, objectTable);
} else if (object instanceof VectorIntType) {
writeU8(Marker.VECTOR_INT);
int vectorIndex = objectTable.indexOf(object);
VectorIntType val = (VectorIntType) object;
if (vectorIndex == -1) {
objectTable.add(val);
writeU29((val.getValues().size() << 1) | NO_REFERENCE_FLAG);
writeU8(val.isFixed() ? 1 : 0);
for (long v : val.getValues()) {
writeU32(v);
}
} else {
writeU29(vectorIndex << 1);
}
} else if (object instanceof VectorUIntType) {
writeU8(Marker.VECTOR_UINT);
int vectorIndex = objectTable.indexOf(object);
VectorUIntType val = (VectorUIntType) object;
if (vectorIndex == -1) {
objectTable.add(val);
writeU29((val.getValues().size() << 1) | NO_REFERENCE_FLAG);
writeU8(val.isFixed() ? 1 : 0);
for (long v : val.getValues()) {
writeU32(v);
}
} else {
writeU29(vectorIndex << 1);
}
} else if (object instanceof VectorDoubleType) {
writeU8(Marker.VECTOR_DOUBLE);
int vectorIndex = objectTable.indexOf(object);
VectorDoubleType val = (VectorDoubleType) object;
if (vectorIndex == -1) {
objectTable.add(val);
writeU29((val.getValues().size() << 1) | NO_REFERENCE_FLAG);
writeU8(val.isFixed() ? 1 : 0);
for (double v : val.getValues()) {
writeDouble(v);
}
} else {
writeU29(vectorIndex << 1);
}
} else if (object instanceof VectorObjectType) {
writeU8(Marker.VECTOR_OBJECT);
int vectorIndex = objectTable.indexOf(object);
VectorObjectType val = (VectorObjectType) object;
if (vectorIndex == -1) {
objectTable.add(val);
writeU29((val.getValues().size() << 1) | NO_REFERENCE_FLAG);
writeU8(val.isFixed() ? 1 : 0);
writeUtf8Vr(val.getTypeName(), stringTable);
for (Object v : val.getValues()) {
writeValue(v, serializers, stringTable, traitsTable, objectTable);
}
} else {
writeU29(vectorIndex << 1);
}
} else if (object instanceof DictionaryType) {
writeU8(Marker.DICTIONARY);
int dictionaryIndex = objectTable.indexOf(object);
DictionaryType val = (DictionaryType) object;
if (dictionaryIndex == -1) {
objectTable.add(val);
writeU29((val.getPairs().size() << 1) | NO_REFERENCE_FLAG);
writeU8(val.hasWeakKeys() ? 1 : 0);
for (Pair<Object, Object> p : val.getPairs()) {
writeValue(p.getFirst(), serializers, stringTable, traitsTable, objectTable);
writeValue(p.getSecond(), serializers, stringTable, traitsTable, objectTable);
}
} else {
writeU29(dictionaryIndex << 1);
}
} else {
throw new UnsupportedValueTypeException(object.getClass());
}
}
}

View File

@@ -0,0 +1,58 @@
package com.jpexs.decompiler.flash.amf.amf3;
import com.jpexs.decompiler.flash.amf.amf3.types.Amf3ValueType;
import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter;
public class Amf3Value {
private Object value;
public Amf3Value() {
setValue(null);
}
public Amf3Value(Object value) {
setValue(value);
}
public void setValue(Object value) {
if (!isValueValid(value)) {
throw new IllegalArgumentException("Invalid Amf value: " + value.getClass().getSimpleName());
}
this.value = value;
}
public static boolean isValueValid(Object value) {
if (value == null) {
return true;
}
if (value instanceof Long) {
return true;
}
if (value instanceof Double) {
return true;
}
if (value instanceof String) {
return true;
}
if (value instanceof Boolean) {
return true;
}
if (value instanceof Amf3ValueType) {
return true;
}
return false;
}
public Object getValue() {
return value;
}
@Override
public String toString() {
if (value == null) {
return "";
}
return Amf3Exporter.amfToString(value);
}
}

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,25 @@
package com.jpexs.decompiler.flash.amf.amf3;
public class NoSerializerExistsException extends Exception {
private final String className;
private final Object incompleteData;
/*public NoSerializerExistsException(String className, Object incompleteData) {
this(className, incompleteData, null);
}*/
public NoSerializerExistsException(String className, Object incompleteData, Throwable cause) {
super("Cannot read AMF - no deserializer defined for class \"" + className + "\".", cause);
this.className = className;
this.incompleteData = incompleteData;
}
public String getClassName() {
return className;
}
public Object getIncompleteData() {
return incompleteData;
}
}

View File

@@ -0,0 +1,13 @@
package com.jpexs.decompiler.flash.amf.amf3;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
public interface ObjectTypeSerializeHandler {
public List<Pair<String, Object>> readObject(String className, InputStream is) throws IOException;
public void writeObject(List<Pair<String, Object>> members, OutputStream os) throws IOException;
}

View File

@@ -0,0 +1,28 @@
package com.jpexs.decompiler.flash.amf.amf3;
public class Pair<T1, T2> {
private T1 first;
private T2 second;
public Pair(T1 first, T2 second) {
this.first = first;
this.second = second;
}
public T1 getFirst() {
return first;
}
public T2 getSecond() {
return second;
}
public void setSecond(T2 second) {
this.second = second;
}
public void setFirst(T1 first) {
this.first = first;
}
}

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,26 @@
package com.jpexs.decompiler.flash.amf.amf3;
public class UnsupportedValueTypeException extends RuntimeException {
private Integer marker = null;
private Class cls = null;
public UnsupportedValueTypeException(Class cls) {
super("Unsupported type of value - class: " + cls.getSimpleName());
this.cls = cls;
}
public UnsupportedValueTypeException(int marker) {
super("Unsupported type of value - marker: 0x" + Integer.toHexString(marker));
this.marker = marker;
}
public Integer getMarker() {
return marker;
}
public Class getUnsupportedClass() {
return this.cls;
}
}

View File

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

View File

@@ -0,0 +1,40 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter;
import java.util.ArrayList;
import java.util.List;
import com.jpexs.decompiler.flash.amf.amf3.WithSubValues;
public abstract class AbstractVectorType<T> implements WithSubValues, Amf3ValueType {
private boolean fixed;
private List<T> values;
public boolean isFixed() {
return fixed;
}
public AbstractVectorType(boolean fixed, List<T> values) {
this.values = values;
this.fixed = fixed;
}
public List<T> getValues() {
return values;
}
@Override
public List<Object> getSubValues() {
List<Object> ret = new ArrayList<>();
ret.addAll(values);
return ret;
}
public abstract String getTypeName();
@Override
public String toString() {
return Amf3Exporter.amfToString(this);
}
}

View File

@@ -0,0 +1,5 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
public interface Amf3ValueType {
}

View File

@@ -0,0 +1,46 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter;
import com.jpexs.decompiler.flash.amf.amf3.Pair;
import com.jpexs.helpers.Helper;
import java.util.ArrayList;
import java.util.List;
import com.jpexs.decompiler.flash.amf.amf3.WithSubValues;
public class ArrayType implements WithSubValues, Amf3ValueType {
private List<Object> denseValues;
private List<Pair<String, Object>> associativeValues;
public ArrayType(List<Object> denseValues, List<Pair<String, Object>> associativeValues) {
this.denseValues = denseValues;
this.associativeValues = associativeValues;
}
public List<Object> getDenseValues() {
return denseValues;
}
public List<Pair<String, Object>> getAssociativeValues() {
return associativeValues;
}
@Override
public String toString() {
return Amf3Exporter.amfToString(this);
}
@Override
public List<Object> getSubValues() {
List<Object> ret = new ArrayList<>();
for (Pair<String, Object> p : associativeValues) {
ret.add(p.getFirst());
ret.add(p.getSecond());
}
for (Object v : denseValues) {
ret.add(v);
}
return ret;
}
}

View File

@@ -0,0 +1,26 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
public enum BasicType implements Amf3ValueType {
NULL {
@Override
public String toString() {
return "null";
}
},
UNDEFINED {
@Override
public String toString() {
return "undefined";
}
},
//Special types for errors while reading
UNKNOWN {
@Override
public String toString() {
return "unknown";
}
}
}

View File

@@ -0,0 +1,22 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter;
public class ByteArrayType {
private byte[] data;
public ByteArrayType(byte[] data) {
this.data = data;
}
public byte[] getData() {
return data;
}
@Override
public String toString() {
return Amf3Exporter.amfToString(this);
}
}

View File

@@ -0,0 +1,30 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter;
import java.util.Date;
public class DateType implements Amf3ValueType {
private double val;
public DateType(double val) {
this.val = val;
}
public double getVal() {
return val;
}
public void setVal(double val) {
this.val = val;
}
@Override
public String toString() {
return Amf3Exporter.amfToString(this);
}
public Date toDate() {
return new Date((long) val);
}
}

View File

@@ -0,0 +1,42 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter;
import com.jpexs.decompiler.flash.amf.amf3.Pair;
import java.util.ArrayList;
import java.util.List;
import com.jpexs.decompiler.flash.amf.amf3.WithSubValues;
public class DictionaryType implements WithSubValues, Amf3ValueType {
private boolean weakKeys;
private List<Pair<Object, Object>> pairs;
public DictionaryType(boolean weakKeys, List<Pair<Object, Object>> pairs) {
this.weakKeys = weakKeys;
this.pairs = pairs;
}
public List<Pair<Object, Object>> getPairs() {
return pairs;
}
@Override
public List<Object> getSubValues() {
List<Object> ret = new ArrayList<>();
for (Pair<Object, Object> p : pairs) {
ret.add(p.getFirst());
ret.add(p.getSecond());
}
return ret;
}
@Override
public String toString() {
return Amf3Exporter.amfToString(this);
}
public boolean hasWeakKeys() {
return weakKeys;
}
}

View File

@@ -0,0 +1,103 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter;
import com.jpexs.decompiler.flash.amf.amf3.Pair;
import com.jpexs.decompiler.flash.amf.amf3.Traits;
import com.jpexs.helpers.Helper;
import java.util.ArrayList;
import java.util.List;
import com.jpexs.decompiler.flash.amf.amf3.WithSubValues;
public class ObjectType implements WithSubValues, Amf3ValueType {
private List<Pair<String, Object>> sealedMembers;
private List<Pair<String, Object>> dynamicMembers;
private List<Pair<String, Object>> serializedMembers;
//null = not serialized or unknown
private byte[] serializedData = null;
private boolean serialized;
private Traits traits;
public boolean isSerialized() {
return serialized;
}
public Traits getTraits() {
return traits;
}
public ObjectType(Traits traits, byte[] serializedData, List<Pair<String, Object>> serializedMembers) {
this.traits = traits;
this.serializedData = serializedData;
this.serializedMembers = serializedMembers;
this.dynamicMembers = new ArrayList<>();
this.sealedMembers = new ArrayList<>();
this.serialized = true;
}
public ObjectType(Traits traits, List<Pair<String, Object>> sealedMembers, List<Pair<String, Object>> dynamicMembers) {
this.sealedMembers = sealedMembers;
this.dynamicMembers = dynamicMembers;
this.serializedMembers = new ArrayList<>();
this.serialized = false;
this.traits = traits;
}
public boolean isDynamic() {
return traits.isDynamic();
}
public List<Pair<String, Object>> getDynamicMembers() {
return dynamicMembers;
}
public List<Pair<String, Object>> getSealedMembers() {
return sealedMembers;
}
public String getClassName() {
return traits.getClassName();
}
@Override
public List<Object> getSubValues() {
List<Object> ret = new ArrayList<>();
for (Pair<String, Object> p : dynamicMembers) {
ret.add(p.getFirst());
ret.add(p.getSecond());
}
for (Pair<String, Object> p : sealedMembers) {
ret.add(p.getFirst());
ret.add(p.getSecond());
}
for (Pair<String, Object> p : serializedMembers) {
ret.add(p.getFirst());
ret.add(p.getSecond());
}
return ret;
}
@Override
public String toString() {
return Amf3Exporter.amfToString(this);
}
public void setSerializedData(byte[] serializedData) {
this.serializedData = serializedData;
}
public byte[] getSerializedData() {
return serializedData;
}
public void setSerializedMembers(List<Pair<String, Object>> serializedMembers) {
this.serializedMembers = serializedMembers;
}
public List<Pair<String, Object>> getSerializedMembers() {
return serializedMembers;
}
}

View File

@@ -0,0 +1,16 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import java.util.List;
public class VectorDoubleType extends AbstractVectorType<Double> {
public VectorDoubleType(boolean fixed, List<Double> values) {
super(fixed, values);
}
@Override
public String getTypeName() {
return "Number";
}
}

View File

@@ -0,0 +1,16 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import java.util.List;
public class VectorIntType extends AbstractVectorType<Long> {
public VectorIntType(boolean fixed, List<Long> values) {
super(fixed, values);
}
@Override
public String getTypeName() {
return "int";
}
}

View File

@@ -0,0 +1,21 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.helpers.Helper;
import java.util.ArrayList;
import java.util.List;
public class VectorObjectType extends AbstractVectorType<Object> {
private String typeName;
public VectorObjectType(boolean fixed, String typeName, List<Object> values) {
super(fixed, values);
this.typeName = typeName;
}
@Override
public String getTypeName() {
return typeName;
}
}

View File

@@ -0,0 +1,17 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import java.util.ArrayList;
import java.util.List;
public class VectorUIntType extends AbstractVectorType<Long> {
public VectorUIntType(boolean fixed, List<Long> values) {
super(fixed, values);
}
@Override
public String getTypeName() {
return "uint";
}
}

View File

@@ -0,0 +1,24 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
public class XmlDocType implements Amf3ValueType {
private String data;
public XmlDocType(String data) {
this.data = data;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
@Override
public String toString() {
return data;
}
}

View File

@@ -0,0 +1,24 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
public class XmlType implements Amf3ValueType {
private String data;
public XmlType(String data) {
this.data = data;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
@Override
public String toString() {
return data;
}
}

View File

@@ -0,0 +1,274 @@
package com.jpexs.decompiler.flash.exporters.amf.amf3;
import com.jpexs.decompiler.flash.amf.amf3.Pair;
import com.jpexs.decompiler.flash.amf.amf3.WithSubValues;
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.decompiler.flash.ecma.EcmaScript;
import com.jpexs.helpers.Helper;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Amf3Exporter {
/**
* Converts AMF value to something human-readable.
*
* @param amfValue
* @return
*/
public static String amfToString(Object amfValue) {
Map<Object, Integer> refCount = new HashMap<>();
List<Object> objectList = new ArrayList<>();
Map<Object, String> objectAlias = new HashMap<>();
populateObjects(amfValue, refCount, objectList, objectAlias);
return amfToString(new ArrayList<>(), 0, amfValue, refCount, objectAlias);
}
/**
* Populates all object instances and their references and generates aliases
*
* @param object Object to be populated
* @param referenceCount Result: Map of reference number
* @param objectList Result: List of all found object instances
* @param objectAlias Result: Map of assigned object names
*/
public static void populateObjects(Object object, Map<Object, Integer> referenceCount, List<Object> objectList, Map<Object, String> objectAlias) {
if (((List<? extends Class>) Arrays.asList(String.class, Long.class, Double.class, BasicType.class, Boolean.class)).contains(object.getClass())) {
return;
}
if (object instanceof BasicType) {
return;
}
int prevRef = 0;
if (referenceCount.containsKey(object)) {
prevRef = referenceCount.get(object);
}
referenceCount.put(object, prevRef + 1);
if (prevRef == 0) {
if (object instanceof WithSubValues) {
List<Object> subvalues = ((WithSubValues) object).getSubValues();
for (Object o : subvalues) {
populateObjects(o, referenceCount, objectList, objectAlias);
}
}
objectList.add(object);
objectAlias.put(object, "obj" + objectList.size());
}
}
private static String indent(int level) {
String na = "";
for (int i = 0; i < level; i++) {
na += " ";
}
return na;
}
/**
* Processes one level of object and converts it to string
*
* @param processedObjects
* @param level
* @param object
* @param referenceCount
* @param objectAlias
* @return
*/
private static String amfToString(List<Object> processedObjects, int level, Object object, Map<Object, Integer> referenceCount, Map<Object, String> objectAlias) {
if (object instanceof String) {
return "\"" + Helper.escapeActionScriptString((String) object) + "\"";
}
if (((List<? extends Class>) Arrays.asList(Long.class, Double.class, Boolean.class)).contains(object.getClass())) {
return EcmaScript.toString(object);
}
if (object instanceof BasicType) {
return object.toString();
}
StringBuilder ret = new StringBuilder();
Integer refCount = referenceCount.get(object);
if (refCount > 1 && processedObjects.contains(object)) {
ret.append("#").append(objectAlias.get(object));
return ret.toString();
}
processedObjects.add(object);
String addId = refCount > 1 ? indent(level + 1) + "\"id\": \"" + objectAlias.get(object) + "\",\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)).append("\"type\": \"Object\",\r\n");
ret.append(addId);
ret.append(indent(level + 1)).append("\"className\": ").append(amfToString(processedObjects, level, ot.getClassName(), referenceCount, objectAlias)).append(",\r\n");
if (ot.isSerialized()) {
byte[] serData = ot.getSerializedData();
if (serData == null) {
ret.append(indent(level + 1)).append("\"serialized\": unknown\r\n");
} else {
ret.append(indent(level + 1)).append("\"serialized\": \"").append(javax.xml.bind.DatatypeConverter.printHexBinary(serData)).append("\",\r\n");
if (!ot.getSerializedMembers().isEmpty()) {
ret.append(indent(level + 1)).append("\"unserializedMembers\": {\r\n");
for (int i = 0; i < ot.getSerializedMembers().size(); i++) {
Pair<String, Object> member = ot.getSerializedMembers().get(i);
ret.append(indent(level + 2)).append(amfToString(processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias)).append(":").append(amfToString(processedObjects, level + 1, member.getSecond(), referenceCount, objectAlias));
if (i < ot.getSerializedMembers().size() - 1) {
ret.append(",\r\n");
} else {
ret.append("\r\n");
}
}
ret.append(indent(level + 1)).append("}");
ret.append("\r\n");
}
}
} else {
ret.append(indent(level + 1)).append("\"dynamic\": ").append(ot.isDynamic()).append(",\r\n");
//if (!ot.getSealedMembers().isEmpty()) {
ret.append(indent(level + 1)).append("\"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)).append(amfToString(processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias)).append(":").append(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)).append("}");
//if (!ot.getDynamicMembers().isEmpty()) {
ret.append(",");
//}
ret.append("\r\n");
//}
//if (!ot.getDynamicMembers().isEmpty()) {
ret.append(indent(level + 1)).append("\"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)).append(amfToString(processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias));
ret.append(":");
ret.append(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)).append("}\r\n");
//}
}
ret.append(indent(level)).append("}");
} else if (object instanceof ArrayType) {
ArrayType at = (ArrayType) object;
ret.append("{\r\n");
ret.append(indent(level + 1)).append("\"type\": \"Array\",\r\n");
ret.append(addId);
ret.append(indent(level + 1)).append("\"denseValues\": [");
for (int i = 0; i < at.getDenseValues().size(); i++) {
if (i > 0) {
ret.append(", ");
}
ret.append(amfToString(processedObjects, level + 2, at.getDenseValues().get(i), referenceCount, objectAlias));
}
ret.append("],\r\n");
ret.append(indent(level + 1)).append("\"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)).append(amfToString(processedObjects, level + 1, p.getFirst(), referenceCount, objectAlias)).append(" : ").append(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)).append("}");
} else if (object instanceof DictionaryType) {
DictionaryType dt = (DictionaryType) object;
ret.append("{\r\n");
ret.append(indent(level + 1)).append("\"type\": \"Dictionary\",\r\n");
ret.append(addId);
ret.append(indent(level + 1)).append("\"weakKeys\": ").append(dt.hasWeakKeys()).append(",\r\n");
ret.append(indent(level + 1)).append("\"entries\": {\r\n");
for (int i = 0; i < dt.getPairs().size(); i++) {
Pair<Object, Object> pair = dt.getPairs().get(i);
ret.append(indent(level + 1)).append(amfToString(processedObjects, level + 1, pair.getFirst(), referenceCount, objectAlias)).append(" : ").append(amfToString(processedObjects, level + 1, pair.getSecond(), referenceCount, objectAlias));
if (i < dt.getPairs().size() - 1) {
ret.append(",");
}
ret.append("\r\n");
}
ret.append(indent(level + 1)).append("}\r\n");
ret.append(indent(level)).append("}");
} else if (object instanceof ByteArrayType) {
ByteArrayType ba = (ByteArrayType) object;
byte data[] = ba.getData();
return "{\r\n"
+ indent(level + 1) + "\"type\": \"ByteArray\",\r\n"
+ addId
+ indent(level + 1) + "\"value\": \"" + 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\": \"XMLDocument\",\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\": \"XML\",\r\n"
+ addId
+ indent(level + 1) + "\"value\": " + amfToString(processedObjects, level, ((XmlType) object).getData(), referenceCount, objectAlias) + "\r\n"
+ indent(level) + "}";
} else {
throw new IllegalArgumentException("Unsupported type: " + object.getClass());
}
return ret.toString();
}
}

View File

@@ -0,0 +1,556 @@
package com.jpexs.decompiler.flash.importers.amf.amf3;
import com.jpexs.decompiler.flash.amf.amf3.Pair;
import com.jpexs.decompiler.flash.amf.amf3.Traits;
import com.jpexs.decompiler.flash.amf.amf3.types.ArrayType;
import com.jpexs.decompiler.flash.amf.amf3.types.BasicType;
import com.jpexs.decompiler.flash.amf.amf3.types.ByteArrayType;
import com.jpexs.decompiler.flash.amf.amf3.types.DateType;
import com.jpexs.decompiler.flash.amf.amf3.types.DictionaryType;
import com.jpexs.decompiler.flash.amf.amf3.types.ObjectType;
import com.jpexs.decompiler.flash.amf.amf3.types.VectorDoubleType;
import com.jpexs.decompiler.flash.amf.amf3.types.VectorIntType;
import com.jpexs.decompiler.flash.amf.amf3.types.VectorObjectType;
import com.jpexs.decompiler.flash.amf.amf3.types.VectorUIntType;
import com.jpexs.decompiler.flash.amf.amf3.types.XmlDocType;
import com.jpexs.decompiler.flash.amf.amf3.types.XmlType;
import java.io.IOException;
import java.io.StringReader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Amf3Importer {
private Amf3Lexer lexer;
private ParsedSymbol lex() throws IOException, Amf3ParseException {
ParsedSymbol ret = lexer.lex();
return ret;
}
private void pushback(ParsedSymbol s) {
lexer.pushback(s);
}
private void expected(ParsedSymbol symb, int line, Object... expected) throws IOException, Amf3ParseException {
boolean found = false;
for (Object t : expected) {
if (symb.type == t) {
found = true;
}
if (symb.group == t) {
found = true;
}
}
if (!found) {
String expStr = "";
boolean first = true;
for (Object e : expected) {
if (!first) {
expStr += " or ";
}
expStr += e;
first = false;
}
throw new Amf3ParseException("" + expStr + " expected but " + symb.type + " found", line);
}
}
private ParsedSymbol expectedType(Object... type) throws IOException, Amf3ParseException {
ParsedSymbol symb = lex();
expected(symb, lexer.yyline(), type);
return symb;
}
private JsArray parseArray(Map<String, Object> objectTable) throws IOException, Amf3ParseException {
expectedType(SymbolType.BRACKET_OPEN);
List<Object> arrayVals = new ArrayList<>();
ParsedSymbol s = lex();
if (!s.isType(SymbolType.BRACKET_CLOSE)) {
pushback(s);
arrayVals.add(value(objectTable));
s = lex();
while (s.isType(SymbolType.COMMA)) {
arrayVals.add(value(objectTable));
s = lex();
}
pushback(s);
}
expectedType(SymbolType.BRACKET_CLOSE);
return new JsArray(arrayVals);
}
private class JsArray {
private List<Object> values = new ArrayList<>();
public JsArray() {
}
public JsArray(List<Object> values) {
this.values = values;
}
public void add(Object value) {
values.add(value);
}
public List<Object> getValues() {
return values;
}
}
private class JsObject {
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{");
for (Pair<Object, Object> p : values) {
sb.append(p.getFirst()).append(":").append("?").append(",\r\n");
}
sb.append("}");
return sb.toString();
}
private final List<Pair<Object, Object>> values = new ArrayList<>();
public void add(Object key, Object value) {
values.add(new Pair<>(key, value));
}
public String getString(Object key) throws Amf3ParseException {
return (String) getRequired(key, "String");
}
public Boolean getBoolean(Object key) throws Amf3ParseException {
return (Boolean) getRequired(key, "Boolean");
}
public JsObject getJsObject(Object key) throws Amf3ParseException {
return (JsObject) getRequired(key, "JsObject");
}
public JsArray getJsArray(Object key) throws Amf3ParseException {
return (JsArray) getRequired(key, "JsArray");
}
public List<Object> getJsArrayOfObject(Object key) throws Amf3ParseException {
return getJsArray(key).getValues();
}
@SuppressWarnings("unchecked")
public List<String> getJsArrayOfString(Object key) throws Amf3ParseException {
return (List<String>) getJsArray(key, "String");
}
@SuppressWarnings("unchecked")
public List<Long> getJsArrayOfInt(Object key) throws Amf3ParseException {
return (List<Long>) getJsArray(key, "int");
}
@SuppressWarnings("unchecked")
public List<Long> getJsArrayOfUint(Object key) throws Amf3ParseException {
return (List<Long>) getJsArray(key, "uint");
}
@SuppressWarnings("unchecked")
public List<Double> getJsArrayOfNumber(Object key) throws Amf3ParseException {
return (List<Double>) getJsArray(key, "Number");
}
public List getJsArray(Object key, String valueType) throws Amf3ParseException {
JsArray jsArr = (JsArray) getRequired(key, "JsArray");
switch (valueType) {
case "String":
List<String> stringList = new ArrayList<>();
for (Object v : jsArr.getValues()) {
String sv = null;
if (v instanceof String) {
sv = (String) v;
} else {
throw new Amf3ParseException("Not String: " + v, 0);
}
stringList.add(sv);
}
return stringList;
case "int":
case "uint":
List<Long> longList = new ArrayList<>();
for (Object v : jsArr.getValues()) {
Long lv = null;
if (v instanceof Long) {
lv = (Long) v;
} else {
throw new Amf3ParseException("Not an Integer value: " + v, 0);
}
if (valueType.equals("uint") && lv < 0) {
throw new Amf3ParseException("Not an Unsigned Integer value: " + v, 0);
}
longList.add(lv);
}
return longList;
case "Number":
List<Double> doubleList = new ArrayList<>();
for (Object v : jsArr.getValues()) {
Double cv = null;
if (v instanceof Long) {
cv = (double) (long) (Long) v;
} else if (v instanceof Double) {
cv = (Double) v;
} else {
throw new Amf3ParseException("Not a Number: " + v, 0);
}
doubleList.add(cv);
}
return doubleList;
default:
throw new Amf3ParseException("Unsupported array value type: " + valueType, 0);
}
}
public Long getLong(Object key) throws Amf3ParseException {
return (Long) getRequired(key, "Long");
}
public Object getRequired(Object key, String requiredType) throws Amf3ParseException {
if (!containsKey(key)) {
throw new Amf3ParseException("\"" + key + "\" is missing", 0);
}
Object val = get(key);
boolean typeMatches = true;
if (requiredType != null) {
switch (requiredType) {
case "String":
typeMatches = val instanceof String;
break;
case "Long":
typeMatches = val instanceof Long;
break;
case "JsObject":
typeMatches = val instanceof JsObject;
break;
case "JsArray":
typeMatches = val instanceof JsArray;
break;
case "Boolean":
typeMatches = val instanceof Boolean;
break;
}
}
if (!typeMatches) {
throw new Amf3ParseException("\"" + key + "\" value must be of type " + requiredType, 0);
}
return val;
}
public boolean containsKey(Object key) {
for (Pair<Object, Object> p : values) {
if (p.getFirst().equals(key)) {
return true;
}
}
return false;
}
public Object get(Object key) {
for (Pair<Object, Object> p : values) {
if (p.getFirst().equals(key)) {
return p.getSecond();
}
}
return null;
}
public void resolve(Object key, Map<String, Object> objectTable, boolean allowTypedObject) throws Amf3ParseException {
for (Pair<Object, Object> p : values) {
if (p.getFirst().equals(key)) {
Object resolved = resolveObjects(p.getSecond(), objectTable, allowTypedObject);
p.setSecond(resolved);
return;
}
}
}
public List<String> stringKeys() {
List<String> ret = new ArrayList<>();
for (Pair<Object, Object> p : values) {
if (p.getFirst() instanceof String) {
ret.add((String) p.getFirst());
}
}
return ret;
}
public List<Pair<Object, Object>> getAll() {
return values;
}
public List<Pair<String, Object>> getStringMapped() {
List<Pair<String, Object>> ret = new ArrayList<>();
for (Pair<Object, Object> p : values) {
if (p.getFirst() instanceof String) {
String keyStr = (String) p.getFirst();
ret.add(new Pair<>(keyStr, p.getSecond()));
}
}
return ret;
}
public Map<String, Object> getStringMap() {
Map<String, Object> ret = new HashMap<>();
for (Pair<Object, Object> p : values) {
if (p.getFirst() instanceof String) {
String keyStr = (String) p.getFirst();
ret.put(keyStr, values);
}
}
return ret;
}
}
private JsObject parseObject(Map<String, Object> objectTable) throws IOException, Amf3ParseException {
JsObject ret = new JsObject();
expectedType(SymbolType.CURLY_OPEN);
ParsedSymbol s = lex();
if (!s.isType(SymbolType.CURLY_CLOSE)) {
pushback(s);
do {
Object key = value(objectTable);
expectedType(SymbolType.COLON);
Object value = value(objectTable);
ret.add(key, value);
if ("id".equals(key)) {
if (!(value instanceof String)) {
throw new Amf3ParseException("id must be string value", lexer.yyline());
}
objectTable.put((String) value, BasicType.UNDEFINED);
}
s = lex();
} while (s.isType(SymbolType.COMMA));
}
pushback(s);
expectedType(SymbolType.CURLY_CLOSE);
return ret;
}
private Object resolveObjects(Object object, Map<String, Object> objectTable, boolean allowTypedObject) throws Amf3ParseException {
Object resultObject = object;
if (object instanceof JsArray) {
JsArray jsa = (JsArray) object;
JsArray ret = new JsArray();
for (int i = 0; i < jsa.values.size(); i++) {
ret.values.add(resolveObjects(jsa.values.get(i), objectTable, true));
}
resultObject = ret;
} else if (object instanceof JsObject) {
if (allowTypedObject) {
JsObject typedObject = (JsObject) object;
if (typedObject.containsKey("type")) {
String typeStr = typedObject.getString("type");
String id = typedObject.containsKey("id") ? typedObject.getString("id") : null;
switch (typeStr) {
case "Date":
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SS");
String dateStr = typedObject.getString("value");
try {
resultObject = new DateType((double) sdf.parse(dateStr).getTime());
} catch (ParseException ex) {
throw new Amf3ParseException("Invalid date format: " + dateStr, lexer.yyline());
}
break;
case "XML":
resultObject = new XmlType(typedObject.getString("value"));
break;
case "XMLDocument":
resultObject = new XmlDocType(typedObject.getString("value"));
break;
case "Object":
String className = typedObject.getString("className");
if (typedObject.containsKey("serialized")) {
//TODO
} else {
boolean dynamic = typedObject.getBoolean("dynamic");
typedObject.resolve("sealedMembers", objectTable, false);
JsObject jsoSealed = typedObject.getJsObject("sealedMembers");
List<Pair<String, Object>> sealedMembers = jsoSealed.getStringMapped();
typedObject.resolve("dynamicMembers", objectTable, false);
List<Pair<String, Object>> dynamicMembers = typedObject.getJsObject("dynamicMembers").getStringMapped();
List<String> sealedMemberNames = new ArrayList<>(jsoSealed.stringKeys());
resultObject = new ObjectType(new Traits(className, dynamic, sealedMemberNames), sealedMembers, dynamicMembers);
}
break;
case "Array":
typedObject.resolve("denseValues", objectTable, false);
List<Object> denseValues = typedObject.getJsArray("denseValues").getValues();
typedObject.resolve("associativeValues", objectTable, false);
JsObject resolvedArr = typedObject.getJsObject("associativeValues");
List<Pair<String, Object>> associativeValues = resolvedArr.getStringMapped();
resultObject = new ArrayType(denseValues, associativeValues);
break;
case "Vector":
boolean fixed = typedObject.getBoolean("fixed");
String subtype = typedObject.getString("subtype");
typedObject.resolve("values", objectTable, false);
switch (subtype) {
case "int":
resultObject = new VectorIntType(fixed, typedObject.getJsArrayOfInt("values"));
break;
case "uint":
resultObject = new VectorUIntType(fixed, typedObject.getJsArrayOfUint("values"));
break;
case "Number":
resultObject = new VectorDoubleType(fixed, typedObject.getJsArrayOfNumber("values"));
break;
default:
resultObject = new VectorObjectType(fixed, subtype, typedObject.getJsArrayOfObject("values"));
break;
}
break;
case "ByteArray":
try {
resultObject = new ByteArrayType(javax.xml.bind.DatatypeConverter.parseHexBinary(typedObject.getString("value")));
} catch (IllegalArgumentException iex) {
throw new Amf3ParseException("Invalid hex byte sequence", lexer.yyline());
}
break;
case "Dictionary":
boolean weakKeys = typedObject.getBoolean("weakKeys");
typedObject.resolve("entries", objectTable, false);
List<Pair<Object, Object>> entries = typedObject.getJsObject("entries").getAll();
resultObject = new DictionaryType(weakKeys, entries);
break;
default:
throw new Amf3ParseException("Unknown object type: " + typeStr, lexer.yyline());
}
if (id != null) {
objectTable.put(id, resultObject);
}
}
} else { //not allowTypeObject
JsObject jsObject = (JsObject) object;
for (Pair<Object, Object> p : jsObject.getAll()) {
p.setFirst(resolveObjects(p.getFirst(), objectTable, true));
p.setSecond(resolveObjects(p.getSecond(), objectTable, true));
}
resultObject = jsObject;
}
}
return resultObject;
}
private Object value(Map<String, Object> objectTable) throws IOException, Amf3ParseException {
ParsedSymbol s = lex();
switch (s.type) {
case CURLY_OPEN:
pushback(s);
return parseObject(objectTable);
case BRACKET_OPEN:
pushback(s);
return parseArray(objectTable);
case STRING:
case DOUBLE:
case INTEGER:
return s.value;
case UNDEFINED:
return BasicType.UNDEFINED;
case NULL:
return BasicType.NULL;
case UNKNOWN:
return BasicType.UNKNOWN;
case TRUE:
return Boolean.TRUE;
case FALSE:
return Boolean.FALSE;
case REFERENCE:
String referencedId = (String) s.value;
return new ReferencedObjectType(referencedId);
default:
throw new Amf3ParseException("Unexpected symbol: " + s, lexer.yyline());
}
}
/**
* Deeply replace all ReferencedObjectType with the correct value
*
* @param object
* @param objectsTable
* @return
*/
private Object replaceReferences(Object object, Map<String, Object> objectsTable) throws Amf3ParseException {
if (object instanceof ReferencedObjectType) {
String key = ((ReferencedObjectType) object).key;
if (!objectsTable.containsKey(key)) {
throw new Amf3ParseException("Reference to undefined object: #" + key, 0);
}
return objectsTable.get(key);
} else if (object instanceof ObjectType) {
ObjectType ot = (ObjectType) object;
for (Pair<String, Object> p : ot.getSealedMembers()) {
p.setSecond(replaceReferences(p.getSecond(), objectsTable));
}
for (Pair<String, Object> p : ot.getDynamicMembers()) {
p.setSecond(replaceReferences(p.getSecond(), objectsTable));
}
} else if (object instanceof ArrayType) {
ArrayType at = (ArrayType) object;
for (Pair<String, Object> p : at.getAssociativeValues()) {
p.setSecond(replaceReferences(p.getSecond(), objectsTable));
}
for (int i = 0; i < at.getDenseValues().size(); i++) {
at.getDenseValues().set(i, replaceReferences(at.getDenseValues().get(i), objectsTable));
}
} else if (object instanceof VectorObjectType) {
VectorObjectType vot = (VectorObjectType) object;
for (int i = 0; i < vot.getValues().size(); i++) {
vot.getValues().set(i, replaceReferences(vot.getValues().get(i), objectsTable));
}
} else if (object instanceof DictionaryType) {
DictionaryType dt = (DictionaryType) object;
for (Pair<Object, Object> p : dt.getPairs()) {
p.setFirst(replaceReferences(p.getFirst(), objectsTable));
p.setSecond(replaceReferences(p.getSecond(), objectsTable));
}
}
return object;
}
private class ReferencedObjectType {
private final String key;
public ReferencedObjectType(String key) {
this.key = key;
}
public String getKey() {
return key;
}
@Override
public String toString() {
return "#" + key;
}
}
public Object stringToAmf(String val) throws IOException, Amf3ParseException {
lexer = new Amf3Lexer(new StringReader(val));
Map<String, Object> objectsTable = new HashMap<>();
List<ReferencedObjectType> references = new ArrayList<>();
Object result = value(objectsTable);
Object resultResolved = resolveObjects(result, objectsTable, true);
Object resultNoRef = replaceReferences(resultResolved, objectsTable);
return resultNoRef;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2010-2016 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.importers.amf.amf3;
import com.jpexs.decompiler.flash.ParseException;
/**
*
* @author JPEXS
*/
public class Amf3ParseException extends ParseException {
public Amf3ParseException(String text, long line) {
super(text, line);
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2010-2016 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.importers.amf.amf3;
/**
*
* @author JPEXS
*/
public class ParsedSymbol {
public SymbolGroup group;
public Object value;
public SymbolType type;
public ParsedSymbol(SymbolGroup group, SymbolType type) {
this.group = group;
this.type = type;
this.value = null;
}
public ParsedSymbol(SymbolGroup group, SymbolType type, Object value) {
this.group = group;
this.type = type;
this.value = value;
}
@Override
public String toString() {
return group.toString() + " " + type.toString() + " " + (value != null ? value.toString() : "");
}
public boolean isType(Object... types) {
for (Object t : types) {
if (t instanceof SymbolGroup) {
if (group == t) {
return true;
}
}
if (t instanceof SymbolType) {
if (type == t) {
return true;
}
}
}
return false;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2010-2016 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.importers.amf.amf3;
/**
*
* @author JPEXS
*/
public enum SymbolGroup {
OPERATOR,
KEYWORD,
STRING,
COMMENT,
IDENTIFIER,
INTEGER,
DOUBLE,
EOF,
GLOBALCONST,
UNKNOWN
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2010-2016 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.importers.amf.amf3;
/**
*
* @author JPEXS
*/
public enum SymbolType {
NULL,
UNDEFINED,
FALSE,
TRUE,
CURLY_OPEN,
CURLY_CLOSE,
BRACKET_OPEN,
BRACKET_CLOSE,
COMMA,
COLON,
UNKNOWN,
REFERENCE,
IDENTIFIER,
INTEGER,
STRING,
DOUBLE,
EOF
}

View File

@@ -19,6 +19,7 @@ package com.jpexs.decompiler.flash.tags;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.SWFOutputStream;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Value;
import com.jpexs.decompiler.flash.tags.base.ASMSourceContainer;
import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag;
import com.jpexs.decompiler.flash.types.BasicType;
@@ -436,4 +437,9 @@ public class PlaceObject2Tag extends PlaceObjectTypeTag implements ASMSourceCont
matrix = old;
}
}
@Override
public Amf3Value getAmfData() {
return null;
}
}

View File

@@ -20,6 +20,7 @@ import com.jpexs.decompiler.flash.EndOfStreamException;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.SWFOutputStream;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Value;
import com.jpexs.decompiler.flash.tags.base.ASMSourceContainer;
import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag;
import com.jpexs.decompiler.flash.types.BasicType;
@@ -615,4 +616,10 @@ public class PlaceObject3Tag extends PlaceObjectTypeTag implements ASMSourceCont
matrix = old;
}
}
@Override
public Amf3Value getAmfData() {
return null;
}
}

View File

@@ -20,6 +20,8 @@ import com.jpexs.decompiler.flash.EndOfStreamException;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.SWFOutputStream;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Value;
import com.jpexs.decompiler.flash.amf.amf3.NoSerializerExistsException;
import com.jpexs.decompiler.flash.tags.base.ASMSourceContainer;
import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag;
import com.jpexs.decompiler.flash.types.BasicType;
@@ -37,10 +39,13 @@ import com.jpexs.decompiler.flash.types.annotations.SWFType;
import com.jpexs.decompiler.flash.types.annotations.SWFVersion;
import com.jpexs.decompiler.flash.types.filters.FILTER;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.Helper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Same as PlaceObject3Tag except additional AMF data
@@ -230,7 +235,7 @@ public class PlaceObject4Tag extends PlaceObjectTypeTag implements ASMSourceCont
@Reserved
public boolean reserved;
public byte[] amfData; //TODO: Parse AMF data?
public Amf3Value amfData;
/**
* Constructor
@@ -241,7 +246,7 @@ public class PlaceObject4Tag extends PlaceObjectTypeTag implements ASMSourceCont
super(swf, ID, NAME, null);
}
public PlaceObject4Tag(SWF swf, boolean placeFlagMove, int depth, String className, int characterId, MATRIX matrix, CXFORMWITHALPHA colorTransform, int ratio, String name, int clipDepth, List<FILTER> surfaceFilterList, int blendMode, int bitmapCache, int visible, RGBA backgroundColor, CLIPACTIONS clipActions, byte[] amfData) {
public PlaceObject4Tag(SWF swf, boolean placeFlagMove, int depth, String className, int characterId, MATRIX matrix, CXFORMWITHALPHA colorTransform, int ratio, String name, int clipDepth, List<FILTER> surfaceFilterList, int blendMode, int bitmapCache, int visible, RGBA backgroundColor, CLIPACTIONS clipActions, Amf3Value amfData) {
super(swf, ID, NAME, null);
this.placeFlagHasClassName = className != null;
this.placeFlagHasFilterList = surfaceFilterList != null;
@@ -353,8 +358,9 @@ public class PlaceObject4Tag extends PlaceObjectTypeTag implements ASMSourceCont
if (placeFlagHasClipActions) {
clipActions = sis.readCLIPACTIONS(swf, this, "clipActions");
}
amfData = sis.readBytesEx(sis.available(), "amfData");
if (sis.available() > 0) {
amfData = sis.readAmf3Object("amfValue");
}
}
/**
@@ -424,6 +430,13 @@ public class PlaceObject4Tag extends PlaceObjectTypeTag implements ASMSourceCont
if (placeFlagHasClipActions) {
sos.writeCLIPACTIONS(clipActions);
}
if (amfData != null && amfData.getValue() != null) {
try {
sos.writeAmf3Object(amfData);
} catch (NoSerializerExistsException ex) {
throw new IOException("Class \"" + ex.getClassName() + "\" implements IExternalizable, it cannot be saved");
}
}
}
@Override
@@ -620,4 +633,10 @@ public class PlaceObject4Tag extends PlaceObjectTypeTag implements ASMSourceCont
matrix = old;
}
}
@Override
public Amf3Value getAmfData() {
return amfData;
}
}

View File

@@ -19,6 +19,7 @@ package com.jpexs.decompiler.flash.tags;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.SWFOutputStream;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Value;
import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag;
import com.jpexs.decompiler.flash.types.BasicType;
import com.jpexs.decompiler.flash.types.CLIPACTIONS;
@@ -255,4 +256,10 @@ public class PlaceObjectTag extends PlaceObjectTypeTag {
matrix = old;
}
}
@Override
public Amf3Value getAmfData() {
return null;
}
}

View File

@@ -18,6 +18,7 @@ package com.jpexs.decompiler.flash.tags.base;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFOutputStream;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Value;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.types.CLIPACTIONS;
import com.jpexs.decompiler.flash.types.ColorTransform;
@@ -76,6 +77,8 @@ public abstract class PlaceObjectTypeTag extends Tag implements CharacterIdTag {
public abstract void writeTagWithMatrix(SWFOutputStream sos, MATRIX m) throws IOException;
public abstract Amf3Value getAmfData();
@Override
public String getName() {
String result = super.getName();

View File

@@ -23,6 +23,9 @@ import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFCompression;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.action.Action;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Value;
import com.jpexs.decompiler.flash.amf.amf3.Pair;
import com.jpexs.decompiler.flash.amf.amf3.types.ObjectType;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.exporters.MovieExporter;
import com.jpexs.decompiler.flash.exporters.SoundExporter;
@@ -70,6 +73,7 @@ import com.jpexs.decompiler.flash.tags.base.SoundTag;
import com.jpexs.decompiler.flash.tags.base.TextTag;
import com.jpexs.decompiler.flash.tags.enums.ImageFormat;
import com.jpexs.decompiler.flash.tags.font.CharacterRanges;
import com.jpexs.decompiler.flash.timeline.Timelined;
import com.jpexs.decompiler.flash.types.BUTTONCONDACTION;
import com.jpexs.decompiler.flash.types.BUTTONRECORD;
import com.jpexs.decompiler.flash.types.CLIPACTIONRECORD;
@@ -167,6 +171,10 @@ public class XFLConverter {
public static final int KEY_MODE_SHAPE_LAYERS = 8192;
public static final String PUBLISH_DATA_PREFIX = "PUB_PRST_DATA";
public static final String PUBLISH_DATA_FORMAT = "_EMBED_SWF_";
private final Random random = new Random(123); // predictable random
private static void convertShapeEdge(MATRIX mat, SHAPERECORD record, int x, int y, StringBuilder ret) {
@@ -581,7 +589,7 @@ public class XFLConverter {
LINESTYLEARRAY actualLinestyles = lineStyles;
int strokeStyleOrig = 0;
fillStyleCount = fillStyles.fillStyles.length;
fillStyleCount = fillStyles == null ? 0 : fillStyles.fillStyles.length;
for (SHAPERECORD edge : shapeRecords) {
if (edge instanceof StyleChangeRecord) {
StyleChangeRecord scr = (StyleChangeRecord) edge;
@@ -602,7 +610,7 @@ public class XFLConverter {
if ((fillStyle0 <= 0) && (fillStyle1 <= 0) && (strokeStyle > 0) && morphshape) {
if (shapeNum == 4) {
if (strokeStyleOrig > 0) {
if (!((LINESTYLE2) actualLinestyles.lineStyles[strokeStyleOrig]).hasFillFlag) {
if (actualLinestyles != null && !((LINESTYLE2) actualLinestyles.lineStyles[strokeStyleOrig]).hasFillFlag) {
RGBA color = (RGBA) actualLinestyles.lineStyles[strokeStyleOrig].color;
if (color.alpha == 0 && color.red == 0 && color.green == 0 && color.blue == 0) {
empty = true;
@@ -701,7 +709,7 @@ public class XFLConverter {
if ((fillStyle0 <= 0) && (fillStyle1 <= 0) && (strokeStyle > 0) && morphshape) {
if (shapeNum == 4) {
if (strokeStyleOrig > 0) {
if (!((LINESTYLE2) actualLinestyles.lineStyles[strokeStyleOrig]).hasFillFlag) {
if (actualLinestyles != null && !((LINESTYLE2) actualLinestyles.lineStyles[strokeStyleOrig]).hasFillFlag) {
RGBA color = (RGBA) actualLinestyles.lineStyles[strokeStyleOrig].color;
if (color.alpha == 0 && color.red == 0 && color.green == 0 && color.blue == 0) {
empty = true;
@@ -745,7 +753,7 @@ public class XFLConverter {
if ((fillStyle0 <= 0) && (fillStyle1 <= 0) && (strokeStyle > 0) && morphshape) {
if (shapeNum == 4) {
if (strokeStyleOrig > 0) {
if (!((LINESTYLE2) actualLinestyles.lineStyles[strokeStyleOrig]).hasFillFlag) {
if (actualLinestyles != null && !((LINESTYLE2) actualLinestyles.lineStyles[strokeStyleOrig]).hasFillFlag) {
RGBA color = (RGBA) actualLinestyles.lineStyles[strokeStyleOrig].color;
if (color.alpha == 0 && color.red == 0 && color.green == 0 && color.blue == 0) {
empty = true;
@@ -1058,7 +1066,7 @@ public class XFLConverter {
}
}
private static void convertSymbolInstance(String name, MATRIX matrix, ColorTransform colorTransform, boolean cacheAsBitmap, int blendMode, List<FILTER> filters, boolean isVisible, RGBA backgroundColor, CLIPACTIONS clipActions, CharacterTag tag, HashMap<Integer, CharacterTag> characters, ReadOnlyTagList tags, FLAVersion flaVersion, XFLXmlWriter writer) throws XMLStreamException {
private static void convertSymbolInstance(String name, MATRIX matrix, ColorTransform colorTransform, boolean cacheAsBitmap, int blendMode, List<FILTER> filters, boolean isVisible, RGBA backgroundColor, CLIPACTIONS clipActions, Amf3Value metadata, CharacterTag tag, HashMap<Integer, CharacterTag> characters, ReadOnlyTagList tags, FLAVersion flaVersion, XFLXmlWriter writer) throws XMLStreamException {
if (matrix == null) {
matrix = new MATRIX();
}
@@ -1185,6 +1193,55 @@ public class XFLConverter {
writer.writeEndElement();
writer.writeEndElement();
}
if (metadata != null && (metadata.getValue() instanceof ObjectType)) {
ObjectType metadataObject = (ObjectType) metadata.getValue();
if (metadataObject.isDynamic()) {
writer.writeStartElement("persistentData");
List<String> exportedNames = new ArrayList<>();
for (Pair<String, Object> dynamicMember : metadataObject.getDynamicMembers()) {
String n = dynamicMember.getFirst();
Object v = dynamicMember.getSecond();
if (v instanceof Long) {
exportedNames.add(n);
writer.writeStartElement("PD");
writer.writeAttribute("n", n);
writer.writeAttribute("t", "i");
writer.writeAttribute("v", (Long) v);
writer.writeEndElement();
exportedNames.add(n);
} else if (v instanceof Double) {
writer.writeStartElement("PD");
writer.writeAttribute("n", n);
writer.writeAttribute("t", "d");
writer.writeAttribute("v", (Double) v);
writer.writeEndElement();
exportedNames.add(n);
} else if (v instanceof String) {
writer.writeStartElement("PD");
writer.writeAttribute("n", n);
//missing t attrinute = string (maybe "s"?)
writer.writeAttribute("v", (String) v);
writer.writeEndElement();
exportedNames.add(n);
}
/*
From JSFL, also data types integerArray ("I"), doubleArray("D") and byteArray("B") can be set.
These datatypes can be in the FLA file but are not exported to SWF with _EMBED_SWF_ publish format.
*/
}
//Mark these names for publishing in embedded swf format: (setPublishPersistentData function in JSFL)
for (String n : exportedNames) {
writer.writeStartElement("PD");
writer.writeAttribute("n", PUBLISH_DATA_PREFIX + PUBLISH_DATA_FORMAT + n);
writer.writeAttribute("t", "i");
writer.writeAttribute("v", 1);
writer.writeEndElement();
}
writer.writeEndElement();
}
}
writer.writeEndElement();
}
@@ -1332,7 +1389,7 @@ public class XFLConverter {
} else if (character instanceof DefineVideoStreamTag) {
convertVideoInstance(null, matrix, (DefineVideoStreamTag) character, null, recCharWriter);
} else {
convertSymbolInstance(null, matrix, colorTransformAlpha, false, blendMode, filters, true, null, null, characters.get(rec.characterId), characters, tags, flaVersion, recCharWriter);
convertSymbolInstance(null, matrix, colorTransformAlpha, false, blendMode, filters, true, null, null, null, characters.get(rec.characterId), characters, tags, flaVersion, recCharWriter);
}
int duration = frame - lastFrame;
@@ -1909,6 +1966,7 @@ public class XFLConverter {
CharacterTag character = null;
MATRIX matrix = null;
Amf3Value metadata = null;
String instanceName = null;
ColorTransform colorTransForm = null;
boolean cacheAsBitmap = false;
@@ -1953,6 +2011,10 @@ public class XFLConverter {
if (characters.containsKey(characterId)) {
character = characters.get(characterId);
if (po.flagMove()) {
Amf3Value metadata2 = po.getAmfData();
if (metadata2 != null && metadata2.getValue() != null) {
metadata = metadata2;
}
MATRIX matrix2 = po.getMatrix();
if (matrix2 != null) {
matrix = matrix2;
@@ -1986,6 +2048,7 @@ public class XFLConverter {
ratio = ratio2;
}
} else {
metadata = po.getAmfData();
matrix = po.getMatrix();
instanceName = po.getInstanceName();
colorTransForm = po.getColorTransform();
@@ -2009,6 +2072,7 @@ public class XFLConverter {
shapeTween = false;
}
character = null;
metadata = null;
matrix = null;
instanceName = null;
colorTransForm = null;
@@ -2041,7 +2105,7 @@ public class XFLConverter {
} else if (character instanceof DefineVideoStreamTag) {
convertVideoInstance(instanceName, matrix, (DefineVideoStreamTag) character, clipActions, elementsWriter);
} else {
convertSymbolInstance(instanceName, matrix, colorTransForm, cacheAsBitmap, blendMode, filters, isVisible, backGroundColor, clipActions, character, characters, tags, flaVersion, elementsWriter);
convertSymbolInstance(instanceName, matrix, colorTransForm, cacheAsBitmap, blendMode, filters, isVisible, backGroundColor, clipActions, metadata, character, characters, tags, flaVersion, elementsWriter);
}
}
}
@@ -2601,14 +2665,14 @@ public class XFLConverter {
writer.writeEndElement();
writer.writeStartElement("textRuns");
int fontId = -1;
int fontId;
FontTag font = null;
String fontName = null;
String fontName;
String psFontName = null;
int textHeight = -1;
RGB textColor = null;
RGBA textColorA = null;
boolean newline = false;
boolean newline;
boolean firstRun = true;
@SuppressWarnings("unchecked")
List<Integer> leftMargins = (List<Integer>) attrs.get("allLeftMargins");
@@ -2776,8 +2840,8 @@ public class XFLConverter {
int indent = -1;
int lineSpacing = -1;
String alignment = null;
boolean italic = false;
boolean bold = false;
boolean italic;
boolean bold;
String fontFace = null;
int size = -1;
RGBA textColor = null;
@@ -2807,9 +2871,9 @@ public class XFLConverter {
bold = ft.isBold();
size = det.fontHeight;
fontFace = fontName;
String installedFont = null;
String installedFont;
if ((installedFont = FontTag.isFontFamilyInstalled(fontName)) != null) {
fontName = installedFont;
//fontName = installedFont;
fontFace = new Font(installedFont, (italic ? Font.ITALIC : 0) | (bold ? Font.BOLD : 0) | (!italic && !bold ? Font.PLAIN : 0), size < 0 ? 10 : size).getPSName();
}
@@ -2866,6 +2930,33 @@ public class XFLConverter {
}
}
private boolean hasAmfMetadata(Tag tag) {
if (tag instanceof PlaceObjectTypeTag) {
PlaceObjectTypeTag po = (PlaceObjectTypeTag) tag;
if (po.getAmfData() != null && po.getAmfData().getValue() != null) {
return true;
}
}
if (tag instanceof Timelined) {
Timelined tl = (Timelined) tag;
for (Tag t : tl.getTags()) {
if (hasAmfMetadata(t)) {
return true;
}
}
}
return false;
}
private boolean hasAmfMetadata(SWF swf) {
for (Tag t : swf.getTags()) {
if (hasAmfMetadata(t)) {
return true;
}
}
return false;
}
public void convertSWF(AbortRetryIgnoreHandler handler, SWF swf, String swfFileName, String outfile, XFLExportSettings settings, String generator, String generatorVerName, String generatorVersion, boolean parallel, FLAVersion flaVersion) throws IOException, InterruptedException {
FileAttributesTag fa = swf.getFileAttributes();
@@ -2895,6 +2986,7 @@ public class XFLConverter {
List<Integer> nonLibraryShapes = getNonLibraryShapes(swf.getTags(), characters);
Map<Integer, String> characterClasses = getCharacterClasses(swf.getTags());
Map<Integer, String> characterVariables = getCharacterVariables(swf.getTags());
boolean hasAmfMetadata = hasAmfMetadata(swf);
String backgroundColor = "#ffffff";
SetBackgroundColorTag setBgColorTag = swf.getBackgroundColor();
@@ -2941,6 +3033,18 @@ public class XFLConverter {
convertTimeline(0, nonLibraryShapes, backgroundColor, swf.getTags(), swf.getTags(), characters, "Scene 1", flaVersion, files, domDocument);
domDocument.writeEndElement();
if (hasAmfMetadata) {
domDocument.writeStartElement("persistentData");
domDocument.writeStartElement("PD");
domDocument.writeAttribute("n", PUBLISH_DATA_PREFIX + PUBLISH_DATA_FORMAT);
domDocument.writeAttribute("t", "i");
domDocument.writeAttribute("v", 1);
domDocument.writeEndElement();
domDocument.writeEndElement();
}
domDocument.writeEndElement();
} catch (XMLStreamException ex) {
logger.log(Level.SEVERE, null, ex);