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");

View File

@@ -1,12 +1,8 @@
package {
public class MyClass {
public dynamic class MyClass {
public var a:int = 1;
public var b:int = 2;
public function MyClass() {
// constructor code
}
}
}

View File

@@ -18,6 +18,8 @@ import flash.events.MouseEvent;
import flash.net.ObjectEncoding;
import flash.xml.XMLDocument;
import flash.net.registerClassAlias;
import flash.utils.ByteArray;
import flash.utils.Dictionary;
var s:String = "";
for (var i = 0; i < 70000; i++) {
@@ -41,6 +43,36 @@ reftest["c"] = reftest;
registerClassAlias("MyClassAlias", MyClass);
var arr = ["a","b","c"];
arr["akey"] = "hello";
var cls = new MyClass();
cls["c"] = "dynamicValue"
var ba = new ByteArray();
ba.writeByte(0x12);
ba.writeByte(0x34);
ba.writeByte(0xAB);
var vi:Vector.<int> = new Vector.<int>();
vi.push(10);
vi.push(20);
vi.push(30);
var di:Dictionary = new Dictionary(false);
var key1:Object = { id: 1 };
var key2:Object = { id: 2 };
di[key1] = "First";
di[key2] = "Second";
var diso:SharedObject = SharedObject.getLocal("dict");
diso.objectEncoding = ObjectEncoding.AMF3;
diso.data.mydict = di;
diso.flush();
var data = {
mynumber : 1.5,
mybool : true,
@@ -48,14 +80,17 @@ var data = {
myobj : {a:1, b:2},
mynull : null,
myundefined : undefined,
myarray : ["a","b","c"],
myarray : arr,
mydate : dt,
mydate2 : dt,
myref: reftest,
mylongstring : s,
myxml : xm,
myxml2 : xm,
mytypedobject : new MyClass()
mytypedobject : cls,
mybytearray : ba,
myvectorint: vi,
mydictionary: di
};
amf0test.data.tref = reftest;
@@ -134,6 +169,17 @@ function fonLoad(event:MouseEvent):void {
</timelines>
<PrinterSettings/>
<publishHistory>
<PublishItem publishSize="21920" publishTime="1731241363"/>
<PublishItem publishSize="21920" publishTime="1731241267"/>
<PublishItem publishSize="21884" publishTime="1731240255"/>
<PublishItem publishSize="21875" publishTime="1731239891"/>
<PublishItem publishSize="20278" publishTime="1731239868"/>
<PublishItem publishSize="21783" publishTime="1731239610"/>
<PublishItem publishSize="21775" publishTime="1731239428"/>
<PublishItem publishSize="21695" publishTime="1731238381"/>
<PublishItem publishSize="21633" publishTime="1731228977"/>
<PublishItem publishSize="21633" publishTime="1731228925"/>
<PublishItem publishSize="21599" publishTime="1731228388"/>
<PublishItem publishSize="21570" publishTime="1731164207"/>
<PublishItem publishSize="21571" publishTime="1731164104"/>
<PublishItem publishSize="21715" publishTime="1731055062"/>
@@ -143,16 +189,5 @@ function fonLoad(event:MouseEvent):void {
<PublishItem publishSize="21507" publishTime="1730740224"/>
<PublishItem publishSize="21508" publishTime="1730740069"/>
<PublishItem publishSize="21505" publishTime="1730740015"/>
<PublishItem publishSize="21520" publishTime="1730739945"/>
<PublishItem publishSize="21510" publishTime="1730739873"/>
<PublishItem publishSize="21496" publishTime="1730739737"/>
<PublishItem publishSize="21491" publishTime="1730738502"/>
<PublishItem publishSize="21497" publishTime="1730738131"/>
<PublishItem publishSize="21516" publishTime="1730705495"/>
<PublishItem publishSize="21264" publishTime="1730677777"/>
<PublishItem publishSize="20259" publishTime="1730677769"/>
<PublishItem publishSize="15819" publishTime="1730676977"/>
<PublishItem publishSize="15799" publishTime="1730676777"/>
<PublishItem publishSize="20794" publishTime="1730676730"/>
</publishHistory>
</DOMDocument>

View File

@@ -5,8 +5,8 @@
xmlns:xmp="http://ns.adobe.com/xap/1.0/">
<xmp:CreatorTool>Adobe Flash Professional CS6 - build 481</xmp:CreatorTool>
<xmp:CreateDate>2024-11-03T09:46:34-08:00</xmp:CreateDate>
<xmp:MetadataDate>2024-11-09T06:55:03-08:00</xmp:MetadataDate>
<xmp:ModifyDate>2024-11-09T06:55:03-08:00</xmp:ModifyDate>
<xmp:MetadataDate>2024-11-10T00:46:15-08:00</xmp:MetadataDate>
<xmp:ModifyDate>2024-11-10T00:46:15-08:00</xmp:ModifyDate>
</rdf:Description>
<rdf:Description rdf:about=""
xmlns:dc="http://purl.org/dc/elements/1.1/">
@@ -15,7 +15,7 @@
<rdf:Description rdf:about=""
xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#">
<xmpMM:InstanceID>xmp.iid:10BB9B99AA9EEF11A208DFE3564218EE</xmpMM:InstanceID>
<xmpMM:InstanceID>xmp.iid:EAE3DCBBDD9EEF11A208DFE3564218EE</xmpMM:InstanceID>
<xmpMM:DocumentID>xmp.did:329919207399EF119BABD30F3587D305</xmpMM:DocumentID>
<xmpMM:OriginalDocumentID>xmp.did:329919207399EF119BABD30F3587D305</xmpMM:OriginalDocumentID>
<xmpMM:History>
@@ -32,6 +32,12 @@
<stEvt:when>2024-11-03T09:46:34-08:00</stEvt:when>
<stEvt:softwareAgent>Adobe Flash Professional CS6 - build 481</stEvt:softwareAgent>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<stEvt:action>created</stEvt:action>
<stEvt:instanceID>xmp.iid:EAE3DCBBDD9EEF11A208DFE3564218EE</stEvt:instanceID>
<stEvt:when>2024-11-03T09:46:34-08:00</stEvt:when>
<stEvt:softwareAgent>Adobe Flash Professional CS6 - build 481</stEvt:softwareAgent>
</rdf:li>
</rdf:Seq>
</xmpMM:History>
</rdf:Description>

View File

@@ -665,20 +665,46 @@ public class Main {
private static void deleteCookiesAfterRun(File tempFile) {
SharedObjectsStorage.removeChangedListener(tempFile, runCookieListener);
View.execInEventDispatchLater(new Runnable() {
public void run() {
File solDir = SharedObjectsStorage.getSolDirectoryForLocalFile(tempFile);
if (solDir == null) {
return;
File solDir = SharedObjectsStorage.getSolDirectoryForLocalFile(tempFile);
File origSolDir = runningSWF.getFile() == null ? null : SharedObjectsStorage.getSolDirectoryForLocalFile(new File(runningSWF.getFile()));
if (solDir != null) {
WatchKey foundKey = null;
for (WatchKey key : SharedObjectsStorage.watchedCookieDirectories.keySet()) {
if (SharedObjectsStorage.watchedCookieDirectories.get(key).equals(solDir)) {
foundKey = key;
break;
}
if (solDir.exists()) {
for (File f : solDir.listFiles()) {
f.delete();
}
solDir.delete();
}
}
if (foundKey != null) {
SharedObjectsStorage.watchedCookieDirectories.remove(foundKey);
}
});
View.execInEventDispatchLater(new Runnable() {
public void run() {
if (solDir.exists()) {
if (origSolDir != null && origSolDir.exists()) {
for (File f : origSolDir.listFiles()) {
f.delete();
}
for (File f : solDir.listFiles()) {
try {
Files.copy(f.toPath(), origSolDir.toPath().resolve(f.getName()), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
} catch (IOException ex) {
//ignored
}
}
}
for (File f : solDir.listFiles()) {
f.delete();
}
solDir.delete();
}
}
});
}
synchronized (Main.class) {
runCookieListener = null;
}
@@ -704,7 +730,7 @@ public class Main {
} catch (IOException ex) {
//ignored
}
}
}
}
runCookieListener = new CookiesChangedListener() {
@@ -727,7 +753,7 @@ public class Main {
} catch (IOException ex) {
//ignored
}
}
}
}
};
SharedObjectsStorage.addChangedListener(tempFile, runCookieListener);
@@ -1986,9 +2012,30 @@ public class Main {
if (fileName != null) {
Configuration.addRecentFile(fileName);
SharedObjectsStorage.addChangedListener(new File(fileName), new CookiesChangedListener() {
Timer timer;
@Override
public void cookiesChanged(File swfFile, List<File> cookies) {
getMainFrame().getPanel().refreshTree();
public void cookiesChanged(File swfFile, List<File> cookies) {
if (timer != null) {
return;
}
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
View.execInEventDispatchLater(new Runnable(){
@Override
public void run() {
getMainFrame().getPanel().refreshTree();
timer = null;
}
});
}
}, 500);
}
});
if (watcher != null && Configuration.checkForModifications.get()) {
@@ -2517,7 +2564,6 @@ public class Main {
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == StandardWatchEventKinds.OVERFLOW) {
System.err.println("overflow");
continue;
}
@@ -2530,10 +2576,10 @@ public class Main {
File dir = SharedObjectsStorage.watchedCookieDirectories.get(key);
java.nio.file.Path child = dir.toPath().resolve(filename);
File fullPath = child.toFile();
View.execInEventDispatchLater(new Runnable() {
@Override
public void run() {
public void run() {
SharedObjectsStorage.watchedDirectoryChanged(fullPath);
}
});

View File

@@ -42,6 +42,8 @@ import java.util.regex.Pattern;
*/
public class SharedObjectsStorage {
public static boolean watchingPaused = false;
public static Map<WatchKey, File> watchedCookieDirectories = new HashMap<>();
@@ -77,7 +79,6 @@ public class SharedObjectsStorage {
try {
WatchKey key = dir.toPath().register(Main.getWatcher(), StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
watchedCookieDirectories.put(key, dir);
//System.err.println("started monitoring " + dir.getAbsolutePath());
} catch (IOException ex) {
//ignored
//ex.printStackTrace();
@@ -348,11 +349,9 @@ public class SharedObjectsStorage {
}
public static void watchedDirectoryChanged(File file) {
//System.err.println("changed file: " + file.getAbsolutePath());
if (!file.exists()) {
//System.err.println("no exist");
//return;
}
if (watchingPaused) {
return;
}
List<File> swfFiles = new ArrayList<>(swfFileToListeners.keySet());
for (File swfFile : swfFiles) {
@@ -379,6 +378,7 @@ public class SharedObjectsStorage {
//System.err.println("- firing changed " + swfFile.getAbsolutePath());
List<CookiesChangedListener> listeners = swfFileToListeners.get(swfFile);
if (listeners != null) {
listeners = new ArrayList<>(listeners);
for (CookiesChangedListener l:listeners) {
l.cookiesChanged(swfFile, files);
}