mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-07-18 21:38:26 +00:00
@@ -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
|
||||
*
|
||||
|
||||
@@ -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<>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.jpexs.decompiler.flash.amf.amf3;
|
||||
|
||||
public class Marker {
|
||||
|
||||
public static final int UNDEFINED = 0x00;
|
||||
public static final int NULL = 0x01;
|
||||
public static final int FALSE = 0x02;
|
||||
public static final int TRUE = 0x03;
|
||||
public static final int INTEGER = 0x04;
|
||||
public static final int DOUBLE = 0x05;
|
||||
public static final int STRING = 0x06;
|
||||
public static final int XML_DOC = 0x07;
|
||||
public static final int DATE = 0x08;
|
||||
public static final int ARRAY = 0x09;
|
||||
public static final int OBJECT = 0x0A;
|
||||
public static final int XML = 0x0B;
|
||||
public static final int BYTE_ARRAY = 0x0C;
|
||||
public static final int VECTOR_INT = 0x0D;
|
||||
public static final int VECTOR_UINT = 0x0E;
|
||||
public static final int VECTOR_DOUBLE = 0x0F;
|
||||
public static final int VECTOR_OBJECT = 0x10;
|
||||
public static final int DICTIONARY = 0x11;
|
||||
}
|
||||
@@ -0,0 +1,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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.jpexs.decompiler.flash.amf.amf3;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Traits {
|
||||
|
||||
private String className;
|
||||
private boolean dynamic;
|
||||
private List<String> sealedMemberNames;
|
||||
|
||||
public Traits(String className, boolean dynamic, List<String> sealedMemberNames) {
|
||||
this.className = className;
|
||||
this.dynamic = dynamic;
|
||||
this.sealedMemberNames = sealedMemberNames;
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return className;
|
||||
}
|
||||
|
||||
public boolean isDynamic() {
|
||||
return dynamic;
|
||||
}
|
||||
|
||||
public List<String> getSealedMemberNames() {
|
||||
return sealedMemberNames;
|
||||
}
|
||||
|
||||
public void setClassName(String className) {
|
||||
this.className = className;
|
||||
}
|
||||
|
||||
public void setDynamic(boolean dynamic) {
|
||||
this.dynamic = dynamic;
|
||||
}
|
||||
|
||||
public void setSealedMemberNames(List<String> sealedMemberNames) {
|
||||
this.sealedMemberNames = sealedMemberNames;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.jpexs.decompiler.flash.amf.amf3;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface WithSubValues {
|
||||
|
||||
public List<Object> getSubValues();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.jpexs.decompiler.flash.amf.amf3.types;
|
||||
|
||||
public interface Amf3ValueType {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user