Added: #2360 SOL file (Flash Local Shared Object - flash cookie) editor

This commit is contained in:
Jindra Petřík
2024-11-05 22:21:35 +01:00
parent 0b776dd06a
commit 3712c3be45
59 changed files with 3842 additions and 150 deletions

View File

@@ -0,0 +1,477 @@
/*
* Copyright (C) 2010-2024 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.amf.amf0;
import com.jpexs.decompiler.flash.EndOfStreamException;
import com.jpexs.decompiler.flash.amf.amf0.types.ArrayType;
import com.jpexs.decompiler.flash.amf.amf0.types.BasicType;
import com.jpexs.decompiler.flash.amf.amf0.types.ComplexObject;
import com.jpexs.decompiler.flash.amf.amf0.types.DateType;
import com.jpexs.decompiler.flash.amf.amf0.types.EcmaArrayType;
import com.jpexs.decompiler.flash.amf.amf0.types.ObjectType;
import com.jpexs.decompiler.flash.amf.amf0.types.ReferenceType;
import com.jpexs.decompiler.flash.amf.amf0.types.TypedObjectType;
import com.jpexs.decompiler.flash.amf.amf0.types.XmlDocumentType;
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.helpers.Helper;
import com.jpexs.helpers.MemoryInputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* InputStream for AMF0 data.
*
* @author JPEXS
*/
public class Amf0InputStream extends InputStream {
private final MemoryInputStream is;
/**
* Dump info
*/
public DumpInfo dumpInfo;
public Amf0InputStream(MemoryInputStream is) {
this.is = is;
}
private int readInternal() throws IOException {
int ret = read();
if (ret == -1) {
throw new EndOfStreamException();
}
return ret;
}
public byte[] readBytes(int count) throws IOException {
DataInputStream dais = new DataInputStream(is);
byte[] ret = new byte[count];
try {
dais.readFully(ret);
} catch (EOFException e) {
throw new EndOfStreamException();
}
return ret;
}
@Override
public int read() throws IOException {
return is.read();
}
@Override
public int available() throws IOException {
return is.available();
}
/**
* New dump level.
*
* @param name Name
* @param type Type
* @return Dump info
*/
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;
}
/**
* Ends dump level
*/
public void endDumpLevel() {
endDumpLevel(null);
}
/**
* Ends dump level
*
* @param value Value
*/
public void endDumpLevel(Object value) {
if (dumpInfo != null) {
dumpInfo.lengthBytes = is.getPos() - dumpInfo.startByte;
dumpInfo.previewValue = value;
dumpInfo = dumpInfo.parent;
}
}
/**
* Ends dump level until
*
* @param di Dump info
*/
public void endDumpLevelUntil(DumpInfo di) {
if (di != null) {
while (dumpInfo != null && dumpInfo != di) {
endDumpLevel();
}
}
}
/**
* Reads U8 (unsigned 8-bit integer) value.
*
* @param name Name
* @return U8 value
* @throws IOException On I/O error
*/
public int readU8(String name) throws IOException {
newDumpLevel(name, "U8");
int ret = readInternal();
endDumpLevel(ret);
return ret;
}
/**
* Reads U16 (unsigned 16-bit integer) value.
*
* @param name Name
* @return U16 value
* @throws IOException On I/O error
*/
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;
}
/**
* Reads S16 (signed 16-bit integer) value.
*
* @param name Name
* @return S16 value
* @throws IOException On I/O error
*/
public int readS16(String name) throws IOException {
newDumpLevel(name, "S16");
int b1 = readInternal();
int b2 = readInternal();
int ret = (b1 << 8) + b2;
ret = (int) signExtend(ret, 16);
endDumpLevel(ret);
return ret;
}
/**
* Reads U32 (unsigned 32-bit integer) value.
*
* @param name Name
* @return U32 value
* @throws IOException On I/O error
*/
public long readU32(String name) throws IOException {
newDumpLevel(name, "U32");
long ret = readU32Internal();
endDumpLevel(ret);
return ret;
}
/**
* Reads U32 (unsigned 32-bit integer) value.
*
* @return U32 value
* @throws IOException On I/O error
*/
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;
}
/**
* Reads S32 (signed 32-bit integer) value.
*
* @param name Name
* @return S32 value
* @throws IOException On I/O error
*/
public long readS32(String name) throws IOException {
newDumpLevel(name, "S32");
long ret = signExtend(readU32Internal(), 32);
endDumpLevel(ret);
return ret;
}
/**
* Reads long value.
*
* @return Long value
* @throws IOException On I/O error
*/
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)));
}
/**
* Reads double value.
*
* @param name Name
* @return Double value
* @throws IOException On I/O error
*/
public double readDouble(String name) throws IOException {
newDumpLevel(name, "DOUBLE");
long lval = readLong();
double ret = Double.longBitsToDouble(lval);
endDumpLevel(EcmaScript.toString(ret));
return ret;
}
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;
}
/**
* Reads UTF-8 value
* @param name Name
* @return UTF-8 value
* @throws IOException On I/O error
*/
public String readUtf8(String name) throws IOException {
newDumpLevel(name, "UTF-8");
int len = readU16("length");
byte[] data = len == 0 ? null : readBytes(len);
String retString = data == null ? "" : new String(data, "UTF-8");
endDumpLevel("\"" + Helper.escapeActionScriptString(retString) + "\"");
return retString;
}
/**
* Reads UTF-8-long value
* @param name Name
* @return UTF-8-long value
* @throws IOException On I/O error
*/
public String readUtf8Long(String name) throws IOException {
newDumpLevel(name, "UTF-8-long");
int len = (int) readU32("length"); //TODO: handle lengths that not fit int
byte[] data = len == 0 ? null : readBytes(len);
String retString = data == null ? "" : new String(data, "UTF-8");
endDumpLevel();
return retString;
}
public Object readValueWithReferences(String name) throws IOException, NoSerializerExistsException {
Object value = readValue(name);
List<Object> complexObjects = new ArrayList<>();
populateComplexObjects(value, complexObjects);
return resolveReferences(value, complexObjects);
}
/**
* Reads AMF0 value
* @param name Name
* @return AMF0 value
* @throws IOException On I/O error
* @throws NoSerializerExistsException When reading is switched to AMF3 and no serializer found for an object
*/
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;
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);
}
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"));
}
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;
while (true) {
propName = readUtf8("propertyName");
val = readValue("propertyValue");
if (propName.equals("")) {
break;
}
typedObject.properties.put(propName, val);
}
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();
}
}
public void resolveMapReferences(Map<String, Object> map) {
List<Object> complexObjects = new ArrayList<>();
populateComplexObjects((List<Object>)new ArrayList<>(map.values()), complexObjects);
for (String key : map.keySet()) {
map.put(key, resolveReferences(map.get(key), complexObjects));
}
}
public Object resolveReferences(Object value, List<Object> complexObjects) {
if (value instanceof ReferenceType) {
ReferenceType rt = (ReferenceType) value;
return complexObjects.get(rt.referenceIndex);
}
if (value instanceof ObjectType) {
ObjectType ot = (ObjectType) value;
for (String key : ot.properties.keySet()) {
ot.properties.put(key, resolveReferences(ot.properties.get(key), complexObjects));
}
}
if (value instanceof TypedObjectType) {
TypedObjectType tot = (TypedObjectType) value;
for (String key : tot.properties.keySet()) {
tot.properties.put(key, resolveReferences(tot.properties.get(key), complexObjects));
}
}
if (value instanceof EcmaArrayType) {
EcmaArrayType eat = (EcmaArrayType) value;
for (String key : eat.values.keySet()) {
eat.values.put(key, resolveReferences(eat.values.get(key), complexObjects));
}
}
if (value instanceof ArrayType) {
ArrayType at = (ArrayType) value;
for (int i = 0; i < at.values.size(); i++) {
at.values.set(i, resolveReferences(at.values.get(i), complexObjects));
}
}
return value;
}
public void populateComplexObjects(List<Object> values, List<Object> result) {
for (Object value : values) {
populateComplexObjects(value, result);
}
}
public void populateComplexObjects(Object value, List<Object> result) {
if (result.contains(value)) {
return;
}
if (value instanceof ComplexObject) {
result.add(value);
for (Object subvalue : ((ComplexObject) value).getSubValues()) {
populateComplexObjects(subvalue, result);
}
}
}
@Override
public long skip(long n) throws IOException {
return is.skip(n);
}
}

View File

@@ -0,0 +1,252 @@
/*
* Copyright (C) 2010-2024 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.amf.amf0;
import com.jpexs.decompiler.flash.amf.amf0.types.ArrayType;
import com.jpexs.decompiler.flash.amf.amf0.types.BasicType;
import com.jpexs.decompiler.flash.amf.amf0.types.DateType;
import com.jpexs.decompiler.flash.amf.amf0.types.EcmaArrayType;
import com.jpexs.decompiler.flash.amf.amf0.types.ObjectType;
import com.jpexs.decompiler.flash.amf.amf0.types.TypedObjectType;
import com.jpexs.decompiler.flash.amf.amf0.types.XmlDocumentType;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
/**
*
* @author JPEXS
*/
public class Amf0OutputStream extends OutputStream {
private final OutputStream os;
/**
* Constructor.
*
* @param os Output stream
*/
public Amf0OutputStream(OutputStream os) {
this.os = os;
}
/**
* Writes U8 (unsigned 8-bit integer).
* @param v Value
* @throws IOException On I/O error
*/
public void writeU8(int v) throws IOException {
write(v);
}
/**
* Writes U16 (unsigned 16-bit integer).
* @param v Value
* @throws IOException On I/O error
*/
public void writeU16(int v) throws IOException {
int b1 = (v >> 8) & 0xff;
int b2 = v & 0xff;
write(b1);
write(b2);
}
/**
* Writes S16 (signed 16-bit integer).
* @param v Value
* @throws IOException On I/O error
*/
public void writeS16(int v) throws IOException {
int b1 = (v >> 8) & 0xff;
int b2 = v & 0xff;
write(b1);
write(b2);
}
/**
* Writes U32 (unsigned 32-bit integer).
* @param v Value
* @throws IOException On I/O error
*/
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);
}
/**
* Writes double.
* @param v Value
* @throws IOException On I/O error
*/
public void writeDouble(double v) throws IOException {
writeLong(Double.doubleToLongBits(v));
}
/**
* Writes long.
* @param value Value
* @throws IOException On I/O error
*/
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);
}
/**
* Writes bytes.
* @param data Data
* @throws IOException On I/O error
*/
public void writeBytes(byte[] data) throws IOException {
os.write(data);
}
@Override
public void write(int v) throws IOException {
os.write(v);
}
@Override
public void write(byte[] b) throws IOException {
os.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
os.write(b, off, len);
}
public void writeUtf8(String value) throws IOException {
byte[] data = value.getBytes("UTF-8");
writeU16(data.length);
writeBytes(data);
}
public void writeUtf8Long(String value) throws IOException {
byte[] data = value.getBytes("UTF-8");
writeU32(data.length);
writeBytes(data);
}
public void writeObjectProperty(String name, Object value, List<Object> complexObjectsList) throws IOException {
writeUtf8(name);
writeValue(value, complexObjectsList);
}
public void writeUtf8Empty() throws IOException {
writeU16(0);
}
public void writeValue(Object value, List<Object> complexObjectsList) throws IOException {
if ((value instanceof ObjectType)
|| (value instanceof TypedObjectType)
|| (value instanceof ArrayType)
|| (value instanceof EcmaArrayType)
) {
int index = complexObjectsList.indexOf(value);
if (index != -1 && index <= 65535 ) {
write(Marker.REFERENCE);
writeU16(index);
return;
} else {
complexObjectsList.add(value);
}
}
if (value instanceof Double) {
write(Marker.NUMBER);
writeDouble((Double) value);
} else if (value instanceof Boolean) {
write(Marker.BOOLEAN);
write(((Boolean) value) ? 1 : 0);
} else if (value instanceof String) {
String sval = (String) value;
if (sval.length() > 65535) {
write(Marker.LONG_STRING);
writeUtf8Long(sval);
} else {
write(Marker.STRING);
writeUtf8(sval);
}
} else if (value instanceof ObjectType) {
write(Marker.OBJECT);
ObjectType ot = (ObjectType) value;
for (String key : ot.properties.keySet()) {
writeObjectProperty(key, ot.properties.get(key), complexObjectsList);
}
writeUtf8Empty();
write(Marker.OBJECT_END);
} else if (value == BasicType.NULL) {
write(Marker.NULL);
} else if (value == BasicType.UNDEFINED) {
write(Marker.UNDEFINED);
} 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);
}
writeUtf8Empty();
write(Marker.OBJECT_END);
} else if (value instanceof ArrayType) {
write(Marker.STRICT_ARRAY);
ArrayType at = (ArrayType) value;
writeU32(at.values.size());
for (Object v : at.values) {
writeValue(v, complexObjectsList);
}
} else if (value instanceof DateType) {
write(Marker.DATE);
DateType dt = (DateType) value;
writeDouble(dt.getVal());
writeS16(dt.getTimezone());
} else if (value instanceof XmlDocumentType) {
write(Marker.XML_DOCUMENT);
XmlDocumentType xmlDoc = (XmlDocumentType) value;
writeUtf8Long(xmlDoc.getData());
} else if (value instanceof TypedObjectType) {
write(Marker.TYPED_OBJECT);
TypedObjectType tot = (TypedObjectType) value;
writeUtf8(tot.className);
for (String key : tot.properties.keySet()) {
writeObjectProperty(key, tot.properties.get(key), complexObjectsList);
}
writeUtf8Empty();
write(Marker.OBJECT_END);
} else {
throw new IllegalArgumentException("Unsupported value type for serialization");
}
//TODO: Switching to AMF3 when necessary
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright (C) 2010-2024 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.amf.amf0;
/**
* AMF0 marker.
* @author JPEXS
*/
public class Marker {
/**
* Number
*/
public static final int NUMBER = 0x00;
/**
* Boolean
*/
public static final int BOOLEAN = 0x01;
/**
* String
*/
public static final int STRING = 0x02;
/**
* Object
*/
public static final int OBJECT = 0x03;
/**
* MovieClip - Reserved, not supported
*/
public static final int MOVIECLIP = 0x04;
/**
* Null
*/
public static final int NULL = 0x05;
/**
* Undefined
*/
public static final int UNDEFINED = 0x06;
/**
* Reference
*/
public static final int REFERENCE = 0x07;
/**
* Ecma array
*/
public static final int ECMA_ARRAY = 0x08;
/**
* Object end
*/
public static final int OBJECT_END = 0x09;
/**
* Strict array
*/
public static final int STRICT_ARRAY = 0x0A;
/**
* Date
*/
public static final int DATE = 0x0B;
/**
* Long string
*/
public static final int LONG_STRING = 0x0C;
/**
* Unsupported
*/
public static final int UNSUPPORTED = 0x0D;
/**
* Record set - Reserved, not supported
*/
public static final int RECORDSET = 0x0E;
/**
* XML document
*/
public static final int XML_DOCUMENT = 0x0F;
/**
* Typed object
*/
public static final int TYPED_OBJECT = 0x10;
/**
* AvmPlus object
*/
public static final int AVMPLUS_OBJECT = 0x11;
}

View File

@@ -0,0 +1,4 @@
/**
* AMF0.
*/
package com.jpexs.decompiler.flash.amf.amf0;

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2010-2024 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.amf.amf0.types;
import java.util.ArrayList;
import java.util.List;
/**
* Array type
* @author JPEXS
*/
public class ArrayType implements ComplexObject {
public List<Object> values = new ArrayList<>();
@Override
public List<Object> getSubValues() {
return values;
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2010-2024 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.amf.amf0.types;
import com.jpexs.decompiler.flash.amf.amf3.types.*;
/**
* Basic AMF0 types.
*/
public enum BasicType implements Amf3ValueType {
/**
* Null
*/
NULL {
@Override
public String toString() {
return "null";
}
},
/**
* Undefined
*/
UNDEFINED {
@Override
public String toString() {
return "undefined";
}
},
/**
* Unknown - Special type for errors while reading
*/
UNKNOWN {
@Override
public String toString() {
return "unknown";
}
},
/**
* Object end
*/
OBJECT_END {
@Override
public String toString() {
return "object-end";
}
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2010-2024 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.amf.amf0.types;
import java.util.List;
/**
* Interface for AMF0 objects that have sub values
*/
public interface ComplexObject {
/**
* Gets sub values.
*
* @return List of sub values
*/
public List<Object> getSubValues();
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2010-2024 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.amf.amf0.types;
import com.jpexs.decompiler.flash.amf.amf3.types.*;
import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* AMF0 date type.
*/
public class DateType implements Amf3ValueType {
private int timezone;
private double val;
/**
* Constructor.
* @param val Date value
* @param timezone Time zone
*/
public DateType(double val, int timezone) {
this.val = val;
this.timezone = timezone;
}
/**
* Gets date value.
* @return Date value
*/
public double getVal() {
return val;
}
/**
* Sets date value.
* @param val Date value
*/
public void setVal(double val) {
this.val = val;
}
/**
* Converts this to date.
* @return Date
*/
public Date toDate() {
return new Date((long) val);
}
/**
* Gets timezone
* @return Timezone
*/
public int getTimezone() {
return timezone;
}
@Override
public String toString() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SS");
return sdf.format(toDate()) + " timezone " + timezone;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2010-2024 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.amf.amf0.types;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Ecma Array
* @author JPEXS
*/
public class EcmaArrayType implements ComplexObject {
public Map<String, Object> values = new LinkedHashMap<>();
@Override
public String toString() {
return "EcmaArrayType";
}
@Override
public List<Object> getSubValues() {
return new ArrayList<>(values.values());
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2010-2024 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.amf.amf0.types;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Object type.
* @author JPEXS
*/
public class ObjectType implements ComplexObject {
public Map<String, Object> properties = new LinkedHashMap<>();
@Override
public String toString() {
return "ObjectType";
}
@Override
public List<Object> getSubValues() {
return new ArrayList<>(properties.values());
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2010-2024 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.amf.amf0.types;
/**
*
* @author JPEXS
*/
public class ReferenceType {
public int referenceIndex;
public Object referencedObject;
public ReferenceType(int referenceIndex) {
this.referenceIndex = referenceIndex;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2010-2024 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.amf.amf0.types;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Typed object type.
* @author JPEXS
*/
public class TypedObjectType implements ComplexObject {
public String className;
public Map<String, Object> properties = new LinkedHashMap<>();
@Override
public String toString() {
return "TypedObjectType (" + className + ")";
}
@Override
public List<Object> getSubValues() {
return new ArrayList<>(properties.values());
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2010-2024 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.amf.amf0.types;
import com.jpexs.decompiler.flash.amf.amf3.types.*;
/**
* AMF0 XML document type.
*/
public class XmlDocumentType implements Amf3ValueType {
/**
* Data
*/
private String data;
/**
* Constructor.
* @param data Data
*/
public XmlDocumentType(String data) {
this.data = data;
}
/**
* Gets data.
* @return Data
*/
public String getData() {
return data;
}
/**
* Sets data.
* @param data Data
*/
public void setData(String data) {
this.data = data;
}
@Override
public String toString() {
return data;
}
}

View File

@@ -0,0 +1,4 @@
/**
* AMF0 types.
*/
package com.jpexs.decompiler.flash.amf.amf0.types;

View File

@@ -73,6 +73,11 @@ public class Amf3InputStream extends InputStream {
this.is = is;
}
@Override
public int available() throws IOException {
return is.available();
}
/**
* New dump level.
* @param name Name
@@ -424,7 +429,18 @@ public class Amf3InputStream extends InputStream {
return readValue(name, serializers, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
}
private Object readValue(String name, Map<String, ObjectTypeSerializeHandler> serializers,
/**
* Reads value
* @param name Name
* @param serializers Serializers
* @param objectTable Object table
* @param traitsTable Traits table
* @param stringTable String table
* @return Value
* @throws IOException On I/O error
* @throws NoSerializerExistsException If no serializer exists
*/
public Object readValue(String name, Map<String, ObjectTypeSerializeHandler> serializers,
List<Object> objectTable,
List<Traits> traitsTable,
List<String> stringTable

View File

@@ -182,7 +182,7 @@ public class Amf3OutputStream extends OutputStream {
* @param stringTable String table
* @throws IOException On I/O error
*/
private void writeUtf8Vr(String val, List<String> stringTable) throws IOException {
public void writeUtf8Vr(String val, List<String> stringTable) throws IOException {
int stringIndex = stringTable.indexOf(val);
if (stringIndex == -1) {
if (!val.isEmpty()) {
@@ -336,7 +336,17 @@ public class Amf3OutputStream extends OutputStream {
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 {
/**
* Writes value.
* @param object Object
* @param serializers Serializers
* @param stringTable String table
* @param traitsTable Traits table
* @param objectTable Object table
* @throws IOException On I/O error
* @throws NoSerializerExistsException If no serializer exists
*/
public 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) {

View File

@@ -20,20 +20,21 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Map which maintains order of keys.
* Map which maintains order of keys. Similar to LinkedHashMap.
*
* @param <K> Key type
* @param <V> Value type
*/
public class ListMap<K, V> implements Map<K, V> {
private final Set<K> orderedKeys = new ListSet<>();
private final Set<K> orderedKeys = new LinkedHashSet<>();
private final Map<K, V> map;
/**
@@ -129,7 +130,7 @@ public class ListMap<K, V> implements Map<K, V> {
@Override
public Set<K> keySet() {
return new ListSet<>(orderedKeys);
return new LinkedHashSet<>(orderedKeys);
}
@Override
@@ -143,7 +144,7 @@ public class ListMap<K, V> implements Map<K, V> {
@Override
public Set<Entry<K, V>> entrySet() {
Set<Entry<K, V>> ret = new ListSet<>();
Set<Entry<K, V>> ret = new LinkedHashSet<>();
for (K key : orderedKeys) {
V value = map.get(key);
ret.add(new MyEntry<>(key, value));
@@ -158,7 +159,7 @@ public class ListMap<K, V> implements Map<K, V> {
*/
public static class MyEntry<K, V> implements Entry<K, V> {
private K key;
private final K key;
private V value;
public MyEntry(K key, V value) {

View File

@@ -1,125 +0,0 @@
/*
* Copyright (C) 2010-2024 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.amf.amf3;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* Set which maintains orders elements by time they were added.
*
* @param <E> Type of element
*/
public class ListSet<E> implements Set<E> {
private final List<E> list = new ArrayList<>();
/**
* Constructor.
*/
public ListSet() {
}
/**
* Constructor.
*
* @param c Collection
*/
public ListSet(Collection<? extends E> c) {
addAll(c);
}
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public boolean contains(Object o) {
return list.contains(o);
}
@Override
public Iterator<E> iterator() {
return list.iterator();
}
@Override
public Object[] toArray() {
return list.toArray();
}
@Override
public <E> E[] toArray(E[] a) {
return (E[]) list.toArray(a);
}
@Override
public boolean add(E e) {
if (!contains(e)) {
list.add(e);
return true;
}
return false;
}
@Override
public boolean remove(Object o) {
return list.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return list.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c) {
if (add(e)) {
modified = true;
}
}
return modified;
}
@Override
public boolean retainAll(Collection<?> c) {
return list.retainAll(c);
}
@Override
public boolean removeAll(Collection<?> c) {
return list.removeAll(c);
}
@Override
public void clear() {
list.clear();
}
}

View File

@@ -17,6 +17,8 @@
package com.jpexs.decompiler.flash.amf.amf3;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
/**
@@ -37,7 +39,7 @@ public class Traits {
public Traits(String className, boolean dynamic, Collection<? extends String> sealedMemberNames) {
this.className = className;
this.dynamic = dynamic;
this.sealedMemberNames = new ListSet<>(sealedMemberNames);
this.sealedMemberNames = new LinkedHashSet<>(sealedMemberNames);
}
/**
@@ -61,7 +63,7 @@ public class Traits {
* @return Sealed member names
*/
public Set<String> getSealedMemberNames() {
return new ListSet<>(sealedMemberNames);
return new LinkedHashSet<>(sealedMemberNames);
}
/**
@@ -85,7 +87,36 @@ public class Traits {
* @param sealedMemberNames Sealed member names
*/
public void setSealedMemberNames(Collection<? extends String> sealedMemberNames) {
this.sealedMemberNames = new ListSet<>(sealedMemberNames);
this.sealedMemberNames = new LinkedHashSet<>(sealedMemberNames);
}
@Override
public int hashCode() {
int hash = 7;
hash = 11 * hash + Objects.hashCode(this.className);
hash = 11 * hash + (this.dynamic ? 1 : 0);
hash = 11 * hash + Objects.hashCode(this.sealedMemberNames);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Traits other = (Traits) obj;
if (this.dynamic != other.dynamic) {
return false;
}
if (!Objects.equals(this.className, other.className)) {
return false;
}
return Objects.equals(this.sealedMemberNames, other.sealedMemberNames);
}
}

View File

@@ -21,6 +21,7 @@ import com.jpexs.decompiler.flash.amf.amf3.WithSubValues;
import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -33,7 +34,7 @@ public class DictionaryType extends ListMap<Object, Object> implements WithSubVa
* True if keys are weak
*/
private final boolean weakKeys;
/**
* Constructor.
* @param weakKeys True if keys are weak

View File

@@ -17,13 +17,13 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.amf.amf3.ListMap;
import com.jpexs.decompiler.flash.amf.amf3.ListSet;
import com.jpexs.decompiler.flash.amf.amf3.Traits;
import com.jpexs.decompiler.flash.amf.amf3.WithSubValues;
import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -436,7 +436,7 @@ public class ObjectType implements WithSubValues, Amf3ValueType, Map<String, Obj
@Override
public Set<String> keySet() {
Set<String> ret = new ListSet<>();
Set<String> ret = new LinkedHashSet<>();
ret.addAll(sealedMembers.keySet());
ret.addAll(dynamicMembers.keySet());
ret.addAll(serializedMembers.keySet());
@@ -448,7 +448,7 @@ public class ObjectType implements WithSubValues, Amf3ValueType, Map<String, Obj
* @return The key set of the sealed members.
*/
public Set<String> sealedMembersKeySet() {
return new ListSet<>(sealedMembers.keySet());
return new LinkedHashSet<>(sealedMembers.keySet());
}
/**
@@ -456,7 +456,7 @@ public class ObjectType implements WithSubValues, Amf3ValueType, Map<String, Obj
* @return The key set of the dynamic members.
*/
public Set<String> dynamicMembersKeySet() {
return new ListSet<>(dynamicMembers.keySet());
return new LinkedHashSet<>(dynamicMembers.keySet());
}
/**
@@ -464,7 +464,7 @@ public class ObjectType implements WithSubValues, Amf3ValueType, Map<String, Obj
* @return The key set of the serialized members.
*/
public Set<String> serializedMembersKeySet() {
return new ListSet<>(serializedMembers.keySet());
return new LinkedHashSet<>(serializedMembers.keySet());
}
@Override
@@ -486,7 +486,7 @@ public class ObjectType implements WithSubValues, Amf3ValueType, Map<String, Obj
@Override
public Set<Entry<String, Object>> entrySet() {
Set<String> keys = keySet();
Set<Entry<String, Object>> ret = new ListSet<>();
Set<Entry<String, Object>> ret = new LinkedHashSet<>();
for (String key : keys) {
ret.add(new ListMap.MyEntry<>(key, get(key)));
}

View File

@@ -1045,6 +1045,10 @@ public final class Configuration {
@ConfigurationCategory("limit")
public static ConfigurationItem<Integer> maxScriptLineLength = null;
@ConfigurationDefaultString(".")
@ConfigurationDirectory
public static ConfigurationItem<String> lastSolEditorDirectory = null;
private enum OSId {
WINDOWS, OSX, UNIX
}

View File

@@ -0,0 +1,250 @@
/*
* Copyright (C) 2010-2024 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.exporters.amf.amf0;
import com.jpexs.decompiler.flash.amf.amf0.types.ArrayType;
import com.jpexs.decompiler.flash.amf.amf0.types.BasicType;
import com.jpexs.decompiler.flash.amf.amf0.types.DateType;
import com.jpexs.decompiler.flash.amf.amf0.types.EcmaArrayType;
import com.jpexs.decompiler.flash.amf.amf0.types.ObjectType;
import com.jpexs.decompiler.flash.amf.amf0.types.TypedObjectType;
import com.jpexs.decompiler.flash.amf.amf0.types.XmlDocumentType;
import com.jpexs.helpers.Helper;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.jpexs.decompiler.flash.amf.amf0.types.ComplexObject;
import com.jpexs.decompiler.flash.ecma.EcmaScript;
/**
* AMF0 exporter.
*
* @author JPEXS
*/
public class Amf0Exporter {
public static String amfMapToString(
Map<String, Object> map,
int level,
String newLine
) {
List<Object> processedObjects = new ArrayList<>();
Map<Object, Integer> referenceCount = new LinkedHashMap<>();
Map<Object, String> objectAlias = new LinkedHashMap<>();
List<Object> objectList = new ArrayList<>();
for (String key: map.keySet()) {
Object val = map.get(key);
populateObjects(val, referenceCount, objectList, objectAlias);
}
StringBuilder sb = new StringBuilder();
sb.append("{").append(newLine);
boolean first = true;
for (String key: map.keySet()) {
if (!first) {
sb.append(",").append(newLine);
}
first = false;
sb.append(indent(level + 1)).append("\"").append(Helper.escapeActionScriptString(key)).append("\": ");
sb.append(amfToString(map.get(key), level + 1, newLine, processedObjects, referenceCount, objectAlias));
}
sb.append(newLine);
sb.append(indent(level)).append("}");
return sb.toString();
}
public static String amfToString(
Object value,
int level,
String newLine,
List<Object> processedObjects,
Map<Object, Integer> referenceCount,
Map<Object, String> objectAlias
) {
String addId = "";
if (referenceCount.containsKey(value)) {
Integer refCount = referenceCount.get(value);
if (refCount > 1 && processedObjects.contains(value)) {
return "#" + objectAlias.get(value);
}
if (refCount > 1) {
addId = indent(level + 1) + "\"id\": \"" + objectAlias.get(value) + "\"," + newLine;
}
processedObjects.add(value);
}
if (value instanceof Double) {
return EcmaScript.toString(value);
}
if (value instanceof Boolean) {
return value.toString();
}
if (value instanceof String) {
return "\"" + Helper.escapeActionScriptString((String) value) + "\"";
}
if (value instanceof ObjectType) {
ObjectType ot = (ObjectType) value;
StringBuilder sb = new StringBuilder();
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);
sb.append(indent(level)).append("}");
return sb.toString();
}
if (value instanceof EcmaArrayType) {
EcmaArrayType eat = (EcmaArrayType) value;
StringBuilder sb = new StringBuilder();
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);
sb.append(indent(level)).append("}");
return sb.toString();
}
if (value instanceof ArrayType) {
ArrayType at = (ArrayType) value;
StringBuilder sb = new StringBuilder();
sb.append("{").append(newLine);
sb.append(indent(level + 1)).append("\"type\": \"Array\",").append(newLine);
sb.append(addId);
sb.append(indent(level + 1)).append("\"values\": [").append(newLine);
boolean first = true;
for (Object val : at.values) {
if (!first) {
sb.append(", ");
}
first = false;
sb.append(amfToString(val, level + 1, newLine, processedObjects, referenceCount, objectAlias));
}
sb.append(indent(level + 1)).append("]").append(newLine);
sb.append(indent(level)).append("}");
return sb.toString();
}
if (value instanceof TypedObjectType) {
TypedObjectType tot = (TypedObjectType) value;
StringBuilder sb = new StringBuilder();
sb.append("{").append(newLine);
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);
sb.append(indent(level)).append("}");
return sb.toString();
}
if (value instanceof BasicType) {
return value.toString();
}
if (value instanceof DateType) {
DateType dt = (DateType) value;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SS");
StringBuilder sb = new StringBuilder();
sb.append("{").append(newLine);
sb.append(indent(level + 1)).append("\"type\": \"Date\",").append(newLine);
sb.append(indent(level + 1)).append("\"value\": \"").append(sdf.format(dt.toDate())).append("\",").append(newLine);
sb.append(indent(level + 1)).append("\"timezone\": ").append(dt.getTimezone()).append(newLine);
sb.append(indent(level)).append("}");
return sb.toString();
}
if (value instanceof XmlDocumentType) {
XmlDocumentType xdt = (XmlDocumentType) value;
StringBuilder sb = new StringBuilder();
sb.append("{").append(newLine);
sb.append(indent(level + 1)).append("\"type\": \"XMLDocument\",").append(newLine);
sb.append(indent(level + 1)).append("\"data\": \"").append(Helper.escapeActionScriptString(xdt.getData())).append("\"").append(newLine);
sb.append(indent(level)).append("}");
return sb.toString();
}
return "unknown";
}
private static void membersToString(
StringBuilder sb,
Map<String, Object> members,
int level,
String newLine,
List<Object> processedObjects,
Map<Object, Integer> referenceCount,
Map<Object, String> objectAlias) {
sb.append(indent(level)).append("\"members\": {").append(newLine);
boolean first = true;
for (String key : members.keySet()) {
if (!first) {
sb.append(",").append(newLine);
}
first = false;
sb.append(indent(level + 1)).append("\"").append(Helper.escapeActionScriptString(key)).append("\": ");
sb.append(amfToString(members.get(key), level + 1, newLine, processedObjects, referenceCount, objectAlias));
}
sb.append(newLine);
sb.append(indent(level)).append("}").append(newLine);
}
private static String indent(int level) {
String na = "";
for (int i = 0; i < level; i++) {
na += " ";
}
return na;
}
/**
* 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, 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 ComplexObject) {
List<Object> subvalues = ((ComplexObject) object).getSubValues();
for (Object o : subvalues) {
populateObjects(o, referenceCount, objectList, objectAlias);
}
}
objectList.add(object);
objectAlias.put(object, "obj" + objectList.size());
}
}
}

View File

@@ -0,0 +1,4 @@
/**
* AMF0 export.
*/
package com.jpexs.decompiler.flash.exporters.amf.amf0;

View File

@@ -115,6 +115,30 @@ public class Amf3Exporter {
populateObjects(amfValue, refCount, objectList, objectAlias);
return amfToString(indentStr, newLine, new ArrayList<>(), 0, amfValue, refCount, objectAlias);
}
public static String amfMapToString(Map<String, Object> map, String indentStr, String newLine, int level) {
Map<Object, Integer> refCount = new HashMap<>();
List<Object> objectList = new ArrayList<>();
Map<Object, String> objectAlias = new HashMap<>();
for (Object val : map.values()) {
populateObjects(val, refCount, objectList, objectAlias);
}
List<Object> processedObjects = new ArrayList<>();
StringBuilder sb = new StringBuilder();
sb.append("{").append(newLine);
boolean first = true;
for (String key: map.keySet()) {
if (!first) {
sb.append(", ").append(newLine);
}
first = false;
sb.append(indent(level + 1)).append("\"").append(Helper.escapeActionScriptString(key)).append("\": ");
sb.append(amfToString(indentStr, newLine, processedObjects, level + 1, map.get(key), refCount, objectAlias));
}
sb.append(newLine);
sb.append(indent(level)).append("}");
return sb.toString();
}
/**
* Processes one level of object and converts it to string
@@ -226,7 +250,7 @@ public class Amf3Exporter {
for (String key : ot.dynamicMembersKeySet()) {
Object val = ot.getDynamicMember(key);
ret.append(indent(level + 2)).append(amfToString(indentStr, newLine, processedObjects, level + 2, key, referenceCount, objectAlias));
ret.append(":");
ret.append(": ");
ret.append(amfToString(indentStr, newLine, processedObjects, level + 2, val, referenceCount, objectAlias));
if (i < ot.dynamicMembersSize() - 1) {
ret.append(",");

View File

@@ -0,0 +1,583 @@
/*
* Copyright (C) 2010-2024 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.amf0;
import com.jpexs.decompiler.flash.amf.amf0.types.ArrayType;
import com.jpexs.decompiler.flash.amf.amf0.types.BasicType;
import com.jpexs.decompiler.flash.amf.amf0.types.DateType;
import com.jpexs.decompiler.flash.amf.amf0.types.EcmaArrayType;
import com.jpexs.decompiler.flash.amf.amf0.types.ObjectType;
import com.jpexs.decompiler.flash.amf.amf0.types.TypedObjectType;
import com.jpexs.decompiler.flash.amf.amf0.types.XmlDocumentType;
import com.jpexs.decompiler.flash.importers.amf.amf3.Amf3Lexer;
import com.jpexs.decompiler.flash.importers.amf.amf3.Amf3ParseException;
import com.jpexs.decompiler.flash.importers.amf.amf3.ParsedSymbol;
import com.jpexs.decompiler.flash.importers.amf.amf3.SymbolType;
import com.jpexs.helpers.Helper;
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.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* AMF0 importer.
*/
public class Amf0Importer {
private Amf3Lexer lexer;
/**
* Constructor.
*/
public Amf0Importer() {
}
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 (Object key : values.keySet()) {
sb.append(key).append(":").append("?").append(",\r\n");
}
sb.append("}");
return sb.toString();
}
private final Map<Object, Object> values = new LinkedHashMap<>();
public Object remove(Object key) {
return values.remove(key);
}
public Set<Object> keySet() {
return values.keySet();
}
public Object get(Object key) {
return values.get(key);
}
public void put(Object key, Object value) {
values.put(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 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 JsArray getJsArray(Object key) throws Amf3ParseException {
return (JsArray) getRequired(key, "JsArray");
}
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 Double getDouble(Object key) throws Amf3ParseException {
return (Double) getRequired(key, "Double");
}
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) {
return values.containsKey(key);
}
public void resolve(Object key, Map<String, Object> objectTable, boolean allowTypedObject) throws Amf3ParseException {
Object val = values.get(key);
Object resolved = resolveObjects(val, objectTable, allowTypedObject);
values.put(key, resolved);
}
public List<String> stringKeys() {
List<String> ret = new ArrayList<>();
for (Object key : values.keySet()) {
if (key instanceof String) {
ret.add((String) key);
}
}
return ret;
}
public Map<Object, Object> getAll() {
return values;
}
public Map<String, Object> getStringMapped() {
Map<String, Object> ret = new LinkedHashMap<>();
for (Object key : values.keySet()) {
if (key instanceof String) {
String keyStr = (String) key;
ret.put(keyStr, values.get(key));
}
}
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.put(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");
int timeZone = (int) (long) (double) typedObject.getDouble("timezone");
try {
resultObject = new DateType((double) sdf.parse(dateStr).getTime(), timeZone);
} catch (ParseException ex) {
throw new Amf3ParseException("Invalid date format: " + dateStr, lexer.yyline());
}
break;
case "XMLDocument":
resultObject = new XmlDocumentType(typedObject.getString("data"));
break;
case "Object":
ObjectType ot = new ObjectType();
typedObject.resolve("members", objectTable, false);
ot.properties = typedObject.getJsObject("members").getStringMapped();
resultObject = ot;
break;
case "TypedObject":
TypedObjectType tot = new TypedObjectType();
tot.className = typedObject.getString("className");
typedObject.resolve("members", objectTable, false);
tot.properties = typedObject.getJsObject("members").getStringMapped();
resultObject = tot;
break;
case "EcmaArray":
EcmaArrayType eat = new EcmaArrayType();
typedObject.resolve("members", objectTable, false);
eat.values = typedObject.getJsObject("members").getStringMapped();
resultObject = eat;
break;
case "Array":
ArrayType at = new ArrayType();
typedObject.resolve("values", objectTable, false);
at.values = typedObject.getJsArray("values").getValues();
resultObject = at;
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 (Object key : jsObject.keySet()) {
Object val = jsObject.get(key);
//Object resKey = resolveObjects(key, objectTable, true);
Object resVal = resolveObjects(val, objectTable, true);
//jsObject.remove(key);
jsObject.put(key, resVal);
}
resultObject = jsObject;
}
}
return resultObject;
}
private Map<String, Object> map(Map<String, Object> objectTable) throws IOException, Amf3ParseException {
Map<String, Object> result = new HashMap<>();
expectedType(SymbolType.CURLY_OPEN);
ParsedSymbol s;
do {
s = lex();
if (!s.isType(SymbolType.STRING)) {
break;
}
String key = (String) s.value;
expectedType(SymbolType.COLON);
result.put(key, value(objectTable));
s = lex();
} while (s.type == SymbolType.COMMA);
expected(s, lexer.yyline(), SymbolType.CURLY_CLOSE);
return result;
}
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:
return s.value;
case INTEGER:
return (double) (long) (Long) 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 Object
* @param objectsTable Objects table
* @return Replaced object
*/
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 (String key : ot.properties.keySet()) {
ot.properties.put(key, replaceReferences(ot.properties.get(key), objectsTable));
}
} else if (object instanceof TypedObjectType) {
TypedObjectType tot = (TypedObjectType) object;
for (String key : tot.properties.keySet()) {
tot.properties.put(key, replaceReferences(tot.properties.get(key), objectsTable));
}
} 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));
}
} else if (object instanceof ArrayType) {
ArrayType at = (ArrayType) object;
for (int i = 0; i < at.values.size(); i++) {
at.values.set(i, replaceReferences(at.values.get(i), 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;
}
}
/**
* Convert AMF3 string to object
*
* @param val AMF3 string
* @return Object
* @throws IOException On I/O error
* @throws Amf3ParseException On parse error
*/
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;
}
/**
* Convert AMF3 map string to object
*
* @param val AMF3 string
* @return Object
* @throws IOException On I/O error
* @throws Amf3ParseException On parse error
*/
public Map<String, Object> stringToAmfMap(String val) throws IOException, Amf3ParseException {
lexer = new Amf3Lexer(new StringReader(val));
Map<String, Object> objectsTable = new HashMap<>();
List<ReferencedObjectType> references = new ArrayList<>();
Map<String, Object> result = map(objectsTable);
for (String key : result.keySet()) {
Object resultResolved = resolveObjects(result.get(key), objectsTable, true);
result.put(key, resultResolved);
}
for (String key : result.keySet()) {
Object resultNoRef = replaceReferences(result.get(key), objectsTable);
result.put(key, resultNoRef);
}
return result;
}
}

View File

@@ -0,0 +1,4 @@
/**
* AMF0 importer.
*/
package com.jpexs.decompiler.flash.importers.amf.amf0;

View File

@@ -460,8 +460,27 @@ public class Amf3Importer {
}
}
return resultObject;
}
private Map<String, Object> map(Map<String, Object> objectTable) throws IOException, Amf3ParseException {
Map<String, Object> result = new HashMap<>();
expectedType(SymbolType.CURLY_OPEN);
ParsedSymbol s;
do {
s = lex();
if (!s.isType(SymbolType.STRING)) {
break;
}
String key = (String) s.value;
expectedType(SymbolType.COLON);
result.put(key, value(objectTable));
s = lex();
} while(s.type == SymbolType.COMMA);
expected(s, lexer.yyline(), SymbolType.CURLY_CLOSE);
return result;
}
private Object value(Map<String, Object> objectTable) throws IOException, Amf3ParseException {
ParsedSymbol s = lex();
switch (s.type) {
@@ -581,4 +600,30 @@ public class Amf3Importer {
Object resultNoRef = replaceReferences(resultResolved, objectsTable);
return resultNoRef;
}
/**
* Convert AMF3 map string to object
*
* @param val AMF3 string
* @return Object
* @throws IOException On I/O error
* @throws Amf3ParseException On parse error
*/
public Map<String, Object> stringToAmfMap(String val) throws IOException, Amf3ParseException {
lexer = new Amf3Lexer(new StringReader(val));
Map<String, Object> objectsTable = new HashMap<>();
List<ReferencedObjectType> references = new ArrayList<>();
Map<String, Object> result = map(objectsTable);
for (String key: result.keySet()) {
Object resultResolved = resolveObjects(result.get(key), objectsTable, true);
result.put(key, resultResolved);
}
for (String key: result.keySet()) {
Object resultNoRef = replaceReferences(result.get(key), objectsTable);
result.put(key, resultNoRef);
}
return result;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2010-2024 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.sol;
import com.jpexs.decompiler.flash.amf.amf0.Amf0InputStream;
import com.jpexs.decompiler.flash.amf.amf0.Amf0OutputStream;
import com.jpexs.helpers.MemoryInputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
*
* @author JPEXS
*/
public class FilePathTag extends Tag {
public static final int ID = 3;
public String filePath;
public FilePathTag(byte[] data, boolean forceWriteAsLong) {
super(ID, "DefineFilePath", data, forceWriteAsLong);
}
@Override
public void readData() throws IOException {
Amf0InputStream is = new Amf0InputStream(new MemoryInputStream(data));
int filePathLen = is.readU16("filePath");
filePath = new String(is.readBytes(filePathLen), "UTF-8");
}
@Override
public void writeData(OutputStream os) throws IOException {
Amf0OutputStream aos = new Amf0OutputStream(os);
byte[] filePathData = filePath.getBytes("UTF-8");
aos.writeU16(filePathData.length);
aos.writeBytes(filePathData);
}
}

View File

@@ -0,0 +1,149 @@
/*
* Copyright (C) 2010-2024 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.sol;
import com.jpexs.decompiler.flash.amf.amf0.Amf0InputStream;
import com.jpexs.decompiler.flash.amf.amf0.Amf0OutputStream;
import com.jpexs.decompiler.flash.amf.amf3.Amf3InputStream;
import com.jpexs.decompiler.flash.amf.amf3.Amf3OutputStream;
import com.jpexs.decompiler.flash.amf.amf3.NoSerializerExistsException;
import com.jpexs.decompiler.flash.amf.amf3.Traits;
import com.jpexs.helpers.MemoryInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author JPEXS
*/
public class LsoTag extends Tag {
public static final int ID = 2;
public String fileName;
public int amfVersion;
public Map<String, Object> amfValues = new LinkedHashMap<>();
public LsoTag(byte[] data, boolean forceWriteAsLong) {
super(ID, "DefineLso", data, forceWriteAsLong);
}
public LsoTag(String fileName, int amfVersion, Map<String, Object> amfValues) {
super(ID, "DefineLso", new byte[0], true);
this.fileName = fileName;
this.amfVersion = amfVersion;
this.amfValues = amfValues;
}
@Override
public void readData() throws IOException {
Amf0InputStream is = new Amf0InputStream(new MemoryInputStream(data));
byte[] expectedSig = new byte[]{'T', 'C', 'S', 'O'};
byte[] actualSig = is.readBytes(4);
if (!Arrays.equals(actualSig, expectedSig)) {
throw new IllegalArgumentException("Not a SOL file - invalid signature");
}
is.skip(6); //00 04 00 00 00 00
int filenameLen = is.readU16("filenameLen");
fileName = new String(is.readBytes(filenameLen), "UTF-8");
amfVersion = (int) is.readU32("amfVersion");
if (amfVersion != 0 && amfVersion != 3) {
throw new IllegalArgumentException("Unsupported AMF version");
}
byte[] amfData = is.readBytes(is.available());
if (amfVersion == 0) {
Amf0InputStream ais = new Amf0InputStream(new MemoryInputStream(amfData));
List<Object> complexObjects = new ArrayList<>();
while (ais.available() > 0) {
String varName = ais.readUtf8("varName");
try {
Object varValue = ais.readValue("varValue");
amfValues.put(varName, varValue);
} catch (NoSerializerExistsException ex) {
throw new IllegalArgumentException("Serializer for class " + ex.getClassName() + " not found");
}
ais.read(); //ending byte
}
ais.resolveMapReferences(amfValues);
}
if (amfVersion == 3) {
Amf3InputStream ais = new Amf3InputStream(new MemoryInputStream(amfData));
List<Object> objectsTable = new ArrayList<>();
List<Traits> traitsTable = new ArrayList<>();
List<String> stringTable = new ArrayList<>();
while (ais.available() > 0) {
String varName = ais.readUtf8Vr("varName", new ArrayList<>());
try {
Object varValue = ais.readValue("varValue", new HashMap<>(), objectsTable, traitsTable, stringTable);
amfValues.put(varName, varValue);
} catch (NoSerializerExistsException ex) {
throw new IllegalArgumentException("Serializer for class " + ex.getClassName() + " not found");
}
ais.read(); //ending byte
}
}
}
@Override
public void writeData(OutputStream os) throws IOException {
Amf0OutputStream aos = new Amf0OutputStream(os);
aos.write(new byte[] {'T', 'C', 'S', 'O'});
aos.write(new byte[] {0x00,0x04,0x00,0x00,0x00,0x00});
byte[] fileNameData = fileName.getBytes("UTF-8");
aos.writeU16(fileNameData.length);
aos.write(fileNameData);
aos.writeU32(amfVersion);
if (amfVersion == 0) {
List<Object> complexObjects = new ArrayList<>();
for(String key: amfValues.keySet()) {
aos.writeUtf8(key);
aos.writeValue(amfValues.get(key), complexObjects);
aos.write(0);
}
}
if (amfVersion == 3) {
Amf3OutputStream a3os = new Amf3OutputStream(os);
List<String> stringTable = new ArrayList<>();
List<Traits> traitTable = new ArrayList<>();
List<Object> objectTable = new ArrayList<>();
for(String key: amfValues.keySet()) {
try {
a3os.writeUtf8Vr(key, new ArrayList<>()); //Intentionally not using string table
a3os.writeValue(amfValues.get(key), new HashMap<>(), stringTable, traitTable, objectTable);
} catch (NoSerializerExistsException ex) {
throw new IllegalArgumentException("Serializer not found for class " + ex.getClassName());
}
aos.write(0);
}
}
}
}

View File

@@ -0,0 +1,225 @@
/*
* Copyright (C) 2010-2024 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.sol;
import com.jpexs.decompiler.flash.EndOfStreamException;
import com.jpexs.decompiler.flash.amf.amf0.Amf0OutputStream;
import com.jpexs.decompiler.flash.exporters.amf.amf0.Amf0Exporter;
import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter;
import com.jpexs.decompiler.flash.importers.amf.amf0.Amf0Importer;
import com.jpexs.decompiler.flash.importers.amf.amf3.Amf3Importer;
import com.jpexs.decompiler.flash.importers.amf.amf3.Amf3ParseException;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
* @author JPEXS
*/
public class SolFile {
private DataInputStream is;
private long pos = 0;
private List<Tag> tags = new ArrayList<>();
public SolFile(InputStream is) throws IOException {
this.is = new DataInputStream(is);
readTags();
}
public SolFile(String fileName, int amfVersion, Map<String, Object> amfValues) {
tags.add(new LsoTag(fileName, amfVersion, amfValues));
}
public void writeTo(OutputStream os) throws IOException {
for (Tag t: tags) {
writeTag(os, t);
}
}
private void readTags() throws IOException {
while (is.available() > 0) {
Tag t = readTag();
t.readData();
tags.add(t);
}
}
private int readInternal() throws IOException {
int ret = is.read();
if (ret == -1) {
throw new EndOfStreamException();
}
pos++;
return ret;
}
private int readUI16() throws IOException {
int b1 = readInternal();
int b2 = readInternal();
int ret = (b1 << 8) + b2;
return ret;
}
private long readUI32() throws IOException {
int b1 = readInternal();
int b2 = readInternal();
int b3 = readInternal();
int b4 = readInternal();
return ((b1 << 24) + (b2 << 16) + (b3 << 8) + b4) & 0xffffffff;
}
private byte[] readBytes(int count) throws IOException {
byte[] ret = new byte[count];
try {
is.readFully(ret);
} catch (EOFException eof) {
throw new EndOfStreamException();
}
pos += count;
return ret;
}
private void skipBytes(int count) throws IOException {
is.skip(count);
pos += count;
}
private void writeTag(OutputStream os, Tag t) throws IOException{
Amf0OutputStream aos = new Amf0OutputStream(os);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
t.writeData(baos);
int contentLength = baos.size();
if (contentLength > 0x3F || t.forceWriteAsLong) {
aos.writeU16((t.getTagType() << 6) | 0x3F);
aos.writeU32(baos.size());
} else {
aos.writeU16((t.getTagType() << 6) | contentLength);
}
aos.writeBytes(baos.toByteArray());
}
private Tag readTag() throws IOException {
long headerPos = pos;
int tagTypeAndLength = readUI16();
int contentLength = tagTypeAndLength & 0x3F;
boolean writeAsLong = false;
if (contentLength == 0x3F) {
contentLength = (int) readUI32();
writeAsLong = true;
}
int tagType = tagTypeAndLength >> 6;
byte[] data = readBytes(contentLength);
switch (tagType) {
case LsoTag.ID:
return new LsoTag(data, writeAsLong);
case FilePathTag.ID:
return new FilePathTag(data, writeAsLong);
default:
return new UnknownTag(tagType, data, writeAsLong);
}
}
public List<Tag> getTags() {
return tags;
}
private LsoTag getLsoTag() throws IOException {
for (Tag t : getTags()) {
if (t instanceof LsoTag) {
return (LsoTag) t;
}
}
return null;
}
public int getAmfVersion() throws IOException {
LsoTag lsoTag = getLsoTag();
if (lsoTag == null) {
return -1;
}
return lsoTag.amfVersion;
}
public String getFileName() throws IOException {
LsoTag lsoTag = getLsoTag();
if (lsoTag == null) {
return null;
}
return lsoTag.fileName;
}
public Map<String, Object> getAmfValues() throws IOException {
LsoTag lsoTag = getLsoTag();
if (lsoTag == null) {
return new HashMap<>();
}
return lsoTag.amfValues;
}
public static void main(String[] args) throws FileNotFoundException, IOException {
SolFile sol0 = new SolFile(new FileInputStream("testdata/sharedobjects/data/amf0test.sol"));
sol0.writeTo(new FileOutputStream("testdata/sharedobjects/data/out/amf0test.sol"));
for (Tag t : sol0.getTags()) {
if (t instanceof LsoTag) {
LsoTag lt = (LsoTag) t;
String amf0string = Amf0Exporter.amfMapToString(lt.amfValues, 0, "\r\n");
Amf0Importer importer = new Amf0Importer();
try {
Map<String, Object> imported = importer.stringToAmfMap(amf0string);
String amf0stringNew = Amf0Exporter.amfMapToString(imported, 0, "\r\n");
System.err.println("same0 = " + amf0stringNew.equals(amf0string));
} catch (Amf3ParseException ex) {
ex.printStackTrace();
}
}
}
SolFile sol3 = new SolFile(new FileInputStream("testdata/sharedobjects/data/amf3test.sol"));
sol3.writeTo(new FileOutputStream("testdata/sharedobjects/data/out/amf3test.sol"));
for (Tag t : sol3.getTags()) {
if (t instanceof LsoTag) {
LsoTag lt = (LsoTag) t;
String amf3string = Amf3Exporter.amfMapToString(lt.amfValues, " ", "\r\n", 0);
Amf3Importer importer = new Amf3Importer();
try {
Map<String, Object> imported = importer.stringToAmfMap(amf3string);
String amf3stringNew = Amf3Exporter.amfMapToString(imported, " ", "\r\n", 0);;
System.err.println("same3 = " + amf3stringNew.equals(amf3string));
} catch (Amf3ParseException ex) {
ex.printStackTrace();
}
}
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2010-2024 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.sol;
import java.io.IOException;
import java.io.OutputStream;
/**
* SOL file tag.
* @author JPEXS
*/
public abstract class Tag {
protected boolean forceWriteAsLong;
private final int tagType;
private final String tagName;
protected byte[] data;
public Tag(int tagType, String tagName, byte[] data, boolean forceWriteAsLong) {
this.tagType = tagType;
this.tagName = tagName;
this.data = data;
this.forceWriteAsLong = forceWriteAsLong;
}
public abstract void readData() throws IOException;
public abstract void writeData(OutputStream os) throws IOException;
public int getTagType() {
return tagType;
}
public boolean isForceWriteAsLong() {
return forceWriteAsLong;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2010-2024 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.sol;
import java.io.IOException;
import java.io.OutputStream;
/**
*
* @author JPEXS
*/
public class UnknownTag extends Tag {
public UnknownTag(int tagType, byte[] data, boolean forceWriteAsLong) {
super(tagType, "Unknown(" + tagType + ")", data, forceWriteAsLong);
}
@Override
public void readData() {
}
@Override
public void writeData(OutputStream os) throws IOException {
os.write(data);
}
}

View File

@@ -0,0 +1,4 @@
/**
* Reading Local Shared Objects.
*/
package com.jpexs.decompiler.flash.sol;

View File

@@ -21,7 +21,6 @@ import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.SWFOutputStream;
import com.jpexs.decompiler.flash.abc.CopyOutputStream;
import com.jpexs.decompiler.flash.amf.amf3.ListSet;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.tags.base.BoundedTag;
import com.jpexs.decompiler.flash.tags.base.CharacterIdTag;
@@ -794,7 +793,7 @@ public abstract class Tag implements NeedsCharacters, Exportable, Serializable {
* @return Missing needed characters
*/
public Set<Integer> getMissingNeededCharacters(Set<Integer> needed) {
Set<Integer> needed2 = new ListSet<>(needed);
Set<Integer> needed2 = new LinkedHashSet<>(needed);
if (needed2.isEmpty()) {
return new LinkedHashSet<>();
}