AMF3 importer

refactoring AMFTools to AMF3 exporter
This commit is contained in:
Jindra Petřík
2016-07-19 22:21:47 +02:00
parent d82f0f17c3
commit 0d8e621a17
23 changed files with 2109 additions and 69 deletions
@@ -2,8 +2,8 @@ package com.jpexs.decompiler.flash.amf.amf3;
public class Pair<T1, T2> {
private final T1 first;
private final T2 second;
private T1 first;
private T2 second;
public Pair(T1 first, T2 second) {
this.first = first;
@@ -17,4 +17,12 @@ public class Pair<T1, T2> {
public T2 getSecond() {
return second;
}
public void setSecond(T2 second) {
this.second = second;
}
public void setFirst(T1 first) {
this.first = first;
}
}
@@ -1,6 +1,6 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Tools;
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;
@@ -34,7 +34,7 @@ public abstract class AbstractVectorType<T> implements WithSubValues {
@Override
public String toString() {
return Amf3Tools.amfToString(this);
return Amf3Exporter.amfToString(this);
}
}
@@ -1,6 +1,6 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Tools;
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;
@@ -27,7 +27,7 @@ public class ArrayType implements WithSubValues {
@Override
public String toString() {
return Amf3Tools.amfToString(this);
return Amf3Exporter.amfToString(this);
}
@Override
@@ -1,6 +1,6 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Tools;
import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter;
public class ByteArrayType {
@@ -16,7 +16,7 @@ public class ByteArrayType {
@Override
public String toString() {
return Amf3Tools.amfToString(this);
return Amf3Exporter.amfToString(this);
}
}
@@ -1,6 +1,6 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Tools;
import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter;
import java.util.Date;
public class DateType {
@@ -21,7 +21,7 @@ public class DateType {
@Override
public String toString() {
return Amf3Tools.amfToString(this);
return Amf3Exporter.amfToString(this);
}
public Date toDate() {
@@ -1,6 +1,6 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Tools;
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;
@@ -32,7 +32,7 @@ public class DictionaryType implements WithSubValues {
@Override
public String toString() {
return Amf3Tools.amfToString(this);
return Amf3Exporter.amfToString(this);
}
public boolean hasWeakKeys() {
@@ -1,6 +1,6 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Tools;
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;
@@ -81,7 +81,7 @@ public class ObjectType implements WithSubValues {
@Override
public String toString() {
return Amf3Tools.amfToString(this);
return Amf3Exporter.amfToString(this);
}
public void setSerializedData(byte[] serializedData) {
@@ -1,5 +1,7 @@
package com.jpexs.decompiler.flash.amf.amf3;
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;
@@ -9,6 +11,7 @@ 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;
@@ -18,20 +21,20 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Amf3Tools {
public class Amf3Exporter {
/**
* Converts AMF value to something human-readable.
*
* @param obj
* @param amfValue
* @return
*/
public static String amfToString(Object obj) {
public static String amfToString(Object amfValue) {
Map<Object, Integer> refCount = new HashMap<>();
List<Object> objectList = new ArrayList<>();
Map<Object, String> objectAlias = new HashMap<>();
populateObjects(obj, refCount, objectList, objectAlias);
return amfToString(new ArrayList<>(), 0, obj, refCount, objectAlias);
populateObjects(amfValue, refCount, objectList, objectAlias);
return amfToString(new ArrayList<>(), 0, amfValue, refCount, objectAlias);
}
/**
@@ -89,7 +92,7 @@ public class Amf3Tools {
return "\"" + Helper.escapeActionScriptString((String) object) + "\"";
}
if (((List<? extends Class>) Arrays.asList(Long.class, Double.class, Boolean.class)).contains(object.getClass())) {
return object.toString();
return EcmaScript.toString(object);
}
if (object instanceof BasicType) {
@@ -105,7 +108,7 @@ public class Amf3Tools {
}
processedObjects.add(object);
String addId = refCount > 1 ? indent(level + 1) + "\"id\": \"" + objectAlias.get(object) + "\"\r\n" : "";
String addId = refCount > 1 ? indent(level + 1) + "\"id\": \"" + objectAlias.get(object) + "\",\r\n" : "";
if (object instanceof AbstractVectorType) {
AbstractVectorType avt = (AbstractVectorType) object;
@@ -134,7 +137,7 @@ public class Amf3Tools {
if (serData == null) {
ret.append(indent(level + 1)).append("\"serialized\": unknown\r\n");
} else {
ret.append(indent(level + 1)).append("\"serialized\": 0x").append(javax.xml.bind.DatatypeConverter.printHexBinary(serData)).append(",\r\n");
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++) {
@@ -152,41 +155,44 @@ public class Amf3Tools {
}
} 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");
}
//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(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");
}
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)).append(":").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 + 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++) {
@@ -196,7 +202,7 @@ public class Amf3Tools {
ret.append(amfToString(processedObjects, level + 2, at.getDenseValues().get(i), referenceCount, objectAlias));
}
ret.append("],\r\n");
ret.append(indent(level + 1)).append("\"associativeValues\": [");
ret.append(indent(level + 1)).append("\"associativeValues\": {");
if (!at.getAssociativeValues().isEmpty()) {
ret.append("\r\n");
}
@@ -211,15 +217,15 @@ public class Amf3Tools {
if (!at.getAssociativeValues().isEmpty()) {
ret.append(indent(level + 1));
}
ret.append("]\r\n");
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\": " + dt.hasWeakKeys() + ",\r\n");
ret.append(indent(level + 1)).append("\"value\": {\r\n");
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));
@@ -229,14 +235,14 @@ public class Amf3Tools {
ret.append("\r\n");
}
ret.append(indent(level + 1)).append("}\r\n");
ret.append(indent(level) + "}");
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\": 0x" + javax.xml.bind.DatatypeConverter.printHexBinary(data) + "\r\n"
+ indent(level + 1) + "\"value\": \"" + javax.xml.bind.DatatypeConverter.printHexBinary(data) + "\"\r\n"
+ indent(level) + "}";
} else if (object instanceof DateType) {
DateType dt = (DateType) object;
@@ -249,13 +255,13 @@ public class Amf3Tools {
} else if (object instanceof XmlDocType) {
return "{\r\n"
+ indent(level + 1) + "\"type\": \"XML\",\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\": \"XMLDocument\",\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) + "}";
@@ -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;
}
}
@@ -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
}