AMF0 EcmaArray has dense and associative parts

This commit is contained in:
Jindra Petřík
2024-11-10 13:38:52 +01:00
parent 8d5b1a7292
commit 58c9d2f2cc
13 changed files with 273 additions and 143 deletions

View File

@@ -30,6 +30,7 @@ import com.jpexs.decompiler.flash.amf.amf3.Amf3InputStream;
import com.jpexs.decompiler.flash.amf.amf3.NoSerializerExistsException;
import com.jpexs.decompiler.flash.dumpview.DumpInfo;
import com.jpexs.decompiler.flash.ecma.EcmaScript;
import com.jpexs.decompiler.flash.exporters.amf.amf0.Amf0Exporter;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.MemoryInputStream;
import java.io.DataInputStream;
@@ -37,6 +38,7 @@ import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -322,93 +324,118 @@ public class Amf0InputStream extends InputStream {
*/
public Object readValue(String name) throws IOException, NoSerializerExistsException {
newDumpLevel(name, "value-type");
try {
int marker = readInternal();
switch (marker) {
case Marker.NUMBER:
return readDouble("DOUBLE");
case Marker.BOOLEAN:
return readU8("U8") > 0;
case Marker.STRING:
return readUtf8("UTF-8");
case Marker.OBJECT_END:
return BasicType.OBJECT_END;
case Marker.OBJECT:
ObjectType object = new ObjectType();
String propName;
Object val;
Object result = null;
int marker = readInternal();
System.err.println("marker " + Integer.toHexString(marker));
switch (marker) {
case Marker.NUMBER:
result = readDouble("DOUBLE");
break;
case Marker.BOOLEAN:
result = readU8("U8") > 0;
break;
case Marker.STRING:
result = readUtf8("UTF-8");
break;
case Marker.OBJECT_END:
result = BasicType.OBJECT_END;
break;
case Marker.OBJECT:
ObjectType object = new ObjectType();
String propName;
Object val;
while (true) {
propName = readUtf8("propertyName");
val = readValue("propertyValue");
if (propName.equals("")) {
break;
}
object.properties.put(propName, val);
}
return object;
case Marker.MOVIECLIP:
throw new IllegalArgumentException("MovieClip not supported in AMF0");
case Marker.NULL:
return BasicType.NULL;
case Marker.UNDEFINED:
return BasicType.UNDEFINED;
case Marker.REFERENCE:
return new ReferenceType(readU16("referenceIndex"));
case Marker.ECMA_ARRAY:
int associativeCount = (int) readU32("associative-count");
EcmaArrayType ea = new EcmaArrayType();
for (int a = 0; a < associativeCount; a++) {
String eaKey = readUtf8("key");
Object eaVal = readValue("value");
ea.values.put(eaKey, eaVal);
while (true) {
propName = readUtf8("propertyName");
val = readValue("propertyValue");
if (propName.equals("")) {
break;
}
readUtf8("UTF-8-empty");
readValue("object-end");
return ea;
case Marker.STRICT_ARRAY:
int arrayCount = (int) readU32("array-count");
ArrayType at = new ArrayType();
for (int a = 0; a < arrayCount; a++) {
at.values.add(readValue("value"));
object.properties.put(propName, val);
}
result = object;
break;
case Marker.MOVIECLIP:
throw new IllegalArgumentException("MovieClip not supported in AMF0");
case Marker.NULL:
result = BasicType.NULL;
break;
case Marker.UNDEFINED:
result = BasicType.UNDEFINED;
break;
case Marker.REFERENCE:
result = new ReferenceType(readU16("referenceIndex"));
break;
case Marker.ECMA_ARRAY:
int associativeCount = (int) readU32("associative-count");
System.err.println("associativeCount = " + associativeCount);
EcmaArrayType ea = new EcmaArrayType();
for (int a = 0; a < associativeCount; a++) {
String eaKey = readUtf8("key");
Object eaVal = readValue("value");
ea.denseValues.put(eaKey, eaVal);
}
while (true) {
String eaKey = readUtf8("key");
Object eaVal = readValue("value");
if ("".equals(eaKey)) {
break;
}
return at;
case Marker.DATE:
double dval = readDouble("epoch-millis");
int timezone = readS16("time-zone");
return new DateType(dval, timezone);
case Marker.LONG_STRING:
return readUtf8Long("long-string");
case Marker.UNSUPPORTED:
throw new IllegalArgumentException("Unsupported type");
case Marker.RECORDSET:
throw new IllegalArgumentException("RecordSet not supported in AMF0");
case Marker.XML_DOCUMENT:
return new XmlDocumentType(readUtf8Long("xml"));
case Marker.TYPED_OBJECT:
String className = readUtf8("class-name");
TypedObjectType typedObject = new TypedObjectType();
typedObject.className = className;
ea.associativeValues.put(eaKey, eaVal);
}
while (true) {
propName = readUtf8("propertyName");
val = readValue("propertyValue");
if (propName.equals("")) {
break;
}
typedObject.properties.put(propName, val);
result = ea;
break;
case Marker.STRICT_ARRAY:
int arrayCount = (int) readU32("array-count");
ArrayType at = new ArrayType();
for (int a = 0; a < arrayCount; a++) {
at.values.add(readValue("value"));
}
result = at;
break;
case Marker.DATE:
double dval = readDouble("epoch-millis");
int timezone = readS16("time-zone");
result = new DateType(dval, timezone);
break;
case Marker.LONG_STRING:
result = readUtf8Long("long-string");
break;
case Marker.UNSUPPORTED:
throw new IllegalArgumentException("Unsupported type");
case Marker.RECORDSET:
throw new IllegalArgumentException("RecordSet not supported in AMF0");
case Marker.XML_DOCUMENT:
return new XmlDocumentType(readUtf8Long("xml"));
case Marker.TYPED_OBJECT:
String className = readUtf8("class-name");
TypedObjectType typedObject = new TypedObjectType();
typedObject.className = className;
while (true) {
propName = readUtf8("propertyName");
val = readValue("propertyValue");
if (propName.equals("")) {
break;
}
return typedObject;
case Marker.AVMPLUS_OBJECT:
Amf3InputStream amf3 = new Amf3InputStream(is);
return amf3.readValue("avm-plus-object");
default:
throw new IllegalArgumentException("Unsupported type");
}
} finally {
endDumpLevel();
typedObject.properties.put(propName, val);
}
result = typedObject;
break;
case Marker.AVMPLUS_OBJECT:
Amf3InputStream amf3 = new Amf3InputStream(is);
result = amf3.readValue("avm-plus-object");
break;
default:
throw new IllegalArgumentException("Unsupported type");
}
if (result != null) {
System.err.println("Read: " + Amf0Exporter.amfToString(result, 0, "\r\n", new ArrayList<>(), new HashMap<>(), new HashMap<>()));
}
endDumpLevel();
return result;
}
public void resolveMapReferences(Map<String, Object> map) {
@@ -439,8 +466,11 @@ public class Amf0InputStream extends InputStream {
}
if (value instanceof EcmaArrayType) {
EcmaArrayType eat = (EcmaArrayType) value;
for (String key : eat.values.keySet()) {
eat.values.put(key, resolveReferences(eat.values.get(key), complexObjects));
for (String key : eat.denseValues.keySet()) {
eat.denseValues.put(key, resolveReferences(eat.denseValues.get(key), complexObjects));
}
for (String key : eat.associativeValues.keySet()) {
eat.associativeValues.put(key, resolveReferences(eat.associativeValues.get(key), complexObjects));
}
}
if (value instanceof ArrayType) {

View File

@@ -209,9 +209,12 @@ public class Amf0OutputStream extends OutputStream {
} else if (value instanceof EcmaArrayType) {
write(Marker.ECMA_ARRAY);
EcmaArrayType ea = (EcmaArrayType) value;
writeU32(ea.values.size());
for (String key : ea.values.keySet()) {
writeObjectProperty(key, ea.values.get(key), complexObjectsList);
writeU32(ea.denseValues.size());
for (String key : ea.denseValues.keySet()) {
writeObjectProperty(key, ea.denseValues.get(key), complexObjectsList);
}
for (String key : ea.associativeValues.keySet()) {
writeObjectProperty(key, ea.associativeValues.get(key), complexObjectsList);
}
writeUtf8Empty();
write(Marker.OBJECT_END);

View File

@@ -26,7 +26,8 @@ import java.util.Map;
* @author JPEXS
*/
public class EcmaArrayType implements ComplexObject {
public Map<String, Object> values = new LinkedHashMap<>();
public Map<String, Object> denseValues = new LinkedHashMap<>();
public Map<String, Object> associativeValues = new LinkedHashMap<>();
@Override
public String toString() {
@@ -35,6 +36,9 @@ public class EcmaArrayType implements ComplexObject {
@Override
public List<Object> getSubValues() {
return new ArrayList<>(values.values());
List<Object> result = new ArrayList<>();
result.addAll(denseValues.values());
result.addAll(associativeValues.values());
return result;
}
}

View File

@@ -129,7 +129,8 @@ public class Amf0Exporter {
sb.append("{").append(newLine);
sb.append(indent(level + 1)).append("\"type\": \"Object\",").append(newLine);
sb.append(addId);
membersToString(sb, ot.properties, level + 1, newLine, processedObjects, referenceCount, objectAlias);
membersToString("members", sb, ot.properties, level + 1, newLine, processedObjects, referenceCount, objectAlias);
sb.append(newLine);
sb.append(indent(level)).append("}");
return sb.toString();
}
@@ -139,7 +140,10 @@ public class Amf0Exporter {
sb.append("{").append(newLine);
sb.append(indent(level + 1)).append("\"type\": \"EcmaArray\",").append(newLine);
sb.append(addId);
membersToString(sb, eat.values, level + 1, newLine, processedObjects, referenceCount, objectAlias);
membersToString("denseValues", sb, eat.denseValues, level + 1, newLine, processedObjects, referenceCount, objectAlias);
sb.append(",").append(newLine);
membersToString("associativeValues", sb, eat.associativeValues, level + 1, newLine, processedObjects, referenceCount, objectAlias);
sb.append(newLine);
sb.append(indent(level)).append("}");
return sb.toString();
}
@@ -172,7 +176,8 @@ public class Amf0Exporter {
sb.append(indent(level + 1)).append("\"type\": \"TypedObject\",").append(newLine);
sb.append(addId);
sb.append(indent(level + 1)).append("\"className\": \"").append(Helper.escapeActionScriptString(tot.className)).append("\",").append(newLine);
membersToString(sb, tot.properties, level + 1, newLine, processedObjects, referenceCount, objectAlias);
membersToString("members", sb, tot.properties, level + 1, newLine, processedObjects, referenceCount, objectAlias);
sb.append(newLine);
sb.append(indent(level)).append("}");
return sb.toString();
}
@@ -209,6 +214,7 @@ public class Amf0Exporter {
}
private static void membersToString(
String membersLabel,
StringBuilder sb,
Map<String, Object> members,
int level,
@@ -216,7 +222,7 @@ public class Amf0Exporter {
List<Object> processedObjects,
Map<Object, Integer> referenceCount,
Map<Object, String> objectAlias) {
sb.append(indent(level)).append("\"members\": {").append(newLine);
sb.append(indent(level)).append("\"").append(membersLabel).append("\": {").append(newLine);
boolean first = true;
for (String key : members.keySet()) {
if (!first) {
@@ -227,7 +233,7 @@ public class Amf0Exporter {
sb.append(amfToString(members.get(key), level + 1, newLine, processedObjects, referenceCount, objectAlias));
}
sb.append(newLine);
sb.append(indent(level)).append("}").append(newLine);
sb.append(indent(level)).append("}");
}

View File

@@ -395,7 +395,8 @@ public class Amf0Importer {
case "EcmaArray":
EcmaArrayType eat = new EcmaArrayType();
typedObject.resolve("members", objectTable, false);
eat.values = typedObject.getJsObject("members").getStringMapped();
eat.denseValues = typedObject.getJsObject("denseValues").getStringMapped();
eat.associativeValues = typedObject.getJsObject("associativeValues").getStringMapped();
resultObject = eat;
break;
case "Array":
@@ -510,8 +511,11 @@ public class Amf0Importer {
}
} else if (object instanceof EcmaArrayType) {
EcmaArrayType eat = (EcmaArrayType) object;
for (String key : eat.values.keySet()) {
eat.values.put(key, replaceReferences(eat.values.get(key), objectsTable));
for (String key : eat.denseValues.keySet()) {
eat.denseValues.put(key, replaceReferences(eat.denseValues.get(key), objectsTable));
}
for (String key : eat.associativeValues.keySet()) {
eat.associativeValues.put(key, replaceReferences(eat.associativeValues.get(key), objectsTable));
}
} else if (object instanceof ArrayType) {
ArrayType at = (ArrayType) object;

View File

@@ -81,7 +81,7 @@ public class LsoTag extends Tag {
while (ais.available() > 0) {
String varName = ais.readUtf8("varName");
try {
Object varValue = ais.readValue("varValue");
Object varValue = ais.readValue("varValue");
amfValues.put(varName, varValue);
} catch (NoSerializerExistsException ex) {
throw new IllegalArgumentException("Serializer for class " + ex.getClassName() + " not found");