mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-06-26 19:15:51 +00:00
Added: #2360 SOL file (Flash Local Shared Object - flash cookie) editor
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* AMF0.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.amf.amf0;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* AMF0 types.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.amf.amf0.types;
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* AMF0 export.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.exporters.amf.amf0;
|
||||
@@ -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(",");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* AMF0 importer.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.importers.amf.amf0;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
149
libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/sol/LsoTag.java
Normal file
149
libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/sol/LsoTag.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
225
libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/sol/SolFile.java
Normal file
225
libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/sol/SolFile.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/sol/Tag.java
Normal file
50
libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/sol/Tag.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Reading Local Shared Objects.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.sol;
|
||||
@@ -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<>();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user