diff --git a/.gitignore b/.gitignore index 25c90c1d4..d3caca575 100644 --- a/.gitignore +++ b/.gitignore @@ -1,71 +1,72 @@ -*.orig -*.dcu -*.ddp -*.orig.* -*.chg.* -*.rej -*.conflict~ -*.identcache -*.recompiled.swf -run_test_*.swf -Thumbs.db -/build/ -/reports/ -/installer.cfg -/releases/ -/locales/ -/javadoc/ -/coverage/ -/dist/ -/mac/ -/testdata/decompile/ -/testdata/recompile/ -/build_exe.xml -/build_exe64.xml -/coverage.ec -/libsrc/jsyntaxpane/jsyntaxpane/src/target/ -hs_err_pid*.log -*.~* -/revision.txt -/lib/ffdec_lib.jar -/lib/README.TXT -/libsrc/avi/nbproject/private/ -/libsrc/gnujpdf/dist/ -/libsrc/jsyntaxpane/jsyntaxpane/target/ -/libsrc/ffdec_lib/build/ -/libsrc/ffdec_lib/nbproject/private/ -/libsrc/ffdec_lib/javadoc/ -/libsrc/ffdec_lib/coverage/ -/libsrc/ffdec_lib/reports/ -/libsrc/ffdec_lib/dist/ -/libsrc/ffdec_lib/testdata/decompile/ -/libsrc/ffdec_lib/testdata/recompile/ -/libsrc/ffdec_lib/testdata/directediting/ -/libsrc/ffdec_lib/coverage.ec -/libsrc/ffdec_lib/revision.txt -/libsrc/gif/nbproject/private/ -/libsrc/gif/build/ -/libsrc/jpproxy/nbproject/private/ -/libsrc/jpproxy/dist/ -/libsrc/jpproxy/build/ -/libsrc/LZMA/nbproject/private/ -/libsrc/ttf/nbproject/private/ -/libsrc/ttf/build/ -/libsrc/tablelayout/nbproject/private/ -/libsrc/tablelayout/build/ -/libsrc/tablelayout/dist/ -/libsrc/uploader/nbproject/private/ -/libsrc/uploader/build/ -/libsrc/uploader/dist/ -/jpexs_website.properties -/version.properties -/tools.properties -/nbproject/private/ -/libsrc/cmykjpeg/build/ -/libsrc/cmykjpeg/nbproject/private/ -/libsrc/Plugins/nbproject/private/ -/libsrc/Plugins/build/ -/libsrc/Plugins/dist/ -/libsrc/treetable/nbproject/private/ -/libsrc/treetable/build/ -/libsrc/treetable/dist/ \ No newline at end of file +*.orig +*.dcu +*.ddp +*.orig.* +*.chg.* +*.rej +*.conflict~ +*.identcache +*.recompiled.swf +run_test_*.swf +Thumbs.db +/build/ +/reports/ +/installer.cfg +/releases/ +/locales/ +/javadoc/ +/coverage/ +/dist/ +/mac/ +/testdata/decompile/ +/testdata/recompile/ +/build_exe.xml +/build_exe64.xml +/coverage.ec +/libsrc/jsyntaxpane/jsyntaxpane/src/target/ +hs_err_pid*.log +*.~* +/revision.txt +/lib/ffdec_lib.jar +/lib/README.TXT +/libsrc/avi/nbproject/private/ +/libsrc/gnujpdf/dist/ +/libsrc/jsyntaxpane/jsyntaxpane/target/ +/libsrc/ffdec_lib/build/ +/libsrc/ffdec_lib/nbproject/private/ +/libsrc/ffdec_lib/javadoc/ +/libsrc/ffdec_lib/coverage/ +/libsrc/ffdec_lib/reports/ +/libsrc/ffdec_lib/dist/ +/libsrc/ffdec_lib/testdata/decompile/ +/libsrc/ffdec_lib/testdata/recompile/ +/libsrc/ffdec_lib/testdata/directediting/ +/libsrc/ffdec_lib/coverage.ec +/libsrc/ffdec_lib/revision.txt +/libsrc/gif/nbproject/private/ +/libsrc/gif/build/ +/libsrc/jpproxy/nbproject/private/ +/libsrc/jpproxy/dist/ +/libsrc/jpproxy/build/ +/libsrc/LZMA/nbproject/private/ +/libsrc/ttf/nbproject/private/ +/libsrc/ttf/build/ +/libsrc/tablelayout/nbproject/private/ +/libsrc/tablelayout/build/ +/libsrc/tablelayout/dist/ +/libsrc/uploader/nbproject/private/ +/libsrc/uploader/build/ +/libsrc/uploader/dist/ +/jpexs_website.properties +/version.properties +/tools.properties +/nbproject/private/ +/libsrc/cmykjpeg/build/ +/libsrc/cmykjpeg/nbproject/private/ +/libsrc/Plugins/nbproject/private/ +/libsrc/Plugins/build/ +/libsrc/Plugins/dist/ +/libsrc/treetable/nbproject/private/ +/libsrc/treetable/build/ +/libsrc/treetable/dist/ +/libsrc/plugins/nbproject/private/ \ No newline at end of file diff --git a/libsrc/ffdec_lib/lexers/amf3.flex b/libsrc/ffdec_lib/lexers/amf3.flex new file mode 100644 index 000000000..7cbcd3fd2 --- /dev/null +++ b/libsrc/ffdec_lib/lexers/amf3.flex @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2010-2016 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.importers.amf.amf3; +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +%% + +%public +%class Amf3Lexer +%final +%unicode +%char +%type ParsedSymbol +%throws Amf3ParseException + +%{ + + StringBuilder string = new StringBuilder(); + + private static String xmlTagName = ""; + + public int yychar() { + return yychar; + } + + private final Stack pushedBack = new Stack<>(); + + public int yyline() { + return yyline + 1; + } + + public void pushback(ParsedSymbol symb) { + pushedBack.push(symb); + last = null; + } + + ParsedSymbol last; + public ParsedSymbol lex() throws java.io.IOException, Amf3ParseException{ + ParsedSymbol ret = null; + if (!pushedBack.isEmpty()){ + ret = last = pushedBack.pop(); + } else { + ret = last = yylex(); + } + return ret; + } + +%} + +/* main character classes */ +LineTerminator = \r|\n|\r\n +InputCharacter = [^\r\n] + +WhiteSpace = {LineTerminator} | [ \t\f]+ + +/* comments */ +Comment = {TraditionalComment} | {EndOfLineComment} + +TraditionalComment = "/*" [^*] ~"*/" | "/*" "*"+ "/" +EndOfLineComment = "//" {InputCharacter}* {LineTerminator}? + + + +/* identifiers */ +Identifier = [:jletter:][:jletterdigit:]* + +Reference = "#" {Identifier} + +/* integer literals */ +DecIntegerLiteral = 0 | "-"? [1-9][0-9]* + +HexIntegerLiteral = 0 [xX] 0* {HexDigit} {1,8} +HexDigit = [0-9a-fA-F] + +OctIntegerLiteral = 0+ [1-3]? {OctDigit} {1,15} +OctDigit = [0-7] + +/* floating point literals */ +DoubleLiteral = "-"? ({FLit1}|{FLit2}|{FLit3}) {Exponent}? + +FLit1 = [0-9]+ \. [0-9]* +FLit2 = \. [0-9]+ +FLit3 = [0-9]+ +Exponent = [eE] [+-]? [0-9]+ + +/* string and character literals */ +StringCharacter = [^\r\n\"\\] + +%state STRING, CHARLITERAL + +%% + + { + + /* keywords */ + "null" { return new ParsedSymbol(SymbolGroup.GLOBALCONST, SymbolType.NULL, yytext()); } + "undefined" { return new ParsedSymbol(SymbolGroup.GLOBALCONST, SymbolType.UNDEFINED, yytext()); } + "false" { return new ParsedSymbol(SymbolGroup.KEYWORD, SymbolType.FALSE, yytext()); } + "true" { return new ParsedSymbol(SymbolGroup.KEYWORD, SymbolType.TRUE, yytext()); } + "unknown" { return new ParsedSymbol(SymbolGroup.UNKNOWN, SymbolType.UNKNOWN, yytext()); } + + + /* operators */ + + "{" { return new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.CURLY_OPEN, yytext()); } + "}" { return new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.CURLY_CLOSE, yytext()); } + "[" { return new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.BRACKET_OPEN, yytext()); } + "]" { return new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.BRACKET_CLOSE, yytext()); } + "," { return new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.COMMA, yytext()); } + ":" { return new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.COLON, yytext()); } + + /* string literal */ + \" { + string.setLength(0); + yybegin(STRING); + } + + + /* numeric literals */ + + {DecIntegerLiteral} { return new ParsedSymbol(SymbolGroup.INTEGER, SymbolType.INTEGER, Long.parseLong((yytext()))); } + + {HexIntegerLiteral} { return new ParsedSymbol(SymbolGroup.INTEGER, SymbolType.INTEGER, Long.parseLong(yytext().substring(2), 16)); } + + {OctIntegerLiteral} { return new ParsedSymbol(SymbolGroup.INTEGER, SymbolType.INTEGER, Long.parseLong(yytext(), 8)); } + + {DoubleLiteral} { return new ParsedSymbol(SymbolGroup.DOUBLE, SymbolType.DOUBLE, Double.parseDouble((yytext()))); } + + /* comments */ + {Comment} { /*ignore*/ } + + {LineTerminator} { yyline++;} + /* whitespace */ + {WhiteSpace} { /*ignore*/ } + + /* identifiers */ + {Reference} { return new ParsedSymbol(SymbolGroup.IDENTIFIER, SymbolType.REFERENCE, yytext().substring(1)); } + {Identifier} { return new ParsedSymbol(SymbolGroup.IDENTIFIER, SymbolType.IDENTIFIER, yytext()); } +} + + { + \" { + yybegin(YYINITIAL); + // length also includes the trailing quote + return new ParsedSymbol(SymbolGroup.STRING, SymbolType.STRING, string.toString()); + } + + {StringCharacter}+ { string.append(yytext()); } + + /* escape sequences */ + "\\b" { string.append('\b'); } + "\\t" { string.append('\t'); } + "\\n" { string.append('\n'); } + "\\f" { string.append('\f'); } + "\\r" { string.append('\r'); } + "\\\"" { string.append('\"'); } + "\\'" { string.append('\''); } + "\\\\" { string.append('\\'); } + \\x{HexDigit}{2} { char val = (char) Integer.parseInt(yytext().substring(2), 16); + string.append(val); } + \\u{HexDigit}{4} { char val = (char) Integer.parseInt(yytext().substring(2), 16); + string.append(val); } + \\{OctDigit}{3} { char val = (char) Integer.parseInt(yytext().substring(1), 8); + string.append(val); } + + /* escape sequences */ + + \\. { string.append('\\'); /*illegal escape sequence*/ } + {LineTerminator} { yybegin(YYINITIAL); yyline++;} +} + +/* error fallback */ +[^] { } +<> { return new ParsedSymbol(SymbolGroup.EOF, SymbolType.EOF, null); } + diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java index 55f25e910..7649ff95b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java @@ -118,6 +118,9 @@ import com.jpexs.decompiler.flash.action.swf7.ActionExtends; import com.jpexs.decompiler.flash.action.swf7.ActionImplementsOp; import com.jpexs.decompiler.flash.action.swf7.ActionThrow; import com.jpexs.decompiler.flash.action.swf7.ActionTry; +import com.jpexs.decompiler.flash.amf.amf3.Amf3InputStream; +import com.jpexs.decompiler.flash.amf.amf3.Amf3Value; +import com.jpexs.decompiler.flash.amf.amf3.NoSerializerExistsException; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.dumpview.DumpInfo; import com.jpexs.decompiler.flash.tags.CSMTextSettingsTag; @@ -758,6 +761,23 @@ public class SWFInputStream implements AutoCloseable { return ret; } + /** + * Reads AMF3 encoded value from the stream + * + * @param name + * @return + * @throws IOException + */ + public Amf3Value readAmf3Object(String name) throws IOException { + Amf3InputStream ai = new Amf3InputStream(is); + ai.dumpInfo = this.dumpInfo; + try { + return new Amf3Value(ai.readValue("amfData")); + } catch (NoSerializerExistsException nse) { + return new Amf3Value(nse.getIncompleteData()); + } + } + /** * Reads byte range from the stream * diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFOutputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFOutputStream.java index fd976b55b..9ad13c603 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFOutputStream.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFOutputStream.java @@ -16,6 +16,10 @@ */ package com.jpexs.decompiler.flash; +import com.jpexs.decompiler.flash.amf.amf3.Amf3OutputStream; +import com.jpexs.decompiler.flash.amf.amf3.Amf3Value; +import com.jpexs.decompiler.flash.amf.amf3.NoSerializerExistsException; +import com.jpexs.decompiler.flash.amf.amf3.ObjectTypeSerializeHandler; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.tags.DefineBitsLosslessTag; import com.jpexs.decompiler.flash.tags.Tag; @@ -81,7 +85,9 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.Deflater; @@ -1946,4 +1952,31 @@ public class SWFOutputStream extends OutputStream { writeUB(5, (value >> 11) & 0xff); writeUB(5, (value >> 3) & 0xff); } + + /** + * Writes AMF3 encoded value. Warning: Correct serializer needs to be passed + * as second parameter when using IExternalizable + * + * @param value + * @param serializers Map className=>Serializer for classes implementing + * IExternalizable + * @throws IOException + * @throws NoSerializerExistsException + */ + public void writeAmf3Object(Amf3Value value, Map serializers) throws IOException, NoSerializerExistsException { + Amf3OutputStream ao = new Amf3OutputStream(os); + ao.writeValue(value.getValue(), serializers); + } + + /** + * Writes AMF3 encoded value. Warning: When the object implements + * IExternalizable, you need to pass serializer as second parameter + * + * @param value + * @throws IOException + * @throws NoSerializerExistsException + */ + public void writeAmf3Object(Amf3Value value) throws IOException, NoSerializerExistsException { + writeAmf3Object(value, new HashMap<>()); + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Amf3InputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Amf3InputStream.java new file mode 100644 index 000000000..c13644348 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Amf3InputStream.java @@ -0,0 +1,867 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +import com.jpexs.decompiler.flash.EndOfStreamException; +import com.jpexs.decompiler.flash.amf.amf3.types.ArrayType; +import com.jpexs.decompiler.flash.amf.amf3.types.XmlType; +import com.jpexs.decompiler.flash.amf.amf3.types.ObjectType; +import com.jpexs.decompiler.flash.amf.amf3.types.XmlDocType; +import com.jpexs.decompiler.flash.amf.amf3.types.VectorObjectType; +import com.jpexs.decompiler.flash.amf.amf3.types.VectorIntType; +import com.jpexs.decompiler.flash.amf.amf3.types.ByteArrayType; +import com.jpexs.decompiler.flash.amf.amf3.types.DateType; +import com.jpexs.decompiler.flash.amf.amf3.types.VectorDoubleType; +import com.jpexs.decompiler.flash.amf.amf3.types.DictionaryType; +import com.jpexs.decompiler.flash.amf.amf3.types.VectorUIntType; +import com.jpexs.decompiler.flash.amf.amf3.types.BasicType; +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.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Amf3InputStream extends InputStream { + + public final static Logger LOGGER = Logger.getLogger(Amf3InputStream.class.getName()); + private final MemoryInputStream is; + public DumpInfo dumpInfo; + private static final String NO_REFERENCE_BIT_TEXT = "not reference"; + private static final String OBJECT_INDEX_TEXT = "object index"; + private static final String STRING_INDEX_TEXT = "string index"; + private static final String TRAIT_INDEX_TEXT = "trait index"; + + public Amf3InputStream(MemoryInputStream is) { + this.is = is; + } + + 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; + } + + public void endDumpLevel() { + endDumpLevel(null); + } + + public void endDumpLevel(Object value) { + if (dumpInfo != null) { + dumpInfo.lengthBytes = is.getPos() - dumpInfo.startByte; + dumpInfo.previewValue = value; + dumpInfo = dumpInfo.parent; + } + } + + public void endDumpLevelUntil(DumpInfo di) { + if (di != null) { + while (dumpInfo != null && dumpInfo != di) { + endDumpLevel(); + } + } + } + + public int readU8(String name) throws IOException { + newDumpLevel(name, "U8"); + int ret = readInternal(); + endDumpLevel(ret); + return ret; + } + + 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; + } + + public long readU32(String name) throws IOException { + newDumpLevel(name, "U32"); + long ret = readU32Internal(); + endDumpLevel(ret); + return ret; + } + + 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; + } + + public long readS32(String name) throws IOException { + newDumpLevel(name, "S32"); + long ret = signExtend(readU32Internal(), 32); + endDumpLevel(ret); + return ret; + } + + 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))); + } + + public double readDouble(String name) throws IOException { + newDumpLevel(name, "DOUBLE"); + long lval = readLong(); + double ret = Double.longBitsToDouble(lval); + endDumpLevel(EcmaScript.toString(ret)); + return ret; + } + + public long readU29(String name) throws IOException { + newDumpLevel(name, "U29"); + long val = readU29Internal(); + endDumpLevel(val); + return val; + } + + private void renameU29O_ref() { + renameU29("U29O-ref", NO_REFERENCE_BIT_TEXT, OBJECT_INDEX_TEXT); + } + + private void renameU29S_ref() { + renameU29("U29S-ref", NO_REFERENCE_BIT_TEXT, STRING_INDEX_TEXT); + } + + private void renameU29Traits_ref() { + renameU29("U29O-traits-ref", NO_REFERENCE_BIT_TEXT, "trait reference", TRAIT_INDEX_TEXT); + } + + private void renameLastDump(String wholeName) { + if (dumpInfo != null) { + if (!dumpInfo.getChildInfos().isEmpty()) { + DumpInfo u29DumpInfo = dumpInfo.getChildInfos().get(dumpInfo.getChildInfos().size() - 1); + u29DumpInfo.name = wholeName; + } + } + } + + private void setDumpInfoType(String type) { + if (dumpInfo != null) { + dumpInfo.type = type; + } + } + + private void renameU29(String wholeName, String name1, String... names) { + List bitNames = new ArrayList<>(); + bitNames.add(name1); + bitNames.addAll(Arrays.asList(names)); + + String restName = bitNames.remove(bitNames.size() - 1); + + if (bitNames.size() > 6) { + throw new RuntimeException("Renaming more than 6 bits in U29 is not supported"); + } + + if (dumpInfo != null) { + if (!dumpInfo.getChildInfos().isEmpty()) { + DumpInfo u29DumpInfo = dumpInfo.getChildInfos().get(dumpInfo.getChildInfos().size() - 1); + u29DumpInfo.name = wholeName; + long lastBytePos = u29DumpInfo.startByte + u29DumpInfo.lengthBytes - 1; //last byte of U29 is least significant (U29 is big endian) + + int remainingBitLength = ((int) u29DumpInfo.lengthBytes) * 8 - bitNames.size(); + long u29val = ((long) (Long) u29DumpInfo.previewValue); + + DumpInfo restDumpInfo = new DumpInfo(restName, "UB(" + remainingBitLength + ")", u29val >> bitNames.size(), lastBytePos, 0, u29DumpInfo.lengthBytes, remainingBitLength); + restDumpInfo.parent = u29DumpInfo; + u29DumpInfo.getChildInfos().add(restDumpInfo); + + for (int i = bitNames.size() - 1; i >= 0; i--) { + int bitVal = (int) ((u29val >> i) & 1); + DumpInfo bitDumpInfo = new DumpInfo(bitNames.get(i), "bit", bitVal, lastBytePos, 7 - i, 1, 1); + bitDumpInfo.parent = u29DumpInfo; + u29DumpInfo.getChildInfos().add(bitDumpInfo); + } + + } + } + } + + public long readS29(String name) throws IOException { + newDumpLevel(name, "S29"); + long val = signExtend(readU29Internal(), 29); + endDumpLevel(val); + return val; + } + + public long readU29Internal() throws IOException { + long val = 0; + for (int i = 1; i <= 4; i++) { + int b = readInternal(); + if (i == 4) { + val = ((val << 8) + b); + } else { + val = (val << 7) + (b & 0x7F); + if ((b & 0x80) != 0x80) { + break; + } + } + } + return val; + } + + 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; + } + + private String readUtf8Char(String name, long byteLength) throws IOException { + if (byteLength == 0) { + return ""; + } + newDumpLevel(name, "UTF8-char"); + + byte buf[] = new byte[(int) byteLength]; //how about long strings(?), will the int length be enough? + int cnt = is.read(buf); + if (cnt < buf.length) { + throw new EndOfStreamException(); + } + String retString = new String(buf, "UTF-8"); + endDumpLevel("\"" + Helper.escapeActionScriptString(retString) + "\""); + return retString; + } + + public String readUtf8Vr(String name, List stringTable) throws IOException { + newDumpLevel(name, "UTF-8-vr"); + long u = readU29("U29S"); + int stringNoRefFlag = (int) (u & 1); + String retString; + if (stringNoRefFlag == 1) { + renameU29("U29S-value", NO_REFERENCE_BIT_TEXT, "byte length"); + long byteLength = u >> 1; //TODO: long strings, int is not enough for them + retString = readUtf8Char("characters", byteLength); + if (byteLength > 0) { + stringTable.add(retString); + } + LOGGER.log(Level.FINE, "Read string: \"{0}\"", retString); + } else { //flag==0 + renameU29S_ref(); + int stringRefTableIndex = (int) (u >> 1); + + retString = stringTable.get(stringRefTableIndex); + LOGGER.log(Level.FINE, "Read string: reference({0}):" + retString, stringRefTableIndex); + + } + endDumpLevel("\"" + Helper.escapeActionScriptString(retString) + "\""); + return retString; + + } + + private int readInternal() throws IOException { + int ret = read(); + if (ret == -1) { + throw new EndOfStreamException(); + } + return ret; + } + + @Override + public int read() throws IOException { + return is.read(); + } + + public Object readValue(String name) throws IOException, NoSerializerExistsException { + return readValue(name, new HashMap<>()); + } + + public Object readValue(String name, Map serializers) throws IOException, NoSerializerExistsException { + return readValue(name, serializers, new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + } + + private Object readValue(String name, Map serializers, + List objectTable, + List traitsTable, + List stringTable + ) throws IOException, NoSerializerExistsException { + newDumpLevel(name, "value-type"); + Object result; + + try { + int marker = readU8("marker"); + markerswitch: + switch (marker) { + case Marker.UNDEFINED: + renameLastDump("undefined-marker"); + setDumpInfoType("undefined-type"); + LOGGER.log(Level.FINE, "Read value: undefined"); + result = BasicType.UNDEFINED; + break; + case Marker.NULL: + renameLastDump("null-marker"); + setDumpInfoType("null-type"); + LOGGER.log(Level.FINE, "Read value: null"); + result = BasicType.NULL; + break; + case Marker.FALSE: + renameLastDump("false-marker"); + setDumpInfoType("false-type"); + LOGGER.log(Level.FINE, "Read value: false"); + result = Boolean.FALSE; + break; + case Marker.TRUE: + renameLastDump("true-marker"); + setDumpInfoType("true-type"); + LOGGER.log(Level.FINE, "Read value: true"); + result = Boolean.TRUE; + break; + case Marker.INTEGER: + renameLastDump("integer-marker"); + setDumpInfoType("integer-type"); + LOGGER.log(Level.FINE, "Read value: integer"); + long ival = readS29("intValue"); + LOGGER.log(Level.FINER, "Integer value: {0}", ival); + result = ival; + break; + case Marker.DOUBLE: + renameLastDump("double-marker"); + setDumpInfoType("double-type"); + LOGGER.log(Level.FINE, "Read value: double"); + double dval = readDouble("doubleValue"); + LOGGER.log(Level.FINER, "Double value: {0}", "" + dval); + result = dval; + break; + case Marker.STRING: + renameLastDump("string-marker"); + setDumpInfoType("string-type"); + LOGGER.log(Level.FINE, "Read value: string"); + String sval = readUtf8Vr("stringValue", stringTable); + LOGGER.log(Level.FINER, "String value: {0}", sval); + result = sval; + break; + case Marker.XML_DOC: + renameLastDump("xml-doc-marker"); + setDumpInfoType("xml-doc-type"); + LOGGER.log(Level.FINE, "Read value: xml_doc"); + long xmlDocU29 = readU29("U29"); + int xmlDocNoRefFlag = (int) (xmlDocU29 & 1); + if (xmlDocNoRefFlag == 1) { + renameU29("U29X-value", NO_REFERENCE_BIT_TEXT, "byte length"); + long byteLength = xmlDocU29 >> 1; + String xval = readUtf8Char("characters", byteLength); + LOGGER.log(Level.FINER, "XmlDoc value: {0}", xval); + XmlDocType retXmlDoc = new XmlDocType(xval); + objectTable.add(retXmlDoc); + result = retXmlDoc; + } else { + renameU29O_ref(); + int refIndexXmlDoc = (int) (xmlDocU29 >> 1); + LOGGER.log(Level.FINER, "XmlDoc value: reference({0})", refIndexXmlDoc); + result = objectTable.get(refIndexXmlDoc); //What if it's not XmlRef? + } + break; + case Marker.DATE: + renameLastDump("date-marker"); + setDumpInfoType("date-type"); + LOGGER.log(Level.FINE, "Read value: date"); + long dateU29 = readU29("U29"); + int dateNoRefFlag = (int) (dateU29 & 1); + if (dateNoRefFlag == 1) { + renameU29("U29D-value", NO_REFERENCE_BIT_TEXT, "unused"); + //remaining bits of dateU29 are not used + double dtval = readDouble("date-time"); + DateType retDate = new DateType(dtval); + LOGGER.log(Level.FINER, "Date value: {0}", retDate); + objectTable.add(retDate); + result = retDate; + } else { + renameU29O_ref(); + int refIndexDate = (int) (dateU29 >> 1); + LOGGER.log(Level.FINER, "Date value: reference({0})", refIndexDate); + result = objectTable.get(refIndexDate); //What if it's not Date? + } + break; + case Marker.ARRAY: + renameLastDump("array-marker"); + setDumpInfoType("array-type"); + LOGGER.log(Level.FINE, "Read value: array"); + long arrayU29 = readU29("U29"); + int arrayNoRefFlag = (int) (arrayU29 & 1); + if (arrayNoRefFlag == 1) { + renameU29("U29A-value", NO_REFERENCE_BIT_TEXT, "dense count"); + int denseCount = (int) (arrayU29 >> 1); + LOGGER.log(Level.FINEST, "Array value: denseCount={0}", new Object[]{denseCount}); + List> assocPart = new ArrayList<>(); + List densePart = new ArrayList<>(); + ArrayType retArray = new ArrayType(densePart, assocPart); + objectTable.add(retArray); //add before processing elements which may reference this + newDumpLevel("associativeValues", "assoc-value"); + while (true) { + String key = readUtf8Vr("key", stringTable); + if (key.isEmpty()) { + renameLastDump("UTF-8-empty"); + break; + } else { + try { + Object val = readValue("value", serializers, objectTable, traitsTable, stringTable); + assocPart.add(new Pair<>(key, val)); + } catch (NoSerializerExistsException nse) { + assocPart.add(new Pair<>(key, nse.getIncompleteData())); + throw new NoSerializerExistsException(nse.getClassName(), retArray, nse); + } + } + } + endDumpLevel(); + LOGGER.log(Level.FINEST, "Array value: assocSize={0}", new Object[]{assocPart.size()}); + + newDumpLevel("denseValues", "value-type[]"); + for (int i = 0; i < denseCount; i++) { + try { + densePart.add(readValue("denseValue", serializers, objectTable, traitsTable, stringTable)); + } catch (NoSerializerExistsException nse) { + densePart.add(nse.getIncompleteData()); + for (int j = i + 1; j < denseCount; j++) { + densePart.add(BasicType.UNKNOWN); + } + throw new NoSerializerExistsException(nse.getClassName(), retArray, nse); + } + } + endDumpLevel(); + LOGGER.log(Level.FINER, "Array value: dense_size={0},assocSize={1}", new Object[]{densePart.size(), assocPart.size()}); + result = retArray; + + } else { + renameU29O_ref(); + int refIndexArray = (int) (arrayU29 >> 1); + LOGGER.log(Level.FINER, "Array value: reference({0})", refIndexArray); + result = objectTable.get(refIndexArray); //What if it's not Array? + } + break; + + case Marker.OBJECT: + renameLastDump("object-marker"); + setDumpInfoType("object-type"); + LOGGER.log(Level.FINE, "Read value: object"); + long objectU29 = readU29("U29"); + int objectNoRefFlag = (int) (objectU29 & 1); + if (objectNoRefFlag == 1) { + Traits traits; + int objectTraitsNoRefFlag = (int) ((objectU29 >> 1) & 1); + if (objectTraitsNoRefFlag == 1) { + int objectTraitsExtFlag = (int) ((objectU29 >> 2) & 1); + ObjectType retObjectType; + if (objectTraitsExtFlag == 1) { + renameU29("U29O-traits-ext", NO_REFERENCE_BIT_TEXT, "not trait reference", "externalized traits", "unused"); + String className = readUtf8Vr("className", stringTable); + if (!serializers.containsKey(className)) { + throw new NoSerializerExistsException(className, new ObjectType(new Traits(className, false, new ArrayList<>()), (byte[]) null, new ArrayList<>()), null); + } + newDumpLevel("serializedData", "U8[]"); + MonitoredInputStream mis = new MonitoredInputStream(is); + List> serMembers = serializers.get(className).readObject(className, mis); + byte serData[] = mis.getReadData(); + endDumpLevel(); + Traits unserTraits = new Traits(className, false, new ArrayList<>()); + retObjectType = new ObjectType(unserTraits, serData, serMembers); + + LOGGER.log(Level.FINER, "Object/Traits value: customSerialized"); + objectTable.add(retObjectType); + result = retObjectType; + break markerswitch; + } else { + renameU29("U29O-traits", NO_REFERENCE_BIT_TEXT, "not trait reference", "externalized traits", "dynamic", "sealed count"); + int dynamicFlag = (int) ((objectU29 >> 3) & 1); + int numSealed = (int) (objectU29 >> 4); + LOGGER.log(Level.FINEST, "object dynamicFlag:{0}", dynamicFlag); + LOGGER.log(Level.FINEST, "object numSealed:{0}", numSealed); + String className = readUtf8Vr("className", stringTable); + LOGGER.log(Level.FINEST, "object className:{0}", className); + List sealedMemberNames = new ArrayList<>(); + if (numSealed > 0) { + newDumpLevel("sealedMemberNames", "UTF-8-vr[]"); + + for (int i = 0; i < numSealed; i++) { + sealedMemberNames.add(readUtf8Vr("sealedMemberName", stringTable)); + } + endDumpLevel(); + } + traits = new Traits(className, dynamicFlag == 1, sealedMemberNames); + traitsTable.add(traits); + } + } else { + renameU29Traits_ref(); + int refIndexTraits = (int) (objectU29 >> 2); + traits = traitsTable.get(refIndexTraits); + LOGGER.log(Level.FINER, "Traits value: reference({0}) - traitsize={1}", new Object[]{refIndexTraits, traits.getSealedMemberNames().size()}); + } + List> sealedMembers = new ArrayList<>(); + List> dynamicMembers = new ArrayList<>(); + + Object retObjectType = new ObjectType(traits, sealedMembers, dynamicMembers); + objectTable.add(retObjectType); //add it before any subvalue can reference it + List sealedMemberValues = new ArrayList<>(); + NoSerializerExistsException error = null; + if (!traits.getSealedMemberNames().isEmpty()) { + newDumpLevel("sealedMemberValues", "value-type[]"); + for (int i = 0; i < traits.getSealedMemberNames().size(); i++) { + try { + sealedMemberValues.add(readValue("sealedMemberValue", serializers, objectTable, traitsTable, stringTable)); + } catch (NoSerializerExistsException nse) { + sealedMemberValues.add(nse.getIncompleteData()); + for (int j = i + 1; j < traits.getSealedMemberNames().size(); j++) { + sealedMemberValues.add(BasicType.UNKNOWN); + } + error = nse; + break; + } + } + endDumpLevel(); + } + + for (int i = 0; i < traits.getSealedMemberNames().size(); i++) { + sealedMembers.add(new Pair<>(traits.getSealedMemberNames().get(i), sealedMemberValues.get(i))); + } + if (traits.isDynamic()) { + newDumpLevel("dynamicMembers", "dynamic-member[]"); + String dynamicMemberName; + while (!(dynamicMemberName = readUtf8Vr("name", stringTable)).isEmpty()) { + try { + Object dynamicMemberValue = readValue("value", serializers, objectTable, traitsTable, stringTable); + dynamicMembers.add(new Pair<>(dynamicMemberName, dynamicMemberValue)); + } catch (NoSerializerExistsException nse) { + dynamicMembers.add(new Pair<>(dynamicMemberName, nse.getIncompleteData())); + throw new NoSerializerExistsException(nse.getClassName(), retObjectType, nse); + } finally { + //group dumpInfo to one sub "dynamic-member" + if (dumpInfo != null) { + DumpInfo valueDumpInfo = dumpInfo.getChildInfos().remove(dumpInfo.getChildInfos().size() - 1); + DumpInfo nameDumpInfo = dumpInfo.getChildInfos().remove(dumpInfo.getChildInfos().size() - 1); + DumpInfo memberDumpInfo = new DumpInfo("member", "dynamic-member", "", nameDumpInfo.startByte, nameDumpInfo.lengthBytes + valueDumpInfo.lengthBytes); + memberDumpInfo.getChildInfos().add(nameDumpInfo); + memberDumpInfo.getChildInfos().add(valueDumpInfo); + memberDumpInfo.parent = dumpInfo; + nameDumpInfo.parent = memberDumpInfo; + valueDumpInfo.parent = memberDumpInfo; + memberDumpInfo.previewValue = "" + nameDumpInfo.previewValue + (valueDumpInfo.previewValue != null ? " : " + valueDumpInfo.previewValue : ""); + dumpInfo.getChildInfos().add(memberDumpInfo); + } + } + } + renameLastDump("UTF-8-empty"); + endDumpLevel(); + } + + LOGGER.log(Level.FINER, "Object value: dynamic={0},className={1},sealedSize={2},dynamicSize={3}", new Object[]{traits.isDynamic(), traits.getClassName(), sealedMembers.size(), dynamicMembers.size()}); + result = retObjectType; + } else { + renameU29O_ref(); + int refIndexObject = (int) (objectU29 >> 1); + LOGGER.log(Level.FINER, "Object value: reference({0})", refIndexObject); + result = objectTable.get(refIndexObject); + } + break; + case Marker.XML: + renameLastDump("xml-marker"); + setDumpInfoType("xml-type"); + LOGGER.log(Level.FINE, "Read value: xml"); + long xmlU29 = readU29("U29"); + int xmlNoRefFlag = (int) (xmlU29 & 1); + if (xmlNoRefFlag == 1) { + renameU29("U29X-value", NO_REFERENCE_BIT_TEXT, "byte length"); + long byteLength = (xmlU29 >> 1); + String xString = readUtf8Char("characters", byteLength); + XmlType retXmlType = new XmlType(xString); + LOGGER.log(Level.FINER, "Xml value: {0}", xString); + objectTable.add(retXmlType); + result = retXmlType; + } else { + renameU29O_ref(); + int refIndexXml = (int) (xmlU29 >> 1); + LOGGER.log(Level.FINER, "XML value: reference({0})", refIndexXml); + result = objectTable.get(refIndexXml); + } + break; + case Marker.BYTE_ARRAY: + renameLastDump("byte-array-marker"); + setDumpInfoType("bytearray-type"); + LOGGER.log(Level.FINE, "Read value: bytearray"); + long byteArrayU29 = readU29("U29"); + int byteArrayNoRefFlag = (int) (byteArrayU29 & 1); + if (byteArrayNoRefFlag == 1) { + renameU29("U29B-value", NO_REFERENCE_BIT_TEXT, "byte array length"); + int byteArrayLength = (int) (byteArrayU29 >> 1); + newDumpLevel("bytes", "U8[]"); + byte byteArrayBuf[] = new byte[byteArrayLength]; + if (is.read(byteArrayBuf) != byteArrayLength) { + throw new EndOfStreamException(); + } + endDumpLevel(); + + LOGGER.log(Level.FINER, "ByteArray value: bytes[{0}]", byteArrayLength); + ByteArrayType retByteArrayType = new ByteArrayType(byteArrayBuf); + objectTable.add(retByteArrayType); + result = retByteArrayType; + } else { + renameU29O_ref(); + int refIndexByteArray = (int) (byteArrayU29 >> 1); + LOGGER.log(Level.FINER, "ByteArray value: reference({0})", refIndexByteArray); + result = objectTable.get(refIndexByteArray); + } + break; + case Marker.VECTOR_INT: + renameLastDump("vector-int-marker"); + setDumpInfoType("vector-int-type"); + LOGGER.log(Level.FINE, "Read value: vector_int"); + long vectorIntU29 = readU29("U29"); + int vectorIntNoRefFlag = (int) (vectorIntU29 & 1); + if (vectorIntNoRefFlag == 1) { + renameU29("U29V-value", NO_REFERENCE_BIT_TEXT, "item count"); + int vectorIntCountItems = (int) (vectorIntU29 >> 1); + int fixed = readU8("fixed"); + List vals = new ArrayList<>(); + newDumpLevel("items", "S32[]"); + for (int i = 0; i < vectorIntCountItems; i++) { + vals.add(readS32("intValue")); + } + endDumpLevel(); + VectorIntType retVectorInt = new VectorIntType(fixed == 1, vals); + LOGGER.log(Level.FINER, "Vector value: fixed={0}, size={1}]", new Object[]{fixed, vectorIntCountItems}); + objectTable.add(retVectorInt); + result = retVectorInt; + } else { + renameU29O_ref(); + int refIndexVectorInt = (int) (vectorIntU29 >> 1); + LOGGER.log(Level.FINER, "Vector value: reference({0})", refIndexVectorInt); + result = objectTable.get(refIndexVectorInt); + } + break; + case Marker.VECTOR_UINT: + renameLastDump("vector-uint-marker"); + setDumpInfoType("vector-uint-type"); + + LOGGER.log(Level.FINE, "Read value: vector_uint"); + long vectorUIntU29 = readU29("U29"); + int vectorUIntNoRefFlag = (int) (vectorUIntU29 & 1); + if (vectorUIntNoRefFlag == 1) { + renameU29("U29V-value", NO_REFERENCE_BIT_TEXT, "item count"); + int vectorUIntCountItems = (int) (vectorUIntU29 >> 1); + int fixed = readU8("fixed"); + List vals = new ArrayList<>(); + newDumpLevel("items", "U32[]"); + for (int i = 0; i < vectorUIntCountItems; i++) { + vals.add(readU32("uintValue")); + } + endDumpLevel(); + VectorUIntType retVectorUInt = new VectorUIntType(fixed == 1, vals); + LOGGER.log(Level.FINER, "Vector value: fixed={0}, size={1}]", new Object[]{fixed, vectorUIntCountItems}); + objectTable.add(retVectorUInt); + result = retVectorUInt; + } else { + renameU29O_ref(); + int refIndexVectorUInt = (int) (vectorUIntU29 >> 1); + LOGGER.log(Level.FINER, "Vector value: reference({0})", refIndexVectorUInt); + result = objectTable.get(refIndexVectorUInt); + } + break; + case Marker.VECTOR_DOUBLE: + renameLastDump("vector-double-marker"); + setDumpInfoType("vector-double-type"); + + LOGGER.log(Level.FINE, "Read value: vector_double"); + long vectorDoubleU29 = readU29("U29"); + int vectorDoubleNoRefFlag = (int) (vectorDoubleU29 & 1); + if (vectorDoubleNoRefFlag == 1) { + renameU29("U29V-value", NO_REFERENCE_BIT_TEXT, "item count"); + int vectorDoubleCountItems = (int) (vectorDoubleU29 >> 1); + int fixed = readU8("fixed"); + List vals = new ArrayList<>(); + newDumpLevel("items", "DOUBLE[]"); + for (int i = 0; i < vectorDoubleCountItems; i++) { + vals.add(readDouble("doubleValue")); + } + endDumpLevel(); + VectorDoubleType retVectorDouble = new VectorDoubleType(fixed == 1, vals); + LOGGER.log(Level.FINER, "Vector value: fixed={0}, size={1}]", new Object[]{fixed, vectorDoubleCountItems}); + objectTable.add(retVectorDouble); + result = retVectorDouble; + } else { + renameU29O_ref(); + int refIndexVectorDouble = (int) (vectorDoubleU29 >> 1); + LOGGER.log(Level.FINER, "Vector value: reference({0})", refIndexVectorDouble); + result = objectTable.get(refIndexVectorDouble); + } + break; + case Marker.VECTOR_OBJECT: + renameLastDump("vector-object-marker"); + setDumpInfoType("vector-object-type"); + + LOGGER.log(Level.FINE, "Read value: vector_object"); + long vectorObjectU29 = readU29("U29"); + int vectorObjectNoRefFlag = (int) (vectorObjectU29 & 1); + if (vectorObjectNoRefFlag == 1) { + renameU29("U29V-value", NO_REFERENCE_BIT_TEXT, "item count"); + int vectorObjectCountItems = (int) (vectorObjectU29 >> 1); + int fixed = readU8("fixed"); + String objectTypeName = readUtf8Vr("object-type-name", stringTable); //uses "*" for any type + List vals = new ArrayList<>(); + NoSerializerExistsException error = null; + newDumpLevel("items", "value_type[]"); + for (int i = 0; i < vectorObjectCountItems; i++) { + try { + vals.add(readValue("value", serializers, objectTable, traitsTable, stringTable)); + } catch (NoSerializerExistsException nse) { + vals.add(nse.getIncompleteData()); + for (int j = i + 1; j < vectorObjectCountItems; j++) { + vals.add(BasicType.UNKNOWN); + } + error = nse; + break; + } + } + endDumpLevel(); + VectorObjectType retVectorObject = new VectorObjectType(fixed == 1, objectTypeName, vals); + LOGGER.log(Level.FINER, "Vector value: fixed={0}, size={1}, typeName:{2}]", new Object[]{fixed, vectorObjectCountItems, objectTypeName}); + objectTable.add(retVectorObject); + if (error != null) { + throw new NoSerializerExistsException(error.getClassName(), retVectorObject, error); + } + result = retVectorObject; + } else { + renameU29O_ref(); + + int refIndexVectorObject = (int) (vectorObjectU29 >> 1); + LOGGER.log(Level.FINER, "Vector value: reference({0})", refIndexVectorObject); + result = objectTable.get(refIndexVectorObject); + } + break; + case Marker.DICTIONARY: + renameLastDump("dictionary-marker"); + setDumpInfoType("dictionary-type"); + + long dictionaryObjectU29 = readU29("U29"); + int dictionaryNoRefFlag = (int) (dictionaryObjectU29 & 1); + if (dictionaryNoRefFlag == 1) { + renameU29("U29Dict-value", NO_REFERENCE_BIT_TEXT, "entries count"); + int numEntries = (int) (dictionaryObjectU29 >> 1); + int weakKeys = readU8("weak keys"); + List> data = new ArrayList<>(); + DictionaryType retDictionary = new DictionaryType(weakKeys == 1, data); + objectTable.add(retDictionary); + NoSerializerExistsException error = null; + newDumpLevel("entries", ""); + for (int i = 0; i < numEntries; i++) { + Object key; + Object val; + try { + key = readValue("entry-key", serializers, objectTable, traitsTable, stringTable); + try { + val = readValue("entry-value", serializers, objectTable, traitsTable, stringTable); + } catch (NoSerializerExistsException nse) { + error = nse; + val = BasicType.UNKNOWN; + } + } catch (NoSerializerExistsException nse) { + error = nse; + key = BasicType.UNKNOWN; + val = BasicType.UNKNOWN; + } + + data.add(new Pair<>(key, val)); + if (error != null) { + for (int j = i + 1; j < numEntries; j++) { + data.add(new Pair<>(BasicType.UNKNOWN, BasicType.UNKNOWN)); + } + break; + } + } + endDumpLevel(); + if (error != null) { + throw new NoSerializerExistsException(error.getClassName(), retDictionary, error); + } + result = retDictionary; + } else { + renameU29O_ref(); + int refIndexDictionary = (int) (dictionaryObjectU29 >> 1); + LOGGER.log(Level.FINER, "Dictionary value: reference({0})", refIndexDictionary); + result = objectTable.get(refIndexDictionary); + } + break; + default: + throw new UnsupportedValueTypeException(marker); + } + } finally { + endDumpLevel(); + } + return result; + } + + private static String valToPreviewString(Object v) { + if (v instanceof ObjectType) { + return "{...}"; + } + if (v instanceof ArrayType) { + return "[...]"; + } + if (v instanceof DictionaryType) { + return ""; + } + if (v instanceof BasicType) { + return ""; + } + if (v instanceof DateType) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SS"); + return sdf.format(((DateType) v).toDate()); + } + if (v instanceof String) { + return "\"" + Helper.escapeActionScriptString((String) v) + "\""; + } + return EcmaScript.toString(v); + } + + private class MonitoredInputStream extends InputStream { + + private final InputStream is; + private ByteArrayOutputStream baos; + + public MonitoredInputStream(InputStream is) { + this.is = is; + this.baos = new ByteArrayOutputStream(); + } + + @Override + public int read() throws IOException { + int ret = is.read(); + if (ret > -1) { + baos.write(ret); + } + return ret; + } + + public byte[] getReadData() { + return baos.toByteArray(); + } + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Amf3OutputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Amf3OutputStream.java new file mode 100644 index 000000000..39371a5ae --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Amf3OutputStream.java @@ -0,0 +1,365 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +import com.jpexs.decompiler.flash.amf.amf3.types.ArrayType; +import com.jpexs.decompiler.flash.amf.amf3.types.BasicType; +import com.jpexs.decompiler.flash.amf.amf3.types.ByteArrayType; +import com.jpexs.decompiler.flash.amf.amf3.types.DateType; +import com.jpexs.decompiler.flash.amf.amf3.types.DictionaryType; +import com.jpexs.decompiler.flash.amf.amf3.types.ObjectType; +import com.jpexs.decompiler.flash.amf.amf3.types.VectorDoubleType; +import com.jpexs.decompiler.flash.amf.amf3.types.VectorIntType; +import com.jpexs.decompiler.flash.amf.amf3.types.VectorObjectType; +import com.jpexs.decompiler.flash.amf.amf3.types.VectorUIntType; +import com.jpexs.decompiler.flash.amf.amf3.types.XmlDocType; +import com.jpexs.decompiler.flash.amf.amf3.types.XmlType; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +public class Amf3OutputStream extends OutputStream { + + public final static Logger LOGGER = Logger.getLogger(Amf3OutputStream.class.getName()); + + private final OutputStream os; + private static final int NO_REFERENCE_FLAG = 1; + + private static final int NO_TRAIT_REFERENCE_FLAG = 2; + private static final int TRAIT_EXT_FLAG = 4; + private static final int DYNAMIC_FLAG = 8; + + public Amf3OutputStream(OutputStream os) { + this.os = os; + } + + public void writeU8(int v) throws IOException { + write(v); + } + + public void writeU16(int v) throws IOException { + int b1 = (v >> 8) & 0xff; + int b2 = v & 0xff; + write(b1); + write(b2); + } + + 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); + } + + 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); + } + + public void writeDouble(double v) throws IOException { + writeLong(Double.doubleToLongBits(v)); + } + + public void writeBytes(byte[] data) throws IOException { + os.write(data); + } + + public void writeU29(long v) throws IOException { + v = v & 0x3FFFFFFF; //make unsigned + + final int USE_NEXT_BYTE_FLAG = 0x80; + final int SEVEN_BITS_MASK = 0x7f; + final int EIGHT_BITS_MASK = 0xff; + + if (v <= 0x7F) { + write((int) v); + } else if (v <= 0x3FFF) { + int b1 = (int) ((v >> 7) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG; + int b2 = (int) (v & SEVEN_BITS_MASK); + write(b1); + write(b2); + } else if (v <= 0x1FFFFF) { + int b1 = (int) ((v >> 14) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG; + int b2 = (int) ((v >> 7) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG; + int b3 = (int) (v & SEVEN_BITS_MASK); + write(b1); + write(b2); + write(b3); + } else if (v <= 0x3FFFFFFF) { + int b1 = (int) ((v >> 21) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG; + int b2 = (int) ((v >> 14) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG; + int b3 = (int) ((v >> 7) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG; + int b4 = (int) (v & EIGHT_BITS_MASK); + write(b1); + write(b2); + write(b3); + write(b4); + } else { + throw new IllegalArgumentException("Value too long"); + } + } + + private void writeUtf8Vr(String val, List stringTable) throws IOException { + int stringIndex = stringTable.indexOf(val); + if (stringIndex == -1) { + if (!val.isEmpty()) { + stringTable.add(val); + } + byte data[] = val.getBytes("UTF-8"); + writeU29((data.length << 1) | NO_REFERENCE_FLAG); + writeBytes(data); + } else { + writeU29((stringIndex << 1)); + } + } + + private void writeByteArray(ByteArrayType val, List objectTable) throws IOException { + int objectIndex = objectTable.indexOf(val); + if (objectIndex == -1) { + objectTable.add(val); + byte data[] = val.getData(); + writeU29((data.length << 1) | NO_REFERENCE_FLAG); + writeBytes(data); + } else { + writeU29((objectIndex << 1)); + } + } + + private void writeXmlDoc(XmlDocType val, List objectTable) throws IOException { + int objectIndex = objectTable.indexOf(val); + if (objectIndex == -1) { + objectTable.add(val); + byte data[] = val.getData().getBytes("UTF-8"); + writeU29((data.length << 1) | NO_REFERENCE_FLAG); + writeBytes(data); + } else { + writeU29((objectIndex << 1)); + } + } + + private void writeXml(XmlType val, List objectTable) throws IOException { + int objectIndex = objectTable.indexOf(val); + if (objectIndex == -1) { + objectTable.add(val); + byte data[] = val.getData().getBytes("UTF-8"); + writeU29((data.length << 1) | NO_REFERENCE_FLAG); + writeBytes(data); + } else { + writeU29((objectIndex << 1)); + } + } + + @Override + public void write(int v) throws IOException { + os.write(v); + } + + private void writeArray(ArrayType val, Map serializers, List stringTable, List traitsTable, List objectTable) throws IOException, NoSerializerExistsException { + int objectIndex = objectTable.indexOf(val); + if (objectIndex == -1) { + objectTable.add(val); + + writeU29((val.getDenseValues().size() << 1) | NO_REFERENCE_FLAG); + for (Pair p : val.getAssociativeValues()) { + writeUtf8Vr(p.getFirst(), stringTable); + writeValue(p.getSecond(), serializers, stringTable, traitsTable, objectTable); + } + writeUtf8Vr("", stringTable); + for (Object v : val.getDenseValues()) { + writeValue(v, serializers, stringTable, traitsTable, objectTable); + } + } else { + writeU29((objectIndex << 1)); + } + } + + private void writeObject(ObjectType val, Map serializers, List stringTable, List traitsTable, List objectTable) throws IOException, NoSerializerExistsException { + int objectIndex = objectTable.indexOf(val); + if (objectIndex == -1) { + objectTable.add(val); + Traits traits = val.getTraits(); + int traitsIndex = traitsTable.indexOf(traits); + if (traitsIndex == -1) { + if (val.isSerialized()) { + writeU29(NO_REFERENCE_FLAG | NO_TRAIT_REFERENCE_FLAG | TRAIT_EXT_FLAG); + writeUtf8Vr(val.getClassName(), stringTable); + if (serializers.containsKey(val.getClassName())) { + serializers.get(val.getClassName()).writeObject(val.getSerializedMembers(), os); + } else if (val.getSerializedData() != null) { + writeBytes(val.getSerializedData()); + } else { + throw new NoSerializerExistsException(val.getClassName(), null, null); + } + } else { + traitsTable.add(traits); + writeU29((val.getSealedMembers().size() << 4) | NO_REFERENCE_FLAG | NO_TRAIT_REFERENCE_FLAG | (traits.isDynamic() ? DYNAMIC_FLAG : 0)); + writeUtf8Vr(val.getClassName(), stringTable); + for (Pair v : val.getSealedMembers()) { + writeUtf8Vr(v.getFirst(), stringTable); + } + } + } else { + writeU29((traitsIndex << 2) | NO_REFERENCE_FLAG); + } + for (Pair v : val.getSealedMembers()) { + writeValue(v.getSecond(), serializers, stringTable, traitsTable, objectTable); + } + if (traits.isDynamic()) { + for (Pair v : val.getDynamicMembers()) { + writeUtf8Vr(v.getFirst(), stringTable); + writeValue(v.getSecond(), serializers, stringTable, traitsTable, objectTable); + } + writeUtf8Vr("", stringTable); + } + } else { + writeU29((objectIndex << 1)); + } + } + + public void writeValue(Object object) throws IOException, NoSerializerExistsException { + writeValue(object, new HashMap<>()); + } + + public void writeValue(Object object, Map serializers) throws IOException, NoSerializerExistsException { + writeValue(object, serializers, new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + } + + private void writeValue(Object object, Map serializers, List stringTable, List traitsTable, List objectTable) throws IOException, NoSerializerExistsException { + if (object == BasicType.UNDEFINED) { + writeU8(Marker.UNDEFINED); + } else if (object == BasicType.NULL) { + writeU8(Marker.NULL); + } else if (object == Boolean.FALSE) { + writeU8(Marker.FALSE); + } else if (object == Boolean.TRUE) { + writeU8(Marker.TRUE); + } else if (object == BasicType.UNKNOWN) { + //Nothing + } else if (object instanceof Long) { + writeU8(Marker.INTEGER); + writeU29((Long) object); + } else if (object instanceof Double) { + writeU8(Marker.DOUBLE); + writeDouble((Double) object); + } else if (object instanceof String) { + writeU8(Marker.STRING); + writeUtf8Vr((String) object, stringTable); + } else if (object instanceof XmlDocType) { + writeU8(Marker.XML_DOC); + writeXmlDoc((XmlDocType) object, objectTable); + } else if (object instanceof DateType) { + writeU8(Marker.DATE); + int dateIndex = objectTable.indexOf(object); + DateType val = (DateType) object; + if (dateIndex == -1) { + objectTable.add(val); + writeU29(NO_REFERENCE_FLAG); + writeDouble(val.getVal()); + } else { + writeU29(dateIndex << 1); + } + } else if (object instanceof ArrayType) { + writeU8(Marker.ARRAY); + writeArray((ArrayType) object, serializers, stringTable, traitsTable, objectTable); + } else if (object instanceof ObjectType) { + writeU8(Marker.OBJECT); + writeObject((ObjectType) object, serializers, stringTable, traitsTable, objectTable); + } else if (object instanceof XmlType) { + writeU8(Marker.XML); + writeXml((XmlType) object, objectTable); + } else if (object instanceof ByteArrayType) { + writeU8(Marker.BYTE_ARRAY); + writeByteArray((ByteArrayType) object, objectTable); + } else if (object instanceof VectorIntType) { + writeU8(Marker.VECTOR_INT); + + int vectorIndex = objectTable.indexOf(object); + VectorIntType val = (VectorIntType) object; + if (vectorIndex == -1) { + objectTable.add(val); + writeU29((val.getValues().size() << 1) | NO_REFERENCE_FLAG); + writeU8(val.isFixed() ? 1 : 0); + for (long v : val.getValues()) { + writeU32(v); + } + } else { + writeU29(vectorIndex << 1); + } + } else if (object instanceof VectorUIntType) { + writeU8(Marker.VECTOR_UINT); + int vectorIndex = objectTable.indexOf(object); + VectorUIntType val = (VectorUIntType) object; + if (vectorIndex == -1) { + objectTable.add(val); + writeU29((val.getValues().size() << 1) | NO_REFERENCE_FLAG); + writeU8(val.isFixed() ? 1 : 0); + for (long v : val.getValues()) { + writeU32(v); + } + } else { + writeU29(vectorIndex << 1); + } + } else if (object instanceof VectorDoubleType) { + writeU8(Marker.VECTOR_DOUBLE); + int vectorIndex = objectTable.indexOf(object); + VectorDoubleType val = (VectorDoubleType) object; + if (vectorIndex == -1) { + objectTable.add(val); + writeU29((val.getValues().size() << 1) | NO_REFERENCE_FLAG); + writeU8(val.isFixed() ? 1 : 0); + for (double v : val.getValues()) { + writeDouble(v); + } + } else { + writeU29(vectorIndex << 1); + } + } else if (object instanceof VectorObjectType) { + writeU8(Marker.VECTOR_OBJECT); + int vectorIndex = objectTable.indexOf(object); + VectorObjectType val = (VectorObjectType) object; + if (vectorIndex == -1) { + objectTable.add(val); + writeU29((val.getValues().size() << 1) | NO_REFERENCE_FLAG); + writeU8(val.isFixed() ? 1 : 0); + writeUtf8Vr(val.getTypeName(), stringTable); + for (Object v : val.getValues()) { + writeValue(v, serializers, stringTable, traitsTable, objectTable); + } + } else { + writeU29(vectorIndex << 1); + } + } else if (object instanceof DictionaryType) { + writeU8(Marker.DICTIONARY); + int dictionaryIndex = objectTable.indexOf(object); + DictionaryType val = (DictionaryType) object; + if (dictionaryIndex == -1) { + objectTable.add(val); + writeU29((val.getPairs().size() << 1) | NO_REFERENCE_FLAG); + writeU8(val.hasWeakKeys() ? 1 : 0); + for (Pair p : val.getPairs()) { + writeValue(p.getFirst(), serializers, stringTable, traitsTable, objectTable); + writeValue(p.getSecond(), serializers, stringTable, traitsTable, objectTable); + } + } else { + writeU29(dictionaryIndex << 1); + } + } else { + throw new UnsupportedValueTypeException(object.getClass()); + } + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Amf3Value.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Amf3Value.java new file mode 100644 index 000000000..9da5c62a2 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Amf3Value.java @@ -0,0 +1,58 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +import com.jpexs.decompiler.flash.amf.amf3.types.Amf3ValueType; +import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter; + +public class Amf3Value { + + private Object value; + + public Amf3Value() { + setValue(null); + } + + public Amf3Value(Object value) { + setValue(value); + } + + public void setValue(Object value) { + if (!isValueValid(value)) { + throw new IllegalArgumentException("Invalid Amf value: " + value.getClass().getSimpleName()); + } + this.value = value; + } + + public static boolean isValueValid(Object value) { + if (value == null) { + return true; + } + if (value instanceof Long) { + return true; + } + if (value instanceof Double) { + return true; + } + if (value instanceof String) { + return true; + } + if (value instanceof Boolean) { + return true; + } + if (value instanceof Amf3ValueType) { + return true; + } + return false; + } + + public Object getValue() { + return value; + } + + @Override + public String toString() { + if (value == null) { + return ""; + } + return Amf3Exporter.amfToString(value); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Marker.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Marker.java new file mode 100644 index 000000000..2ace21d0e --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Marker.java @@ -0,0 +1,23 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +public class Marker { + + public static final int UNDEFINED = 0x00; + public static final int NULL = 0x01; + public static final int FALSE = 0x02; + public static final int TRUE = 0x03; + public static final int INTEGER = 0x04; + public static final int DOUBLE = 0x05; + public static final int STRING = 0x06; + public static final int XML_DOC = 0x07; + public static final int DATE = 0x08; + public static final int ARRAY = 0x09; + public static final int OBJECT = 0x0A; + public static final int XML = 0x0B; + public static final int BYTE_ARRAY = 0x0C; + public static final int VECTOR_INT = 0x0D; + public static final int VECTOR_UINT = 0x0E; + public static final int VECTOR_DOUBLE = 0x0F; + public static final int VECTOR_OBJECT = 0x10; + public static final int DICTIONARY = 0x11; +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/NoSerializerExistsException.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/NoSerializerExistsException.java new file mode 100644 index 000000000..f4cd492f8 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/NoSerializerExistsException.java @@ -0,0 +1,25 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +public class NoSerializerExistsException extends Exception { + + private final String className; + private final Object incompleteData; + + /*public NoSerializerExistsException(String className, Object incompleteData) { + this(className, incompleteData, null); + }*/ + public NoSerializerExistsException(String className, Object incompleteData, Throwable cause) { + super("Cannot read AMF - no deserializer defined for class \"" + className + "\".", cause); + this.className = className; + this.incompleteData = incompleteData; + } + + public String getClassName() { + return className; + } + + public Object getIncompleteData() { + return incompleteData; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/ObjectTypeSerializeHandler.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/ObjectTypeSerializeHandler.java new file mode 100644 index 000000000..1acbc0b71 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/ObjectTypeSerializeHandler.java @@ -0,0 +1,13 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +public interface ObjectTypeSerializeHandler { + + public List> readObject(String className, InputStream is) throws IOException; + + public void writeObject(List> members, OutputStream os) throws IOException; +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Pair.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Pair.java new file mode 100644 index 000000000..81e7dd7b1 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Pair.java @@ -0,0 +1,28 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +public class Pair { + + private T1 first; + private T2 second; + + public Pair(T1 first, T2 second) { + this.first = first; + this.second = second; + } + + public T1 getFirst() { + return first; + } + + public T2 getSecond() { + return second; + } + + public void setSecond(T2 second) { + this.second = second; + } + + public void setFirst(T1 first) { + this.first = first; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Traits.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Traits.java new file mode 100644 index 000000000..2ebd59327 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/Traits.java @@ -0,0 +1,41 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +import java.util.List; + +public class Traits { + + private String className; + private boolean dynamic; + private List sealedMemberNames; + + public Traits(String className, boolean dynamic, List sealedMemberNames) { + this.className = className; + this.dynamic = dynamic; + this.sealedMemberNames = sealedMemberNames; + } + + public String getClassName() { + return className; + } + + public boolean isDynamic() { + return dynamic; + } + + public List getSealedMemberNames() { + return sealedMemberNames; + } + + public void setClassName(String className) { + this.className = className; + } + + public void setDynamic(boolean dynamic) { + this.dynamic = dynamic; + } + + public void setSealedMemberNames(List sealedMemberNames) { + this.sealedMemberNames = sealedMemberNames; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/UnsupportedValueTypeException.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/UnsupportedValueTypeException.java new file mode 100644 index 000000000..30717768e --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/UnsupportedValueTypeException.java @@ -0,0 +1,26 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +public class UnsupportedValueTypeException extends RuntimeException { + + private Integer marker = null; + private Class cls = null; + + public UnsupportedValueTypeException(Class cls) { + super("Unsupported type of value - class: " + cls.getSimpleName()); + this.cls = cls; + } + + public UnsupportedValueTypeException(int marker) { + super("Unsupported type of value - marker: 0x" + Integer.toHexString(marker)); + this.marker = marker; + } + + public Integer getMarker() { + return marker; + } + + public Class getUnsupportedClass() { + return this.cls; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/WithSubValues.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/WithSubValues.java new file mode 100644 index 000000000..4baa7973c --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/WithSubValues.java @@ -0,0 +1,8 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +import java.util.List; + +public interface WithSubValues { + + public List getSubValues(); +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/AbstractVectorType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/AbstractVectorType.java new file mode 100644 index 000000000..9449e7049 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/AbstractVectorType.java @@ -0,0 +1,40 @@ +package com.jpexs.decompiler.flash.amf.amf3.types; + +import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter; +import java.util.ArrayList; +import java.util.List; +import com.jpexs.decompiler.flash.amf.amf3.WithSubValues; + +public abstract class AbstractVectorType implements WithSubValues, Amf3ValueType { + + private boolean fixed; + private List values; + + public boolean isFixed() { + return fixed; + } + + public AbstractVectorType(boolean fixed, List values) { + this.values = values; + this.fixed = fixed; + } + + public List getValues() { + return values; + } + + @Override + public List getSubValues() { + List ret = new ArrayList<>(); + ret.addAll(values); + return ret; + } + + public abstract String getTypeName(); + + @Override + public String toString() { + return Amf3Exporter.amfToString(this); + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/Amf3ValueType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/Amf3ValueType.java new file mode 100644 index 000000000..9cbd90d01 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/Amf3ValueType.java @@ -0,0 +1,5 @@ +package com.jpexs.decompiler.flash.amf.amf3.types; + +public interface Amf3ValueType { + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/ArrayType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/ArrayType.java new file mode 100644 index 000000000..637219def --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/ArrayType.java @@ -0,0 +1,46 @@ +package com.jpexs.decompiler.flash.amf.amf3.types; + +import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter; +import com.jpexs.decompiler.flash.amf.amf3.Pair; +import com.jpexs.helpers.Helper; +import java.util.ArrayList; +import java.util.List; +import com.jpexs.decompiler.flash.amf.amf3.WithSubValues; + +public class ArrayType implements WithSubValues, Amf3ValueType { + + private List denseValues; + private List> associativeValues; + + public ArrayType(List denseValues, List> associativeValues) { + this.denseValues = denseValues; + this.associativeValues = associativeValues; + } + + public List getDenseValues() { + return denseValues; + } + + public List> getAssociativeValues() { + return associativeValues; + } + + @Override + public String toString() { + return Amf3Exporter.amfToString(this); + } + + @Override + public List getSubValues() { + List ret = new ArrayList<>(); + for (Pair p : associativeValues) { + ret.add(p.getFirst()); + ret.add(p.getSecond()); + } + for (Object v : denseValues) { + ret.add(v); + } + return ret; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/BasicType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/BasicType.java new file mode 100644 index 000000000..6fc6866c9 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/BasicType.java @@ -0,0 +1,26 @@ +package com.jpexs.decompiler.flash.amf.amf3.types; + +public enum BasicType implements Amf3ValueType { + NULL { + @Override + public String toString() { + return "null"; + } + + }, + UNDEFINED { + @Override + public String toString() { + return "undefined"; + } + + }, + //Special types for errors while reading + UNKNOWN { + @Override + public String toString() { + return "unknown"; + } + + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/ByteArrayType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/ByteArrayType.java new file mode 100644 index 000000000..57699226c --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/ByteArrayType.java @@ -0,0 +1,22 @@ +package com.jpexs.decompiler.flash.amf.amf3.types; + +import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter; + +public class ByteArrayType { + + private byte[] data; + + public ByteArrayType(byte[] data) { + this.data = data; + } + + public byte[] getData() { + return data; + } + + @Override + public String toString() { + return Amf3Exporter.amfToString(this); + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/DateType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/DateType.java new file mode 100644 index 000000000..e481a3cc9 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/DateType.java @@ -0,0 +1,30 @@ +package com.jpexs.decompiler.flash.amf.amf3.types; + +import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter; +import java.util.Date; + +public class DateType implements Amf3ValueType { + + private double val; + + public DateType(double val) { + this.val = val; + } + + public double getVal() { + return val; + } + + public void setVal(double val) { + this.val = val; + } + + @Override + public String toString() { + return Amf3Exporter.amfToString(this); + } + + public Date toDate() { + return new Date((long) val); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/DictionaryType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/DictionaryType.java new file mode 100644 index 000000000..5acd6a9dd --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/DictionaryType.java @@ -0,0 +1,42 @@ +package com.jpexs.decompiler.flash.amf.amf3.types; + +import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter; +import com.jpexs.decompiler.flash.amf.amf3.Pair; +import java.util.ArrayList; +import java.util.List; +import com.jpexs.decompiler.flash.amf.amf3.WithSubValues; + +public class DictionaryType implements WithSubValues, Amf3ValueType { + + private boolean weakKeys; + private List> pairs; + + public DictionaryType(boolean weakKeys, List> pairs) { + this.weakKeys = weakKeys; + this.pairs = pairs; + } + + public List> getPairs() { + return pairs; + } + + @Override + public List getSubValues() { + List ret = new ArrayList<>(); + for (Pair p : pairs) { + ret.add(p.getFirst()); + ret.add(p.getSecond()); + } + return ret; + } + + @Override + public String toString() { + return Amf3Exporter.amfToString(this); + } + + public boolean hasWeakKeys() { + return weakKeys; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/ObjectType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/ObjectType.java new file mode 100644 index 000000000..98cf21353 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/ObjectType.java @@ -0,0 +1,103 @@ +package com.jpexs.decompiler.flash.amf.amf3.types; + +import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter; +import com.jpexs.decompiler.flash.amf.amf3.Pair; +import com.jpexs.decompiler.flash.amf.amf3.Traits; +import com.jpexs.helpers.Helper; +import java.util.ArrayList; +import java.util.List; +import com.jpexs.decompiler.flash.amf.amf3.WithSubValues; + +public class ObjectType implements WithSubValues, Amf3ValueType { + + private List> sealedMembers; + private List> dynamicMembers; + private List> serializedMembers; + //null = not serialized or unknown + private byte[] serializedData = null; + private boolean serialized; + private Traits traits; + + public boolean isSerialized() { + return serialized; + } + + public Traits getTraits() { + return traits; + } + + public ObjectType(Traits traits, byte[] serializedData, List> serializedMembers) { + this.traits = traits; + this.serializedData = serializedData; + this.serializedMembers = serializedMembers; + this.dynamicMembers = new ArrayList<>(); + this.sealedMembers = new ArrayList<>(); + this.serialized = true; + } + + public ObjectType(Traits traits, List> sealedMembers, List> dynamicMembers) { + this.sealedMembers = sealedMembers; + this.dynamicMembers = dynamicMembers; + this.serializedMembers = new ArrayList<>(); + this.serialized = false; + this.traits = traits; + } + + public boolean isDynamic() { + return traits.isDynamic(); + } + + public List> getDynamicMembers() { + return dynamicMembers; + } + + public List> getSealedMembers() { + return sealedMembers; + } + + public String getClassName() { + return traits.getClassName(); + } + + @Override + public List getSubValues() { + List ret = new ArrayList<>(); + for (Pair p : dynamicMembers) { + ret.add(p.getFirst()); + ret.add(p.getSecond()); + } + for (Pair p : sealedMembers) { + ret.add(p.getFirst()); + ret.add(p.getSecond()); + } + + for (Pair p : serializedMembers) { + ret.add(p.getFirst()); + ret.add(p.getSecond()); + } + + return ret; + } + + @Override + public String toString() { + return Amf3Exporter.amfToString(this); + } + + public void setSerializedData(byte[] serializedData) { + this.serializedData = serializedData; + } + + public byte[] getSerializedData() { + return serializedData; + } + + public void setSerializedMembers(List> serializedMembers) { + this.serializedMembers = serializedMembers; + } + + public List> getSerializedMembers() { + return serializedMembers; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/VectorDoubleType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/VectorDoubleType.java new file mode 100644 index 000000000..6755a08db --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/VectorDoubleType.java @@ -0,0 +1,16 @@ +package com.jpexs.decompiler.flash.amf.amf3.types; + +import java.util.List; + +public class VectorDoubleType extends AbstractVectorType { + + public VectorDoubleType(boolean fixed, List values) { + super(fixed, values); + } + + @Override + public String getTypeName() { + return "Number"; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/VectorIntType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/VectorIntType.java new file mode 100644 index 000000000..2841545a8 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/VectorIntType.java @@ -0,0 +1,16 @@ +package com.jpexs.decompiler.flash.amf.amf3.types; + +import java.util.List; + +public class VectorIntType extends AbstractVectorType { + + public VectorIntType(boolean fixed, List values) { + super(fixed, values); + } + + @Override + public String getTypeName() { + return "int"; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/VectorObjectType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/VectorObjectType.java new file mode 100644 index 000000000..98bad08cb --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/VectorObjectType.java @@ -0,0 +1,21 @@ +package com.jpexs.decompiler.flash.amf.amf3.types; + +import com.jpexs.helpers.Helper; +import java.util.ArrayList; +import java.util.List; + +public class VectorObjectType extends AbstractVectorType { + + private String typeName; + + public VectorObjectType(boolean fixed, String typeName, List values) { + super(fixed, values); + this.typeName = typeName; + } + + @Override + public String getTypeName() { + return typeName; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/VectorUIntType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/VectorUIntType.java new file mode 100644 index 000000000..a130fd3ac --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/VectorUIntType.java @@ -0,0 +1,17 @@ +package com.jpexs.decompiler.flash.amf.amf3.types; + +import java.util.ArrayList; +import java.util.List; + +public class VectorUIntType extends AbstractVectorType { + + public VectorUIntType(boolean fixed, List values) { + super(fixed, values); + } + + @Override + public String getTypeName() { + return "uint"; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/XmlDocType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/XmlDocType.java new file mode 100644 index 000000000..20fddf116 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/XmlDocType.java @@ -0,0 +1,24 @@ +package com.jpexs.decompiler.flash.amf.amf3.types; + +public class XmlDocType implements Amf3ValueType { + + private String data; + + public XmlDocType(String data) { + this.data = data; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + @Override + public String toString() { + return data; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/XmlType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/XmlType.java new file mode 100644 index 000000000..43beffc45 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/amf/amf3/types/XmlType.java @@ -0,0 +1,24 @@ +package com.jpexs.decompiler.flash.amf.amf3.types; + +public class XmlType implements Amf3ValueType { + + private String data; + + public XmlType(String data) { + this.data = data; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + @Override + public String toString() { + return data; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/amf/amf3/Amf3Exporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/amf/amf3/Amf3Exporter.java new file mode 100644 index 000000000..afe62a3c2 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/amf/amf3/Amf3Exporter.java @@ -0,0 +1,274 @@ +package com.jpexs.decompiler.flash.exporters.amf.amf3; + +import com.jpexs.decompiler.flash.amf.amf3.Pair; +import com.jpexs.decompiler.flash.amf.amf3.WithSubValues; +import com.jpexs.decompiler.flash.amf.amf3.types.ArrayType; +import com.jpexs.decompiler.flash.amf.amf3.types.XmlType; +import com.jpexs.decompiler.flash.amf.amf3.types.ObjectType; +import com.jpexs.decompiler.flash.amf.amf3.types.AbstractVectorType; +import com.jpexs.decompiler.flash.amf.amf3.types.XmlDocType; +import com.jpexs.decompiler.flash.amf.amf3.types.ByteArrayType; +import com.jpexs.decompiler.flash.amf.amf3.types.DateType; +import com.jpexs.decompiler.flash.amf.amf3.types.DictionaryType; +import com.jpexs.decompiler.flash.amf.amf3.types.BasicType; +import com.jpexs.decompiler.flash.ecma.EcmaScript; +import com.jpexs.helpers.Helper; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Amf3Exporter { + + /** + * Converts AMF value to something human-readable. + * + * @param amfValue + * @return + */ + public static String amfToString(Object amfValue) { + Map refCount = new HashMap<>(); + List objectList = new ArrayList<>(); + Map objectAlias = new HashMap<>(); + populateObjects(amfValue, refCount, objectList, objectAlias); + return amfToString(new ArrayList<>(), 0, amfValue, refCount, objectAlias); + } + + /** + * 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 referenceCount, List objectList, Map objectAlias) { + if (((List) Arrays.asList(String.class, Long.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 WithSubValues) { + List subvalues = ((WithSubValues) object).getSubValues(); + for (Object o : subvalues) { + populateObjects(o, referenceCount, objectList, objectAlias); + } + } + objectList.add(object); + objectAlias.put(object, "obj" + objectList.size()); + } + } + + private static String indent(int level) { + String na = ""; + for (int i = 0; i < level; i++) { + na += " "; + } + return na; + } + + /** + * Processes one level of object and converts it to string + * + * @param processedObjects + * @param level + * @param object + * @param referenceCount + * @param objectAlias + * @return + */ + private static String amfToString(List processedObjects, int level, Object object, Map referenceCount, Map objectAlias) { + if (object instanceof String) { + return "\"" + Helper.escapeActionScriptString((String) object) + "\""; + } + if (((List) Arrays.asList(Long.class, Double.class, Boolean.class)).contains(object.getClass())) { + return EcmaScript.toString(object); + } + + if (object instanceof BasicType) { + return object.toString(); + } + + StringBuilder ret = new StringBuilder(); + + Integer refCount = referenceCount.get(object); + if (refCount > 1 && processedObjects.contains(object)) { + ret.append("#").append(objectAlias.get(object)); + return ret.toString(); + } + processedObjects.add(object); + + String addId = refCount > 1 ? indent(level + 1) + "\"id\": \"" + objectAlias.get(object) + "\",\r\n" : ""; + + if (object instanceof AbstractVectorType) { + AbstractVectorType avt = (AbstractVectorType) object; + ret.append("{\r\n"); + ret.append(indent(level + 1)).append("\"type\": \"Vector\",\r\n"); + ret.append(addId); + ret.append(indent(level + 1)).append("\"fixed\": ").append(avt.isFixed()).append(",\r\n"); + ret.append(indent(level + 1)).append("\"subtype\": ").append(amfToString(processedObjects, level, avt.getTypeName(), referenceCount, objectAlias)).append(",\r\n"); + ret.append(indent(level + 1)).append("\"values\": ["); + for (int i = 0; i < avt.getValues().size(); i++) { + if (i > 0) { + ret.append(", "); + } + ret.append(amfToString(processedObjects, level + 1, avt.getValues().get(i), referenceCount, objectAlias)); + } + ret.append("]\r\n"); + ret.append(indent(level)).append("}"); + } else if (object instanceof ObjectType) { + ObjectType ot = (ObjectType) object; + ret.append("{\r\n"); + ret.append(indent(level + 1)).append("\"type\": \"Object\",\r\n"); + ret.append(addId); + ret.append(indent(level + 1)).append("\"className\": ").append(amfToString(processedObjects, level, ot.getClassName(), referenceCount, objectAlias)).append(",\r\n"); + if (ot.isSerialized()) { + byte[] serData = ot.getSerializedData(); + if (serData == null) { + ret.append(indent(level + 1)).append("\"serialized\": unknown\r\n"); + } else { + ret.append(indent(level + 1)).append("\"serialized\": \"").append(javax.xml.bind.DatatypeConverter.printHexBinary(serData)).append("\",\r\n"); + if (!ot.getSerializedMembers().isEmpty()) { + ret.append(indent(level + 1)).append("\"unserializedMembers\": {\r\n"); + for (int i = 0; i < ot.getSerializedMembers().size(); i++) { + Pair member = ot.getSerializedMembers().get(i); + ret.append(indent(level + 2)).append(amfToString(processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias)).append(":").append(amfToString(processedObjects, level + 1, member.getSecond(), referenceCount, objectAlias)); + if (i < ot.getSerializedMembers().size() - 1) { + ret.append(",\r\n"); + } else { + ret.append("\r\n"); + } + } + ret.append(indent(level + 1)).append("}"); + ret.append("\r\n"); + } + } + } else { + ret.append(indent(level + 1)).append("\"dynamic\": ").append(ot.isDynamic()).append(",\r\n"); + //if (!ot.getSealedMembers().isEmpty()) { + ret.append(indent(level + 1)).append("\"sealedMembers\": {\r\n"); + for (int i = 0; i < ot.getSealedMembers().size(); i++) { + Pair member = ot.getSealedMembers().get(i); + ret.append(indent(level + 2)).append(amfToString(processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias)).append(":").append(amfToString(processedObjects, level + 1, member.getSecond(), referenceCount, objectAlias)); + if (i < ot.getSealedMembers().size() - 1) { + ret.append(",\r\n"); + } else { + ret.append("\r\n"); + } + } + ret.append(indent(level + 1)).append("}"); + //if (!ot.getDynamicMembers().isEmpty()) { + ret.append(","); + //} + ret.append("\r\n"); + //} + //if (!ot.getDynamicMembers().isEmpty()) { + ret.append(indent(level + 1)).append("\"dynamicMembers\": {\r\n"); + for (int i = 0; i < ot.getDynamicMembers().size(); i++) { + Pair member = ot.getDynamicMembers().get(i); + ret.append(indent(level + 2)).append(amfToString(processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias)); + ret.append(":"); + ret.append(amfToString(processedObjects, level + 2, member.getSecond(), referenceCount, objectAlias)); + if (i < ot.getDynamicMembers().size() - 1) { + ret.append(","); + } + ret.append("\r\n"); + } + ret.append(indent(level + 1)).append("}\r\n"); + //} + } + ret.append(indent(level)).append("}"); + } else if (object instanceof ArrayType) { + ArrayType at = (ArrayType) object; + ret.append("{\r\n"); + ret.append(indent(level + 1)).append("\"type\": \"Array\",\r\n"); + ret.append(addId); + ret.append(indent(level + 1)).append("\"denseValues\": ["); + + for (int i = 0; i < at.getDenseValues().size(); i++) { + if (i > 0) { + ret.append(", "); + } + ret.append(amfToString(processedObjects, level + 2, at.getDenseValues().get(i), referenceCount, objectAlias)); + } + ret.append("],\r\n"); + ret.append(indent(level + 1)).append("\"associativeValues\": {"); + if (!at.getAssociativeValues().isEmpty()) { + ret.append("\r\n"); + } + for (int i = 0; i < at.getAssociativeValues().size(); i++) { + Pair p = at.getAssociativeValues().get(i); + ret.append(indent(level + 2)).append(amfToString(processedObjects, level + 1, p.getFirst(), referenceCount, objectAlias)).append(" : ").append(amfToString(processedObjects, level + 1, p.getSecond(), referenceCount, objectAlias)); + if (i < at.getAssociativeValues().size() - 1) { + ret.append(","); + } + ret.append("\r\n"); + } + if (!at.getAssociativeValues().isEmpty()) { + ret.append(indent(level + 1)); + } + ret.append("}\r\n"); + ret.append(indent(level)).append("}"); + } else if (object instanceof DictionaryType) { + DictionaryType dt = (DictionaryType) object; + ret.append("{\r\n"); + ret.append(indent(level + 1)).append("\"type\": \"Dictionary\",\r\n"); + ret.append(addId); + ret.append(indent(level + 1)).append("\"weakKeys\": ").append(dt.hasWeakKeys()).append(",\r\n"); + ret.append(indent(level + 1)).append("\"entries\": {\r\n"); + for (int i = 0; i < dt.getPairs().size(); i++) { + Pair pair = dt.getPairs().get(i); + ret.append(indent(level + 1)).append(amfToString(processedObjects, level + 1, pair.getFirst(), referenceCount, objectAlias)).append(" : ").append(amfToString(processedObjects, level + 1, pair.getSecond(), referenceCount, objectAlias)); + if (i < dt.getPairs().size() - 1) { + ret.append(","); + } + ret.append("\r\n"); + } + ret.append(indent(level + 1)).append("}\r\n"); + ret.append(indent(level)).append("}"); + } else if (object instanceof ByteArrayType) { + ByteArrayType ba = (ByteArrayType) object; + byte data[] = ba.getData(); + return "{\r\n" + + indent(level + 1) + "\"type\": \"ByteArray\",\r\n" + + addId + + indent(level + 1) + "\"value\": \"" + javax.xml.bind.DatatypeConverter.printHexBinary(data) + "\"\r\n" + + indent(level) + "}"; + } else if (object instanceof DateType) { + DateType dt = (DateType) object; + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SS"); + return "{\r\n" + + indent(level + 1) + "\"type\": \"Date\",\r\n" + + addId + + indent(level + 1) + "\"value\": " + amfToString(processedObjects, level, sdf.format(new Date((long) dt.getVal())), referenceCount, objectAlias) + "\r\n" + + indent(level) + "}"; + + } else if (object instanceof XmlDocType) { + return "{\r\n" + + indent(level + 1) + "\"type\": \"XMLDocument\",\r\n" + + addId + + indent(level + 1) + "\"value\": " + amfToString(processedObjects, level, ((XmlDocType) object).getData(), referenceCount, objectAlias) + "\r\n" + + indent(level) + "}"; + } else if (object instanceof XmlType) { + return "{\r\n" + + indent(level + 1) + "\"type\": \"XML\",\r\n" + + addId + + indent(level + 1) + "\"value\": " + amfToString(processedObjects, level, ((XmlType) object).getData(), referenceCount, objectAlias) + "\r\n" + + indent(level) + "}"; + } else { + throw new IllegalArgumentException("Unsupported type: " + object.getClass()); + } + + return ret.toString(); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/Amf3Importer.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/Amf3Importer.java new file mode 100644 index 000000000..e21bd342f --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/Amf3Importer.java @@ -0,0 +1,556 @@ +package com.jpexs.decompiler.flash.importers.amf.amf3; + +import com.jpexs.decompiler.flash.amf.amf3.Pair; +import com.jpexs.decompiler.flash.amf.amf3.Traits; +import com.jpexs.decompiler.flash.amf.amf3.types.ArrayType; +import com.jpexs.decompiler.flash.amf.amf3.types.BasicType; +import com.jpexs.decompiler.flash.amf.amf3.types.ByteArrayType; +import com.jpexs.decompiler.flash.amf.amf3.types.DateType; +import com.jpexs.decompiler.flash.amf.amf3.types.DictionaryType; +import com.jpexs.decompiler.flash.amf.amf3.types.ObjectType; +import com.jpexs.decompiler.flash.amf.amf3.types.VectorDoubleType; +import com.jpexs.decompiler.flash.amf.amf3.types.VectorIntType; +import com.jpexs.decompiler.flash.amf.amf3.types.VectorObjectType; +import com.jpexs.decompiler.flash.amf.amf3.types.VectorUIntType; +import com.jpexs.decompiler.flash.amf.amf3.types.XmlDocType; +import com.jpexs.decompiler.flash.amf.amf3.types.XmlType; +import java.io.IOException; +import java.io.StringReader; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Amf3Importer { + + private Amf3Lexer lexer; + + private ParsedSymbol lex() throws IOException, Amf3ParseException { + ParsedSymbol ret = lexer.lex(); + return ret; + } + + private void pushback(ParsedSymbol s) { + lexer.pushback(s); + } + + private void expected(ParsedSymbol symb, int line, Object... expected) throws IOException, Amf3ParseException { + boolean found = false; + for (Object t : expected) { + if (symb.type == t) { + found = true; + } + if (symb.group == t) { + found = true; + } + } + if (!found) { + String expStr = ""; + boolean first = true; + for (Object e : expected) { + if (!first) { + expStr += " or "; + } + expStr += e; + first = false; + } + throw new Amf3ParseException("" + expStr + " expected but " + symb.type + " found", line); + } + } + + private ParsedSymbol expectedType(Object... type) throws IOException, Amf3ParseException { + ParsedSymbol symb = lex(); + expected(symb, lexer.yyline(), type); + return symb; + } + + private JsArray parseArray(Map objectTable) throws IOException, Amf3ParseException { + expectedType(SymbolType.BRACKET_OPEN); + List 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 values = new ArrayList<>(); + + public JsArray() { + } + + public JsArray(List values) { + this.values = values; + } + + public void add(Object value) { + values.add(value); + } + + public List getValues() { + return values; + } + + } + + private class JsObject { + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("{"); + for (Pair p : values) { + sb.append(p.getFirst()).append(":").append("?").append(",\r\n"); + } + sb.append("}"); + return sb.toString(); + } + + private final List> values = new ArrayList<>(); + + public void add(Object key, Object value) { + values.add(new Pair<>(key, value)); + } + + public String getString(Object key) throws Amf3ParseException { + return (String) getRequired(key, "String"); + } + + public Boolean getBoolean(Object key) throws Amf3ParseException { + return (Boolean) getRequired(key, "Boolean"); + } + + public JsObject getJsObject(Object key) throws Amf3ParseException { + return (JsObject) getRequired(key, "JsObject"); + } + + public JsArray getJsArray(Object key) throws Amf3ParseException { + return (JsArray) getRequired(key, "JsArray"); + } + + public List getJsArrayOfObject(Object key) throws Amf3ParseException { + return getJsArray(key).getValues(); + } + + @SuppressWarnings("unchecked") + public List getJsArrayOfString(Object key) throws Amf3ParseException { + return (List) getJsArray(key, "String"); + } + + @SuppressWarnings("unchecked") + public List getJsArrayOfInt(Object key) throws Amf3ParseException { + return (List) getJsArray(key, "int"); + } + + @SuppressWarnings("unchecked") + + public List getJsArrayOfUint(Object key) throws Amf3ParseException { + return (List) getJsArray(key, "uint"); + } + + @SuppressWarnings("unchecked") + public List getJsArrayOfNumber(Object key) throws Amf3ParseException { + return (List) getJsArray(key, "Number"); + } + + public List getJsArray(Object key, String valueType) throws Amf3ParseException { + JsArray jsArr = (JsArray) getRequired(key, "JsArray"); + switch (valueType) { + case "String": + List 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 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 doubleList = new ArrayList<>(); + for (Object v : jsArr.getValues()) { + Double cv = null; + if (v instanceof Long) { + cv = (double) (long) (Long) v; + } else if (v instanceof Double) { + cv = (Double) v; + } else { + throw new Amf3ParseException("Not a Number: " + v, 0); + } + doubleList.add(cv); + } + return doubleList; + default: + throw new Amf3ParseException("Unsupported array value type: " + valueType, 0); + } + } + + public Long getLong(Object key) throws Amf3ParseException { + return (Long) getRequired(key, "Long"); + } + + public Object getRequired(Object key, String requiredType) throws Amf3ParseException { + if (!containsKey(key)) { + throw new Amf3ParseException("\"" + key + "\" is missing", 0); + } + Object val = get(key); + boolean typeMatches = true; + if (requiredType != null) { + switch (requiredType) { + case "String": + typeMatches = val instanceof String; + break; + case "Long": + typeMatches = val instanceof Long; + break; + case "JsObject": + typeMatches = val instanceof JsObject; + break; + case "JsArray": + typeMatches = val instanceof JsArray; + break; + case "Boolean": + typeMatches = val instanceof Boolean; + break; + } + } + if (!typeMatches) { + throw new Amf3ParseException("\"" + key + "\" value must be of type " + requiredType, 0); + } + return val; + } + + public boolean containsKey(Object key) { + for (Pair p : values) { + if (p.getFirst().equals(key)) { + return true; + } + } + return false; + } + + public Object get(Object key) { + for (Pair p : values) { + if (p.getFirst().equals(key)) { + return p.getSecond(); + } + } + return null; + } + + public void resolve(Object key, Map objectTable, boolean allowTypedObject) throws Amf3ParseException { + for (Pair p : values) { + if (p.getFirst().equals(key)) { + Object resolved = resolveObjects(p.getSecond(), objectTable, allowTypedObject); + p.setSecond(resolved); + return; + } + } + } + + public List stringKeys() { + List ret = new ArrayList<>(); + for (Pair p : values) { + if (p.getFirst() instanceof String) { + ret.add((String) p.getFirst()); + } + } + return ret; + } + + public List> getAll() { + return values; + } + + public List> getStringMapped() { + List> ret = new ArrayList<>(); + for (Pair p : values) { + if (p.getFirst() instanceof String) { + String keyStr = (String) p.getFirst(); + ret.add(new Pair<>(keyStr, p.getSecond())); + } + } + return ret; + } + + public Map getStringMap() { + Map ret = new HashMap<>(); + for (Pair p : values) { + if (p.getFirst() instanceof String) { + String keyStr = (String) p.getFirst(); + ret.put(keyStr, values); + } + } + return ret; + } + } + + private JsObject parseObject(Map objectTable) throws IOException, Amf3ParseException { + JsObject ret = new JsObject(); + + expectedType(SymbolType.CURLY_OPEN); + ParsedSymbol s = lex(); + if (!s.isType(SymbolType.CURLY_CLOSE)) { + pushback(s); + do { + Object key = value(objectTable); + expectedType(SymbolType.COLON); + Object value = value(objectTable); + ret.add(key, value); + if ("id".equals(key)) { + if (!(value instanceof String)) { + throw new Amf3ParseException("id must be string value", lexer.yyline()); + } + objectTable.put((String) value, BasicType.UNDEFINED); + } + s = lex(); + } while (s.isType(SymbolType.COMMA)); + } + pushback(s); + expectedType(SymbolType.CURLY_CLOSE); + return ret; + } + + private Object resolveObjects(Object object, Map objectTable, boolean allowTypedObject) throws Amf3ParseException { + Object resultObject = object; + if (object instanceof JsArray) { + JsArray jsa = (JsArray) object; + JsArray ret = new JsArray(); + for (int i = 0; i < jsa.values.size(); i++) { + ret.values.add(resolveObjects(jsa.values.get(i), objectTable, true)); + } + resultObject = ret; + } else if (object instanceof JsObject) { + if (allowTypedObject) { + JsObject typedObject = (JsObject) object; + if (typedObject.containsKey("type")) { + String typeStr = typedObject.getString("type"); + String id = typedObject.containsKey("id") ? typedObject.getString("id") : null; + switch (typeStr) { + case "Date": + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SS"); + String dateStr = typedObject.getString("value"); + try { + resultObject = new DateType((double) sdf.parse(dateStr).getTime()); + } catch (ParseException ex) { + throw new Amf3ParseException("Invalid date format: " + dateStr, lexer.yyline()); + } + break; + case "XML": + resultObject = new XmlType(typedObject.getString("value")); + break; + case "XMLDocument": + resultObject = new XmlDocType(typedObject.getString("value")); + break; + case "Object": + String className = typedObject.getString("className"); + if (typedObject.containsKey("serialized")) { + //TODO + } else { + boolean dynamic = typedObject.getBoolean("dynamic"); + typedObject.resolve("sealedMembers", objectTable, false); + JsObject jsoSealed = typedObject.getJsObject("sealedMembers"); + + List> sealedMembers = jsoSealed.getStringMapped(); + typedObject.resolve("dynamicMembers", objectTable, false); + List> dynamicMembers = typedObject.getJsObject("dynamicMembers").getStringMapped(); + + List sealedMemberNames = new ArrayList<>(jsoSealed.stringKeys()); + resultObject = new ObjectType(new Traits(className, dynamic, sealedMemberNames), sealedMembers, dynamicMembers); + } + break; + case "Array": + typedObject.resolve("denseValues", objectTable, false); + List denseValues = typedObject.getJsArray("denseValues").getValues(); + typedObject.resolve("associativeValues", objectTable, false); + JsObject resolvedArr = typedObject.getJsObject("associativeValues"); + List> associativeValues = resolvedArr.getStringMapped(); + resultObject = new ArrayType(denseValues, associativeValues); + break; + + case "Vector": + boolean fixed = typedObject.getBoolean("fixed"); + String subtype = typedObject.getString("subtype"); + typedObject.resolve("values", objectTable, false); + switch (subtype) { + case "int": + resultObject = new VectorIntType(fixed, typedObject.getJsArrayOfInt("values")); + break; + case "uint": + resultObject = new VectorUIntType(fixed, typedObject.getJsArrayOfUint("values")); + break; + case "Number": + resultObject = new VectorDoubleType(fixed, typedObject.getJsArrayOfNumber("values")); + break; + default: + resultObject = new VectorObjectType(fixed, subtype, typedObject.getJsArrayOfObject("values")); + break; + } + break; + case "ByteArray": + try { + resultObject = new ByteArrayType(javax.xml.bind.DatatypeConverter.parseHexBinary(typedObject.getString("value"))); + } catch (IllegalArgumentException iex) { + throw new Amf3ParseException("Invalid hex byte sequence", lexer.yyline()); + } + break; + case "Dictionary": + boolean weakKeys = typedObject.getBoolean("weakKeys"); + typedObject.resolve("entries", objectTable, false); + List> entries = typedObject.getJsObject("entries").getAll(); + resultObject = new DictionaryType(weakKeys, entries); + break; + default: + throw new Amf3ParseException("Unknown object type: " + typeStr, lexer.yyline()); + } + if (id != null) { + objectTable.put(id, resultObject); + } + } + } else { //not allowTypeObject + JsObject jsObject = (JsObject) object; + for (Pair p : jsObject.getAll()) { + p.setFirst(resolveObjects(p.getFirst(), objectTable, true)); + p.setSecond(resolveObjects(p.getSecond(), objectTable, true)); + } + resultObject = jsObject; + } + } + return resultObject; + } + + private Object value(Map objectTable) throws IOException, Amf3ParseException { + ParsedSymbol s = lex(); + switch (s.type) { + case CURLY_OPEN: + pushback(s); + return parseObject(objectTable); + case BRACKET_OPEN: + pushback(s); + return parseArray(objectTable); + case STRING: + case DOUBLE: + case INTEGER: + return s.value; + case UNDEFINED: + return BasicType.UNDEFINED; + case NULL: + return BasicType.NULL; + case UNKNOWN: + return BasicType.UNKNOWN; + case TRUE: + return Boolean.TRUE; + case FALSE: + return Boolean.FALSE; + case REFERENCE: + String referencedId = (String) s.value; + return new ReferencedObjectType(referencedId); + default: + throw new Amf3ParseException("Unexpected symbol: " + s, lexer.yyline()); + } + } + + /** + * Deeply replace all ReferencedObjectType with the correct value + * + * @param object + * @param objectsTable + * @return + */ + private Object replaceReferences(Object object, Map objectsTable) throws Amf3ParseException { + if (object instanceof ReferencedObjectType) { + String key = ((ReferencedObjectType) object).key; + if (!objectsTable.containsKey(key)) { + throw new Amf3ParseException("Reference to undefined object: #" + key, 0); + } + return objectsTable.get(key); + } else if (object instanceof ObjectType) { + ObjectType ot = (ObjectType) object; + for (Pair p : ot.getSealedMembers()) { + p.setSecond(replaceReferences(p.getSecond(), objectsTable)); + } + for (Pair p : ot.getDynamicMembers()) { + p.setSecond(replaceReferences(p.getSecond(), objectsTable)); + } + } else if (object instanceof ArrayType) { + ArrayType at = (ArrayType) object; + for (Pair p : at.getAssociativeValues()) { + p.setSecond(replaceReferences(p.getSecond(), objectsTable)); + } + for (int i = 0; i < at.getDenseValues().size(); i++) { + at.getDenseValues().set(i, replaceReferences(at.getDenseValues().get(i), objectsTable)); + } + } else if (object instanceof VectorObjectType) { + VectorObjectType vot = (VectorObjectType) object; + for (int i = 0; i < vot.getValues().size(); i++) { + vot.getValues().set(i, replaceReferences(vot.getValues().get(i), objectsTable)); + } + } else if (object instanceof DictionaryType) { + DictionaryType dt = (DictionaryType) object; + for (Pair p : dt.getPairs()) { + p.setFirst(replaceReferences(p.getFirst(), objectsTable)); + p.setSecond(replaceReferences(p.getSecond(), objectsTable)); + } + } + + return object; + } + + private class ReferencedObjectType { + + private final String key; + + public ReferencedObjectType(String key) { + this.key = key; + } + + public String getKey() { + return key; + } + + @Override + public String toString() { + return "#" + key; + } + } + + public Object stringToAmf(String val) throws IOException, Amf3ParseException { + lexer = new Amf3Lexer(new StringReader(val)); + Map objectsTable = new HashMap<>(); + List references = new ArrayList<>(); + Object result = value(objectsTable); + Object resultResolved = resolveObjects(result, objectsTable, true); + Object resultNoRef = replaceReferences(resultResolved, objectsTable); + return resultNoRef; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/Amf3Lexer.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/Amf3Lexer.java new file mode 100644 index 000000000..7052c61ef --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/Amf3Lexer.java @@ -0,0 +1,1033 @@ +/* The following code was generated by JFlex 1.6.0 */ + +/* + * Copyright (C) 2010-2016 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.importers.amf.amf3; +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + + +/** + * This class is a scanner generated by + * JFlex 1.6.0 + * from the specification file D:/Dropbox/Programovani/JavaSE/FFDec/libsrc/ffdec_lib/lexers/amf3.flex + */ +public final class Amf3Lexer { + + /** This character denotes the end of file */ + public static final int YYEOF = -1; + + /** initial size of the lookahead buffer */ + private static final int ZZ_BUFFERSIZE = 16384; + + /** lexical states */ + public static final int YYINITIAL = 0; + public static final int STRING = 2; + public static final int CHARLITERAL = 4; + + /** + * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l + * ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l + * at the beginning of a line + * l is of the form l = 2*k, k a non negative integer + */ + private static final int ZZ_LEXSTATE[] = { + 0, 0, 1, 1, 2, 2 + }; + + /** + * Translates characters to character classes + */ + private static final String ZZ_CMAP_PACKED = + "\11\7\1\3\1\2\1\54\1\55\1\1\16\7\4\0\1\3\1\0"+ + "\1\50\1\10\1\6\2\0\1\52\2\0\1\5\1\22\1\46\1\12"+ + "\1\20\1\4\1\11\3\16\4\17\2\13\1\47\6\0\4\15\1\21"+ + "\1\15\21\6\1\14\2\6\1\44\1\23\1\45\1\0\1\6\1\0"+ + "\1\33\1\51\1\15\1\27\1\30\1\31\2\6\1\32\1\6\1\37"+ + "\1\26\1\6\1\24\1\40\2\6\1\36\1\34\1\35\1\25\1\6"+ + "\1\41\1\53\2\6\1\42\1\0\1\43\1\0\6\7\1\56\32\7"+ + "\2\0\4\6\4\0\1\6\2\0\1\7\7\0\1\6\4\0\1\6"+ + "\5\0\27\6\1\0\37\6\1\0\u01ca\6\4\0\14\6\16\0\5\6"+ + "\7\0\1\6\1\0\1\6\21\0\160\7\5\6\1\0\2\6\2\0"+ + "\4\6\10\0\1\6\1\0\3\6\1\0\1\6\1\0\24\6\1\0"+ + "\123\6\1\0\213\6\1\0\5\7\2\0\236\6\11\0\46\6\2\0"+ + "\1\6\7\0\47\6\7\0\1\6\1\0\55\7\1\0\1\7\1\0"+ + "\2\7\1\0\2\7\1\0\1\7\10\0\33\6\5\0\3\6\15\0"+ + "\5\7\6\0\1\6\4\0\13\7\5\0\53\6\37\7\4\0\2\6"+ + "\1\7\143\6\1\0\1\6\10\7\1\0\6\7\2\6\2\7\1\0"+ + "\4\7\2\6\12\7\3\6\2\0\1\6\17\0\1\7\1\6\1\7"+ + "\36\6\33\7\2\0\131\6\13\7\1\6\16\0\12\7\41\6\11\7"+ + "\2\6\4\0\1\6\5\0\26\6\4\7\1\6\11\7\1\6\3\7"+ + "\1\6\5\7\22\0\31\6\3\7\104\0\1\6\1\0\13\6\67\0"+ + "\33\7\1\0\4\7\66\6\3\7\1\6\22\7\1\6\7\7\12\6"+ + "\2\7\2\0\12\7\1\0\7\6\1\0\7\6\1\0\3\7\1\0"+ + "\10\6\2\0\2\6\2\0\26\6\1\0\7\6\1\0\1\6\3\0"+ + "\4\6\2\0\1\7\1\6\7\7\2\0\2\7\2\0\3\7\1\6"+ + "\10\0\1\7\4\0\2\6\1\0\3\6\2\7\2\0\12\7\4\6"+ + "\7\0\1\6\5\0\3\7\1\0\6\6\4\0\2\6\2\0\26\6"+ + "\1\0\7\6\1\0\2\6\1\0\2\6\1\0\2\6\2\0\1\7"+ + "\1\0\5\7\4\0\2\7\2\0\3\7\3\0\1\7\7\0\4\6"+ + "\1\0\1\6\7\0\14\7\3\6\1\7\13\0\3\7\1\0\11\6"+ + "\1\0\3\6\1\0\26\6\1\0\7\6\1\0\2\6\1\0\5\6"+ + "\2\0\1\7\1\6\10\7\1\0\3\7\1\0\3\7\2\0\1\6"+ + "\17\0\2\6\2\7\2\0\12\7\1\0\1\6\17\0\3\7\1\0"+ + "\10\6\2\0\2\6\2\0\26\6\1\0\7\6\1\0\2\6\1\0"+ + "\5\6\2\0\1\7\1\6\7\7\2\0\2\7\2\0\3\7\10\0"+ + "\2\7\4\0\2\6\1\0\3\6\2\7\2\0\12\7\1\0\1\6"+ + "\20\0\1\7\1\6\1\0\6\6\3\0\3\6\1\0\4\6\3\0"+ + "\2\6\1\0\1\6\1\0\2\6\3\0\2\6\3\0\3\6\3\0"+ + "\14\6\4\0\5\7\3\0\3\7\1\0\4\7\2\0\1\6\6\0"+ + "\1\7\16\0\12\7\11\0\1\6\7\0\3\7\1\0\10\6\1\0"+ + "\3\6\1\0\27\6\1\0\12\6\1\0\5\6\3\0\1\6\7\7"+ + "\1\0\3\7\1\0\4\7\7\0\2\7\1\0\2\6\6\0\2\6"+ + "\2\7\2\0\12\7\22\0\2\7\1\0\10\6\1\0\3\6\1\0"+ + "\27\6\1\0\12\6\1\0\5\6\2\0\1\7\1\6\7\7\1\0"+ + "\3\7\1\0\4\7\7\0\2\7\7\0\1\6\1\0\2\6\2\7"+ + "\2\0\12\7\1\0\2\6\17\0\2\7\1\0\10\6\1\0\3\6"+ + "\1\0\51\6\2\0\1\6\7\7\1\0\3\7\1\0\4\7\1\6"+ + "\10\0\1\7\10\0\2\6\2\7\2\0\12\7\12\0\6\6\2\0"+ + "\2\7\1\0\22\6\3\0\30\6\1\0\11\6\1\0\1\6\2\0"+ + "\7\6\3\0\1\7\4\0\6\7\1\0\1\7\1\0\10\7\22\0"+ + "\2\7\15\0\60\6\1\7\2\6\7\7\4\0\10\6\10\7\1\0"+ + "\12\7\47\0\2\6\1\0\1\6\2\0\2\6\1\0\1\6\2\0"+ + "\1\6\6\0\4\6\1\0\7\6\1\0\3\6\1\0\1\6\1\0"+ + "\1\6\2\0\2\6\1\0\4\6\1\7\2\6\6\7\1\0\2\7"+ + "\1\6\2\0\5\6\1\0\1\6\1\0\6\7\2\0\12\7\2\0"+ + "\4\6\40\0\1\6\27\0\2\7\6\0\12\7\13\0\1\7\1\0"+ + "\1\7\1\0\1\7\4\0\2\7\10\6\1\0\44\6\4\0\24\7"+ + "\1\0\2\7\5\6\13\7\1\0\44\7\11\0\1\7\71\0\53\6"+ + "\24\7\1\6\12\7\6\0\6\6\4\7\4\6\3\7\1\6\3\7"+ + "\2\6\7\7\3\6\4\7\15\6\14\7\1\6\17\7\2\0\46\6"+ + "\1\0\1\6\5\0\1\6\2\0\53\6\1\0\u014d\6\1\0\4\6"+ + "\2\0\7\6\1\0\1\6\1\0\4\6\2\0\51\6\1\0\4\6"+ + "\2\0\41\6\1\0\4\6\2\0\7\6\1\0\1\6\1\0\4\6"+ + "\2\0\17\6\1\0\71\6\1\0\4\6\2\0\103\6\2\0\3\7"+ + "\40\0\20\6\20\0\125\6\14\0\u026c\6\2\0\21\6\1\0\32\6"+ + "\5\0\113\6\3\0\3\6\17\0\15\6\1\0\4\6\3\7\13\0"+ + "\22\6\3\7\13\0\22\6\2\7\14\0\15\6\1\0\3\6\1\0"+ + "\2\7\14\0\64\6\40\7\3\0\1\6\3\0\2\6\1\7\2\0"+ + "\12\7\41\0\3\7\2\0\12\7\6\0\130\6\10\0\51\6\1\7"+ + "\1\6\5\0\106\6\12\0\35\6\3\0\14\7\4\0\14\7\12\0"+ + "\12\7\36\6\2\0\5\6\13\0\54\6\4\0\21\7\7\6\2\7"+ + "\6\0\12\7\46\0\27\6\5\7\4\0\65\6\12\7\1\0\35\7"+ + "\2\0\13\7\6\0\12\7\15\0\1\6\130\0\5\7\57\6\21\7"+ + "\7\6\4\0\12\7\21\0\11\7\14\0\3\7\36\6\15\7\2\6"+ + "\12\7\54\6\16\7\14\0\44\6\24\7\10\0\12\7\3\0\3\6"+ + "\12\7\44\6\122\0\3\7\1\0\25\7\4\6\1\7\4\6\3\7"+ + "\2\6\11\0\300\6\47\7\25\0\4\7\u0116\6\2\0\6\6\2\0"+ + "\46\6\2\0\6\6\2\0\10\6\1\0\1\6\1\0\1\6\1\0"+ + "\1\6\1\0\37\6\2\0\65\6\1\0\7\6\1\0\1\6\3\0"+ + "\3\6\1\0\7\6\3\0\4\6\2\0\6\6\4\0\15\6\5\0"+ + "\3\6\1\0\7\6\16\0\5\7\30\0\1\54\1\54\5\7\20\0"+ + "\2\6\23\0\1\6\13\0\5\7\5\0\6\7\1\0\1\6\15\0"+ + "\1\6\20\0\15\6\3\0\33\6\25\0\15\7\4\0\1\7\3\0"+ + "\14\7\21\0\1\6\4\0\1\6\2\0\12\6\1\0\1\6\3\0"+ + "\5\6\6\0\1\6\1\0\1\6\1\0\1\6\1\0\4\6\1\0"+ + "\13\6\2\0\4\6\5\0\5\6\4\0\1\6\21\0\51\6\u0a77\0"+ + "\57\6\1\0\57\6\1\0\205\6\6\0\4\6\3\7\2\6\14\0"+ + "\46\6\1\0\1\6\5\0\1\6\2\0\70\6\7\0\1\6\17\0"+ + "\1\7\27\6\11\0\7\6\1\0\7\6\1\0\7\6\1\0\7\6"+ + "\1\0\7\6\1\0\7\6\1\0\7\6\1\0\7\6\1\0\40\7"+ + "\57\0\1\6\u01d5\0\3\6\31\0\11\6\6\7\1\0\5\6\2\0"+ + "\5\6\4\0\126\6\2\0\2\7\2\0\3\6\1\0\132\6\1\0"+ + "\4\6\5\0\51\6\3\0\136\6\21\0\33\6\65\0\20\6\u0200\0"+ + "\u19b6\6\112\0\u51cd\6\63\0\u048d\6\103\0\56\6\2\0\u010d\6\3\0"+ + "\20\6\12\7\2\6\24\0\57\6\1\7\4\0\12\7\1\0\31\6"+ + "\7\0\1\7\120\6\2\7\45\0\11\6\2\0\147\6\2\0\4\6"+ + "\1\0\4\6\14\0\13\6\115\0\12\6\1\7\3\6\1\7\4\6"+ + "\1\7\27\6\5\7\20\0\1\6\7\0\64\6\14\0\2\7\62\6"+ + "\21\7\13\0\12\7\6\0\22\7\6\6\3\0\1\6\4\0\12\7"+ + "\34\6\10\7\2\0\27\6\15\7\14\0\35\6\3\0\4\7\57\6"+ + "\16\7\16\0\1\6\12\7\46\0\51\6\16\7\11\0\3\6\1\7"+ + "\10\6\2\7\2\0\12\7\6\0\27\6\3\0\1\6\1\7\4\0"+ + "\60\6\1\7\1\6\3\7\2\6\2\7\5\6\2\7\1\6\1\7"+ + "\1\6\30\0\3\6\2\0\13\6\5\7\2\0\3\6\2\7\12\0"+ + "\6\6\2\0\6\6\2\0\6\6\11\0\7\6\1\0\7\6\221\0"+ + "\43\6\10\7\1\0\2\7\2\0\12\7\6\0\u2ba4\6\14\0\27\6"+ + "\4\0\61\6\u2104\0\u016e\6\2\0\152\6\46\0\7\6\14\0\5\6"+ + "\5\0\1\6\1\7\12\6\1\0\15\6\1\0\5\6\1\0\1\6"+ + "\1\0\2\6\1\0\2\6\1\0\154\6\41\0\u016b\6\22\0\100\6"+ + "\2\0\66\6\50\0\15\6\3\0\20\7\20\0\7\7\14\0\2\6"+ + "\30\0\3\6\31\0\1\6\6\0\5\6\1\0\207\6\2\0\1\7"+ + "\4\0\1\6\13\0\12\7\7\0\32\6\4\0\1\6\1\0\32\6"+ + "\13\0\131\6\3\0\6\6\2\0\6\6\2\0\6\6\2\0\3\6"+ + "\3\0\2\6\3\0\2\6\22\0\3\7\4\0\14\6\1\0\32\6"+ + "\1\0\23\6\1\0\2\6\1\0\17\6\2\0\16\6\42\0\173\6"+ + "\105\0\65\6\210\0\1\7\202\0\35\6\3\0\61\6\57\0\37\6"+ + "\21\0\33\6\65\0\36\6\2\0\44\6\4\0\10\6\1\0\5\6"+ + "\52\0\236\6\2\0\12\7\u0356\0\6\6\2\0\1\6\1\0\54\6"+ + "\1\0\2\6\3\0\1\6\2\0\27\6\252\0\26\6\12\0\32\6"+ + "\106\0\70\6\6\0\2\6\100\0\1\6\3\7\1\0\2\7\5\0"+ + "\4\7\4\6\1\0\3\6\1\0\33\6\4\0\3\7\4\0\1\7"+ + "\40\0\35\6\203\0\66\6\12\0\26\6\12\0\23\6\215\0\111\6"+ + "\u03b7\0\3\7\65\6\17\7\37\0\12\7\20\0\3\7\55\6\13\7"+ + "\2\0\1\7\22\0\31\6\7\0\12\7\6\0\3\7\44\6\16\7"+ + "\1\0\12\7\100\0\3\7\60\6\16\7\4\6\13\0\12\7\u04a6\0"+ + "\53\6\15\7\10\0\12\7\u0936\0\u036f\6\221\0\143\6\u0b9d\0\u042f\6"+ + "\u33d1\0\u0239\6\u04c7\0\105\6\13\0\1\6\56\7\20\0\4\7\15\6"+ + "\u4060\0\2\6\u2163\0\5\7\3\0\26\7\2\0\7\7\36\0\4\7"+ + "\224\0\3\7\u01bb\0\125\6\1\0\107\6\1\0\2\6\2\0\1\6"+ + "\2\0\2\6\2\0\4\6\1\0\14\6\1\0\1\6\1\0\7\6"+ + "\1\0\101\6\1\0\4\6\2\0\10\6\1\0\7\6\1\0\34\6"+ + "\1\0\4\6\1\0\5\6\1\0\1\6\3\0\7\6\1\0\u0154\6"+ + "\2\0\31\6\1\0\31\6\1\0\37\6\1\0\31\6\1\0\37\6"+ + "\1\0\31\6\1\0\37\6\1\0\31\6\1\0\37\6\1\0\31\6"+ + "\1\0\10\6\2\0\62\7\u1600\0\4\6\1\0\33\6\1\0\2\6"+ + "\1\0\1\6\2\0\1\6\1\0\12\6\1\0\4\6\1\0\1\6"+ + "\1\0\1\6\6\0\1\6\4\0\1\6\1\0\1\6\1\0\1\6"+ + "\1\0\3\6\1\0\2\6\1\0\1\6\2\0\1\6\1\0\1\6"+ + "\1\0\1\6\1\0\1\6\1\0\1\6\1\0\2\6\1\0\1\6"+ + "\2\0\4\6\1\0\7\6\1\0\4\6\1\0\4\6\1\0\1\6"+ + "\1\0\12\6\1\0\21\6\5\0\3\6\1\0\5\6\1\0\21\6"+ + "\u1144\0\ua6d7\6\51\0\u1035\6\13\0\336\6\u3fe2\0\u021e\6\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\u05ee\0"+ + "\1\7\36\0\140\7\200\0\360\7\uffff\0\uffff\0\ufe12\0"; + + /** + * Translates characters to character classes + */ + private static final char [] ZZ_CMAP = zzUnpackCMap(ZZ_CMAP_PACKED); + + /** + * Translates DFA states to action switch labels. + */ + private static final int [] ZZ_ACTION = zzUnpackAction(); + + private static final String ZZ_ACTION_PACKED_0 = + "\3\0\1\1\2\2\1\3\1\1\1\4\1\1\1\5"+ + "\1\1\1\5\1\1\4\4\1\6\1\7\1\10\1\11"+ + "\1\12\1\13\1\14\1\15\2\16\1\1\1\17\1\3"+ + "\1\0\1\20\1\21\1\22\1\0\2\21\1\22\2\0"+ + "\4\4\2\23\1\24\1\25\1\23\1\26\1\27\1\30"+ + "\1\31\1\32\1\33\1\23\2\3\2\0\2\34\1\21"+ + "\1\22\1\0\5\4\4\0\1\34\1\21\1\35\3\4"+ + "\1\36\1\37\1\0\1\40\1\34\1\21\2\4\1\41"+ + "\1\34\1\21\2\4\1\34\1\21\1\4\1\42\1\34"+ + "\1\21\1\4\1\34\1\21\1\43\1\34\7\21"; + + private static int [] zzUnpackAction() { + int [] result = new int[112]; + int offset = 0; + offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAction(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do result[j++] = value; while (--count > 0); + } + return j; + } + + + /** + * Translates a state to a row index in the transition table + */ + private static final int [] ZZ_ROWMAP = zzUnpackRowMap(); + + private static final String ZZ_ROWMAP_PACKED_0 = + "\0\0\0\57\0\136\0\215\0\274\0\215\0\353\0\u011a"+ + "\0\u0149\0\u0178\0\u01a7\0\u01d6\0\u0205\0\u0234\0\u0263\0\u0292"+ + "\0\u02c1\0\u02f0\0\215\0\215\0\215\0\215\0\215\0\215"+ + "\0\215\0\u031f\0\u034e\0\215\0\u037d\0\215\0\u03ac\0\u03db"+ + "\0\u040a\0\u0439\0\u0468\0\u0497\0\u04c6\0\u04f5\0\u0524\0\u0553"+ + "\0\u0234\0\u0582\0\u05b1\0\u05e0\0\u060f\0\215\0\u063e\0\215"+ + "\0\215\0\u066d\0\215\0\215\0\215\0\215\0\215\0\215"+ + "\0\u069c\0\u06cb\0\215\0\u06fa\0\u0729\0\u0497\0\u0758\0\u0787"+ + "\0\u07b6\0\u07b6\0\u07e5\0\u0814\0\u0843\0\u0872\0\u08a1\0\u08d0"+ + "\0\u08ff\0\u092e\0\u095d\0\u098c\0\u09bb\0\u0149\0\u09ea\0\u0a19"+ + "\0\u0a48\0\u0149\0\215\0\u069c\0\215\0\u0a77\0\u0aa6\0\u0ad5"+ + "\0\u0b04\0\u0149\0\u0b33\0\u0b62\0\u0b91\0\u0bc0\0\u0bef\0\u0c1e"+ + "\0\u0c4d\0\u0149\0\u0c7c\0\u0cab\0\u0cda\0\u0d09\0\u0d38\0\u0149"+ + "\0\215\0\u0d67\0\u0d96\0\u0dc5\0\u0df4\0\u0e23\0\u0e52\0\u0468"; + + private static int [] zzUnpackRowMap() { + int [] result = new int[112]; + int offset = 0; + offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackRowMap(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int high = packed.charAt(i++) << 16; + result[j++] = high | packed.charAt(i++); + } + return j; + } + + /** + * The transition table of the DFA + */ + private static final int [] ZZ_TRANS = zzUnpackTrans(); + + private static final String ZZ_TRANS_PACKED_0 = + "\1\4\1\5\1\6\1\7\1\10\1\4\1\11\1\4"+ + "\1\12\1\13\1\14\1\15\2\11\2\15\1\16\1\11"+ + "\2\4\1\17\1\20\3\11\1\21\3\11\1\22\4\11"+ + "\1\23\1\24\1\25\1\26\1\27\1\30\1\31\1\11"+ + "\1\4\1\11\1\4\1\7\1\4\1\32\1\33\1\34"+ + "\20\32\1\35\24\32\1\36\6\32\57\4\61\0\1\6"+ + "\57\0\1\7\51\0\1\7\5\0\1\37\1\40\57\0"+ + "\2\11\1\0\1\11\1\0\5\11\1\0\1\11\2\0"+ + "\16\11\7\0\1\11\1\0\1\11\2\0\1\11\6\0"+ + "\1\41\5\0\2\41\3\0\1\41\2\0\16\41\7\0"+ + "\1\41\1\0\1\41\14\0\1\42\1\0\1\43\1\44"+ + "\1\0\1\45\1\46\1\47\1\50\6\0\1\50\22\0"+ + "\1\44\14\0\1\43\1\0\1\15\2\0\2\15\1\51"+ + "\47\0\1\15\1\0\1\15\2\0\2\15\1\47\1\50"+ + "\6\0\1\50\37\0\1\47\1\0\1\47\2\0\2\47"+ + "\45\0\2\11\1\0\1\11\1\0\5\11\1\0\1\11"+ + "\2\0\1\11\1\52\14\11\7\0\1\11\1\0\1\11"+ + "\2\0\1\11\6\0\2\11\1\0\1\11\1\0\5\11"+ + "\1\0\1\11\2\0\1\53\15\11\7\0\1\11\1\0"+ + "\1\11\2\0\1\11\6\0\2\11\1\0\1\11\1\0"+ + "\5\11\1\0\1\11\2\0\7\11\1\54\6\11\7\0"+ + "\1\11\1\0\1\11\2\0\1\11\6\0\2\11\1\0"+ + "\1\11\1\0\5\11\1\0\1\11\2\0\12\11\1\55"+ + "\3\11\7\0\1\11\1\0\1\11\2\0\1\11\1\32"+ + "\2\0\20\32\1\0\24\32\1\0\6\32\2\0\1\34"+ + "\54\0\1\56\2\0\6\56\1\57\4\56\2\57\3\56"+ + "\1\60\1\61\1\62\3\56\1\63\3\56\1\64\1\65"+ + "\11\56\1\66\1\67\1\70\1\71\3\0\1\37\1\72"+ + "\1\73\54\37\5\74\1\75\51\74\6\0\2\41\1\0"+ + "\1\41\1\0\5\41\1\0\1\41\2\0\16\41\7\0"+ + "\1\41\1\0\1\41\2\0\1\41\11\0\1\42\1\0"+ + "\1\43\2\0\1\45\1\46\1\47\1\50\6\0\1\50"+ + "\37\0\1\43\1\0\1\43\2\0\2\43\1\47\1\50"+ + "\6\0\1\50\37\0\1\76\1\0\1\77\1\0\3\77"+ + "\1\0\1\77\5\0\3\77\1\0\1\77\15\0\1\77"+ + "\16\0\1\46\1\0\1\43\2\0\2\46\1\47\1\50"+ + "\6\0\1\50\37\0\1\100\1\0\1\43\2\0\2\100"+ + "\1\47\1\50\6\0\1\50\37\0\1\47\1\0\1\47"+ + "\2\0\2\47\1\0\1\50\6\0\1\50\37\0\1\101"+ + "\1\102\1\101\2\0\2\101\2\0\1\102\42\0\2\11"+ + "\1\0\1\11\1\0\5\11\1\0\1\11\2\0\2\11"+ + "\1\103\13\11\7\0\1\11\1\0\1\11\2\0\1\11"+ + "\6\0\2\11\1\0\1\11\1\0\5\11\1\0\1\11"+ + "\2\0\3\11\1\104\7\11\1\105\2\11\7\0\1\11"+ + "\1\0\1\11\2\0\1\11\6\0\2\11\1\0\1\11"+ + "\1\0\5\11\1\0\1\11\2\0\2\11\1\106\13\11"+ + "\7\0\1\11\1\0\1\11\2\0\1\11\6\0\2\11"+ + "\1\0\1\11\1\0\5\11\1\0\1\11\2\0\1\11"+ + "\1\107\14\11\7\0\1\11\1\0\1\11\2\0\1\11"+ + "\11\0\1\110\4\0\2\110\50\0\1\111\1\0\1\111"+ + "\1\0\3\111\1\0\1\111\5\0\3\111\1\0\1\111"+ + "\15\0\1\111\16\0\1\112\1\0\1\112\1\0\3\112"+ + "\1\0\1\112\5\0\3\112\1\0\1\112\15\0\1\112"+ + "\7\0\1\73\54\0\5\74\1\113\51\74\4\0\1\73"+ + "\1\75\62\0\1\114\1\0\1\114\1\0\3\114\1\0"+ + "\1\114\5\0\3\114\1\0\1\114\15\0\1\114\16\0"+ + "\1\115\1\0\1\43\2\0\2\115\1\47\1\50\6\0"+ + "\1\50\37\0\1\101\1\0\1\101\2\0\2\101\45\0"+ + "\2\11\1\0\1\11\1\0\5\11\1\0\1\11\2\0"+ + "\2\11\1\116\13\11\7\0\1\11\1\0\1\11\2\0"+ + "\1\11\6\0\2\11\1\0\1\11\1\0\5\11\1\0"+ + "\1\11\2\0\4\11\1\117\11\11\7\0\1\11\1\0"+ + "\1\11\2\0\1\11\6\0\2\11\1\0\1\11\1\0"+ + "\5\11\1\0\1\11\2\0\1\120\15\11\7\0\1\11"+ + "\1\0\1\11\2\0\1\11\6\0\2\11\1\0\1\11"+ + "\1\0\5\11\1\0\1\11\2\0\10\11\1\121\5\11"+ + "\7\0\1\11\1\0\1\11\2\0\1\11\6\0\2\11"+ + "\1\0\1\11\1\0\5\11\1\0\1\11\2\0\4\11"+ + "\1\122\11\11\7\0\1\11\1\0\1\11\2\0\1\11"+ + "\11\0\1\123\4\0\2\123\50\0\1\124\1\0\1\124"+ + "\1\0\3\124\1\0\1\124\5\0\3\124\1\0\1\124"+ + "\15\0\1\124\16\0\1\125\1\0\1\125\1\0\3\125"+ + "\1\0\1\125\5\0\3\125\1\0\1\125\15\0\1\125"+ + "\5\0\4\74\1\73\1\113\51\74\11\0\1\126\1\0"+ + "\1\126\1\0\3\126\1\0\1\126\5\0\3\126\1\0"+ + "\1\126\15\0\1\126\16\0\1\127\1\0\1\43\2\0"+ + "\2\127\1\47\1\50\6\0\1\50\34\0\2\11\1\0"+ + "\1\11\1\0\5\11\1\0\1\11\2\0\5\11\1\130"+ + "\10\11\7\0\1\11\1\0\1\11\2\0\1\11\6\0"+ + "\2\11\1\0\1\11\1\0\5\11\1\0\1\11\2\0"+ + "\14\11\1\131\1\11\7\0\1\11\1\0\1\11\2\0"+ + "\1\11\6\0\2\11\1\0\1\11\1\0\5\11\1\0"+ + "\1\11\2\0\4\11\1\132\11\11\7\0\1\11\1\0"+ + "\1\11\2\0\1\11\11\0\1\133\1\0\1\133\1\0"+ + "\3\133\1\0\1\133\5\0\3\133\1\0\1\133\15\0"+ + "\1\133\16\0\1\134\1\0\1\43\2\0\2\134\1\47"+ + "\1\50\6\0\1\50\34\0\2\11\1\0\1\11\1\0"+ + "\5\11\1\0\1\11\2\0\6\11\1\135\7\11\7\0"+ + "\1\11\1\0\1\11\2\0\1\11\6\0\2\11\1\0"+ + "\1\11\1\0\5\11\1\0\1\11\2\0\15\11\1\136"+ + "\7\0\1\11\1\0\1\11\2\0\1\11\11\0\1\137"+ + "\1\0\1\137\1\0\3\137\1\0\1\137\5\0\3\137"+ + "\1\0\1\137\15\0\1\137\16\0\1\140\1\0\1\43"+ + "\2\0\2\140\1\47\1\50\6\0\1\50\34\0\2\11"+ + "\1\0\1\11\1\0\5\11\1\0\1\11\2\0\1\141"+ + "\15\11\7\0\1\11\1\0\1\11\2\0\1\11\6\0"+ + "\2\11\1\0\1\11\1\0\5\11\1\0\1\11\2\0"+ + "\1\142\15\11\7\0\1\11\1\0\1\11\2\0\1\11"+ + "\11\0\1\143\1\0\1\143\1\0\3\143\1\0\1\143"+ + "\5\0\3\143\1\0\1\143\15\0\1\143\16\0\1\144"+ + "\1\0\1\43\2\0\2\144\1\47\1\50\6\0\1\50"+ + "\34\0\2\11\1\0\1\11\1\0\5\11\1\0\1\11"+ + "\2\0\4\11\1\145\11\11\7\0\1\11\1\0\1\11"+ + "\2\0\1\11\11\0\1\146\1\0\1\146\1\0\3\146"+ + "\1\0\1\146\5\0\3\146\1\0\1\146\15\0\1\146"+ + "\16\0\1\147\1\0\1\43\2\0\2\147\1\47\1\50"+ + "\6\0\1\50\34\0\2\11\1\0\1\11\1\0\5\11"+ + "\1\0\1\11\2\0\3\11\1\150\12\11\7\0\1\11"+ + "\1\0\1\11\2\0\1\11\11\0\1\151\1\0\1\151"+ + "\1\0\3\151\1\0\1\151\5\0\3\151\1\0\1\151"+ + "\15\0\1\151\16\0\1\152\1\0\1\43\2\0\2\152"+ + "\1\47\1\50\6\0\1\50\37\0\1\153\1\0\1\43"+ + "\2\0\2\153\1\47\1\50\6\0\1\50\37\0\1\154"+ + "\1\0\1\43\2\0\2\154\1\47\1\50\6\0\1\50"+ + "\37\0\1\155\1\0\1\43\2\0\2\155\1\47\1\50"+ + "\6\0\1\50\37\0\1\156\1\0\1\43\2\0\2\156"+ + "\1\47\1\50\6\0\1\50\37\0\1\157\1\0\1\43"+ + "\2\0\2\157\1\47\1\50\6\0\1\50\37\0\1\160"+ + "\1\0\1\43\2\0\2\160\1\47\1\50\6\0\1\50"+ + "\26\0"; + + private static int [] zzUnpackTrans() { + int [] result = new int[3713]; + int offset = 0; + offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackTrans(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + value--; + do result[j++] = value; while (--count > 0); + } + return j; + } + + + /* error codes */ + private static final int ZZ_UNKNOWN_ERROR = 0; + private static final int ZZ_NO_MATCH = 1; + private static final int ZZ_PUSHBACK_2BIG = 2; + + /* error messages for the codes above */ + private static final String ZZ_ERROR_MSG[] = { + "Unkown internal scanner error", + "Error: could not match input", + "Error: pushback value was too large" + }; + + /** + * ZZ_ATTRIBUTE[aState] contains the attributes of state aState + */ + private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute(); + + private static final String ZZ_ATTRIBUTE_PACKED_0 = + "\3\0\1\11\1\1\1\11\14\1\7\11\2\1\1\11"+ + "\1\1\1\11\1\1\1\0\3\1\1\0\3\1\2\0"+ + "\4\1\1\11\1\1\2\11\1\1\6\11\2\1\1\11"+ + "\2\0\4\1\1\0\5\1\4\0\7\1\1\11\1\0"+ + "\1\11\23\1\1\11\7\1"; + + private static int [] zzUnpackAttribute() { + int [] result = new int[112]; + int offset = 0; + offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAttribute(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do result[j++] = value; while (--count > 0); + } + return j; + } + + /** the input device */ + private java.io.Reader zzReader; + + /** the current state of the DFA */ + private int zzState; + + /** the current lexical state */ + private int zzLexicalState = YYINITIAL; + + /** this buffer contains the current text to be matched and is + the source of the yytext() string */ + private char zzBuffer[] = new char[ZZ_BUFFERSIZE]; + + /** the textposition at the last accepting state */ + private int zzMarkedPos; + + /** the current text position in the buffer */ + private int zzCurrentPos; + + /** startRead marks the beginning of the yytext() string in the buffer */ + private int zzStartRead; + + /** endRead marks the last character in the buffer, that has been read + from input */ + private int zzEndRead; + + /** number of newlines encountered up to the start of the matched text */ + private int yyline; + + /** the number of characters up to the start of the matched text */ + private int yychar; + + /** + * the number of characters from the last newline up to the start of the + * matched text + */ + private int yycolumn; + + /** + * zzAtBOL == true <=> the scanner is currently at the beginning of a line + */ + private boolean zzAtBOL = true; + + /** zzAtEOF == true <=> the scanner is at the EOF */ + private boolean zzAtEOF; + + /** denotes if the user-EOF-code has already been executed */ + private boolean zzEOFDone; + + /** + * The number of occupied positions in zzBuffer beyond zzEndRead. + * When a lead/high surrogate has been read from the input stream + * into the final zzBuffer position, this will have a value of 1; + * otherwise, it will have a value of 0. + */ + private int zzFinalHighSurrogate = 0; + + /* user code: */ + + StringBuilder string = new StringBuilder(); + + private static String xmlTagName = ""; + + public int yychar() { + return yychar; + } + + private final Stack pushedBack = new Stack<>(); + + public int yyline() { + return yyline + 1; + } + + public void pushback(ParsedSymbol symb) { + pushedBack.push(symb); + last = null; + } + + ParsedSymbol last; + public ParsedSymbol lex() throws java.io.IOException, Amf3ParseException{ + ParsedSymbol ret = null; + if (!pushedBack.isEmpty()){ + ret = last = pushedBack.pop(); + } else { + ret = last = yylex(); + } + return ret; + } + + + + /** + * Creates a new scanner + * + * @param in the java.io.Reader to read input from. + */ + public Amf3Lexer(java.io.Reader in) { + this.zzReader = in; + } + + + /** + * Unpacks the compressed character translation table. + * + * @param packed the packed character translation table + * @return the unpacked character translation table + */ + private static char [] zzUnpackCMap(String packed) { + char [] map = new char[0x110000]; + int i = 0; /* index in packed string */ + int j = 0; /* index in unpacked array */ + while (i < 2856) { + int count = packed.charAt(i++); + char value = packed.charAt(i++); + do map[j++] = value; while (--count > 0); + } + return map; + } + + + /** + * Refills the input buffer. + * + * @return false, iff there was new input. + * + * @exception java.io.IOException if any I/O-Error occurs + */ + private boolean zzRefill() throws java.io.IOException { + + /* first: make room (if you can) */ + if (zzStartRead > 0) { + zzEndRead += zzFinalHighSurrogate; + zzFinalHighSurrogate = 0; + System.arraycopy(zzBuffer, zzStartRead, + zzBuffer, 0, + zzEndRead-zzStartRead); + + /* translate stored positions */ + zzEndRead-= zzStartRead; + zzCurrentPos-= zzStartRead; + zzMarkedPos-= zzStartRead; + zzStartRead = 0; + } + + /* is the buffer big enough? */ + if (zzCurrentPos >= zzBuffer.length - zzFinalHighSurrogate) { + /* if not: blow it up */ + char newBuffer[] = new char[zzBuffer.length*2]; + System.arraycopy(zzBuffer, 0, newBuffer, 0, zzBuffer.length); + zzBuffer = newBuffer; + zzEndRead += zzFinalHighSurrogate; + zzFinalHighSurrogate = 0; + } + + /* fill the buffer with new input */ + int requested = zzBuffer.length - zzEndRead; + int totalRead = 0; + while (totalRead < requested) { + int numRead = zzReader.read(zzBuffer, zzEndRead + totalRead, requested - totalRead); + if (numRead == -1) { + break; + } + totalRead += numRead; + } + + if (totalRead > 0) { + zzEndRead += totalRead; + if (totalRead == requested) { /* possibly more input available */ + if (Character.isHighSurrogate(zzBuffer[zzEndRead - 1])) { + --zzEndRead; + zzFinalHighSurrogate = 1; + } + } + return false; + } + + // totalRead = 0: End of stream + return true; + } + + + /** + * Closes the input stream. + */ + public final void yyclose() throws java.io.IOException { + zzAtEOF = true; /* indicate end of file */ + zzEndRead = zzStartRead; /* invalidate buffer */ + + if (zzReader != null) + zzReader.close(); + } + + + /** + * Resets the scanner to read from a new input stream. + * Does not close the old reader. + * + * All internal variables are reset, the old input stream + * cannot be reused (internal buffer is discarded and lost). + * Lexical state is set to ZZ_INITIAL. + * + * Internal scan buffer is resized down to its initial length, if it has grown. + * + * @param reader the new input stream + */ + public final void yyreset(java.io.Reader reader) { + zzReader = reader; + zzAtBOL = true; + zzAtEOF = false; + zzEOFDone = false; + zzEndRead = zzStartRead = 0; + zzCurrentPos = zzMarkedPos = 0; + zzFinalHighSurrogate = 0; + yyline = yychar = yycolumn = 0; + zzLexicalState = YYINITIAL; + if (zzBuffer.length > ZZ_BUFFERSIZE) + zzBuffer = new char[ZZ_BUFFERSIZE]; + } + + + /** + * Returns the current lexical state. + */ + public final int yystate() { + return zzLexicalState; + } + + + /** + * Enters a new lexical state + * + * @param newState the new lexical state + */ + public final void yybegin(int newState) { + zzLexicalState = newState; + } + + + /** + * Returns the text matched by the current regular expression. + */ + public final String yytext() { + return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead ); + } + + + /** + * Returns the character at position pos from the + * matched text. + * + * It is equivalent to yytext().charAt(pos), but faster + * + * @param pos the position of the character to fetch. + * A value from 0 to yylength()-1. + * + * @return the character at position pos + */ + public final char yycharat(int pos) { + return zzBuffer[zzStartRead+pos]; + } + + + /** + * Returns the length of the matched text region. + */ + public final int yylength() { + return zzMarkedPos-zzStartRead; + } + + + /** + * Reports an error that occured while scanning. + * + * In a wellformed scanner (no or only correct usage of + * yypushback(int) and a match-all fallback rule) this method + * will only be called with things that "Can't Possibly Happen". + * If this method is called, something is seriously wrong + * (e.g. a JFlex bug producing a faulty scanner etc.). + * + * Usual syntax/scanner level error handling should be done + * in error fallback rules. + * + * @param errorCode the code of the errormessage to display + */ + private void zzScanError(int errorCode) { + String message; + try { + message = ZZ_ERROR_MSG[errorCode]; + } + catch (ArrayIndexOutOfBoundsException e) { + message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR]; + } + + throw new Error(message); + } + + + /** + * Pushes the specified amount of characters back into the input stream. + * + * They will be read again by then next call of the scanning method + * + * @param number the number of characters to be read again. + * This number must not be greater than yylength()! + */ + public void yypushback(int number) { + if ( number > yylength() ) + zzScanError(ZZ_PUSHBACK_2BIG); + + zzMarkedPos -= number; + } + + + /** + * Resumes scanning until the next regular expression is matched, + * the end of input is encountered or an I/O-Error occurs. + * + * @return the next token + * @exception java.io.IOException if any I/O-Error occurs + */ + public ParsedSymbol yylex() throws java.io.IOException, Amf3ParseException { + int zzInput; + int zzAction; + + // cached fields: + int zzCurrentPosL; + int zzMarkedPosL; + int zzEndReadL = zzEndRead; + char [] zzBufferL = zzBuffer; + char [] zzCMapL = ZZ_CMAP; + + int [] zzTransL = ZZ_TRANS; + int [] zzRowMapL = ZZ_ROWMAP; + int [] zzAttrL = ZZ_ATTRIBUTE; + + while (true) { + zzMarkedPosL = zzMarkedPos; + + yychar+= zzMarkedPosL-zzStartRead; + + zzAction = -1; + + zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL; + + zzState = ZZ_LEXSTATE[zzLexicalState]; + + // set up zzAction for empty match case: + int zzAttributes = zzAttrL[zzState]; + if ( (zzAttributes & 1) == 1 ) { + zzAction = zzState; + } + + + zzForAction: { + while (true) { + + if (zzCurrentPosL < zzEndReadL) { + zzInput = Character.codePointAt(zzBufferL, zzCurrentPosL, zzEndReadL); + zzCurrentPosL += Character.charCount(zzInput); + } + else if (zzAtEOF) { + zzInput = YYEOF; + break zzForAction; + } + else { + // store back cached positions + zzCurrentPos = zzCurrentPosL; + zzMarkedPos = zzMarkedPosL; + boolean eof = zzRefill(); + // get translated positions and possibly new buffer + zzCurrentPosL = zzCurrentPos; + zzMarkedPosL = zzMarkedPos; + zzBufferL = zzBuffer; + zzEndReadL = zzEndRead; + if (eof) { + zzInput = YYEOF; + break zzForAction; + } + else { + zzInput = Character.codePointAt(zzBufferL, zzCurrentPosL, zzEndReadL); + zzCurrentPosL += Character.charCount(zzInput); + } + } + int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput] ]; + if (zzNext == -1) break zzForAction; + zzState = zzNext; + + zzAttributes = zzAttrL[zzState]; + if ( (zzAttributes & 1) == 1 ) { + zzAction = zzState; + zzMarkedPosL = zzCurrentPosL; + if ( (zzAttributes & 8) == 8 ) break zzForAction; + } + + } + } + + // store back cached position + zzMarkedPos = zzMarkedPosL; + + switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) { + case 1: + { + } + case 36: break; + case 2: + { yyline++; + } + case 37: break; + case 3: + { /*ignore*/ + } + case 38: break; + case 4: + { return new ParsedSymbol(SymbolGroup.IDENTIFIER, SymbolType.IDENTIFIER, yytext()); + } + case 39: break; + case 5: + { return new ParsedSymbol(SymbolGroup.INTEGER, SymbolType.INTEGER, Long.parseLong((yytext()))); + } + case 40: break; + case 6: + { return new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.CURLY_OPEN, yytext()); + } + case 41: break; + case 7: + { return new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.CURLY_CLOSE, yytext()); + } + case 42: break; + case 8: + { return new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.BRACKET_OPEN, yytext()); + } + case 43: break; + case 9: + { return new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.BRACKET_CLOSE, yytext()); + } + case 44: break; + case 10: + { return new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.COMMA, yytext()); + } + case 45: break; + case 11: + { return new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.COLON, yytext()); + } + case 46: break; + case 12: + { string.setLength(0); + yybegin(STRING); + } + case 47: break; + case 13: + { string.append(yytext()); + } + case 48: break; + case 14: + { yybegin(YYINITIAL); yyline++; + } + case 49: break; + case 15: + { yybegin(YYINITIAL); + // length also includes the trailing quote + return new ParsedSymbol(SymbolGroup.STRING, SymbolType.STRING, string.toString()); + } + case 50: break; + case 16: + { return new ParsedSymbol(SymbolGroup.IDENTIFIER, SymbolType.REFERENCE, yytext().substring(1)); + } + case 51: break; + case 17: + { return new ParsedSymbol(SymbolGroup.INTEGER, SymbolType.INTEGER, Long.parseLong(yytext(), 8)); + } + case 52: break; + case 18: + { return new ParsedSymbol(SymbolGroup.DOUBLE, SymbolType.DOUBLE, Double.parseDouble((yytext()))); + } + case 53: break; + case 19: + { string.append('\\'); /*illegal escape sequence*/ + } + case 54: break; + case 20: + { string.append('\\'); + } + case 55: break; + case 21: + { string.append('\n'); + } + case 56: break; + case 22: + { string.append('\f'); + } + case 57: break; + case 23: + { string.append('\t'); + } + case 58: break; + case 24: + { string.append('\r'); + } + case 59: break; + case 25: + { string.append('\"'); + } + case 60: break; + case 26: + { string.append('\b'); + } + case 61: break; + case 27: + { string.append('\''); + } + case 62: break; + case 28: + { return new ParsedSymbol(SymbolGroup.INTEGER, SymbolType.INTEGER, Long.parseLong(yytext().substring(2), 16)); + } + case 63: break; + case 29: + { return new ParsedSymbol(SymbolGroup.GLOBALCONST, SymbolType.NULL, yytext()); + } + case 64: break; + case 30: + { return new ParsedSymbol(SymbolGroup.KEYWORD, SymbolType.TRUE, yytext()); + } + case 65: break; + case 31: + { char val = (char) Integer.parseInt(yytext().substring(1), 8); + string.append(val); + } + case 66: break; + case 32: + { char val = (char) Integer.parseInt(yytext().substring(2), 16); + string.append(val); + } + case 67: break; + case 33: + { return new ParsedSymbol(SymbolGroup.KEYWORD, SymbolType.FALSE, yytext()); + } + case 68: break; + case 34: + { return new ParsedSymbol(SymbolGroup.UNKNOWN, SymbolType.UNKNOWN, yytext()); + } + case 69: break; + case 35: + { return new ParsedSymbol(SymbolGroup.GLOBALCONST, SymbolType.UNDEFINED, yytext()); + } + case 70: break; + default: + if (zzInput == YYEOF && zzStartRead == zzCurrentPos) { + zzAtEOF = true; + { + return new ParsedSymbol(SymbolGroup.EOF, SymbolType.EOF, null); + } + } + else { + zzScanError(ZZ_NO_MATCH); + } + } + } + } + + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/Amf3ParseException.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/Amf3ParseException.java new file mode 100644 index 000000000..0f343f283 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/Amf3ParseException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2010-2016 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.importers.amf.amf3; + +import com.jpexs.decompiler.flash.ParseException; + +/** + * + * @author JPEXS + */ +public class Amf3ParseException extends ParseException { + + public Amf3ParseException(String text, long line) { + super(text, line); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/ParsedSymbol.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/ParsedSymbol.java new file mode 100644 index 000000000..c2244cbf6 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/ParsedSymbol.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2010-2016 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.importers.amf.amf3; + +/** + * + * @author JPEXS + */ +public class ParsedSymbol { + + public SymbolGroup group; + + public Object value; + + public SymbolType type; + + public ParsedSymbol(SymbolGroup group, SymbolType type) { + this.group = group; + this.type = type; + this.value = null; + } + + public ParsedSymbol(SymbolGroup group, SymbolType type, Object value) { + this.group = group; + this.type = type; + this.value = value; + } + + @Override + public String toString() { + return group.toString() + " " + type.toString() + " " + (value != null ? value.toString() : ""); + } + + public boolean isType(Object... types) { + for (Object t : types) { + if (t instanceof SymbolGroup) { + if (group == t) { + return true; + } + } + if (t instanceof SymbolType) { + if (type == t) { + return true; + } + } + } + return false; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/SymbolGroup.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/SymbolGroup.java new file mode 100644 index 000000000..a6a8b953f --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/SymbolGroup.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2010-2016 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.importers.amf.amf3; + +/** + * + * @author JPEXS + */ +public enum SymbolGroup { + OPERATOR, + KEYWORD, + STRING, + COMMENT, + IDENTIFIER, + INTEGER, + DOUBLE, + EOF, + GLOBALCONST, + UNKNOWN +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/SymbolType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/SymbolType.java new file mode 100644 index 000000000..03fad7666 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/amf/amf3/SymbolType.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2010-2016 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.importers.amf.amf3; + +/** + * + * @author JPEXS + */ +public enum SymbolType { + NULL, + UNDEFINED, + FALSE, + TRUE, + CURLY_OPEN, + CURLY_CLOSE, + BRACKET_OPEN, + BRACKET_CLOSE, + COMMA, + COLON, + UNKNOWN, + REFERENCE, + IDENTIFIER, + INTEGER, + STRING, + DOUBLE, + EOF +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java index 7c61e56d6..a4604ced5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java @@ -19,6 +19,7 @@ package com.jpexs.decompiler.flash.tags; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.amf.amf3.Amf3Value; import com.jpexs.decompiler.flash.tags.base.ASMSourceContainer; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.types.BasicType; @@ -436,4 +437,9 @@ public class PlaceObject2Tag extends PlaceObjectTypeTag implements ASMSourceCont matrix = old; } } + + @Override + public Amf3Value getAmfData() { + return null; + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java index ea1f877a5..dddf83f8b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java @@ -20,6 +20,7 @@ import com.jpexs.decompiler.flash.EndOfStreamException; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.amf.amf3.Amf3Value; import com.jpexs.decompiler.flash.tags.base.ASMSourceContainer; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.types.BasicType; @@ -615,4 +616,10 @@ public class PlaceObject3Tag extends PlaceObjectTypeTag implements ASMSourceCont matrix = old; } } + + @Override + public Amf3Value getAmfData() { + return null; + } + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java index cf8eada0e..311ac8011 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java @@ -20,6 +20,8 @@ import com.jpexs.decompiler.flash.EndOfStreamException; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.amf.amf3.Amf3Value; +import com.jpexs.decompiler.flash.amf.amf3.NoSerializerExistsException; import com.jpexs.decompiler.flash.tags.base.ASMSourceContainer; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.types.BasicType; @@ -37,10 +39,13 @@ import com.jpexs.decompiler.flash.types.annotations.SWFType; import com.jpexs.decompiler.flash.types.annotations.SWFVersion; import com.jpexs.decompiler.flash.types.filters.FILTER; import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.Helper; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Same as PlaceObject3Tag except additional AMF data @@ -230,7 +235,7 @@ public class PlaceObject4Tag extends PlaceObjectTypeTag implements ASMSourceCont @Reserved public boolean reserved; - public byte[] amfData; //TODO: Parse AMF data? + public Amf3Value amfData; /** * Constructor @@ -241,7 +246,7 @@ public class PlaceObject4Tag extends PlaceObjectTypeTag implements ASMSourceCont super(swf, ID, NAME, null); } - public PlaceObject4Tag(SWF swf, boolean placeFlagMove, int depth, String className, int characterId, MATRIX matrix, CXFORMWITHALPHA colorTransform, int ratio, String name, int clipDepth, List surfaceFilterList, int blendMode, int bitmapCache, int visible, RGBA backgroundColor, CLIPACTIONS clipActions, byte[] amfData) { + public PlaceObject4Tag(SWF swf, boolean placeFlagMove, int depth, String className, int characterId, MATRIX matrix, CXFORMWITHALPHA colorTransform, int ratio, String name, int clipDepth, List surfaceFilterList, int blendMode, int bitmapCache, int visible, RGBA backgroundColor, CLIPACTIONS clipActions, Amf3Value amfData) { super(swf, ID, NAME, null); this.placeFlagHasClassName = className != null; this.placeFlagHasFilterList = surfaceFilterList != null; @@ -353,8 +358,9 @@ public class PlaceObject4Tag extends PlaceObjectTypeTag implements ASMSourceCont if (placeFlagHasClipActions) { clipActions = sis.readCLIPACTIONS(swf, this, "clipActions"); } - - amfData = sis.readBytesEx(sis.available(), "amfData"); + if (sis.available() > 0) { + amfData = sis.readAmf3Object("amfValue"); + } } /** @@ -424,6 +430,13 @@ public class PlaceObject4Tag extends PlaceObjectTypeTag implements ASMSourceCont if (placeFlagHasClipActions) { sos.writeCLIPACTIONS(clipActions); } + if (amfData != null && amfData.getValue() != null) { + try { + sos.writeAmf3Object(amfData); + } catch (NoSerializerExistsException ex) { + throw new IOException("Class \"" + ex.getClassName() + "\" implements IExternalizable, it cannot be saved"); + } + } } @Override @@ -620,4 +633,10 @@ public class PlaceObject4Tag extends PlaceObjectTypeTag implements ASMSourceCont matrix = old; } } + + @Override + public Amf3Value getAmfData() { + return amfData; + } + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObjectTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObjectTag.java index 63c18b004..c3a4c3d91 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObjectTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObjectTag.java @@ -19,6 +19,7 @@ package com.jpexs.decompiler.flash.tags; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.amf.amf3.Amf3Value; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.types.BasicType; import com.jpexs.decompiler.flash.types.CLIPACTIONS; @@ -255,4 +256,10 @@ public class PlaceObjectTag extends PlaceObjectTypeTag { matrix = old; } } + + @Override + public Amf3Value getAmfData() { + return null; + } + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PlaceObjectTypeTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PlaceObjectTypeTag.java index a5a566d18..9aa8603f2 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PlaceObjectTypeTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PlaceObjectTypeTag.java @@ -18,6 +18,7 @@ package com.jpexs.decompiler.flash.tags.base; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.amf.amf3.Amf3Value; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.types.CLIPACTIONS; import com.jpexs.decompiler.flash.types.ColorTransform; @@ -76,6 +77,8 @@ public abstract class PlaceObjectTypeTag extends Tag implements CharacterIdTag { public abstract void writeTagWithMatrix(SWFOutputStream sos, MATRIX m) throws IOException; + public abstract Amf3Value getAmfData(); + @Override public String getName() { String result = super.getName(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java index 640dd8f6e..dc7a5d623 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java @@ -23,6 +23,9 @@ import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFCompression; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.action.Action; +import com.jpexs.decompiler.flash.amf.amf3.Amf3Value; +import com.jpexs.decompiler.flash.amf.amf3.Pair; +import com.jpexs.decompiler.flash.amf.amf3.types.ObjectType; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.exporters.MovieExporter; import com.jpexs.decompiler.flash.exporters.SoundExporter; @@ -70,6 +73,7 @@ import com.jpexs.decompiler.flash.tags.base.SoundTag; import com.jpexs.decompiler.flash.tags.base.TextTag; import com.jpexs.decompiler.flash.tags.enums.ImageFormat; import com.jpexs.decompiler.flash.tags.font.CharacterRanges; +import com.jpexs.decompiler.flash.timeline.Timelined; import com.jpexs.decompiler.flash.types.BUTTONCONDACTION; import com.jpexs.decompiler.flash.types.BUTTONRECORD; import com.jpexs.decompiler.flash.types.CLIPACTIONRECORD; @@ -167,6 +171,10 @@ public class XFLConverter { public static final int KEY_MODE_SHAPE_LAYERS = 8192; + public static final String PUBLISH_DATA_PREFIX = "PUB_PRST_DATA"; + + public static final String PUBLISH_DATA_FORMAT = "_EMBED_SWF_"; + private final Random random = new Random(123); // predictable random private static void convertShapeEdge(MATRIX mat, SHAPERECORD record, int x, int y, StringBuilder ret) { @@ -581,7 +589,7 @@ public class XFLConverter { LINESTYLEARRAY actualLinestyles = lineStyles; int strokeStyleOrig = 0; - fillStyleCount = fillStyles.fillStyles.length; + fillStyleCount = fillStyles == null ? 0 : fillStyles.fillStyles.length; for (SHAPERECORD edge : shapeRecords) { if (edge instanceof StyleChangeRecord) { StyleChangeRecord scr = (StyleChangeRecord) edge; @@ -602,7 +610,7 @@ public class XFLConverter { if ((fillStyle0 <= 0) && (fillStyle1 <= 0) && (strokeStyle > 0) && morphshape) { if (shapeNum == 4) { if (strokeStyleOrig > 0) { - if (!((LINESTYLE2) actualLinestyles.lineStyles[strokeStyleOrig]).hasFillFlag) { + if (actualLinestyles != null && !((LINESTYLE2) actualLinestyles.lineStyles[strokeStyleOrig]).hasFillFlag) { RGBA color = (RGBA) actualLinestyles.lineStyles[strokeStyleOrig].color; if (color.alpha == 0 && color.red == 0 && color.green == 0 && color.blue == 0) { empty = true; @@ -701,7 +709,7 @@ public class XFLConverter { if ((fillStyle0 <= 0) && (fillStyle1 <= 0) && (strokeStyle > 0) && morphshape) { if (shapeNum == 4) { if (strokeStyleOrig > 0) { - if (!((LINESTYLE2) actualLinestyles.lineStyles[strokeStyleOrig]).hasFillFlag) { + if (actualLinestyles != null && !((LINESTYLE2) actualLinestyles.lineStyles[strokeStyleOrig]).hasFillFlag) { RGBA color = (RGBA) actualLinestyles.lineStyles[strokeStyleOrig].color; if (color.alpha == 0 && color.red == 0 && color.green == 0 && color.blue == 0) { empty = true; @@ -745,7 +753,7 @@ public class XFLConverter { if ((fillStyle0 <= 0) && (fillStyle1 <= 0) && (strokeStyle > 0) && morphshape) { if (shapeNum == 4) { if (strokeStyleOrig > 0) { - if (!((LINESTYLE2) actualLinestyles.lineStyles[strokeStyleOrig]).hasFillFlag) { + if (actualLinestyles != null && !((LINESTYLE2) actualLinestyles.lineStyles[strokeStyleOrig]).hasFillFlag) { RGBA color = (RGBA) actualLinestyles.lineStyles[strokeStyleOrig].color; if (color.alpha == 0 && color.red == 0 && color.green == 0 && color.blue == 0) { empty = true; @@ -1058,7 +1066,7 @@ public class XFLConverter { } } - private static void convertSymbolInstance(String name, MATRIX matrix, ColorTransform colorTransform, boolean cacheAsBitmap, int blendMode, List filters, boolean isVisible, RGBA backgroundColor, CLIPACTIONS clipActions, CharacterTag tag, HashMap characters, ReadOnlyTagList tags, FLAVersion flaVersion, XFLXmlWriter writer) throws XMLStreamException { + private static void convertSymbolInstance(String name, MATRIX matrix, ColorTransform colorTransform, boolean cacheAsBitmap, int blendMode, List filters, boolean isVisible, RGBA backgroundColor, CLIPACTIONS clipActions, Amf3Value metadata, CharacterTag tag, HashMap characters, ReadOnlyTagList tags, FLAVersion flaVersion, XFLXmlWriter writer) throws XMLStreamException { if (matrix == null) { matrix = new MATRIX(); } @@ -1185,6 +1193,55 @@ public class XFLConverter { writer.writeEndElement(); writer.writeEndElement(); } + if (metadata != null && (metadata.getValue() instanceof ObjectType)) { + ObjectType metadataObject = (ObjectType) metadata.getValue(); + + if (metadataObject.isDynamic()) { + writer.writeStartElement("persistentData"); + List exportedNames = new ArrayList<>(); + for (Pair dynamicMember : metadataObject.getDynamicMembers()) { + String n = dynamicMember.getFirst(); + Object v = dynamicMember.getSecond(); + if (v instanceof Long) { + exportedNames.add(n); + writer.writeStartElement("PD"); + writer.writeAttribute("n", n); + writer.writeAttribute("t", "i"); + writer.writeAttribute("v", (Long) v); + writer.writeEndElement(); + exportedNames.add(n); + } else if (v instanceof Double) { + writer.writeStartElement("PD"); + writer.writeAttribute("n", n); + writer.writeAttribute("t", "d"); + writer.writeAttribute("v", (Double) v); + writer.writeEndElement(); + exportedNames.add(n); + } else if (v instanceof String) { + writer.writeStartElement("PD"); + writer.writeAttribute("n", n); + //missing t attrinute = string (maybe "s"?) + writer.writeAttribute("v", (String) v); + writer.writeEndElement(); + exportedNames.add(n); + } + /* + From JSFL, also data types integerArray ("I"), doubleArray("D") and byteArray("B") can be set. + These datatypes can be in the FLA file but are not exported to SWF with _EMBED_SWF_ publish format. + */ + } + + //Mark these names for publishing in embedded swf format: (setPublishPersistentData function in JSFL) + for (String n : exportedNames) { + writer.writeStartElement("PD"); + writer.writeAttribute("n", PUBLISH_DATA_PREFIX + PUBLISH_DATA_FORMAT + n); + writer.writeAttribute("t", "i"); + writer.writeAttribute("v", 1); + writer.writeEndElement(); + } + writer.writeEndElement(); + } + } writer.writeEndElement(); } @@ -1332,7 +1389,7 @@ public class XFLConverter { } else if (character instanceof DefineVideoStreamTag) { convertVideoInstance(null, matrix, (DefineVideoStreamTag) character, null, recCharWriter); } else { - convertSymbolInstance(null, matrix, colorTransformAlpha, false, blendMode, filters, true, null, null, characters.get(rec.characterId), characters, tags, flaVersion, recCharWriter); + convertSymbolInstance(null, matrix, colorTransformAlpha, false, blendMode, filters, true, null, null, null, characters.get(rec.characterId), characters, tags, flaVersion, recCharWriter); } int duration = frame - lastFrame; @@ -1909,6 +1966,7 @@ public class XFLConverter { CharacterTag character = null; MATRIX matrix = null; + Amf3Value metadata = null; String instanceName = null; ColorTransform colorTransForm = null; boolean cacheAsBitmap = false; @@ -1953,6 +2011,10 @@ public class XFLConverter { if (characters.containsKey(characterId)) { character = characters.get(characterId); if (po.flagMove()) { + Amf3Value metadata2 = po.getAmfData(); + if (metadata2 != null && metadata2.getValue() != null) { + metadata = metadata2; + } MATRIX matrix2 = po.getMatrix(); if (matrix2 != null) { matrix = matrix2; @@ -1986,6 +2048,7 @@ public class XFLConverter { ratio = ratio2; } } else { + metadata = po.getAmfData(); matrix = po.getMatrix(); instanceName = po.getInstanceName(); colorTransForm = po.getColorTransform(); @@ -2009,6 +2072,7 @@ public class XFLConverter { shapeTween = false; } character = null; + metadata = null; matrix = null; instanceName = null; colorTransForm = null; @@ -2041,7 +2105,7 @@ public class XFLConverter { } else if (character instanceof DefineVideoStreamTag) { convertVideoInstance(instanceName, matrix, (DefineVideoStreamTag) character, clipActions, elementsWriter); } else { - convertSymbolInstance(instanceName, matrix, colorTransForm, cacheAsBitmap, blendMode, filters, isVisible, backGroundColor, clipActions, character, characters, tags, flaVersion, elementsWriter); + convertSymbolInstance(instanceName, matrix, colorTransForm, cacheAsBitmap, blendMode, filters, isVisible, backGroundColor, clipActions, metadata, character, characters, tags, flaVersion, elementsWriter); } } } @@ -2601,14 +2665,14 @@ public class XFLConverter { writer.writeEndElement(); writer.writeStartElement("textRuns"); - int fontId = -1; + int fontId; FontTag font = null; - String fontName = null; + String fontName; String psFontName = null; int textHeight = -1; RGB textColor = null; RGBA textColorA = null; - boolean newline = false; + boolean newline; boolean firstRun = true; @SuppressWarnings("unchecked") List leftMargins = (List) attrs.get("allLeftMargins"); @@ -2776,8 +2840,8 @@ public class XFLConverter { int indent = -1; int lineSpacing = -1; String alignment = null; - boolean italic = false; - boolean bold = false; + boolean italic; + boolean bold; String fontFace = null; int size = -1; RGBA textColor = null; @@ -2807,9 +2871,9 @@ public class XFLConverter { bold = ft.isBold(); size = det.fontHeight; fontFace = fontName; - String installedFont = null; + String installedFont; if ((installedFont = FontTag.isFontFamilyInstalled(fontName)) != null) { - fontName = installedFont; + //fontName = installedFont; fontFace = new Font(installedFont, (italic ? Font.ITALIC : 0) | (bold ? Font.BOLD : 0) | (!italic && !bold ? Font.PLAIN : 0), size < 0 ? 10 : size).getPSName(); } @@ -2866,6 +2930,33 @@ public class XFLConverter { } } + private boolean hasAmfMetadata(Tag tag) { + if (tag instanceof PlaceObjectTypeTag) { + PlaceObjectTypeTag po = (PlaceObjectTypeTag) tag; + if (po.getAmfData() != null && po.getAmfData().getValue() != null) { + return true; + } + } + if (tag instanceof Timelined) { + Timelined tl = (Timelined) tag; + for (Tag t : tl.getTags()) { + if (hasAmfMetadata(t)) { + return true; + } + } + } + return false; + } + + private boolean hasAmfMetadata(SWF swf) { + for (Tag t : swf.getTags()) { + if (hasAmfMetadata(t)) { + return true; + } + } + return false; + } + public void convertSWF(AbortRetryIgnoreHandler handler, SWF swf, String swfFileName, String outfile, XFLExportSettings settings, String generator, String generatorVerName, String generatorVersion, boolean parallel, FLAVersion flaVersion) throws IOException, InterruptedException { FileAttributesTag fa = swf.getFileAttributes(); @@ -2895,6 +2986,7 @@ public class XFLConverter { List nonLibraryShapes = getNonLibraryShapes(swf.getTags(), characters); Map characterClasses = getCharacterClasses(swf.getTags()); Map characterVariables = getCharacterVariables(swf.getTags()); + boolean hasAmfMetadata = hasAmfMetadata(swf); String backgroundColor = "#ffffff"; SetBackgroundColorTag setBgColorTag = swf.getBackgroundColor(); @@ -2941,6 +3033,18 @@ public class XFLConverter { convertTimeline(0, nonLibraryShapes, backgroundColor, swf.getTags(), swf.getTags(), characters, "Scene 1", flaVersion, files, domDocument); domDocument.writeEndElement(); + if (hasAmfMetadata) { + domDocument.writeStartElement("persistentData"); + + domDocument.writeStartElement("PD"); + domDocument.writeAttribute("n", PUBLISH_DATA_PREFIX + PUBLISH_DATA_FORMAT); + domDocument.writeAttribute("t", "i"); + domDocument.writeAttribute("v", 1); + domDocument.writeEndElement(); + + domDocument.writeEndElement(); + } + domDocument.writeEndElement(); } catch (XMLStreamException ex) { logger.log(Level.SEVERE, null, ex); diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/amf/amf3/Amf3InputStreamTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/amf/amf3/Amf3InputStreamTest.java new file mode 100644 index 000000000..dd4bab2e9 --- /dev/null +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/amf/amf3/Amf3InputStreamTest.java @@ -0,0 +1,165 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +import com.jpexs.decompiler.flash.amf.amf3.types.ArrayType; +import com.jpexs.decompiler.flash.amf.amf3.types.DictionaryType; +import com.jpexs.decompiler.flash.amf.amf3.types.ObjectType; +import com.jpexs.decompiler.flash.amf.amf3.types.VectorObjectType; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.MemoryInputStream; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +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; +import static org.testng.Assert.*; +import org.testng.annotations.AfterTest; +import org.testng.annotations.Test; + +public class Amf3InputStreamTest { + + private Amf3InputStream is; + + @AfterTest + public void deinitStream() { + if (is != null) { + try { + is.close(); + } catch (IOException ex) { + //ignore + } + is = null; + } + } + + private void initStream(String fileName) throws IOException { + String file = "testdata/amf3/generated/" + fileName; + is = new Amf3InputStream(new MemoryInputStream(Helper.readFile(file))); + } + + @Test + public void testReadObject() throws IOException, NoSerializerExistsException { + initStream("all.bin"); + is.readValue("testValue"); + } + + private Map getSerializers() { + Map serializers = new HashMap<>(); + serializers.put("CustomClass", new ObjectTypeSerializeHandler() { + @Override + public List> readObject(String className, InputStream is) throws IOException { + List> members = new ArrayList<>(); + members.add(new Pair<>("val8", (long) is.read())); + members.add(new Pair<>("val32", (long) ((is.read() << 24) + (is.read() << 16) + (is.read() << 8) + (is.read())))); + return members; + } + + @Override + public void writeObject(List> members, OutputStream os) throws IOException { + throw new UnsupportedOperationException("Not implemented"); + } + }); + return serializers; + } + + @Test(expectedExceptions = NoSerializerExistsException.class) + public void testReadCustomSerializedNeedsSerializer() throws IOException, NoSerializerExistsException { + initStream("custom.bin"); + is.readValue("testValue"); //needs deserializer for CustomClass + } + + @Test + public void testReadCustomSerialized() throws IOException, NoSerializerExistsException { + initStream("custom.bin"); + is.readValue("testValue", getSerializers()); + } + + @Test + public void testNoSerializerHandlingInObjectDynamicProp() throws IOException { + initStream("noserializer_object_dynamic.bin"); + try { + is.readValue("testValue"); + } catch (NoSerializerExistsException ex) { + assertTrue(ex.getIncompleteData() instanceof ObjectType, "Expected datatype: ObjectType, Actual datatype: " + ex.getIncompleteData().getClass()); + //TODO: examinate the data more + } + } + + @Test + public void testNoSerializerHandlingInObjectSealedProp() throws IOException { + initStream("noserializer_object_sealed.bin"); + try { + is.readValue("testValue"); + } catch (NoSerializerExistsException ex) { + assertTrue(ex.getIncompleteData() instanceof ObjectType, "Expected datatype: ObjectType, Actual datatype: " + ex.getIncompleteData().getClass()); + //TODO: examinate the data more + } + } + + @Test + public void testNoSerializerHandlingInArrayDense() throws IOException { + initStream("noserializer_array_dense.bin"); + try { + is.readValue("testValue"); + } catch (NoSerializerExistsException ex) { + assertTrue(ex.getIncompleteData() instanceof ArrayType, "Expected datatype: ArrayType, Actual datatype: " + ex.getIncompleteData().getClass()); + //TODO: examinate the data more + } + } + + @Test + public void testNoSerializerHandlingInArrayAssociative() throws IOException { + initStream("noserializer_array_associative.bin"); + try { + is.readValue("testValue"); + } catch (NoSerializerExistsException ex) { + assertTrue(ex.getIncompleteData() instanceof ArrayType, "Expected datatype: ArrayType, Actual datatype: " + ex.getIncompleteData().getClass()); + //TODO: examinate the data more + } + } + + @Test + public void testNoSerializerHandlingInVector() throws IOException { + initStream("noserializer_vector.bin"); + try { + is.readValue("testValue"); + } catch (NoSerializerExistsException ex) { + assertTrue(ex.getIncompleteData() instanceof VectorObjectType, "Expected datatype: VectorObjectType, Actual datatype: " + ex.getIncompleteData().getClass()); + //TODO: examinate the data more + } + } + + @Test + public void testNoSerializerHandlingInDictionaryValues() throws IOException { + initStream("noserializer_dictionary_value.bin"); + try { + is.readValue("testValue"); + } catch (NoSerializerExistsException ex) { + assertTrue(ex.getIncompleteData() instanceof DictionaryType, "Expected datatype: DictionaryType, Actual datatype: " + ex.getIncompleteData().getClass()); + //TODO: examinate the data more + } + } + + @Test + public void testNoSerializerHandlingInDictionaryKeys() throws IOException { + initStream("noserializer_dictionary_key.bin"); + try { + is.readValue("testValue"); + } catch (NoSerializerExistsException ex) { + assertTrue(ex.getIncompleteData() instanceof DictionaryType, "Expected datatype: DictionaryType, Actual datatype: " + ex.getIncompleteData().getClass()); + //TODO: examinate the data more + } + } + + @Test(expectedExceptions = UnsupportedValueTypeException.class) + public void testUnsupportedMarker() throws IOException, NoSerializerExistsException { + final int UNSUPPORTED_MARKER = 100; + is = new Amf3InputStream(new MemoryInputStream(new byte[]{UNSUPPORTED_MARKER})); + is.readValue("testValue"); + } +} diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/amf/amf3/Amf3OutputStreamTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/amf/amf3/Amf3OutputStreamTest.java new file mode 100644 index 000000000..49ecef29f --- /dev/null +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/amf/amf3/Amf3OutputStreamTest.java @@ -0,0 +1,88 @@ +package com.jpexs.decompiler.flash.amf.amf3; + +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.MemoryInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +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; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class Amf3OutputStreamTest { + + private Map getSerializers() { + Map serializers = new HashMap<>(); + serializers.put("CustomClass", new ObjectTypeSerializeHandler() { + @Override + public List> readObject(String className, InputStream is) throws IOException { + List> members = new ArrayList<>(); + members.add(new Pair<>("val8", (long) is.read())); + members.add(new Pair<>("val32", (long) ((is.read() << 24) + (is.read() << 16) + (is.read() << 8) + (is.read())))); + return members; + } + + @Override + public void writeObject(List> members, OutputStream os) throws IOException { + Map memberMap = new HashMap<>(); + for (Pair m : members) { + memberMap.put(m.getFirst(), m.getSecond()); + } + os.write((int) (long) (Long) memberMap.get("val8")); + long val32 = (long) (Long) memberMap.get("val32"); + os.write((int) ((val32 >> 24) & 0xff)); + os.write((int) ((val32 >> 16) & 0xff)); + os.write((int) ((val32 >> 8) & 0xff)); + os.write((int) (val32 & 0xff)); + } + }); + return serializers; + } + + @DataProvider(name = "files") + public static Object[][] provideSamples() { + return new Object[][]{ + {"all.bin"}, + {"custom.bin"}, + {"noserializer_array_associative.bin"}, + {"noserializer_array_dense.bin"}, + {"noserializer_dictionary_key.bin"}, + {"noserializer_dictionary_value.bin"}, + {"noserializer_object_dynamic.bin"}, + {"noserializer_object_sealed.bin"}, + {"noserializer_vector.bin"} + }; + } + + @Test(dataProvider = "files") + public void testRecompile(String fileName) throws FileNotFoundException, IOException, NoSerializerExistsException { + + String originalFile = "testdata/amf3/generated/" + fileName; + + byte[] originalData = Helper.readFile(originalFile); + byte[] savedData; + + Amf3InputStream is = new Amf3InputStream(new MemoryInputStream(Helper.readFile(originalFile))); + + Object val = is.readValue("testValue", getSerializers()); + String savedFile = "testdata/amf3/generated/recompiled." + fileName; + try (FileOutputStream fos = new FileOutputStream(savedFile)) { + Amf3OutputStream os = new Amf3OutputStream(fos); + os.writeValue(val, getSerializers()); + } + savedData = Helper.readFile(savedFile); + + Assert.assertEquals(savedData, originalData); + new File(savedFile).delete(); + + } +} diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/importers/amf/amf3/parser/Amf3ImporterTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/importers/amf/amf3/parser/Amf3ImporterTest.java new file mode 100644 index 000000000..5ac5fae8b --- /dev/null +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/importers/amf/amf3/parser/Amf3ImporterTest.java @@ -0,0 +1,85 @@ +package com.jpexs.decompiler.flash.importers.amf.amf3.parser; + +import com.jpexs.decompiler.flash.importers.amf.amf3.Amf3ParseException; +import com.jpexs.decompiler.flash.importers.amf.amf3.Amf3Importer; +import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter; +import com.jpexs.decompiler.flash.amf.amf3.Amf3InputStream; +import com.jpexs.decompiler.flash.amf.amf3.NoSerializerExistsException; +import com.jpexs.decompiler.flash.amf.amf3.ObjectTypeSerializeHandler; +import com.jpexs.decompiler.flash.amf.amf3.Pair; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.MemoryInputStream; +import java.io.File; +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; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class Amf3ImporterTest { + + @DataProvider(name = "files") + public static Object[][] provideSamples() { + return new Object[][]{ + {"all.bin"} + }; + } + + private Map getSerializers() { + Map serializers = new HashMap<>(); + serializers.put("CustomClass", new ObjectTypeSerializeHandler() { + @Override + public List> readObject(String className, InputStream is) throws IOException { + List> members = new ArrayList<>(); + members.add(new Pair<>("val8", (long) is.read())); + members.add(new Pair<>("val32", (long) ((is.read() << 24) + (is.read() << 16) + (is.read() << 8) + (is.read())))); + return members; + } + + @Override + public void writeObject(List> members, OutputStream os) throws IOException { + Map memberMap = new HashMap<>(); + for (Pair m : members) { + memberMap.put(m.getFirst(), m.getSecond()); + } + os.write((int) (long) (Long) memberMap.get("val8")); + long val32 = (long) (Long) memberMap.get("val32"); + os.write((int) ((val32 >> 24) & 0xff)); + os.write((int) ((val32 >> 16) & 0xff)); + os.write((int) ((val32 >> 8) & 0xff)); + os.write((int) (val32 & 0xff)); + } + }); + return serializers; + } + + @Test(dataProvider = "files") + public void testRecompile(String fileName) throws IOException, NoSerializerExistsException, Amf3ParseException { + + String originalFile = "testdata/amf3/generated/" + fileName; + + Amf3InputStream is = new Amf3InputStream(new MemoryInputStream(Helper.readFile(originalFile))); + + Object val = is.readValue("testValue", getSerializers()); + String exported1File = "testdata/amf3/generated/exported1." + fileName; + String exported2File = "testdata/amf3/generated/exported2." + fileName; + + String exported1 = Amf3Exporter.amfToString(val); + Helper.writeFile(exported1File, exported1.getBytes("UTF-8")); + Amf3Importer imp = new Amf3Importer(); + Object valImported = imp.stringToAmf(exported1); + String exported2 = Amf3Exporter.amfToString(valImported); + + Helper.writeFile(exported2File, exported2.getBytes("UTF-8")); + + Assert.assertEquals(exported2, exported1); + new File(exported1File).delete(); + new File(exported2File).delete(); + + } +} diff --git a/libsrc/ffdec_lib/testdata/amf3/AmfTest.as b/libsrc/ffdec_lib/testdata/amf3/AmfTest.as new file mode 100644 index 000000000..83658a2c7 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/amf3/AmfTest.as @@ -0,0 +1,172 @@ +package +{ + import flash.display.Sprite; + import flash.utils.ByteArray; + import flash.filesystem.File; + import flash.filesystem.FileStream; + import flash.filesystem.FileMode; + + import flash.utils.Dictionary; + import flash.geom.Point; + import flash.xml.XMLDocument; + + import flash.net.registerClassAlias; + + /** + * Generating test data file + */ + public class AmfTest extends Sprite + { + + private function writeToFile(filename:String,data) + { + var urlStr:String = "file:///"+File.applicationDirectory.nativePath+"/generated/"+filename; + var file:File = new File() ; + file.url = urlStr; + var fileStream:FileStream = new FileStream(); + fileStream.open(file, FileMode.WRITE); + var bytes:ByteArray = new ByteArray(); + bytes.writeObject(data); + fileStream.writeBytes(bytes, 0, bytes.length); + fileStream.close(); + + } + + public function AmfTest() { + + + + var ar = ["a","b","c"]; + + var asoc_array = [1,2,3]; + asoc_array["key1"] = 5; + asoc_array["key2"] = 6; + + var ba:ByteArray = new ByteArray(); + ba.writeByte(65); + ba.writeByte(66); + ba.writeByte(67); + + + var vector_int:Vector. = new [-10,20,-30,40]; + var vector_uint:Vector. = new [10,20,30,40]; + var vector_double:Vector. = new [-10.1,20.2,-30.3,40.4]; + var vector_string:Vector. = new ["x","y","z"]; + var vector_point:Vector. = new [new Point(10,20),new Point(30,40),new Point(50,60)]; + + var dict = new Dictionary(); + dict["dkey1"] = "TestOne"; + dict["dkey2"] = "TestTwo"; + + var txmldoc:XMLDocument = new XMLDocument("aa"); + + var txml:XML = + Hello + Hi + ; + + var date = new Date(); + + var me = { + "01_int":5, + "02_negative-int":-5, + "03_string":"String", + "04_true":true, + "05_false":false, + "06_undefined":undefined, + "07_null":null, + "08_double":5.6, + "09_negative-double":-5.6, + "10_array":ar, + "11_asoc_array": asoc_array, + "12_date":date, + "13_xml":txml, + "14_byteArray":ba, + "15_vectorInt":vector_int, + "16_vectorUInt":vector_uint, + "17_vectorDouble":vector_double, + "18_vectorString":vector_string, + "19_vectorPoint":vector_point, + "20_dictionary":dict, + "21_xmldoc":txmldoc, + "22_ref_string":"String", + "23_ref_array":ar, + "24_ref_date":date, + "25_ref_byteArray":ba, + "26_ref_vectorInt":vector_int, + "27_ref_vectorUInt":vector_uint, + "28_ref_vectorDouble":vector_double, + "29_ref_vectorString":vector_string, + "30_ref_dictionary":dict, + "31_ref_xml":txml, + "32_ref_xmldoc":txmldoc + }; + me["33_me"] = me; + + writeToFile("all.bin",me); + + var custom = new CustomClass(); + custom.setVal8(127); + custom.setVal32(-2500); + registerClassAlias("CustomClass", CustomClass); + + var cust_obj = { + "member1":5, + "member2":custom, + "member3":27 + }; + + writeToFile("custom.bin",cust_obj); + + writeToFile("noserializer_object_dynamic.bin",{ + "a":5, + "b":8, + "c":custom, + "d":6, + "e":7, + "f":26 + }); + writeToFile("noserializer_array_dense.bin", + ["a","b",custom,"d","e","f"] + ); + + var arx = []; + arx["a"] = 1; + arx["b"] = 2; + arx["c"] = custom; + arx["d"] = 4; + arx["e"] = 5; + arx["f"] = 6; + writeToFile("noserializer_array_associative.bin", arx); + + + writeToFile("noserializer_vector.bin", new ["a","b",custom,"d","e","f"]); + + var nsdict = new Dictionary(); + nsdict["a"] = "One"; + nsdict["b"] = "Two"; + nsdict["c"] = custom; + nsdict["d"] = "Three"; + nsdict["e"] = "Four"; + nsdict["f"] = "Five"; + + writeToFile("noserializer_dictionary_value.bin", nsdict); + + var nsdict2 = new Dictionary(); + nsdict2["a"] = "One"; + nsdict2["b"] = "Two"; + nsdict2[custom] = "Three"; + nsdict2["d"] = "Three"; + nsdict2["e"] = "Four"; + nsdict2["f"] = "Five"; + + writeToFile("noserializer_dictionary_key.bin", nsdict2); + + var objWithCustom:ObjectWithCustom = new ObjectWithCustom("1","2",custom,"4","5","6"); + writeToFile("noserializer_object_sealed.bin", objWithCustom); + + + writeToFile("simple.bin", {"a":7, "b":29}); + } + } +} \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/amf3/AmfTest.swf b/libsrc/ffdec_lib/testdata/amf3/AmfTest.swf new file mode 100644 index 000000000..b7547e8c0 Binary files /dev/null and b/libsrc/ffdec_lib/testdata/amf3/AmfTest.swf differ diff --git a/libsrc/ffdec_lib/testdata/amf3/CustomClass.as b/libsrc/ffdec_lib/testdata/amf3/CustomClass.as new file mode 100644 index 000000000..d353e27f4 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/amf3/CustomClass.as @@ -0,0 +1,32 @@ +package +{ + import flash.utils.IExternalizable; + import flash.utils.IDataOutput; + import flash.utils.IDataInput; + + public class CustomClass implements IExternalizable + { + + private var val8:int; + private var val32:int; + public function CustomClass() { //No constructor parameters allowed + } + + public function setVal8(v:int){ + this.val8 = v; + } + public function setVal32(v:int){ + this.val32 = v; + } + + public function writeExternal(output:IDataOutput):void { + output.writeByte(val8); + output.writeInt(val32); + } + + public function readExternal(input:IDataInput):void { + this.val8 = input.readByte(); + this.val32 = input.readInt(); + } + } +} \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/amf3/ObjectWithCustom.as b/libsrc/ffdec_lib/testdata/amf3/ObjectWithCustom.as new file mode 100644 index 000000000..21db1efa6 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/amf3/ObjectWithCustom.as @@ -0,0 +1,24 @@ +package +{ + public class ObjectWithCustom + { + + public var a:String; + public var b:String; + public var c:CustomClass; + public var d:String; + public var e:String; + public var f:String; + + + public function ObjectWithCustom(a:String,b:String,c:CustomClass,d:String,e:String,f:String) { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.e = e; + this.f = f; + } + + } +} \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/amf3/app.xml b/libsrc/ffdec_lib/testdata/amf3/app.xml new file mode 100644 index 000000000..403dadf31 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/amf3/app.xml @@ -0,0 +1,14 @@ + + + samples.flex.HelloWorld + 1 + HelloWorld + + AmfTest.swf + true + none + true + 400 + 200 + + \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/amf3/generated/all.bin b/libsrc/ffdec_lib/testdata/amf3/generated/all.bin new file mode 100644 index 000000000..e9f33e746 Binary files /dev/null and b/libsrc/ffdec_lib/testdata/amf3/generated/all.bin differ diff --git a/libsrc/ffdec_lib/testdata/amf3/generated/custom.bin b/libsrc/ffdec_lib/testdata/amf3/generated/custom.bin new file mode 100644 index 000000000..74ae73be5 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/amf3/generated/custom.bin @@ -0,0 +1,3 @@ + + member3member2 +CustomClassÿÿö<member1 \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_array_associative.bin b/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_array_associative.bin new file mode 100644 index 000000000..c1b45c8b6 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_array_associative.bin @@ -0,0 +1,2 @@ + fabc +CustomClassÿÿö<de \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_array_dense.bin b/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_array_dense.bin new file mode 100644 index 000000000..5438f07e1 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_array_dense.bin @@ -0,0 +1,2 @@ + ab +CustomClassÿÿö<def \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_dictionary_key.bin b/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_dictionary_key.bin new file mode 100644 index 000000000..28445e990 Binary files /dev/null and b/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_dictionary_key.bin differ diff --git a/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_dictionary_value.bin b/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_dictionary_value.bin new file mode 100644 index 000000000..05d9b4e62 Binary files /dev/null and b/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_dictionary_value.bin differ diff --git a/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_object_dynamic.bin b/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_object_dynamic.bin new file mode 100644 index 000000000..e7793c3ae --- /dev/null +++ b/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_object_dynamic.bin @@ -0,0 +1,3 @@ + + fabc +CustomClassÿÿö<de \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_object_sealed.bin b/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_object_sealed.bin new file mode 100644 index 000000000..cfaf1f8c4 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_object_sealed.bin @@ -0,0 +1,3 @@ + +ccfadbe +CustomClassÿÿö<61425 \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_vector.bin b/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_vector.bin new file mode 100644 index 000000000..ff6653f8d Binary files /dev/null and b/libsrc/ffdec_lib/testdata/amf3/generated/noserializer_vector.bin differ diff --git a/libsrc/ffdec_lib/testdata/amf3/generated/simple.bin b/libsrc/ffdec_lib/testdata/amf3/generated/simple.bin new file mode 100644 index 000000000..ab4fd477c --- /dev/null +++ b/libsrc/ffdec_lib/testdata/amf3/generated/simple.bin @@ -0,0 +1,2 @@ + + ba \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/amf3/run.bat b/libsrc/ffdec_lib/testdata/amf3/run.bat new file mode 100644 index 000000000..e7aa460f3 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/amf3/run.bat @@ -0,0 +1,20 @@ +@echo off +set ISDEBUG=false +if "%1" == "debug" goto blockset +goto block2 +:blockset +set ISDEBUG=true +:block2 +set AIRPATH=c:\air +set COMPILERPATH=%AIRPATH%\bin\amxmlc.bat +if not exist %COMPILERPATH% goto notex +call %COMPILERPATH% -warnings=false -debug=%ISDEBUG% AmfTest.as>NUL +if errorlevel==1 goto failed +goto end +:notex +echo AIR SDK not found. Download and unpack Flex SDK into C:\air directory, then run build again +goto end +:failed +pause +:end +%AIRPATH%\bin\adl.exe app.xml \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/as3/CustomClass.as b/libsrc/ffdec_lib/testdata/as3/CustomClass.as new file mode 100644 index 000000000..3f268b9dc --- /dev/null +++ b/libsrc/ffdec_lib/testdata/as3/CustomClass.as @@ -0,0 +1,34 @@ +package +{ + import flash.utils.IExternalizable; + import flash.utils.IDataOutput; + import flash.utils.IDataInput; + + public class CustomClass implements IExternalizable + { + + private var val8:int; + private var val32:int; + public function CustomClass() + { + } + + public function setVal8(v:int){ + this.val8 = v; + } + public function setVal32(v:int){ + this.val32 = v; + } + + public function writeExternal(output:IDataOutput):void { + output.writeByte(val8); + output.writeInt(val32); + + } + + public function readExternal(input:IDataInput):void { + this.val8 = input.readByte(); + this.val32 = input.readInt(); + } + } +} \ No newline at end of file diff --git a/libsrc/ttf/nbproject/build-impl.xml b/libsrc/ttf/nbproject/build-impl.xml index 41c1b513d..0e2c1d29f 100644 --- a/libsrc/ttf/nbproject/build-impl.xml +++ b/libsrc/ttf/nbproject/build-impl.xml @@ -1,1413 +1,1419 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must set src.dir - Must set test.src.dir - Must set build.dir - Must set dist.dir - Must set build.classes.dir - Must set dist.javadoc.dir - Must set build.test.classes.dir - Must set build.test.results.dir - Must set build.classes.excludes - Must set dist.jar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must set javac.includes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - No tests executed. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must set JVM to use for profiling in profiler.info.jvm - Must set profiler agent JVM arguments in profiler.info.jvmargs.agent - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must select some files in the IDE or set javac.includes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - To run this application from the command line without Ant, try: - - java -jar "${dist.jar.resolved}" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must select one file in the IDE or set run.class - - - - Must select one file in the IDE or set run.class - - - - - - - - - - - - - - - - - - - - - - - Must select one file in the IDE or set debug.class - - - - - Must select one file in the IDE or set debug.class - - - - - Must set fix.includes - - - - - - - - - - This target only works when run from inside the NetBeans IDE. - - - - - - - - - Must select one file in the IDE or set profile.class - This target only works when run from inside the NetBeans IDE. - - - - - - - - - This target only works when run from inside the NetBeans IDE. - - - - - - - - - - - - - This target only works when run from inside the NetBeans IDE. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must select one file in the IDE or set run.class - - - - - - Must select some files in the IDE or set test.includes - - - - - Must select one file in the IDE or set run.class - - - - - Must select one file in the IDE or set applet.url - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must select some files in the IDE or set javac.includes - - - - - - - - - - - - - - - - - - - - Some tests failed; see details above. - - - - - - - - - Must select some files in the IDE or set test.includes - - - - Some tests failed; see details above. - - - - Must select some files in the IDE or set test.class - Must select some method in the IDE or set test.method - - - - Some tests failed; see details above. - - - - - Must select one file in the IDE or set test.class - - - - Must select one file in the IDE or set test.class - Must select some method in the IDE or set test.method - - - - - - - - - - - - - - Must select one file in the IDE or set applet.url - - - - - - - - - Must select one file in the IDE or set applet.url - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set test.src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No tests executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + Must select one file in the IDE or set profile.class + This target only works when run from inside the NetBeans IDE. + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + Must select some files in the IDE or set test.includes + + + + + Must select one file in the IDE or set run.class + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + Must select some files in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + Must select one file in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libsrc/ttf/nbproject/genfiles.properties b/libsrc/ttf/nbproject/genfiles.properties index 29b2c0e4d..32a75fe67 100644 --- a/libsrc/ttf/nbproject/genfiles.properties +++ b/libsrc/ttf/nbproject/genfiles.properties @@ -1,8 +1,8 @@ -build.xml.data.CRC32=ffc041f7 -build.xml.script.CRC32=9bce9219 -build.xml.stylesheet.CRC32=8064a381@1.68.1.46 -# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. -# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. -nbproject/build-impl.xml.data.CRC32=ffc041f7 -nbproject/build-impl.xml.script.CRC32=fdde8048 -nbproject/build-impl.xml.stylesheet.CRC32=876e7a8f@1.74.1.48 +build.xml.data.CRC32=ffc041f7 +build.xml.script.CRC32=9bce9219 +build.xml.stylesheet.CRC32=8064a381@1.68.1.46 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=ffc041f7 +nbproject/build-impl.xml.script.CRC32=c196f24b +nbproject/build-impl.xml.stylesheet.CRC32=05530350@1.79.1.48 diff --git a/src/com/jpexs/decompiler/flash/gui/GenericTagPanel.java b/src/com/jpexs/decompiler/flash/gui/GenericTagPanel.java index 67e2522f0..efa2c213a 100644 --- a/src/com/jpexs/decompiler/flash/gui/GenericTagPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/GenericTagPanel.java @@ -17,12 +17,15 @@ package com.jpexs.decompiler.flash.gui; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.amf.amf3.Amf3Value; +import com.jpexs.decompiler.flash.gui.generictageditors.Amf3ValueEditor; import com.jpexs.decompiler.flash.gui.generictageditors.BinaryDataEditor; import com.jpexs.decompiler.flash.gui.generictageditors.BooleanEditor; import com.jpexs.decompiler.flash.gui.generictageditors.ChangeListener; import com.jpexs.decompiler.flash.gui.generictageditors.ColorEditor; import com.jpexs.decompiler.flash.gui.generictageditors.GenericTagEditor; import com.jpexs.decompiler.flash.gui.generictageditors.NumberEditor; +import com.jpexs.decompiler.flash.gui.generictageditors.ScrollPanedEditor; import com.jpexs.decompiler.flash.gui.generictageditors.StringEditor; import com.jpexs.decompiler.flash.gui.helpers.SpringUtilities; import com.jpexs.decompiler.flash.tags.Tag; @@ -409,6 +412,8 @@ public class GenericTagPanel extends JPanel implements ChangeListener { editor = new ColorEditor(name, obj, field, index, type); } else if (type.equals(ByteArrayRange.class)) { editor = new BinaryDataEditor(mainPanel, name, obj, field, index, type); + } else if (type.equals(Amf3Value.class)) { + editor = new ScrollPanedEditor(new Amf3ValueEditor(name, obj, field, index, type)); } else { if (value == null) { if (readonly) { @@ -436,6 +441,8 @@ public class GenericTagPanel extends JPanel implements ChangeListener { fieldPaths.put(name, parList); fieldIndices.put(name, parIndices); addRow(name, editor, field); + + ce.added(); } return 1; } diff --git a/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java b/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java index 6f27fc481..96eb27d64 100644 --- a/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java @@ -17,11 +17,14 @@ package com.jpexs.decompiler.flash.gui; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.amf.amf3.Amf3Value; +import com.jpexs.decompiler.flash.gui.generictageditors.Amf3ValueEditor; import com.jpexs.decompiler.flash.gui.generictageditors.BinaryDataEditor; import com.jpexs.decompiler.flash.gui.generictageditors.BooleanEditor; import com.jpexs.decompiler.flash.gui.generictageditors.ColorEditor; import com.jpexs.decompiler.flash.gui.generictageditors.GenericTagEditor; import com.jpexs.decompiler.flash.gui.generictageditors.NumberEditor; +import com.jpexs.decompiler.flash.gui.generictageditors.ScrollPanedEditor; import com.jpexs.decompiler.flash.gui.generictageditors.StringEditor; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.base.ASMSource; @@ -166,6 +169,8 @@ public class GenericTagTreePanel extends GenericTagPanel { editor = new ColorEditor(field.getName(), obj, field, index, type); } else if (type.equals(byte[].class) || type.equals(ByteArrayRange.class)) { editor = new BinaryDataEditor(mainPanel, field.getName(), obj, field, index, type); + } else if (type.equals(Amf3Value.class)) { + editor = new ScrollPanedEditor(new Amf3ValueEditor(field.getName(), obj, field, index, type)); } if (editor != null) { if (editors == null) { @@ -199,6 +204,9 @@ public class GenericTagTreePanel extends GenericTagPanel { nameLabel.setSize(nameLabel.getWidth(), editorComponent.getHeight()); editorComponent.setAlignmentY(TOP_ALIGNMENT); pan.add(editorComponent); + if (editorComponent instanceof GenericTagEditor) { + ((GenericTagEditor) editorComponent).added(); + } pan.setPreferredSize(new Dimension((int) nameLabel.getPreferredSize().getWidth() + 5 + (int) editorComponent.getPreferredSize().getWidth(), (int) editorComponent.getPreferredSize().getHeight())); } else { pan.setPreferredSize(new Dimension((int) nameLabel.getPreferredSize().getWidth(), (int) nameLabel.getPreferredSize().getHeight())); @@ -946,6 +954,8 @@ public class GenericTagTreePanel extends GenericTagPanel { return true; } else if (isByteArray || type.equals(ByteArrayRange.class)) { return true; + } else if (type.equals(Amf3Value.class)) { + return true; } else { return false; } diff --git a/src/com/jpexs/decompiler/flash/gui/generictageditors/Amf3ValueEditor.java b/src/com/jpexs/decompiler/flash/gui/generictageditors/Amf3ValueEditor.java new file mode 100644 index 000000000..222394eca --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/generictageditors/Amf3ValueEditor.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2016 Jindra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.generictageditors; + +import com.jpexs.decompiler.flash.amf.amf3.Amf3Value; +import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter; +import com.jpexs.decompiler.flash.importers.amf.amf3.Amf3Importer; +import com.jpexs.decompiler.flash.importers.amf.amf3.Amf3ParseException; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.ReflectionTools; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.io.IOException; +import java.lang.reflect.Field; +import javax.swing.JEditorPane; + +/** + * + * @author JPEXS + */ +public class Amf3ValueEditor extends JEditorPane implements GenericTagEditor { + + private final Object obj; + + private final Field field; + + private final int index; + + private final Class type; + + private String fieldName; + + @Override + public void added() { + String s = getText(); + setContentType("text/javascript"); + setText(s); + } + + @Override + public boolean getScrollableTracksViewportWidth() { + return true; + } + + /* @Override + public Dimension getPreferredSize() { + Dimension ret = super.getPreferredSize(); + ret.width = 300; + return ret; + }*/ + @Override + public Component.BaselineResizeBehavior getBaselineResizeBehavior() { + return Component.BaselineResizeBehavior.CONSTANT_ASCENT; + } + + @Override + public int getBaseline(int width, int height) { + return 0; + } + + public Amf3ValueEditor(String fieldName, Object obj, Field field, int index, Class type) { + this.obj = obj; + this.field = field; + this.index = index; + this.type = type; + this.fieldName = fieldName; + try { + Amf3Value val = (Amf3Value) ReflectionTools.getValue(obj, field, index); + if (val == null || val.getValue() == null) { + setText(""); + } else { + String stringVal = Amf3Exporter.amfToString(val.getValue()); + setText(stringVal); + } + } catch (IllegalArgumentException | IllegalAccessException ex) { + // ignore + } + } + + @Override + public void save() { + try { + ReflectionTools.setValue(obj, field, index, getChangedValue()); + } catch (IllegalArgumentException | IllegalAccessException ex) { + //ignore + } + } + + @Override + public void addChangeListener(final ChangeListener l) { + final GenericTagEditor t = this; + addFocusListener(new FocusAdapter() { + + @Override + public void focusLost(FocusEvent e) { + l.change(t); + } + + }); + } + + @Override + public Object getChangedValue() { + Amf3Importer importer = new Amf3Importer(); + String textVal = getText(); + try { + return textVal.trim().isEmpty() ? null : new Amf3Value(importer.stringToAmf(textVal)); + } catch (IOException | Amf3ParseException ex) { + //ignore + } + return null; + } + + @Override + public String getFieldName() { + return fieldName; + } + + @Override + public Field getField() { + return field; + } + + @Override + public String getReadOnlyValue() { + return Helper.escapeHTML(getChangedValue().toString()); + } + +} diff --git a/src/com/jpexs/decompiler/flash/gui/generictageditors/BinaryDataEditor.java b/src/com/jpexs/decompiler/flash/gui/generictageditors/BinaryDataEditor.java index 030072b80..57f999c53 100644 --- a/src/com/jpexs/decompiler/flash/gui/generictageditors/BinaryDataEditor.java +++ b/src/com/jpexs/decompiler/flash/gui/generictageditors/BinaryDataEditor.java @@ -139,4 +139,9 @@ public class BinaryDataEditor extends JButton implements GenericTagEditor { public int getBaseline(int width, int height) { return 0; } + + @Override + public void added() { + + } } diff --git a/src/com/jpexs/decompiler/flash/gui/generictageditors/BooleanEditor.java b/src/com/jpexs/decompiler/flash/gui/generictageditors/BooleanEditor.java index 5f907548b..5474f3a2a 100644 --- a/src/com/jpexs/decompiler/flash/gui/generictageditors/BooleanEditor.java +++ b/src/com/jpexs/decompiler/flash/gui/generictageditors/BooleanEditor.java @@ -38,6 +38,11 @@ public class BooleanEditor extends JCheckBox implements GenericTagEditor { private final String fieldName; + @Override + public void added() { + + } + public BooleanEditor(String fieldName, Object obj, Field field, int index, Class type) { super(); this.obj = obj; diff --git a/src/com/jpexs/decompiler/flash/gui/generictageditors/ColorEditor.java b/src/com/jpexs/decompiler/flash/gui/generictageditors/ColorEditor.java index e01bc321e..8ac7fa53e 100644 --- a/src/com/jpexs/decompiler/flash/gui/generictageditors/ColorEditor.java +++ b/src/com/jpexs/decompiler/flash/gui/generictageditors/ColorEditor.java @@ -75,6 +75,11 @@ public class ColorEditor extends JPanel implements GenericTagEditor, ActionListe return colorType; } + @Override + public void added() { + + } + public ColorEditor(String fieldName, Object obj, Field field, int index, Class type) { this.obj = obj; this.field = field; diff --git a/src/com/jpexs/decompiler/flash/gui/generictageditors/GenericTagEditor.java b/src/com/jpexs/decompiler/flash/gui/generictageditors/GenericTagEditor.java index d105e315a..ff55a7f5e 100644 --- a/src/com/jpexs/decompiler/flash/gui/generictageditors/GenericTagEditor.java +++ b/src/com/jpexs/decompiler/flash/gui/generictageditors/GenericTagEditor.java @@ -24,6 +24,8 @@ import java.lang.reflect.Field; */ public interface GenericTagEditor { + public void added(); + public void save(); public void addChangeListener(ChangeListener l); diff --git a/src/com/jpexs/decompiler/flash/gui/generictageditors/NumberEditor.java b/src/com/jpexs/decompiler/flash/gui/generictageditors/NumberEditor.java index e79f46299..16b44fe92 100644 --- a/src/com/jpexs/decompiler/flash/gui/generictageditors/NumberEditor.java +++ b/src/com/jpexs/decompiler/flash/gui/generictageditors/NumberEditor.java @@ -57,6 +57,11 @@ public class NumberEditor extends JSpinner implements GenericTagEditor { return 0; } + @Override + public void added() { + + } + public NumberEditor(String fieldName, Object obj, Field field, int index, Class type, SWFType swfType) { setSize(100, getSize().height); setMaximumSize(getSize()); diff --git a/src/com/jpexs/decompiler/flash/gui/generictageditors/ScrollPanedEditor.java b/src/com/jpexs/decompiler/flash/gui/generictageditors/ScrollPanedEditor.java new file mode 100644 index 000000000..af151a97c --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/generictageditors/ScrollPanedEditor.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2016 Jindra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.generictageditors; + +import java.awt.Component; +import java.awt.Dimension; +import java.lang.reflect.Field; +import javax.swing.JScrollPane; + +/** + * + * @author JPEXS + */ +public class ScrollPanedEditor extends JScrollPane implements GenericTagEditor { + + private final GenericTagEditor editor; + + public ScrollPanedEditor(GenericTagEditor editor) { + super((Component) editor); + this.editor = editor; + Dimension d = new Dimension(500, 300); + setSize(d); + setPreferredSize(d); + } + + @Override + public void save() { + editor.save(); + } + + @Override + public void addChangeListener(ChangeListener l) { + editor.addChangeListener(l); + } + + @Override + public Object getChangedValue() { + return editor.getChangedValue(); + } + + @Override + public String getFieldName() { + return editor.getFieldName(); + } + + @Override + public Field getField() { + return editor.getField(); + } + + @Override + public String getReadOnlyValue() { + return editor.getReadOnlyValue(); + } + + @Override + public void added() { + editor.added(); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/generictageditors/StringEditor.java b/src/com/jpexs/decompiler/flash/gui/generictageditors/StringEditor.java index 4d7e8b218..c08cfb0cd 100644 --- a/src/com/jpexs/decompiler/flash/gui/generictageditors/StringEditor.java +++ b/src/com/jpexs/decompiler/flash/gui/generictageditors/StringEditor.java @@ -126,4 +126,9 @@ public class StringEditor extends JTextArea implements GenericTagEditor { public String getReadOnlyValue() { return Helper.escapeHTML(getChangedValue().toString()); } + + @Override + public void added() { + + } }