diff --git a/commit-test.txt b/commit-test.txt new file mode 100644 index 000000000..5aff529a2 --- /dev/null +++ b/commit-test.txt @@ -0,0 +1 @@ +Testing CI2 diff --git a/git_eol_normalization.sh b/git_eol_normalization.sh new file mode 100644 index 000000000..8120dfee8 --- /dev/null +++ b/git_eol_normalization.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e +git add . -u +git commit -m "Saving files before refreshing line endings" +git rm --cached -r . +git reset --hard +git add . +set +e +git commit -m "Normalize all the line endings" \ No newline at end of file diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java index 664af0e18..c0211c047 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -2513,6 +2513,10 @@ public final class SWF implements SWFContainerItem, Timelined { } } + public void clearSoundCache() { + soundCache.clear(); + } + public void clearScriptCache() { as2PcodeCache.clear(); as2Cache.clear(); 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 fb7fc28f6..6e2328a87 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java @@ -123,6 +123,8 @@ 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.dumpview.DumpInfoSpecial; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; import com.jpexs.decompiler.flash.tags.CSMTextSettingsTag; import com.jpexs.decompiler.flash.tags.DebugIDTag; import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; @@ -392,17 +394,25 @@ public class SWFInputStream implements AutoCloseable { is.seek(pos - startingPos); } - private void newDumpLevel(String name, String type) { + private DumpInfo newDumpLevel(String name, String type) { + return newDumpLevel(name, type, DumpInfoSpecialType.NONE, null); + } + + private DumpInfo newDumpLevel(String name, String type, DumpInfoSpecialType specialType, Object specialValue) { if (dumpInfo != null) { long startByte = is.getPos(); if (bitPos > 0) { startByte--; } - DumpInfo di = new DumpInfo(name, type, null, startByte, bitPos, 0, 0); + DumpInfo di = specialType == DumpInfoSpecialType.NONE + ? new DumpInfo(name, type, null, startByte, bitPos, 0, 0) + : new DumpInfoSpecial(name, type, null, startByte, bitPos, 0, 0, specialType, specialValue); di.parent = dumpInfo; dumpInfo.getChildInfos().add(di); dumpInfo = di; } + + return dumpInfo; } private void endDumpLevel() { @@ -784,11 +794,26 @@ public class SWFInputStream implements AutoCloseable { * @throws IOException */ public ByteArrayRange readByteRangeEx(long count, String name) throws IOException { + return readByteRangeEx(count, name, DumpInfoSpecialType.NONE, null); + } + + /** + * Reads byte range from the stream + * + * @param count Number of bytes to read + * @param name + * @param specialType + * @param specialValue + * @return ByteArrayRange object + * @throws IOException + */ + public ByteArrayRange readByteRangeEx(long count, String name, DumpInfoSpecialType specialType, Object specialValue) throws IOException { if (count <= 0) { return ByteArrayRange.EMPTY; } - newDumpLevel(name, "bytes"); + newDumpLevel(name, "bytes", specialType, specialValue); + int startPos = (int) getPos(); skipBytesEx(count); endDumpLevel(); @@ -1179,7 +1204,7 @@ public class SWFInputStream implements AutoCloseable { boolean isAS3 = false; while (available() > 0) { long pos = getPos(); - newDumpLevel(null, "TAG"); + newDumpLevel(null, "TAG", DumpInfoSpecialType.TAG, getPos()); try { tag = readTag(timelined, level, pos, false, parallel1, skipUnusualTags, lazy); } catch (EOFException | EndOfStreamException ex) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABC.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABC.java index 29f090534..0b94e7080 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABC.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABC.java @@ -55,11 +55,9 @@ import com.jpexs.decompiler.flash.abc.usages.MethodParamsMultinameUsage; import com.jpexs.decompiler.flash.abc.usages.MethodReturnTypeMultinameUsage; import com.jpexs.decompiler.flash.abc.usages.MultinameUsage; import com.jpexs.decompiler.flash.abc.usages.TypeNameMultinameUsage; -import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.dumpview.DumpInfo; -import com.jpexs.decompiler.flash.exporters.script.LinkReportExporter; -import com.jpexs.decompiler.flash.flexsdk.As3ScriptReplacer; -import com.jpexs.decompiler.flash.flexsdk.MxmlcException; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecial; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; import com.jpexs.decompiler.flash.helpers.SWFDecompilerPlugin; import com.jpexs.decompiler.flash.tags.ABCContainerTag; import com.jpexs.decompiler.flash.tags.Tag; @@ -629,16 +627,20 @@ public class ABC { bodies = new ArrayList<>(bodies_count); for (int i = 0; i < bodies_count; i++) { DumpInfo di = ais.dumpInfo; - ais.newDumpLevel("method_body", "method_body_info"); + DumpInfoSpecial dis = (DumpInfoSpecial) ais.newDumpLevel("method_body", "method_body_info", DumpInfoSpecialType.ABC_METHOD_BODY); MethodBody mb = new MethodBody(this, null, null, null); // do not create Traits in constructor try { mb.method_info = ais.readU30("method_info"); + if (dis != null) { + dis.specialValue = mb.method_info; + } + mb.max_stack = ais.readU30("max_stack"); mb.max_regs = ais.readU30("max_regs"); mb.init_scope_depth = ais.readU30("init_scope_depth"); mb.max_scope_depth = ais.readU30("max_scope_depth"); int code_length = ais.readU30("code_length"); - mb.setCodeBytes(ais.readBytes(code_length, "code")); + mb.setCodeBytes(ais.readBytes(code_length, "code", DumpInfoSpecialType.ABC_CODE)); int ex_count = ais.readU30("ex_count"); mb.exceptions = new ABCException[ex_count]; for (int j = 0; j < ex_count; j++) { @@ -996,9 +998,7 @@ public class ABC { public List getScriptPacks(String packagePrefix, List allAbcs) { List ret = new ArrayList<>(); for (int i = 0; i < script_info.size(); i++) { - if (!script_info.get(i).deleted) { - ret.addAll(script_info.get(i).getPacks(this, i, packagePrefix, allAbcs)); - } + ret.addAll(script_info.get(i).getPacks(this, i, packagePrefix, allAbcs)); } return ret; } @@ -1362,63 +1362,49 @@ public class ABC { } public boolean replaceScriptPack(ScriptPack pack, String as) throws AVM2ParseException, CompilationException, IOException, InterruptedException { - - final boolean USE_FLEX = true; + String scriptName = pack.getPathScriptName() + ".as"; + int oldIndex = pack.scriptIndex; + int newIndex = script_info.size(); + String documentClass = getSwf().getDocumentClass(); + boolean isDocumentClass = documentClass != null && documentClass.equals(pack.getClassPath().toString()); boolean isSimple = pack.isSimple; - if (USE_FLEX) { - if (!pack.isSimple) { - return false; - } - As3ScriptReplacer asr = new As3ScriptReplacer(Configuration.flexSdkLocation.get(), new LinkReportExporter()); - try { - asr.replaceScript(pack.getSwf(), pack, as); - } catch (MxmlcException ex) { - throw new AVM2ParseException(ex.getMxmlcErrorOutput(), 0); - } + ScriptInfo si = script_info.get(oldIndex); + if (isSimple) { + si.delete(this, true); } else { - String scriptName = pack.getPathScriptName() + ".as"; - int oldIndex = pack.scriptIndex; - int newIndex = script_info.size(); - String documentClass = getSwf().getDocumentClass(); - boolean isDocumentClass = documentClass != null && documentClass.equals(pack.getClassPath().toString()); - - ScriptInfo si = script_info.get(oldIndex); - if (isSimple) { - si.delete(this, true); - } else { - for (int t : pack.traitIndices) { - si.traits.traits.get(t).delete(this, true); - } - } - - int newClassIndex = instance_info.size(); for (int t : pack.traitIndices) { - if (si.traits.traits.get(t) instanceof TraitClass) { - TraitClass tc = (TraitClass) si.traits.traits.get(t); - newClassIndex = tc.class_info + 1; - } - + si.traits.traits.get(t).delete(this, true); } - List otherAbcs = new ArrayList<>(pack.allABCs); - otherAbcs.remove(this); - ActionScript3Parser.compile(as, this, otherAbcs, isDocumentClass, scriptName, newClassIndex, oldIndex); - - if (isSimple) { - // Move newly added script to its position - script_info.set(oldIndex, script_info.get(newIndex)); - script_info.remove(newIndex); - } else { - script_info.get(newIndex).setModified(true); - //Note: Is deleting traits safe? - List todel = new ArrayList<>(new TreeSet<>(pack.traitIndices)); - for (int i = todel.size() - 1; i >= 0; i--) { - si.traits.traits.remove((int) todel.get(i)); - } - } - script_info.get(oldIndex).setModified(true); } + + int newClassIndex = instance_info.size(); + for (int t : pack.traitIndices) { + if (si.traits.traits.get(t) instanceof TraitClass) { + TraitClass tc = (TraitClass) si.traits.traits.get(t); + newClassIndex = tc.class_info + 1; + } + + } + List otherAbcs = new ArrayList<>(pack.allABCs); + otherAbcs.remove(this); + ActionScript3Parser.compile(as, this, otherAbcs, isDocumentClass, scriptName, newClassIndex, oldIndex); + + if (isSimple) { + // Move newly added script to its position + script_info.set(oldIndex, script_info.get(newIndex)); + script_info.remove(newIndex); + } else { + script_info.get(newIndex).setModified(true); + //Note: Is deleting traits safe? + List todel = new ArrayList<>(new TreeSet<>(pack.traitIndices)); + for (int i = todel.size() - 1; i >= 0; i--) { + si.traits.traits.remove((int) todel.get(i)); + } + } + + script_info.get(oldIndex).setModified(true); pack(); // removes old classes/methods ((Tag) parentTag).setModified(true); return !isSimple; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCInputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCInputStream.java index 9d69dfea1..8de309061 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCInputStream.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCInputStream.java @@ -1,535 +1,543 @@ -/* - * 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.abc; - -import com.jpexs.decompiler.flash.EndOfStreamException; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.abc.types.Decimal; -import com.jpexs.decompiler.flash.abc.types.Float4; -import com.jpexs.decompiler.flash.abc.types.InstanceInfo; -import com.jpexs.decompiler.flash.abc.types.MethodInfo; -import com.jpexs.decompiler.flash.abc.types.Multiname; -import com.jpexs.decompiler.flash.abc.types.Namespace; -import com.jpexs.decompiler.flash.abc.types.ValueKind; -import com.jpexs.decompiler.flash.abc.types.traits.Trait; -import com.jpexs.decompiler.flash.abc.types.traits.TraitClass; -import com.jpexs.decompiler.flash.abc.types.traits.TraitFunction; -import com.jpexs.decompiler.flash.abc.types.traits.TraitMethodGetterSetter; -import com.jpexs.decompiler.flash.abc.types.traits.TraitSlotConst; -import com.jpexs.decompiler.flash.abc.types.traits.Traits; -import com.jpexs.decompiler.flash.dumpview.DumpInfo; -import com.jpexs.helpers.MemoryInputStream; -import com.jpexs.helpers.utf8.Utf8Helper; -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -/** - * - * @author JPEXS - */ -public class ABCInputStream implements AutoCloseable { - - private static final int CLASS_PROTECTED_NS = 8; - - private static final int ATTR_METADATA = 4; - - private final MemoryInputStream is; - - private ByteArrayOutputStream bufferOs = null; - - public static final boolean DEBUG_READ = false; - - public DumpInfo dumpInfo; - - private byte[] stringDataBuffer = new byte[256]; - - public void startBuffer() { - if (bufferOs == null) { - bufferOs = new ByteArrayOutputStream(); - } else { - bufferOs.reset(); - } - } - - public byte[] stopBuffer() { - if (bufferOs == null) { - return SWFInputStream.BYTE_ARRAY_EMPTY; - } - byte[] ret = bufferOs.toByteArray(); - bufferOs.reset(); - return ret; - } - - public ABCInputStream(MemoryInputStream is) { - this.is = is; - } - - /** - * Sets position in bytes in the stream - * - * @param pos Number of bytes - * @throws java.io.IOException - */ - public void seek(long pos) throws IOException { - is.seek(pos); - } - - 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(); - } - } - } - - private int readInternal() throws IOException { - int i = is.read(); - if (i == -1) { - throw new EndOfStreamException(); - } - if (DEBUG_READ) { - System.out.println("Read:0x" + Integer.toHexString(i)); - } - if (bufferOs != null) { - if (i != -1) { - bufferOs.write(i); - } - } - return i; - } - - public int read(String name) throws IOException { - newDumpLevel(name, "byte"); - int ret = readInternal(); - endDumpLevel(ret); - return ret; - } - - private int read(byte[] b) throws IOException { - int currBytesRead = is.read(b); - if (DEBUG_READ) { - StringBuilder sb = new StringBuilder("Read["); - sb.append(currBytesRead); - sb.append('/'); - sb.append(b.length); - sb.append("]: "); - for (int jj = 0; jj < currBytesRead; jj++) { - sb.append("0x"); - sb.append(Integer.toHexString(b[jj])); - sb.append(' '); - } - System.out.println(sb.toString()); - } - if (bufferOs != null) { - if (currBytesRead > 0) { - bufferOs.write(b, 0, currBytesRead); - } - } - return currBytesRead; - } - - public int readU8(String name) throws IOException { - newDumpLevel(name, "U8"); - int ret = readInternal(); - endDumpLevel(ret); - return ret; - } - - private long readU32Internal() throws IOException { - int i; - long ret = 0; - int bytePos = 0; - int byteCount = 0; - boolean nextByte; - do { - i = readInternal(); - nextByte = (i >> 7) == 1; - i &= 0x7f; - ret += (((long) i) << bytePos); - byteCount++; - bytePos += 7; - } while (nextByte && byteCount < 5); - return ret; - } - - public long readU32(String name) throws IOException { - newDumpLevel(name, "U32"); - long ret = readU32Internal(); - endDumpLevel(ret); - return ret; - } - - private int readU30Internal() throws IOException { - long u32 = readU32Internal(); - //no bits above bit 30 - return (int) (u32 & 0x3FFFFFFF); - } - - public int readU30(String name) throws IOException { - newDumpLevel(name, "U30"); - int ret = readU30Internal(); - endDumpLevel(ret); - return ret; - } - - public int readS24(String name) throws IOException { - newDumpLevel(name, "S24"); - int ret = (readInternal()) + (readInternal() << 8) + (readInternal() << 16); - - if ((ret >> 23) == 1) { - ret |= 0xff000000; - } - - endDumpLevel(ret); - return ret; - } - - public int readU16(String name) throws IOException { - newDumpLevel(name, "U16"); - int ret = (readInternal()) + (readInternal() << 8); - endDumpLevel(ret); - return ret; - } - - public long readS32(String name) throws IOException { - int i; - long ret = 0; - int bytePos = 0; - int byteCount = 0; - boolean nextByte; - newDumpLevel(name, "S32"); - do { - i = readInternal(); - nextByte = (i >> 7) == 1; - i &= 0x7f; - ret += (i << bytePos); - byteCount++; - bytePos += 7; - if (bytePos == 35) { - if ((ret >> 31) == 1) { - ret = -(ret & 0x7fffffff); - } - break; - } - } while (nextByte && byteCount < 5); - endDumpLevel(ret); - return ret; - } - - public int available() throws IOException { - return is.available(); - } - - private long readLong() throws IOException { - safeRead(8, stringDataBuffer); - byte[] readBuffer = stringDataBuffer; - return (((long) readBuffer[7] << 56) - + ((long) (readBuffer[6] & 255) << 48) - + ((long) (readBuffer[5] & 255) << 40) - + ((long) (readBuffer[4] & 255) << 32) - + ((long) (readBuffer[3] & 255) << 24) - + ((readBuffer[2] & 255) << 16) - + ((readBuffer[1] & 255) << 8) - + ((readBuffer[0] & 255))); - } - - public double readDouble(String name) throws IOException { - newDumpLevel(name, "Double"); - long el = readLong(); - double ret = Double.longBitsToDouble(el); - endDumpLevel(ret); - return ret; - } - - private void safeRead(int count, byte[] data) throws IOException { - for (int i = 0; i < count; i++) { - data[i] = (byte) readInternal(); - } - } - - public Namespace readNamespace(String name) throws IOException { - newDumpLevel(name, "Namespace"); - int kind = read("kind"); - int name_index = 0; - for (int k = 0; k < Namespace.nameSpaceKinds.length; k++) { - if (Namespace.nameSpaceKinds[k] == kind) { - name_index = readU30("name_index"); - break; - } - } - endDumpLevel(); - return new Namespace(kind, name_index); - } - - public Multiname readMultiname(String name) throws IOException { - int kind = readU8("kind"); - Multiname result = null; - - newDumpLevel(name, "Multiname"); - if ((kind == Multiname.QNAME) || (kind == Multiname.QNAMEA)) { - int namespace_index = readU30("namespace_index"); - int name_index = readU30("name_index"); - result = Multiname.createQName(kind == Multiname.QNAMEA, name_index, namespace_index); - } else if ((kind == Multiname.RTQNAME) || (kind == Multiname.RTQNAMEA)) { - int name_index = readU30("name_index"); - result = Multiname.createRTQName(kind == Multiname.RTQNAMEA, name_index); - } else if ((kind == Multiname.RTQNAMEL) || (kind == Multiname.RTQNAMELA)) { - result = Multiname.createRTQNameL(kind == Multiname.RTQNAMELA); - } else if ((kind == Multiname.MULTINAME) || (kind == Multiname.MULTINAMEA)) { - int name_index = readU30("name_index"); - int namespace_set_index = readU30("namespace_set_index"); - result = Multiname.createMultiname(kind == Multiname.MULTINAMEA, name_index, namespace_set_index); - } else if ((kind == Multiname.MULTINAMEL) || (kind == Multiname.MULTINAMELA)) { - int namespace_set_index = readU30("namespace_set_index"); - result = Multiname.createMultinameL(kind == Multiname.MULTINAMELA, namespace_set_index); - } else if (kind == Multiname.TYPENAME) { - int qname_index = readU30("qname_index"); // Multiname index!!! - int paramsLength = readU30("paramsLength"); - int[] params = new int[paramsLength]; - for (int i = 0; i < paramsLength; i++) { - params[i] = readU30("param"); // multiname indices! - } - result = Multiname.createTypeName(qname_index, params); - } else { - throw new IOException("Unknown kind of Multiname:0x" + Integer.toHexString(kind)); - } - - endDumpLevel(); - return result; - } - - public MethodInfo readMethodInfo(String name) throws IOException { - newDumpLevel(name, "method_info"); - int param_count = readU30("param_count"); - int ret_type = readU30("ret_type"); - int[] param_types = new int[param_count]; - for (int i = 0; i < param_count; i++) { - param_types[i] = readU30("param_type"); - } - int name_index = readU30("name_index"); - int flags = read("flags"); - - // 1=need_arguments, 2=need_activation, 4=need_rest 8=has_optional (16=ignore_rest, 32=explicit,) 64=setsdxns, 128=has_paramnames - ValueKind[] optional = new ValueKind[0]; - if ((flags & 8) == 8) { // if has_optional - int optional_count = readU30("optional_count"); - optional = new ValueKind[optional_count]; - for (int i = 0; i < optional_count; i++) { - optional[i] = new ValueKind(readU30("value_index"), read("value_kind")); - } - } - - int[] param_names = new int[param_count]; - if ((flags & 128) == 128) { // if has_paramnames - for (int i = 0; i < param_count; i++) { - param_names[i] = readU30("param_name"); - } - } - - endDumpLevel(); - return new MethodInfo(param_types, ret_type, name_index, flags, optional, param_names); - } - - public Trait readTrait(String name) throws IOException { - newDumpLevel(name, "Trait"); - long pos = getPosition(); - startBuffer(); - int name_index = readU30("name_index"); - int kind = read("kind"); - int kindType = 0xf & kind; - int kindFlags = kind >> 4; - Trait trait; - - switch (kindType) { - case 0: // slot - case 6: // const - TraitSlotConst t1 = new TraitSlotConst(); - t1.slot_id = readU30("slot_id"); - t1.type_index = readU30("type_index"); - t1.value_index = readU30("value_index"); - if (t1.value_index != 0) { - t1.value_kind = read("value_kind"); - } - trait = t1; - break; - case 1: // method - case 2: // getter - case 3: // setter - TraitMethodGetterSetter t2 = new TraitMethodGetterSetter(); - t2.disp_id = readU30("disp_id"); - t2.method_info = readU30("method_info"); - trait = t2; - break; - case 4: // class - TraitClass t3 = new TraitClass(); - t3.slot_id = readU30("slot_id"); - t3.class_info = readU30("class_info"); - trait = t3; - break; - case 5: // function - TraitFunction t4 = new TraitFunction(); - t4.slot_id = readU30("slot_id"); - t4.method_info = readU30("method_info"); - trait = t4; - break; - default: - throw new IOException("Unknown trait kind:" + kind); - } - trait.fileOffset = pos; - trait.kindType = kindType; - trait.kindFlags = kindFlags; - trait.name_index = name_index; - if ((kindFlags & ATTR_METADATA) != 0) { - int metadata_count = readU30("metadata_count"); - trait.metadata = new int[metadata_count]; - for (int i = 0; i < metadata_count; i++) { - trait.metadata[i] = readU30("metadata"); - } - } - trait.bytes = stopBuffer(); - endDumpLevel(); - return trait; - } - - public Traits readTraits(String name) throws IOException { - newDumpLevel(name, "Traits"); - int count = readU30("count"); - Traits traits = new Traits(count); - for (int i = 0; i < count; i++) { - traits.traits.add(readTrait("trait")); - } - endDumpLevel(); - return traits; - } - - private byte[] readBytesInternal(int count) throws IOException { - byte[] ret = new byte[count]; - for (int i = 0; i < count; i++) { - ret[i] = (byte) readInternal(); - } - return ret; - } - - public byte[] readBytes(int count, String name) throws IOException { - newDumpLevel(name, "Bytes"); - byte[] ret = readBytesInternal(count); - endDumpLevel(); - return ret; - } - - public Decimal readDecimal(String name) throws IOException { - newDumpLevel(name, "Decimal"); - byte[] data = readBytesInternal(16); - endDumpLevel(); - return new Decimal(data); - } - - public Float readFloat(String name) throws IOException { - newDumpLevel(name, "Float"); - int intBits = (readInternal()) + (readInternal() << 8); - float ret = Float.intBitsToFloat(intBits); - endDumpLevel(ret); - return ret; - } - - public Float4 readFloat4(String name) throws IOException { - newDumpLevel(name, "Float4"); - float f1 = readFloat("value1"); - float f2 = readFloat("value2"); - float f3 = readFloat("value3"); - float f4 = readFloat("value4"); - Float4 ret = new Float4(f1, f2, f3, f4); - endDumpLevel(ret); - return ret; - } - - public InstanceInfo readInstanceInfo(String name) throws IOException { - newDumpLevel(name, "instance_info"); - InstanceInfo ret = new InstanceInfo(null); // do not create Traits in constructor - ret.name_index = readU30("name_index"); - ret.super_index = readU30("super_index"); - ret.flags = readInternal(); - if ((ret.flags & CLASS_PROTECTED_NS) != 0) { - ret.protectedNS = readU30("protectedNS"); - } - int interfaces_count = readU30("interfaces_count"); - ret.interfaces = new int[interfaces_count]; - for (int i = 0; i < interfaces_count; i++) { - ret.interfaces[i] = readU30("interface"); - } - ret.iinit_index = readU30("iinit_index"); - ret.instance_traits = readTraits("instance_traits"); - endDumpLevel(); - return ret; - } - - public String readString(String name) throws IOException { - newDumpLevel(name, "String"); - int length = readU30Internal(); - - // avoid creating new byte array every time - if (stringDataBuffer.length < length) { - int newLength = stringDataBuffer.length * 2; - while (newLength < length) { - newLength *= 2; - } - - stringDataBuffer = new byte[newLength]; - } - - safeRead(length, stringDataBuffer); - String r = new String(stringDataBuffer, 0, length, Utf8Helper.charset); - endDumpLevel(r); - return r; - } - - - /*public void markStart(){ - bytesRead=0; - }*/ - public long getPosition() { - return is.getPos(); - } - - @Override - public void close() { - } -} +/* + * 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.abc; + +import com.jpexs.decompiler.flash.EndOfStreamException; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.abc.types.Decimal; +import com.jpexs.decompiler.flash.abc.types.Float4; +import com.jpexs.decompiler.flash.abc.types.InstanceInfo; +import com.jpexs.decompiler.flash.abc.types.MethodInfo; +import com.jpexs.decompiler.flash.abc.types.Multiname; +import com.jpexs.decompiler.flash.abc.types.Namespace; +import com.jpexs.decompiler.flash.abc.types.ValueKind; +import com.jpexs.decompiler.flash.abc.types.traits.Trait; +import com.jpexs.decompiler.flash.abc.types.traits.TraitClass; +import com.jpexs.decompiler.flash.abc.types.traits.TraitFunction; +import com.jpexs.decompiler.flash.abc.types.traits.TraitMethodGetterSetter; +import com.jpexs.decompiler.flash.abc.types.traits.TraitSlotConst; +import com.jpexs.decompiler.flash.abc.types.traits.Traits; +import com.jpexs.decompiler.flash.dumpview.DumpInfo; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecial; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; +import com.jpexs.helpers.MemoryInputStream; +import com.jpexs.helpers.utf8.Utf8Helper; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * + * @author JPEXS + */ +public class ABCInputStream implements AutoCloseable { + + private static final int CLASS_PROTECTED_NS = 8; + + private static final int ATTR_METADATA = 4; + + private final MemoryInputStream is; + + private ByteArrayOutputStream bufferOs = null; + + public static final boolean DEBUG_READ = false; + + public DumpInfo dumpInfo; + + private byte[] stringDataBuffer = new byte[256]; + + public void startBuffer() { + if (bufferOs == null) { + bufferOs = new ByteArrayOutputStream(); + } else { + bufferOs.reset(); + } + } + + public byte[] stopBuffer() { + if (bufferOs == null) { + return SWFInputStream.BYTE_ARRAY_EMPTY; + } + byte[] ret = bufferOs.toByteArray(); + bufferOs.reset(); + return ret; + } + + public ABCInputStream(MemoryInputStream is) { + this.is = is; + } + + /** + * Sets position in bytes in the stream + * + * @param pos Number of bytes + * @throws java.io.IOException + */ + public void seek(long pos) throws IOException { + is.seek(pos); + } + + public DumpInfo newDumpLevel(String name, String type) { + return newDumpLevel(name, type, DumpInfoSpecialType.NONE); + } + + public DumpInfo newDumpLevel(String name, String type, DumpInfoSpecialType specialType) { + if (dumpInfo != null) { + long startByte = is.getPos(); + DumpInfo di = specialType == DumpInfoSpecialType.NONE + ? new DumpInfo(name, type, null, startByte, 0, 0, 0) + : new DumpInfoSpecial(name, type, null, startByte, 0, 0, 0, specialType); + 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(); + } + } + } + + private int readInternal() throws IOException { + int i = is.read(); + if (i == -1) { + throw new EndOfStreamException(); + } + if (DEBUG_READ) { + System.out.println("Read:0x" + Integer.toHexString(i)); + } + if (bufferOs != null) { + if (i != -1) { + bufferOs.write(i); + } + } + return i; + } + + public int read(String name) throws IOException { + newDumpLevel(name, "byte"); + int ret = readInternal(); + endDumpLevel(ret); + return ret; + } + + private int read(byte[] b) throws IOException { + int currBytesRead = is.read(b); + if (DEBUG_READ) { + StringBuilder sb = new StringBuilder("Read["); + sb.append(currBytesRead); + sb.append('/'); + sb.append(b.length); + sb.append("]: "); + for (int jj = 0; jj < currBytesRead; jj++) { + sb.append("0x"); + sb.append(Integer.toHexString(b[jj])); + sb.append(' '); + } + System.out.println(sb.toString()); + } + if (bufferOs != null) { + if (currBytesRead > 0) { + bufferOs.write(b, 0, currBytesRead); + } + } + return currBytesRead; + } + + public int readU8(String name) throws IOException { + newDumpLevel(name, "U8"); + int ret = readInternal(); + endDumpLevel(ret); + return ret; + } + + private long readU32Internal() throws IOException { + int i; + long ret = 0; + int bytePos = 0; + int byteCount = 0; + boolean nextByte; + do { + i = readInternal(); + nextByte = (i >> 7) == 1; + i &= 0x7f; + ret += (((long) i) << bytePos); + byteCount++; + bytePos += 7; + } while (nextByte && byteCount < 5); + return ret; + } + + public long readU32(String name) throws IOException { + newDumpLevel(name, "U32"); + long ret = readU32Internal(); + endDumpLevel(ret); + return ret; + } + + private int readU30Internal() throws IOException { + long u32 = readU32Internal(); + //no bits above bit 30 + return (int) (u32 & 0x3FFFFFFF); + } + + public int readU30(String name) throws IOException { + newDumpLevel(name, "U30"); + int ret = readU30Internal(); + endDumpLevel(ret); + return ret; + } + + public int readS24(String name) throws IOException { + newDumpLevel(name, "S24"); + int ret = (readInternal()) + (readInternal() << 8) + (readInternal() << 16); + + if ((ret >> 23) == 1) { + ret |= 0xff000000; + } + + endDumpLevel(ret); + return ret; + } + + public int readU16(String name) throws IOException { + newDumpLevel(name, "U16"); + int ret = (readInternal()) + (readInternal() << 8); + endDumpLevel(ret); + return ret; + } + + public long readS32(String name) throws IOException { + int i; + long ret = 0; + int bytePos = 0; + int byteCount = 0; + boolean nextByte; + newDumpLevel(name, "S32"); + do { + i = readInternal(); + nextByte = (i >> 7) == 1; + i &= 0x7f; + ret += (i << bytePos); + byteCount++; + bytePos += 7; + if (bytePos == 35) { + if ((ret >> 31) == 1) { + ret = -(ret & 0x7fffffff); + } + break; + } + } while (nextByte && byteCount < 5); + endDumpLevel(ret); + return ret; + } + + public int available() throws IOException { + return is.available(); + } + + private long readLong() throws IOException { + safeRead(8, stringDataBuffer); + byte[] readBuffer = stringDataBuffer; + return (((long) readBuffer[7] << 56) + + ((long) (readBuffer[6] & 255) << 48) + + ((long) (readBuffer[5] & 255) << 40) + + ((long) (readBuffer[4] & 255) << 32) + + ((long) (readBuffer[3] & 255) << 24) + + ((readBuffer[2] & 255) << 16) + + ((readBuffer[1] & 255) << 8) + + ((readBuffer[0] & 255))); + } + + public double readDouble(String name) throws IOException { + newDumpLevel(name, "Double"); + long el = readLong(); + double ret = Double.longBitsToDouble(el); + endDumpLevel(ret); + return ret; + } + + private void safeRead(int count, byte[] data) throws IOException { + for (int i = 0; i < count; i++) { + data[i] = (byte) readInternal(); + } + } + + public Namespace readNamespace(String name) throws IOException { + newDumpLevel(name, "Namespace"); + int kind = read("kind"); + int name_index = 0; + for (int k = 0; k < Namespace.nameSpaceKinds.length; k++) { + if (Namespace.nameSpaceKinds[k] == kind) { + name_index = readU30("name_index"); + break; + } + } + endDumpLevel(); + return new Namespace(kind, name_index); + } + + public Multiname readMultiname(String name) throws IOException { + int kind = readU8("kind"); + Multiname result = null; + + newDumpLevel(name, "Multiname"); + if ((kind == Multiname.QNAME) || (kind == Multiname.QNAMEA)) { + int namespace_index = readU30("namespace_index"); + int name_index = readU30("name_index"); + result = Multiname.createQName(kind == Multiname.QNAMEA, name_index, namespace_index); + } else if ((kind == Multiname.RTQNAME) || (kind == Multiname.RTQNAMEA)) { + int name_index = readU30("name_index"); + result = Multiname.createRTQName(kind == Multiname.RTQNAMEA, name_index); + } else if ((kind == Multiname.RTQNAMEL) || (kind == Multiname.RTQNAMELA)) { + result = Multiname.createRTQNameL(kind == Multiname.RTQNAMELA); + } else if ((kind == Multiname.MULTINAME) || (kind == Multiname.MULTINAMEA)) { + int name_index = readU30("name_index"); + int namespace_set_index = readU30("namespace_set_index"); + result = Multiname.createMultiname(kind == Multiname.MULTINAMEA, name_index, namespace_set_index); + } else if ((kind == Multiname.MULTINAMEL) || (kind == Multiname.MULTINAMELA)) { + int namespace_set_index = readU30("namespace_set_index"); + result = Multiname.createMultinameL(kind == Multiname.MULTINAMELA, namespace_set_index); + } else if (kind == Multiname.TYPENAME) { + int qname_index = readU30("qname_index"); // Multiname index!!! + int paramsLength = readU30("paramsLength"); + int[] params = new int[paramsLength]; + for (int i = 0; i < paramsLength; i++) { + params[i] = readU30("param"); // multiname indices! + } + result = Multiname.createTypeName(qname_index, params); + } else { + throw new IOException("Unknown kind of Multiname:0x" + Integer.toHexString(kind)); + } + + endDumpLevel(); + return result; + } + + public MethodInfo readMethodInfo(String name) throws IOException { + newDumpLevel(name, "method_info"); + int param_count = readU30("param_count"); + int ret_type = readU30("ret_type"); + int[] param_types = new int[param_count]; + for (int i = 0; i < param_count; i++) { + param_types[i] = readU30("param_type"); + } + int name_index = readU30("name_index"); + int flags = read("flags"); + + // 1=need_arguments, 2=need_activation, 4=need_rest 8=has_optional (16=ignore_rest, 32=explicit,) 64=setsdxns, 128=has_paramnames + ValueKind[] optional = new ValueKind[0]; + if ((flags & 8) == 8) { // if has_optional + int optional_count = readU30("optional_count"); + optional = new ValueKind[optional_count]; + for (int i = 0; i < optional_count; i++) { + optional[i] = new ValueKind(readU30("value_index"), read("value_kind")); + } + } + + int[] param_names = new int[param_count]; + if ((flags & 128) == 128) { // if has_paramnames + for (int i = 0; i < param_count; i++) { + param_names[i] = readU30("param_name"); + } + } + + endDumpLevel(); + return new MethodInfo(param_types, ret_type, name_index, flags, optional, param_names); + } + + public Trait readTrait(String name) throws IOException { + newDumpLevel(name, "Trait"); + long pos = getPosition(); + startBuffer(); + int name_index = readU30("name_index"); + int kind = read("kind"); + int kindType = 0xf & kind; + int kindFlags = kind >> 4; + Trait trait; + + switch (kindType) { + case 0: // slot + case 6: // const + TraitSlotConst t1 = new TraitSlotConst(); + t1.slot_id = readU30("slot_id"); + t1.type_index = readU30("type_index"); + t1.value_index = readU30("value_index"); + if (t1.value_index != 0) { + t1.value_kind = read("value_kind"); + } + trait = t1; + break; + case 1: // method + case 2: // getter + case 3: // setter + TraitMethodGetterSetter t2 = new TraitMethodGetterSetter(); + t2.disp_id = readU30("disp_id"); + t2.method_info = readU30("method_info"); + trait = t2; + break; + case 4: // class + TraitClass t3 = new TraitClass(); + t3.slot_id = readU30("slot_id"); + t3.class_info = readU30("class_info"); + trait = t3; + break; + case 5: // function + TraitFunction t4 = new TraitFunction(); + t4.slot_id = readU30("slot_id"); + t4.method_info = readU30("method_info"); + trait = t4; + break; + default: + throw new IOException("Unknown trait kind:" + kind); + } + trait.fileOffset = pos; + trait.kindType = kindType; + trait.kindFlags = kindFlags; + trait.name_index = name_index; + if ((kindFlags & ATTR_METADATA) != 0) { + int metadata_count = readU30("metadata_count"); + trait.metadata = new int[metadata_count]; + for (int i = 0; i < metadata_count; i++) { + trait.metadata[i] = readU30("metadata"); + } + } + trait.bytes = stopBuffer(); + endDumpLevel(); + return trait; + } + + public Traits readTraits(String name) throws IOException { + newDumpLevel(name, "Traits"); + int count = readU30("count"); + Traits traits = new Traits(count); + for (int i = 0; i < count; i++) { + traits.traits.add(readTrait("trait")); + } + endDumpLevel(); + return traits; + } + + private byte[] readBytesInternal(int count) throws IOException { + byte[] ret = new byte[count]; + for (int i = 0; i < count; i++) { + ret[i] = (byte) readInternal(); + } + return ret; + } + + public byte[] readBytes(int count, String name, DumpInfoSpecialType specialType) throws IOException { + newDumpLevel(name, "Bytes", specialType); + byte[] ret = readBytesInternal(count); + endDumpLevel(); + return ret; + } + + public Decimal readDecimal(String name) throws IOException { + newDumpLevel(name, "Decimal"); + byte[] data = readBytesInternal(16); + endDumpLevel(); + return new Decimal(data); + } + + public Float readFloat(String name) throws IOException { + newDumpLevel(name, "Float"); + int intBits = (readInternal()) + (readInternal() << 8); + float ret = Float.intBitsToFloat(intBits); + endDumpLevel(ret); + return ret; + } + + public Float4 readFloat4(String name) throws IOException { + newDumpLevel(name, "Float4"); + float f1 = readFloat("value1"); + float f2 = readFloat("value2"); + float f3 = readFloat("value3"); + float f4 = readFloat("value4"); + Float4 ret = new Float4(f1, f2, f3, f4); + endDumpLevel(ret); + return ret; + } + + public InstanceInfo readInstanceInfo(String name) throws IOException { + newDumpLevel(name, "instance_info"); + InstanceInfo ret = new InstanceInfo(null); // do not create Traits in constructor + ret.name_index = readU30("name_index"); + ret.super_index = readU30("super_index"); + ret.flags = readInternal(); + if ((ret.flags & CLASS_PROTECTED_NS) != 0) { + ret.protectedNS = readU30("protectedNS"); + } + int interfaces_count = readU30("interfaces_count"); + ret.interfaces = new int[interfaces_count]; + for (int i = 0; i < interfaces_count; i++) { + ret.interfaces[i] = readU30("interface"); + } + ret.iinit_index = readU30("iinit_index"); + ret.instance_traits = readTraits("instance_traits"); + endDumpLevel(); + return ret; + } + + public String readString(String name) throws IOException { + newDumpLevel(name, "String"); + int length = readU30Internal(); + + // avoid creating new byte array every time + if (stringDataBuffer.length < length) { + int newLength = stringDataBuffer.length * 2; + while (newLength < length) { + newLength *= 2; + } + + stringDataBuffer = new byte[newLength]; + } + + safeRead(length, stringDataBuffer); + String r = new String(stringDataBuffer, 0, length, Utf8Helper.charset); + endDumpLevel(r); + return r; + } + + + /*public void markStart(){ + bytesRead=0; + }*/ + public long getPosition() { + return is.getPos(); + } + + @Override + public void close() { + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/AVM2Code.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/AVM2Code.java index 87f49c4ac..c1013919a 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/AVM2Code.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/AVM2Code.java @@ -2095,9 +2095,9 @@ public class AVM2Code implements Cloneable { } // Declarations - DeclarationAVM2Item d[] = new DeclarationAVM2Item[regCount]; + DeclarationAVM2Item[] d = new DeclarationAVM2Item[regCount]; - int param_types[] = abc.method_info.get(body.method_info).param_types; + int[] param_types = abc.method_info.get(body.method_info).param_types; int r = 1; for (int i = 0; i < param_types.length; i++) { GraphTargetItem type; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AVM2SourceGenerator.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AVM2SourceGenerator.java index d6ec7864c..e7f86c494 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AVM2SourceGenerator.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AVM2SourceGenerator.java @@ -1,2682 +1,2682 @@ -/* - * 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.abc.avm2.parser.script; - -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SourceGeneratorLocalData; -import com.jpexs.decompiler.flash.abc.ABC; -import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; -import com.jpexs.decompiler.flash.abc.avm2.AVM2ConstantPool; -import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; -import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instructions; -import com.jpexs.decompiler.flash.abc.avm2.instructions.InstructionDefinition; -import com.jpexs.decompiler.flash.abc.avm2.instructions.construction.ConstructSuperIns; -import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.JumpIns; -import com.jpexs.decompiler.flash.abc.avm2.instructions.other.ReturnValueIns; -import com.jpexs.decompiler.flash.abc.avm2.instructions.other.ReturnVoidIns; -import com.jpexs.decompiler.flash.abc.avm2.model.AVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.ApplyTypeAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.BooleanAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.FloatValueAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.GetDescendantsAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.IntegerValueAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.LocalRegAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.NameValuePair; -import com.jpexs.decompiler.flash.abc.avm2.model.NanAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.NewObjectAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.NullAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.ReturnValueAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.ReturnVoidAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.StringAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.ThrowAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.UndefinedAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.WithAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.WithObjectAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.clauses.ForEachInAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.clauses.ForInAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.clauses.TryAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.IfCondition; -import com.jpexs.decompiler.flash.abc.avm2.parser.AVM2ParseException; -import com.jpexs.decompiler.flash.abc.avm2.parser.script.AbcIndexing.ClassIndex; -import com.jpexs.decompiler.flash.abc.types.ABCException; -import com.jpexs.decompiler.flash.abc.types.ClassInfo; -import com.jpexs.decompiler.flash.abc.types.ConvertData; -import com.jpexs.decompiler.flash.abc.types.InstanceInfo; -import com.jpexs.decompiler.flash.abc.types.MetadataInfo; -import com.jpexs.decompiler.flash.abc.types.MethodBody; -import com.jpexs.decompiler.flash.abc.types.MethodInfo; -import com.jpexs.decompiler.flash.abc.types.Multiname; -import com.jpexs.decompiler.flash.abc.types.Namespace; -import com.jpexs.decompiler.flash.abc.types.ScriptInfo; -import com.jpexs.decompiler.flash.abc.types.ValueKind; -import com.jpexs.decompiler.flash.abc.types.traits.Trait; -import com.jpexs.decompiler.flash.abc.types.traits.TraitClass; -import com.jpexs.decompiler.flash.abc.types.traits.TraitFunction; -import com.jpexs.decompiler.flash.abc.types.traits.TraitMethodGetterSetter; -import com.jpexs.decompiler.flash.abc.types.traits.TraitSlotConst; -import com.jpexs.decompiler.flash.abc.types.traits.Traits; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.ecma.EcmaScript; -import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; -import com.jpexs.decompiler.flash.helpers.GraphTextWriter; -import com.jpexs.decompiler.flash.helpers.NulWriter; -import com.jpexs.decompiler.graph.CompilationException; -import com.jpexs.decompiler.graph.DottedChain; -import com.jpexs.decompiler.graph.GraphSourceItem; -import com.jpexs.decompiler.graph.GraphTargetItem; -import com.jpexs.decompiler.graph.Loop; -import com.jpexs.decompiler.graph.ScopeStack; -import com.jpexs.decompiler.graph.SourceGenerator; -import com.jpexs.decompiler.graph.TypeItem; -import com.jpexs.decompiler.graph.model.AndItem; -import com.jpexs.decompiler.graph.model.BreakItem; -import com.jpexs.decompiler.graph.model.CommaExpressionItem; -import com.jpexs.decompiler.graph.model.ContinueItem; -import com.jpexs.decompiler.graph.model.DefaultItem; -import com.jpexs.decompiler.graph.model.DoWhileItem; -import com.jpexs.decompiler.graph.model.DuplicateItem; -import com.jpexs.decompiler.graph.model.FalseItem; -import com.jpexs.decompiler.graph.model.ForItem; -import com.jpexs.decompiler.graph.model.IfItem; -import com.jpexs.decompiler.graph.model.LocalData; -import com.jpexs.decompiler.graph.model.NotItem; -import com.jpexs.decompiler.graph.model.OrItem; -import com.jpexs.decompiler.graph.model.PopItem; -import com.jpexs.decompiler.graph.model.PushItem; -import com.jpexs.decompiler.graph.model.SwitchItem; -import com.jpexs.decompiler.graph.model.TernarOpItem; -import com.jpexs.decompiler.graph.model.TrueItem; -import com.jpexs.decompiler.graph.model.UnboundedTypeItem; -import com.jpexs.decompiler.graph.model.WhileItem; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -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; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * - * @author JPEXS - */ -public class AVM2SourceGenerator implements SourceGenerator { - - public final AbcIndexing abcIndex; - - public static final int MARK_E_START = 0; - - public static final int MARK_E_END = 1; - - public static final int MARK_E_TARGET = 2; - - public static final int MARK_E_FINALLYPART = 3; - - private AVM2Instruction ins(int instructionCode, int... operands) { - return new AVM2Instruction(0, instructionCode, operands); - } - - private AVM2Instruction ins(InstructionDefinition def, int... operands) { - return new AVM2Instruction(0, def, operands); - } - - @Override - public List generate(SourceGeneratorLocalData localData, FalseItem item) throws CompilationException { - return GraphTargetItem.toSourceMerge(localData, this, ins(AVM2Instructions.PushFalse)); - } - - @Override - public List generate(SourceGeneratorLocalData localData, TrueItem item) throws CompilationException { - return GraphTargetItem.toSourceMerge(localData, this, ins(AVM2Instructions.PushTrue)); - } - - public List generate(SourceGeneratorLocalData localData, GetDescendantsAVM2Item item) throws CompilationException { - - AVM2ConstantPool constants = abcIndex.getSelectedAbc().constants; - int[] nssa = new int[item.openedNamespaces.size()]; - for (int i = 0; i < item.openedNamespaces.size(); i++) { - nssa[i] = item.openedNamespaces.get(i).getCpoolIndex(abcIndex); - } - - int nsset = constants.getNamespaceSetId(nssa, true); - - return GraphTargetItem.toSourceMerge(localData, this, - item.object, - ins(AVM2Instructions.GetDescendants, constants.getMultinameId(Multiname.createMultiname(false, constants.getStringId(item.nameStr, true), nsset), true)) - ); - } - - @Override - public List generate(SourceGeneratorLocalData localData, AndItem item) throws CompilationException { - List ret = new ArrayList<>(); - ret.addAll(generateToActionList(localData, item.leftSide)); - ret.add(ins(AVM2Instructions.Dup)); - if (!("" + item.leftSide.returnType()).equals("Boolean")) { - ret.add(ins(AVM2Instructions.ConvertB)); - } - List andExpr = generateToActionList(localData, item.rightSide); - andExpr.add(0, ins(AVM2Instructions.Pop)); - int andExprLen = insToBytes(andExpr).length; - ret.add(ins(AVM2Instructions.IfFalse, andExprLen)); - ret.addAll(andExpr); - return ret; - - } - - private byte[] insToBytes(List code) { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - for (AVM2Instruction instruction : code) { - - baos.write(instruction.getBytes()); - - } - return baos.toByteArray(); - } catch (IOException ex) { - Logger.getLogger(AVM2SourceGenerator.class.getName()).log(Level.SEVERE, null, ex); - } - return SWFInputStream.BYTE_ARRAY_EMPTY; - } - - @Override - public List generate(SourceGeneratorLocalData localData, OrItem item) throws CompilationException { - List ret = new ArrayList<>(); - ret.addAll(generateToActionList(localData, item.leftSide)); - ret.add(ins(AVM2Instructions.Dup)); - if (!("" + item.leftSide.returnType()).equals("Boolean")) { - ret.add(ins(AVM2Instructions.ConvertB)); - } - List orExpr = generateToActionList(localData, item.rightSide); - orExpr.add(0, ins(AVM2Instructions.Pop)); - int orExprLen = insToBytes(orExpr).length; - ret.add(ins(AVM2Instructions.IfTrue, orExprLen)); - ret.addAll(orExpr); - return ret; - } - - public ArrayList toInsList(List items) { - ArrayList ret = new ArrayList<>(); - for (GraphSourceItem s : items) { - if (s instanceof AVM2Instruction) { - ret.add((AVM2Instruction) s); - } - } - return ret; - } - - private List nonempty(List list) { - if (list == null) { - return new ArrayList<>(); - } - return list; - } - - private List condition(SourceGeneratorLocalData localData, GraphTargetItem t, int offset) throws CompilationException { - if (t instanceof IfCondition) { - IfCondition ic = (IfCondition) t; - return GraphTargetItem.toSourceMerge(localData, this, ic.getLeftSide(), ic.getRightSide(), ins(ic.getIfDefinition(), offset)); - } - return GraphTargetItem.toSourceMerge(localData, this, t, ins(AVM2Instructions.IfTrue, offset)); - } - - private List notCondition(SourceGeneratorLocalData localData, GraphTargetItem t, int offset) throws CompilationException { - if (t instanceof IfCondition) { - IfCondition ic = (IfCondition) t; - return GraphTargetItem.toSourceMerge(localData, this, ic.getLeftSide(), ic.getRightSide(), ins(ic.getIfNotDefinition(), offset)); - } - return GraphTargetItem.toSourceMerge(localData, this, t, ins(AVM2Instructions.IfFalse, offset)); - } - - private List generateIf(SourceGeneratorLocalData localData, GraphTargetItem expression, List onTrueCmds, List onFalseCmds, boolean ternar) throws CompilationException { - List ret = new ArrayList<>(); - //ret.addAll(notCondition(localData, expression)); - List onTrue; - List onFalse = null; - if (ternar) { - onTrue = toInsList(onTrueCmds.get(0).toSource(localData, this)); - } else { - onTrue = generateToInsList(localData, onTrueCmds); - } - - if (onFalseCmds != null && !onFalseCmds.isEmpty()) { - if (ternar) { - onFalse = toInsList(onFalseCmds.get(0).toSource(localData, this)); - } else { - onFalse = generateToInsList(localData, onFalseCmds); - } - } - AVM2Instruction ajmp = null; - if (onFalse != null) { - if (!((!nonempty(onTrue).isEmpty()) - && ((onTrue.get(onTrue.size() - 1).definition instanceof ContinueJumpIns) - || ((onTrue.get(onTrue.size() - 1).definition instanceof BreakJumpIns))))) { - ajmp = ins(AVM2Instructions.Jump, 0); - onTrue.add(ajmp); - } - } - - byte[] onTrueBytes = insToBytes(onTrue); - int onTrueLen = onTrueBytes.length; - - ret.addAll(notCondition(localData, expression, onTrueLen)); - ret.addAll(onTrue); - - if (onFalse != null) { - byte[] onFalseBytes = insToBytes(onFalse); - int onFalseLen = onFalseBytes.length; - if (ajmp != null) { - ajmp.operands[0] = onFalseLen; - } - ret.addAll(onFalse); - } - return ret; - } - - public List generate(SourceGeneratorLocalData localData, XMLFilterAVM2Item item) throws CompilationException { - List ret = new ArrayList<>(); - final Reference counterReg = new Reference<>(0); - final Reference collectionReg = new Reference<>(0); - final Reference xmlListReg = new Reference<>(0); - List xmlListSetTemp = AssignableAVM2Item.setTemp(localData, this, xmlListReg); - AVM2ConstantPool constants = abcIndex.getSelectedAbc().constants; - ret.addAll(GraphTargetItem.toSourceMerge(localData, this, - ins(AVM2Instructions.PushByte, 0), - AssignableAVM2Item.setTemp(localData, this, counterReg), - item.object, - ins(AVM2Instructions.CheckFilter), - NameAVM2Item.generateCoerce(localData, this, TypeItem.UNBOUNDED), - AssignableAVM2Item.setTemp(localData, this, collectionReg), - ins(AVM2Instructions.GetLex, constants.getMultinameId(Multiname.createQName(false, constants.getStringId("XMLList", true), constants.getNamespaceId(Namespace.KIND_PACKAGE, "", 0, true)), true)), - ins(AVM2Instructions.PushString, constants.getStringId("", true)), - ins(AVM2Instructions.Construct, 1), - xmlListSetTemp - )); - final Reference tempVal1 = new Reference<>(0); - final Reference tempVal2 = new Reference<>(0); - - List forBody = toInsList(GraphTargetItem.toSourceMerge(localData, this, - ins(AVM2Instructions.Label), - AssignableAVM2Item.getTemp(localData, this, collectionReg), - AssignableAVM2Item.getTemp(localData, this, counterReg), - ins(AVM2Instructions.NextValue), - AssignableAVM2Item.dupSetTemp(localData, this, tempVal1), - AssignableAVM2Item.dupSetTemp(localData, this, tempVal2), - ins(AVM2Instructions.PushWith) - )); - localData.scopeStack.add(new LocalRegAVM2Item(null, null, tempVal2.getVal(), null)); - forBody.addAll(toInsList(item.value.toSource(localData, this))); - List trueBody = new ArrayList<>(); - trueBody.addAll(toInsList(AssignableAVM2Item.getTemp(localData, this, xmlListReg))); - trueBody.addAll(toInsList(AssignableAVM2Item.getTemp(localData, this, counterReg))); - trueBody.addAll(toInsList(AssignableAVM2Item.getTemp(localData, this, tempVal1))); - trueBody.add(ins(AVM2Instructions.SetProperty, constants.getMultinameId(Multiname.createMultinameL(false, NamespaceItem.getCpoolSetIndex(abcIndex, item.openedNamespaces)), true))); - forBody.add(ins(AVM2Instructions.IfFalse, insToBytes(trueBody).length)); - forBody.addAll(trueBody); - forBody.add(ins(AVM2Instructions.PopScope)); - localData.scopeStack.remove(localData.scopeStack.size() - 1); - forBody.addAll(toInsList(AssignableAVM2Item.killTemp(localData, this, Arrays.asList(tempVal2, tempVal1)))); - - int forBodyLen = insToBytes(forBody).length; - AVM2Instruction forwardJump = ins(AVM2Instructions.Jump, forBodyLen); - ret.add(forwardJump); - - List expr = new ArrayList<>(); - expr.add(ins(AVM2Instructions.HasNext2, collectionReg.getVal(), counterReg.getVal())); - AVM2Instruction backIf = ins(AVM2Instructions.IfTrue, 0); - expr.add(backIf); - - int exprLen = insToBytes(expr).length; - backIf.operands[0] = -(exprLen + forBodyLen); - - ret.addAll(forBody); - ret.addAll(expr); - ret.addAll(AssignableAVM2Item.killTemp(localData, this, Arrays.asList(collectionReg, counterReg))); - ret.addAll(AssignableAVM2Item.getTemp(localData, this, xmlListReg)); - ret.addAll(AssignableAVM2Item.killTemp(localData, this, Arrays.asList(xmlListReg))); - return ret; - } - - @Override - public List generate(SourceGeneratorLocalData localData, IfItem item) throws CompilationException { - return generateIf(localData, item.expression, item.onTrue, item.onFalse, false); - } - - private void fixSwitch(List code, int breakOffset, long loopId) { - fixLoop(code, breakOffset, Integer.MAX_VALUE, loopId); - } - - private void fixLoop(List code, int breakOffset, int continueOffset, long loopId) { - int pos = 0; - for (int a = 0; a < code.size(); a++) { - AVM2Instruction ins = code.get(a); - pos += ins.getBytesLength(); - if (ins.definition instanceof JumpIns) { - if (ins.definition instanceof ContinueJumpIns) { - if (continueOffset != Integer.MAX_VALUE) { - ins.operands[0] = (-pos + continueOffset); - ins.definition = AVM2Code.instructionSet[AVM2Instructions.Jump]; - } - } - if (ins.definition instanceof BreakJumpIns) { - ins.operands[0] = (-pos + breakOffset); - ins.definition = AVM2Code.instructionSet[AVM2Instructions.Jump]; - } - } - } - } - - @Override - public List generate(SourceGeneratorLocalData localData, TernarOpItem item) throws CompilationException { - List onTrue = new ArrayList<>(); - onTrue.add(item.onTrue); - List onFalse = new ArrayList<>(); - onFalse.add(item.onFalse); - return generateIf(localData, item.expression, onTrue, onFalse, true); - } - - @Override - public List generate(SourceGeneratorLocalData localData, WhileItem item) throws CompilationException { - List ret = new ArrayList<>(); - List whileExpr = new ArrayList<>(); - - List ex = new ArrayList<>(item.expression); - GraphTargetItem lastItem = null; - if (!ex.isEmpty()) { - lastItem = ex.remove(ex.size() - 1); - while (lastItem instanceof CommaExpressionItem) { - CommaExpressionItem cei = (CommaExpressionItem) lastItem; - ex.addAll(cei.commands); - lastItem = ex.remove(ex.size() - 1); - } - whileExpr.addAll(generateToInsList(localData, ex)); - } - List whileBody = generateToInsList(localData, item.commands); - AVM2Instruction forwardJump = ins(AVM2Instructions.Jump, 0); - ret.add(forwardJump); - whileBody.add(0, ins(AVM2Instructions.Label)); - ret.addAll(whileBody); - int whileBodyLen = insToBytes(whileBody).length; - forwardJump.operands[0] = whileBodyLen; - whileExpr.addAll(toInsList(condition(localData, lastItem, 0))); - int whileExprLen = insToBytes(whileExpr).length; - whileExpr.get(whileExpr.size() - 1).operands[0] = -(whileExprLen + whileBodyLen); //Assuming last is if instruction - ret.addAll(whileExpr); - fixLoop(whileBody, whileBodyLen + whileExprLen, whileBodyLen, item.loop.id); - return ret; - } - - public List generate(SourceGeneratorLocalData localData, ForEachInAVM2Item item) throws CompilationException { - return generateForIn(localData, item.loop, item.expression.collection, (AssignableAVM2Item) item.expression.object, item.commands, true); - } - - public List generate(SourceGeneratorLocalData localData, ForInAVM2Item item) throws CompilationException { - return generateForIn(localData, item.loop, item.expression.collection, (AssignableAVM2Item) item.expression.object, item.commands, false); - } - - public List generateForIn(SourceGeneratorLocalData localData, Loop loop, GraphTargetItem collection, AssignableAVM2Item assignable, List commands, final boolean each) throws CompilationException { - List ret = new ArrayList<>(); - final Reference counterReg = new Reference<>(0); - final Reference collectionReg = new Reference<>(0); - - if (assignable instanceof UnresolvedAVM2Item) { - assignable = (AssignableAVM2Item) ((UnresolvedAVM2Item) assignable).resolved; - } - - ret.addAll(GraphTargetItem.toSourceMerge(localData, this, - ins(AVM2Instructions.PushByte, 0), - AssignableAVM2Item.setTemp(localData, this, counterReg), - collection, - NameAVM2Item.generateCoerce(localData, this, TypeItem.UNBOUNDED), - AssignableAVM2Item.setTemp(localData, this, collectionReg) - )); - - GraphTargetItem assigned = new GraphTargetItem() { - - @Override - public GraphTextWriter appendTo(GraphTextWriter writer, LocalData localData) throws InterruptedException { - return null; - } - - @Override - public boolean hasReturnValue() { - return true; - } - - @Override - public GraphTargetItem returnType() { - return TypeItem.UNBOUNDED; - } - - @Override - public List toSource(SourceGeneratorLocalData localData, SourceGenerator generator) throws CompilationException { - return toSourceMerge(localData, generator, - AssignableAVM2Item.getTemp(localData, generator, collectionReg), - AssignableAVM2Item.getTemp(localData, generator, counterReg), - ins(each ? AVM2Instructions.NextValue : AVM2Instructions.NextName) - ); - } - }; - assignable.setAssignedValue(assigned); - - List forBody = toInsList(GraphTargetItem.toSourceMerge(localData, this, - ins(AVM2Instructions.Label), - assignable.toSourceIgnoreReturnValue(localData, this) - )); - - forBody.addAll(generateToInsList(localData, commands)); - int forBodyLen = insToBytes(forBody).length; - - AVM2Instruction forwardJump = ins(AVM2Instructions.Jump, forBodyLen); - ret.add(forwardJump); - - List expr = new ArrayList<>(); - expr.add(ins(AVM2Instructions.HasNext2, collectionReg.getVal(), counterReg.getVal())); - AVM2Instruction backIf = ins(AVM2Instructions.IfTrue, 0); - expr.add(backIf); - - int exprLen = insToBytes(expr).length; - backIf.operands[0] = -(exprLen + forBodyLen); - - fixLoop(forBody, forBodyLen + exprLen, forBodyLen, loop.id); - ret.addAll(forBody); - ret.addAll(expr); - ret.addAll(AssignableAVM2Item.killTemp(localData, this, Arrays.asList(collectionReg, counterReg))); - return ret; - } - - @Override - public List generate(SourceGeneratorLocalData localData, DoWhileItem item) throws CompilationException { - List ret = new ArrayList<>(); - List whileExpr = new ArrayList<>(); - - List ex = new ArrayList<>(item.expression); - GraphTargetItem lastItem = null; - if (!ex.isEmpty()) { - lastItem = ex.remove(ex.size() - 1); - while (lastItem instanceof CommaExpressionItem) { - CommaExpressionItem cei = (CommaExpressionItem) lastItem; - ex.addAll(cei.commands); - lastItem = ex.remove(ex.size() - 1); - } - whileExpr.addAll(generateToInsList(localData, ex)); - } - List dowhileBody = generateToInsList(localData, item.commands); - List labelBody = new ArrayList<>(); - labelBody.add(ins(AVM2Instructions.Label)); - int labelBodyLen = insToBytes(labelBody).length; - - AVM2Instruction forwardJump = ins(AVM2Instructions.Jump, labelBodyLen); - ret.add(forwardJump); - ret.addAll(labelBody); - ret.addAll(dowhileBody); - int dowhileBodyLen = insToBytes(dowhileBody).length; - whileExpr.addAll(toInsList(condition(localData, lastItem, 0))); - int dowhileExprLen = insToBytes(whileExpr).length; - whileExpr.get(whileExpr.size() - 1).operands[0] = -(dowhileExprLen + dowhileBodyLen + labelBodyLen); //Assuming last is if instruction - ret.addAll(whileExpr); - fixLoop(dowhileBody, dowhileBodyLen + dowhileExprLen, dowhileBodyLen, item.loop.id); - return ret; - } - - public List generate(SourceGeneratorLocalData localData, WithAVM2Item item) throws CompilationException { - - List ret = new ArrayList<>(); - ret.addAll(item.scope.toSource(localData, this)); - Reference tempReg = new Reference<>(0); - ret.addAll(AssignableAVM2Item.dupSetTemp(localData, this, tempReg)); - localData.scopeStack.add(new WithObjectAVM2Item(null, null, new LocalRegAVM2Item(null, null, tempReg.getVal(), null))); - ret.add(ins(AVM2Instructions.PushWith)); - ret.addAll(generate(localData, item.items)); - ret.add(ins(AVM2Instructions.PopScope)); - ret.addAll(AssignableAVM2Item.killTemp(localData, this, Arrays.asList(tempReg))); - localData.scopeStack.remove(localData.scopeStack.size() - 1); - return ret; - } - - @Override - public List generate(SourceGeneratorLocalData localData, ForItem item) throws CompilationException { - List ret = new ArrayList<>(); - List forExpr = new ArrayList<>(); - - List ex = new ArrayList<>(); - if (item.expression != null) { - ex.add(item.expression); - } else { - ex.add(new BooleanAVM2Item(null, null, true)); - } - GraphTargetItem lastItem = null; - if (!ex.isEmpty()) { - lastItem = ex.remove(ex.size() - 1); - while (lastItem instanceof CommaExpressionItem) { - CommaExpressionItem cei = (CommaExpressionItem) lastItem; - ex.addAll(cei.commands); - lastItem = ex.remove(ex.size() - 1); - } - forExpr.addAll(generateToInsList(localData, ex)); - } - List forBody = generateToInsList(localData, item.commands); - List forFinalCommands = generateToInsList(localData, item.finalCommands); - - ret.addAll(generateToInsList(localData, item.firstCommands)); - - AVM2Instruction forwardJump = ins(AVM2Instructions.Jump, 0); - ret.add(forwardJump); - forBody.add(0, ins(AVM2Instructions.Label)); - ret.addAll(forBody); - ret.addAll(forFinalCommands); - int forBodyLen = insToBytes(forBody).length; - int forFinalCLen = insToBytes(forFinalCommands).length; - forwardJump.operands[0] = forBodyLen + forFinalCLen; - forExpr.addAll(toInsList(condition(localData, lastItem, 0))); - int forExprLen = insToBytes(forExpr).length; - forExpr.get(forExpr.size() - 1).operands[0] = -(forExprLen + forBodyLen + forFinalCLen); //Assuming last is if instruction - ret.addAll(forExpr); - fixLoop(forBody, forBodyLen + forFinalCLen + forExprLen, forBodyLen, item.loop.id); - return ret; - } - - private long uniqLast = 0; - - public String uniqId() { - uniqLast++; - return "" + uniqLast; - } - - @Override - public List generate(SourceGeneratorLocalData localData, SwitchItem item) throws CompilationException { - List ret = new ArrayList<>(); - Reference switchedReg = new Reference<>(0); - AVM2Instruction forwardJump = ins(AVM2Instructions.Jump, 0); - ret.add(forwardJump); - - int defIndex = -1; - - for (int i = item.caseValues.size() - 1; i >= 0; i--) { - if (item.caseValues.get(i) instanceof DefaultItem) { - defIndex = i; - break; - } - } - - List cases = new ArrayList<>(); - cases.addAll(toInsList(new IntegerValueAVM2Item(null, null, (long) defIndex).toSource(localData, this))); - int cLen = insToBytes(cases).length; - List caseLast = new ArrayList<>(); - caseLast.add(0, ins(AVM2Instructions.Jump, cLen)); - caseLast.addAll(0, toInsList(new IntegerValueAVM2Item(null, null, (long) defIndex).toSource(localData, this))); - int cLastLen = insToBytes(caseLast).length; - caseLast.add(0, ins(AVM2Instructions.Jump, cLastLen)); - cases.addAll(0, caseLast); - - List preCases = new ArrayList<>(); - preCases.addAll(toInsList(item.switchedObject.toSource(localData, this))); - preCases.addAll(toInsList(AssignableAVM2Item.setTemp(localData, this, switchedReg))); - - for (int i = item.caseValues.size() - 1; i >= 0; i--) { - if (item.caseValues.get(i) instanceof DefaultItem) { - continue; - } - List sub = new ArrayList<>(); - sub.addAll(toInsList(new IntegerValueAVM2Item(null, null, (long) i).toSource(localData, this))); - sub.add(ins(AVM2Instructions.Jump, insToBytes(cases).length)); - int subLen = insToBytes(sub).length; - - cases.addAll(0, sub); - cases.add(0, ins(AVM2Instructions.IfStrictNe, subLen)); - cases.addAll(0, toInsList(AssignableAVM2Item.getTemp(localData, this, switchedReg))); - cases.addAll(0, toInsList(item.caseValues.get(i).toSource(localData, this))); - } - cases.addAll(0, preCases); - - AVM2Instruction lookupOp = new AVM2Instruction(0, AVM2Instructions.LookupSwitch, new int[item.caseValues.size() + 1 + 1]); - cases.addAll(toInsList(AssignableAVM2Item.killTemp(localData, this, Arrays.asList(switchedReg)))); - List bodies = new ArrayList<>(); - List bodiesOffsets = new ArrayList<>(); - int defOffset; - int casesLen = insToBytes(cases).length; - bodies.add(0, ins(AVM2Instructions.Label)); - bodies.add(ins(new BreakJumpIns(item.loop.id), 0)); //There could be two breaks when default clause ends with break, but official compiler does this too, so who cares... - defOffset = -(insToBytes(bodies).length + casesLen); - for (int i = item.caseCommands.size() - 1; i >= 0; i--) { - bodies.addAll(0, generateToInsList(localData, item.caseCommands.get(i))); - bodies.add(0, ins(AVM2Instructions.Label)); - bodiesOffsets.add(0, -(insToBytes(bodies).length + casesLen)); - } - lookupOp.operands[0] = defOffset; - lookupOp.operands[1] = item.valuesMapping.size(); - for (int i = 0; i < item.valuesMapping.size(); i++) { - lookupOp.operands[2 + i] = bodiesOffsets.get(item.valuesMapping.get(i)); - } - - forwardJump.operands[0] = insToBytes(bodies).length; - ret.addAll(bodies); - ret.addAll(cases); - ret.add(lookupOp); - fixSwitch(toInsList(ret), insToBytes(toInsList(ret)).length, uniqLast); - return ret; - } - - @Override - public List generate(SourceGeneratorLocalData localData, NotItem item) throws CompilationException { - /*if (item.getOriginal() instanceof Inverted) { - GraphTargetItem norig = ((Inverted) item).invert(); - return norig.toSource(localData, this); - }*/ - List ret = new ArrayList<>(); - ret.addAll(item.getOriginal().toSource(localData, this)); - ret.add(ins(AVM2Instructions.Not)); - return ret; - } - - @Override - public List generate(SourceGeneratorLocalData localData, DuplicateItem item) { - List ret = new ArrayList<>(); - ret.add(ins(AVM2Instructions.Dup)); - return ret; - } - - @Override - public List generate(SourceGeneratorLocalData localData, BreakItem item) { - List ret = new ArrayList<>(); - AVM2Instruction abreak = ins(new BreakJumpIns(item.loopId), 0); - ret.add(abreak); - return ret; - } - - public List generate(SourceGeneratorLocalData localData, FunctionAVM2Item item) throws CompilationException { - List ret = new ArrayList<>(); - int scope = 0; - if (!item.functionName.isEmpty()) { - ret.add(ins(AVM2Instructions.NewObject, 0)); - ret.add(ins(AVM2Instructions.PushWith)); - scope = localData.scopeStack.size(); - localData.scopeStack.add(new PropertyAVM2Item(null, item.functionName, abcIndex, new ArrayList<>(), localData.callStack)); - } - AVM2ConstantPool constants = abcIndex.getSelectedAbc().constants; - ret.add(ins(AVM2Instructions.NewFunction, method(false, constants.getStringId(item.functionName, true), true, false, localData.callStack, localData.pkg, item.needsActivation, item.subvariables, 0 /*Set later*/, item.hasRest, item.line, localData.currentClass, null, false, localData, item.paramTypes, item.paramNames, item.paramValues, item.body, item.retType))); - if (!item.functionName.isEmpty()) { - ret.add(ins(AVM2Instructions.Dup)); - ret.add(ins(AVM2Instructions.GetScopeObject, scope)); - ret.add(ins(AVM2Instructions.Swap)); - ret.add(ins(AVM2Instructions.SetProperty, constants.getMultinameId(Multiname.createQName(false, constants.getStringId(item.functionName, true), constants.getNamespaceId(Namespace.KIND_PACKAGE, localData.pkg, 0, true)), true))); - ret.add(ins(AVM2Instructions.PopScope)); - localData.scopeStack.remove(localData.scopeStack.size() - 1); - } - return ret; - } - - private static int currentFinId = 1; - - private static int finId() { - return currentFinId++; - } - - public List generate(SourceGeneratorLocalData localData, TryAVM2Item item) throws CompilationException { - List ret = new ArrayList<>(); - - boolean newFinallyReg = false; - List newex = new ArrayList<>(); - int aloneFinallyEx = -1; - int finallyEx = -1; - for (NameAVM2Item e : item.catchExceptions2) { - ABCException aex = new ABCException(); - aex.name_index = abcIndex.getSelectedAbc().constants.getMultinameId(Multiname.createQName(false, abcIndex.getSelectedAbc().constants.getStringId(e.getVariableName(), true), abcIndex.getSelectedAbc().constants.getNamespaceId(Namespace.KIND_PACKAGE, "", 0, true)), true); - aex.type_index = typeName(localData, e.type); - newex.add(aex); - } - int finId = 0; - if (item.finallyCommands != null) { - if (item.catchExceptions2.isEmpty()) { - ABCException aex = new ABCException(); - aex.name_index = 0; - aex.type_index = 0; - newex.add(aex); - aloneFinallyEx = newex.size() - 1; - } - ABCException aex = new ABCException(); - aex.name_index = 0; - aex.type_index = 0; - newex.add(aex); - finallyEx = newex.size() - 1; - if (localData.finallyRegister == -1) { - localData.finallyRegister = getFreeRegister(localData); - killRegister(localData, localData.finallyRegister); //reuse for catches - newFinallyReg = true; - } - finId = finId(); - } - - if (finallyEx > -1) { - localData.finallyCatches.add(finId); - } - List tryCmds = generateToInsList(localData, item.tryCommands); - - //int i = firstId + item.catchCommands.size() - 1; - List catches = new ArrayList<>(); - Reference tempReg = new Reference<>(0); - - List currentExceptionIds = new ArrayList<>(); - List> catchCmds = new ArrayList<>(); - for (int c = 0; c < item.catchCommands.size(); c++) { - int i = localData.exceptions.size(); - localData.exceptions.add(newex.get(c)); - - currentExceptionIds.add(i); - - //Reference tempReg=new Reference<>(0); - List catchCmd = new ArrayList<>(); - catchCmd.add(ins(AVM2Instructions.NewCatch, i)); - catchCmd.addAll(toInsList(AssignableAVM2Item.dupSetTemp(localData, this, tempReg))); - catchCmd.add(ins(AVM2Instructions.Dup)); - catchCmd.add(ins(AVM2Instructions.PushScope)); - catchCmd.add(ins(AVM2Instructions.Swap)); - catchCmd.add(ins(AVM2Instructions.SetSlot, 1)); - - for (AssignableAVM2Item a : item.catchVariables.get(c)) { - GraphTargetItem r = a; - if (r instanceof UnresolvedAVM2Item) { - r = ((UnresolvedAVM2Item) r).resolvedRoot; - } - if (r instanceof NameAVM2Item) { - NameAVM2Item n = (NameAVM2Item) r; - if (item.catchExceptions2.get(c).getVariableName().equals(n.getVariableName())) { - n.setSlotScope(localData.scopeStack.size()); - } - } - } - localData.scopeStack.add(new LocalRegAVM2Item(null, null, tempReg.getVal(), null)); - catchCmd.addAll(generateToInsList(localData, item.catchCommands.get(c))); - localData.scopeStack.remove(localData.scopeStack.size() - 1); - catchCmd.add(ins(AVM2Instructions.PopScope)); - catchCmd.addAll(toInsList(AssignableAVM2Item.killTemp(localData, this, Arrays.asList(tempReg)))); - catchCmds.add(catchCmd); - } - for (int c = item.catchCommands.size() - 1; c >= 0; c--) { - List preCatches = new ArrayList<>(); - /*preCatches.add(ins(AVM2Instructions.GetLocal0)); - preCatches.add(ins(AVM2Instructions.PushScope)); - preCatches.add(AssignableAVM2Item.generateGetLoc(localData.activationReg)); - preCatches.add(ins(AVM2Instructions.PushScope));*/ - for (GraphTargetItem s : localData.scopeStack) { - preCatches.addAll(toInsList(s.toSource(localData, this))); - if (s instanceof WithObjectAVM2Item) { - preCatches.add(ins(AVM2Instructions.PushWith)); - } else { - preCatches.add(ins(AVM2Instructions.PushScope)); - } - } - - //catchCmds.add(catchCmd); - preCatches.addAll(catchCmds.get(c)); - catches.addAll(0, preCatches); - catches.add(0, new ExceptionMarkAVM2Instruction(currentExceptionIds.get(c), MARK_E_TARGET)); - catches.add(0, ins(AVM2Instructions.Jump, insToBytes(catches).length)); - } - - if (aloneFinallyEx > -1) { - localData.exceptions.add(newex.get(aloneFinallyEx)); - aloneFinallyEx = localData.exceptions.size() - 1; - - } - if (finallyEx > -1) { - localData.exceptions.add(newex.get(finallyEx)); - finallyEx = localData.exceptions.size() - 1; - } - - for (int i : currentExceptionIds) { - ret.add(new ExceptionMarkAVM2Instruction(i, MARK_E_START)); - } - if (aloneFinallyEx > -1) { - ret.add(new ExceptionMarkAVM2Instruction(aloneFinallyEx, MARK_E_START)); - } - if (finallyEx > -1) { - ret.add(new ExceptionMarkAVM2Instruction(finallyEx, MARK_E_START)); - } - - ret.addAll(tryCmds); - - for (int i : currentExceptionIds) { - ret.add(new ExceptionMarkAVM2Instruction(i, MARK_E_END)); - } - if (aloneFinallyEx > -1) { - ret.add(new ExceptionMarkAVM2Instruction(aloneFinallyEx, MARK_E_END)); - } - - if (aloneFinallyEx > -1) { - List preCatches = new ArrayList<>(); - for (GraphTargetItem s : localData.scopeStack) { - preCatches.addAll(toInsList(s.toSource(localData, this))); - if (s instanceof WithObjectAVM2Item) { - preCatches.add(ins(AVM2Instructions.PushWith)); - } else { - preCatches.add(ins(AVM2Instructions.PushScope)); - } - } - preCatches.add(ins(AVM2Instructions.NewCatch, aloneFinallyEx)); - preCatches.addAll(toInsList(AssignableAVM2Item.dupSetTemp(localData, this, tempReg))); - preCatches.add(ins(AVM2Instructions.PushScope)); - preCatches.add(ins(AVM2Instructions.Throw)); - preCatches.add(ins(AVM2Instructions.PopScope)); - preCatches.addAll(toInsList(AssignableAVM2Item.killTemp(localData, this, Arrays.asList(tempReg)))); - catches.add(ins(AVM2Instructions.Jump, insToBytes(preCatches).length)); - catches.add(new ExceptionMarkAVM2Instruction(aloneFinallyEx, MARK_E_TARGET)); - catches.addAll(preCatches); - } - AVM2Instruction finSwitch = null; - AVM2Instruction pushDefIns = ins(AVM2Instructions.PushByte, 0); - - int defPos = 0; - if (finallyEx > -1) { - List preCatches = new ArrayList<>(); - preCatches.add(0, new ExceptionMarkAVM2Instruction(finallyEx, MARK_E_TARGET)); - for (GraphTargetItem s : localData.scopeStack) { - preCatches.addAll(toInsList(s.toSource(localData, this))); - if (s instanceof WithObjectAVM2Item) { - preCatches.add(ins(AVM2Instructions.PushWith)); - } else { - preCatches.add(ins(AVM2Instructions.PushScope)); - } - } - preCatches.add(ins(AVM2Instructions.NewCatch, finallyEx)); - preCatches.addAll(toInsList(AssignableAVM2Item.dupSetTemp(localData, this, tempReg))); - preCatches.add(ins(AVM2Instructions.PushScope)); - preCatches.add(ins(AVM2Instructions.PopScope)); - Reference tempReg2 = new Reference<>(0); - preCatches.add(ins(AVM2Instructions.Kill, tempReg.getVal())); - preCatches.add(ins(AVM2Instructions.CoerceA)); - preCatches.addAll(toInsList(AssignableAVM2Item.setTemp(localData, this, tempReg2))); - preCatches.add(pushDefIns); - - List finallySwitchCmds = new ArrayList<>(); - - finSwitch = new AVM2Instruction(0, AVM2Instructions.LookupSwitch, new int[1 + 1 + 1]); - finSwitch.operands[0] = finSwitch.getBytesLength(); - finSwitch.operands[1] = 0; //switch cnt - - List preFinallySwitch = new ArrayList<>(); - preFinallySwitch.add(ins(AVM2Instructions.Label)); - preFinallySwitch.add(ins(AVM2Instructions.Pop)); - int preFinallySwitchLen = insToBytes(preFinallySwitch).length; - - finallySwitchCmds.add(ins(AVM2Instructions.Label)); - finallySwitchCmds.addAll(toInsList(AssignableAVM2Item.getTemp(localData, this, tempReg2))); - finallySwitchCmds.add(ins(AVM2Instructions.Kill, tempReg2.getVal())); - finallySwitchCmds.add(ins(AVM2Instructions.Throw)); - finallySwitchCmds.add(ins(AVM2Instructions.PushByte, 255)); - finallySwitchCmds.add(ins(AVM2Instructions.PopScope)); - finallySwitchCmds.add(ins(AVM2Instructions.Kill, tempReg.getVal())); - - int finSwitchLen = insToBytes(finallySwitchCmds).length; - - preCatches.add(ins(AVM2Instructions.Jump, preFinallySwitchLen + finSwitchLen)); - AVM2Instruction fjump = ins(AVM2Instructions.Jump, 0); - fjump.operands[0] = insToBytes(preCatches).length + preFinallySwitchLen + finSwitchLen; - - preCatches.add(0, fjump); - preCatches.add(0, new ExceptionMarkAVM2Instruction(finallyEx, MARK_E_END)); - preCatches.add(0, ins(AVM2Instructions.PushByte, 255)); - - finallySwitchCmds.add(new ExceptionMarkAVM2Instruction(finallyEx, MARK_E_FINALLYPART)); - - int oldReg = localData.finallyRegister; - localData.finallyRegister = getFreeRegister(localData); - Integer cnt = localData.finallyCounter.get(finId); - if (cnt == null) { - cnt = -1; - } - defPos = cnt; - cnt++; //Skip default clause (throw) - localData.finallyCounter.put(finId, cnt); - finallySwitchCmds.addAll(generateToInsList(localData, item.finallyCommands)); - killRegister(localData, localData.finallyRegister); - localData.finallyRegister = oldReg; - finSwitchLen = insToBytes(finallySwitchCmds).length; - - finSwitch.operands[2] = -finSwitchLen; - preCatches.addAll(preFinallySwitch); - preCatches.addAll(finallySwitchCmds); - preCatches.add(finSwitch); - - catches.addAll(preCatches); - AssignableAVM2Item.killTemp(localData, this, Arrays.asList(tempReg, tempReg2)); - } - - ret.addAll(catches); - //localData.exceptions.addAll(newex); - - if (finallyEx > -1) { - localData.finallyCatches.remove(localData.finallyCatches.size() - 1); - } - if (newFinallyReg) { - localData.finallyRegister = -1; - killRegister(localData, localData.finallyRegister); - } - int pos = 0; - int finallyPos = 0; - int switchPos = 0; - for (int s = 0; s < ret.size(); s++) { - GraphSourceItem src = ret.get(s); - if (src == finSwitch) { - switchPos = pos; - } - if (src instanceof AVM2Instruction) { - AVM2Instruction ins = (AVM2Instruction) src; - if (ins instanceof ExceptionMarkAVM2Instruction) { - ExceptionMarkAVM2Instruction em = (ExceptionMarkAVM2Instruction) ins; - if (em.exceptionId == finallyEx && em.markType == MARK_E_FINALLYPART) { - finallyPos = pos; - ret.remove(s); - s--; - continue; - } - } - pos += ins.getBytesLength(); - } - - } - - if (finSwitch != null) { - pos = 0; - int defLoc = finSwitch.operands[2]; - List switchLoc = new ArrayList<>(); - boolean wasDef = false; - for (int s = 0; s < ret.size(); s++) { - GraphSourceItem src = ret.get(s); - if (src instanceof AVM2Instruction) { - AVM2Instruction ins = (AVM2Instruction) src; - if (ins.definition instanceof FinallyJumpIns) { - FinallyJumpIns fji = (FinallyJumpIns) ins.definition; - if (fji.getClauseId() == finId) { - List bet = new ArrayList<>(); - bet.add(ins(AVM2Instructions.Label)); - bet.add(ins(AVM2Instructions.Pop)); - int betLen = insToBytes(bet).length; - if (wasDef) { - ins.operands[0] = 0; - } else { - ins.operands[0] = finallyPos - (pos + ins.getBytesLength()); - } - ins.definition = AVM2Code.instructionSet[AVM2Instructions.Jump]; - switchLoc.add(pos + ins.getBytesLength() + betLen - switchPos); - } - } - pos += ins.getBytesLength(); - } - if (defPos == switchLoc.size() - 1) { - switchLoc.add(defLoc); - wasDef = true; - } - } - finSwitch.operands = new int[1 + 1 + switchLoc.size()]; - pushDefIns.operands[0] = defPos + 1; - int afterLoc = finSwitch.getBytesLength(); - finSwitch.operands[0] = afterLoc; - finSwitch.operands[1] = switchLoc.size() - 1; - for (int j = 0; j < switchLoc.size(); j++) { - finSwitch.operands[2 + j] = switchLoc.get(j); - } - } - - return ret; - } - - @Override - public List generate(SourceGeneratorLocalData localData, ContinueItem item) { - List ret = new ArrayList<>(); - AVM2Instruction acontinue = ins(new ContinueJumpIns(item.loopId), 0); - ret.add(acontinue); - return ret; - } - - public List generate(SourceGeneratorLocalData localData, ReturnValueAVM2Item item) throws CompilationException { - List ret = new ArrayList<>(); - ret.addAll(item.value.toSource(localData, this)); - if (!localData.finallyCatches.isEmpty()) { - ret.add(ins(AVM2Instructions.CoerceA)); - ret.add(AssignableAVM2Item.generateSetLoc(localData.finallyRegister)); - for (int i = localData.finallyCatches.size() - 1; i >= 0; i--) { - if (i < localData.finallyCatches.size() - 1) { - ret.add(ins(AVM2Instructions.Label)); - } - int clauseId = localData.finallyCatches.get(i); - Integer cnt = localData.finallyCounter.get(clauseId); - if (cnt == null) { - cnt = -1; - } - cnt++; - localData.finallyCounter.put(clauseId, cnt); - ret.addAll(new IntegerValueAVM2Item(null, null, (long) cnt).toSource(localData, this)); - ret.add(ins(new FinallyJumpIns(clauseId), 0)); - ret.add(ins(AVM2Instructions.Label)); - ret.add(ins(AVM2Instructions.Pop)); - } - ret.add(ins(AVM2Instructions.Label)); - ret.add(AssignableAVM2Item.generateGetLoc(localData.finallyRegister)); - ret.add(ins(AVM2Instructions.Kill, localData.finallyRegister)); - } - ret.add(ins(AVM2Instructions.ReturnValue)); - return ret; - } - - public List generate(SourceGeneratorLocalData localData, ReturnVoidAVM2Item item) throws CompilationException { - List ret = new ArrayList<>(); - if (!localData.finallyCatches.isEmpty()) { - - for (int i = 0; i < localData.finallyCatches.size(); i++) { - if (i > 0) { - ret.add(ins(AVM2Instructions.Label)); - } - int clauseId = localData.finallyCatches.get(i); - Integer cnt = localData.finallyCounter.get(clauseId); - if (cnt == null) { - cnt = -1; - } - cnt++; - localData.finallyCounter.put(clauseId, cnt); - ret.addAll(new IntegerValueAVM2Item(null, null, (long) cnt).toSource(localData, this)); - ret.add(ins(new FinallyJumpIns(clauseId), 0)); - ret.add(ins(AVM2Instructions.Label)); - ret.add(ins(AVM2Instructions.Pop)); - } - ret.add(ins(AVM2Instructions.Label)); - } - ret.add(ins(AVM2Instructions.ReturnVoid)); - return ret; - } - - public List generate(SourceGeneratorLocalData localData, ThrowAVM2Item item) throws CompilationException { - List ret = new ArrayList<>(); - ret.addAll(item.value.toSource(localData, this)); - ret.add(ins(AVM2Instructions.Throw)); - return ret; - } - - private List generateToInsList(SourceGeneratorLocalData localData, List commands) throws CompilationException { - return toInsList(generate(localData, commands)); - } - - private List generateToActionList(SourceGeneratorLocalData localData, GraphTargetItem command) throws CompilationException { - return toInsList(command.toSource(localData, this)); - } - - @Override - public List generate(SourceGeneratorLocalData localData, List commands) throws CompilationException { - List ret = new ArrayList<>(); - for (GraphTargetItem item : commands) { - ret.addAll(item.toSourceIgnoreReturnValue(localData, this)); - } - return ret; - } - - public HashMap getRegisterVars(SourceGeneratorLocalData localData) { - return localData.registerVars; - } - - public void setRegisterVars(SourceGeneratorLocalData localData, HashMap value) { - localData.registerVars = value; - } - - public void setInFunction(SourceGeneratorLocalData localData, int value) { - localData.inFunction = value; - } - - public int isInFunction(SourceGeneratorLocalData localData) { - return localData.inFunction; - } - - public boolean isInMethod(SourceGeneratorLocalData localData) { - return localData.inMethod; - } - - public void setInMethod(SourceGeneratorLocalData localData, boolean value) { - localData.inMethod = value; - } - - public int getForInLevel(SourceGeneratorLocalData localData) { - return localData.forInLevel; - } - - public void setForInLevel(SourceGeneratorLocalData localData, int value) { - localData.forInLevel = value; - } - - public int getTempRegister(SourceGeneratorLocalData localData) { - HashMap registerVars = getRegisterVars(localData); - int tmpReg = 0; - for (int i = 0; i < 256; i++) { - if (!registerVars.containsValue(i)) { - tmpReg = i; - break; - } - } - return tmpReg; - } - - public AVM2SourceGenerator(AbcIndexing abc) { - this.abcIndex = abc; - } - - /*public ABC getABC() { - return abc; - }*/ - public void generateClass(List importedClasses, List cinitVariables, boolean cinitNeedsActivation, List cinit, List openedNamespaces, int namespace, int initScope, DottedChain pkg, ClassInfo classInfo, InstanceInfo instanceInfo, SourceGeneratorLocalData localData, boolean isInterface, String name, String superName, GraphTargetItem extendsVal, List implementsStr, GraphTargetItem iinit, List iinitVariables, boolean iinitNeedsActivation, List traitItems, Reference class_index) throws AVM2ParseException, CompilationException { - localData.currentClass = name; - localData.pkg = pkg; - localData.privateNs = abcIndex.getSelectedAbc().constants.getNamespaceId(Namespace.KIND_PRIVATE, pkg.toRawString() + ":" + name, 0, true); - localData.protectedNs = abcIndex.getSelectedAbc().constants.getNamespaceId(Namespace.KIND_PROTECTED, pkg.toRawString() + ":" + name, 0, true); - if (extendsVal == null && !isInterface) { - extendsVal = new TypeItem(DottedChain.OBJECT); - } - ParsedSymbol s = null; - - if (Configuration.handleSkinPartsAutomatically.get()) { - - Map skinParts = new HashMap<>(); - for (GraphTargetItem t : traitItems) { - String tname = null; - List>> tmetadata = null; - if (t instanceof MethodAVM2Item) { - tname = ((MethodAVM2Item) t).functionName; - tmetadata = ((MethodAVM2Item) t).metadata; - } else if (t instanceof SlotAVM2Item) { - tname = ((SlotAVM2Item) t).var; - tmetadata = ((SlotAVM2Item) t).metadata; - } else if (t instanceof ConstAVM2Item) { - tname = ((ConstAVM2Item) t).var; - tmetadata = ((ConstAVM2Item) t).metadata; - } - if (tname != null && tmetadata != null) { - for (Map.Entry> en : tmetadata) { - if ("SkinPart".equals(en.getKey())) { - boolean req = false; - if (en.getValue().containsKey("required")) { - if ("true".equals(en.getValue().get("required"))) { - req = true; - } - } - skinParts.put(tname, req); - } - } - } - } - if (!skinParts.isEmpty()) { - - //Merge parts from _skinParts attribute of parent class - GraphTargetItem parent = extendsVal; - if (parent instanceof UnresolvedAVM2Item) { - parent = ((UnresolvedAVM2Item) parent).resolved; - } - if (parent instanceof TypeItem) { - ClassIndex ci = abcIndex.findClass(parent); - if (ci != null) { - int mi = ci.abc.class_info.get(ci.index).cinit_index; - MethodBody pcinit = ci.abc.findBody(mi); - ConvertData d = new ConvertData(); - - List initt = new ArrayList<>(); - initt.add(ci.abc.class_info.get(ci.index).static_traits); - - try { - pcinit.convert(d, "-", ScriptExportMode.AS, true, mi, -1, ci.index, ci.abc, null, new ScopeStack(), GraphTextWriter.TRAIT_CLASS_INITIALIZER, new NulWriter(), new ArrayList<>(), initt, false); - //FIXME! Add skinparts from _skinParts attribute of parent class!!! - } catch (InterruptedException ex) { - Logger.getLogger(AVM2SourceGenerator.class.getName()).log(Level.SEVERE, "Getting parent skinparts interrupted", ex); - } - for (Trait t : ci.abc.class_info.get(ci.index).static_traits.traits) { - if (t instanceof TraitSlotConst) { - TraitSlotConst tsc = (TraitSlotConst) t; - if (tsc.kindType == Trait.TRAIT_SLOT) { - if ("_skinParts".equals(tsc.getName(ci.abc).getName(ci.abc.constants, new ArrayList<>(), true))) { - if (d.assignedValues.containsKey(tsc)) { - if (d.assignedValues.get(tsc).value instanceof NewObjectAVM2Item) { - NewObjectAVM2Item no = (NewObjectAVM2Item) d.assignedValues.get(tsc).value; - for (NameValuePair nvp : no.pairs) { - skinParts.put(EcmaScript.toString(nvp.name.getResult()), EcmaScript.toBoolean(nvp.value.getResult())); - } - } - - } - } - } - } - } - - } - } - - /* - Add - override protected function get skinParts() : Object - { - return _skinParts; - } - */ - List getterBody = new ArrayList<>(); - UnresolvedAVM2Item sp = new UnresolvedAVM2Item(new ArrayList<>(), importedClasses, false, TypeItem.UNBOUNDED, 0, new DottedChain("_skinParts"), - null, openedNamespaces); - getterBody.add(new ReturnValueAVM2Item(null, null, sp)); - List subvars = new ArrayList<>(); - subvars.add(sp); - List> allopns = new ArrayList<>(); - allopns.add(openedNamespaces); - - GetterAVM2Item getter = new GetterAVM2Item(allopns, false, false, new ArrayList<>(), new NamespaceItem(pkg.toRawString() + ":" + name, Namespace.KIND_PROTECTED), isInterface, null, false, false, 0, - true, false, false, "skinParts", new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), - getterBody, subvars, new TypeItem("Object")); - - /* - Add - private static var _skinParts = {attr1:false, attr2:true}; - */ - List pairs = new ArrayList<>(); - for (String tname : skinParts.keySet()) { - pairs.add(new NameValuePair(new StringAVM2Item(null, null, tname), skinParts.get(tname) ? new TrueItem(null, null) : new FalseItem(null, null))); - } - - NewObjectAVM2Item sltVal = new NewObjectAVM2Item(null, null, pairs); - - SlotAVM2Item slt = new SlotAVM2Item( - new ArrayList<>(), new NamespaceItem(pkg.toRawString() + ":" + name, Namespace.KIND_PRIVATE), - null, true, "_skinParts", new TypeItem("Object"), sltVal, 0); - - traitItems.add(0, slt); - traitItems.add(getter); - - } - } - - Trait[] it = generateTraitsPhase1(importedClasses, openedNamespaces, name, superName, false, localData, traitItems, instanceInfo.instance_traits, class_index); - Trait[] st = generateTraitsPhase1(importedClasses, openedNamespaces, name, superName, true, localData, traitItems, classInfo.static_traits, class_index); - generateTraitsPhase2(importedClasses, pkg, traitItems, it, openedNamespaces, localData); - generateTraitsPhase2(importedClasses, pkg, traitItems, st, openedNamespaces, localData); - abcIndex.refreshSelected(); - generateTraitsPhase3(importedClasses, initScope, isInterface, name, superName, false, localData, traitItems, instanceInfo.instance_traits, it, new HashMap<>(), class_index); - generateTraitsPhase3(importedClasses, initScope, isInterface, name, superName, true, localData, traitItems, classInfo.static_traits, st, new HashMap<>(), class_index); - int init; - if (iinit == null || isInterface) { - instanceInfo.iinit_index = init = method(false, 0, false, isInterface, new ArrayList<>(), pkg, false, new ArrayList<>(), initScope + 1, false, 0, isInterface ? null : name, extendsVal != null ? extendsVal.toString() : null, true, localData, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), TypeItem.UNBOUNDED/*?? FIXME*/); - } else { - MethodAVM2Item m = (MethodAVM2Item) iinit; - instanceInfo.iinit_index = init = method(false, str(pkg.toRawString() + ":" + name + "/" + name), false, false, new ArrayList<>(), pkg, m.needsActivation, m.subvariables, initScope + 1, m.hasRest, m.line, name, extendsVal != null ? extendsVal.toString() : null, true, localData, m.paramTypes, m.paramNames, m.paramValues, m.body, TypeItem.UNBOUNDED/*?? FIXME*/); - } - - //Class initializer - int cinit_index = method(true, str(""), false, false, new ArrayList<>(), pkg, cinitNeedsActivation, cinitVariables, initScope + (implementsStr.isEmpty() ? 0 : 1), false, 0, isInterface ? null : name, superName, false, localData, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), cinit, TypeItem.UNBOUNDED); - MethodBody cinitBody = abcIndex.getSelectedAbc().findBody(cinit_index); - - List sinitcode = new ArrayList<>(); - List initcode = new ArrayList<>(); - for (GraphTargetItem ti : traitItems) { - if ((ti instanceof SlotAVM2Item) || (ti instanceof ConstAVM2Item)) { - GraphTargetItem val = null; - boolean isStatic = false; - int ns = -1; - String tname = null; - boolean isConst = false; - if (ti instanceof SlotAVM2Item) { - val = ((SlotAVM2Item) ti).value; - isStatic = ((SlotAVM2Item) ti).isStatic(); - ns = genNs(importedClasses, pkg, ((SlotAVM2Item) ti).pkg, openedNamespaces, localData, ((SlotAVM2Item) ti).line); - tname = ((SlotAVM2Item) ti).var; - } - if (ti instanceof ConstAVM2Item) { - val = ((ConstAVM2Item) ti).value; - isStatic = ((ConstAVM2Item) ti).isStatic(); - ns = genNs(importedClasses, pkg, ((ConstAVM2Item) ti).pkg, openedNamespaces, localData, ((ConstAVM2Item) ti).line); - tname = ((ConstAVM2Item) ti).var; - isConst = true; - } - if (isStatic && val != null) { - sinitcode.add(ins(AVM2Instructions.FindProperty, traitName(ns, tname))); - localData.isStatic = true; - sinitcode.addAll(toInsList(val.toSource(localData, this))); - sinitcode.add(ins(isConst ? AVM2Instructions.InitProperty : AVM2Instructions.SetProperty, traitName(ns, tname))); - } - if (!isStatic && val != null) { - //do not init basic values, that can be stored in trait - if (!(val instanceof IntegerValueAVM2Item) && !(val instanceof StringAVM2Item) && !(val instanceof BooleanAVM2Item) && !(val instanceof NullAVM2Item) && !(val instanceof UndefinedAVM2Item)) { - initcode.add(ins(AVM2Instructions.GetLocal0)); - localData.isStatic = false; - initcode.addAll(toInsList(val.toSource(localData, this))); - initcode.add(ins(isConst ? AVM2Instructions.InitProperty : AVM2Instructions.SetProperty, traitName(ns, tname))); - } - } - } - } - MethodBody initBody = null; - if (!isInterface) { - initBody = abcIndex.getSelectedAbc().findBody(init); - initBody.insertAll(iinit == null ? 0 : 2, initcode);//after getlocal0,pushscope - - if (cinitBody.getCode().code.get(cinitBody.getCode().code.size() - 1).definition instanceof ReturnVoidIns) { - cinitBody.insertAll(2, sinitcode); //after getlocal0,pushscope - } - } - cinitBody.markOffsets(); - cinitBody.autoFillStats(abcIndex.getSelectedAbc(), initScope + (implementsStr.isEmpty() ? 0 : 1), true); - - classInfo.cinit_index = cinit_index; - if (initBody != null) { - initBody.autoFillStats(abcIndex.getSelectedAbc(), initScope + 1, true); - } - instanceInfo.interfaces = new int[implementsStr.size()]; - for (int i = 0; i < implementsStr.size(); i++) { - instanceInfo.interfaces[i] = superIntName(localData, implementsStr.get(i)); - } - - } - - @Override - public List generate(SourceGeneratorLocalData localData, CommaExpressionItem item) throws CompilationException { - if (item.commands.isEmpty()) { - return new ArrayList<>(); - } - - //We need to handle commands and last expression separately, otherwise last expression result will be popped - List cmds = new ArrayList<>(item.commands); - GraphTargetItem lastExpr = cmds.remove(cmds.size() - 1); - List ret = new ArrayList<>(); - ret.addAll(generate(localData, cmds)); - ret.addAll(lastExpr.toSource(localData, this)); - return ret; - } - - public int generateClass(int namespace, ClassInfo ci, InstanceInfo ii, int initScope, DottedChain pkg, SourceGeneratorLocalData localData, AVM2Item cls, Reference class_index) throws AVM2ParseException, CompilationException { - /*ClassInfo ci = new ClassInfo(); - InstanceInfo ii = new InstanceInfo(); - abc.class_info.add(ci); - abc.instance_info.add(ii); - */ - if (cls instanceof ClassAVM2Item) { - ClassAVM2Item cai = (ClassAVM2Item) cls; - //TODO: iinit variables, iinit activation - generateClass(cai.importedClasses, cai.cinitVariables, cai.cinitActivation, cai.staticInit, - cai.openedNamespaces, - namespace, - initScope, pkg, ci, ii, - localData, false, - cai.className, cai.extendsOp == null ? "Object" : cai.extendsOp.toString(), - cai.extendsOp, cai.implementsOp, cai.iinit, - cai.iinitVariables, cai.iinitActivation, cai.traits, class_index - ); - if (!cai.isDynamic) { - ii.flags |= InstanceInfo.CLASS_SEALED; - } - if (cai.isFinal) { - ii.flags |= InstanceInfo.CLASS_FINAL; - } - ii.flags |= InstanceInfo.CLASS_PROTECTEDNS; - ii.protectedNS = abcIndex.getSelectedAbc().constants.getNamespaceId(Namespace.KIND_PROTECTED, pkg.toRawString() + ":" + cai.className, 0, true); - } - if (cls instanceof InterfaceAVM2Item) { - InterfaceAVM2Item iai = (InterfaceAVM2Item) cls; - ii.flags |= InstanceInfo.CLASS_INTERFACE; - ii.flags |= InstanceInfo.CLASS_SEALED; - generateClass(iai.importedClasses, new ArrayList<>(), false, new ArrayList<>(), - iai.openedNamespaces, namespace, initScope, pkg, ci, ii, localData, true, iai.name, null, null, iai.superInterfaces, null, null, false, iai.methods, - class_index - ); - } - - return abcIndex.getSelectedAbc().instance_info.size() - 1; - } - - public int traitName(int namespace, String var) { - return abcIndex.getSelectedAbc().constants.getMultinameId(Multiname.createQName(false, str(var), namespace), true); - } - - public int typeName(SourceGeneratorLocalData localData, GraphTargetItem type) throws CompilationException { - if (type instanceof UnboundedTypeItem) { - return 0; - } - if (("" + type).equals("*")) { - return 0; - } - - return resolveType(localData, type, abcIndex); - /* - TypeItem nameItem = (TypeItem) type; - name = nameItem.fullTypeName; - if (name.contains(".")) { - pkg = name.substring(0, name.lastIndexOf('.')); - name = name.substring(name.lastIndexOf('.') + 1); - } - if (!nameItem.subtypes.isEmpty()) { //It's vector => TypeName - List params = new ArrayList<>(); - for (GraphTargetItem p : nameItem.subtypes) { - params.add(typeName(localData, p));//abc.getLastAbc().constants.getMultinameId(new Multiname(Multiname.QNAME, str(p), namespace(Namespace.KIND_PACKAGE, ppkg), 0, 0, new ArrayList()), true)); - } - int qname = abc.getLastAbc().constants.getMultinameId(new Multiname(Multiname.QNAME, str(name), namespace(Namespace.KIND_PACKAGE, pkg), 0, 0, new ArrayList()), true); - return abc.getLastAbc().constants.getMultinameId(new Multiname(Multiname.TYPENAME, 0, 0, 0, qname, params), true); - } else { - return abc.getLastAbc().constants.getMultinameId(new Multiname(Multiname.QNAME, str(name), namespace(Namespace.KIND_PACKAGE, pkg), 0, 0, new ArrayList()), true); - }*/ - } - - public int ident(GraphTargetItem name) { - if (name instanceof NameAVM2Item) { - return str(((NameAVM2Item) name).getVariableName()); - } - throw new RuntimeException("no ident"); //FIXME - } - - public int namespace(int nsKind, String name) { - return abcIndex.getSelectedAbc().constants.getNamespaceId(nsKind, str(name), 0, true); - } - - public int str(String name) { - return abcIndex.getSelectedAbc().constants.getStringId(name, true); - } - - public int propertyName(GraphTargetItem name) { - if (name instanceof NameAVM2Item) { - NameAVM2Item va = (NameAVM2Item) name; - return abcIndex.getSelectedAbc().constants.getMultinameId(Multiname.createQName(false, str(va.getVariableName()), namespace(Namespace.KIND_PACKAGE, "")), true); - } - throw new RuntimeException("no prop"); //FIXME - } - - public int getFreeRegister(SourceGeneratorLocalData localData) { - for (int i = 0;; i++) { - if (!localData.registerVars.containsValue(i)) { - localData.registerVars.put("__TEMP__" + i, i); - return i; - } - } - } - - public boolean killRegister(SourceGeneratorLocalData localData, int i) { - String key = null; - for (String k : localData.registerVars.keySet()) { - if (localData.registerVars.get(k) == i) { - key = k; - break; - } - } - if (key != null) { - localData.registerVars.remove(key); - return true; - } - return false; - } - - public int method(boolean isStatic, int name_index, boolean subMethod, boolean isInterface, List callStack, DottedChain pkg, boolean needsActivation, List subvariables, int initScope, boolean hasRest, int line, String className, String superType, boolean constructor, SourceGeneratorLocalData localData, List paramTypes, List paramNames, List paramValues, List body, GraphTargetItem retType) throws CompilationException { - //Reference hasArgs = new Reference<>(Boolean.FALSE); - //calcRegisters(localData,needsActivation,paramNames,subvariables,body, hasArgs); - SourceGeneratorLocalData newlocalData = new SourceGeneratorLocalData(new HashMap<>(), 1, true, 0); - newlocalData.currentClass = className; - newlocalData.pkg = localData.pkg; - newlocalData.callStack.addAll(localData.callStack); - newlocalData.traitUsages = localData.traitUsages; - newlocalData.currentScript = localData.currentScript; - newlocalData.documentClass = localData.documentClass; - newlocalData.privateNs = localData.privateNs; - newlocalData.protectedNs = localData.protectedNs; - newlocalData.isStatic = isStatic; - newlocalData.subMethod = subMethod; - localData = newlocalData; - - localData.activationReg = 0; - - for (int i = 0; i < subvariables.size(); i++) { - AssignableAVM2Item an = subvariables.get(i); - if (an instanceof UnresolvedAVM2Item) { - UnresolvedAVM2Item n = (UnresolvedAVM2Item) an; - if (n.resolved == null) { - String fullClass = localData.getFullClass(); - GraphTargetItem res = n.resolve(new TypeItem(fullClass), paramTypes, paramNames, abcIndex, callStack, subvariables); - if (res instanceof AssignableAVM2Item) { - subvariables.set(i, (AssignableAVM2Item) res); - } else { - subvariables.remove(i); - i--; - } - } - } - } - - for (int t = 0; t < paramTypes.size(); t++) { - GraphTargetItem an = paramTypes.get(t); - if (an instanceof UnresolvedAVM2Item) { - UnresolvedAVM2Item n = (UnresolvedAVM2Item) an; - if (n.resolved == null) { - String fullClass = localData.getFullClass(); - GraphTargetItem res = n.resolve(new TypeItem(fullClass), paramTypes, paramNames, abcIndex, callStack, subvariables); - paramTypes.set(t, res); - } - } - } - - boolean hasArguments = false; - List slotNames = new ArrayList<>(); - List slotTypes = new ArrayList<>(); - slotNames.add("--first"); - slotTypes.add("-"); - - int paramLine = 0; //? - - List registerNames = new ArrayList<>(); - List registerLines = new ArrayList<>(); - List registerTypes = new ArrayList<>(); - if (className != null) { - String fullClassName = pkg.add(className).toRawString(); - registerTypes.add(fullClassName); - localData.scopeStack.add(new LocalRegAVM2Item(null, null, registerNames.size(), null)); - registerNames.add("this"); - registerLines.add(0); //? - - } else { - registerTypes.add("global"); - registerNames.add("this"); - registerLines.add(0); //? - } - for (GraphTargetItem t : paramTypes) { - registerTypes.add(t.toString()); - slotTypes.add(t.toString()); - } - for (int i = 0; i < paramNames.size(); i++) { - registerLines.add(paramLine); - } - registerNames.addAll(paramNames); - slotNames.addAll(paramNames); - /*for (GraphTargetItem p : paramTypes) { - slotTypes.add("" + p); - }*/ - if (hasRest) { - registerTypes.add("Array"); - slotTypes.add("Array"); - } - localData.registerVars.clear(); - for (AssignableAVM2Item an : subvariables) { - if (an instanceof NameAVM2Item) { - NameAVM2Item n = (NameAVM2Item) an; - if (n.getVariableName().equals("arguments") & !n.isDefinition()) { - registerNames.add("arguments"); - registerTypes.add("Object"); - registerLines.add(0); //? - hasArguments = true; - break; - } - } - } - int paramRegCount = registerNames.size(); - - if (needsActivation) { - registerNames.add("+$activation"); - registerLines.add(0); //? - localData.activationReg = registerNames.size() - 1; - registerTypes.add("Object"); - localData.scopeStack.add(new LocalRegAVM2Item(null, null, localData.activationReg, null)); - } - - String mask = Configuration.registerNameFormat.get(); - mask = mask.replace("%d", "([0-9]+)"); - Pattern pat = Pattern.compile(mask); - - //Two rounds - for (int round = 1; round <= 2; round++) { - for (AssignableAVM2Item an : subvariables) { - if (an instanceof NameAVM2Item) { - NameAVM2Item n = (NameAVM2Item) an; - if (n.isDefinition() && !registerNames.contains(n.getVariableName())) { - if (!needsActivation || (n.getSlotScope() <= 0)) { - String varName = n.getVariableName(); - Matcher m = pat.matcher(varName); - //In first round, make all register that match standard loc_xx register - if ((round == 1) && (m.matches())) { - String regIndexStr = m.group(1); - int regIndex = Integer.parseInt(regIndexStr); - while (registerNames.size() <= regIndex + 1) { - String standardName = String.format(mask, registerNames.size() - 1); - registerNames.add(standardName); - registerTypes.add("*"); - slotNames.add(standardName); - slotTypes.add("*"); - registerLines.add(paramLine); - } - registerNames.set(regIndex, varName); - registerTypes.set(regIndex, varName); - slotNames.set(regIndex, varName); - slotTypes.set(regIndex, varName); - registerLines.set(regIndex, n.line); - } //in second round the rest - else if (round == 2 && !m.matches()) { - registerNames.add(n.getVariableName()); - registerTypes.add(n.type.toString()); - slotNames.add(n.getVariableName()); - slotTypes.add(n.type.toString()); - registerLines.add(n.line); - } - } - } - } - } - } - - int slotScope = subMethod ? 0 : 1; - - for (AssignableAVM2Item an : subvariables) { - if (an instanceof NameAVM2Item) { - NameAVM2Item n = (NameAVM2Item) an; - String variableName = n.getVariableName(); - if (variableName != null) { - boolean isThisOrSuper = variableName.equals("this") || variableName.equals("super"); - if (!isThisOrSuper && needsActivation) { - if (n.getSlotNumber() <= 0) { - n.setSlotNumber(slotNames.indexOf(variableName)); - n.setSlotScope(slotScope); - } - } else if (isThisOrSuper) { - n.setRegNumber(0); - } else { - n.setRegNumber(registerNames.indexOf(variableName)); - } - } - } - } - - for (int i = 0; i < registerNames.size(); i++) { - if (needsActivation && i > localData.activationReg) { - break; - } - localData.registerVars.put(registerNames.get(i), i); - } - List declarations = new ArrayList<>(); - loopn: - for (AssignableAVM2Item an : subvariables) { - if (an instanceof NameAVM2Item) { - NameAVM2Item n = (NameAVM2Item) an; - - if (needsActivation) { - if (n.getSlotScope() != slotScope) { - continue; - } else if (n.getSlotNumber() < paramRegCount) { - continue; - } - } - for (NameAVM2Item d : declarations) { - if (n.getVariableName() != null && n.getVariableName().equals(d.getVariableName())) { - continue loopn; - } - } - - for (GraphTargetItem it : body) { //search first level of commands - if (it instanceof NameAVM2Item) { - NameAVM2Item n2 = (NameAVM2Item) it; - if (n2.isDefinition() && n2.getAssignedValue() != null && n2.getVariableName().equals(n.getVariableName())) { - continue loopn; - } - if (!n2.isDefinition() && n2.getVariableName() != null && n2.getVariableName().equals(n.getVariableName())) { //used earlier than defined - break; - } - } - } - if (n.unresolved) { - continue; - } - if (n.redirect != null) { - continue; - } - if (n.getNs() != null) { - continue; - } - - String variableName = n.getVariableName(); - if ("this".equals(variableName) || "super".equals(variableName) || paramNames.contains(variableName) || "arguments".equals(variableName)) { - continue; - } - - NameAVM2Item d = new NameAVM2Item(n.type, n.line, n.getVariableName(), NameAVM2Item.getDefaultValue("" + n.type), true, n.openedNamespaces); - //no index - if (needsActivation) { - if (d.getSlotNumber() <= 0) { - d.setSlotNumber(n.getSlotNumber()); - d.setSlotScope(n.getSlotScope()); - } - } else { - d.setRegNumber(n.getRegNumber()); - } - declarations.add(d); - } - } - - int[] param_types = new int[paramTypes.size()]; - ValueKind[] optional = new ValueKind[paramValues.size()]; - //int[] param_names = new int[paramNames.size()]; - for (int i = 0; i < paramTypes.size(); i++) { - param_types[i] = typeName(localData, paramTypes.get(i)); - //param_names[i] = str(paramNames.get(i)); - } - - for (int i = 0; i < paramValues.size(); i++) { - optional[i] = getValueKind(Namespace.KIND_NAMESPACE/*FIXME*/, paramTypes.get(paramTypes.size() - paramValues.size() + i), paramValues.get(i)); - if (optional[i] == null) { - throw new CompilationException("Default value must be compiletime constant", line); - } - } - - MethodInfo mi = new MethodInfo(param_types, constructor ? 0 : typeName(localData, retType), name_index, 0, optional, new int[0]/*no param_names*/); - if (hasArguments) { - mi.setFlagNeed_Arguments(); - } - //No param names like in official - /* - if (!paramNames.isEmpty()) { - mi.setFlagHas_paramnames(); - }*/ - if (!paramValues.isEmpty()) { - mi.setFlagHas_optional(); - } - if (hasRest) { - mi.setFlagNeed_rest(); - } - - int mindex; - if (!isInterface) { - MethodBody mbody = new MethodBody(abcIndex.getSelectedAbc(), new Traits(), new byte[0], new ABCException[0]); - - if (needsActivation) { - int slotId = 1; - for (int i = 1; i < slotNames.size(); i++) { - TraitSlotConst tsc = new TraitSlotConst(); - tsc.slot_id = slotId++; - tsc.name_index = abcIndex.getSelectedAbc().constants.getMultinameId(Multiname.createQName(false, abcIndex.getSelectedAbc().constants.getStringId(slotNames.get(i), true), abcIndex.getSelectedAbc().constants.getNamespaceId(Namespace.KIND_PACKAGE_INTERNAL, pkg, 0, true)), true); - tsc.type_index = typeName(localData, new TypeItem(slotTypes.get(i))); - mbody.traits.traits.add(tsc); - } - for (int i = 1; i < paramRegCount; i++) { - NameAVM2Item param = new NameAVM2Item(new TypeItem(registerTypes.get(i)), 0, registerNames.get(i), null, false, new ArrayList<>()); - param.setRegNumber(i); - NameAVM2Item d = new NameAVM2Item(new TypeItem(registerTypes.get(i)), 0, registerNames.get(i), param, true, new ArrayList<>()); - d.setSlotScope(slotScope); - d.setSlotNumber(slotNames.indexOf(registerNames.get(i))); - declarations.add(d); - } - } - if (body != null) { - body.addAll(0, declarations); - } - - localData.exceptions = new ArrayList<>(); - localData.callStack.add(mbody); - List src = body == null ? new ArrayList<>() : generate(localData, body); - - mbody.method_info = abcIndex.getSelectedAbc().addMethodInfo(mi); - ArrayList mbodyCode = toInsList(src); - mbody.setCode(new AVM2Code(mbodyCode)); - - if (needsActivation) { - if (localData.traitUsages.containsKey(mbody)) { - List usages = localData.traitUsages.get(mbody); - for (int i = 0; i < mbody.traits.traits.size(); i++) { - if (usages.contains(i)) { - TraitSlotConst tsc = (TraitSlotConst) mbody.traits.traits.get(i); - GraphTargetItem type = TypeItem.UNBOUNDED; - if (tsc.type_index > 0) { - type = new TypeItem(abcIndex.getSelectedAbc().constants.getMultiname(tsc.type_index).getNameWithNamespace(abcIndex.getSelectedAbc().constants)); - } - NameAVM2Item d = new NameAVM2Item(type, 0, tsc.getName(abcIndex.getSelectedAbc()).getName(abcIndex.getSelectedAbc().constants, null, true), NameAVM2Item.getDefaultValue("" + type), true, new ArrayList<>()); - d.setSlotNumber(tsc.slot_id); - d.setSlotScope(slotScope); - mbodyCode.addAll(0, toInsList(d.toSourceIgnoreReturnValue(localData, this))); - } - } - } - - List acts = new ArrayList<>(); - acts.add(ins(AVM2Instructions.NewActivation)); - acts.add(ins(AVM2Instructions.Dup)); - acts.add(AssignableAVM2Item.generateSetLoc(localData.activationReg)); - acts.add(ins(AVM2Instructions.PushScope)); - - mbodyCode.addAll(0, acts); - } - - if (constructor) { - /* List abcs = new ArrayList<>(); - abcs.add(abc); - abcs.addAll(allABCs); - */ - int parentConstMinAC = 0; - - AbcIndexing.ClassIndex ci = abcIndex.findClass(new TypeItem(superType)); - - if (ci != null) { - MethodInfo pmi = ci.abc.method_info.get(ci.abc.instance_info.get(ci.index).iinit_index); - parentConstMinAC = pmi.param_types.length; - if (pmi.flagHas_optional()) { - parentConstMinAC -= pmi.optional.length; - } - } - int ac = -1; - for (AVM2Instruction ins : mbodyCode) { - if (ins.definition instanceof ConstructSuperIns) { - ac = ins.operands[0]; - if (parentConstMinAC > ac) { - throw new CompilationException("Parent constructor call requires different number of arguments", line); - } - - } - } - if (ac == -1) { - if (parentConstMinAC == 0) { - mbodyCode.add(0, new AVM2Instruction(0, AVM2Instructions.GetLocal0, null)); - mbodyCode.add(1, new AVM2Instruction(0, AVM2Instructions.ConstructSuper, new int[]{0})); - - } else { - throw new CompilationException("Parent constructor must be called", line); - } - } - } - for (int i = 1; i < registerNames.size(); i++) { - mbodyCode.add(i - 1, ins(AVM2Instructions.Debug, 1, str(registerNames.get(i)), i - 1, (int) registerLines.get(i))); - } - if (!subMethod) { - mbodyCode.add(0, new AVM2Instruction(0, AVM2Instructions.GetLocal0, null)); - mbodyCode.add(1, new AVM2Instruction(0, AVM2Instructions.PushScope, null)); - } - boolean addRet = false; - if (!mbodyCode.isEmpty()) { - InstructionDefinition lastDef = mbodyCode.get(mbodyCode.size() - 1).definition; - if (!((lastDef instanceof ReturnVoidIns) || (lastDef instanceof ReturnValueIns))) { - addRet = true; - } - } else { - addRet = true; - } - if (addRet) { - if (retType.toString().equals("*") || retType.toString().equals("void") || constructor) { - mbodyCode.add(new AVM2Instruction(0, AVM2Instructions.ReturnVoid, null)); - } else { - mbodyCode.add(new AVM2Instruction(0, AVM2Instructions.PushUndefined, null)); - mbodyCode.add(new AVM2Instruction(0, AVM2Instructions.ReturnValue, null)); - } - } - mbody.exceptions = localData.exceptions.toArray(new ABCException[localData.exceptions.size()]); - int offset = 0; - for (int i = 0; i < mbodyCode.size(); i++) { - AVM2Instruction ins = mbodyCode.get(i); - if (ins instanceof ExceptionMarkAVM2Instruction) { - ExceptionMarkAVM2Instruction m = (ExceptionMarkAVM2Instruction) ins; - switch (m.markType) { - case MARK_E_START: - mbody.exceptions[m.exceptionId].start = offset; - break; - case MARK_E_END: - mbody.exceptions[m.exceptionId].end = offset; - break; - case MARK_E_TARGET: - mbody.exceptions[m.exceptionId].target = offset; - break; - } - mbodyCode.remove(i); - i--; - continue; - } - offset += ins.getBytesLength(); - } - - mbody.markOffsets(); - mbody.autoFillStats(abcIndex.getSelectedAbc(), initScope, className != null); - abcIndex.getSelectedAbc().addMethodBody(mbody); - mindex = mbody.method_info; - } else { - mindex = abcIndex.getSelectedAbc().addMethodInfo(mi); - } - - return mindex; - } - - public ValueKind getValueKind(int ns, GraphTargetItem type, GraphTargetItem val) { - - if (val instanceof BooleanAVM2Item) { - BooleanAVM2Item bi = (BooleanAVM2Item) val; - if (bi.value) { - return new ValueKind(0, ValueKind.CONSTANT_True); - } else { - return new ValueKind(0, ValueKind.CONSTANT_False); - } - } - - boolean isNs = false; - if (type instanceof NameAVM2Item) { - if (((NameAVM2Item) type).getVariableName().equals("namespace")) { - isNs = true; - } - } - - if ((type instanceof TypeItem) && (((TypeItem) type).fullTypeName.equals(DottedChain.NAMESPACE))) { - isNs = true; - } - - if (val instanceof StringAVM2Item) { - StringAVM2Item sval = (StringAVM2Item) val; - if (isNs) { - return new ValueKind(namespace(Namespace.KIND_NAMESPACE, sval.getValue()), ValueKind.CONSTANT_Namespace); - } else { - return new ValueKind(str(sval.getValue()), ValueKind.CONSTANT_Utf8); - } - } - if (val instanceof IntegerValueAVM2Item) { - return new ValueKind(abcIndex.getSelectedAbc().constants.getIntId(((IntegerValueAVM2Item) val).value, true), ValueKind.CONSTANT_Int); - } - if (val instanceof FloatValueAVM2Item) { - return new ValueKind(abcIndex.getSelectedAbc().constants.getDoubleId(((FloatValueAVM2Item) val).value, true), ValueKind.CONSTANT_Double); - } - if (val instanceof NanAVM2Item) { - return new ValueKind(abcIndex.getSelectedAbc().constants.getDoubleId(Double.NaN, true), ValueKind.CONSTANT_Double); - } - if (val instanceof NullAVM2Item) { - return new ValueKind(0, ValueKind.CONSTANT_Null); - } - if (val instanceof UndefinedAVM2Item) { - return new ValueKind(0, ValueKind.CONSTANT_Undefined); - } - return null; - } - - private int genNs(List importedClasses, DottedChain pkg, NamespaceItem ns, List openedNamespaces, SourceGeneratorLocalData localData, int line) throws CompilationException { - ns.resolveCustomNs(abcIndex, importedClasses, pkg, openedNamespaces, localData); - return ns.getCpoolIndex(abcIndex); - } - - public void generateTraitsPhase2(List importedClasses, DottedChain pkg, List items, Trait[] traits, List openedNamespaces, SourceGeneratorLocalData localData) throws CompilationException { - for (int k = 0; k < items.size(); k++) { - GraphTargetItem item = items.get(k); - if (traits[k] == null) { - - } else if (item instanceof InterfaceAVM2Item) { - traits[k].name_index = traitName(((InterfaceAVM2Item) item).pkg == null ? 0 : ((InterfaceAVM2Item) item).pkg.getCpoolIndex(abcIndex), ((InterfaceAVM2Item) item).name); - } else if (item instanceof ClassAVM2Item) { - traits[k].name_index = traitName(((ClassAVM2Item) item).pkg == null ? 0 : ((ClassAVM2Item) item).pkg.getCpoolIndex(abcIndex), ((ClassAVM2Item) item).className); - } else if ((item instanceof MethodAVM2Item) || (item instanceof GetterAVM2Item) || (item instanceof SetterAVM2Item)) { - traits[k].name_index = traitName(genNs(importedClasses, pkg, ((MethodAVM2Item) item).pkg, openedNamespaces, localData, ((MethodAVM2Item) item).line), ((MethodAVM2Item) item).functionName); - } else if (item instanceof FunctionAVM2Item) { - traits[k].name_index = traitName(((FunctionAVM2Item) item).pkg == null ? 0 : ((FunctionAVM2Item) item).pkg.getCpoolIndex(abcIndex), ((FunctionAVM2Item) item).functionName); - } else if (item instanceof ConstAVM2Item) { - traits[k].name_index = traitName(genNs(importedClasses, pkg, ((ConstAVM2Item) item).pkg, openedNamespaces, localData, ((ConstAVM2Item) item).line), ((ConstAVM2Item) item).var); - } else if (item instanceof SlotAVM2Item) { - traits[k].name_index = traitName(genNs(importedClasses, pkg, ((SlotAVM2Item) item).pkg, openedNamespaces, localData, ((SlotAVM2Item) item).line), ((SlotAVM2Item) item).var); - } - } - - for (int k = 0; k < items.size(); k++) { - GraphTargetItem item = items.get(k); - if (traits[k] == null) { - continue; - } - if (item instanceof ClassAVM2Item) { - - InstanceInfo instanceInfo = abcIndex.getSelectedAbc().instance_info.get(((TraitClass) traits[k]).class_info); - instanceInfo.name_index = abcIndex.getSelectedAbc().constants.getMultinameId( - Multiname.createQName( - false, - abcIndex.getSelectedAbc().constants.getStringId(((ClassAVM2Item) item).className, true), - ((ClassAVM2Item) item).pkg.getCpoolIndex(abcIndex)), true); - - if (((ClassAVM2Item) item).extendsOp != null) { - instanceInfo.super_index = typeName(localData, ((ClassAVM2Item) item).extendsOp); - } else { - instanceInfo.super_index = abcIndex.getSelectedAbc().constants.getMultinameId(Multiname.createQName(false, str("Object"), namespace(Namespace.KIND_PACKAGE, "")), true); - } - instanceInfo.interfaces = new int[((ClassAVM2Item) item).implementsOp.size()]; - for (int i = 0; i < ((ClassAVM2Item) item).implementsOp.size(); i++) { - instanceInfo.interfaces[i] = superIntName(localData, ((ClassAVM2Item) item).implementsOp.get(i)); - } - } - if (item instanceof InterfaceAVM2Item) { - ABC abc = abcIndex.getSelectedAbc(); - AVM2ConstantPool constants = abc.constants; - InstanceInfo instanceInfo = abc.instance_info.get(((TraitClass) traits[k]).class_info); - instanceInfo.name_index = constants.getMultinameId(Multiname.createQName(false, constants.getStringId(((InterfaceAVM2Item) item).name, true), - ((InterfaceAVM2Item) item).pkg.getCpoolIndex(abcIndex)), true); - - instanceInfo.interfaces = new int[((InterfaceAVM2Item) item).superInterfaces.size()]; - for (int i = 0; i < ((InterfaceAVM2Item) item).superInterfaces.size(); i++) { - GraphTargetItem un = ((InterfaceAVM2Item) item).superInterfaces.get(i); - instanceInfo.interfaces[i] = superIntName(localData, un); - } - } - } - } - - public int superIntName(SourceGeneratorLocalData localData, GraphTargetItem un) throws CompilationException { - if (un instanceof UnresolvedAVM2Item) { - ((UnresolvedAVM2Item) un).resolve(null, new ArrayList<>(), new ArrayList<>(), abcIndex, new ArrayList<>(), new ArrayList<>()); - un = ((UnresolvedAVM2Item) un).resolved; - } - if (!(un instanceof TypeItem)) { //not applyType - throw new CompilationException("Invalid type", 0); - } - TypeItem sup = (TypeItem) un; - int propId = resolveType(localData, sup, abcIndex); - int[] nss = new int[]{abcIndex.getSelectedAbc().constants.getMultiname(propId).namespace_index}; - return abcIndex.getSelectedAbc().constants.getMultinameId(Multiname.createMultiname(false, abcIndex.getSelectedAbc().constants.getMultiname(propId).name_index, abcIndex.getSelectedAbc().constants.getNamespaceSetId(nss, true)), true); - - } - - public int[] generateMetadata(List>> metadata) { - int ret[] = new int[metadata.size()]; - for (int i = 0; i < metadata.size(); i++) { - Map.Entry> en = metadata.get(i); - int keys[] = new int[en.getValue().size()]; - int values[] = new int[en.getValue().size()]; - int j = 0; - for (String key : en.getValue().keySet()) { - keys[j] = abcIndex.getSelectedAbc().constants.getStringId(key, true); - values[j] = abcIndex.getSelectedAbc().constants.getStringId(en.getValue().get(key), true); - j++; - } - MetadataInfo mi = new MetadataInfo(abcIndex.getSelectedAbc().constants.getStringId(en.getKey(), true), keys, values); - ret[i] = abcIndex.getSelectedAbc().metadata_info.size(); - abcIndex.getSelectedAbc().metadata_info.add(mi); - } - return ret; - } - - public void generateTraitsPhase3(List importedClasses, int methodInitScope, boolean isInterface, String className, String superName, boolean generateStatic, SourceGeneratorLocalData localData, List items, Traits ts, Trait[] traits, Map initScopes, Reference class_index) throws AVM2ParseException, CompilationException { - - //Note: Names must be generated first before accesed in inner subs - for (int k = 0; k < items.size(); k++) { - GraphTargetItem item = items.get(k); - if (traits[k] == null) { - continue; - } - if (item instanceof InterfaceAVM2Item) { - generateClass(((InterfaceAVM2Item) item).pkg.getCpoolIndex(abcIndex), abcIndex.getSelectedAbc().class_info.get(((TraitClass) traits[k]).class_info), abcIndex.getSelectedAbc().instance_info.get(((TraitClass) traits[k]).class_info), initScopes.get(traits[k]), ((InterfaceAVM2Item) item).pkg.name, localData, (InterfaceAVM2Item) item, class_index); - } - - if (item instanceof ClassAVM2Item) { - generateClass(((ClassAVM2Item) item).pkg.getCpoolIndex(abcIndex), abcIndex.getSelectedAbc().class_info.get(((TraitClass) traits[k]).class_info), abcIndex.getSelectedAbc().instance_info.get(((TraitClass) traits[k]).class_info), initScopes.get(traits[k]), ((ClassAVM2Item) item).pkg.name, localData, (ClassAVM2Item) item, class_index); - } - if ((item instanceof MethodAVM2Item) || (item instanceof GetterAVM2Item) || (item instanceof SetterAVM2Item)) { - MethodAVM2Item mai = (MethodAVM2Item) item; - if (mai.isStatic() != generateStatic) { - continue; - } - for (List ln : mai.allOpenedNamespaces) { - for (NamespaceItem n : ln) { - n.resolveCustomNs(abcIndex, importedClasses, localData.pkg, ln, localData); - } - } - String suffix = null; - if (item instanceof GetterAVM2Item) { - suffix = "get"; - } - if (item instanceof SetterAVM2Item) { - suffix = "set"; - } - - ((TraitMethodGetterSetter) traits[k]).method_info = method(mai.isStatic(), methodName(mai.outsidePackage, localData.pkg, mai.functionName, mai.pkg, className, mai.customNamespace, suffix), false, isInterface, new ArrayList<>(), localData.pkg, mai.needsActivation, mai.subvariables, methodInitScope + (mai.isStatic() ? 0 : 1), mai.hasRest, mai.line, className, superName, false, localData, mai.paramTypes, mai.paramNames, mai.paramValues, mai.body, mai.retType); - } else if (item instanceof FunctionAVM2Item) { - FunctionAVM2Item fai = (FunctionAVM2Item) item; - ((TraitFunction) traits[k]).method_info = method(false, methodName(false/*?*/, localData.pkg, fai.functionName, fai.pkg, null, null, ""), false, isInterface, new ArrayList<>(), localData.pkg, fai.needsActivation, fai.subvariables, methodInitScope, fai.hasRest, fai.line, className, superName, false, localData, fai.paramTypes, fai.paramNames, fai.paramValues, fai.body, fai.retType); - } - } - } - - private int methodName(boolean outsidePkg, DottedChain pkg, String methodName, NamespaceItem ns, String className, String customNs, String typeSuffix) { - StringBuilder sb = new StringBuilder(); - /*if (ns != null) { - sb.append(ns.name.toRawString()); - }*/ - if (className != null) { - if (pkg != null && !pkg.isEmpty() && !pkg.isTopLevel()) { - sb.append(pkg.toRawString()); - sb.append(":"); - } - sb.append(className); - } - if (customNs != null) { - sb.append(customNs); - } else if (ns != null) { - switch (ns.kind) { - case Namespace.KIND_PACKAGE_INTERNAL: - sb.append(pkg == null ? "" /*?*/ : pkg.toRawString()); - break; - case Namespace.KIND_PRIVATE: - - if (!outsidePkg) { - sb.append("/private"); - } - break; - case Namespace.KIND_PROTECTED: - case Namespace.KIND_STATIC_PROTECTED: - sb.append("/protected"); - break; - } - } - sb.append(":"); - sb.append(methodName); - if (typeSuffix != null && !typeSuffix.isEmpty()) { - sb.append("/"); - sb.append(typeSuffix); - } - return abcIndex.getSelectedAbc().constants.getStringId(sb.toString(), true); - } - - public Trait[] generateTraitsPhase1(List importedClasses, List openedNamespaces, String className, String superName, boolean generateStatic, SourceGeneratorLocalData localData, List items, Traits ts, Reference classIndex) throws AVM2ParseException, CompilationException { - Trait[] traits = new Trait[items.size()]; - int slot_id = 1; - int disp_id = 3; //1 and 2 are for constructor - for (int k = 0; k < items.size(); k++) { - GraphTargetItem item = items.get(k); - if (item instanceof InterfaceAVM2Item) { - TraitClass tc = new TraitClass(); - ClassInfo ci = new ClassInfo(); - InstanceInfo ii = new InstanceInfo(); - /*abc.class_info.add(ci); - abc.instance_info.add(ii);*/ - tc.class_info = classIndex.getVal(); - abcIndex.getSelectedAbc().addClass(ci, ii, classIndex.getVal()); - classIndex.setVal(classIndex.getVal() + 1); - ii.flags |= InstanceInfo.CLASS_INTERFACE; - //ii.name_index = traitName(((InterfaceAVM2Item) item).namespace, ((InterfaceAVM2Item) item).name); - //tc.class_info = abc.instance_info.size() - 1; - tc.kindType = Trait.TRAIT_CLASS; - //tc.name_index = traitName(((InterfaceAVM2Item) item).namespace, ((InterfaceAVM2Item) item).name); - tc.slot_id = 0; //? - ts.traits.add(tc); - traits[k] = tc; - traits[k].metadata = generateMetadata(((InterfaceAVM2Item) item).metadata); - } - - if (item instanceof ClassAVM2Item) { - TraitClass tc = new TraitClass(); - ClassInfo ci = new ClassInfo(); - InstanceInfo ii = new InstanceInfo(); - //ii.name_index = traitName(((ClassAVM2Item) item).namespace, ((ClassAVM2Item) item).className); - /*abc.class_info.add(ci); - abc.instance_info.add(instanceInfo);*/ - tc.class_info = classIndex.getVal(); - abcIndex.getSelectedAbc().addClass(ci, ii, classIndex.getVal()); - classIndex.setVal(classIndex.getVal() + 1); - tc.kindType = Trait.TRAIT_CLASS; - // tc.name_index = traitName(((ClassAVM2Item) item).namespace, ((ClassAVM2Item) item).className); - tc.slot_id = slot_id++; - ts.traits.add(tc); - traits[k] = tc; - traits[k].metadata = generateMetadata(((ClassAVM2Item) item).metadata); - - } - if ((item instanceof SlotAVM2Item) || (item instanceof ConstAVM2Item)) { - TraitSlotConst tsc = new TraitSlotConst(); - tsc.kindType = (item instanceof SlotAVM2Item) ? Trait.TRAIT_SLOT : Trait.TRAIT_CONST; - String var = null; - GraphTargetItem val = null; - GraphTargetItem type = null; - boolean isNamespace = false; - int namespace = 0; - boolean isStatic = false; - int metadata[] = new int[0]; - if (item instanceof SlotAVM2Item) { - SlotAVM2Item sai = (SlotAVM2Item) item; - if (sai.isStatic() != generateStatic) { - continue; - } - var = sai.var; - val = sai.value; - type = sai.type; - isStatic = sai.isStatic(); - if (sai.pkg != null) { - sai.pkg.resolveCustomNs(abcIndex, importedClasses, localData.pkg, openedNamespaces, localData); - } - namespace = sai.pkg == null ? 0 : sai.pkg.getCpoolIndex(abcIndex); - metadata = generateMetadata(((SlotAVM2Item) item).metadata); - } - if (item instanceof ConstAVM2Item) { - ConstAVM2Item cai = (ConstAVM2Item) item; - if (cai.isStatic() != generateStatic) { - continue; - } - var = cai.var; - val = cai.value; - type = cai.type; - if (cai.pkg != null) { - cai.pkg.resolveCustomNs(abcIndex, importedClasses, localData.pkg, openedNamespaces, localData); - } - namespace = cai.pkg == null ? 0 : cai.pkg.getCpoolIndex(abcIndex); - isNamespace = type.toString().equals("Namespace"); - isStatic = cai.isStatic(); - metadata = generateMetadata(((ConstAVM2Item) item).metadata); - } - if (isNamespace) { - tsc.name_index = traitName(namespace, var); - } - tsc.type_index = isNamespace ? 0 : (type == null ? 0 : typeName(localData, type)); - - ValueKind vk = getValueKind(namespace, type, val); - if (vk == null) { - tsc.value_kind = ValueKind.CONSTANT_Undefined; - } else { - tsc.value_kind = vk.value_kind; - tsc.value_index = vk.value_index; - } - tsc.slot_id = isStatic ? slot_id++ : 0; - ts.traits.add(tsc); - traits[k] = tsc; - traits[k].metadata = metadata; - } - if ((item instanceof MethodAVM2Item) || (item instanceof GetterAVM2Item) || (item instanceof SetterAVM2Item)) { - MethodAVM2Item mai = (MethodAVM2Item) item; - if (mai.isStatic() != generateStatic) { - continue; - } - TraitMethodGetterSetter tmgs = new TraitMethodGetterSetter(); - tmgs.kindType = (item instanceof GetterAVM2Item) ? Trait.TRAIT_GETTER : ((item instanceof SetterAVM2Item) ? Trait.TRAIT_SETTER : Trait.TRAIT_METHOD); - tmgs.disp_id = mai.isStatic() ? disp_id++ : 0; //For a reason, there is disp_id only for static methods (or not?) - if (mai.isFinal() || (className != null && mai.isStatic())) { - tmgs.kindFlags |= Trait.ATTR_Final; - } - if (mai.isOverride()) { - tmgs.kindFlags |= Trait.ATTR_Override; - } - ts.traits.add(tmgs); - - traits[k] = tmgs; - traits[k].metadata = generateMetadata(((MethodAVM2Item) item).metadata); - } - /*else if (item instanceof FunctionAVM2Item) { - TraitFunction tf = new TraitFunction(); - tf.slot_id = slot_id++; - tf.kindType = Trait.TRAIT_FUNCTION; - //tf.name_index = traitName(((FunctionAVM2Item) item).namespace, ((FunctionAVM2Item) item).functionName); - ts.traits.add(tf); - traits[k] = tf; - traits[k].metadata = generateMetadata(((FunctionAVM2Item) item).metadata); - }*/ - - } - - return traits; - } - - public ScriptInfo generateScriptInfo(List> allOpenedNamespaces, SourceGeneratorLocalData localData, List commands, int classPos) throws AVM2ParseException, CompilationException { - Reference class_index = new Reference<>(classPos); - ScriptInfo si = new ScriptInfo(); - localData.currentScript = si; - Trait[] traitArr = generateTraitsPhase1(new ArrayList<>(), new ArrayList<>(), null, null, true, localData, commands, si.traits, class_index); - generateTraitsPhase2(new ArrayList<>(), null/*FIXME*/, commands, traitArr, new ArrayList<>(), localData); - - abcIndex.refreshSelected(); - - ABC abc = abcIndex.getSelectedAbc(); - AVM2ConstantPool constants = abc.constants; - MethodInfo mi = new MethodInfo(new int[0], 0, constants.getStringId("", true), 0, new ValueKind[0], new int[0]); - MethodBody mb = new MethodBody(abc, new Traits(), new byte[0], new ABCException[0]); - mb.method_info = abc.addMethodInfo(mi); - mb.setCode(new AVM2Code()); - List mbCode = mb.getCode().code; - mbCode.add(ins(AVM2Instructions.GetLocal0)); - mbCode.add(ins(AVM2Instructions.PushScope)); - - int traitScope = 2; - - Map initScopes = new HashMap<>(); - - for (Trait t : si.traits.traits) { - if (t instanceof TraitClass) { - TraitClass tc = (TraitClass) t; - List parents = new ArrayList<>(); - if (localData.documentClass) { - mbCode.add(ins(AVM2Instructions.GetScopeObject, 0)); - traitScope++; - } else { - int[] nsset = new int[]{constants.getMultiname(tc.name_index).namespace_index}; - mbCode.add(ins(AVM2Instructions.FindPropertyStrict, constants.getMultinameId(Multiname.createMultiname(false, constants.getMultiname(tc.name_index).name_index, constants.getNamespaceSetId(nsset, true)), true))); - } - if (abc.instance_info.get(tc.class_info).isInterface()) { - mbCode.add(ins(AVM2Instructions.PushNull)); - } else { - - AbcIndexing.ClassIndex ci = abcIndex.findClass(AbcIndexing.multinameToType(abc.instance_info.get(tc.class_info).name_index, constants)); - while (ci != null && ci.parent != null) { - ci = ci.parent; - Multiname origM = ci.abc.constants.getMultiname(ci.abc.instance_info.get(ci.index).name_index); - Namespace origNs = ci.abc.constants.getNamespace(origM.namespace_index); - if (origM.kind == Multiname.QNAME || origM.kind == Multiname.QNAMEA) { - parents.add(constants.getMultinameId( - Multiname.createQName(origM.kind == Multiname.QNAMEA, - constants.getStringId(ci.abc.constants.getString(origM.name_index), true), - constants.getNamespaceId(origNs.kind, - ci.abc.constants.getString(origNs.name_index), 0, true)), true)); - } - } - - //add all parent objects to scopestack - for (int i = parents.size() - 1; i >= 0; i--) { - mbCode.add(ins(AVM2Instructions.GetLex, parents.get(i))); - mbCode.add(ins(AVM2Instructions.PushScope)); - traitScope++; - } - //direct parent class to new_class instruction - if (!parents.isEmpty()) { //NON EXISTING PARENT CLASS - TODO: handle as error! - mbCode.add(ins(AVM2Instructions.GetLex, parents.get(0))); - } - } - mbCode.add(ins(AVM2Instructions.NewClass, tc.class_info)); - for (int i = 0; i < parents.size(); i++) { - mbCode.add(ins(AVM2Instructions.PopScope)); - } - - mbCode.add(ins(AVM2Instructions.InitProperty, tc.name_index)); - initScopes.put(t, traitScope); - traitScope = 1; - } - } - - abc.addMethodBody(mb); - si.init_index = mb.method_info; - localData.pkg = DottedChain.EMPTY; - generateTraitsPhase3(new ArrayList<>(), 1/*??*/, false, null, null, true, localData, commands, si.traits, traitArr, initScopes, class_index); - - int maxSlotId = 0; - for (int k = 0; k < si.traits.traits.size(); k++) { - if (si.traits.traits.get(k) instanceof TraitSlotConst) { - TraitSlotConst ti = (TraitSlotConst) si.traits.traits.get(k); - if (ti.slot_id > maxSlotId) { - maxSlotId = ti.slot_id; - } - } - } - for (int k = 0; k < si.traits.traits.size(); k++) { - if ((si.traits.traits.get(k) instanceof TraitMethodGetterSetter) && (commands.get(k) instanceof MethodAVM2Item)) { - MethodAVM2Item mai = (MethodAVM2Item) commands.get(k); - if (mai.outsidePackage) { - TraitMethodGetterSetter tmgs = (TraitMethodGetterSetter) si.traits.traits.get(k); - TraitSlotConst nts = new TraitSlotConst(); - nts.name_index = si.traits.traits.get(k).name_index; - nts.metadata = si.traits.traits.get(k).metadata; - - nts.slot_id = maxSlotId + 1; - maxSlotId++; - nts.type_index = abcIndex.getSelectedAbc().constants.getQnameId("Function", Namespace.KIND_PACKAGE, "", true); - nts.value_index = 0; - nts.value_kind = 0; - int methodinfo = tmgs.method_info; - si.traits.traits.set(k, nts); - mbCode.add(ins(AVM2Instructions.NewFunction, methodinfo)); - mbCode.add(ins(AVM2Instructions.InitProperty, nts.name_index)); - } - } - } - - mbCode.add(ins(AVM2Instructions.ReturnVoid)); - mb.autoFillStats(abc, 1, false); - - return si; - } - - public static void parentNamesAddNames(AbcIndexing abc, int name_index, List indices, List names, List namespaces) { - List cindices = new ArrayList<>(); - - List outABCs = new ArrayList<>(); - parentNames(abc, name_index, cindices, names, namespaces, outABCs); - for (int i = 0; i < cindices.size(); i++) { - ABC a = outABCs.get(i); - int m = cindices.get(i); - if (a == abc.getSelectedAbc()) { - indices.add(m); - continue; - } - Multiname superName = a.constants.getMultiname(m); - indices.add( - abc.getSelectedAbc().constants.getMultinameId( - Multiname.createQName(false, - abc.getSelectedAbc().constants.getStringId(superName.getName(a.constants, null, true), true), - abc.getSelectedAbc().constants.getNamespaceId(superName.getNamespace(a.constants).kind, superName.getNamespace(a.constants).getName(a.constants), 0, true)), true) - ); - } - } - - public static GraphTargetItem getTraitReturnType(AbcIndexing abc, Trait t) { - if (t instanceof TraitSlotConst) { - TraitSlotConst tsc = (TraitSlotConst) t; - if (tsc.type_index == 0) { - return TypeItem.UNBOUNDED; - } - return PropertyAVM2Item.multinameToType(tsc.type_index, abc.getSelectedAbc().constants); - } - if (t instanceof TraitMethodGetterSetter) { - TraitMethodGetterSetter tmgs = (TraitMethodGetterSetter) t; - if (tmgs.kindType == Trait.TRAIT_GETTER) { - return PropertyAVM2Item.multinameToType(abc.getSelectedAbc().method_info.get(tmgs.method_info).ret_type, abc.getSelectedAbc().constants); - } - if (tmgs.kindType == Trait.TRAIT_SETTER) { - if (abc.getSelectedAbc().method_info.get(tmgs.method_info).param_types.length > 0) { - return PropertyAVM2Item.multinameToType(abc.getSelectedAbc().method_info.get(tmgs.method_info).param_types[0], abc.getSelectedAbc().constants); - } else { - return TypeItem.UNBOUNDED; - } - } - } - if (t instanceof TraitFunction) { - return new TypeItem(DottedChain.FUNCTION); - } - return TypeItem.UNBOUNDED; - } - - public static boolean searchPrototypeChain(List otherNs, int privateNs, int protectedNs, boolean instanceOnly, AbcIndexing abc, DottedChain pkg, String obj, String propertyName, Reference outName, Reference outNs, Reference outPropNs, Reference outPropNsKind, Reference outPropNsIndex, Reference outPropType, Reference outPropValue, Reference outPropValueAbc) { - for (int ns : otherNs) { - if (searchPrototypeChain(ns, instanceOnly, abc, pkg, obj, propertyName, outName, outNs, outPropNs, outPropNsKind, outPropNsIndex, outPropType, outPropValue, outPropValueAbc)) { - return true; - } - } - - if (searchPrototypeChain(privateNs, instanceOnly, abc, pkg, obj, propertyName, outName, outNs, outPropNs, outPropNsKind, outPropNsIndex, outPropType, outPropValue, outPropValueAbc)) { - return true; - } - if (searchPrototypeChain(protectedNs, instanceOnly, abc, pkg, obj, propertyName, outName, outNs, outPropNs, outPropNsKind, outPropNsIndex, outPropType, outPropValue, outPropValueAbc)) { - return true; - } - return searchPrototypeChain(0, instanceOnly, abc, pkg, obj, propertyName, outName, outNs, outPropNs, outPropNsKind, outPropNsIndex, outPropType, outPropValue, outPropValueAbc); - } - - private static boolean searchPrototypeChain(int selectedNs, boolean instanceOnly, AbcIndexing abc, DottedChain pkg, String obj, String propertyName, Reference outName, Reference outNs, Reference outPropNs, Reference outPropNsKind, Reference outPropNsIndex, Reference outPropType, Reference outPropValue, Reference outPropValueAbc) { - - AbcIndexing.TraitIndex sp = abc.findScriptProperty(pkg.add(propertyName)); - if (sp == null) { - sp = abc.findProperty(new AbcIndexing.PropertyDef(propertyName, new TypeItem(pkg.add(obj)), abc.getSelectedAbc(), selectedNs), !instanceOnly, true); - } - if (sp != null) { - if (sp.objType instanceof TypeItem) { - outName.setVal(((TypeItem) sp.objType).fullTypeName.getLast()); - outNs.setVal(((TypeItem) sp.objType).fullTypeName.getWithoutLast()); - } else { - //FIXME? Vector? - } - outPropNs.setVal(sp.trait.getName(sp.abc).getNamespace(sp.abc.constants).getName(sp.abc.constants)); - outPropNsKind.setVal(sp.trait.getName(sp.abc).getNamespace(sp.abc.constants).kind); - int nsi = sp.trait.getName(sp.abc).namespace_index; - outPropNsIndex.setVal(sp.abc == abc.getSelectedAbc() ? sp.abc.constants.getNamespaceSubIndex(nsi) : 0); - outPropType.setVal(sp.returnType); - outPropValue.setVal(sp.value); - outPropValueAbc.setVal(sp.abc); - return true; - } - return false; - } - - public static void parentNames(AbcIndexing abc, int name_index, List indices, List names, List namespaces, List outABCs) { - AbcIndexing.ClassIndex ci = abc.findClass(new TypeItem(abc.getSelectedAbc().constants.getMultiname(name_index).getNameWithNamespace(abc.getSelectedAbc().constants))); - while (ci != null) { - int ni = ci.abc.instance_info.get(ci.index).name_index; - indices.add(ni); - outABCs.add(ci.abc); - names.add(ci.abc.constants.getMultiname(ni).getName(ci.abc.constants, null, true)); - namespaces.add(ci.abc.constants.getMultiname(ni).getNamespace(ci.abc.constants).getName(ci.abc.constants).toRawString()); - ci = ci.parent; - } - } - - /* public void calcRegisters(Reference activationReg, SourceGeneratorLocalData localData, boolean needsActivation, List funParamNames,List funSubVariables,List funBody, Reference hasArguments) throws ParseException { - - }*/ - /*public int resolveType(String objType) { - if (objType.equals("*")) { - return 0; - } - List abcs = new ArrayList<>(); - abcs.add(abc); - abcs.addAll(allABCs); - for (ABC a : abcs) { - int ci = a.findClassByName(objType); - if (ci != -1) { - Multiname tname = a.instance_info.get(ci).getName(a.constants); - return abc.getLastAbc().constants.getMultinameId(new Multiname(tname.kind, - abc.getLastAbc().constants.getStringId(tname.getName(a.constants, new ArrayList<>()), true), - abc.getLastAbc().constants.getNamespaceId(new Namespace(tname.getNamespace(a.constants).kind, abc.getLastAbc().constants.getStringId(tname.getNamespace(a.constants).getName(a.constants), true)), 0, true), 0, 0, new ArrayList()), true); - } - } - return 0; - }*/ - @Override - public List generate(SourceGeneratorLocalData localData, TypeItem item) throws CompilationException { - String currentFullClassName = localData.getFullClass(); - - if (localData.documentClass && item.toString().equals(currentFullClassName)) { - int slotId = 0; - int c = abcIndex.getSelectedAbc().findClassByName(currentFullClassName); - for (Trait t : localData.currentScript.traits.traits) { - if (t instanceof TraitClass) { - TraitClass tc = (TraitClass) t; - if (tc.class_info == c) { - slotId = tc.slot_id; - break; - } - } - } - return GraphTargetItem.toSourceMerge(localData, this, ins(AVM2Instructions.GetGlobalScope), ins(AVM2Instructions.GetSlot, slotId)); - } else { - return GraphTargetItem.toSourceMerge(localData, this, ins(AVM2Instructions.GetLex, resolveType(localData, item, abcIndex))); - } - } - - public static int resolveType(SourceGeneratorLocalData localData, GraphTargetItem item, AbcIndexing abcIndex) throws CompilationException { - int name_index = 0; - GraphTargetItem typeItem = null; - - if (item instanceof UnresolvedAVM2Item) { - String fullClass = localData.getFullClass(); - item = ((UnresolvedAVM2Item) item).resolve(new TypeItem(fullClass), new ArrayList<>(), new ArrayList<>(), abcIndex, new ArrayList<>(), new ArrayList<>()); - } - if (item instanceof TypeItem) { - typeItem = item; - } else if (item instanceof ApplyTypeAVM2Item) { - typeItem = ((ApplyTypeAVM2Item) item).object; - } else { - throw new CompilationException("Invalid type:" + item + " (" + item.getClass().getName() + ")", 0/*??*/); - } - if (typeItem instanceof UnresolvedAVM2Item) { - String fullClass = localData.getFullClass(); - typeItem = ((UnresolvedAVM2Item) typeItem).resolve(new TypeItem(fullClass), new ArrayList<>(), new ArrayList<>(), abcIndex, new ArrayList<>(), new ArrayList<>()); - } - - if (!(typeItem instanceof TypeItem)) { - throw new CompilationException("Invalid type", 0/*??*/); - } - - TypeItem type = (TypeItem) typeItem; - - DottedChain dname = type.fullTypeName; - DottedChain pkg = dname.getWithoutLast(); - String name = dname.getLast(); - /*for (InstanceInfo ii : abc.getSelectedAbc().instance_info) { - Multiname mname = abc.getSelectedAbc().constants.constant_multiname.get(ii.name_index); - if (mname != null && name.equals(mname.getName(abc.getSelectedAbc().constants, null, true))) { - Namespace ns = mname.getNamespace(abc.getSelectedAbc().constants); - if (ns != null && ns.hasName(pkg, abc.getSelectedAbc().constants)) { - name_index = ii.name_index; - break; - } - } - }*/ - ABC abc = abcIndex.getSelectedAbc(); - AVM2ConstantPool constants = abc.constants; - AbcIndexing.ClassIndex ci = abcIndex.findClass(new TypeItem(dname)); - if (ci != null) { - Multiname m = ci.abc.instance_info.get(ci.index).getName(ci.abc.constants); - if (m != null) { - Namespace ns = ci.abc.instance_info.get(ci.index).getName(ci.abc.constants).getNamespace(ci.abc.constants); - String n = m.getName(ci.abc.constants, new ArrayList<>(), true); - String nsn = ns == null ? null : ns.getName(ci.abc.constants).toRawString(); - name_index = constants.getQnameId( - n, - ns == null ? Namespace.KIND_PACKAGE : ns.kind, - nsn, true); - } - } - - for (int i = 1; i < constants.getMultinameCount(); i++) { - Multiname mname = constants.getMultiname(i); - if (mname != null && name.equals(mname.getName(constants, null, true))) { - if (mname.getNamespace(constants) != null && pkg.equals(mname.getNamespace(constants).getName(constants))) { - name_index = i; - break; - } - } - } - if (name_index == 0) { - if (pkg.isEmpty() && localData.currentScript != null /*FIXME!*/) { - for (Trait t : localData.currentScript.traits.traits) { - if (t.getName(abc).getName(constants, null, true).equals(name)) { - name_index = t.name_index; - break; - } - } - } - if (name_index == 0) { - name_index = constants.getMultinameId(Multiname.createQName(false, constants.getStringId(name, true), constants.getNamespaceId(Namespace.KIND_PACKAGE, pkg, 0, true)), true); - } - } - - if (item instanceof ApplyTypeAVM2Item) { - ApplyTypeAVM2Item atype = (ApplyTypeAVM2Item) item; - int[] params = new int[atype.params.size()]; - int i = 0; - for (GraphTargetItem s : atype.params) { - params[i++] = resolveType(localData, s, abcIndex); - } - return constants.getMultinameId(Multiname.createTypeName(name_index, params), true); - } - - return name_index; - } - - @Override - public List generateDiscardValue(SourceGeneratorLocalData localData, GraphTargetItem item) throws CompilationException { - List ret = item.toSource(localData, this); - ret.add(ins(AVM2Instructions.Pop)); - return ret; - } - - @Override - public List generate(SourceGeneratorLocalData localData, PushItem item) throws CompilationException { - return item.value.toSource(localData, this); - } - - @Override - public List generate(SourceGeneratorLocalData localData, PopItem item) throws CompilationException { - List ret = new ArrayList<>(); - return ret; - } - -} +/* + * 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.abc.avm2.parser.script; + +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SourceGeneratorLocalData; +import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; +import com.jpexs.decompiler.flash.abc.avm2.AVM2ConstantPool; +import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; +import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instructions; +import com.jpexs.decompiler.flash.abc.avm2.instructions.InstructionDefinition; +import com.jpexs.decompiler.flash.abc.avm2.instructions.construction.ConstructSuperIns; +import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.JumpIns; +import com.jpexs.decompiler.flash.abc.avm2.instructions.other.ReturnValueIns; +import com.jpexs.decompiler.flash.abc.avm2.instructions.other.ReturnVoidIns; +import com.jpexs.decompiler.flash.abc.avm2.model.AVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.ApplyTypeAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.BooleanAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.FloatValueAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.GetDescendantsAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.IntegerValueAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.LocalRegAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.NameValuePair; +import com.jpexs.decompiler.flash.abc.avm2.model.NanAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.NewObjectAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.NullAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.ReturnValueAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.ReturnVoidAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.StringAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.ThrowAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.UndefinedAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.WithAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.WithObjectAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.clauses.ForEachInAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.clauses.ForInAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.clauses.TryAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.IfCondition; +import com.jpexs.decompiler.flash.abc.avm2.parser.AVM2ParseException; +import com.jpexs.decompiler.flash.abc.avm2.parser.script.AbcIndexing.ClassIndex; +import com.jpexs.decompiler.flash.abc.types.ABCException; +import com.jpexs.decompiler.flash.abc.types.ClassInfo; +import com.jpexs.decompiler.flash.abc.types.ConvertData; +import com.jpexs.decompiler.flash.abc.types.InstanceInfo; +import com.jpexs.decompiler.flash.abc.types.MetadataInfo; +import com.jpexs.decompiler.flash.abc.types.MethodBody; +import com.jpexs.decompiler.flash.abc.types.MethodInfo; +import com.jpexs.decompiler.flash.abc.types.Multiname; +import com.jpexs.decompiler.flash.abc.types.Namespace; +import com.jpexs.decompiler.flash.abc.types.ScriptInfo; +import com.jpexs.decompiler.flash.abc.types.ValueKind; +import com.jpexs.decompiler.flash.abc.types.traits.Trait; +import com.jpexs.decompiler.flash.abc.types.traits.TraitClass; +import com.jpexs.decompiler.flash.abc.types.traits.TraitFunction; +import com.jpexs.decompiler.flash.abc.types.traits.TraitMethodGetterSetter; +import com.jpexs.decompiler.flash.abc.types.traits.TraitSlotConst; +import com.jpexs.decompiler.flash.abc.types.traits.Traits; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.ecma.EcmaScript; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.helpers.GraphTextWriter; +import com.jpexs.decompiler.flash.helpers.NulWriter; +import com.jpexs.decompiler.graph.CompilationException; +import com.jpexs.decompiler.graph.DottedChain; +import com.jpexs.decompiler.graph.GraphSourceItem; +import com.jpexs.decompiler.graph.GraphTargetItem; +import com.jpexs.decompiler.graph.Loop; +import com.jpexs.decompiler.graph.ScopeStack; +import com.jpexs.decompiler.graph.SourceGenerator; +import com.jpexs.decompiler.graph.TypeItem; +import com.jpexs.decompiler.graph.model.AndItem; +import com.jpexs.decompiler.graph.model.BreakItem; +import com.jpexs.decompiler.graph.model.CommaExpressionItem; +import com.jpexs.decompiler.graph.model.ContinueItem; +import com.jpexs.decompiler.graph.model.DefaultItem; +import com.jpexs.decompiler.graph.model.DoWhileItem; +import com.jpexs.decompiler.graph.model.DuplicateItem; +import com.jpexs.decompiler.graph.model.FalseItem; +import com.jpexs.decompiler.graph.model.ForItem; +import com.jpexs.decompiler.graph.model.IfItem; +import com.jpexs.decompiler.graph.model.LocalData; +import com.jpexs.decompiler.graph.model.NotItem; +import com.jpexs.decompiler.graph.model.OrItem; +import com.jpexs.decompiler.graph.model.PopItem; +import com.jpexs.decompiler.graph.model.PushItem; +import com.jpexs.decompiler.graph.model.SwitchItem; +import com.jpexs.decompiler.graph.model.TernarOpItem; +import com.jpexs.decompiler.graph.model.TrueItem; +import com.jpexs.decompiler.graph.model.UnboundedTypeItem; +import com.jpexs.decompiler.graph.model.WhileItem; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +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; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * + * @author JPEXS + */ +public class AVM2SourceGenerator implements SourceGenerator { + + public final AbcIndexing abcIndex; + + public static final int MARK_E_START = 0; + + public static final int MARK_E_END = 1; + + public static final int MARK_E_TARGET = 2; + + public static final int MARK_E_FINALLYPART = 3; + + private AVM2Instruction ins(int instructionCode, int... operands) { + return new AVM2Instruction(0, instructionCode, operands); + } + + private AVM2Instruction ins(InstructionDefinition def, int... operands) { + return new AVM2Instruction(0, def, operands); + } + + @Override + public List generate(SourceGeneratorLocalData localData, FalseItem item) throws CompilationException { + return GraphTargetItem.toSourceMerge(localData, this, ins(AVM2Instructions.PushFalse)); + } + + @Override + public List generate(SourceGeneratorLocalData localData, TrueItem item) throws CompilationException { + return GraphTargetItem.toSourceMerge(localData, this, ins(AVM2Instructions.PushTrue)); + } + + public List generate(SourceGeneratorLocalData localData, GetDescendantsAVM2Item item) throws CompilationException { + + AVM2ConstantPool constants = abcIndex.getSelectedAbc().constants; + int[] nssa = new int[item.openedNamespaces.size()]; + for (int i = 0; i < item.openedNamespaces.size(); i++) { + nssa[i] = item.openedNamespaces.get(i).getCpoolIndex(abcIndex); + } + + int nsset = constants.getNamespaceSetId(nssa, true); + + return GraphTargetItem.toSourceMerge(localData, this, + item.object, + ins(AVM2Instructions.GetDescendants, constants.getMultinameId(Multiname.createMultiname(false, constants.getStringId(item.nameStr, true), nsset), true)) + ); + } + + @Override + public List generate(SourceGeneratorLocalData localData, AndItem item) throws CompilationException { + List ret = new ArrayList<>(); + ret.addAll(generateToActionList(localData, item.leftSide)); + ret.add(ins(AVM2Instructions.Dup)); + if (!("" + item.leftSide.returnType()).equals("Boolean")) { + ret.add(ins(AVM2Instructions.ConvertB)); + } + List andExpr = generateToActionList(localData, item.rightSide); + andExpr.add(0, ins(AVM2Instructions.Pop)); + int andExprLen = insToBytes(andExpr).length; + ret.add(ins(AVM2Instructions.IfFalse, andExprLen)); + ret.addAll(andExpr); + return ret; + + } + + private byte[] insToBytes(List code) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (AVM2Instruction instruction : code) { + + baos.write(instruction.getBytes()); + + } + return baos.toByteArray(); + } catch (IOException ex) { + Logger.getLogger(AVM2SourceGenerator.class.getName()).log(Level.SEVERE, null, ex); + } + return SWFInputStream.BYTE_ARRAY_EMPTY; + } + + @Override + public List generate(SourceGeneratorLocalData localData, OrItem item) throws CompilationException { + List ret = new ArrayList<>(); + ret.addAll(generateToActionList(localData, item.leftSide)); + ret.add(ins(AVM2Instructions.Dup)); + if (!("" + item.leftSide.returnType()).equals("Boolean")) { + ret.add(ins(AVM2Instructions.ConvertB)); + } + List orExpr = generateToActionList(localData, item.rightSide); + orExpr.add(0, ins(AVM2Instructions.Pop)); + int orExprLen = insToBytes(orExpr).length; + ret.add(ins(AVM2Instructions.IfTrue, orExprLen)); + ret.addAll(orExpr); + return ret; + } + + public ArrayList toInsList(List items) { + ArrayList ret = new ArrayList<>(); + for (GraphSourceItem s : items) { + if (s instanceof AVM2Instruction) { + ret.add((AVM2Instruction) s); + } + } + return ret; + } + + private List nonempty(List list) { + if (list == null) { + return new ArrayList<>(); + } + return list; + } + + private List condition(SourceGeneratorLocalData localData, GraphTargetItem t, int offset) throws CompilationException { + if (t instanceof IfCondition) { + IfCondition ic = (IfCondition) t; + return GraphTargetItem.toSourceMerge(localData, this, ic.getLeftSide(), ic.getRightSide(), ins(ic.getIfDefinition(), offset)); + } + return GraphTargetItem.toSourceMerge(localData, this, t, ins(AVM2Instructions.IfTrue, offset)); + } + + private List notCondition(SourceGeneratorLocalData localData, GraphTargetItem t, int offset) throws CompilationException { + if (t instanceof IfCondition) { + IfCondition ic = (IfCondition) t; + return GraphTargetItem.toSourceMerge(localData, this, ic.getLeftSide(), ic.getRightSide(), ins(ic.getIfNotDefinition(), offset)); + } + return GraphTargetItem.toSourceMerge(localData, this, t, ins(AVM2Instructions.IfFalse, offset)); + } + + private List generateIf(SourceGeneratorLocalData localData, GraphTargetItem expression, List onTrueCmds, List onFalseCmds, boolean ternar) throws CompilationException { + List ret = new ArrayList<>(); + //ret.addAll(notCondition(localData, expression)); + List onTrue; + List onFalse = null; + if (ternar) { + onTrue = toInsList(onTrueCmds.get(0).toSource(localData, this)); + } else { + onTrue = generateToInsList(localData, onTrueCmds); + } + + if (onFalseCmds != null && !onFalseCmds.isEmpty()) { + if (ternar) { + onFalse = toInsList(onFalseCmds.get(0).toSource(localData, this)); + } else { + onFalse = generateToInsList(localData, onFalseCmds); + } + } + AVM2Instruction ajmp = null; + if (onFalse != null) { + if (!((!nonempty(onTrue).isEmpty()) + && ((onTrue.get(onTrue.size() - 1).definition instanceof ContinueJumpIns) + || ((onTrue.get(onTrue.size() - 1).definition instanceof BreakJumpIns))))) { + ajmp = ins(AVM2Instructions.Jump, 0); + onTrue.add(ajmp); + } + } + + byte[] onTrueBytes = insToBytes(onTrue); + int onTrueLen = onTrueBytes.length; + + ret.addAll(notCondition(localData, expression, onTrueLen)); + ret.addAll(onTrue); + + if (onFalse != null) { + byte[] onFalseBytes = insToBytes(onFalse); + int onFalseLen = onFalseBytes.length; + if (ajmp != null) { + ajmp.operands[0] = onFalseLen; + } + ret.addAll(onFalse); + } + return ret; + } + + public List generate(SourceGeneratorLocalData localData, XMLFilterAVM2Item item) throws CompilationException { + List ret = new ArrayList<>(); + final Reference counterReg = new Reference<>(0); + final Reference collectionReg = new Reference<>(0); + final Reference xmlListReg = new Reference<>(0); + List xmlListSetTemp = AssignableAVM2Item.setTemp(localData, this, xmlListReg); + AVM2ConstantPool constants = abcIndex.getSelectedAbc().constants; + ret.addAll(GraphTargetItem.toSourceMerge(localData, this, + ins(AVM2Instructions.PushByte, 0), + AssignableAVM2Item.setTemp(localData, this, counterReg), + item.object, + ins(AVM2Instructions.CheckFilter), + NameAVM2Item.generateCoerce(localData, this, TypeItem.UNBOUNDED), + AssignableAVM2Item.setTemp(localData, this, collectionReg), + ins(AVM2Instructions.GetLex, constants.getMultinameId(Multiname.createQName(false, constants.getStringId("XMLList", true), constants.getNamespaceId(Namespace.KIND_PACKAGE, "", 0, true)), true)), + ins(AVM2Instructions.PushString, constants.getStringId("", true)), + ins(AVM2Instructions.Construct, 1), + xmlListSetTemp + )); + final Reference tempVal1 = new Reference<>(0); + final Reference tempVal2 = new Reference<>(0); + + List forBody = toInsList(GraphTargetItem.toSourceMerge(localData, this, + ins(AVM2Instructions.Label), + AssignableAVM2Item.getTemp(localData, this, collectionReg), + AssignableAVM2Item.getTemp(localData, this, counterReg), + ins(AVM2Instructions.NextValue), + AssignableAVM2Item.dupSetTemp(localData, this, tempVal1), + AssignableAVM2Item.dupSetTemp(localData, this, tempVal2), + ins(AVM2Instructions.PushWith) + )); + localData.scopeStack.add(new LocalRegAVM2Item(null, null, tempVal2.getVal(), null)); + forBody.addAll(toInsList(item.value.toSource(localData, this))); + List trueBody = new ArrayList<>(); + trueBody.addAll(toInsList(AssignableAVM2Item.getTemp(localData, this, xmlListReg))); + trueBody.addAll(toInsList(AssignableAVM2Item.getTemp(localData, this, counterReg))); + trueBody.addAll(toInsList(AssignableAVM2Item.getTemp(localData, this, tempVal1))); + trueBody.add(ins(AVM2Instructions.SetProperty, constants.getMultinameId(Multiname.createMultinameL(false, NamespaceItem.getCpoolSetIndex(abcIndex, item.openedNamespaces)), true))); + forBody.add(ins(AVM2Instructions.IfFalse, insToBytes(trueBody).length)); + forBody.addAll(trueBody); + forBody.add(ins(AVM2Instructions.PopScope)); + localData.scopeStack.remove(localData.scopeStack.size() - 1); + forBody.addAll(toInsList(AssignableAVM2Item.killTemp(localData, this, Arrays.asList(tempVal2, tempVal1)))); + + int forBodyLen = insToBytes(forBody).length; + AVM2Instruction forwardJump = ins(AVM2Instructions.Jump, forBodyLen); + ret.add(forwardJump); + + List expr = new ArrayList<>(); + expr.add(ins(AVM2Instructions.HasNext2, collectionReg.getVal(), counterReg.getVal())); + AVM2Instruction backIf = ins(AVM2Instructions.IfTrue, 0); + expr.add(backIf); + + int exprLen = insToBytes(expr).length; + backIf.operands[0] = -(exprLen + forBodyLen); + + ret.addAll(forBody); + ret.addAll(expr); + ret.addAll(AssignableAVM2Item.killTemp(localData, this, Arrays.asList(collectionReg, counterReg))); + ret.addAll(AssignableAVM2Item.getTemp(localData, this, xmlListReg)); + ret.addAll(AssignableAVM2Item.killTemp(localData, this, Arrays.asList(xmlListReg))); + return ret; + } + + @Override + public List generate(SourceGeneratorLocalData localData, IfItem item) throws CompilationException { + return generateIf(localData, item.expression, item.onTrue, item.onFalse, false); + } + + private void fixSwitch(List code, int breakOffset, long loopId) { + fixLoop(code, breakOffset, Integer.MAX_VALUE, loopId); + } + + private void fixLoop(List code, int breakOffset, int continueOffset, long loopId) { + int pos = 0; + for (int a = 0; a < code.size(); a++) { + AVM2Instruction ins = code.get(a); + pos += ins.getBytesLength(); + if (ins.definition instanceof JumpIns) { + if (ins.definition instanceof ContinueJumpIns) { + if (continueOffset != Integer.MAX_VALUE) { + ins.operands[0] = (-pos + continueOffset); + ins.definition = AVM2Code.instructionSet[AVM2Instructions.Jump]; + } + } + if (ins.definition instanceof BreakJumpIns) { + ins.operands[0] = (-pos + breakOffset); + ins.definition = AVM2Code.instructionSet[AVM2Instructions.Jump]; + } + } + } + } + + @Override + public List generate(SourceGeneratorLocalData localData, TernarOpItem item) throws CompilationException { + List onTrue = new ArrayList<>(); + onTrue.add(item.onTrue); + List onFalse = new ArrayList<>(); + onFalse.add(item.onFalse); + return generateIf(localData, item.expression, onTrue, onFalse, true); + } + + @Override + public List generate(SourceGeneratorLocalData localData, WhileItem item) throws CompilationException { + List ret = new ArrayList<>(); + List whileExpr = new ArrayList<>(); + + List ex = new ArrayList<>(item.expression); + GraphTargetItem lastItem = null; + if (!ex.isEmpty()) { + lastItem = ex.remove(ex.size() - 1); + while (lastItem instanceof CommaExpressionItem) { + CommaExpressionItem cei = (CommaExpressionItem) lastItem; + ex.addAll(cei.commands); + lastItem = ex.remove(ex.size() - 1); + } + whileExpr.addAll(generateToInsList(localData, ex)); + } + List whileBody = generateToInsList(localData, item.commands); + AVM2Instruction forwardJump = ins(AVM2Instructions.Jump, 0); + ret.add(forwardJump); + whileBody.add(0, ins(AVM2Instructions.Label)); + ret.addAll(whileBody); + int whileBodyLen = insToBytes(whileBody).length; + forwardJump.operands[0] = whileBodyLen; + whileExpr.addAll(toInsList(condition(localData, lastItem, 0))); + int whileExprLen = insToBytes(whileExpr).length; + whileExpr.get(whileExpr.size() - 1).operands[0] = -(whileExprLen + whileBodyLen); //Assuming last is if instruction + ret.addAll(whileExpr); + fixLoop(whileBody, whileBodyLen + whileExprLen, whileBodyLen, item.loop.id); + return ret; + } + + public List generate(SourceGeneratorLocalData localData, ForEachInAVM2Item item) throws CompilationException { + return generateForIn(localData, item.loop, item.expression.collection, (AssignableAVM2Item) item.expression.object, item.commands, true); + } + + public List generate(SourceGeneratorLocalData localData, ForInAVM2Item item) throws CompilationException { + return generateForIn(localData, item.loop, item.expression.collection, (AssignableAVM2Item) item.expression.object, item.commands, false); + } + + public List generateForIn(SourceGeneratorLocalData localData, Loop loop, GraphTargetItem collection, AssignableAVM2Item assignable, List commands, final boolean each) throws CompilationException { + List ret = new ArrayList<>(); + final Reference counterReg = new Reference<>(0); + final Reference collectionReg = new Reference<>(0); + + if (assignable instanceof UnresolvedAVM2Item) { + assignable = (AssignableAVM2Item) ((UnresolvedAVM2Item) assignable).resolved; + } + + ret.addAll(GraphTargetItem.toSourceMerge(localData, this, + ins(AVM2Instructions.PushByte, 0), + AssignableAVM2Item.setTemp(localData, this, counterReg), + collection, + NameAVM2Item.generateCoerce(localData, this, TypeItem.UNBOUNDED), + AssignableAVM2Item.setTemp(localData, this, collectionReg) + )); + + GraphTargetItem assigned = new GraphTargetItem() { + + @Override + public GraphTextWriter appendTo(GraphTextWriter writer, LocalData localData) throws InterruptedException { + return null; + } + + @Override + public boolean hasReturnValue() { + return true; + } + + @Override + public GraphTargetItem returnType() { + return TypeItem.UNBOUNDED; + } + + @Override + public List toSource(SourceGeneratorLocalData localData, SourceGenerator generator) throws CompilationException { + return toSourceMerge(localData, generator, + AssignableAVM2Item.getTemp(localData, generator, collectionReg), + AssignableAVM2Item.getTemp(localData, generator, counterReg), + ins(each ? AVM2Instructions.NextValue : AVM2Instructions.NextName) + ); + } + }; + assignable.setAssignedValue(assigned); + + List forBody = toInsList(GraphTargetItem.toSourceMerge(localData, this, + ins(AVM2Instructions.Label), + assignable.toSourceIgnoreReturnValue(localData, this) + )); + + forBody.addAll(generateToInsList(localData, commands)); + int forBodyLen = insToBytes(forBody).length; + + AVM2Instruction forwardJump = ins(AVM2Instructions.Jump, forBodyLen); + ret.add(forwardJump); + + List expr = new ArrayList<>(); + expr.add(ins(AVM2Instructions.HasNext2, collectionReg.getVal(), counterReg.getVal())); + AVM2Instruction backIf = ins(AVM2Instructions.IfTrue, 0); + expr.add(backIf); + + int exprLen = insToBytes(expr).length; + backIf.operands[0] = -(exprLen + forBodyLen); + + fixLoop(forBody, forBodyLen + exprLen, forBodyLen, loop.id); + ret.addAll(forBody); + ret.addAll(expr); + ret.addAll(AssignableAVM2Item.killTemp(localData, this, Arrays.asList(collectionReg, counterReg))); + return ret; + } + + @Override + public List generate(SourceGeneratorLocalData localData, DoWhileItem item) throws CompilationException { + List ret = new ArrayList<>(); + List whileExpr = new ArrayList<>(); + + List ex = new ArrayList<>(item.expression); + GraphTargetItem lastItem = null; + if (!ex.isEmpty()) { + lastItem = ex.remove(ex.size() - 1); + while (lastItem instanceof CommaExpressionItem) { + CommaExpressionItem cei = (CommaExpressionItem) lastItem; + ex.addAll(cei.commands); + lastItem = ex.remove(ex.size() - 1); + } + whileExpr.addAll(generateToInsList(localData, ex)); + } + List dowhileBody = generateToInsList(localData, item.commands); + List labelBody = new ArrayList<>(); + labelBody.add(ins(AVM2Instructions.Label)); + int labelBodyLen = insToBytes(labelBody).length; + + AVM2Instruction forwardJump = ins(AVM2Instructions.Jump, labelBodyLen); + ret.add(forwardJump); + ret.addAll(labelBody); + ret.addAll(dowhileBody); + int dowhileBodyLen = insToBytes(dowhileBody).length; + whileExpr.addAll(toInsList(condition(localData, lastItem, 0))); + int dowhileExprLen = insToBytes(whileExpr).length; + whileExpr.get(whileExpr.size() - 1).operands[0] = -(dowhileExprLen + dowhileBodyLen + labelBodyLen); //Assuming last is if instruction + ret.addAll(whileExpr); + fixLoop(dowhileBody, dowhileBodyLen + dowhileExprLen, dowhileBodyLen, item.loop.id); + return ret; + } + + public List generate(SourceGeneratorLocalData localData, WithAVM2Item item) throws CompilationException { + + List ret = new ArrayList<>(); + ret.addAll(item.scope.toSource(localData, this)); + Reference tempReg = new Reference<>(0); + ret.addAll(AssignableAVM2Item.dupSetTemp(localData, this, tempReg)); + localData.scopeStack.add(new WithObjectAVM2Item(null, null, new LocalRegAVM2Item(null, null, tempReg.getVal(), null))); + ret.add(ins(AVM2Instructions.PushWith)); + ret.addAll(generate(localData, item.items)); + ret.add(ins(AVM2Instructions.PopScope)); + ret.addAll(AssignableAVM2Item.killTemp(localData, this, Arrays.asList(tempReg))); + localData.scopeStack.remove(localData.scopeStack.size() - 1); + return ret; + } + + @Override + public List generate(SourceGeneratorLocalData localData, ForItem item) throws CompilationException { + List ret = new ArrayList<>(); + List forExpr = new ArrayList<>(); + + List ex = new ArrayList<>(); + if (item.expression != null) { + ex.add(item.expression); + } else { + ex.add(new BooleanAVM2Item(null, null, true)); + } + GraphTargetItem lastItem = null; + if (!ex.isEmpty()) { + lastItem = ex.remove(ex.size() - 1); + while (lastItem instanceof CommaExpressionItem) { + CommaExpressionItem cei = (CommaExpressionItem) lastItem; + ex.addAll(cei.commands); + lastItem = ex.remove(ex.size() - 1); + } + forExpr.addAll(generateToInsList(localData, ex)); + } + List forBody = generateToInsList(localData, item.commands); + List forFinalCommands = generateToInsList(localData, item.finalCommands); + + ret.addAll(generateToInsList(localData, item.firstCommands)); + + AVM2Instruction forwardJump = ins(AVM2Instructions.Jump, 0); + ret.add(forwardJump); + forBody.add(0, ins(AVM2Instructions.Label)); + ret.addAll(forBody); + ret.addAll(forFinalCommands); + int forBodyLen = insToBytes(forBody).length; + int forFinalCLen = insToBytes(forFinalCommands).length; + forwardJump.operands[0] = forBodyLen + forFinalCLen; + forExpr.addAll(toInsList(condition(localData, lastItem, 0))); + int forExprLen = insToBytes(forExpr).length; + forExpr.get(forExpr.size() - 1).operands[0] = -(forExprLen + forBodyLen + forFinalCLen); //Assuming last is if instruction + ret.addAll(forExpr); + fixLoop(forBody, forBodyLen + forFinalCLen + forExprLen, forBodyLen, item.loop.id); + return ret; + } + + private long uniqLast = 0; + + public String uniqId() { + uniqLast++; + return "" + uniqLast; + } + + @Override + public List generate(SourceGeneratorLocalData localData, SwitchItem item) throws CompilationException { + List ret = new ArrayList<>(); + Reference switchedReg = new Reference<>(0); + AVM2Instruction forwardJump = ins(AVM2Instructions.Jump, 0); + ret.add(forwardJump); + + int defIndex = -1; + + for (int i = item.caseValues.size() - 1; i >= 0; i--) { + if (item.caseValues.get(i) instanceof DefaultItem) { + defIndex = i; + break; + } + } + + List cases = new ArrayList<>(); + cases.addAll(toInsList(new IntegerValueAVM2Item(null, null, (long) defIndex).toSource(localData, this))); + int cLen = insToBytes(cases).length; + List caseLast = new ArrayList<>(); + caseLast.add(0, ins(AVM2Instructions.Jump, cLen)); + caseLast.addAll(0, toInsList(new IntegerValueAVM2Item(null, null, (long) defIndex).toSource(localData, this))); + int cLastLen = insToBytes(caseLast).length; + caseLast.add(0, ins(AVM2Instructions.Jump, cLastLen)); + cases.addAll(0, caseLast); + + List preCases = new ArrayList<>(); + preCases.addAll(toInsList(item.switchedObject.toSource(localData, this))); + preCases.addAll(toInsList(AssignableAVM2Item.setTemp(localData, this, switchedReg))); + + for (int i = item.caseValues.size() - 1; i >= 0; i--) { + if (item.caseValues.get(i) instanceof DefaultItem) { + continue; + } + List sub = new ArrayList<>(); + sub.addAll(toInsList(new IntegerValueAVM2Item(null, null, (long) i).toSource(localData, this))); + sub.add(ins(AVM2Instructions.Jump, insToBytes(cases).length)); + int subLen = insToBytes(sub).length; + + cases.addAll(0, sub); + cases.add(0, ins(AVM2Instructions.IfStrictNe, subLen)); + cases.addAll(0, toInsList(AssignableAVM2Item.getTemp(localData, this, switchedReg))); + cases.addAll(0, toInsList(item.caseValues.get(i).toSource(localData, this))); + } + cases.addAll(0, preCases); + + AVM2Instruction lookupOp = new AVM2Instruction(0, AVM2Instructions.LookupSwitch, new int[item.caseValues.size() + 1 + 1]); + cases.addAll(toInsList(AssignableAVM2Item.killTemp(localData, this, Arrays.asList(switchedReg)))); + List bodies = new ArrayList<>(); + List bodiesOffsets = new ArrayList<>(); + int defOffset; + int casesLen = insToBytes(cases).length; + bodies.add(0, ins(AVM2Instructions.Label)); + bodies.add(ins(new BreakJumpIns(item.loop.id), 0)); //There could be two breaks when default clause ends with break, but official compiler does this too, so who cares... + defOffset = -(insToBytes(bodies).length + casesLen); + for (int i = item.caseCommands.size() - 1; i >= 0; i--) { + bodies.addAll(0, generateToInsList(localData, item.caseCommands.get(i))); + bodies.add(0, ins(AVM2Instructions.Label)); + bodiesOffsets.add(0, -(insToBytes(bodies).length + casesLen)); + } + lookupOp.operands[0] = defOffset; + lookupOp.operands[1] = item.valuesMapping.size(); + for (int i = 0; i < item.valuesMapping.size(); i++) { + lookupOp.operands[2 + i] = bodiesOffsets.get(item.valuesMapping.get(i)); + } + + forwardJump.operands[0] = insToBytes(bodies).length; + ret.addAll(bodies); + ret.addAll(cases); + ret.add(lookupOp); + fixSwitch(toInsList(ret), insToBytes(toInsList(ret)).length, uniqLast); + return ret; + } + + @Override + public List generate(SourceGeneratorLocalData localData, NotItem item) throws CompilationException { + /*if (item.getOriginal() instanceof Inverted) { + GraphTargetItem norig = ((Inverted) item).invert(); + return norig.toSource(localData, this); + }*/ + List ret = new ArrayList<>(); + ret.addAll(item.getOriginal().toSource(localData, this)); + ret.add(ins(AVM2Instructions.Not)); + return ret; + } + + @Override + public List generate(SourceGeneratorLocalData localData, DuplicateItem item) { + List ret = new ArrayList<>(); + ret.add(ins(AVM2Instructions.Dup)); + return ret; + } + + @Override + public List generate(SourceGeneratorLocalData localData, BreakItem item) { + List ret = new ArrayList<>(); + AVM2Instruction abreak = ins(new BreakJumpIns(item.loopId), 0); + ret.add(abreak); + return ret; + } + + public List generate(SourceGeneratorLocalData localData, FunctionAVM2Item item) throws CompilationException { + List ret = new ArrayList<>(); + int scope = 0; + if (!item.functionName.isEmpty()) { + ret.add(ins(AVM2Instructions.NewObject, 0)); + ret.add(ins(AVM2Instructions.PushWith)); + scope = localData.scopeStack.size(); + localData.scopeStack.add(new PropertyAVM2Item(null, item.functionName, abcIndex, new ArrayList<>(), localData.callStack)); + } + AVM2ConstantPool constants = abcIndex.getSelectedAbc().constants; + ret.add(ins(AVM2Instructions.NewFunction, method(false, constants.getStringId(item.functionName, true), true, false, localData.callStack, localData.pkg, item.needsActivation, item.subvariables, 0 /*Set later*/, item.hasRest, item.line, localData.currentClass, null, false, localData, item.paramTypes, item.paramNames, item.paramValues, item.body, item.retType))); + if (!item.functionName.isEmpty()) { + ret.add(ins(AVM2Instructions.Dup)); + ret.add(ins(AVM2Instructions.GetScopeObject, scope)); + ret.add(ins(AVM2Instructions.Swap)); + ret.add(ins(AVM2Instructions.SetProperty, constants.getMultinameId(Multiname.createQName(false, constants.getStringId(item.functionName, true), constants.getNamespaceId(Namespace.KIND_PACKAGE, localData.pkg, 0, true)), true))); + ret.add(ins(AVM2Instructions.PopScope)); + localData.scopeStack.remove(localData.scopeStack.size() - 1); + } + return ret; + } + + private static int currentFinId = 1; + + private static int finId() { + return currentFinId++; + } + + public List generate(SourceGeneratorLocalData localData, TryAVM2Item item) throws CompilationException { + List ret = new ArrayList<>(); + + boolean newFinallyReg = false; + List newex = new ArrayList<>(); + int aloneFinallyEx = -1; + int finallyEx = -1; + for (NameAVM2Item e : item.catchExceptions2) { + ABCException aex = new ABCException(); + aex.name_index = abcIndex.getSelectedAbc().constants.getMultinameId(Multiname.createQName(false, abcIndex.getSelectedAbc().constants.getStringId(e.getVariableName(), true), abcIndex.getSelectedAbc().constants.getNamespaceId(Namespace.KIND_PACKAGE, "", 0, true)), true); + aex.type_index = typeName(localData, e.type); + newex.add(aex); + } + int finId = 0; + if (item.finallyCommands != null) { + if (item.catchExceptions2.isEmpty()) { + ABCException aex = new ABCException(); + aex.name_index = 0; + aex.type_index = 0; + newex.add(aex); + aloneFinallyEx = newex.size() - 1; + } + ABCException aex = new ABCException(); + aex.name_index = 0; + aex.type_index = 0; + newex.add(aex); + finallyEx = newex.size() - 1; + if (localData.finallyRegister == -1) { + localData.finallyRegister = getFreeRegister(localData); + killRegister(localData, localData.finallyRegister); //reuse for catches + newFinallyReg = true; + } + finId = finId(); + } + + if (finallyEx > -1) { + localData.finallyCatches.add(finId); + } + List tryCmds = generateToInsList(localData, item.tryCommands); + + //int i = firstId + item.catchCommands.size() - 1; + List catches = new ArrayList<>(); + Reference tempReg = new Reference<>(0); + + List currentExceptionIds = new ArrayList<>(); + List> catchCmds = new ArrayList<>(); + for (int c = 0; c < item.catchCommands.size(); c++) { + int i = localData.exceptions.size(); + localData.exceptions.add(newex.get(c)); + + currentExceptionIds.add(i); + + //Reference tempReg=new Reference<>(0); + List catchCmd = new ArrayList<>(); + catchCmd.add(ins(AVM2Instructions.NewCatch, i)); + catchCmd.addAll(toInsList(AssignableAVM2Item.dupSetTemp(localData, this, tempReg))); + catchCmd.add(ins(AVM2Instructions.Dup)); + catchCmd.add(ins(AVM2Instructions.PushScope)); + catchCmd.add(ins(AVM2Instructions.Swap)); + catchCmd.add(ins(AVM2Instructions.SetSlot, 1)); + + for (AssignableAVM2Item a : item.catchVariables.get(c)) { + GraphTargetItem r = a; + if (r instanceof UnresolvedAVM2Item) { + r = ((UnresolvedAVM2Item) r).resolvedRoot; + } + if (r instanceof NameAVM2Item) { + NameAVM2Item n = (NameAVM2Item) r; + if (item.catchExceptions2.get(c).getVariableName().equals(n.getVariableName())) { + n.setSlotScope(localData.scopeStack.size()); + } + } + } + localData.scopeStack.add(new LocalRegAVM2Item(null, null, tempReg.getVal(), null)); + catchCmd.addAll(generateToInsList(localData, item.catchCommands.get(c))); + localData.scopeStack.remove(localData.scopeStack.size() - 1); + catchCmd.add(ins(AVM2Instructions.PopScope)); + catchCmd.addAll(toInsList(AssignableAVM2Item.killTemp(localData, this, Arrays.asList(tempReg)))); + catchCmds.add(catchCmd); + } + for (int c = item.catchCommands.size() - 1; c >= 0; c--) { + List preCatches = new ArrayList<>(); + /*preCatches.add(ins(AVM2Instructions.GetLocal0)); + preCatches.add(ins(AVM2Instructions.PushScope)); + preCatches.add(AssignableAVM2Item.generateGetLoc(localData.activationReg)); + preCatches.add(ins(AVM2Instructions.PushScope));*/ + for (GraphTargetItem s : localData.scopeStack) { + preCatches.addAll(toInsList(s.toSource(localData, this))); + if (s instanceof WithObjectAVM2Item) { + preCatches.add(ins(AVM2Instructions.PushWith)); + } else { + preCatches.add(ins(AVM2Instructions.PushScope)); + } + } + + //catchCmds.add(catchCmd); + preCatches.addAll(catchCmds.get(c)); + catches.addAll(0, preCatches); + catches.add(0, new ExceptionMarkAVM2Instruction(currentExceptionIds.get(c), MARK_E_TARGET)); + catches.add(0, ins(AVM2Instructions.Jump, insToBytes(catches).length)); + } + + if (aloneFinallyEx > -1) { + localData.exceptions.add(newex.get(aloneFinallyEx)); + aloneFinallyEx = localData.exceptions.size() - 1; + + } + if (finallyEx > -1) { + localData.exceptions.add(newex.get(finallyEx)); + finallyEx = localData.exceptions.size() - 1; + } + + for (int i : currentExceptionIds) { + ret.add(new ExceptionMarkAVM2Instruction(i, MARK_E_START)); + } + if (aloneFinallyEx > -1) { + ret.add(new ExceptionMarkAVM2Instruction(aloneFinallyEx, MARK_E_START)); + } + if (finallyEx > -1) { + ret.add(new ExceptionMarkAVM2Instruction(finallyEx, MARK_E_START)); + } + + ret.addAll(tryCmds); + + for (int i : currentExceptionIds) { + ret.add(new ExceptionMarkAVM2Instruction(i, MARK_E_END)); + } + if (aloneFinallyEx > -1) { + ret.add(new ExceptionMarkAVM2Instruction(aloneFinallyEx, MARK_E_END)); + } + + if (aloneFinallyEx > -1) { + List preCatches = new ArrayList<>(); + for (GraphTargetItem s : localData.scopeStack) { + preCatches.addAll(toInsList(s.toSource(localData, this))); + if (s instanceof WithObjectAVM2Item) { + preCatches.add(ins(AVM2Instructions.PushWith)); + } else { + preCatches.add(ins(AVM2Instructions.PushScope)); + } + } + preCatches.add(ins(AVM2Instructions.NewCatch, aloneFinallyEx)); + preCatches.addAll(toInsList(AssignableAVM2Item.dupSetTemp(localData, this, tempReg))); + preCatches.add(ins(AVM2Instructions.PushScope)); + preCatches.add(ins(AVM2Instructions.Throw)); + preCatches.add(ins(AVM2Instructions.PopScope)); + preCatches.addAll(toInsList(AssignableAVM2Item.killTemp(localData, this, Arrays.asList(tempReg)))); + catches.add(ins(AVM2Instructions.Jump, insToBytes(preCatches).length)); + catches.add(new ExceptionMarkAVM2Instruction(aloneFinallyEx, MARK_E_TARGET)); + catches.addAll(preCatches); + } + AVM2Instruction finSwitch = null; + AVM2Instruction pushDefIns = ins(AVM2Instructions.PushByte, 0); + + int defPos = 0; + if (finallyEx > -1) { + List preCatches = new ArrayList<>(); + preCatches.add(0, new ExceptionMarkAVM2Instruction(finallyEx, MARK_E_TARGET)); + for (GraphTargetItem s : localData.scopeStack) { + preCatches.addAll(toInsList(s.toSource(localData, this))); + if (s instanceof WithObjectAVM2Item) { + preCatches.add(ins(AVM2Instructions.PushWith)); + } else { + preCatches.add(ins(AVM2Instructions.PushScope)); + } + } + preCatches.add(ins(AVM2Instructions.NewCatch, finallyEx)); + preCatches.addAll(toInsList(AssignableAVM2Item.dupSetTemp(localData, this, tempReg))); + preCatches.add(ins(AVM2Instructions.PushScope)); + preCatches.add(ins(AVM2Instructions.PopScope)); + Reference tempReg2 = new Reference<>(0); + preCatches.add(ins(AVM2Instructions.Kill, tempReg.getVal())); + preCatches.add(ins(AVM2Instructions.CoerceA)); + preCatches.addAll(toInsList(AssignableAVM2Item.setTemp(localData, this, tempReg2))); + preCatches.add(pushDefIns); + + List finallySwitchCmds = new ArrayList<>(); + + finSwitch = new AVM2Instruction(0, AVM2Instructions.LookupSwitch, new int[1 + 1 + 1]); + finSwitch.operands[0] = finSwitch.getBytesLength(); + finSwitch.operands[1] = 0; //switch cnt + + List preFinallySwitch = new ArrayList<>(); + preFinallySwitch.add(ins(AVM2Instructions.Label)); + preFinallySwitch.add(ins(AVM2Instructions.Pop)); + int preFinallySwitchLen = insToBytes(preFinallySwitch).length; + + finallySwitchCmds.add(ins(AVM2Instructions.Label)); + finallySwitchCmds.addAll(toInsList(AssignableAVM2Item.getTemp(localData, this, tempReg2))); + finallySwitchCmds.add(ins(AVM2Instructions.Kill, tempReg2.getVal())); + finallySwitchCmds.add(ins(AVM2Instructions.Throw)); + finallySwitchCmds.add(ins(AVM2Instructions.PushByte, 255)); + finallySwitchCmds.add(ins(AVM2Instructions.PopScope)); + finallySwitchCmds.add(ins(AVM2Instructions.Kill, tempReg.getVal())); + + int finSwitchLen = insToBytes(finallySwitchCmds).length; + + preCatches.add(ins(AVM2Instructions.Jump, preFinallySwitchLen + finSwitchLen)); + AVM2Instruction fjump = ins(AVM2Instructions.Jump, 0); + fjump.operands[0] = insToBytes(preCatches).length + preFinallySwitchLen + finSwitchLen; + + preCatches.add(0, fjump); + preCatches.add(0, new ExceptionMarkAVM2Instruction(finallyEx, MARK_E_END)); + preCatches.add(0, ins(AVM2Instructions.PushByte, 255)); + + finallySwitchCmds.add(new ExceptionMarkAVM2Instruction(finallyEx, MARK_E_FINALLYPART)); + + int oldReg = localData.finallyRegister; + localData.finallyRegister = getFreeRegister(localData); + Integer cnt = localData.finallyCounter.get(finId); + if (cnt == null) { + cnt = -1; + } + defPos = cnt; + cnt++; //Skip default clause (throw) + localData.finallyCounter.put(finId, cnt); + finallySwitchCmds.addAll(generateToInsList(localData, item.finallyCommands)); + killRegister(localData, localData.finallyRegister); + localData.finallyRegister = oldReg; + finSwitchLen = insToBytes(finallySwitchCmds).length; + + finSwitch.operands[2] = -finSwitchLen; + preCatches.addAll(preFinallySwitch); + preCatches.addAll(finallySwitchCmds); + preCatches.add(finSwitch); + + catches.addAll(preCatches); + AssignableAVM2Item.killTemp(localData, this, Arrays.asList(tempReg, tempReg2)); + } + + ret.addAll(catches); + //localData.exceptions.addAll(newex); + + if (finallyEx > -1) { + localData.finallyCatches.remove(localData.finallyCatches.size() - 1); + } + if (newFinallyReg) { + localData.finallyRegister = -1; + killRegister(localData, localData.finallyRegister); + } + int pos = 0; + int finallyPos = 0; + int switchPos = 0; + for (int s = 0; s < ret.size(); s++) { + GraphSourceItem src = ret.get(s); + if (src == finSwitch) { + switchPos = pos; + } + if (src instanceof AVM2Instruction) { + AVM2Instruction ins = (AVM2Instruction) src; + if (ins instanceof ExceptionMarkAVM2Instruction) { + ExceptionMarkAVM2Instruction em = (ExceptionMarkAVM2Instruction) ins; + if (em.exceptionId == finallyEx && em.markType == MARK_E_FINALLYPART) { + finallyPos = pos; + ret.remove(s); + s--; + continue; + } + } + pos += ins.getBytesLength(); + } + + } + + if (finSwitch != null) { + pos = 0; + int defLoc = finSwitch.operands[2]; + List switchLoc = new ArrayList<>(); + boolean wasDef = false; + for (int s = 0; s < ret.size(); s++) { + GraphSourceItem src = ret.get(s); + if (src instanceof AVM2Instruction) { + AVM2Instruction ins = (AVM2Instruction) src; + if (ins.definition instanceof FinallyJumpIns) { + FinallyJumpIns fji = (FinallyJumpIns) ins.definition; + if (fji.getClauseId() == finId) { + List bet = new ArrayList<>(); + bet.add(ins(AVM2Instructions.Label)); + bet.add(ins(AVM2Instructions.Pop)); + int betLen = insToBytes(bet).length; + if (wasDef) { + ins.operands[0] = 0; + } else { + ins.operands[0] = finallyPos - (pos + ins.getBytesLength()); + } + ins.definition = AVM2Code.instructionSet[AVM2Instructions.Jump]; + switchLoc.add(pos + ins.getBytesLength() + betLen - switchPos); + } + } + pos += ins.getBytesLength(); + } + if (defPos == switchLoc.size() - 1) { + switchLoc.add(defLoc); + wasDef = true; + } + } + finSwitch.operands = new int[1 + 1 + switchLoc.size()]; + pushDefIns.operands[0] = defPos + 1; + int afterLoc = finSwitch.getBytesLength(); + finSwitch.operands[0] = afterLoc; + finSwitch.operands[1] = switchLoc.size() - 1; + for (int j = 0; j < switchLoc.size(); j++) { + finSwitch.operands[2 + j] = switchLoc.get(j); + } + } + + return ret; + } + + @Override + public List generate(SourceGeneratorLocalData localData, ContinueItem item) { + List ret = new ArrayList<>(); + AVM2Instruction acontinue = ins(new ContinueJumpIns(item.loopId), 0); + ret.add(acontinue); + return ret; + } + + public List generate(SourceGeneratorLocalData localData, ReturnValueAVM2Item item) throws CompilationException { + List ret = new ArrayList<>(); + ret.addAll(item.value.toSource(localData, this)); + if (!localData.finallyCatches.isEmpty()) { + ret.add(ins(AVM2Instructions.CoerceA)); + ret.add(AssignableAVM2Item.generateSetLoc(localData.finallyRegister)); + for (int i = localData.finallyCatches.size() - 1; i >= 0; i--) { + if (i < localData.finallyCatches.size() - 1) { + ret.add(ins(AVM2Instructions.Label)); + } + int clauseId = localData.finallyCatches.get(i); + Integer cnt = localData.finallyCounter.get(clauseId); + if (cnt == null) { + cnt = -1; + } + cnt++; + localData.finallyCounter.put(clauseId, cnt); + ret.addAll(new IntegerValueAVM2Item(null, null, (long) cnt).toSource(localData, this)); + ret.add(ins(new FinallyJumpIns(clauseId), 0)); + ret.add(ins(AVM2Instructions.Label)); + ret.add(ins(AVM2Instructions.Pop)); + } + ret.add(ins(AVM2Instructions.Label)); + ret.add(AssignableAVM2Item.generateGetLoc(localData.finallyRegister)); + ret.add(ins(AVM2Instructions.Kill, localData.finallyRegister)); + } + ret.add(ins(AVM2Instructions.ReturnValue)); + return ret; + } + + public List generate(SourceGeneratorLocalData localData, ReturnVoidAVM2Item item) throws CompilationException { + List ret = new ArrayList<>(); + if (!localData.finallyCatches.isEmpty()) { + + for (int i = 0; i < localData.finallyCatches.size(); i++) { + if (i > 0) { + ret.add(ins(AVM2Instructions.Label)); + } + int clauseId = localData.finallyCatches.get(i); + Integer cnt = localData.finallyCounter.get(clauseId); + if (cnt == null) { + cnt = -1; + } + cnt++; + localData.finallyCounter.put(clauseId, cnt); + ret.addAll(new IntegerValueAVM2Item(null, null, (long) cnt).toSource(localData, this)); + ret.add(ins(new FinallyJumpIns(clauseId), 0)); + ret.add(ins(AVM2Instructions.Label)); + ret.add(ins(AVM2Instructions.Pop)); + } + ret.add(ins(AVM2Instructions.Label)); + } + ret.add(ins(AVM2Instructions.ReturnVoid)); + return ret; + } + + public List generate(SourceGeneratorLocalData localData, ThrowAVM2Item item) throws CompilationException { + List ret = new ArrayList<>(); + ret.addAll(item.value.toSource(localData, this)); + ret.add(ins(AVM2Instructions.Throw)); + return ret; + } + + private List generateToInsList(SourceGeneratorLocalData localData, List commands) throws CompilationException { + return toInsList(generate(localData, commands)); + } + + private List generateToActionList(SourceGeneratorLocalData localData, GraphTargetItem command) throws CompilationException { + return toInsList(command.toSource(localData, this)); + } + + @Override + public List generate(SourceGeneratorLocalData localData, List commands) throws CompilationException { + List ret = new ArrayList<>(); + for (GraphTargetItem item : commands) { + ret.addAll(item.toSourceIgnoreReturnValue(localData, this)); + } + return ret; + } + + public HashMap getRegisterVars(SourceGeneratorLocalData localData) { + return localData.registerVars; + } + + public void setRegisterVars(SourceGeneratorLocalData localData, HashMap value) { + localData.registerVars = value; + } + + public void setInFunction(SourceGeneratorLocalData localData, int value) { + localData.inFunction = value; + } + + public int isInFunction(SourceGeneratorLocalData localData) { + return localData.inFunction; + } + + public boolean isInMethod(SourceGeneratorLocalData localData) { + return localData.inMethod; + } + + public void setInMethod(SourceGeneratorLocalData localData, boolean value) { + localData.inMethod = value; + } + + public int getForInLevel(SourceGeneratorLocalData localData) { + return localData.forInLevel; + } + + public void setForInLevel(SourceGeneratorLocalData localData, int value) { + localData.forInLevel = value; + } + + public int getTempRegister(SourceGeneratorLocalData localData) { + HashMap registerVars = getRegisterVars(localData); + int tmpReg = 0; + for (int i = 0; i < 256; i++) { + if (!registerVars.containsValue(i)) { + tmpReg = i; + break; + } + } + return tmpReg; + } + + public AVM2SourceGenerator(AbcIndexing abc) { + this.abcIndex = abc; + } + + /*public ABC getABC() { + return abc; + }*/ + public void generateClass(List importedClasses, List cinitVariables, boolean cinitNeedsActivation, List cinit, List openedNamespaces, int namespace, int initScope, DottedChain pkg, ClassInfo classInfo, InstanceInfo instanceInfo, SourceGeneratorLocalData localData, boolean isInterface, String name, String superName, GraphTargetItem extendsVal, List implementsStr, GraphTargetItem iinit, List iinitVariables, boolean iinitNeedsActivation, List traitItems, Reference class_index) throws AVM2ParseException, CompilationException { + localData.currentClass = name; + localData.pkg = pkg; + localData.privateNs = abcIndex.getSelectedAbc().constants.getNamespaceId(Namespace.KIND_PRIVATE, pkg.toRawString() + ":" + name, 0, true); + localData.protectedNs = abcIndex.getSelectedAbc().constants.getNamespaceId(Namespace.KIND_PROTECTED, pkg.toRawString() + ":" + name, 0, true); + if (extendsVal == null && !isInterface) { + extendsVal = new TypeItem(DottedChain.OBJECT); + } + ParsedSymbol s = null; + + if (Configuration.handleSkinPartsAutomatically.get()) { + + Map skinParts = new HashMap<>(); + for (GraphTargetItem t : traitItems) { + String tname = null; + List>> tmetadata = null; + if (t instanceof MethodAVM2Item) { + tname = ((MethodAVM2Item) t).functionName; + tmetadata = ((MethodAVM2Item) t).metadata; + } else if (t instanceof SlotAVM2Item) { + tname = ((SlotAVM2Item) t).var; + tmetadata = ((SlotAVM2Item) t).metadata; + } else if (t instanceof ConstAVM2Item) { + tname = ((ConstAVM2Item) t).var; + tmetadata = ((ConstAVM2Item) t).metadata; + } + if (tname != null && tmetadata != null) { + for (Map.Entry> en : tmetadata) { + if ("SkinPart".equals(en.getKey())) { + boolean req = false; + if (en.getValue().containsKey("required")) { + if ("true".equals(en.getValue().get("required"))) { + req = true; + } + } + skinParts.put(tname, req); + } + } + } + } + if (!skinParts.isEmpty()) { + + //Merge parts from _skinParts attribute of parent class + GraphTargetItem parent = extendsVal; + if (parent instanceof UnresolvedAVM2Item) { + parent = ((UnresolvedAVM2Item) parent).resolved; + } + if (parent instanceof TypeItem) { + ClassIndex ci = abcIndex.findClass(parent); + if (ci != null) { + int mi = ci.abc.class_info.get(ci.index).cinit_index; + MethodBody pcinit = ci.abc.findBody(mi); + ConvertData d = new ConvertData(); + + List initt = new ArrayList<>(); + initt.add(ci.abc.class_info.get(ci.index).static_traits); + + try { + pcinit.convert(d, "-", ScriptExportMode.AS, true, mi, -1, ci.index, ci.abc, null, new ScopeStack(), GraphTextWriter.TRAIT_CLASS_INITIALIZER, new NulWriter(), new ArrayList<>(), initt, false); + //FIXME! Add skinparts from _skinParts attribute of parent class!!! + } catch (InterruptedException ex) { + Logger.getLogger(AVM2SourceGenerator.class.getName()).log(Level.SEVERE, "Getting parent skinparts interrupted", ex); + } + for (Trait t : ci.abc.class_info.get(ci.index).static_traits.traits) { + if (t instanceof TraitSlotConst) { + TraitSlotConst tsc = (TraitSlotConst) t; + if (tsc.kindType == Trait.TRAIT_SLOT) { + if ("_skinParts".equals(tsc.getName(ci.abc).getName(ci.abc.constants, new ArrayList<>(), true))) { + if (d.assignedValues.containsKey(tsc)) { + if (d.assignedValues.get(tsc).value instanceof NewObjectAVM2Item) { + NewObjectAVM2Item no = (NewObjectAVM2Item) d.assignedValues.get(tsc).value; + for (NameValuePair nvp : no.pairs) { + skinParts.put(EcmaScript.toString(nvp.name.getResult()), EcmaScript.toBoolean(nvp.value.getResult())); + } + } + + } + } + } + } + } + + } + } + + /* + Add + override protected function get skinParts() : Object + { + return _skinParts; + } + */ + List getterBody = new ArrayList<>(); + UnresolvedAVM2Item sp = new UnresolvedAVM2Item(new ArrayList<>(), importedClasses, false, TypeItem.UNBOUNDED, 0, new DottedChain("_skinParts"), + null, openedNamespaces); + getterBody.add(new ReturnValueAVM2Item(null, null, sp)); + List subvars = new ArrayList<>(); + subvars.add(sp); + List> allopns = new ArrayList<>(); + allopns.add(openedNamespaces); + + GetterAVM2Item getter = new GetterAVM2Item(allopns, false, false, new ArrayList<>(), new NamespaceItem(pkg.toRawString() + ":" + name, Namespace.KIND_PROTECTED), isInterface, null, false, false, 0, + true, false, false, "skinParts", new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), + getterBody, subvars, new TypeItem("Object")); + + /* + Add + private static var _skinParts = {attr1:false, attr2:true}; + */ + List pairs = new ArrayList<>(); + for (String tname : skinParts.keySet()) { + pairs.add(new NameValuePair(new StringAVM2Item(null, null, tname), skinParts.get(tname) ? new TrueItem(null, null) : new FalseItem(null, null))); + } + + NewObjectAVM2Item sltVal = new NewObjectAVM2Item(null, null, pairs); + + SlotAVM2Item slt = new SlotAVM2Item( + new ArrayList<>(), new NamespaceItem(pkg.toRawString() + ":" + name, Namespace.KIND_PRIVATE), + null, true, "_skinParts", new TypeItem("Object"), sltVal, 0); + + traitItems.add(0, slt); + traitItems.add(getter); + + } + } + + Trait[] it = generateTraitsPhase1(importedClasses, openedNamespaces, name, superName, false, localData, traitItems, instanceInfo.instance_traits, class_index); + Trait[] st = generateTraitsPhase1(importedClasses, openedNamespaces, name, superName, true, localData, traitItems, classInfo.static_traits, class_index); + generateTraitsPhase2(importedClasses, pkg, traitItems, it, openedNamespaces, localData); + generateTraitsPhase2(importedClasses, pkg, traitItems, st, openedNamespaces, localData); + abcIndex.refreshSelected(); + generateTraitsPhase3(importedClasses, initScope, isInterface, name, superName, false, localData, traitItems, instanceInfo.instance_traits, it, new HashMap<>(), class_index); + generateTraitsPhase3(importedClasses, initScope, isInterface, name, superName, true, localData, traitItems, classInfo.static_traits, st, new HashMap<>(), class_index); + int init; + if (iinit == null || isInterface) { + instanceInfo.iinit_index = init = method(false, 0, false, isInterface, new ArrayList<>(), pkg, false, new ArrayList<>(), initScope + 1, false, 0, isInterface ? null : name, extendsVal != null ? extendsVal.toString() : null, true, localData, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), TypeItem.UNBOUNDED/*?? FIXME*/); + } else { + MethodAVM2Item m = (MethodAVM2Item) iinit; + instanceInfo.iinit_index = init = method(false, str(pkg.toRawString() + ":" + name + "/" + name), false, false, new ArrayList<>(), pkg, m.needsActivation, m.subvariables, initScope + 1, m.hasRest, m.line, name, extendsVal != null ? extendsVal.toString() : null, true, localData, m.paramTypes, m.paramNames, m.paramValues, m.body, TypeItem.UNBOUNDED/*?? FIXME*/); + } + + //Class initializer + int cinit_index = method(true, str(""), false, false, new ArrayList<>(), pkg, cinitNeedsActivation, cinitVariables, initScope + (implementsStr.isEmpty() ? 0 : 1), false, 0, isInterface ? null : name, superName, false, localData, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), cinit, TypeItem.UNBOUNDED); + MethodBody cinitBody = abcIndex.getSelectedAbc().findBody(cinit_index); + + List sinitcode = new ArrayList<>(); + List initcode = new ArrayList<>(); + for (GraphTargetItem ti : traitItems) { + if ((ti instanceof SlotAVM2Item) || (ti instanceof ConstAVM2Item)) { + GraphTargetItem val = null; + boolean isStatic = false; + int ns = -1; + String tname = null; + boolean isConst = false; + if (ti instanceof SlotAVM2Item) { + val = ((SlotAVM2Item) ti).value; + isStatic = ((SlotAVM2Item) ti).isStatic(); + ns = genNs(importedClasses, pkg, ((SlotAVM2Item) ti).pkg, openedNamespaces, localData, ((SlotAVM2Item) ti).line); + tname = ((SlotAVM2Item) ti).var; + } + if (ti instanceof ConstAVM2Item) { + val = ((ConstAVM2Item) ti).value; + isStatic = ((ConstAVM2Item) ti).isStatic(); + ns = genNs(importedClasses, pkg, ((ConstAVM2Item) ti).pkg, openedNamespaces, localData, ((ConstAVM2Item) ti).line); + tname = ((ConstAVM2Item) ti).var; + isConst = true; + } + if (isStatic && val != null) { + sinitcode.add(ins(AVM2Instructions.FindProperty, traitName(ns, tname))); + localData.isStatic = true; + sinitcode.addAll(toInsList(val.toSource(localData, this))); + sinitcode.add(ins(isConst ? AVM2Instructions.InitProperty : AVM2Instructions.SetProperty, traitName(ns, tname))); + } + if (!isStatic && val != null) { + //do not init basic values, that can be stored in trait + if (!(val instanceof IntegerValueAVM2Item) && !(val instanceof StringAVM2Item) && !(val instanceof BooleanAVM2Item) && !(val instanceof NullAVM2Item) && !(val instanceof UndefinedAVM2Item)) { + initcode.add(ins(AVM2Instructions.GetLocal0)); + localData.isStatic = false; + initcode.addAll(toInsList(val.toSource(localData, this))); + initcode.add(ins(isConst ? AVM2Instructions.InitProperty : AVM2Instructions.SetProperty, traitName(ns, tname))); + } + } + } + } + MethodBody initBody = null; + if (!isInterface) { + initBody = abcIndex.getSelectedAbc().findBody(init); + initBody.insertAll(iinit == null ? 0 : 2, initcode);//after getlocal0,pushscope + + if (cinitBody.getCode().code.get(cinitBody.getCode().code.size() - 1).definition instanceof ReturnVoidIns) { + cinitBody.insertAll(2, sinitcode); //after getlocal0,pushscope + } + } + cinitBody.markOffsets(); + cinitBody.autoFillStats(abcIndex.getSelectedAbc(), initScope + (implementsStr.isEmpty() ? 0 : 1), true); + + classInfo.cinit_index = cinit_index; + if (initBody != null) { + initBody.autoFillStats(abcIndex.getSelectedAbc(), initScope + 1, true); + } + instanceInfo.interfaces = new int[implementsStr.size()]; + for (int i = 0; i < implementsStr.size(); i++) { + instanceInfo.interfaces[i] = superIntName(localData, implementsStr.get(i)); + } + + } + + @Override + public List generate(SourceGeneratorLocalData localData, CommaExpressionItem item) throws CompilationException { + if (item.commands.isEmpty()) { + return new ArrayList<>(); + } + + //We need to handle commands and last expression separately, otherwise last expression result will be popped + List cmds = new ArrayList<>(item.commands); + GraphTargetItem lastExpr = cmds.remove(cmds.size() - 1); + List ret = new ArrayList<>(); + ret.addAll(generate(localData, cmds)); + ret.addAll(lastExpr.toSource(localData, this)); + return ret; + } + + public int generateClass(int namespace, ClassInfo ci, InstanceInfo ii, int initScope, DottedChain pkg, SourceGeneratorLocalData localData, AVM2Item cls, Reference class_index) throws AVM2ParseException, CompilationException { + /*ClassInfo ci = new ClassInfo(); + InstanceInfo ii = new InstanceInfo(); + abc.class_info.add(ci); + abc.instance_info.add(ii); + */ + if (cls instanceof ClassAVM2Item) { + ClassAVM2Item cai = (ClassAVM2Item) cls; + //TODO: iinit variables, iinit activation + generateClass(cai.importedClasses, cai.cinitVariables, cai.cinitActivation, cai.staticInit, + cai.openedNamespaces, + namespace, + initScope, pkg, ci, ii, + localData, false, + cai.className, cai.extendsOp == null ? "Object" : cai.extendsOp.toString(), + cai.extendsOp, cai.implementsOp, cai.iinit, + cai.iinitVariables, cai.iinitActivation, cai.traits, class_index + ); + if (!cai.isDynamic) { + ii.flags |= InstanceInfo.CLASS_SEALED; + } + if (cai.isFinal) { + ii.flags |= InstanceInfo.CLASS_FINAL; + } + ii.flags |= InstanceInfo.CLASS_PROTECTEDNS; + ii.protectedNS = abcIndex.getSelectedAbc().constants.getNamespaceId(Namespace.KIND_PROTECTED, pkg.toRawString() + ":" + cai.className, 0, true); + } + if (cls instanceof InterfaceAVM2Item) { + InterfaceAVM2Item iai = (InterfaceAVM2Item) cls; + ii.flags |= InstanceInfo.CLASS_INTERFACE; + ii.flags |= InstanceInfo.CLASS_SEALED; + generateClass(iai.importedClasses, new ArrayList<>(), false, new ArrayList<>(), + iai.openedNamespaces, namespace, initScope, pkg, ci, ii, localData, true, iai.name, null, null, iai.superInterfaces, null, null, false, iai.methods, + class_index + ); + } + + return abcIndex.getSelectedAbc().instance_info.size() - 1; + } + + public int traitName(int namespace, String var) { + return abcIndex.getSelectedAbc().constants.getMultinameId(Multiname.createQName(false, str(var), namespace), true); + } + + public int typeName(SourceGeneratorLocalData localData, GraphTargetItem type) throws CompilationException { + if (type instanceof UnboundedTypeItem) { + return 0; + } + if (("" + type).equals("*")) { + return 0; + } + + return resolveType(localData, type, abcIndex); + /* + TypeItem nameItem = (TypeItem) type; + name = nameItem.fullTypeName; + if (name.contains(".")) { + pkg = name.substring(0, name.lastIndexOf('.')); + name = name.substring(name.lastIndexOf('.') + 1); + } + if (!nameItem.subtypes.isEmpty()) { //It's vector => TypeName + List params = new ArrayList<>(); + for (GraphTargetItem p : nameItem.subtypes) { + params.add(typeName(localData, p));//abc.getLastAbc().constants.getMultinameId(new Multiname(Multiname.QNAME, str(p), namespace(Namespace.KIND_PACKAGE, ppkg), 0, 0, new ArrayList()), true)); + } + int qname = abc.getLastAbc().constants.getMultinameId(new Multiname(Multiname.QNAME, str(name), namespace(Namespace.KIND_PACKAGE, pkg), 0, 0, new ArrayList()), true); + return abc.getLastAbc().constants.getMultinameId(new Multiname(Multiname.TYPENAME, 0, 0, 0, qname, params), true); + } else { + return abc.getLastAbc().constants.getMultinameId(new Multiname(Multiname.QNAME, str(name), namespace(Namespace.KIND_PACKAGE, pkg), 0, 0, new ArrayList()), true); + }*/ + } + + public int ident(GraphTargetItem name) { + if (name instanceof NameAVM2Item) { + return str(((NameAVM2Item) name).getVariableName()); + } + throw new RuntimeException("no ident"); //FIXME + } + + public int namespace(int nsKind, String name) { + return abcIndex.getSelectedAbc().constants.getNamespaceId(nsKind, str(name), 0, true); + } + + public int str(String name) { + return abcIndex.getSelectedAbc().constants.getStringId(name, true); + } + + public int propertyName(GraphTargetItem name) { + if (name instanceof NameAVM2Item) { + NameAVM2Item va = (NameAVM2Item) name; + return abcIndex.getSelectedAbc().constants.getMultinameId(Multiname.createQName(false, str(va.getVariableName()), namespace(Namespace.KIND_PACKAGE, "")), true); + } + throw new RuntimeException("no prop"); //FIXME + } + + public int getFreeRegister(SourceGeneratorLocalData localData) { + for (int i = 0;; i++) { + if (!localData.registerVars.containsValue(i)) { + localData.registerVars.put("__TEMP__" + i, i); + return i; + } + } + } + + public boolean killRegister(SourceGeneratorLocalData localData, int i) { + String key = null; + for (String k : localData.registerVars.keySet()) { + if (localData.registerVars.get(k) == i) { + key = k; + break; + } + } + if (key != null) { + localData.registerVars.remove(key); + return true; + } + return false; + } + + public int method(boolean isStatic, int name_index, boolean subMethod, boolean isInterface, List callStack, DottedChain pkg, boolean needsActivation, List subvariables, int initScope, boolean hasRest, int line, String className, String superType, boolean constructor, SourceGeneratorLocalData localData, List paramTypes, List paramNames, List paramValues, List body, GraphTargetItem retType) throws CompilationException { + //Reference hasArgs = new Reference<>(Boolean.FALSE); + //calcRegisters(localData,needsActivation,paramNames,subvariables,body, hasArgs); + SourceGeneratorLocalData newlocalData = new SourceGeneratorLocalData(new HashMap<>(), 1, true, 0); + newlocalData.currentClass = className; + newlocalData.pkg = localData.pkg; + newlocalData.callStack.addAll(localData.callStack); + newlocalData.traitUsages = localData.traitUsages; + newlocalData.currentScript = localData.currentScript; + newlocalData.documentClass = localData.documentClass; + newlocalData.privateNs = localData.privateNs; + newlocalData.protectedNs = localData.protectedNs; + newlocalData.isStatic = isStatic; + newlocalData.subMethod = subMethod; + localData = newlocalData; + + localData.activationReg = 0; + + for (int i = 0; i < subvariables.size(); i++) { + AssignableAVM2Item an = subvariables.get(i); + if (an instanceof UnresolvedAVM2Item) { + UnresolvedAVM2Item n = (UnresolvedAVM2Item) an; + if (n.resolved == null) { + String fullClass = localData.getFullClass(); + GraphTargetItem res = n.resolve(new TypeItem(fullClass), paramTypes, paramNames, abcIndex, callStack, subvariables); + if (res instanceof AssignableAVM2Item) { + subvariables.set(i, (AssignableAVM2Item) res); + } else { + subvariables.remove(i); + i--; + } + } + } + } + + for (int t = 0; t < paramTypes.size(); t++) { + GraphTargetItem an = paramTypes.get(t); + if (an instanceof UnresolvedAVM2Item) { + UnresolvedAVM2Item n = (UnresolvedAVM2Item) an; + if (n.resolved == null) { + String fullClass = localData.getFullClass(); + GraphTargetItem res = n.resolve(new TypeItem(fullClass), paramTypes, paramNames, abcIndex, callStack, subvariables); + paramTypes.set(t, res); + } + } + } + + boolean hasArguments = false; + List slotNames = new ArrayList<>(); + List slotTypes = new ArrayList<>(); + slotNames.add("--first"); + slotTypes.add("-"); + + int paramLine = 0; //? + + List registerNames = new ArrayList<>(); + List registerLines = new ArrayList<>(); + List registerTypes = new ArrayList<>(); + if (className != null) { + String fullClassName = pkg.add(className).toRawString(); + registerTypes.add(fullClassName); + localData.scopeStack.add(new LocalRegAVM2Item(null, null, registerNames.size(), null)); + registerNames.add("this"); + registerLines.add(0); //? + + } else { + registerTypes.add("global"); + registerNames.add("this"); + registerLines.add(0); //? + } + for (GraphTargetItem t : paramTypes) { + registerTypes.add(t.toString()); + slotTypes.add(t.toString()); + } + for (int i = 0; i < paramNames.size(); i++) { + registerLines.add(paramLine); + } + registerNames.addAll(paramNames); + slotNames.addAll(paramNames); + /*for (GraphTargetItem p : paramTypes) { + slotTypes.add("" + p); + }*/ + if (hasRest) { + registerTypes.add("Array"); + slotTypes.add("Array"); + } + localData.registerVars.clear(); + for (AssignableAVM2Item an : subvariables) { + if (an instanceof NameAVM2Item) { + NameAVM2Item n = (NameAVM2Item) an; + if (n.getVariableName().equals("arguments") & !n.isDefinition()) { + registerNames.add("arguments"); + registerTypes.add("Object"); + registerLines.add(0); //? + hasArguments = true; + break; + } + } + } + int paramRegCount = registerNames.size(); + + if (needsActivation) { + registerNames.add("+$activation"); + registerLines.add(0); //? + localData.activationReg = registerNames.size() - 1; + registerTypes.add("Object"); + localData.scopeStack.add(new LocalRegAVM2Item(null, null, localData.activationReg, null)); + } + + String mask = Configuration.registerNameFormat.get(); + mask = mask.replace("%d", "([0-9]+)"); + Pattern pat = Pattern.compile(mask); + + //Two rounds + for (int round = 1; round <= 2; round++) { + for (AssignableAVM2Item an : subvariables) { + if (an instanceof NameAVM2Item) { + NameAVM2Item n = (NameAVM2Item) an; + if (n.isDefinition() && !registerNames.contains(n.getVariableName())) { + if (!needsActivation || (n.getSlotScope() <= 0)) { + String varName = n.getVariableName(); + Matcher m = pat.matcher(varName); + //In first round, make all register that match standard loc_xx register + if ((round == 1) && (m.matches())) { + String regIndexStr = m.group(1); + int regIndex = Integer.parseInt(regIndexStr); + while (registerNames.size() <= regIndex + 1) { + String standardName = String.format(mask, registerNames.size() - 1); + registerNames.add(standardName); + registerTypes.add("*"); + slotNames.add(standardName); + slotTypes.add("*"); + registerLines.add(paramLine); + } + registerNames.set(regIndex, varName); + registerTypes.set(regIndex, varName); + slotNames.set(regIndex, varName); + slotTypes.set(regIndex, varName); + registerLines.set(regIndex, n.line); + } //in second round the rest + else if (round == 2 && !m.matches()) { + registerNames.add(n.getVariableName()); + registerTypes.add(n.type.toString()); + slotNames.add(n.getVariableName()); + slotTypes.add(n.type.toString()); + registerLines.add(n.line); + } + } + } + } + } + } + + int slotScope = subMethod ? 0 : 1; + + for (AssignableAVM2Item an : subvariables) { + if (an instanceof NameAVM2Item) { + NameAVM2Item n = (NameAVM2Item) an; + String variableName = n.getVariableName(); + if (variableName != null) { + boolean isThisOrSuper = variableName.equals("this") || variableName.equals("super"); + if (!isThisOrSuper && needsActivation) { + if (n.getSlotNumber() <= 0) { + n.setSlotNumber(slotNames.indexOf(variableName)); + n.setSlotScope(slotScope); + } + } else if (isThisOrSuper) { + n.setRegNumber(0); + } else { + n.setRegNumber(registerNames.indexOf(variableName)); + } + } + } + } + + for (int i = 0; i < registerNames.size(); i++) { + if (needsActivation && i > localData.activationReg) { + break; + } + localData.registerVars.put(registerNames.get(i), i); + } + List declarations = new ArrayList<>(); + loopn: + for (AssignableAVM2Item an : subvariables) { + if (an instanceof NameAVM2Item) { + NameAVM2Item n = (NameAVM2Item) an; + + if (needsActivation) { + if (n.getSlotScope() != slotScope) { + continue; + } else if (n.getSlotNumber() < paramRegCount) { + continue; + } + } + for (NameAVM2Item d : declarations) { + if (n.getVariableName() != null && n.getVariableName().equals(d.getVariableName())) { + continue loopn; + } + } + + for (GraphTargetItem it : body) { //search first level of commands + if (it instanceof NameAVM2Item) { + NameAVM2Item n2 = (NameAVM2Item) it; + if (n2.isDefinition() && n2.getAssignedValue() != null && n2.getVariableName().equals(n.getVariableName())) { + continue loopn; + } + if (!n2.isDefinition() && n2.getVariableName() != null && n2.getVariableName().equals(n.getVariableName())) { //used earlier than defined + break; + } + } + } + if (n.unresolved) { + continue; + } + if (n.redirect != null) { + continue; + } + if (n.getNs() != null) { + continue; + } + + String variableName = n.getVariableName(); + if ("this".equals(variableName) || "super".equals(variableName) || paramNames.contains(variableName) || "arguments".equals(variableName)) { + continue; + } + + NameAVM2Item d = new NameAVM2Item(n.type, n.line, n.getVariableName(), NameAVM2Item.getDefaultValue("" + n.type), true, n.openedNamespaces); + //no index + if (needsActivation) { + if (d.getSlotNumber() <= 0) { + d.setSlotNumber(n.getSlotNumber()); + d.setSlotScope(n.getSlotScope()); + } + } else { + d.setRegNumber(n.getRegNumber()); + } + declarations.add(d); + } + } + + int[] param_types = new int[paramTypes.size()]; + ValueKind[] optional = new ValueKind[paramValues.size()]; + //int[] param_names = new int[paramNames.size()]; + for (int i = 0; i < paramTypes.size(); i++) { + param_types[i] = typeName(localData, paramTypes.get(i)); + //param_names[i] = str(paramNames.get(i)); + } + + for (int i = 0; i < paramValues.size(); i++) { + optional[i] = getValueKind(Namespace.KIND_NAMESPACE/*FIXME*/, paramTypes.get(paramTypes.size() - paramValues.size() + i), paramValues.get(i)); + if (optional[i] == null) { + throw new CompilationException("Default value must be compiletime constant", line); + } + } + + MethodInfo mi = new MethodInfo(param_types, constructor ? 0 : typeName(localData, retType), name_index, 0, optional, new int[0]/*no param_names*/); + if (hasArguments) { + mi.setFlagNeed_Arguments(); + } + //No param names like in official + /* + if (!paramNames.isEmpty()) { + mi.setFlagHas_paramnames(); + }*/ + if (!paramValues.isEmpty()) { + mi.setFlagHas_optional(); + } + if (hasRest) { + mi.setFlagNeed_rest(); + } + + int mindex; + if (!isInterface) { + MethodBody mbody = new MethodBody(abcIndex.getSelectedAbc(), new Traits(), new byte[0], new ABCException[0]); + + if (needsActivation) { + int slotId = 1; + for (int i = 1; i < slotNames.size(); i++) { + TraitSlotConst tsc = new TraitSlotConst(); + tsc.slot_id = slotId++; + tsc.name_index = abcIndex.getSelectedAbc().constants.getMultinameId(Multiname.createQName(false, abcIndex.getSelectedAbc().constants.getStringId(slotNames.get(i), true), abcIndex.getSelectedAbc().constants.getNamespaceId(Namespace.KIND_PACKAGE_INTERNAL, pkg, 0, true)), true); + tsc.type_index = typeName(localData, new TypeItem(slotTypes.get(i))); + mbody.traits.traits.add(tsc); + } + for (int i = 1; i < paramRegCount; i++) { + NameAVM2Item param = new NameAVM2Item(new TypeItem(registerTypes.get(i)), 0, registerNames.get(i), null, false, new ArrayList<>()); + param.setRegNumber(i); + NameAVM2Item d = new NameAVM2Item(new TypeItem(registerTypes.get(i)), 0, registerNames.get(i), param, true, new ArrayList<>()); + d.setSlotScope(slotScope); + d.setSlotNumber(slotNames.indexOf(registerNames.get(i))); + declarations.add(d); + } + } + if (body != null) { + body.addAll(0, declarations); + } + + localData.exceptions = new ArrayList<>(); + localData.callStack.add(mbody); + List src = body == null ? new ArrayList<>() : generate(localData, body); + + mbody.method_info = abcIndex.getSelectedAbc().addMethodInfo(mi); + ArrayList mbodyCode = toInsList(src); + mbody.setCode(new AVM2Code(mbodyCode)); + + if (needsActivation) { + if (localData.traitUsages.containsKey(mbody)) { + List usages = localData.traitUsages.get(mbody); + for (int i = 0; i < mbody.traits.traits.size(); i++) { + if (usages.contains(i)) { + TraitSlotConst tsc = (TraitSlotConst) mbody.traits.traits.get(i); + GraphTargetItem type = TypeItem.UNBOUNDED; + if (tsc.type_index > 0) { + type = new TypeItem(abcIndex.getSelectedAbc().constants.getMultiname(tsc.type_index).getNameWithNamespace(abcIndex.getSelectedAbc().constants)); + } + NameAVM2Item d = new NameAVM2Item(type, 0, tsc.getName(abcIndex.getSelectedAbc()).getName(abcIndex.getSelectedAbc().constants, null, true), NameAVM2Item.getDefaultValue("" + type), true, new ArrayList<>()); + d.setSlotNumber(tsc.slot_id); + d.setSlotScope(slotScope); + mbodyCode.addAll(0, toInsList(d.toSourceIgnoreReturnValue(localData, this))); + } + } + } + + List acts = new ArrayList<>(); + acts.add(ins(AVM2Instructions.NewActivation)); + acts.add(ins(AVM2Instructions.Dup)); + acts.add(AssignableAVM2Item.generateSetLoc(localData.activationReg)); + acts.add(ins(AVM2Instructions.PushScope)); + + mbodyCode.addAll(0, acts); + } + + if (constructor) { + /* List abcs = new ArrayList<>(); + abcs.add(abc); + abcs.addAll(allABCs); + */ + int parentConstMinAC = 0; + + AbcIndexing.ClassIndex ci = abcIndex.findClass(new TypeItem(superType)); + + if (ci != null) { + MethodInfo pmi = ci.abc.method_info.get(ci.abc.instance_info.get(ci.index).iinit_index); + parentConstMinAC = pmi.param_types.length; + if (pmi.flagHas_optional()) { + parentConstMinAC -= pmi.optional.length; + } + } + int ac = -1; + for (AVM2Instruction ins : mbodyCode) { + if (ins.definition instanceof ConstructSuperIns) { + ac = ins.operands[0]; + if (parentConstMinAC > ac) { + throw new CompilationException("Parent constructor call requires different number of arguments", line); + } + + } + } + if (ac == -1) { + if (parentConstMinAC == 0) { + mbodyCode.add(0, new AVM2Instruction(0, AVM2Instructions.GetLocal0, null)); + mbodyCode.add(1, new AVM2Instruction(0, AVM2Instructions.ConstructSuper, new int[]{0})); + + } else { + throw new CompilationException("Parent constructor must be called", line); + } + } + } + for (int i = 1; i < registerNames.size(); i++) { + mbodyCode.add(i - 1, ins(AVM2Instructions.Debug, 1, str(registerNames.get(i)), i - 1, (int) registerLines.get(i))); + } + if (!subMethod) { + mbodyCode.add(0, new AVM2Instruction(0, AVM2Instructions.GetLocal0, null)); + mbodyCode.add(1, new AVM2Instruction(0, AVM2Instructions.PushScope, null)); + } + boolean addRet = false; + if (!mbodyCode.isEmpty()) { + InstructionDefinition lastDef = mbodyCode.get(mbodyCode.size() - 1).definition; + if (!((lastDef instanceof ReturnVoidIns) || (lastDef instanceof ReturnValueIns))) { + addRet = true; + } + } else { + addRet = true; + } + if (addRet) { + if (retType.toString().equals("*") || retType.toString().equals("void") || constructor) { + mbodyCode.add(new AVM2Instruction(0, AVM2Instructions.ReturnVoid, null)); + } else { + mbodyCode.add(new AVM2Instruction(0, AVM2Instructions.PushUndefined, null)); + mbodyCode.add(new AVM2Instruction(0, AVM2Instructions.ReturnValue, null)); + } + } + mbody.exceptions = localData.exceptions.toArray(new ABCException[localData.exceptions.size()]); + int offset = 0; + for (int i = 0; i < mbodyCode.size(); i++) { + AVM2Instruction ins = mbodyCode.get(i); + if (ins instanceof ExceptionMarkAVM2Instruction) { + ExceptionMarkAVM2Instruction m = (ExceptionMarkAVM2Instruction) ins; + switch (m.markType) { + case MARK_E_START: + mbody.exceptions[m.exceptionId].start = offset; + break; + case MARK_E_END: + mbody.exceptions[m.exceptionId].end = offset; + break; + case MARK_E_TARGET: + mbody.exceptions[m.exceptionId].target = offset; + break; + } + mbodyCode.remove(i); + i--; + continue; + } + offset += ins.getBytesLength(); + } + + mbody.markOffsets(); + mbody.autoFillStats(abcIndex.getSelectedAbc(), initScope, className != null); + abcIndex.getSelectedAbc().addMethodBody(mbody); + mindex = mbody.method_info; + } else { + mindex = abcIndex.getSelectedAbc().addMethodInfo(mi); + } + + return mindex; + } + + public ValueKind getValueKind(int ns, GraphTargetItem type, GraphTargetItem val) { + + if (val instanceof BooleanAVM2Item) { + BooleanAVM2Item bi = (BooleanAVM2Item) val; + if (bi.value) { + return new ValueKind(0, ValueKind.CONSTANT_True); + } else { + return new ValueKind(0, ValueKind.CONSTANT_False); + } + } + + boolean isNs = false; + if (type instanceof NameAVM2Item) { + if (((NameAVM2Item) type).getVariableName().equals("namespace")) { + isNs = true; + } + } + + if ((type instanceof TypeItem) && (((TypeItem) type).fullTypeName.equals(DottedChain.NAMESPACE))) { + isNs = true; + } + + if (val instanceof StringAVM2Item) { + StringAVM2Item sval = (StringAVM2Item) val; + if (isNs) { + return new ValueKind(namespace(Namespace.KIND_NAMESPACE, sval.getValue()), ValueKind.CONSTANT_Namespace); + } else { + return new ValueKind(str(sval.getValue()), ValueKind.CONSTANT_Utf8); + } + } + if (val instanceof IntegerValueAVM2Item) { + return new ValueKind(abcIndex.getSelectedAbc().constants.getIntId(((IntegerValueAVM2Item) val).value, true), ValueKind.CONSTANT_Int); + } + if (val instanceof FloatValueAVM2Item) { + return new ValueKind(abcIndex.getSelectedAbc().constants.getDoubleId(((FloatValueAVM2Item) val).value, true), ValueKind.CONSTANT_Double); + } + if (val instanceof NanAVM2Item) { + return new ValueKind(abcIndex.getSelectedAbc().constants.getDoubleId(Double.NaN, true), ValueKind.CONSTANT_Double); + } + if (val instanceof NullAVM2Item) { + return new ValueKind(0, ValueKind.CONSTANT_Null); + } + if (val instanceof UndefinedAVM2Item) { + return new ValueKind(0, ValueKind.CONSTANT_Undefined); + } + return null; + } + + private int genNs(List importedClasses, DottedChain pkg, NamespaceItem ns, List openedNamespaces, SourceGeneratorLocalData localData, int line) throws CompilationException { + ns.resolveCustomNs(abcIndex, importedClasses, pkg, openedNamespaces, localData); + return ns.getCpoolIndex(abcIndex); + } + + public void generateTraitsPhase2(List importedClasses, DottedChain pkg, List items, Trait[] traits, List openedNamespaces, SourceGeneratorLocalData localData) throws CompilationException { + for (int k = 0; k < items.size(); k++) { + GraphTargetItem item = items.get(k); + if (traits[k] == null) { + + } else if (item instanceof InterfaceAVM2Item) { + traits[k].name_index = traitName(((InterfaceAVM2Item) item).pkg == null ? 0 : ((InterfaceAVM2Item) item).pkg.getCpoolIndex(abcIndex), ((InterfaceAVM2Item) item).name); + } else if (item instanceof ClassAVM2Item) { + traits[k].name_index = traitName(((ClassAVM2Item) item).pkg == null ? 0 : ((ClassAVM2Item) item).pkg.getCpoolIndex(abcIndex), ((ClassAVM2Item) item).className); + } else if ((item instanceof MethodAVM2Item) || (item instanceof GetterAVM2Item) || (item instanceof SetterAVM2Item)) { + traits[k].name_index = traitName(genNs(importedClasses, pkg, ((MethodAVM2Item) item).pkg, openedNamespaces, localData, ((MethodAVM2Item) item).line), ((MethodAVM2Item) item).functionName); + } else if (item instanceof FunctionAVM2Item) { + traits[k].name_index = traitName(((FunctionAVM2Item) item).pkg == null ? 0 : ((FunctionAVM2Item) item).pkg.getCpoolIndex(abcIndex), ((FunctionAVM2Item) item).functionName); + } else if (item instanceof ConstAVM2Item) { + traits[k].name_index = traitName(genNs(importedClasses, pkg, ((ConstAVM2Item) item).pkg, openedNamespaces, localData, ((ConstAVM2Item) item).line), ((ConstAVM2Item) item).var); + } else if (item instanceof SlotAVM2Item) { + traits[k].name_index = traitName(genNs(importedClasses, pkg, ((SlotAVM2Item) item).pkg, openedNamespaces, localData, ((SlotAVM2Item) item).line), ((SlotAVM2Item) item).var); + } + } + + for (int k = 0; k < items.size(); k++) { + GraphTargetItem item = items.get(k); + if (traits[k] == null) { + continue; + } + if (item instanceof ClassAVM2Item) { + + InstanceInfo instanceInfo = abcIndex.getSelectedAbc().instance_info.get(((TraitClass) traits[k]).class_info); + instanceInfo.name_index = abcIndex.getSelectedAbc().constants.getMultinameId( + Multiname.createQName( + false, + abcIndex.getSelectedAbc().constants.getStringId(((ClassAVM2Item) item).className, true), + ((ClassAVM2Item) item).pkg.getCpoolIndex(abcIndex)), true); + + if (((ClassAVM2Item) item).extendsOp != null) { + instanceInfo.super_index = typeName(localData, ((ClassAVM2Item) item).extendsOp); + } else { + instanceInfo.super_index = abcIndex.getSelectedAbc().constants.getMultinameId(Multiname.createQName(false, str("Object"), namespace(Namespace.KIND_PACKAGE, "")), true); + } + instanceInfo.interfaces = new int[((ClassAVM2Item) item).implementsOp.size()]; + for (int i = 0; i < ((ClassAVM2Item) item).implementsOp.size(); i++) { + instanceInfo.interfaces[i] = superIntName(localData, ((ClassAVM2Item) item).implementsOp.get(i)); + } + } + if (item instanceof InterfaceAVM2Item) { + ABC abc = abcIndex.getSelectedAbc(); + AVM2ConstantPool constants = abc.constants; + InstanceInfo instanceInfo = abc.instance_info.get(((TraitClass) traits[k]).class_info); + instanceInfo.name_index = constants.getMultinameId(Multiname.createQName(false, constants.getStringId(((InterfaceAVM2Item) item).name, true), + ((InterfaceAVM2Item) item).pkg.getCpoolIndex(abcIndex)), true); + + instanceInfo.interfaces = new int[((InterfaceAVM2Item) item).superInterfaces.size()]; + for (int i = 0; i < ((InterfaceAVM2Item) item).superInterfaces.size(); i++) { + GraphTargetItem un = ((InterfaceAVM2Item) item).superInterfaces.get(i); + instanceInfo.interfaces[i] = superIntName(localData, un); + } + } + } + } + + public int superIntName(SourceGeneratorLocalData localData, GraphTargetItem un) throws CompilationException { + if (un instanceof UnresolvedAVM2Item) { + ((UnresolvedAVM2Item) un).resolve(null, new ArrayList<>(), new ArrayList<>(), abcIndex, new ArrayList<>(), new ArrayList<>()); + un = ((UnresolvedAVM2Item) un).resolved; + } + if (!(un instanceof TypeItem)) { //not applyType + throw new CompilationException("Invalid type", 0); + } + TypeItem sup = (TypeItem) un; + int propId = resolveType(localData, sup, abcIndex); + int[] nss = new int[]{abcIndex.getSelectedAbc().constants.getMultiname(propId).namespace_index}; + return abcIndex.getSelectedAbc().constants.getMultinameId(Multiname.createMultiname(false, abcIndex.getSelectedAbc().constants.getMultiname(propId).name_index, abcIndex.getSelectedAbc().constants.getNamespaceSetId(nss, true)), true); + + } + + public int[] generateMetadata(List>> metadata) { + int[] ret = new int[metadata.size()]; + for (int i = 0; i < metadata.size(); i++) { + Map.Entry> en = metadata.get(i); + int[] keys = new int[en.getValue().size()]; + int[] values = new int[en.getValue().size()]; + int j = 0; + for (String key : en.getValue().keySet()) { + keys[j] = abcIndex.getSelectedAbc().constants.getStringId(key, true); + values[j] = abcIndex.getSelectedAbc().constants.getStringId(en.getValue().get(key), true); + j++; + } + MetadataInfo mi = new MetadataInfo(abcIndex.getSelectedAbc().constants.getStringId(en.getKey(), true), keys, values); + ret[i] = abcIndex.getSelectedAbc().metadata_info.size(); + abcIndex.getSelectedAbc().metadata_info.add(mi); + } + return ret; + } + + public void generateTraitsPhase3(List importedClasses, int methodInitScope, boolean isInterface, String className, String superName, boolean generateStatic, SourceGeneratorLocalData localData, List items, Traits ts, Trait[] traits, Map initScopes, Reference class_index) throws AVM2ParseException, CompilationException { + + //Note: Names must be generated first before accesed in inner subs + for (int k = 0; k < items.size(); k++) { + GraphTargetItem item = items.get(k); + if (traits[k] == null) { + continue; + } + if (item instanceof InterfaceAVM2Item) { + generateClass(((InterfaceAVM2Item) item).pkg.getCpoolIndex(abcIndex), abcIndex.getSelectedAbc().class_info.get(((TraitClass) traits[k]).class_info), abcIndex.getSelectedAbc().instance_info.get(((TraitClass) traits[k]).class_info), initScopes.get(traits[k]), ((InterfaceAVM2Item) item).pkg.name, localData, (InterfaceAVM2Item) item, class_index); + } + + if (item instanceof ClassAVM2Item) { + generateClass(((ClassAVM2Item) item).pkg.getCpoolIndex(abcIndex), abcIndex.getSelectedAbc().class_info.get(((TraitClass) traits[k]).class_info), abcIndex.getSelectedAbc().instance_info.get(((TraitClass) traits[k]).class_info), initScopes.get(traits[k]), ((ClassAVM2Item) item).pkg.name, localData, (ClassAVM2Item) item, class_index); + } + if ((item instanceof MethodAVM2Item) || (item instanceof GetterAVM2Item) || (item instanceof SetterAVM2Item)) { + MethodAVM2Item mai = (MethodAVM2Item) item; + if (mai.isStatic() != generateStatic) { + continue; + } + for (List ln : mai.allOpenedNamespaces) { + for (NamespaceItem n : ln) { + n.resolveCustomNs(abcIndex, importedClasses, localData.pkg, ln, localData); + } + } + String suffix = null; + if (item instanceof GetterAVM2Item) { + suffix = "get"; + } + if (item instanceof SetterAVM2Item) { + suffix = "set"; + } + + ((TraitMethodGetterSetter) traits[k]).method_info = method(mai.isStatic(), methodName(mai.outsidePackage, localData.pkg, mai.functionName, mai.pkg, className, mai.customNamespace, suffix), false, isInterface, new ArrayList<>(), localData.pkg, mai.needsActivation, mai.subvariables, methodInitScope + (mai.isStatic() ? 0 : 1), mai.hasRest, mai.line, className, superName, false, localData, mai.paramTypes, mai.paramNames, mai.paramValues, mai.body, mai.retType); + } else if (item instanceof FunctionAVM2Item) { + FunctionAVM2Item fai = (FunctionAVM2Item) item; + ((TraitFunction) traits[k]).method_info = method(false, methodName(false/*?*/, localData.pkg, fai.functionName, fai.pkg, null, null, ""), false, isInterface, new ArrayList<>(), localData.pkg, fai.needsActivation, fai.subvariables, methodInitScope, fai.hasRest, fai.line, className, superName, false, localData, fai.paramTypes, fai.paramNames, fai.paramValues, fai.body, fai.retType); + } + } + } + + private int methodName(boolean outsidePkg, DottedChain pkg, String methodName, NamespaceItem ns, String className, String customNs, String typeSuffix) { + StringBuilder sb = new StringBuilder(); + /*if (ns != null) { + sb.append(ns.name.toRawString()); + }*/ + if (className != null) { + if (pkg != null && !pkg.isEmpty() && !pkg.isTopLevel()) { + sb.append(pkg.toRawString()); + sb.append(":"); + } + sb.append(className); + } + if (customNs != null) { + sb.append(customNs); + } else if (ns != null) { + switch (ns.kind) { + case Namespace.KIND_PACKAGE_INTERNAL: + sb.append(pkg == null ? "" /*?*/ : pkg.toRawString()); + break; + case Namespace.KIND_PRIVATE: + + if (!outsidePkg) { + sb.append("/private"); + } + break; + case Namespace.KIND_PROTECTED: + case Namespace.KIND_STATIC_PROTECTED: + sb.append("/protected"); + break; + } + } + sb.append(":"); + sb.append(methodName); + if (typeSuffix != null && !typeSuffix.isEmpty()) { + sb.append("/"); + sb.append(typeSuffix); + } + return abcIndex.getSelectedAbc().constants.getStringId(sb.toString(), true); + } + + public Trait[] generateTraitsPhase1(List importedClasses, List openedNamespaces, String className, String superName, boolean generateStatic, SourceGeneratorLocalData localData, List items, Traits ts, Reference classIndex) throws AVM2ParseException, CompilationException { + Trait[] traits = new Trait[items.size()]; + int slot_id = 1; + int disp_id = 3; //1 and 2 are for constructor + for (int k = 0; k < items.size(); k++) { + GraphTargetItem item = items.get(k); + if (item instanceof InterfaceAVM2Item) { + TraitClass tc = new TraitClass(); + ClassInfo ci = new ClassInfo(); + InstanceInfo ii = new InstanceInfo(); + /*abc.class_info.add(ci); + abc.instance_info.add(ii);*/ + tc.class_info = classIndex.getVal(); + abcIndex.getSelectedAbc().addClass(ci, ii, classIndex.getVal()); + classIndex.setVal(classIndex.getVal() + 1); + ii.flags |= InstanceInfo.CLASS_INTERFACE; + //ii.name_index = traitName(((InterfaceAVM2Item) item).namespace, ((InterfaceAVM2Item) item).name); + //tc.class_info = abc.instance_info.size() - 1; + tc.kindType = Trait.TRAIT_CLASS; + //tc.name_index = traitName(((InterfaceAVM2Item) item).namespace, ((InterfaceAVM2Item) item).name); + tc.slot_id = 0; //? + ts.traits.add(tc); + traits[k] = tc; + traits[k].metadata = generateMetadata(((InterfaceAVM2Item) item).metadata); + } + + if (item instanceof ClassAVM2Item) { + TraitClass tc = new TraitClass(); + ClassInfo ci = new ClassInfo(); + InstanceInfo ii = new InstanceInfo(); + //ii.name_index = traitName(((ClassAVM2Item) item).namespace, ((ClassAVM2Item) item).className); + /*abc.class_info.add(ci); + abc.instance_info.add(instanceInfo);*/ + tc.class_info = classIndex.getVal(); + abcIndex.getSelectedAbc().addClass(ci, ii, classIndex.getVal()); + classIndex.setVal(classIndex.getVal() + 1); + tc.kindType = Trait.TRAIT_CLASS; + // tc.name_index = traitName(((ClassAVM2Item) item).namespace, ((ClassAVM2Item) item).className); + tc.slot_id = slot_id++; + ts.traits.add(tc); + traits[k] = tc; + traits[k].metadata = generateMetadata(((ClassAVM2Item) item).metadata); + + } + if ((item instanceof SlotAVM2Item) || (item instanceof ConstAVM2Item)) { + TraitSlotConst tsc = new TraitSlotConst(); + tsc.kindType = (item instanceof SlotAVM2Item) ? Trait.TRAIT_SLOT : Trait.TRAIT_CONST; + String var = null; + GraphTargetItem val = null; + GraphTargetItem type = null; + boolean isNamespace = false; + int namespace = 0; + boolean isStatic = false; + int[] metadata = new int[0]; + if (item instanceof SlotAVM2Item) { + SlotAVM2Item sai = (SlotAVM2Item) item; + if (sai.isStatic() != generateStatic) { + continue; + } + var = sai.var; + val = sai.value; + type = sai.type; + isStatic = sai.isStatic(); + if (sai.pkg != null) { + sai.pkg.resolveCustomNs(abcIndex, importedClasses, localData.pkg, openedNamespaces, localData); + } + namespace = sai.pkg == null ? 0 : sai.pkg.getCpoolIndex(abcIndex); + metadata = generateMetadata(((SlotAVM2Item) item).metadata); + } + if (item instanceof ConstAVM2Item) { + ConstAVM2Item cai = (ConstAVM2Item) item; + if (cai.isStatic() != generateStatic) { + continue; + } + var = cai.var; + val = cai.value; + type = cai.type; + if (cai.pkg != null) { + cai.pkg.resolveCustomNs(abcIndex, importedClasses, localData.pkg, openedNamespaces, localData); + } + namespace = cai.pkg == null ? 0 : cai.pkg.getCpoolIndex(abcIndex); + isNamespace = type.toString().equals("Namespace"); + isStatic = cai.isStatic(); + metadata = generateMetadata(((ConstAVM2Item) item).metadata); + } + if (isNamespace) { + tsc.name_index = traitName(namespace, var); + } + tsc.type_index = isNamespace ? 0 : (type == null ? 0 : typeName(localData, type)); + + ValueKind vk = getValueKind(namespace, type, val); + if (vk == null) { + tsc.value_kind = ValueKind.CONSTANT_Undefined; + } else { + tsc.value_kind = vk.value_kind; + tsc.value_index = vk.value_index; + } + tsc.slot_id = isStatic ? slot_id++ : 0; + ts.traits.add(tsc); + traits[k] = tsc; + traits[k].metadata = metadata; + } + if ((item instanceof MethodAVM2Item) || (item instanceof GetterAVM2Item) || (item instanceof SetterAVM2Item)) { + MethodAVM2Item mai = (MethodAVM2Item) item; + if (mai.isStatic() != generateStatic) { + continue; + } + TraitMethodGetterSetter tmgs = new TraitMethodGetterSetter(); + tmgs.kindType = (item instanceof GetterAVM2Item) ? Trait.TRAIT_GETTER : ((item instanceof SetterAVM2Item) ? Trait.TRAIT_SETTER : Trait.TRAIT_METHOD); + tmgs.disp_id = mai.isStatic() ? disp_id++ : 0; //For a reason, there is disp_id only for static methods (or not?) + if (mai.isFinal() || (className != null && mai.isStatic())) { + tmgs.kindFlags |= Trait.ATTR_Final; + } + if (mai.isOverride()) { + tmgs.kindFlags |= Trait.ATTR_Override; + } + ts.traits.add(tmgs); + + traits[k] = tmgs; + traits[k].metadata = generateMetadata(((MethodAVM2Item) item).metadata); + } + /*else if (item instanceof FunctionAVM2Item) { + TraitFunction tf = new TraitFunction(); + tf.slot_id = slot_id++; + tf.kindType = Trait.TRAIT_FUNCTION; + //tf.name_index = traitName(((FunctionAVM2Item) item).namespace, ((FunctionAVM2Item) item).functionName); + ts.traits.add(tf); + traits[k] = tf; + traits[k].metadata = generateMetadata(((FunctionAVM2Item) item).metadata); + }*/ + + } + + return traits; + } + + public ScriptInfo generateScriptInfo(List> allOpenedNamespaces, SourceGeneratorLocalData localData, List commands, int classPos) throws AVM2ParseException, CompilationException { + Reference class_index = new Reference<>(classPos); + ScriptInfo si = new ScriptInfo(); + localData.currentScript = si; + Trait[] traitArr = generateTraitsPhase1(new ArrayList<>(), new ArrayList<>(), null, null, true, localData, commands, si.traits, class_index); + generateTraitsPhase2(new ArrayList<>(), null/*FIXME*/, commands, traitArr, new ArrayList<>(), localData); + + abcIndex.refreshSelected(); + + ABC abc = abcIndex.getSelectedAbc(); + AVM2ConstantPool constants = abc.constants; + MethodInfo mi = new MethodInfo(new int[0], 0, constants.getStringId("", true), 0, new ValueKind[0], new int[0]); + MethodBody mb = new MethodBody(abc, new Traits(), new byte[0], new ABCException[0]); + mb.method_info = abc.addMethodInfo(mi); + mb.setCode(new AVM2Code()); + List mbCode = mb.getCode().code; + mbCode.add(ins(AVM2Instructions.GetLocal0)); + mbCode.add(ins(AVM2Instructions.PushScope)); + + int traitScope = 2; + + Map initScopes = new HashMap<>(); + + for (Trait t : si.traits.traits) { + if (t instanceof TraitClass) { + TraitClass tc = (TraitClass) t; + List parents = new ArrayList<>(); + if (localData.documentClass) { + mbCode.add(ins(AVM2Instructions.GetScopeObject, 0)); + traitScope++; + } else { + int[] nsset = new int[]{constants.getMultiname(tc.name_index).namespace_index}; + mbCode.add(ins(AVM2Instructions.FindPropertyStrict, constants.getMultinameId(Multiname.createMultiname(false, constants.getMultiname(tc.name_index).name_index, constants.getNamespaceSetId(nsset, true)), true))); + } + if (abc.instance_info.get(tc.class_info).isInterface()) { + mbCode.add(ins(AVM2Instructions.PushNull)); + } else { + + AbcIndexing.ClassIndex ci = abcIndex.findClass(AbcIndexing.multinameToType(abc.instance_info.get(tc.class_info).name_index, constants)); + while (ci != null && ci.parent != null) { + ci = ci.parent; + Multiname origM = ci.abc.constants.getMultiname(ci.abc.instance_info.get(ci.index).name_index); + Namespace origNs = ci.abc.constants.getNamespace(origM.namespace_index); + if (origM.kind == Multiname.QNAME || origM.kind == Multiname.QNAMEA) { + parents.add(constants.getMultinameId( + Multiname.createQName(origM.kind == Multiname.QNAMEA, + constants.getStringId(ci.abc.constants.getString(origM.name_index), true), + constants.getNamespaceId(origNs.kind, + ci.abc.constants.getString(origNs.name_index), 0, true)), true)); + } + } + + //add all parent objects to scopestack + for (int i = parents.size() - 1; i >= 0; i--) { + mbCode.add(ins(AVM2Instructions.GetLex, parents.get(i))); + mbCode.add(ins(AVM2Instructions.PushScope)); + traitScope++; + } + //direct parent class to new_class instruction + if (!parents.isEmpty()) { //NON EXISTING PARENT CLASS - TODO: handle as error! + mbCode.add(ins(AVM2Instructions.GetLex, parents.get(0))); + } + } + mbCode.add(ins(AVM2Instructions.NewClass, tc.class_info)); + for (int i = 0; i < parents.size(); i++) { + mbCode.add(ins(AVM2Instructions.PopScope)); + } + + mbCode.add(ins(AVM2Instructions.InitProperty, tc.name_index)); + initScopes.put(t, traitScope); + traitScope = 1; + } + } + + abc.addMethodBody(mb); + si.init_index = mb.method_info; + localData.pkg = DottedChain.EMPTY; + generateTraitsPhase3(new ArrayList<>(), 1/*??*/, false, null, null, true, localData, commands, si.traits, traitArr, initScopes, class_index); + + int maxSlotId = 0; + for (int k = 0; k < si.traits.traits.size(); k++) { + if (si.traits.traits.get(k) instanceof TraitSlotConst) { + TraitSlotConst ti = (TraitSlotConst) si.traits.traits.get(k); + if (ti.slot_id > maxSlotId) { + maxSlotId = ti.slot_id; + } + } + } + for (int k = 0; k < si.traits.traits.size(); k++) { + if ((si.traits.traits.get(k) instanceof TraitMethodGetterSetter) && (commands.get(k) instanceof MethodAVM2Item)) { + MethodAVM2Item mai = (MethodAVM2Item) commands.get(k); + if (mai.outsidePackage) { + TraitMethodGetterSetter tmgs = (TraitMethodGetterSetter) si.traits.traits.get(k); + TraitSlotConst nts = new TraitSlotConst(); + nts.name_index = si.traits.traits.get(k).name_index; + nts.metadata = si.traits.traits.get(k).metadata; + + nts.slot_id = maxSlotId + 1; + maxSlotId++; + nts.type_index = abcIndex.getSelectedAbc().constants.getQnameId("Function", Namespace.KIND_PACKAGE, "", true); + nts.value_index = 0; + nts.value_kind = 0; + int methodinfo = tmgs.method_info; + si.traits.traits.set(k, nts); + mbCode.add(ins(AVM2Instructions.NewFunction, methodinfo)); + mbCode.add(ins(AVM2Instructions.InitProperty, nts.name_index)); + } + } + } + + mbCode.add(ins(AVM2Instructions.ReturnVoid)); + mb.autoFillStats(abc, 1, false); + + return si; + } + + public static void parentNamesAddNames(AbcIndexing abc, int name_index, List indices, List names, List namespaces) { + List cindices = new ArrayList<>(); + + List outABCs = new ArrayList<>(); + parentNames(abc, name_index, cindices, names, namespaces, outABCs); + for (int i = 0; i < cindices.size(); i++) { + ABC a = outABCs.get(i); + int m = cindices.get(i); + if (a == abc.getSelectedAbc()) { + indices.add(m); + continue; + } + Multiname superName = a.constants.getMultiname(m); + indices.add( + abc.getSelectedAbc().constants.getMultinameId( + Multiname.createQName(false, + abc.getSelectedAbc().constants.getStringId(superName.getName(a.constants, null, true), true), + abc.getSelectedAbc().constants.getNamespaceId(superName.getNamespace(a.constants).kind, superName.getNamespace(a.constants).getName(a.constants), 0, true)), true) + ); + } + } + + public static GraphTargetItem getTraitReturnType(AbcIndexing abc, Trait t) { + if (t instanceof TraitSlotConst) { + TraitSlotConst tsc = (TraitSlotConst) t; + if (tsc.type_index == 0) { + return TypeItem.UNBOUNDED; + } + return PropertyAVM2Item.multinameToType(tsc.type_index, abc.getSelectedAbc().constants); + } + if (t instanceof TraitMethodGetterSetter) { + TraitMethodGetterSetter tmgs = (TraitMethodGetterSetter) t; + if (tmgs.kindType == Trait.TRAIT_GETTER) { + return PropertyAVM2Item.multinameToType(abc.getSelectedAbc().method_info.get(tmgs.method_info).ret_type, abc.getSelectedAbc().constants); + } + if (tmgs.kindType == Trait.TRAIT_SETTER) { + if (abc.getSelectedAbc().method_info.get(tmgs.method_info).param_types.length > 0) { + return PropertyAVM2Item.multinameToType(abc.getSelectedAbc().method_info.get(tmgs.method_info).param_types[0], abc.getSelectedAbc().constants); + } else { + return TypeItem.UNBOUNDED; + } + } + } + if (t instanceof TraitFunction) { + return new TypeItem(DottedChain.FUNCTION); + } + return TypeItem.UNBOUNDED; + } + + public static boolean searchPrototypeChain(List otherNs, int privateNs, int protectedNs, boolean instanceOnly, AbcIndexing abc, DottedChain pkg, String obj, String propertyName, Reference outName, Reference outNs, Reference outPropNs, Reference outPropNsKind, Reference outPropNsIndex, Reference outPropType, Reference outPropValue, Reference outPropValueAbc) { + for (int ns : otherNs) { + if (searchPrototypeChain(ns, instanceOnly, abc, pkg, obj, propertyName, outName, outNs, outPropNs, outPropNsKind, outPropNsIndex, outPropType, outPropValue, outPropValueAbc)) { + return true; + } + } + + if (searchPrototypeChain(privateNs, instanceOnly, abc, pkg, obj, propertyName, outName, outNs, outPropNs, outPropNsKind, outPropNsIndex, outPropType, outPropValue, outPropValueAbc)) { + return true; + } + if (searchPrototypeChain(protectedNs, instanceOnly, abc, pkg, obj, propertyName, outName, outNs, outPropNs, outPropNsKind, outPropNsIndex, outPropType, outPropValue, outPropValueAbc)) { + return true; + } + return searchPrototypeChain(0, instanceOnly, abc, pkg, obj, propertyName, outName, outNs, outPropNs, outPropNsKind, outPropNsIndex, outPropType, outPropValue, outPropValueAbc); + } + + private static boolean searchPrototypeChain(int selectedNs, boolean instanceOnly, AbcIndexing abc, DottedChain pkg, String obj, String propertyName, Reference outName, Reference outNs, Reference outPropNs, Reference outPropNsKind, Reference outPropNsIndex, Reference outPropType, Reference outPropValue, Reference outPropValueAbc) { + + AbcIndexing.TraitIndex sp = abc.findScriptProperty(pkg.add(propertyName)); + if (sp == null) { + sp = abc.findProperty(new AbcIndexing.PropertyDef(propertyName, new TypeItem(pkg.add(obj)), abc.getSelectedAbc(), selectedNs), !instanceOnly, true); + } + if (sp != null) { + if (sp.objType instanceof TypeItem) { + outName.setVal(((TypeItem) sp.objType).fullTypeName.getLast()); + outNs.setVal(((TypeItem) sp.objType).fullTypeName.getWithoutLast()); + } else { + //FIXME? Vector? + } + outPropNs.setVal(sp.trait.getName(sp.abc).getNamespace(sp.abc.constants).getName(sp.abc.constants)); + outPropNsKind.setVal(sp.trait.getName(sp.abc).getNamespace(sp.abc.constants).kind); + int nsi = sp.trait.getName(sp.abc).namespace_index; + outPropNsIndex.setVal(sp.abc == abc.getSelectedAbc() ? sp.abc.constants.getNamespaceSubIndex(nsi) : 0); + outPropType.setVal(sp.returnType); + outPropValue.setVal(sp.value); + outPropValueAbc.setVal(sp.abc); + return true; + } + return false; + } + + public static void parentNames(AbcIndexing abc, int name_index, List indices, List names, List namespaces, List outABCs) { + AbcIndexing.ClassIndex ci = abc.findClass(new TypeItem(abc.getSelectedAbc().constants.getMultiname(name_index).getNameWithNamespace(abc.getSelectedAbc().constants))); + while (ci != null) { + int ni = ci.abc.instance_info.get(ci.index).name_index; + indices.add(ni); + outABCs.add(ci.abc); + names.add(ci.abc.constants.getMultiname(ni).getName(ci.abc.constants, null, true)); + namespaces.add(ci.abc.constants.getMultiname(ni).getNamespace(ci.abc.constants).getName(ci.abc.constants).toRawString()); + ci = ci.parent; + } + } + + /* public void calcRegisters(Reference activationReg, SourceGeneratorLocalData localData, boolean needsActivation, List funParamNames,List funSubVariables,List funBody, Reference hasArguments) throws ParseException { + + }*/ + /*public int resolveType(String objType) { + if (objType.equals("*")) { + return 0; + } + List abcs = new ArrayList<>(); + abcs.add(abc); + abcs.addAll(allABCs); + for (ABC a : abcs) { + int ci = a.findClassByName(objType); + if (ci != -1) { + Multiname tname = a.instance_info.get(ci).getName(a.constants); + return abc.getLastAbc().constants.getMultinameId(new Multiname(tname.kind, + abc.getLastAbc().constants.getStringId(tname.getName(a.constants, new ArrayList<>()), true), + abc.getLastAbc().constants.getNamespaceId(new Namespace(tname.getNamespace(a.constants).kind, abc.getLastAbc().constants.getStringId(tname.getNamespace(a.constants).getName(a.constants), true)), 0, true), 0, 0, new ArrayList()), true); + } + } + return 0; + }*/ + @Override + public List generate(SourceGeneratorLocalData localData, TypeItem item) throws CompilationException { + String currentFullClassName = localData.getFullClass(); + + if (localData.documentClass && item.toString().equals(currentFullClassName)) { + int slotId = 0; + int c = abcIndex.getSelectedAbc().findClassByName(currentFullClassName); + for (Trait t : localData.currentScript.traits.traits) { + if (t instanceof TraitClass) { + TraitClass tc = (TraitClass) t; + if (tc.class_info == c) { + slotId = tc.slot_id; + break; + } + } + } + return GraphTargetItem.toSourceMerge(localData, this, ins(AVM2Instructions.GetGlobalScope), ins(AVM2Instructions.GetSlot, slotId)); + } else { + return GraphTargetItem.toSourceMerge(localData, this, ins(AVM2Instructions.GetLex, resolveType(localData, item, abcIndex))); + } + } + + public static int resolveType(SourceGeneratorLocalData localData, GraphTargetItem item, AbcIndexing abcIndex) throws CompilationException { + int name_index = 0; + GraphTargetItem typeItem = null; + + if (item instanceof UnresolvedAVM2Item) { + String fullClass = localData.getFullClass(); + item = ((UnresolvedAVM2Item) item).resolve(new TypeItem(fullClass), new ArrayList<>(), new ArrayList<>(), abcIndex, new ArrayList<>(), new ArrayList<>()); + } + if (item instanceof TypeItem) { + typeItem = item; + } else if (item instanceof ApplyTypeAVM2Item) { + typeItem = ((ApplyTypeAVM2Item) item).object; + } else { + throw new CompilationException("Invalid type:" + item + " (" + item.getClass().getName() + ")", 0/*??*/); + } + if (typeItem instanceof UnresolvedAVM2Item) { + String fullClass = localData.getFullClass(); + typeItem = ((UnresolvedAVM2Item) typeItem).resolve(new TypeItem(fullClass), new ArrayList<>(), new ArrayList<>(), abcIndex, new ArrayList<>(), new ArrayList<>()); + } + + if (!(typeItem instanceof TypeItem)) { + throw new CompilationException("Invalid type", 0/*??*/); + } + + TypeItem type = (TypeItem) typeItem; + + DottedChain dname = type.fullTypeName; + DottedChain pkg = dname.getWithoutLast(); + String name = dname.getLast(); + /*for (InstanceInfo ii : abc.getSelectedAbc().instance_info) { + Multiname mname = abc.getSelectedAbc().constants.constant_multiname.get(ii.name_index); + if (mname != null && name.equals(mname.getName(abc.getSelectedAbc().constants, null, true))) { + Namespace ns = mname.getNamespace(abc.getSelectedAbc().constants); + if (ns != null && ns.hasName(pkg, abc.getSelectedAbc().constants)) { + name_index = ii.name_index; + break; + } + } + }*/ + ABC abc = abcIndex.getSelectedAbc(); + AVM2ConstantPool constants = abc.constants; + AbcIndexing.ClassIndex ci = abcIndex.findClass(new TypeItem(dname)); + if (ci != null) { + Multiname m = ci.abc.instance_info.get(ci.index).getName(ci.abc.constants); + if (m != null) { + Namespace ns = ci.abc.instance_info.get(ci.index).getName(ci.abc.constants).getNamespace(ci.abc.constants); + String n = m.getName(ci.abc.constants, new ArrayList<>(), true); + String nsn = ns == null ? null : ns.getName(ci.abc.constants).toRawString(); + name_index = constants.getQnameId( + n, + ns == null ? Namespace.KIND_PACKAGE : ns.kind, + nsn, true); + } + } + + for (int i = 1; i < constants.getMultinameCount(); i++) { + Multiname mname = constants.getMultiname(i); + if (mname != null && name.equals(mname.getName(constants, null, true))) { + if (mname.getNamespace(constants) != null && pkg.equals(mname.getNamespace(constants).getName(constants))) { + name_index = i; + break; + } + } + } + if (name_index == 0) { + if (pkg.isEmpty() && localData.currentScript != null /*FIXME!*/) { + for (Trait t : localData.currentScript.traits.traits) { + if (t.getName(abc).getName(constants, null, true).equals(name)) { + name_index = t.name_index; + break; + } + } + } + if (name_index == 0) { + name_index = constants.getMultinameId(Multiname.createQName(false, constants.getStringId(name, true), constants.getNamespaceId(Namespace.KIND_PACKAGE, pkg, 0, true)), true); + } + } + + if (item instanceof ApplyTypeAVM2Item) { + ApplyTypeAVM2Item atype = (ApplyTypeAVM2Item) item; + int[] params = new int[atype.params.size()]; + int i = 0; + for (GraphTargetItem s : atype.params) { + params[i++] = resolveType(localData, s, abcIndex); + } + return constants.getMultinameId(Multiname.createTypeName(name_index, params), true); + } + + return name_index; + } + + @Override + public List generateDiscardValue(SourceGeneratorLocalData localData, GraphTargetItem item) throws CompilationException { + List ret = item.toSource(localData, this); + ret.add(ins(AVM2Instructions.Pop)); + return ret; + } + + @Override + public List generate(SourceGeneratorLocalData localData, PushItem item) throws CompilationException { + return item.value.toSource(localData, this); + } + + @Override + public List generate(SourceGeneratorLocalData localData, PopItem item) throws CompilationException { + List ret = new ArrayList<>(); + return ret; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/Float4.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/Float4.java index 810fe3a1c..9b825bb63 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/Float4.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/Float4.java @@ -1,21 +1,21 @@ -package com.jpexs.decompiler.flash.abc.types; - -public class Float4 { - - public float values[] = new float[4]; - - public Float4(float value1, float value2, float value3, float value4) { - this.values = new float[]{value1, value2, value3, value4}; - } - - public Float4(float[] values) { - if (values == null || values.length < 4) { - throw new IllegalArgumentException("Invalid values size"); - } - this.values[0] = values[0]; - this.values[1] = values[1]; - this.values[2] = values[2]; - this.values[3] = values[3]; - } - -} +package com.jpexs.decompiler.flash.abc.types; + +public class Float4 { + + public float[] values = new float[4]; + + public Float4(float value1, float value2, float value3, float value4) { + this.values = new float[]{value1, value2, value3, value4}; + } + + public Float4(float[] values) { + if (values == null || values.length < 4) { + throw new IllegalArgumentException("Invalid values size"); + } + this.values[0] = values[0]; + this.values[1] = values[1]; + this.values[2] = values[2]; + this.values[3] = values[3]; + } + +} 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 index aa7f43504..75b12ae80 100644 --- 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 @@ -245,7 +245,7 @@ public class Amf3InputStream extends InputStream { } newDumpLevel(name, "UTF8-char"); - byte buf[] = new byte[(int) byteLength]; //how about long strings(?), will the int length be enough? + 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(); @@ -487,7 +487,7 @@ public class Amf3InputStream extends InputStream { newDumpLevel("serializedData", "U8[]"); MonitoredInputStream mis = new MonitoredInputStream(is); Map serMembers = serializers.get(className).readObject(className, mis); - byte serData[] = mis.getReadData(); + byte[] serData = mis.getReadData(); endDumpLevel(); Traits unserTraits = new Traits(className, false, new ArrayList<>()); retObjectType = new ObjectType(unserTraits, serData, serMembers); @@ -624,7 +624,7 @@ public class Amf3InputStream extends InputStream { renameU29("U29B-value", NO_REFERENCE_BIT_TEXT, "byte array length"); int byteArrayLength = (int) (byteArrayU29 >> 1); newDumpLevel("bytes", "U8[]"); - byte byteArrayBuf[] = new byte[byteArrayLength]; + byte[] byteArrayBuf = new byte[byteArrayLength]; if (is.read(byteArrayBuf) != byteArrayLength) { throw new EndOfStreamException(); } 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 index e9838c76d..d2f3ffc95 100644 --- 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 @@ -120,7 +120,7 @@ public class Amf3OutputStream extends OutputStream { if (!val.isEmpty()) { stringTable.add(val); } - byte data[] = val.getBytes("UTF-8"); + byte[] data = val.getBytes("UTF-8"); writeU29((data.length << 1) | NO_REFERENCE_FLAG); writeBytes(data); } else { @@ -132,7 +132,7 @@ public class Amf3OutputStream extends OutputStream { int objectIndex = objectTable.indexOf(val); if (objectIndex == -1) { objectTable.add(val); - byte data[] = val.getData(); + byte[] data = val.getData(); writeU29((data.length << 1) | NO_REFERENCE_FLAG); writeBytes(data); } else { @@ -144,7 +144,7 @@ public class Amf3OutputStream extends OutputStream { int objectIndex = objectTable.indexOf(val); if (objectIndex == -1) { objectTable.add(val); - byte data[] = val.getData().getBytes("UTF-8"); + byte[] data = val.getData().getBytes("UTF-8"); writeU29((data.length << 1) | NO_REFERENCE_FLAG); writeBytes(data); } else { @@ -156,7 +156,7 @@ public class Amf3OutputStream extends OutputStream { int objectIndex = objectTable.indexOf(val); if (objectIndex == -1) { objectTable.add(val); - byte data[] = val.getData().getBytes("UTF-8"); + byte[] data = val.getData().getBytes("UTF-8"); writeU29((data.length << 1) | NO_REFERENCE_FLAG); writeBytes(data); } else { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/docs/As3PCodeDocs.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/docs/As3PCodeDocs.java index 77a45b572..eafdcf770 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/docs/As3PCodeDocs.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/docs/As3PCodeDocs.java @@ -190,8 +190,8 @@ public class As3PCodeDocs { String operandTypeRaw = AVM2Code.operandTypeToString(op, false); String operandTypeCombined = AVM2Code.operandTypeToString(op, true); if (operandTypeCombined.contains(", ")) { - String operandTypesCombined[] = operandTypeCombined.split(", ?"); - String operandTypesRaw[] = operandTypeRaw.split(", ?"); + String[] operandTypesCombined = operandTypeCombined.split(", ?"); + String[] operandTypesRaw = operandTypeRaw.split(", ?"); for (int j = 0; j < operandTypesCombined.length; j++) { if (!first) { @@ -225,8 +225,8 @@ public class As3PCodeDocs { } } } - sb.append("").append(NEWLINE); } + sb.append("").append(NEWLINE); sb.append("
").append(insShortDescription).append("
").append(NEWLINE); @@ -236,7 +236,7 @@ public class As3PCodeDocs { sb.append("
").append(getProperty("ui.stack")).append("").append(stack).append("").append("
").append(NEWLINE); boolean flagsPrinted = false; - AVM2InstructionFlag flags[] = def.flags.clone(); + AVM2InstructionFlag[] flags = def.flags.clone(); Arrays.sort(flags, Enum::compareTo); for (AVM2InstructionFlag fl : flags) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/dumpview/DumpInfo.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/dumpview/DumpInfo.java index 31c6728b6..9339acc6a 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/dumpview/DumpInfo.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/dumpview/DumpInfo.java @@ -94,7 +94,6 @@ public class DumpInfo implements TreeItem { } Collections.sort(childInfos, new Comparator() { - @Override public int compare(DumpInfo o1, DumpInfo o2) { int res = Long.compare(o1.startByte, o2.startByte); @@ -149,7 +148,7 @@ public class DumpInfo implements TreeItem { @Override public SWF getSwf() { - Tag tag = getTag(); + Tag tag = tagToResolve != null ? tagToResolve : resolvedTag; if (tag != null) { return tag.getSwf(); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/dumpview/DumpInfoSpecial.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/dumpview/DumpInfoSpecial.java new file mode 100644 index 000000000..c81568811 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/dumpview/DumpInfoSpecial.java @@ -0,0 +1,39 @@ +/* + * 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.dumpview; + +/** + * + * @author JPEXS + */ +public class DumpInfoSpecial extends DumpInfo { + + public DumpInfoSpecialType specialType; + + public Object specialValue; + + public DumpInfoSpecial(String name, String type, Object value, long startByte, int startBit, long lengthBytes, int lengthBits, DumpInfoSpecialType specialType) { + super(name, type, value, startByte, startBit, lengthBytes, lengthBits); + this.specialType = specialType; + } + + public DumpInfoSpecial(String name, String type, Object value, long startByte, int startBit, long lengthBytes, int lengthBits, DumpInfoSpecialType specialType, Object specialValue) { + super(name, type, value, startByte, startBit, lengthBytes, lengthBits); + this.specialType = specialType; + this.specialValue = specialValue; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/dumpview/DumpInfoSpecialType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/dumpview/DumpInfoSpecialType.java new file mode 100644 index 000000000..d81467147 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/dumpview/DumpInfoSpecialType.java @@ -0,0 +1,28 @@ +/* + * 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.dumpview; + +/** + * + * @author JPEXS + */ +public enum DumpInfoSpecialType { + NONE, ZLIB_DATA, + TAG, + ACTION_BYTES, + ABC_BYTES, ABC_METHOD_BODY, ABC_CODE +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FontExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FontExporter.java index 01d0e16a7..c5eb9a8e9 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FontExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FontExporter.java @@ -178,6 +178,13 @@ public class FontExporter { int glyphCount = 0; for (int i = 0; i < shapes.size(); i++) { + + //if there are more glyphs for one char (in some weird fonts), use the last glyph + char c = t.glyphToChar(i); + while (i + 1 < shapes.size() && t.glyphToChar(i + 1) == c) { + i++; + } + SHAPE s = shapes.get(i); final List contours = new ArrayList<>(); PathExporter seb = new PathExporter(swf, s, null) { @@ -222,7 +229,6 @@ public class FontExporter { } }; seb.export(); - char c = t.glyphToChar(i); FGlyph g = f.addGlyph(c); glyphCount++; 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 index 8395e9c3d..8d9370d54 100644 --- 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 @@ -270,7 +270,7 @@ public class Amf3Exporter { ret.append(indent(level)).append("}"); } else if (object instanceof ByteArrayType) { ByteArrayType ba = (ByteArrayType) object; - byte data[] = ba.getData(); + byte[] data = ba.getData(); return "{" + newLine + indent(level + 1) + "\"type\": \"ByteArray\"," + newLine + addId diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/helpers/ImageHelper.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/helpers/ImageHelper.java index 49018fd4f..9635b48f2 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/helpers/ImageHelper.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/helpers/ImageHelper.java @@ -54,7 +54,7 @@ public class ImageHelper { public static BufferedImage read(InputStream input) throws IOException { BufferedImage in; - byte data[] = Helper.readStream(input); + byte[] data = Helper.readStream(input); try (ImageInputStream iis = ImageIO.createImageInputStream(new ByteArrayInputStream(data))) { CMYKJPEGImageReader r = new CMYKJPEGImageReader(new CMYKJPEGImageReaderSpi()); r.setInput(iis); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgImporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgImporter.java index 44a33590b..2a642a62f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgImporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgImporter.java @@ -1,1666 +1,1666 @@ -/* - * 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.svg; - -import com.jpexs.decompiler.flash.ReadOnlyTagList; -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.exporters.ShapeExporter; -import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; -import com.jpexs.decompiler.flash.exporters.commonshape.Point; -import com.jpexs.decompiler.flash.exporters.modes.ShapeExportMode; -import com.jpexs.decompiler.flash.exporters.settings.ShapeExportSettings; -import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter; -import com.jpexs.decompiler.flash.importers.ShapeImporter; -import com.jpexs.decompiler.flash.tags.DefineShape4Tag; -import com.jpexs.decompiler.flash.tags.ExportAssetsTag; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.tags.base.ShapeTag; -import com.jpexs.decompiler.flash.types.FILLSTYLE; -import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY; -import com.jpexs.decompiler.flash.types.FOCALGRADIENT; -import com.jpexs.decompiler.flash.types.GRADIENT; -import com.jpexs.decompiler.flash.types.GRADRECORD; -import com.jpexs.decompiler.flash.types.LINESTYLE; -import com.jpexs.decompiler.flash.types.LINESTYLE2; -import com.jpexs.decompiler.flash.types.LINESTYLEARRAY; -import com.jpexs.decompiler.flash.types.RECT; -import com.jpexs.decompiler.flash.types.RGB; -import com.jpexs.decompiler.flash.types.RGBA; -import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE; -import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord; -import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord; -import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; -import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord; -import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord; -import com.jpexs.helpers.Helper; -import com.jpexs.helpers.SerializableImage; -import java.awt.Color; -import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.io.StringReader; -import java.net.URL; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.imageio.ImageIO; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - -/** - * - * @author JPEXS - */ -public class SvgImporter { - - private final Set shownWarnings = new HashSet<>(); - - ShapeTag shapeTag; - - private Rectangle2D.Double viewBox; - - public Tag importSvg(ShapeTag st, String svgXml) { - return importSvg(st, svgXml, true); - } - - public Tag importSvg(ShapeTag st, String svgXml, boolean fill) { - shapeTag = st; - - SHAPEWITHSTYLE shapes = new SHAPEWITHSTYLE(); - shapes.fillStyles = new FILLSTYLEARRAY(); - shapes.lineStyles = new LINESTYLEARRAY(); - shapes.fillStyles.fillStyles = new FILLSTYLE[0]; - shapes.lineStyles.lineStyles = new LINESTYLE[0]; - - int shapeNum = st.getShapeNum(); - shapes.shapeRecords = new ArrayList<>(); - - Rectangle2D.Double viewBox = null; - try { - DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); - /*docFactory.setValidating(false); - docFactory.setNamespaceAware(true); - docFactory.setFeature("http://xml.org/sax/features/namespaces", false); - docFactory.setFeature("http://xml.org/sax/features/validation", false); - docFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);*/ - docFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); - - Document doc = docBuilder.parse(new InputSource(new StringReader(svgXml))); - Element rootElement = doc.getDocumentElement(); - - Map idMap = new HashMap<>(); - populateIds(rootElement, idMap); - - if (!"svg".equals(rootElement.getTagName())) { - throw new IOException("SVG root element should be 'svg'"); - } - - double width = 800; - double height = 600; - - if (rootElement.hasAttribute("viewBox")) { - String params = rootElement.getAttribute("viewBox"); - String[] args = Matrix.parseSvgNumberList(params); - viewBox = new Rectangle2D.Double(); - if (args.length > 0) { - viewBox.x = parseNumber(args[0]); - } - if (args.length > 1) { - viewBox.y = parseNumber(args[1]); - } - if (args.length > 2) { - viewBox.width = parseNumber(args[2]); - } - if (args.length > 3) { - viewBox.height = parseNumber(args[3]); - } - - width = viewBox.width; - height = viewBox.height; - } - - if (rootElement.hasAttribute("width")) { - width = parseLength(rootElement.getAttribute("width"), width); - } - - if (rootElement.hasAttribute("height")) { - height = parseLength(rootElement.getAttribute("height"), height); - } - - if (viewBox == null) { - viewBox = new Rectangle2D.Double(); - viewBox.width = width; - viewBox.height = height; - } - - this.viewBox = viewBox; - - SvgStyle style = new SvgStyle(this, idMap, rootElement); - Matrix transform = new Matrix(); - processSvgObject(idMap, shapeNum, shapes, rootElement, transform, style); - } catch (SAXException | IOException | ParserConfigurationException ex) { - Logger.getLogger(ShapeImporter.class.getName()).log(Level.SEVERE, null, ex); - } - - shapes.shapeRecords.add(new EndShapeRecord()); - - RECT rect = st.getRect(); - int origXmin = rect.Xmin; - int origYmin = rect.Ymin; - rect.Xmin -= origXmin; - rect.Xmax -= origXmin; - rect.Ymin -= origYmin; - rect.Ymax -= origYmin; - - if (!fill && viewBox != null) { - rect.Xmin = (int) Math.round(viewBox.x * SWF.unitDivisor); - rect.Ymin = (int) Math.round(viewBox.y * SWF.unitDivisor); - rect.Xmax = (int) Math.round((viewBox.x + viewBox.width) * SWF.unitDivisor); - rect.Ymax = (int) Math.round((viewBox.y + viewBox.height) * SWF.unitDivisor); - } - - st.shapes = shapes; - st.setModified(true); - - return (Tag) st; - } - - // Generate id-element map, because getElementById does not work in some cases (namespaces?) - protected void populateIds(Element el, Map out) { - if (el.hasAttribute("id")) { - out.put(el.getAttribute("id"), el); - } - NodeList nodes = el.getChildNodes(); - for (int i = 0; i < nodes.getLength(); i++) { - if (nodes.item(i) instanceof Element) { - populateIds((Element) nodes.item(i), out); - } - } - } - - private void processSvgObject(Map idMap, int shapeNum, SHAPEWITHSTYLE shapes, Element element, Matrix transform, SvgStyle style) { - for (int i = 0; i < element.getChildNodes().getLength(); i++) { - Node childNode = element.getChildNodes().item(i); - if (childNode instanceof Element) { - Element childElement = (Element) childNode; - String tagName = childElement.getTagName(); - SvgStyle newStyle = new SvgStyle(this, idMap, childElement); - Matrix m = Matrix.parseSvgMatrix(childElement.getAttribute("transform"), 1, 1); - Matrix m2 = m == null ? transform : transform.concatenate(m); - if ("g".equals(tagName)) { - processSvgObject(idMap, shapeNum, shapes, childElement, m2, newStyle); - } else if ("path".equals(tagName)) { - processPath(shapeNum, shapes, childElement, m2, newStyle); - } else if ("circle".equals(tagName)) { - processCircle(shapeNum, shapes, childElement, m2, newStyle); - } else if ("ellipse".equals(tagName)) { - processEllipse(shapeNum, shapes, childElement, m2, newStyle); - } else if ("rect".equals(tagName)) { - processRect(shapeNum, shapes, childElement, m2, newStyle); - } else if ("line".equals(tagName)) { - processLine(shapeNum, shapes, childElement, m2, newStyle); - } else if ("polyline".equals(tagName)) { - processPolyline(shapeNum, shapes, childElement, m2, newStyle); - } else if ("polygon".equals(tagName)) { - processPolygon(shapeNum, shapes, childElement, m2, newStyle); - } else if ("defs".equals(tagName) || "title".equals(tagName) || "desc".equals(tagName) - || "radialGradient".equals(tagName) || "linearGradient".equals(tagName)) { - // ignore - } else { - showWarning(tagName + "tagNotSupported", "The SVG tag '" + tagName + "' is not supported."); - } - } - } - } - - private void processCommands(int shapeNum, SHAPEWITHSTYLE shapes, List commands, Matrix transform, SvgStyle style) { - Matrix transform2 = transform.preConcatenate(Matrix.getScaleInstance(SWF.unitDivisor)); - Point prevPoint = new Point(0, 0); - Point startPoint = prevPoint; - double x0 = 0; - double y0 = 0; - - StyleChangeRecord scrStyle = getStyleChangeRecord(shapeNum, style); - int fillStyle = scrStyle.fillStyle1; - int lineStyle = scrStyle.lineStyle; - scrStyle.stateFillStyle0 = true; - scrStyle.stateFillStyle1 = true; - scrStyle.stateLineStyle = true; - scrStyle.fillStyle0 = 0; - scrStyle.fillStyle1 = 0; - scrStyle.lineStyle = 0; - - List newRecords = new ArrayList<>(); - - newRecords.add(scrStyle); - - LINESTYLE lineStyleObj = scrStyle.lineStyles.lineStyles.length < 1 ? null : scrStyle.lineStyles.lineStyles[0]; - LINESTYLE2 lineStyle2Obj = null; - if (lineStyleObj instanceof LINESTYLE2) { - lineStyle2Obj = (LINESTYLE2) lineStyleObj; - lineStyle2Obj.noClose = true; - } - - for (PathCommand command : commands) { - double x = x0; - double y = y0; - Point p; - char cmd = Character.toUpperCase(command.command); - switch (cmd) { - case 'M': - StyleChangeRecord scr = new StyleChangeRecord(); - if (fillStyle != 0) { - scr.stateFillStyle1 = true; - scr.fillStyle1 = fillStyle; - } - if (lineStyle != 0) { - scr.lineStyle = lineStyle; - scr.stateLineStyle = true; - } - - x = command.params[0]; - y = command.params[1]; - - p = transform2.transform(x, y); - scr.moveDeltaX = (int) Math.round(p.x); - scr.moveDeltaY = (int) Math.round(p.y); - prevPoint = p; - scr.stateMoveTo = true; - - newRecords.add(scr); - startPoint = p; - break; - case 'Z': - StraightEdgeRecord serz = new StraightEdgeRecord(); - p = startPoint; - serz.deltaX = (int) Math.round(p.x - prevPoint.x); - serz.deltaY = (int) Math.round(p.y - prevPoint.y); - prevPoint = p; - serz.generalLineFlag = true; - newRecords.add(serz); - if (lineStyle2Obj != null) { - lineStyle2Obj.noClose = false; - } - break; - case 'L': - StraightEdgeRecord serl = new StraightEdgeRecord(); - x = command.params[0]; - y = command.params[1]; - - p = transform2.transform(x, y); - serl.deltaX = (int) Math.round(p.x - prevPoint.x); - serl.deltaY = (int) Math.round(p.y - prevPoint.y); - prevPoint = p; - serl.generalLineFlag = true; - serl.simplify(); - newRecords.add(serl); - break; - case 'H': - StraightEdgeRecord serh = new StraightEdgeRecord(); - x = command.params[0]; - - p = transform2.transform(x, y); - serh.deltaX = (int) Math.round(p.x - prevPoint.x); - prevPoint = p; - newRecords.add(serh); - break; - case 'V': - StraightEdgeRecord serv = new StraightEdgeRecord(); - y = command.params[0]; - - p = transform2.transform(x, y); - serv.deltaY = (int) Math.round(p.y - prevPoint.y); - prevPoint = p; - serv.vertLineFlag = true; - newRecords.add(serv); - break; - case 'Q': - CurvedEdgeRecord cer = new CurvedEdgeRecord(); - x = command.params[0]; - y = command.params[1]; - - p = transform2.transform(x, y); - cer.controlDeltaX = (int) Math.round(p.x - prevPoint.x); - cer.controlDeltaY = (int) Math.round(p.y - prevPoint.y); - prevPoint = p; - - x = command.params[2]; - y = command.params[3]; - - p = transform2.transform(x, y); - cer.anchorDeltaX = (int) Math.round(p.x - prevPoint.x); - cer.anchorDeltaY = (int) Math.round(p.y - prevPoint.y); - prevPoint = p; - newRecords.add(cer); - break; - case 'C': - showWarning("cubicCurvesNotSupported", "Cubic curves are not supported by Flash."); - - // create at least something... - Point pStart = prevPoint; - Point pControl1; - - x = command.params[0]; - y = command.params[1]; - - pControl1 = transform2.transform(x, y); - - x = command.params[2]; - y = command.params[3]; - - Point pControl2 = transform2.transform(x, y); - - x = command.params[4]; - y = command.params[5]; - - p = transform2.transform(x, y); - - //StraightEdgeRecord serc = new StraightEdgeRecord(); - //serc.generalLineFlag = true; - //serc.deltaX = (int) Math.round(p.x - prevPoint.x); - //serc.deltaY = (int) Math.round(p.y - prevPoint.y); - //newRecords.add(serc); - List quadCoordinates = new CubicToQuad().cubicToQuad(pStart.x, pStart.y, pControl1.x, pControl1.y, pControl2.x, pControl2.y, p.x, p.y, 1); - for (int i = 2; i < quadCoordinates.size();) { - CurvedEdgeRecord cerc = new CurvedEdgeRecord(); - p = new Point(quadCoordinates.get(i++), quadCoordinates.get(i++)); - cerc.controlDeltaX = (int) Math.round(p.x - prevPoint.x); - cerc.controlDeltaY = (int) Math.round(p.y - prevPoint.y); - prevPoint = p; - - p = new Point(quadCoordinates.get(i++), quadCoordinates.get(i++)); - cerc.anchorDeltaX = (int) Math.round(p.x - prevPoint.x); - cerc.anchorDeltaY = (int) Math.round(p.y - prevPoint.y); - prevPoint = p; - newRecords.add(cerc); - } - - break; - default: - Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Unknown command: {0}", command); - return; - } - - x0 = x; - y0 = y; - } - applyStyleGradients(SHAPERECORD.getBounds(newRecords), scrStyle, transform2, shapeNum, style); - shapes.shapeRecords.addAll(newRecords); - } - - private void processPath(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { - String data = childElement.getAttribute("d"); - - char command = 0; - Point startPoint = new Point(0, 0); - Point prevCControlPoint = null; - Point prevQControlPoint = null; - double x0 = 0; - double y0 = 0; - - List pathCommands = new ArrayList<>(); - SvgPathReader pathReader = new SvgPathReader(data); - try { - while (pathReader.hasNext()) { - char newCommand; - if ((newCommand = pathReader.readCommand()) != 0) { - command = newCommand; - } - - boolean isRelative = Character.isLowerCase(command); - - double x = x0; - double y = y0; - - char cmd = Character.toUpperCase(command); - switch (cmd) { - case 'M': - PathCommand scr = new PathCommand(); - scr.command = 'M'; - - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } - - scr.params = new double[]{x, y}; - - pathCommands.add(scr); - startPoint = new Point(x, y); - - command = isRelative ? 'l' : 'L'; - break; - case 'Z': - PathCommand serz = new PathCommand(); - serz.command = 'Z'; - x = startPoint.x; - y = startPoint.y; - pathCommands.add(serz); - break; - case 'L': - PathCommand serl = new PathCommand(); - serl.command = 'L'; - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } - - serl.params = new double[]{x, y}; - pathCommands.add(serl); - break; - case 'H': - PathCommand serh = new PathCommand(); - serh.command = 'H'; - x = pathReader.readDouble(); - if (isRelative) { - x += x0; - } - - serh.params = new double[]{x}; - pathCommands.add(serh); - break; - case 'V': - PathCommand serv = new PathCommand(); - serv.command = 'V'; - y = pathReader.readDouble(); - if (isRelative) { - y += y0; - } - - serv.params = new double[]{y}; - pathCommands.add(serv); - break; - case 'Q': - case 'T': - PathCommand cer = new PathCommand(); - cer.command = 'Q'; - - Point pControl; - if (cmd == 'Q') { - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } - - pControl = new Point(x, y); - } else if (prevQControlPoint != null) { - pControl = new Point(2 * x0 - prevQControlPoint.x, 2 * y0 - prevQControlPoint.y); - } else { - pControl = new Point(x0, y0); - } - - prevQControlPoint = pControl; - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } - - cer.params = new double[]{pControl.x, pControl.y, x, y}; - pathCommands.add(cer); - break; - case 'C': - case 'S': - showWarning("cubicCurvesNotSupported", "Cubic curves are not supported by Flash."); - - // create at least something... - Point pControl1; - if (cmd == 'C') { - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } - - pControl1 = new Point(x, y); - } else if (prevCControlPoint != null) { - pControl1 = new Point(2 * x0 - prevCControlPoint.x, 2 * y0 - prevCControlPoint.y); - } else { - pControl1 = new Point(x0, y0); - } - - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } - - Point pControl2 = new Point(x, y); - prevCControlPoint = pControl2; - - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } - - PathCommand cerc = new PathCommand(); - cerc.command = 'C'; - cerc.params = new double[]{pControl1.x, pControl1.y, pControl2.x, pControl2.y, x, y}; - pathCommands.add(cerc); - break; - case 'A': - double rx = pathReader.readDouble(); - double ry = pathReader.readDouble(); - double fi = pathReader.readDouble() * Math.PI / 180; - boolean largeFlag = (int) pathReader.readDouble() != 0; - boolean sweepFlag = (int) pathReader.readDouble() != 0; - - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } - - if (rx == 0 || ry == 0) { - // straight line to (x, y) - PathCommand sera = new PathCommand(); - sera.command = 'L'; - sera.params = new double[]{x, y}; - pathCommands.add(sera); - } else { - rx = Math.abs(rx); - ry = Math.abs(ry); - - double x1 = x0; - double y1 = y0; - double x2 = x; - double y2 = y; - - double d1 = (x1 - x2) / 2; - double d2 = (y1 - y2) / 2; - double x1Comma = Math.cos(fi) * d1 + Math.sin(fi) * d2; - double y1Comma = -Math.sin(fi) * d1 + Math.cos(fi) * d2; - - // Correction of out-of-range radii - double lambda = x1Comma * x1Comma / (rx * rx) + y1Comma * y1Comma / (ry * ry); - if (lambda > 1) { - double sqrtLambda = Math.sqrt(lambda); - rx = sqrtLambda * rx; - ry = sqrtLambda * ry; - } - - double c = Math.sqrt((rx * rx * ry * ry - rx * rx * y1Comma * y1Comma - ry * ry * x1Comma * x1Comma) / (rx * rx * y1Comma * y1Comma + ry * ry * x1Comma * x1Comma)); - double cxComma = c * rx * y1Comma / ry; - double cyComma = c * -ry * x1Comma / rx; - - if (largeFlag == sweepFlag) { - cxComma = -cxComma; - cyComma = -cyComma; - } - - double cx = Math.cos(fi) * cxComma - Math.sin(fi) * cyComma + (x1 + x2) / 2; - double cy = Math.sin(fi) * cxComma + Math.cos(fi) * cyComma + (y1 + y2) / 2; - - double px1 = (x1Comma - cxComma) / rx; - double py1 = (y1Comma - cyComma) / ry; - double theta1 = calcAngle(1, 0, px1, py1); - - double px2 = (-x1Comma - cxComma) / rx; - double py2 = (-y1Comma - cyComma) / ry; - double deltaTheta = calcAngle(px1, py1, px2, py2); - if (sweepFlag) { - if (deltaTheta < 0) { - deltaTheta += 2 * Math.PI; - } - } else if (deltaTheta > 0) { - deltaTheta -= 2 * Math.PI; - } - - double rcp = Math.sqrt(4 - 2 * Math.sqrt(2)); - double delta = Math.signum(deltaTheta) * Math.PI / 4; - - int segmentCount = (int) Math.ceil(deltaTheta / delta); - double theta = theta1; - - PathCommand sera; - for (int i = 0; i < segmentCount - 1; i++) { - theta += delta; - /*sera = new PathCommand(); - sera.command = 'L'; - double x12 = Math.cos(theta) * rx; - double y12 = Math.sin(theta) * ry; - x1Comma = Math.cos(fi) * x12 - Math.sin(fi) * y12; - y1Comma = Math.sin(fi) * x12 + Math.cos(fi) * y12; - sera.params = new double[]{cx + x1Comma, cy + y1Comma}; - pathCommands.add(sera);*/ - - sera = new PathCommand(); - sera.command = 'Q'; - double x12 = Math.cos(theta) * rx; - double y12 = Math.sin(theta) * ry; - x1Comma = Math.cos(fi) * x12 - Math.sin(fi) * y12; - y1Comma = Math.sin(fi) * x12 + Math.cos(fi) * y12; - - double theta2 = theta - delta / 2; - x12 = Math.cos(theta2) * rx * rcp; - y12 = Math.sin(theta2) * ry * rcp; - double x1Comma2 = Math.cos(fi) * x12 - Math.sin(fi) * y12; - double y1Comma2 = Math.sin(fi) * x12 + Math.cos(fi) * y12; - sera.params = new double[]{cx + x1Comma2, cy + y1Comma2, cx + x1Comma, cy + y1Comma}; - pathCommands.add(sera); - } - - sera = new PathCommand(); - sera.command = 'Q'; - - theta += delta; - double diff = theta1 + deltaTheta - theta; - diff = -delta - diff; - theta = theta - delta - diff / 2; - - double rcpm = 1 + (rcp - 1) * (diff / delta) * (diff / delta); - double x12 = Math.cos(theta) * rx * rcpm; - double y12 = Math.sin(theta) * ry * rcpm; - x1Comma = Math.cos(fi) * x12 - Math.sin(fi) * y12; - y1Comma = Math.sin(fi) * x12 + Math.cos(fi) * y12; - sera.params = new double[]{cx + x1Comma, cy + y1Comma, x, y}; - pathCommands.add(sera); - /*sera = new PathCommand(); - sera.command = 'L'; - sera.params = new double[]{x, y}; - pathCommands.add(sera);*/ - } - break; - default: - Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Unknown command: {0}", command); - return; - } - - if (cmd != 'C' && cmd != 'S') { - prevCControlPoint = null; - } - - if (cmd != 'Q' && cmd != 'T') { - prevQControlPoint = null; - } - - x0 = x; - y0 = y; - } - } catch (NumberFormatException e) { - // ignore remaining data as specified in SVG Specification F.2 Error processing - } - - processCommands(shapeNum, shapes, pathCommands, transform, style); - } - - private double calcAngle(double ux, double uy, double vx, double vy) { - double lu = Math.sqrt(ux * ux + uy * uy); - double lv = Math.sqrt(ux * ux + uy * uy); - double sign = Math.signum(ux * vy - uy * vx); - if (sign == 0) { - sign = 1; - } - - return sign * Math.acos(ux * vx + uy * vy / (lu * lv)); - } - - private void processCircle(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { - String attr = childElement.getAttribute("cx"); - double cx = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; - - attr = childElement.getAttribute("cy"); - double cy = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; - - attr = childElement.getAttribute("r"); - double r = attr.length() > 0 ? parseLength(attr, viewBox.width/* todo: how much is 100%? */) : 0; - - processEllipse(shapeNum, shapes, transform, style, cx, cy, r, r); - } - - private void processEllipse(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { - String attr = childElement.getAttribute("cx"); - double cx = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; - - attr = childElement.getAttribute("cy"); - double cy = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; - - attr = childElement.getAttribute("rx"); - double rx = attr.length() > 0 ? parseLength(attr, viewBox.width) : 0; - - attr = childElement.getAttribute("ry"); - double ry = attr.length() > 0 ? parseLength(attr, viewBox.height) : 0; - - processEllipse(shapeNum, shapes, transform, style, cx, cy, rx, ry); - } - - private void processEllipse(int shapeNum, SHAPEWITHSTYLE shapes, Matrix transform, SvgStyle style, double cx, double cy, double rx, double ry) { - double sqrt2RXHalf = Math.sqrt(2) * rx / 2; - double sqrt2Minus1RX = (Math.sqrt(2) - 1) * rx; - double sqrt2RYHalf = Math.sqrt(2) * ry / 2; - double sqrt2Minus1RY = (Math.sqrt(2) - 1) * ry; - - List pathCommands = new ArrayList<>(); - PathCommand scr = new PathCommand(); - scr.command = 'M'; - scr.params = new double[]{cx + rx, cy}; - pathCommands.add(scr); - - double[] points = new double[]{ - rx, -sqrt2Minus1RY, - sqrt2RXHalf, -sqrt2RYHalf, - sqrt2Minus1RX, -ry, - 0, -ry, - -sqrt2Minus1RX, -ry, - -sqrt2RXHalf, -sqrt2RYHalf, - -rx, -sqrt2Minus1RY, - -rx, 0, - -rx, sqrt2Minus1RY, - -sqrt2RXHalf, sqrt2RYHalf, - -sqrt2Minus1RX, ry, - 0, ry, - sqrt2Minus1RX, ry, - sqrt2RXHalf, sqrt2RYHalf, - rx, sqrt2Minus1RY, - rx, 0}; - - for (int i = 0; i < points.length; i += 4) { - PathCommand cer = new PathCommand(); - cer.command = 'Q'; - cer.params = new double[]{cx + points[i], cy + points[i + 1], cx + points[i + 2], cy + points[i + 3]}; - - /*double tetha = 30; - tetha *= Math.PI / 180; - double x1 = points[i]; - double y1 = points[i + 1]; - double x2 = points[i + 2]; - double y2 = points[i + 3]; - - double x1Comma = Math.cos(tetha) * x1 + Math.sin(tetha) * y1; - double y1Comma = -Math.sin(tetha) * x1 + Math.cos(tetha) * y1; - double x2Comma = Math.cos(tetha) * x2 + Math.sin(tetha) * y2; - double y2Comma = -Math.sin(tetha) * x2 + Math.cos(tetha) * y2; - - cer.params = new double[]{cx + x1Comma, cy + y1Comma, cx + x2Comma, cy + y2Comma};*/ - pathCommands.add(cer); - } - - PathCommand serz = new PathCommand(); - serz.command = 'Z'; - pathCommands.add(serz); - - processCommands(shapeNum, shapes, pathCommands, transform, style); - } - - private void processRect(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { - String attr = childElement.getAttribute("x"); - double x = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; - - attr = childElement.getAttribute("y"); - double y = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; - - attr = childElement.getAttribute("width"); - double width = attr.length() > 0 ? parseLength(attr, viewBox.width) : 0; - - attr = childElement.getAttribute("height"); - double height = attr.length() > 0 ? parseLength(attr, viewBox.height) : 0; - - attr = childElement.getAttribute("rx"); - double rx = attr.length() > 0 ? parseLength(attr, viewBox.width) : 0; - - attr = childElement.getAttribute("ry"); - double ry = attr.length() > 0 ? parseLength(attr, viewBox.height) : 0; - - if (rx == 0 && ry != 0) { - rx = ry; - } else if (rx != 0 && ry == 0) { - ry = rx; - } - - if (rx > width / 2) { - rx = width / 2; - } - - if (ry > height / 2) { - ry = height / 2; - } - - List pathCommands = new ArrayList<>(); - - if (rx > 0 || ry > 0) { - PathCommand scr = new PathCommand(); - scr.command = 'M'; - scr.params = new double[]{x + width, y + ry}; - pathCommands.add(scr); - - double sqrt2RXHalf = Math.sqrt(2) * rx / 2; - double sqrt2Minus1RX = (Math.sqrt(2) - 1) * rx; - double sqrt2RYHalf = Math.sqrt(2) * ry / 2; - double sqrt2Minus1RY = (Math.sqrt(2) - 1) * ry; - - double[] points = new double[]{ - x + width, y + ry - sqrt2Minus1RY, - x + width - rx + sqrt2RXHalf, y + ry - sqrt2RYHalf, - x + width - rx + sqrt2Minus1RX, y, - x + width - rx, y, - x + rx, y, - x + rx - sqrt2Minus1RX, y, - x + rx - sqrt2RXHalf, y + ry - sqrt2RYHalf, - x, y + ry - sqrt2Minus1RY, - x, y + ry, - x, y + height - ry, - x, y + height - ry + sqrt2Minus1RY, - x + rx - sqrt2RXHalf, y + height - ry + sqrt2RYHalf, - x + rx - sqrt2Minus1RX, y + height, - x + rx, y + height, - x + width - rx, y + height, - x + width - rx + sqrt2Minus1RX, y + height, - x + width - rx + sqrt2RXHalf, y + height - ry + sqrt2RYHalf, - x + width, y + height - ry + sqrt2Minus1RY, - x + width, y + height - ry, - x + width, y + ry}; - - for (int i = 0; i < points.length;) { - if (i % 10 == 8) { - PathCommand cer = new PathCommand(); - cer.command = 'L'; - cer.params = new double[]{points[i], points[i + 1]}; - pathCommands.add(cer); - i += 2; - } else { - PathCommand cer = new PathCommand(); - cer.command = 'Q'; - cer.params = new double[]{points[i], points[i + 1], points[i + 2], points[i + 3]}; - pathCommands.add(cer); - i += 4; - } - } - } else { - PathCommand scr = new PathCommand(); - scr.command = 'M'; - scr.params = new double[]{x, y}; - pathCommands.add(scr); - - double[] points = new double[]{ - x + width, y, - x + width, y + height, - x, y + height, - x, y}; - - for (int i = 0; i < points.length; i += 2) { - PathCommand cer = new PathCommand(); - cer.command = 'L'; - cer.params = new double[]{points[i], points[i + 1]}; - - pathCommands.add(cer); - } - } - - PathCommand serz = new PathCommand(); - serz.command = 'Z'; - pathCommands.add(serz); - - processCommands(shapeNum, shapes, pathCommands, transform, style); - } - - private void processLine(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { - String attr = childElement.getAttribute("x1"); - double x1 = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; - - attr = childElement.getAttribute("y1"); - double y1 = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; - - attr = childElement.getAttribute("x2"); - double x2 = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; - - attr = childElement.getAttribute("y2"); - double y2 = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; - - List pathCommands = new ArrayList<>(); - PathCommand scr = new PathCommand(); - scr.command = 'M'; - scr.params = new double[]{x1, y1}; - pathCommands.add(scr); - - PathCommand cer = new PathCommand(); - cer.command = 'L'; - cer.params = new double[]{x2, y2}; - - pathCommands.add(cer); - - processCommands(shapeNum, shapes, pathCommands, transform, style); - } - - private void processPolyline(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { - processPolyline(shapeNum, shapes, childElement, transform, style, false); - } - - private void processPolygon(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { - processPolyline(shapeNum, shapes, childElement, transform, style, true); - } - - private void processPolyline(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style, boolean close) { - String data = childElement.getAttribute("points"); - - char command = 'M'; - double x0 = 0; - double y0 = 0; - - List pathCommands = new ArrayList<>(); - SvgPathReader pathReader = new SvgPathReader(data); - try { - while (pathReader.hasNext()) { - double x = x0; - double y = y0; - - Point p = null; - switch (command) { - case 'M': - PathCommand scr = new PathCommand(); - scr.command = 'M'; - - x = pathReader.readDouble(); - y = pathReader.readDouble(); - scr.params = new double[]{x, y}; - - pathCommands.add(scr); - break; - case 'L': - PathCommand serl = new PathCommand(); - serl.command = 'L'; - x = pathReader.readDouble(); - y = pathReader.readDouble(); - serl.params = new double[]{x, y}; - pathCommands.add(serl); - break; - } - - x0 = x; - y0 = y; - command = 'L'; - } - } catch (NumberFormatException e) { - // ignore remaining data as specified in SVG Specification F.2 Error processing - } - - if (close) { - PathCommand serz = new PathCommand(); - serz.command = 'Z'; - pathCommands.add(serz); - } - - processCommands(shapeNum, shapes, pathCommands, transform, style); - } - - //Stub for w3 test. TODO: refactor and move to test directory. It's here because of easy access - compiling single file - private static void svgTest(String name) throws IOException, InterruptedException { - if (!new File(name + ".original.svg").exists()) { - URL svgUrl = new URL("http://www.w3.org/Graphics/SVG/Test/20061213/svggen/" + name + ".svg"); - byte[] svgData = Helper.readStream(svgUrl.openStream()); - Helper.writeFile(name + ".orig.svg", svgData); - - URL pngUrl = new URL("http://www.w3.org/Graphics/SVG/Test/20061213/png/full-" + name + ".png"); - byte[] pngData = Helper.readStream(pngUrl.openStream()); - Helper.writeFile(name + ".orig.png", pngData); - } - - String svgDataS = Helper.readTextFile(name + ".orig.svg"); - SWF swf = new SWF(); - DefineShape4Tag st = new DefineShape4Tag(swf); - st = (DefineShape4Tag) (new SvgImporter().importSvg(st, svgDataS)); - swf.addTag(st); - SerializableImage si = new SerializableImage(480, 360, BufferedImage.TYPE_4BYTE_ABGR); - BitmapExporter.export(swf, st.shapes, Color.yellow, si, new Matrix(), new Matrix(), null); - List li = new ArrayList<>(); - li.add(st); - ImageIO.write(si.getBufferedImage(), "PNG", new File(name + ".imported.png")); - ExportAssetsTag eat = new ExportAssetsTag(swf); - eat.tags.add(st.getCharacterId()); - eat.names.add(name); - swf.addTag(eat); - swf.assignExportNamesToSymbols(); - st.shapeBounds.Xmax = (int) (si.getWidth() * SWF.unitDivisor); - st.shapeBounds.Ymax = (int) (si.getHeight() * SWF.unitDivisor); - new ShapeExporter().exportShapes(null, "./outex/", new ReadOnlyTagList(li), new ShapeExportSettings(ShapeExportMode.SVG, 1), null); - } - - //Test for SVG - public static void main(String[] args) throws IOException, InterruptedException { -// svgTest("animate-elem-02-t"); -// svgTest("animate-elem-03-t"); -// svgTest("animate-elem-04-t"); -// svgTest("animate-elem-05-t"); -// svgTest("animate-elem-06-t"); -// svgTest("animate-elem-07-t"); -// svgTest("animate-elem-08-t"); -// svgTest("animate-elem-09-t"); -// svgTest("animate-elem-10-t"); -// svgTest("animate-elem-11-t"); -// svgTest("animate-elem-12-t"); -// svgTest("animate-elem-13-t"); -// svgTest("animate-elem-14-t"); -// svgTest("animate-elem-15-t"); -// svgTest("animate-elem-17-t"); -// svgTest("animate-elem-19-t"); -// svgTest("animate-elem-20-t"); -// svgTest("animate-elem-21-t"); -// svgTest("animate-elem-22-b"); -// svgTest("animate-elem-23-t"); -// svgTest("animate-elem-24-t"); -// svgTest("animate-elem-25-t"); -// svgTest("animate-elem-26-t"); -// svgTest("animate-elem-27-t"); -// svgTest("animate-elem-28-t"); -// svgTest("animate-elem-29-b"); -// svgTest("animate-elem-30-t"); -// svgTest("animate-elem-31-t"); -// svgTest("animate-elem-32-t"); -// svgTest("animate-elem-33-t"); -// svgTest("animate-elem-34-t"); -// svgTest("animate-elem-36-t"); -// svgTest("animate-elem-37-t"); -// svgTest("animate-elem-39-t"); -// svgTest("animate-elem-40-t"); -// svgTest("animate-elem-41-t"); -// svgTest("animate-elem-44-t"); -// svgTest("animate-elem-46-t"); -// svgTest("animate-elem-52-t"); -// svgTest("animate-elem-60-t"); -// svgTest("animate-elem-61-t"); -// svgTest("animate-elem-62-t"); -// svgTest("animate-elem-63-t"); -// svgTest("animate-elem-64-t"); -// svgTest("animate-elem-65-t"); -// svgTest("animate-elem-66-t"); -// svgTest("animate-elem-67-t"); -// svgTest("animate-elem-68-t"); -// svgTest("animate-elem-69-t"); -// svgTest("animate-elem-70-t"); -// svgTest("animate-elem-77-t"); -// svgTest("animate-elem-78-t"); -// svgTest("animate-elem-80-t"); -// svgTest("animate-elem-81-t"); -// svgTest("animate-elem-82-t"); -// svgTest("animate-elem-83-t"); -// svgTest("animate-elem-84-t"); -// svgTest("animate-elem-85-t"); - svgTest("color-prof-01-f"); - svgTest("color-prop-01-b"); - svgTest("color-prop-02-f"); - svgTest("color-prop-03-t"); - svgTest("coords-coord-01-t"); - svgTest("coords-coord-02-t"); - svgTest("coords-trans-01-b"); - svgTest("coords-trans-02-t"); - svgTest("coords-trans-03-t"); - svgTest("coords-trans-04-t"); - svgTest("coords-trans-05-t"); - svgTest("coords-trans-06-t"); - svgTest("coords-units-01-b"); - svgTest("coords-units-02-b"); - svgTest("coords-units-03-b"); - svgTest("coords-viewattr-01-b"); - svgTest("coords-viewattr-02-b"); - svgTest("coords-viewattr-03-b"); - svgTest("extend-namespace-01-f"); -// svgTest("filters-blend-01-b"); -// svgTest("filters-color-01-b"); -// svgTest("filters-composite-02-b"); -// svgTest("filters-comptran-01-b"); -// svgTest("filters-conv-01-f"); -// svgTest("filters-diffuse-01-f"); -// svgTest("filters-displace-01-f"); -// svgTest("filters-example-01-b"); -// svgTest("filters-felem-01-b"); -// svgTest("filters-gauss-01-b"); -// svgTest("filters-image-01-b"); -// svgTest("filters-light-01-f"); -// svgTest("filters-morph-01-f"); -// svgTest("filters-offset-01-b"); -// svgTest("filters-specular-01-f"); -// svgTest("filters-tile-01-b"); -// svgTest("filters-turb-01-f"); -// svgTest("fonts-desc-02-t"); -// svgTest("fonts-elem-01-t"); -// svgTest("fonts-elem-02-t"); -// svgTest("fonts-elem-03-b"); -// svgTest("fonts-elem-04-b"); -// svgTest("fonts-elem-05-t"); -// svgTest("fonts-elem-06-t"); -// svgTest("fonts-elem-07-b"); -// svgTest("fonts-glyph-02-t"); -// svgTest("fonts-glyph-03-t"); -// svgTest("fonts-glyph-04-t"); -// svgTest("fonts-kern-01-t"); -// svgTest("interact-cursor-01-f"); -// svgTest("interact-dom-01-b"); -// svgTest("interact-events-01-b"); -// svgTest("interact-order-01-b"); -// svgTest("interact-order-02-b"); -// svgTest("interact-order-03-b"); -// svgTest("interact-zoom-01-t"); -// svgTest("linking-a-01-b"); -// svgTest("linking-a-02-b"); -// svgTest("linking-a-03-b"); -// svgTest("linking-a-04-t"); -// svgTest("linking-a-05-t"); -// svgTest("linking-a-07-t"); -// svgTest("linking-uri-01-b"); -// svgTest("linking-uri-02-b"); -// svgTest("linking-uri-03-t"); -// svgTest("masking-intro-01-f"); -// svgTest("masking-mask-01-b"); -// svgTest("masking-opacity-01-b"); -// svgTest("masking-path-01-b"); -// svgTest("masking-path-02-b"); -// svgTest("masking-path-03-b"); -// svgTest("masking-path-04-b"); -// svgTest("masking-path-05-f"); -// svgTest("metadata-example-01-b"); - svgTest("painting-fill-01-t"); - svgTest("painting-fill-02-t"); - svgTest("painting-fill-03-t"); - svgTest("painting-fill-04-t"); - svgTest("painting-fill-05-b"); - svgTest("painting-marker-01-f"); - svgTest("painting-marker-02-f"); - svgTest("painting-marker-03-f"); - svgTest("painting-render-01-b"); - svgTest("painting-stroke-01-t"); - svgTest("painting-stroke-02-t"); - svgTest("painting-stroke-03-t"); - svgTest("painting-stroke-04-t"); - svgTest("painting-stroke-07-t"); - svgTest("paths-data-01-t"); - svgTest("paths-data-02-t"); - svgTest("paths-data-03-f"); - svgTest("paths-data-04-t"); - svgTest("paths-data-05-t"); - svgTest("paths-data-06-t"); - svgTest("paths-data-07-t"); - svgTest("paths-data-08-t"); - svgTest("paths-data-09-t"); - svgTest("paths-data-10-t"); - svgTest("paths-data-12-t"); - svgTest("paths-data-13-t"); - svgTest("paths-data-14-t"); - svgTest("paths-data-15-t"); - svgTest("pservers-grad-01-b"); - svgTest("pservers-grad-02-b"); - svgTest("pservers-grad-03-b"); - svgTest("pservers-grad-04-b"); - svgTest("pservers-grad-05-b"); - svgTest("pservers-grad-06-b"); - svgTest("pservers-grad-07-b"); - svgTest("pservers-grad-08-b"); - svgTest("pservers-grad-09-b"); - svgTest("pservers-grad-10-b"); - svgTest("pservers-grad-11-b"); - svgTest("pservers-grad-12-b"); - svgTest("pservers-grad-13-b"); - svgTest("pservers-grad-14-b"); - svgTest("pservers-grad-15-b"); - svgTest("pservers-grad-16-b"); - svgTest("pservers-grad-17-b"); - svgTest("pservers-grad-18-b"); - svgTest("pservers-grad-19-b"); - svgTest("pservers-pattern-01-b"); - svgTest("render-elems-01-t"); - svgTest("render-elems-02-t"); - svgTest("render-elems-03-t"); - svgTest("render-elems-06-t"); - svgTest("render-elems-07-t"); - svgTest("render-elems-08-t"); - svgTest("render-groups-01-b"); - svgTest("render-groups-03-t"); -// svgTest("script-handle-01-b"); -// svgTest("script-handle-02-b"); -// svgTest("script-handle-03-b"); -// svgTest("script-handle-04-b"); - svgTest("shapes-circle-01-t"); - svgTest("shapes-circle-02-t"); - svgTest("shapes-ellipse-01-t"); - svgTest("shapes-ellipse-02-t"); - svgTest("shapes-intro-01-t"); - svgTest("shapes-line-01-t"); - svgTest("shapes-polygon-01-t"); - svgTest("shapes-polyline-01-t"); - svgTest("shapes-rect-01-t"); - svgTest("shapes-rect-02-t"); -// svgTest("struct-cond-01-t"); -// svgTest("struct-cond-02-t"); -// svgTest("struct-cond-03-t"); -// svgTest("struct-defs-01-t"); -// svgTest("struct-dom-01-b"); -// svgTest("struct-dom-02-b"); -// svgTest("struct-dom-03-b"); -// svgTest("struct-dom-04-b"); -// svgTest("struct-dom-05-b"); -// svgTest("struct-dom-06-b"); -// svgTest("struct-frag-01-t"); -// svgTest("struct-frag-02-t"); -// svgTest("struct-frag-03-t"); -// svgTest("struct-frag-04-t"); -// svgTest("struct-frag-05-t"); -// svgTest("struct-frag-06-t"); -// svgTest("struct-group-01-t"); -// svgTest("struct-group-02-b"); -// svgTest("struct-group-03-t"); -// svgTest("struct-image-01-t"); -// svgTest("struct-image-02-b"); -// svgTest("struct-image-03-t"); -// svgTest("struct-image-04-t"); -// svgTest("struct-image-05-b"); -// svgTest("struct-image-06-t"); -// svgTest("struct-image-07-t"); -// svgTest("struct-image-08-t"); -// svgTest("struct-image-09-t"); -// svgTest("struct-image-10-t"); -// svgTest("struct-symbol-01-b"); -// svgTest("struct-use-01-t"); -// svgTest("struct-use-03-t"); -// svgTest("struct-use-05-b"); -// svgTest("styling-css-01-b"); -// svgTest("styling-css-02-b"); -// svgTest("styling-css-03-b"); -// svgTest("styling-css-04-f"); -// svgTest("styling-css-05-b"); -// svgTest("styling-css-06-b"); -// svgTest("styling-inherit-01-b"); -// svgTest("styling-pres-01-t"); -// svgTest("text-align-01-b"); -// svgTest("text-align-02-b"); -// svgTest("text-align-03-b"); -// svgTest("text-align-04-b"); -// svgTest("text-align-05-b"); -// svgTest("text-align-06-b"); -// svgTest("text-align-08-b"); -// svgTest("text-altglyph-01-b"); -// svgTest("text-deco-01-b"); -// svgTest("text-fonts-01-t"); -// svgTest("text-fonts-02-t"); -// svgTest("text-fonts-03-t"); -// svgTest("text-intro-01-t"); -// svgTest("text-intro-02-b"); -// svgTest("text-intro-03-b"); -// svgTest("text-intro-04-t"); -// svgTest("text-intro-05-t"); -// svgTest("text-path-01-b"); -// svgTest("text-spacing-01-b"); -// svgTest("text-text-01-b"); -// svgTest("text-text-03-b"); -// svgTest("text-text-04-t"); -// svgTest("text-text-05-t"); -// svgTest("text-text-06-t"); -// svgTest("text-text-07-t"); -// svgTest("text-text-08-b"); -// svgTest("text-tref-01-b"); -// svgTest("text-tselect-01-b"); -// svgTest("text-tselect-02-f"); -// svgTest("text-tspan-01-b"); -// svgTest("text-ws-01-t"); -// svgTest("text-ws-02-t"); -// svgTest("types-basicDOM-01-b"); - } - - private void applyFillGradients(SvgFill fill, FILLSTYLE fillStyle, RECT bounds, StyleChangeRecord scr, Matrix transform, int shapeNum, SvgStyle style) { - if (fill == null || fillStyle == null) { - return; - } - if (fill instanceof SvgGradient) { - SvgGradient gfill = (SvgGradient) fill; - Matrix gradientMatrix = Matrix.parseSvgMatrix(gfill.gradientTransform, SWF.unitDivisor, 1); - gradientMatrix = transform.concatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).concatenate(gradientMatrix); - fillStyle.gradientMatrix = gradientMatrix.toMATRIX(); - if (fill instanceof SvgLinearGradient) { - SvgLinearGradient lgfill = (SvgLinearGradient) fill; - fillStyle.fillStyleType = FILLSTYLE.LINEAR_GRADIENT; - fillStyle.gradient = new GRADIENT(); - double x1 = parseCoordinate(lgfill.x1, 1/* todo: how much is 100%? */); - double y1 = parseCoordinate(lgfill.y1, 1/* todo: how much is 100%? */); - double x2 = parseCoordinate(lgfill.x2, 1/* todo: how much is 100%? */); - double y2 = parseCoordinate(lgfill.y2, 1/* todo: how much is 100%? */); - - x1 = x1 * SWF.unitDivisor; - y1 = y1 * SWF.unitDivisor; - x2 = x2 * SWF.unitDivisor; - y2 = y2 * SWF.unitDivisor; - - Matrix boundingBoxMatrix = new Matrix(); - if (lgfill.gradientUnits == SvgGradientUnits.OBJECT_BOUNDING_BOX) { - boundingBoxMatrix.scaleX = (bounds.Xmax - bounds.Xmin) / SWF.unitDivisor; - boundingBoxMatrix.rotateSkew0 = 0; - boundingBoxMatrix.rotateSkew1 = 0; - boundingBoxMatrix.scaleY = (bounds.Ymax - bounds.Ymin) / SWF.unitDivisor; - boundingBoxMatrix.translateX = bounds.Xmin; - boundingBoxMatrix.translateY = bounds.Ymin; - } - - Matrix xyMatrix = new Matrix(); - xyMatrix.scaleX = x2 - x1; - xyMatrix.rotateSkew0 = y2 - y1; - xyMatrix.rotateSkew1 = -xyMatrix.rotateSkew0; - xyMatrix.scaleY = xyMatrix.scaleX; - - xyMatrix = xyMatrix.preConcatenate(boundingBoxMatrix); - - Matrix zeroStartMatrix = Matrix.getTranslateInstance(0.5, 0); - - Matrix scaleMatrix = Matrix.getScaleInstance(1 / 16384.0 / 2); - Matrix transMatrix = Matrix.getTranslateInstance(x1, y1); - - Matrix tMatrix = new Matrix(); - tMatrix = tMatrix.preConcatenate(scaleMatrix); - tMatrix = tMatrix.preConcatenate(zeroStartMatrix); - tMatrix = tMatrix.preConcatenate(xyMatrix); - - tMatrix = tMatrix.preConcatenate(transMatrix); - Point p1 = tMatrix.transform(new Point(-16384, 0)); - Point p2 = tMatrix.transform(new Point(16384, 0)); - - tMatrix = tMatrix.preConcatenate(new Matrix(fillStyle.gradientMatrix)); - fillStyle.gradientMatrix = tMatrix.toMATRIX(); - } else if (fill instanceof SvgRadialGradient) { - SvgRadialGradient rgfill = (SvgRadialGradient) fill; - double cx = parseCoordinate(rgfill.cx, 1/* todo: how much is 100%? */); - double cy = parseCoordinate(rgfill.cy, 1/* todo: how much is 100%? */); - double r = parseLength(rgfill.r, 1/* todo: how much is 100%? */); - - Matrix boundingBoxMatrix = new Matrix(); - if (rgfill.gradientUnits == SvgGradientUnits.OBJECT_BOUNDING_BOX) { - boundingBoxMatrix.scaleX = (bounds.Xmax - bounds.Xmin) / SWF.unitDivisor; - boundingBoxMatrix.rotateSkew0 = 0; - boundingBoxMatrix.rotateSkew1 = 0; - boundingBoxMatrix.scaleY = (bounds.Ymax - bounds.Ymin) / SWF.unitDivisor; - boundingBoxMatrix.translateX = bounds.Xmin; - boundingBoxMatrix.translateY = bounds.Ymin; - } - - fillStyle.gradientMatrix = Matrix.getTranslateInstance(SWF.unitDivisor * cx, SWF.unitDivisor * cy).concatenate(new Matrix(fillStyle.gradientMatrix)).concatenate(Matrix.getScaleInstance(r / 819.2)).preConcatenate(boundingBoxMatrix).toMATRIX(); - - double fx = parseCoordinate(rgfill.fx, 1/* todo: how much is 100%? */); - double fy = parseCoordinate(rgfill.fy, 1/* todo: how much is 100%? */); - if (!rgfill.fx.equals(rgfill.cx) || !rgfill.fy.equals(rgfill.cy)) { - fillStyle.fillStyleType = FILLSTYLE.FOCAL_RADIAL_GRADIENT; - fillStyle.gradient = new FOCALGRADIENT(); - FOCALGRADIENT fg = (FOCALGRADIENT) fillStyle.gradient; - double f = Math.sqrt((fx - cx) * (fx - cx) + (fy - cy) * (fy - cy)) / 819.2; - fg.focalPoint = (float) f; - } else { - fillStyle.fillStyleType = FILLSTYLE.RADIAL_GRADIENT; - fillStyle.gradient = new GRADIENT(); - } - } - switch (gfill.spreadMethod) { - case PAD: - fillStyle.gradient.spreadMode = GRADIENT.SPREAD_PAD_MODE; - break; - case REFLECT: - fillStyle.gradient.spreadMode = GRADIENT.SPREAD_REFLECT_MODE; - break; - case REPEAT: - fillStyle.gradient.spreadMode = GRADIENT.SPREAD_REPEAT_MODE; - break; - } - switch (gfill.interpolation) { - case LINEAR_RGB: - fillStyle.gradient.interpolationMode = GRADIENT.INTERPOLATION_LINEAR_RGB_MODE; - break; - case SRGB: - fillStyle.gradient.interpolationMode = GRADIENT.INTERPOLATION_RGB_MODE; - break; - } - - fillStyle.gradient.gradientRecords = new GRADRECORD[gfill.stops.size()]; - int prevRatio = -1; - for (int i = 0; i < gfill.stops.size(); i++) { - SvgStop stop = gfill.stops.get(i); - Color color = stop.color; - color = new Color(color.getRed(), color.getGreen(), color.getBlue(), (int) Math.round(color.getAlpha() * style.getOpacity())); - fillStyle.gradient.gradientRecords[i] = new GRADRECORD(); - fillStyle.gradient.gradientRecords[i].inShape3 = shapeNum >= 3; - fillStyle.gradient.gradientRecords[i].color = getRGB(shapeNum, color); - int ratio = Math.max((int) Math.round(stop.offset * 255), prevRatio + 1); - fillStyle.gradient.gradientRecords[i].ratio = ratio; - prevRatio = ratio; - if (prevRatio == 255) { - break; - } - } - } else if (fill instanceof SvgBitmapFill) { - SvgBitmapFill bfill = (SvgBitmapFill) fill; - fillStyle.fillStyleType = FILLSTYLE.REPEATING_BITMAP; - fillStyle.bitmapId = bfill.characterId; - Matrix fillMatrix = Matrix.parseSvgMatrix(bfill.patternTransform, SWF.unitDivisor, SWF.unitDivisor); - fillMatrix = transform.concatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).concatenate(fillMatrix); - fillStyle.bitmapMatrix = fillMatrix.toMATRIX(); - } - } - - private void applyStyleGradients(RECT bounds, StyleChangeRecord scr, Matrix transform, int shapeNum, SvgStyle style) { - SvgFill fill = style.getFillWithOpacity(); - if (fill != null && fill != SvgTransparentFill.INSTANCE) { - applyFillGradients(fill, scr.fillStyles.fillStyles[0], bounds, scr, transform, shapeNum, style); - } - SvgFill strokeFill = style.getStrokeFillWithOpacity(); - if (strokeFill != null) { - if (scr.lineStyles.lineStyles.length > 0 && scr.lineStyles.lineStyles[0] instanceof LINESTYLE2) { - applyFillGradients(strokeFill, ((LINESTYLE2) scr.lineStyles.lineStyles[0]).fillType, bounds, scr, transform, shapeNum, style); - } - } - } - - private StyleChangeRecord getStyleChangeRecord(int shapeNum, SvgStyle style) { - StyleChangeRecord scr = new StyleChangeRecord(); - - scr.stateNewStyles = true; - scr.fillStyles = new FILLSTYLEARRAY(); - scr.stateFillStyle1 = true; - scr.stateLineStyle = true; - SvgFill fill = style.getFillWithOpacity(); - if (fill != null && fill != SvgTransparentFill.INSTANCE) { - scr.fillStyles.fillStyles = new FILLSTYLE[1]; - scr.fillStyles.fillStyles[0] = new FILLSTYLE(); - if (fill instanceof SvgColor) { - Color colorFill = fill.toColor(); - scr.fillStyles.fillStyles[0].color = getRGB(shapeNum, colorFill); - scr.fillStyles.fillStyles[0].fillStyleType = FILLSTYLE.SOLID; - } else if (fill instanceof SvgGradient) { - //...apply in second step - applyStyleGradients - } - - scr.fillStyle1 = 1; - } else { - scr.fillStyles.fillStyles = new FILLSTYLE[0]; - scr.fillStyle1 = 0; - } - - scr.lineStyles = new LINESTYLEARRAY(); - SvgFill strokeFill = style.getStrokeFillWithOpacity(); - if (strokeFill != null && strokeFill != SvgTransparentFill.INSTANCE) { - Color lineColor = strokeFill.toColor(); - - scr.lineStyles.lineStyles = new LINESTYLE[1]; - LINESTYLE lineStyle = shapeNum <= 3 ? new LINESTYLE() : new LINESTYLE2(); - lineStyle.color = getRGB(shapeNum, lineColor); - lineStyle.width = (int) Math.round(style.getStrokeWidth() * SWF.unitDivisor); - SvgLineCap lineCap = style.getStrokeLineCap(); - SvgLineJoin lineJoin = style.getStrokeLineJoin(); - if (lineStyle instanceof LINESTYLE2) { - LINESTYLE2 lineStyle2 = (LINESTYLE2) lineStyle; - int swfCap = lineCap == SvgLineCap.BUTT ? LINESTYLE2.NO_CAP - : lineCap == SvgLineCap.ROUND ? LINESTYLE2.ROUND_CAP - : lineCap == SvgLineCap.SQUARE ? LINESTYLE2.SQUARE_CAP : 0; - lineStyle2.startCapStyle = swfCap; - lineStyle2.endCapStyle = swfCap; - if (!(strokeFill instanceof SvgColor)) { - lineStyle2.hasFillFlag = true; - lineStyle2.fillType = new FILLSTYLE(); - //...apply in second step - applyStyleGradients - } // Single color does not need fillType attribute - - int swfJoin = lineJoin == SvgLineJoin.MITER ? LINESTYLE2.MITER_JOIN - : lineJoin == SvgLineJoin.ROUND ? LINESTYLE2.ROUND_JOIN - : lineJoin == SvgLineJoin.BEVEL ? LINESTYLE2.BEVEL_JOIN : 0; - lineStyle2.joinStyle = swfJoin; - lineStyle2.miterLimitFactor = (float) style.getStrokeMiterLimit(); - } else { - if (lineCap != SvgLineCap.ROUND) { - showWarning("lineCapNotSupported", "LineCap style not supported in shape " + shapeNum); - } - if (lineJoin != SvgLineJoin.ROUND) { - showWarning("lineJoinNotSupported", "LineJoin style not supported in shape " + shapeNum); - } - } - - scr.lineStyles.lineStyles[0] = lineStyle; - scr.lineStyle = 1; - } else { - scr.lineStyles.lineStyles = new LINESTYLE[0]; - scr.lineStyle = 0; - } - - return scr; - } - - private RGB getRGB(int shapeNum, Color color) { - if (shapeNum < 3 && color.getAlpha() != 0xff) { - showWarning("transparentColorNotSupported", "Transparent color is not supported in shape " + shapeNum); - } - - return shapeNum >= 3 ? new RGBA(color) : new RGB(color); - } - - private double parseCoordinate(String value, double relativeTo) { - return parseLength(value, relativeTo); - } - - private double parseLength(String value, double relativeTo) { - if (value == null) { - throw new NumberFormatException(); - } - - value = value.toLowerCase(); - String unit = null; - if (value.endsWith("em") - || value.endsWith("ex") - || value.endsWith("px") - || value.endsWith("in") - || value.endsWith("cm") - || value.endsWith("mm") - || value.endsWith("pt") - || value.endsWith("pc")) { - unit = value.substring(value.length() - 2); - value = value.substring(0, value.length() - 2); - } else if (value.endsWith("%")) { - unit = "%"; - value = value.substring(0, value.length() - 1); - } - - double result = Double.parseDouble(value); - if (unit != null) { - switch (unit) { - case "em": - case "ex": - // todo: font things - break; - case "in": - result *= getDpi(); - break; - case "pt": - result *= getDpi() / 72; - break; - case "pc": - result *= getDpi() / 6; - break; - case "cm": - result *= getDpi() / 2.54; - break; - case "mm": - result *= getDpi() / 25.4; - break; - case "%": - result = relativeTo * result / 100; - break; - } - } - - return result; - } - - public double parseNumber(String value) { - if (value == null) { - throw new NumberFormatException(); - } - - double result = Double.parseDouble(value); - return result; - } - - public double parseNumberOrPercent(String value) { - if (value == null) { - throw new NumberFormatException(); - } - - boolean percent = value.endsWith("%"); - if (percent) { - value = value.substring(0, value.length() - 1); - } - - double result = Double.parseDouble(value); - if (percent) { - result /= 100; - } - - return result; - } - - private double getDpi() { - return 96; - - } - - class PathCommand { - - public char command; - - public double[] params; - } - - void showWarning(String name, String text) { - if (!shownWarnings.contains(name)) { - Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, text); - shownWarnings.add(name); - } - } -} +/* + * 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.svg; + +import com.jpexs.decompiler.flash.ReadOnlyTagList; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.exporters.ShapeExporter; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.commonshape.Point; +import com.jpexs.decompiler.flash.exporters.modes.ShapeExportMode; +import com.jpexs.decompiler.flash.exporters.settings.ShapeExportSettings; +import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter; +import com.jpexs.decompiler.flash.importers.ShapeImporter; +import com.jpexs.decompiler.flash.tags.DefineShape4Tag; +import com.jpexs.decompiler.flash.tags.ExportAssetsTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ShapeTag; +import com.jpexs.decompiler.flash.types.FILLSTYLE; +import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY; +import com.jpexs.decompiler.flash.types.FOCALGRADIENT; +import com.jpexs.decompiler.flash.types.GRADIENT; +import com.jpexs.decompiler.flash.types.GRADRECORD; +import com.jpexs.decompiler.flash.types.LINESTYLE; +import com.jpexs.decompiler.flash.types.LINESTYLE2; +import com.jpexs.decompiler.flash.types.LINESTYLEARRAY; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.RGB; +import com.jpexs.decompiler.flash.types.RGBA; +import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE; +import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; +import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.SerializableImage; +import java.awt.Color; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.imageio.ImageIO; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * + * @author JPEXS + */ +public class SvgImporter { + + private final Set shownWarnings = new HashSet<>(); + + ShapeTag shapeTag; + + private Rectangle2D.Double viewBox; + + public Tag importSvg(ShapeTag st, String svgXml) { + return importSvg(st, svgXml, true); + } + + public Tag importSvg(ShapeTag st, String svgXml, boolean fill) { + shapeTag = st; + + SHAPEWITHSTYLE shapes = new SHAPEWITHSTYLE(); + shapes.fillStyles = new FILLSTYLEARRAY(); + shapes.lineStyles = new LINESTYLEARRAY(); + shapes.fillStyles.fillStyles = new FILLSTYLE[0]; + shapes.lineStyles.lineStyles = new LINESTYLE[0]; + + int shapeNum = st.getShapeNum(); + shapes.shapeRecords = new ArrayList<>(); + + Rectangle2D.Double viewBox = null; + try { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + /*docFactory.setValidating(false); + docFactory.setNamespaceAware(true); + docFactory.setFeature("http://xml.org/sax/features/namespaces", false); + docFactory.setFeature("http://xml.org/sax/features/validation", false); + docFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);*/ + docFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + + Document doc = docBuilder.parse(new InputSource(new StringReader(svgXml))); + Element rootElement = doc.getDocumentElement(); + + Map idMap = new HashMap<>(); + populateIds(rootElement, idMap); + + if (!"svg".equals(rootElement.getTagName())) { + throw new IOException("SVG root element should be 'svg'"); + } + + double width = 800; + double height = 600; + + if (rootElement.hasAttribute("viewBox")) { + String params = rootElement.getAttribute("viewBox"); + String[] args = Matrix.parseSvgNumberList(params); + viewBox = new Rectangle2D.Double(); + if (args.length > 0) { + viewBox.x = parseNumber(args[0]); + } + if (args.length > 1) { + viewBox.y = parseNumber(args[1]); + } + if (args.length > 2) { + viewBox.width = parseNumber(args[2]); + } + if (args.length > 3) { + viewBox.height = parseNumber(args[3]); + } + + width = viewBox.width; + height = viewBox.height; + } + + if (rootElement.hasAttribute("width")) { + width = parseLength(rootElement.getAttribute("width"), width); + } + + if (rootElement.hasAttribute("height")) { + height = parseLength(rootElement.getAttribute("height"), height); + } + + if (viewBox == null) { + viewBox = new Rectangle2D.Double(); + viewBox.width = width; + viewBox.height = height; + } + + this.viewBox = viewBox; + + SvgStyle style = new SvgStyle(this, idMap, rootElement); + Matrix transform = new Matrix(); + processSvgObject(idMap, shapeNum, shapes, rootElement, transform, style); + } catch (SAXException | IOException | ParserConfigurationException ex) { + Logger.getLogger(ShapeImporter.class.getName()).log(Level.SEVERE, null, ex); + } + + shapes.shapeRecords.add(new EndShapeRecord()); + + RECT rect = st.getRect(); + int origXmin = rect.Xmin; + int origYmin = rect.Ymin; + rect.Xmin -= origXmin; + rect.Xmax -= origXmin; + rect.Ymin -= origYmin; + rect.Ymax -= origYmin; + + if (!fill && viewBox != null) { + rect.Xmin = (int) Math.round(viewBox.x * SWF.unitDivisor); + rect.Ymin = (int) Math.round(viewBox.y * SWF.unitDivisor); + rect.Xmax = (int) Math.round((viewBox.x + viewBox.width) * SWF.unitDivisor); + rect.Ymax = (int) Math.round((viewBox.y + viewBox.height) * SWF.unitDivisor); + } + + st.shapes = shapes; + st.setModified(true); + + return (Tag) st; + } + + // Generate id-element map, because getElementById does not work in some cases (namespaces?) + protected void populateIds(Element el, Map out) { + if (el.hasAttribute("id")) { + out.put(el.getAttribute("id"), el); + } + NodeList nodes = el.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + if (nodes.item(i) instanceof Element) { + populateIds((Element) nodes.item(i), out); + } + } + } + + private void processSvgObject(Map idMap, int shapeNum, SHAPEWITHSTYLE shapes, Element element, Matrix transform, SvgStyle style) { + for (int i = 0; i < element.getChildNodes().getLength(); i++) { + Node childNode = element.getChildNodes().item(i); + if (childNode instanceof Element) { + Element childElement = (Element) childNode; + String tagName = childElement.getTagName(); + SvgStyle newStyle = new SvgStyle(this, idMap, childElement); + Matrix m = Matrix.parseSvgMatrix(childElement.getAttribute("transform"), 1, 1); + Matrix m2 = m == null ? transform : transform.concatenate(m); + if ("g".equals(tagName)) { + processSvgObject(idMap, shapeNum, shapes, childElement, m2, newStyle); + } else if ("path".equals(tagName)) { + processPath(shapeNum, shapes, childElement, m2, newStyle); + } else if ("circle".equals(tagName)) { + processCircle(shapeNum, shapes, childElement, m2, newStyle); + } else if ("ellipse".equals(tagName)) { + processEllipse(shapeNum, shapes, childElement, m2, newStyle); + } else if ("rect".equals(tagName)) { + processRect(shapeNum, shapes, childElement, m2, newStyle); + } else if ("line".equals(tagName)) { + processLine(shapeNum, shapes, childElement, m2, newStyle); + } else if ("polyline".equals(tagName)) { + processPolyline(shapeNum, shapes, childElement, m2, newStyle); + } else if ("polygon".equals(tagName)) { + processPolygon(shapeNum, shapes, childElement, m2, newStyle); + } else if ("defs".equals(tagName) || "title".equals(tagName) || "desc".equals(tagName) + || "radialGradient".equals(tagName) || "linearGradient".equals(tagName)) { + // ignore + } else { + showWarning(tagName + "tagNotSupported", "The SVG tag '" + tagName + "' is not supported."); + } + } + } + } + + private void processCommands(int shapeNum, SHAPEWITHSTYLE shapes, List commands, Matrix transform, SvgStyle style) { + Matrix transform2 = transform.preConcatenate(Matrix.getScaleInstance(SWF.unitDivisor)); + Point prevPoint = new Point(0, 0); + Point startPoint = prevPoint; + double x0 = 0; + double y0 = 0; + + StyleChangeRecord scrStyle = getStyleChangeRecord(shapeNum, style); + int fillStyle = scrStyle.fillStyle1; + int lineStyle = scrStyle.lineStyle; + scrStyle.stateFillStyle0 = true; + scrStyle.stateFillStyle1 = true; + scrStyle.stateLineStyle = true; + scrStyle.fillStyle0 = 0; + scrStyle.fillStyle1 = 0; + scrStyle.lineStyle = 0; + + List newRecords = new ArrayList<>(); + + newRecords.add(scrStyle); + + LINESTYLE lineStyleObj = scrStyle.lineStyles.lineStyles.length < 1 ? null : scrStyle.lineStyles.lineStyles[0]; + LINESTYLE2 lineStyle2Obj = null; + if (lineStyleObj instanceof LINESTYLE2) { + lineStyle2Obj = (LINESTYLE2) lineStyleObj; + lineStyle2Obj.noClose = true; + } + + for (PathCommand command : commands) { + double x = x0; + double y = y0; + Point p; + char cmd = Character.toUpperCase(command.command); + switch (cmd) { + case 'M': + StyleChangeRecord scr = new StyleChangeRecord(); + if (fillStyle != 0) { + scr.stateFillStyle1 = true; + scr.fillStyle1 = fillStyle; + } + if (lineStyle != 0) { + scr.lineStyle = lineStyle; + scr.stateLineStyle = true; + } + + x = command.params[0]; + y = command.params[1]; + + p = transform2.transform(x, y); + scr.moveDeltaX = (int) Math.round(p.x); + scr.moveDeltaY = (int) Math.round(p.y); + prevPoint = p; + scr.stateMoveTo = true; + + newRecords.add(scr); + startPoint = p; + break; + case 'Z': + StraightEdgeRecord serz = new StraightEdgeRecord(); + p = startPoint; + serz.deltaX = (int) Math.round(p.x - prevPoint.x); + serz.deltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + serz.generalLineFlag = true; + newRecords.add(serz); + if (lineStyle2Obj != null) { + lineStyle2Obj.noClose = false; + } + break; + case 'L': + StraightEdgeRecord serl = new StraightEdgeRecord(); + x = command.params[0]; + y = command.params[1]; + + p = transform2.transform(x, y); + serl.deltaX = (int) Math.round(p.x - prevPoint.x); + serl.deltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + serl.generalLineFlag = true; + serl.simplify(); + newRecords.add(serl); + break; + case 'H': + StraightEdgeRecord serh = new StraightEdgeRecord(); + x = command.params[0]; + + p = transform2.transform(x, y); + serh.deltaX = (int) Math.round(p.x - prevPoint.x); + prevPoint = p; + newRecords.add(serh); + break; + case 'V': + StraightEdgeRecord serv = new StraightEdgeRecord(); + y = command.params[0]; + + p = transform2.transform(x, y); + serv.deltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + serv.vertLineFlag = true; + newRecords.add(serv); + break; + case 'Q': + CurvedEdgeRecord cer = new CurvedEdgeRecord(); + x = command.params[0]; + y = command.params[1]; + + p = transform2.transform(x, y); + cer.controlDeltaX = (int) Math.round(p.x - prevPoint.x); + cer.controlDeltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + + x = command.params[2]; + y = command.params[3]; + + p = transform2.transform(x, y); + cer.anchorDeltaX = (int) Math.round(p.x - prevPoint.x); + cer.anchorDeltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + newRecords.add(cer); + break; + case 'C': + showWarning("cubicCurvesNotSupported", "Cubic curves are not supported by Flash."); + + // create at least something... + Point pStart = prevPoint; + Point pControl1; + + x = command.params[0]; + y = command.params[1]; + + pControl1 = transform2.transform(x, y); + + x = command.params[2]; + y = command.params[3]; + + Point pControl2 = transform2.transform(x, y); + + x = command.params[4]; + y = command.params[5]; + + p = transform2.transform(x, y); + + //StraightEdgeRecord serc = new StraightEdgeRecord(); + //serc.generalLineFlag = true; + //serc.deltaX = (int) Math.round(p.x - prevPoint.x); + //serc.deltaY = (int) Math.round(p.y - prevPoint.y); + //newRecords.add(serc); + List quadCoordinates = new CubicToQuad().cubicToQuad(pStart.x, pStart.y, pControl1.x, pControl1.y, pControl2.x, pControl2.y, p.x, p.y, 1); + for (int i = 2; i < quadCoordinates.size();) { + CurvedEdgeRecord cerc = new CurvedEdgeRecord(); + p = new Point(quadCoordinates.get(i++), quadCoordinates.get(i++)); + cerc.controlDeltaX = (int) Math.round(p.x - prevPoint.x); + cerc.controlDeltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + + p = new Point(quadCoordinates.get(i++), quadCoordinates.get(i++)); + cerc.anchorDeltaX = (int) Math.round(p.x - prevPoint.x); + cerc.anchorDeltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + newRecords.add(cerc); + } + + break; + default: + Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Unknown command: {0}", command); + return; + } + + x0 = x; + y0 = y; + } + applyStyleGradients(SHAPERECORD.getBounds(newRecords), scrStyle, transform2, shapeNum, style); + shapes.shapeRecords.addAll(newRecords); + } + + private void processPath(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + String data = childElement.getAttribute("d"); + + char command = 0; + Point startPoint = new Point(0, 0); + Point prevCControlPoint = null; + Point prevQControlPoint = null; + double x0 = 0; + double y0 = 0; + + List pathCommands = new ArrayList<>(); + SvgPathReader pathReader = new SvgPathReader(data); + try { + while (pathReader.hasNext()) { + char newCommand; + if ((newCommand = pathReader.readCommand()) != 0) { + command = newCommand; + } + + boolean isRelative = Character.isLowerCase(command); + + double x = x0; + double y = y0; + + char cmd = Character.toUpperCase(command); + switch (cmd) { + case 'M': + PathCommand scr = new PathCommand(); + scr.command = 'M'; + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + scr.params = new double[]{x, y}; + + pathCommands.add(scr); + startPoint = new Point(x, y); + + command = isRelative ? 'l' : 'L'; + break; + case 'Z': + PathCommand serz = new PathCommand(); + serz.command = 'Z'; + x = startPoint.x; + y = startPoint.y; + pathCommands.add(serz); + break; + case 'L': + PathCommand serl = new PathCommand(); + serl.command = 'L'; + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + serl.params = new double[]{x, y}; + pathCommands.add(serl); + break; + case 'H': + PathCommand serh = new PathCommand(); + serh.command = 'H'; + x = pathReader.readDouble(); + if (isRelative) { + x += x0; + } + + serh.params = new double[]{x}; + pathCommands.add(serh); + break; + case 'V': + PathCommand serv = new PathCommand(); + serv.command = 'V'; + y = pathReader.readDouble(); + if (isRelative) { + y += y0; + } + + serv.params = new double[]{y}; + pathCommands.add(serv); + break; + case 'Q': + case 'T': + PathCommand cer = new PathCommand(); + cer.command = 'Q'; + + Point pControl; + if (cmd == 'Q') { + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + pControl = new Point(x, y); + } else if (prevQControlPoint != null) { + pControl = new Point(2 * x0 - prevQControlPoint.x, 2 * y0 - prevQControlPoint.y); + } else { + pControl = new Point(x0, y0); + } + + prevQControlPoint = pControl; + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + cer.params = new double[]{pControl.x, pControl.y, x, y}; + pathCommands.add(cer); + break; + case 'C': + case 'S': + showWarning("cubicCurvesNotSupported", "Cubic curves are not supported by Flash."); + + // create at least something... + Point pControl1; + if (cmd == 'C') { + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + pControl1 = new Point(x, y); + } else if (prevCControlPoint != null) { + pControl1 = new Point(2 * x0 - prevCControlPoint.x, 2 * y0 - prevCControlPoint.y); + } else { + pControl1 = new Point(x0, y0); + } + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + Point pControl2 = new Point(x, y); + prevCControlPoint = pControl2; + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + PathCommand cerc = new PathCommand(); + cerc.command = 'C'; + cerc.params = new double[]{pControl1.x, pControl1.y, pControl2.x, pControl2.y, x, y}; + pathCommands.add(cerc); + break; + case 'A': + double rx = pathReader.readDouble(); + double ry = pathReader.readDouble(); + double fi = pathReader.readDouble() * Math.PI / 180; + boolean largeFlag = (int) pathReader.readDouble() != 0; + boolean sweepFlag = (int) pathReader.readDouble() != 0; + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + if (rx == 0 || ry == 0) { + // straight line to (x, y) + PathCommand sera = new PathCommand(); + sera.command = 'L'; + sera.params = new double[]{x, y}; + pathCommands.add(sera); + } else { + rx = Math.abs(rx); + ry = Math.abs(ry); + + double x1 = x0; + double y1 = y0; + double x2 = x; + double y2 = y; + + double d1 = (x1 - x2) / 2; + double d2 = (y1 - y2) / 2; + double x1Comma = Math.cos(fi) * d1 + Math.sin(fi) * d2; + double y1Comma = -Math.sin(fi) * d1 + Math.cos(fi) * d2; + + // Correction of out-of-range radii + double lambda = x1Comma * x1Comma / (rx * rx) + y1Comma * y1Comma / (ry * ry); + if (lambda > 1) { + double sqrtLambda = Math.sqrt(lambda); + rx = sqrtLambda * rx; + ry = sqrtLambda * ry; + } + + double c = Math.sqrt((rx * rx * ry * ry - rx * rx * y1Comma * y1Comma - ry * ry * x1Comma * x1Comma) / (rx * rx * y1Comma * y1Comma + ry * ry * x1Comma * x1Comma)); + double cxComma = c * rx * y1Comma / ry; + double cyComma = c * -ry * x1Comma / rx; + + if (largeFlag == sweepFlag) { + cxComma = -cxComma; + cyComma = -cyComma; + } + + double cx = Math.cos(fi) * cxComma - Math.sin(fi) * cyComma + (x1 + x2) / 2; + double cy = Math.sin(fi) * cxComma + Math.cos(fi) * cyComma + (y1 + y2) / 2; + + double px1 = (x1Comma - cxComma) / rx; + double py1 = (y1Comma - cyComma) / ry; + double theta1 = calcAngle(1, 0, px1, py1); + + double px2 = (-x1Comma - cxComma) / rx; + double py2 = (-y1Comma - cyComma) / ry; + double deltaTheta = calcAngle(px1, py1, px2, py2); + if (sweepFlag) { + if (deltaTheta < 0) { + deltaTheta += 2 * Math.PI; + } + } else if (deltaTheta > 0) { + deltaTheta -= 2 * Math.PI; + } + + double rcp = Math.sqrt(4 - 2 * Math.sqrt(2)); + double delta = Math.signum(deltaTheta) * Math.PI / 4; + + int segmentCount = (int) Math.ceil(deltaTheta / delta); + double theta = theta1; + + PathCommand sera; + for (int i = 0; i < segmentCount - 1; i++) { + theta += delta; + /*sera = new PathCommand(); + sera.command = 'L'; + double x12 = Math.cos(theta) * rx; + double y12 = Math.sin(theta) * ry; + x1Comma = Math.cos(fi) * x12 - Math.sin(fi) * y12; + y1Comma = Math.sin(fi) * x12 + Math.cos(fi) * y12; + sera.params = new double[]{cx + x1Comma, cy + y1Comma}; + pathCommands.add(sera);*/ + + sera = new PathCommand(); + sera.command = 'Q'; + double x12 = Math.cos(theta) * rx; + double y12 = Math.sin(theta) * ry; + x1Comma = Math.cos(fi) * x12 - Math.sin(fi) * y12; + y1Comma = Math.sin(fi) * x12 + Math.cos(fi) * y12; + + double theta2 = theta - delta / 2; + x12 = Math.cos(theta2) * rx * rcp; + y12 = Math.sin(theta2) * ry * rcp; + double x1Comma2 = Math.cos(fi) * x12 - Math.sin(fi) * y12; + double y1Comma2 = Math.sin(fi) * x12 + Math.cos(fi) * y12; + sera.params = new double[]{cx + x1Comma2, cy + y1Comma2, cx + x1Comma, cy + y1Comma}; + pathCommands.add(sera); + } + + sera = new PathCommand(); + sera.command = 'Q'; + + theta += delta; + double diff = theta1 + deltaTheta - theta; + diff = -delta - diff; + theta = theta - delta - diff / 2; + + double rcpm = 1 + (rcp - 1) * (diff / delta) * (diff / delta); + double x12 = Math.cos(theta) * rx * rcpm; + double y12 = Math.sin(theta) * ry * rcpm; + x1Comma = Math.cos(fi) * x12 - Math.sin(fi) * y12; + y1Comma = Math.sin(fi) * x12 + Math.cos(fi) * y12; + sera.params = new double[]{cx + x1Comma, cy + y1Comma, x, y}; + pathCommands.add(sera); + /*sera = new PathCommand(); + sera.command = 'L'; + sera.params = new double[]{x, y}; + pathCommands.add(sera);*/ + } + break; + default: + Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Unknown command: {0}", command); + return; + } + + if (cmd != 'C' && cmd != 'S') { + prevCControlPoint = null; + } + + if (cmd != 'Q' && cmd != 'T') { + prevQControlPoint = null; + } + + x0 = x; + y0 = y; + } + } catch (NumberFormatException e) { + // ignore remaining data as specified in SVG Specification F.2 Error processing + } + + processCommands(shapeNum, shapes, pathCommands, transform, style); + } + + private double calcAngle(double ux, double uy, double vx, double vy) { + double lu = Math.sqrt(ux * ux + uy * uy); + double lv = Math.sqrt(ux * ux + uy * uy); + double sign = Math.signum(ux * vy - uy * vx); + if (sign == 0) { + sign = 1; + } + + return sign * Math.acos(ux * vx + uy * vy / (lu * lv)); + } + + private void processCircle(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + String attr = childElement.getAttribute("cx"); + double cx = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; + + attr = childElement.getAttribute("cy"); + double cy = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; + + attr = childElement.getAttribute("r"); + double r = attr.length() > 0 ? parseLength(attr, viewBox.width/* todo: how much is 100%? */) : 0; + + processEllipse(shapeNum, shapes, transform, style, cx, cy, r, r); + } + + private void processEllipse(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + String attr = childElement.getAttribute("cx"); + double cx = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; + + attr = childElement.getAttribute("cy"); + double cy = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; + + attr = childElement.getAttribute("rx"); + double rx = attr.length() > 0 ? parseLength(attr, viewBox.width) : 0; + + attr = childElement.getAttribute("ry"); + double ry = attr.length() > 0 ? parseLength(attr, viewBox.height) : 0; + + processEllipse(shapeNum, shapes, transform, style, cx, cy, rx, ry); + } + + private void processEllipse(int shapeNum, SHAPEWITHSTYLE shapes, Matrix transform, SvgStyle style, double cx, double cy, double rx, double ry) { + double sqrt2RXHalf = Math.sqrt(2) * rx / 2; + double sqrt2Minus1RX = (Math.sqrt(2) - 1) * rx; + double sqrt2RYHalf = Math.sqrt(2) * ry / 2; + double sqrt2Minus1RY = (Math.sqrt(2) - 1) * ry; + + List pathCommands = new ArrayList<>(); + PathCommand scr = new PathCommand(); + scr.command = 'M'; + scr.params = new double[]{cx + rx, cy}; + pathCommands.add(scr); + + double[] points = new double[]{ + rx, -sqrt2Minus1RY, + sqrt2RXHalf, -sqrt2RYHalf, + sqrt2Minus1RX, -ry, + 0, -ry, + -sqrt2Minus1RX, -ry, + -sqrt2RXHalf, -sqrt2RYHalf, + -rx, -sqrt2Minus1RY, + -rx, 0, + -rx, sqrt2Minus1RY, + -sqrt2RXHalf, sqrt2RYHalf, + -sqrt2Minus1RX, ry, + 0, ry, + sqrt2Minus1RX, ry, + sqrt2RXHalf, sqrt2RYHalf, + rx, sqrt2Minus1RY, + rx, 0}; + + for (int i = 0; i < points.length; i += 4) { + PathCommand cer = new PathCommand(); + cer.command = 'Q'; + cer.params = new double[]{cx + points[i], cy + points[i + 1], cx + points[i + 2], cy + points[i + 3]}; + + /*double tetha = 30; + tetha *= Math.PI / 180; + double x1 = points[i]; + double y1 = points[i + 1]; + double x2 = points[i + 2]; + double y2 = points[i + 3]; + + double x1Comma = Math.cos(tetha) * x1 + Math.sin(tetha) * y1; + double y1Comma = -Math.sin(tetha) * x1 + Math.cos(tetha) * y1; + double x2Comma = Math.cos(tetha) * x2 + Math.sin(tetha) * y2; + double y2Comma = -Math.sin(tetha) * x2 + Math.cos(tetha) * y2; + + cer.params = new double[]{cx + x1Comma, cy + y1Comma, cx + x2Comma, cy + y2Comma};*/ + pathCommands.add(cer); + } + + PathCommand serz = new PathCommand(); + serz.command = 'Z'; + pathCommands.add(serz); + + processCommands(shapeNum, shapes, pathCommands, transform, style); + } + + private void processRect(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + String attr = childElement.getAttribute("x"); + double x = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; + + attr = childElement.getAttribute("y"); + double y = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; + + attr = childElement.getAttribute("width"); + double width = attr.length() > 0 ? parseLength(attr, viewBox.width) : 0; + + attr = childElement.getAttribute("height"); + double height = attr.length() > 0 ? parseLength(attr, viewBox.height) : 0; + + attr = childElement.getAttribute("rx"); + double rx = attr.length() > 0 ? parseLength(attr, viewBox.width) : 0; + + attr = childElement.getAttribute("ry"); + double ry = attr.length() > 0 ? parseLength(attr, viewBox.height) : 0; + + if (rx == 0 && ry != 0) { + rx = ry; + } else if (rx != 0 && ry == 0) { + ry = rx; + } + + if (rx > width / 2) { + rx = width / 2; + } + + if (ry > height / 2) { + ry = height / 2; + } + + List pathCommands = new ArrayList<>(); + + if (rx > 0 || ry > 0) { + PathCommand scr = new PathCommand(); + scr.command = 'M'; + scr.params = new double[]{x + width, y + ry}; + pathCommands.add(scr); + + double sqrt2RXHalf = Math.sqrt(2) * rx / 2; + double sqrt2Minus1RX = (Math.sqrt(2) - 1) * rx; + double sqrt2RYHalf = Math.sqrt(2) * ry / 2; + double sqrt2Minus1RY = (Math.sqrt(2) - 1) * ry; + + double[] points = new double[]{ + x + width, y + ry - sqrt2Minus1RY, + x + width - rx + sqrt2RXHalf, y + ry - sqrt2RYHalf, + x + width - rx + sqrt2Minus1RX, y, + x + width - rx, y, + x + rx, y, + x + rx - sqrt2Minus1RX, y, + x + rx - sqrt2RXHalf, y + ry - sqrt2RYHalf, + x, y + ry - sqrt2Minus1RY, + x, y + ry, + x, y + height - ry, + x, y + height - ry + sqrt2Minus1RY, + x + rx - sqrt2RXHalf, y + height - ry + sqrt2RYHalf, + x + rx - sqrt2Minus1RX, y + height, + x + rx, y + height, + x + width - rx, y + height, + x + width - rx + sqrt2Minus1RX, y + height, + x + width - rx + sqrt2RXHalf, y + height - ry + sqrt2RYHalf, + x + width, y + height - ry + sqrt2Minus1RY, + x + width, y + height - ry, + x + width, y + ry}; + + for (int i = 0; i < points.length;) { + if (i % 10 == 8) { + PathCommand cer = new PathCommand(); + cer.command = 'L'; + cer.params = new double[]{points[i], points[i + 1]}; + pathCommands.add(cer); + i += 2; + } else { + PathCommand cer = new PathCommand(); + cer.command = 'Q'; + cer.params = new double[]{points[i], points[i + 1], points[i + 2], points[i + 3]}; + pathCommands.add(cer); + i += 4; + } + } + } else { + PathCommand scr = new PathCommand(); + scr.command = 'M'; + scr.params = new double[]{x, y}; + pathCommands.add(scr); + + double[] points = new double[]{ + x + width, y, + x + width, y + height, + x, y + height, + x, y}; + + for (int i = 0; i < points.length; i += 2) { + PathCommand cer = new PathCommand(); + cer.command = 'L'; + cer.params = new double[]{points[i], points[i + 1]}; + + pathCommands.add(cer); + } + } + + PathCommand serz = new PathCommand(); + serz.command = 'Z'; + pathCommands.add(serz); + + processCommands(shapeNum, shapes, pathCommands, transform, style); + } + + private void processLine(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + String attr = childElement.getAttribute("x1"); + double x1 = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; + + attr = childElement.getAttribute("y1"); + double y1 = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; + + attr = childElement.getAttribute("x2"); + double x2 = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; + + attr = childElement.getAttribute("y2"); + double y2 = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; + + List pathCommands = new ArrayList<>(); + PathCommand scr = new PathCommand(); + scr.command = 'M'; + scr.params = new double[]{x1, y1}; + pathCommands.add(scr); + + PathCommand cer = new PathCommand(); + cer.command = 'L'; + cer.params = new double[]{x2, y2}; + + pathCommands.add(cer); + + processCommands(shapeNum, shapes, pathCommands, transform, style); + } + + private void processPolyline(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + processPolyline(shapeNum, shapes, childElement, transform, style, false); + } + + private void processPolygon(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + processPolyline(shapeNum, shapes, childElement, transform, style, true); + } + + private void processPolyline(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style, boolean close) { + String data = childElement.getAttribute("points"); + + char command = 'M'; + double x0 = 0; + double y0 = 0; + + List pathCommands = new ArrayList<>(); + SvgPathReader pathReader = new SvgPathReader(data); + try { + while (pathReader.hasNext()) { + double x = x0; + double y = y0; + + Point p = null; + switch (command) { + case 'M': + PathCommand scr = new PathCommand(); + scr.command = 'M'; + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + scr.params = new double[]{x, y}; + + pathCommands.add(scr); + break; + case 'L': + PathCommand serl = new PathCommand(); + serl.command = 'L'; + x = pathReader.readDouble(); + y = pathReader.readDouble(); + serl.params = new double[]{x, y}; + pathCommands.add(serl); + break; + } + + x0 = x; + y0 = y; + command = 'L'; + } + } catch (NumberFormatException e) { + // ignore remaining data as specified in SVG Specification F.2 Error processing + } + + if (close) { + PathCommand serz = new PathCommand(); + serz.command = 'Z'; + pathCommands.add(serz); + } + + processCommands(shapeNum, shapes, pathCommands, transform, style); + } + + //Stub for w3 test. TODO: refactor and move to test directory. It's here because of easy access - compiling single file + private static void svgTest(String name) throws IOException, InterruptedException { + if (!new File(name + ".original.svg").exists()) { + URL svgUrl = new URL("http://www.w3.org/Graphics/SVG/Test/20061213/svggen/" + name + ".svg"); + byte[] svgData = Helper.readStream(svgUrl.openStream()); + Helper.writeFile(name + ".orig.svg", svgData); + + URL pngUrl = new URL("http://www.w3.org/Graphics/SVG/Test/20061213/png/full-" + name + ".png"); + byte[] pngData = Helper.readStream(pngUrl.openStream()); + Helper.writeFile(name + ".orig.png", pngData); + } + + String svgDataS = Helper.readTextFile(name + ".orig.svg"); + SWF swf = new SWF(); + DefineShape4Tag st = new DefineShape4Tag(swf); + st = (DefineShape4Tag) (new SvgImporter().importSvg(st, svgDataS)); + swf.addTag(st); + SerializableImage si = new SerializableImage(480, 360, BufferedImage.TYPE_4BYTE_ABGR); + BitmapExporter.export(swf, st.shapes, Color.yellow, si, new Matrix(), new Matrix(), null); + List li = new ArrayList<>(); + li.add(st); + ImageIO.write(si.getBufferedImage(), "PNG", new File(name + ".imported.png")); + ExportAssetsTag eat = new ExportAssetsTag(swf); + eat.tags.add(st.getCharacterId()); + eat.names.add(name); + swf.addTag(eat); + swf.assignExportNamesToSymbols(); + st.shapeBounds.Xmax = (int) (si.getWidth() * SWF.unitDivisor); + st.shapeBounds.Ymax = (int) (si.getHeight() * SWF.unitDivisor); + new ShapeExporter().exportShapes(null, "./outex/", new ReadOnlyTagList(li), new ShapeExportSettings(ShapeExportMode.SVG, 1), null); + } + + //Test for SVG + public static void main(String[] args) throws IOException, InterruptedException { +// svgTest("animate-elem-02-t"); +// svgTest("animate-elem-03-t"); +// svgTest("animate-elem-04-t"); +// svgTest("animate-elem-05-t"); +// svgTest("animate-elem-06-t"); +// svgTest("animate-elem-07-t"); +// svgTest("animate-elem-08-t"); +// svgTest("animate-elem-09-t"); +// svgTest("animate-elem-10-t"); +// svgTest("animate-elem-11-t"); +// svgTest("animate-elem-12-t"); +// svgTest("animate-elem-13-t"); +// svgTest("animate-elem-14-t"); +// svgTest("animate-elem-15-t"); +// svgTest("animate-elem-17-t"); +// svgTest("animate-elem-19-t"); +// svgTest("animate-elem-20-t"); +// svgTest("animate-elem-21-t"); +// svgTest("animate-elem-22-b"); +// svgTest("animate-elem-23-t"); +// svgTest("animate-elem-24-t"); +// svgTest("animate-elem-25-t"); +// svgTest("animate-elem-26-t"); +// svgTest("animate-elem-27-t"); +// svgTest("animate-elem-28-t"); +// svgTest("animate-elem-29-b"); +// svgTest("animate-elem-30-t"); +// svgTest("animate-elem-31-t"); +// svgTest("animate-elem-32-t"); +// svgTest("animate-elem-33-t"); +// svgTest("animate-elem-34-t"); +// svgTest("animate-elem-36-t"); +// svgTest("animate-elem-37-t"); +// svgTest("animate-elem-39-t"); +// svgTest("animate-elem-40-t"); +// svgTest("animate-elem-41-t"); +// svgTest("animate-elem-44-t"); +// svgTest("animate-elem-46-t"); +// svgTest("animate-elem-52-t"); +// svgTest("animate-elem-60-t"); +// svgTest("animate-elem-61-t"); +// svgTest("animate-elem-62-t"); +// svgTest("animate-elem-63-t"); +// svgTest("animate-elem-64-t"); +// svgTest("animate-elem-65-t"); +// svgTest("animate-elem-66-t"); +// svgTest("animate-elem-67-t"); +// svgTest("animate-elem-68-t"); +// svgTest("animate-elem-69-t"); +// svgTest("animate-elem-70-t"); +// svgTest("animate-elem-77-t"); +// svgTest("animate-elem-78-t"); +// svgTest("animate-elem-80-t"); +// svgTest("animate-elem-81-t"); +// svgTest("animate-elem-82-t"); +// svgTest("animate-elem-83-t"); +// svgTest("animate-elem-84-t"); +// svgTest("animate-elem-85-t"); + svgTest("color-prof-01-f"); + svgTest("color-prop-01-b"); + svgTest("color-prop-02-f"); + svgTest("color-prop-03-t"); + svgTest("coords-coord-01-t"); + svgTest("coords-coord-02-t"); + svgTest("coords-trans-01-b"); + svgTest("coords-trans-02-t"); + svgTest("coords-trans-03-t"); + svgTest("coords-trans-04-t"); + svgTest("coords-trans-05-t"); + svgTest("coords-trans-06-t"); + svgTest("coords-units-01-b"); + svgTest("coords-units-02-b"); + svgTest("coords-units-03-b"); + svgTest("coords-viewattr-01-b"); + svgTest("coords-viewattr-02-b"); + svgTest("coords-viewattr-03-b"); + svgTest("extend-namespace-01-f"); +// svgTest("filters-blend-01-b"); +// svgTest("filters-color-01-b"); +// svgTest("filters-composite-02-b"); +// svgTest("filters-comptran-01-b"); +// svgTest("filters-conv-01-f"); +// svgTest("filters-diffuse-01-f"); +// svgTest("filters-displace-01-f"); +// svgTest("filters-example-01-b"); +// svgTest("filters-felem-01-b"); +// svgTest("filters-gauss-01-b"); +// svgTest("filters-image-01-b"); +// svgTest("filters-light-01-f"); +// svgTest("filters-morph-01-f"); +// svgTest("filters-offset-01-b"); +// svgTest("filters-specular-01-f"); +// svgTest("filters-tile-01-b"); +// svgTest("filters-turb-01-f"); +// svgTest("fonts-desc-02-t"); +// svgTest("fonts-elem-01-t"); +// svgTest("fonts-elem-02-t"); +// svgTest("fonts-elem-03-b"); +// svgTest("fonts-elem-04-b"); +// svgTest("fonts-elem-05-t"); +// svgTest("fonts-elem-06-t"); +// svgTest("fonts-elem-07-b"); +// svgTest("fonts-glyph-02-t"); +// svgTest("fonts-glyph-03-t"); +// svgTest("fonts-glyph-04-t"); +// svgTest("fonts-kern-01-t"); +// svgTest("interact-cursor-01-f"); +// svgTest("interact-dom-01-b"); +// svgTest("interact-events-01-b"); +// svgTest("interact-order-01-b"); +// svgTest("interact-order-02-b"); +// svgTest("interact-order-03-b"); +// svgTest("interact-zoom-01-t"); +// svgTest("linking-a-01-b"); +// svgTest("linking-a-02-b"); +// svgTest("linking-a-03-b"); +// svgTest("linking-a-04-t"); +// svgTest("linking-a-05-t"); +// svgTest("linking-a-07-t"); +// svgTest("linking-uri-01-b"); +// svgTest("linking-uri-02-b"); +// svgTest("linking-uri-03-t"); +// svgTest("masking-intro-01-f"); +// svgTest("masking-mask-01-b"); +// svgTest("masking-opacity-01-b"); +// svgTest("masking-path-01-b"); +// svgTest("masking-path-02-b"); +// svgTest("masking-path-03-b"); +// svgTest("masking-path-04-b"); +// svgTest("masking-path-05-f"); +// svgTest("metadata-example-01-b"); + svgTest("painting-fill-01-t"); + svgTest("painting-fill-02-t"); + svgTest("painting-fill-03-t"); + svgTest("painting-fill-04-t"); + svgTest("painting-fill-05-b"); + svgTest("painting-marker-01-f"); + svgTest("painting-marker-02-f"); + svgTest("painting-marker-03-f"); + svgTest("painting-render-01-b"); + svgTest("painting-stroke-01-t"); + svgTest("painting-stroke-02-t"); + svgTest("painting-stroke-03-t"); + svgTest("painting-stroke-04-t"); + svgTest("painting-stroke-07-t"); + svgTest("paths-data-01-t"); + svgTest("paths-data-02-t"); + svgTest("paths-data-03-f"); + svgTest("paths-data-04-t"); + svgTest("paths-data-05-t"); + svgTest("paths-data-06-t"); + svgTest("paths-data-07-t"); + svgTest("paths-data-08-t"); + svgTest("paths-data-09-t"); + svgTest("paths-data-10-t"); + svgTest("paths-data-12-t"); + svgTest("paths-data-13-t"); + svgTest("paths-data-14-t"); + svgTest("paths-data-15-t"); + svgTest("pservers-grad-01-b"); + svgTest("pservers-grad-02-b"); + svgTest("pservers-grad-03-b"); + svgTest("pservers-grad-04-b"); + svgTest("pservers-grad-05-b"); + svgTest("pservers-grad-06-b"); + svgTest("pservers-grad-07-b"); + svgTest("pservers-grad-08-b"); + svgTest("pservers-grad-09-b"); + svgTest("pservers-grad-10-b"); + svgTest("pservers-grad-11-b"); + svgTest("pservers-grad-12-b"); + svgTest("pservers-grad-13-b"); + svgTest("pservers-grad-14-b"); + svgTest("pservers-grad-15-b"); + svgTest("pservers-grad-16-b"); + svgTest("pservers-grad-17-b"); + svgTest("pservers-grad-18-b"); + svgTest("pservers-grad-19-b"); + svgTest("pservers-pattern-01-b"); + svgTest("render-elems-01-t"); + svgTest("render-elems-02-t"); + svgTest("render-elems-03-t"); + svgTest("render-elems-06-t"); + svgTest("render-elems-07-t"); + svgTest("render-elems-08-t"); + svgTest("render-groups-01-b"); + svgTest("render-groups-03-t"); +// svgTest("script-handle-01-b"); +// svgTest("script-handle-02-b"); +// svgTest("script-handle-03-b"); +// svgTest("script-handle-04-b"); + svgTest("shapes-circle-01-t"); + svgTest("shapes-circle-02-t"); + svgTest("shapes-ellipse-01-t"); + svgTest("shapes-ellipse-02-t"); + svgTest("shapes-intro-01-t"); + svgTest("shapes-line-01-t"); + svgTest("shapes-polygon-01-t"); + svgTest("shapes-polyline-01-t"); + svgTest("shapes-rect-01-t"); + svgTest("shapes-rect-02-t"); +// svgTest("struct-cond-01-t"); +// svgTest("struct-cond-02-t"); +// svgTest("struct-cond-03-t"); +// svgTest("struct-defs-01-t"); +// svgTest("struct-dom-01-b"); +// svgTest("struct-dom-02-b"); +// svgTest("struct-dom-03-b"); +// svgTest("struct-dom-04-b"); +// svgTest("struct-dom-05-b"); +// svgTest("struct-dom-06-b"); +// svgTest("struct-frag-01-t"); +// svgTest("struct-frag-02-t"); +// svgTest("struct-frag-03-t"); +// svgTest("struct-frag-04-t"); +// svgTest("struct-frag-05-t"); +// svgTest("struct-frag-06-t"); +// svgTest("struct-group-01-t"); +// svgTest("struct-group-02-b"); +// svgTest("struct-group-03-t"); +// svgTest("struct-image-01-t"); +// svgTest("struct-image-02-b"); +// svgTest("struct-image-03-t"); +// svgTest("struct-image-04-t"); +// svgTest("struct-image-05-b"); +// svgTest("struct-image-06-t"); +// svgTest("struct-image-07-t"); +// svgTest("struct-image-08-t"); +// svgTest("struct-image-09-t"); +// svgTest("struct-image-10-t"); +// svgTest("struct-symbol-01-b"); +// svgTest("struct-use-01-t"); +// svgTest("struct-use-03-t"); +// svgTest("struct-use-05-b"); +// svgTest("styling-css-01-b"); +// svgTest("styling-css-02-b"); +// svgTest("styling-css-03-b"); +// svgTest("styling-css-04-f"); +// svgTest("styling-css-05-b"); +// svgTest("styling-css-06-b"); +// svgTest("styling-inherit-01-b"); +// svgTest("styling-pres-01-t"); +// svgTest("text-align-01-b"); +// svgTest("text-align-02-b"); +// svgTest("text-align-03-b"); +// svgTest("text-align-04-b"); +// svgTest("text-align-05-b"); +// svgTest("text-align-06-b"); +// svgTest("text-align-08-b"); +// svgTest("text-altglyph-01-b"); +// svgTest("text-deco-01-b"); +// svgTest("text-fonts-01-t"); +// svgTest("text-fonts-02-t"); +// svgTest("text-fonts-03-t"); +// svgTest("text-intro-01-t"); +// svgTest("text-intro-02-b"); +// svgTest("text-intro-03-b"); +// svgTest("text-intro-04-t"); +// svgTest("text-intro-05-t"); +// svgTest("text-path-01-b"); +// svgTest("text-spacing-01-b"); +// svgTest("text-text-01-b"); +// svgTest("text-text-03-b"); +// svgTest("text-text-04-t"); +// svgTest("text-text-05-t"); +// svgTest("text-text-06-t"); +// svgTest("text-text-07-t"); +// svgTest("text-text-08-b"); +// svgTest("text-tref-01-b"); +// svgTest("text-tselect-01-b"); +// svgTest("text-tselect-02-f"); +// svgTest("text-tspan-01-b"); +// svgTest("text-ws-01-t"); +// svgTest("text-ws-02-t"); +// svgTest("types-basicDOM-01-b"); + } + + private void applyFillGradients(SvgFill fill, FILLSTYLE fillStyle, RECT bounds, StyleChangeRecord scr, Matrix transform, int shapeNum, SvgStyle style) { + if (fill == null || fillStyle == null) { + return; + } + if (fill instanceof SvgGradient) { + SvgGradient gfill = (SvgGradient) fill; + Matrix gradientMatrix = Matrix.parseSvgMatrix(gfill.gradientTransform, SWF.unitDivisor, 1); + gradientMatrix = transform.concatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).concatenate(gradientMatrix); + fillStyle.gradientMatrix = gradientMatrix.toMATRIX(); + if (fill instanceof SvgLinearGradient) { + SvgLinearGradient lgfill = (SvgLinearGradient) fill; + fillStyle.fillStyleType = FILLSTYLE.LINEAR_GRADIENT; + fillStyle.gradient = new GRADIENT(); + double x1 = parseCoordinate(lgfill.x1, 1/* todo: how much is 100%? */); + double y1 = parseCoordinate(lgfill.y1, 1/* todo: how much is 100%? */); + double x2 = parseCoordinate(lgfill.x2, 1/* todo: how much is 100%? */); + double y2 = parseCoordinate(lgfill.y2, 1/* todo: how much is 100%? */); + + x1 = x1 * SWF.unitDivisor; + y1 = y1 * SWF.unitDivisor; + x2 = x2 * SWF.unitDivisor; + y2 = y2 * SWF.unitDivisor; + + Matrix boundingBoxMatrix = new Matrix(); + if (lgfill.gradientUnits == SvgGradientUnits.OBJECT_BOUNDING_BOX) { + boundingBoxMatrix.scaleX = (bounds.Xmax - bounds.Xmin) / SWF.unitDivisor; + boundingBoxMatrix.rotateSkew0 = 0; + boundingBoxMatrix.rotateSkew1 = 0; + boundingBoxMatrix.scaleY = (bounds.Ymax - bounds.Ymin) / SWF.unitDivisor; + boundingBoxMatrix.translateX = bounds.Xmin; + boundingBoxMatrix.translateY = bounds.Ymin; + } + + Matrix xyMatrix = new Matrix(); + xyMatrix.scaleX = x2 - x1; + xyMatrix.rotateSkew0 = y2 - y1; + xyMatrix.rotateSkew1 = -xyMatrix.rotateSkew0; + xyMatrix.scaleY = xyMatrix.scaleX; + + xyMatrix = xyMatrix.preConcatenate(boundingBoxMatrix); + + Matrix zeroStartMatrix = Matrix.getTranslateInstance(0.5, 0); + + Matrix scaleMatrix = Matrix.getScaleInstance(1 / 16384.0 / 2); + Matrix transMatrix = Matrix.getTranslateInstance(x1, y1); + + Matrix tMatrix = new Matrix(); + tMatrix = tMatrix.preConcatenate(scaleMatrix); + tMatrix = tMatrix.preConcatenate(zeroStartMatrix); + tMatrix = tMatrix.preConcatenate(xyMatrix); + + tMatrix = tMatrix.preConcatenate(transMatrix); + Point p1 = tMatrix.transform(new Point(-16384, 0)); + Point p2 = tMatrix.transform(new Point(16384, 0)); + + tMatrix = tMatrix.preConcatenate(new Matrix(fillStyle.gradientMatrix)); + fillStyle.gradientMatrix = tMatrix.toMATRIX(); + } else if (fill instanceof SvgRadialGradient) { + SvgRadialGradient rgfill = (SvgRadialGradient) fill; + double cx = parseCoordinate(rgfill.cx, 1/* todo: how much is 100%? */); + double cy = parseCoordinate(rgfill.cy, 1/* todo: how much is 100%? */); + double r = parseLength(rgfill.r, 1/* todo: how much is 100%? */); + + Matrix boundingBoxMatrix = new Matrix(); + if (rgfill.gradientUnits == SvgGradientUnits.OBJECT_BOUNDING_BOX) { + boundingBoxMatrix.scaleX = (bounds.Xmax - bounds.Xmin) / SWF.unitDivisor; + boundingBoxMatrix.rotateSkew0 = 0; + boundingBoxMatrix.rotateSkew1 = 0; + boundingBoxMatrix.scaleY = (bounds.Ymax - bounds.Ymin) / SWF.unitDivisor; + boundingBoxMatrix.translateX = bounds.Xmin; + boundingBoxMatrix.translateY = bounds.Ymin; + } + + fillStyle.gradientMatrix = Matrix.getTranslateInstance(SWF.unitDivisor * cx, SWF.unitDivisor * cy).concatenate(new Matrix(fillStyle.gradientMatrix)).concatenate(Matrix.getScaleInstance(r / 819.2)).preConcatenate(boundingBoxMatrix).toMATRIX(); + + double fx = parseCoordinate(rgfill.fx, 1/* todo: how much is 100%? */); + double fy = parseCoordinate(rgfill.fy, 1/* todo: how much is 100%? */); + if (!rgfill.fx.equals(rgfill.cx) || !rgfill.fy.equals(rgfill.cy)) { + fillStyle.fillStyleType = FILLSTYLE.FOCAL_RADIAL_GRADIENT; + fillStyle.gradient = new FOCALGRADIENT(); + FOCALGRADIENT fg = (FOCALGRADIENT) fillStyle.gradient; + double f = Math.sqrt((fx - cx) * (fx - cx) + (fy - cy) * (fy - cy)) / 819.2; + fg.focalPoint = (float) f; + } else { + fillStyle.fillStyleType = FILLSTYLE.RADIAL_GRADIENT; + fillStyle.gradient = new GRADIENT(); + } + } + switch (gfill.spreadMethod) { + case PAD: + fillStyle.gradient.spreadMode = GRADIENT.SPREAD_PAD_MODE; + break; + case REFLECT: + fillStyle.gradient.spreadMode = GRADIENT.SPREAD_REFLECT_MODE; + break; + case REPEAT: + fillStyle.gradient.spreadMode = GRADIENT.SPREAD_REPEAT_MODE; + break; + } + switch (gfill.interpolation) { + case LINEAR_RGB: + fillStyle.gradient.interpolationMode = GRADIENT.INTERPOLATION_LINEAR_RGB_MODE; + break; + case SRGB: + fillStyle.gradient.interpolationMode = GRADIENT.INTERPOLATION_RGB_MODE; + break; + } + + fillStyle.gradient.gradientRecords = new GRADRECORD[gfill.stops.size()]; + int prevRatio = -1; + for (int i = 0; i < gfill.stops.size(); i++) { + SvgStop stop = gfill.stops.get(i); + Color color = stop.color; + color = new Color(color.getRed(), color.getGreen(), color.getBlue(), (int) Math.round(color.getAlpha() * style.getOpacity())); + fillStyle.gradient.gradientRecords[i] = new GRADRECORD(); + fillStyle.gradient.gradientRecords[i].inShape3 = shapeNum >= 3; + fillStyle.gradient.gradientRecords[i].color = getRGB(shapeNum, color); + int ratio = Math.max((int) Math.round(stop.offset * 255), prevRatio + 1); + fillStyle.gradient.gradientRecords[i].ratio = ratio; + prevRatio = ratio; + if (prevRatio == 255) { + break; + } + } + } else if (fill instanceof SvgBitmapFill) { + SvgBitmapFill bfill = (SvgBitmapFill) fill; + fillStyle.fillStyleType = FILLSTYLE.REPEATING_BITMAP; + fillStyle.bitmapId = bfill.characterId; + Matrix fillMatrix = Matrix.parseSvgMatrix(bfill.patternTransform, SWF.unitDivisor, SWF.unitDivisor); + fillMatrix = transform.concatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).concatenate(fillMatrix); + fillStyle.bitmapMatrix = fillMatrix.toMATRIX(); + } + } + + private void applyStyleGradients(RECT bounds, StyleChangeRecord scr, Matrix transform, int shapeNum, SvgStyle style) { + SvgFill fill = style.getFillWithOpacity(); + if (fill != null && fill != SvgTransparentFill.INSTANCE) { + applyFillGradients(fill, scr.fillStyles.fillStyles[0], bounds, scr, transform, shapeNum, style); + } + SvgFill strokeFill = style.getStrokeFillWithOpacity(); + if (strokeFill != null) { + if (scr.lineStyles.lineStyles.length > 0 && scr.lineStyles.lineStyles[0] instanceof LINESTYLE2) { + applyFillGradients(strokeFill, ((LINESTYLE2) scr.lineStyles.lineStyles[0]).fillType, bounds, scr, transform, shapeNum, style); + } + } + } + + private StyleChangeRecord getStyleChangeRecord(int shapeNum, SvgStyle style) { + StyleChangeRecord scr = new StyleChangeRecord(); + + scr.stateNewStyles = true; + scr.fillStyles = new FILLSTYLEARRAY(); + scr.stateFillStyle1 = true; + scr.stateLineStyle = true; + SvgFill fill = style.getFillWithOpacity(); + if (fill != null && fill != SvgTransparentFill.INSTANCE) { + scr.fillStyles.fillStyles = new FILLSTYLE[1]; + scr.fillStyles.fillStyles[0] = new FILLSTYLE(); + if (fill instanceof SvgColor) { + Color colorFill = fill.toColor(); + scr.fillStyles.fillStyles[0].color = getRGB(shapeNum, colorFill); + scr.fillStyles.fillStyles[0].fillStyleType = FILLSTYLE.SOLID; + } else if (fill instanceof SvgGradient) { + //...apply in second step - applyStyleGradients + } + + scr.fillStyle1 = 1; + } else { + scr.fillStyles.fillStyles = new FILLSTYLE[0]; + scr.fillStyle1 = 0; + } + + scr.lineStyles = new LINESTYLEARRAY(); + SvgFill strokeFill = style.getStrokeFillWithOpacity(); + if (strokeFill != null && strokeFill != SvgTransparentFill.INSTANCE) { + Color lineColor = strokeFill.toColor(); + + scr.lineStyles.lineStyles = new LINESTYLE[1]; + LINESTYLE lineStyle = shapeNum <= 3 ? new LINESTYLE() : new LINESTYLE2(); + lineStyle.color = getRGB(shapeNum, lineColor); + lineStyle.width = (int) Math.round(style.getStrokeWidth() * SWF.unitDivisor); + SvgLineCap lineCap = style.getStrokeLineCap(); + SvgLineJoin lineJoin = style.getStrokeLineJoin(); + if (lineStyle instanceof LINESTYLE2) { + LINESTYLE2 lineStyle2 = (LINESTYLE2) lineStyle; + int swfCap = lineCap == SvgLineCap.BUTT ? LINESTYLE2.NO_CAP + : lineCap == SvgLineCap.ROUND ? LINESTYLE2.ROUND_CAP + : lineCap == SvgLineCap.SQUARE ? LINESTYLE2.SQUARE_CAP : 0; + lineStyle2.startCapStyle = swfCap; + lineStyle2.endCapStyle = swfCap; + if (!(strokeFill instanceof SvgColor)) { + lineStyle2.hasFillFlag = true; + lineStyle2.fillType = new FILLSTYLE(); + //...apply in second step - applyStyleGradients + } // Single color does not need fillType attribute + + int swfJoin = lineJoin == SvgLineJoin.MITER ? LINESTYLE2.MITER_JOIN + : lineJoin == SvgLineJoin.ROUND ? LINESTYLE2.ROUND_JOIN + : lineJoin == SvgLineJoin.BEVEL ? LINESTYLE2.BEVEL_JOIN : 0; + lineStyle2.joinStyle = swfJoin; + lineStyle2.miterLimitFactor = (float) style.getStrokeMiterLimit(); + } else { + if (lineCap != SvgLineCap.ROUND) { + showWarning("lineCapNotSupported", "LineCap style not supported in shape " + shapeNum); + } + if (lineJoin != SvgLineJoin.ROUND) { + showWarning("lineJoinNotSupported", "LineJoin style not supported in shape " + shapeNum); + } + } + + scr.lineStyles.lineStyles[0] = lineStyle; + scr.lineStyle = 1; + } else { + scr.lineStyles.lineStyles = new LINESTYLE[0]; + scr.lineStyle = 0; + } + + return scr; + } + + private RGB getRGB(int shapeNum, Color color) { + if (shapeNum < 3 && color.getAlpha() != 0xff) { + showWarning("transparentColorNotSupported", "Transparent color is not supported in shape " + shapeNum); + } + + return shapeNum >= 3 ? new RGBA(color) : new RGB(color); + } + + private double parseCoordinate(String value, double relativeTo) { + return parseLength(value, relativeTo); + } + + private double parseLength(String value, double relativeTo) { + if (value == null) { + throw new NumberFormatException(); + } + + value = value.toLowerCase(); + String unit = null; + if (value.endsWith("em") + || value.endsWith("ex") + || value.endsWith("px") + || value.endsWith("in") + || value.endsWith("cm") + || value.endsWith("mm") + || value.endsWith("pt") + || value.endsWith("pc")) { + unit = value.substring(value.length() - 2); + value = value.substring(0, value.length() - 2); + } else if (value.endsWith("%")) { + unit = "%"; + value = value.substring(0, value.length() - 1); + } + + double result = Double.parseDouble(value); + if (unit != null) { + switch (unit) { + case "em": + case "ex": + // todo: font things + break; + case "in": + result *= getDpi(); + break; + case "pt": + result *= getDpi() / 72; + break; + case "pc": + result *= getDpi() / 6; + break; + case "cm": + result *= getDpi() / 2.54; + break; + case "mm": + result *= getDpi() / 25.4; + break; + case "%": + result = relativeTo * result / 100; + break; + } + } + + return result; + } + + public double parseNumber(String value) { + if (value == null) { + throw new NumberFormatException(); + } + + double result = Double.parseDouble(value); + return result; + } + + public double parseNumberOrPercent(String value) { + if (value == null) { + throw new NumberFormatException(); + } + + boolean percent = value.endsWith("%"); + if (percent) { + value = value.substring(0, value.length() - 1); + } + + double result = Double.parseDouble(value); + if (percent) { + result /= 100; + } + + return result; + } + + private double getDpi() { + return 96; + + } + + class PathCommand { + + public char command; + + public double[] params; + } + + void showWarning(String name, String text) { + if (!shownWarnings.contains(name)) { + Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, text); + shownWarnings.add(name); + } + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgPathReader.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgPathReader.java index 28e6fa9c8..5f5a15bd9 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgPathReader.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgPathReader.java @@ -1,115 +1,138 @@ -/* - * 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.svg; - -/** - * - * @author JPEXS - */ -public class SvgPathReader { - - private final String str; - - private int pos; - - public SvgPathReader(String str) { - this.str = str; - } - - public boolean hasNext() { - return pos < str.length(); - } - - public char peek() { - return str.charAt(pos); - } - - public char readChar() { - char ch = str.charAt(pos); - pos++; - return ch; - } - - public char readCommand() { - if (!hasNext()) { - return 0; - } - - readWhiteSpaces(); - char ch = peek(); - char command = 0; - if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { - command = ch; - pos++; - readSeparators(); - } - - return command; - } - - public double readDouble() { - int startPos = pos; - - readWhiteSpaces(); - if (peek() == '-') { - pos++; - } - - boolean pointFound = false; - while (hasNext()) { - char ch = str.charAt(pos); - if (ch == '.') { - if (pointFound) { - break; - } - - pointFound = true; - } else if (ch >= '0' && ch <= '9') { - } else { - break; - } - - pos++; - } - - double result = Double.parseDouble(str.substring(startPos, pos)); - readSeparators(); - return result; - } - - private void readWhiteSpaces() { - while (hasNext()) { - char ch = peek(); - if (ch != ' ' && ch != '\r' && ch != '\n') { - return; - } - - readChar(); - } - } - - private void readSeparators() { - while (hasNext()) { - char ch = peek(); - if (ch != ' ' && ch != ',' && ch != '\r' && ch != '\n') { - return; - } - - readChar(); - } - } -} +/* + * 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.svg; + +/** + * + * @author JPEXS + */ +public class SvgPathReader { + + private final String str; + + private int pos; + + public SvgPathReader(String str) { + this.str = str; + } + + public boolean hasNext() { + return pos < str.length(); + } + + public char peek() { + return str.charAt(pos); + } + + public char readChar() { + char ch = str.charAt(pos); + pos++; + return ch; + } + + public char readCommand() { + if (!hasNext()) { + return 0; + } + + readWhiteSpaces(); + char ch = peek(); + char command = 0; + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { + command = ch; + pos++; + readSeparators(); + } + + return command; + } + + private void digitSequence() { + while (hasNext()) { + char ch = str.charAt(pos); + + if (ch >= '0' && ch <= '9') { + } else { + break; + } + + pos++; + } + } + + public double readDouble() { + int startPos = pos; + + readWhiteSpaces(); + char ch = str.charAt(pos); + if (ch == '-') { + pos++; + } + + digitSequence(); + ch = str.charAt(pos); + if (ch == '.') { + pos++; + digitSequence(); + } + + ch = str.charAt(pos); + if (ch == 'e') { + pos++; + ch = str.charAt(pos); + if (ch == '-') { + pos++; + } + + digitSequence(); + } + + boolean ok = false; + try { + double result = Double.parseDouble(str.substring(startPos, pos)); + readSeparators(); + ok = true; + return result; + } finally { + if (!ok) { + pos = startPos; + } + } + } + + private void readWhiteSpaces() { + while (hasNext()) { + char ch = peek(); + if (ch != ' ' && ch != '\r' && ch != '\n') { + return; + } + + readChar(); + } + } + + private void readSeparators() { + while (hasNext()) { + char ch = peek(); + if (ch != ' ' && ch != ',' && ch != '\r' && ch != '\n') { + return; + } + + readChar(); + } + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG3Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG3Tag.java index 26a3571e8..398a7d23b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG3Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG3Tag.java @@ -1,238 +1,239 @@ -/* - * 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.tags; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.helpers.ImageHelper; -import com.jpexs.decompiler.flash.tags.base.AloneTag; -import com.jpexs.decompiler.flash.tags.base.ImageTag; -import com.jpexs.decompiler.flash.tags.enums.ImageFormat; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.SerializableImage; -import java.awt.Dimension; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferInt; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 3) //Note: GIF and PNG since version -public class DefineBitsJPEG3Tag extends ImageTag implements AloneTag { - - public static final int ID = 35; - - public static final String NAME = "DefineBitsJPEG3"; - - @SWFType(BasicType.UI8) - public ByteArrayRange imageData; - - @SWFType(BasicType.UI8) - public ByteArrayRange bitmapAlphaData; - - /** - * Constructor - * - * @param swf - */ - public DefineBitsJPEG3Tag(SWF swf) { - super(swf, ID, NAME, null); - characterID = swf.getNextCharacterId(); - imageData = new ByteArrayRange(createEmptyImage()); - bitmapAlphaData = ByteArrayRange.EMPTY; - forceWriteAsLong = true; - } - - public DefineBitsJPEG3Tag(SWF swf, ByteArrayRange data, int characterID, byte[] imageData) throws IOException { - super(swf, ID, NAME, data); - this.characterID = characterID; - this.imageData = new ByteArrayRange(imageData); - bitmapAlphaData = ByteArrayRange.EMPTY; - forceWriteAsLong = true; - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public DefineBitsJPEG3Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { - super(sis.getSwf(), ID, NAME, data); - readData(sis, data, 0, false, false, false); - } - - @Override - public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { - characterID = sis.readUI16("characterID"); - long alphaDataOffset = sis.readUI32("alphaDataOffset"); - imageData = sis.readByteRangeEx(alphaDataOffset, "imageData"); - bitmapAlphaData = sis.readByteRangeEx(sis.available(), "bitmapAlphaData"); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(characterID); - sos.writeUI32(imageData.getLength()); - sos.write(imageData); - sos.write(bitmapAlphaData); - } - - private byte[] createEmptyImage() { - BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE); - ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); - ImageHelper.write(img, ImageFormat.JPEG, bitmapDataOS); - return bitmapDataOS.toByteArray(); - } - - @Override - public void setImage(byte[] data) throws IOException { - if (ImageTag.getImageFormat(data) == ImageFormat.JPEG) { - BufferedImage image = ImageHelper.read(data); - byte[] ba = new byte[image.getWidth() * image.getHeight()]; - for (int i = 0; i < ba.length; i++) { - ba[i] = (byte) 255; - } - - bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(ba)); - } else { - bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(new byte[0])); - } - - imageData = new ByteArrayRange(data); - clearCache(); - setModified(true); - } - - public byte[] getImageAlpha() throws IOException { - return SWFInputStream.uncompressByteArray(bitmapAlphaData.getRangeData()); - } - - public void setImageAlpha(byte[] data) throws IOException { - ImageFormat fmt = ImageTag.getImageFormat(imageData); - if (fmt != ImageFormat.JPEG) { - throw new IOException("Only Jpeg can have alpha channel."); - } - - Dimension dimension = getImageDimension(); - if (data == null || data.length != dimension.getWidth() * dimension.getHeight()) { - throw new IOException("Data length must match the size of the image."); - } - - bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(data)); - clearCache(); - setModified(true); - } - - @Override - public ImageFormat getImageFormat() { - ImageFormat fmt = getOriginalImageFormat(); - if (fmt == ImageFormat.JPEG && bitmapAlphaData.getLength() > 0) { - fmt = ImageFormat.PNG; //transparency - } - return fmt; - } - - @Override - public ImageFormat getOriginalImageFormat() { - return ImageTag.getImageFormat(imageData); - } - - @Override - public InputStream getOriginalImageData() { - if (bitmapAlphaData.getLength() == 0) { // No alpha - int errorLength = hasErrorHeader(imageData) ? 4 : 0; - return new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength); - } - - return null; - } - - @Override - protected SerializableImage getImage() { - try { - int errorLength = hasErrorHeader(imageData) ? 4 : 0; - ByteArrayInputStream bis = new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength); - - BufferedImage image = ImageHelper.read(bis); - if (image == null) { - Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to load image"); - return null; - } - - SerializableImage img = new SerializableImage(image); - if (bitmapAlphaData.getLength() == 0) { - return img; - } - - byte[] alphaData = getImageAlpha(); - if (alphaData.length == 0) { - return img; - } - - int width = img.getWidth(); - int height = img.getHeight(); - SerializableImage img2 = new SerializableImage(width, height, SerializableImage.TYPE_INT_ARGB_PRE); - int[] pixels = ((DataBufferInt) img.getRaster().getDataBuffer()).getData(); - int[] pixels2 = ((DataBufferInt) img2.getRaster().getDataBuffer()).getData(); - for (int i = 0; i < pixels.length; i++) { - int a = alphaData[i] & 0xff; - pixels2[i] = (pixels[i] & 0xffffff) | (a << 24); - } - - return img2; - } catch (IOException ex) { - Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to get image", ex); - } - return null; - } - - @Override - public Dimension getImageDimension() { - if (cachedImage != null) { - return new Dimension(cachedImage.getWidth(), cachedImage.getHeight()); - } - - try { - int errorLength = hasErrorHeader(imageData) ? 4 : 0; - ByteArrayInputStream bis = new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength); - return ImageHelper.getDimesion(bis); - } catch (IOException ex) { - Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to get image dimension", ex); - } - - return null; - } -} +/* + * 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.tags; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.helpers.ImageHelper; +import com.jpexs.decompiler.flash.tags.base.AloneTag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.enums.ImageFormat; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.SerializableImage; +import java.awt.Dimension; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 3) //Note: GIF and PNG since version +public class DefineBitsJPEG3Tag extends ImageTag implements AloneTag { + + public static final int ID = 35; + + public static final String NAME = "DefineBitsJPEG3"; + + @SWFType(BasicType.UI8) + public ByteArrayRange imageData; + + @SWFType(BasicType.UI8) + public ByteArrayRange bitmapAlphaData; + + /** + * Constructor + * + * @param swf + */ + public DefineBitsJPEG3Tag(SWF swf) { + super(swf, ID, NAME, null); + characterID = swf.getNextCharacterId(); + imageData = new ByteArrayRange(createEmptyImage()); + bitmapAlphaData = ByteArrayRange.EMPTY; + forceWriteAsLong = true; + } + + public DefineBitsJPEG3Tag(SWF swf, ByteArrayRange data, int characterID, byte[] imageData) throws IOException { + super(swf, ID, NAME, data); + this.characterID = characterID; + this.imageData = new ByteArrayRange(imageData); + bitmapAlphaData = ByteArrayRange.EMPTY; + forceWriteAsLong = true; + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public DefineBitsJPEG3Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + characterID = sis.readUI16("characterID"); + long alphaDataOffset = sis.readUI32("alphaDataOffset"); + imageData = sis.readByteRangeEx(alphaDataOffset, "imageData"); + bitmapAlphaData = sis.readByteRangeEx(sis.available(), "bitmapAlphaData", DumpInfoSpecialType.ZLIB_DATA, null); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(characterID); + sos.writeUI32(imageData.getLength()); + sos.write(imageData); + sos.write(bitmapAlphaData); + } + + private byte[] createEmptyImage() { + BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE); + ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); + ImageHelper.write(img, ImageFormat.JPEG, bitmapDataOS); + return bitmapDataOS.toByteArray(); + } + + @Override + public void setImage(byte[] data) throws IOException { + if (ImageTag.getImageFormat(data) == ImageFormat.JPEG) { + BufferedImage image = ImageHelper.read(data); + byte[] ba = new byte[image.getWidth() * image.getHeight()]; + for (int i = 0; i < ba.length; i++) { + ba[i] = (byte) 255; + } + + bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(ba)); + } else { + bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(new byte[0])); + } + + imageData = new ByteArrayRange(data); + clearCache(); + setModified(true); + } + + public byte[] getImageAlpha() throws IOException { + return SWFInputStream.uncompressByteArray(bitmapAlphaData.getRangeData()); + } + + public void setImageAlpha(byte[] data) throws IOException { + ImageFormat fmt = ImageTag.getImageFormat(imageData); + if (fmt != ImageFormat.JPEG) { + throw new IOException("Only Jpeg can have alpha channel."); + } + + Dimension dimension = getImageDimension(); + if (data == null || data.length != dimension.getWidth() * dimension.getHeight()) { + throw new IOException("Data length must match the size of the image."); + } + + bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(data)); + clearCache(); + setModified(true); + } + + @Override + public ImageFormat getImageFormat() { + ImageFormat fmt = getOriginalImageFormat(); + if (fmt == ImageFormat.JPEG && bitmapAlphaData.getLength() > 0) { + fmt = ImageFormat.PNG; //transparency + } + return fmt; + } + + @Override + public ImageFormat getOriginalImageFormat() { + return ImageTag.getImageFormat(imageData); + } + + @Override + public InputStream getOriginalImageData() { + if (bitmapAlphaData.getLength() == 0) { // No alpha + int errorLength = hasErrorHeader(imageData) ? 4 : 0; + return new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength); + } + + return null; + } + + @Override + protected SerializableImage getImage() { + try { + int errorLength = hasErrorHeader(imageData) ? 4 : 0; + ByteArrayInputStream bis = new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength); + + BufferedImage image = ImageHelper.read(bis); + if (image == null) { + Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to load image"); + return null; + } + + SerializableImage img = new SerializableImage(image); + if (bitmapAlphaData.getLength() == 0) { + return img; + } + + byte[] alphaData = getImageAlpha(); + if (alphaData.length == 0) { + return img; + } + + int width = img.getWidth(); + int height = img.getHeight(); + SerializableImage img2 = new SerializableImage(width, height, SerializableImage.TYPE_INT_ARGB_PRE); + int[] pixels = ((DataBufferInt) img.getRaster().getDataBuffer()).getData(); + int[] pixels2 = ((DataBufferInt) img2.getRaster().getDataBuffer()).getData(); + for (int i = 0; i < pixels.length; i++) { + int a = alphaData[i] & 0xff; + pixels2[i] = (pixels[i] & 0xffffff) | (a << 24); + } + + return img2; + } catch (IOException ex) { + Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to get image", ex); + } + return null; + } + + @Override + public Dimension getImageDimension() { + if (cachedImage != null) { + return new Dimension(cachedImage.getWidth(), cachedImage.getHeight()); + } + + try { + int errorLength = hasErrorHeader(imageData) ? 4 : 0; + ByteArrayInputStream bis = new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength); + return ImageHelper.getDimesion(bis); + } catch (IOException ex) { + Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to get image dimension", ex); + } + + return null; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG4Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG4Tag.java index faf3db015..fb4bffde8 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG4Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG4Tag.java @@ -1,238 +1,239 @@ -/* - * 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.tags; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.helpers.ImageHelper; -import com.jpexs.decompiler.flash.tags.base.AloneTag; -import com.jpexs.decompiler.flash.tags.base.ImageTag; -import com.jpexs.decompiler.flash.tags.enums.ImageFormat; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.SerializableImage; -import java.awt.Dimension; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferInt; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 10) -public class DefineBitsJPEG4Tag extends ImageTag implements AloneTag { - - public static final int ID = 90; - - public static final String NAME = "DefineBitsJPEG4"; - - @SWFType(BasicType.UI16) - public int deblockParam; - - @SWFType(BasicType.UI8) - public ByteArrayRange imageData; - - @SWFType(BasicType.UI8) - public ByteArrayRange bitmapAlphaData; - - /** - * Constructor - * - * @param swf - */ - public DefineBitsJPEG4Tag(SWF swf) { - super(swf, ID, NAME, null); - characterID = swf.getNextCharacterId(); - imageData = new ByteArrayRange(createEmptyImage()); - bitmapAlphaData = ByteArrayRange.EMPTY; - forceWriteAsLong = true; - } - - public DefineBitsJPEG4Tag(SWF swf, ByteArrayRange data, int characterID, byte[] imageData) throws IOException { - super(swf, ID, NAME, data); - this.characterID = characterID; - this.imageData = new ByteArrayRange(imageData); - bitmapAlphaData = ByteArrayRange.EMPTY; - forceWriteAsLong = true; - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public DefineBitsJPEG4Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { - super(sis.getSwf(), ID, NAME, data); - readData(sis, data, 0, false, false, false); - } - - @Override - public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { - characterID = sis.readUI16("characterID"); - long alphaDataOffset = sis.readUI32("alphaDataOffset"); - deblockParam = sis.readUI16("deblockParam"); - imageData = sis.readByteRangeEx(alphaDataOffset, "imageData"); - bitmapAlphaData = sis.readByteRangeEx(sis.available(), "bitmapAlphaData"); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(characterID); - sos.writeUI32(imageData.getLength()); - sos.writeUI16(deblockParam); - sos.write(imageData); - sos.write(bitmapAlphaData); - } - - private byte[] createEmptyImage() { - BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE); - ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); - ImageHelper.write(img, ImageFormat.JPEG, bitmapDataOS); - return bitmapDataOS.toByteArray(); - } - - @Override - public void setImage(byte[] data) throws IOException { - if (ImageTag.getImageFormat(data) == ImageFormat.JPEG) { - BufferedImage image = ImageHelper.read(data); - byte[] ba = new byte[image.getWidth() * image.getHeight()]; - for (int i = 0; i < ba.length; i++) { - ba[i] = (byte) 255; - } - - bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(ba)); - } else { - bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(new byte[0])); - } - - imageData = new ByteArrayRange(data); - clearCache(); - setModified(true); - } - - public byte[] getImageAlpha() throws IOException { - return SWFInputStream.uncompressByteArray(bitmapAlphaData.getRangeData()); - } - - public void setImageAlpha(byte[] data) throws IOException { - ImageFormat fmt = ImageTag.getImageFormat(imageData); - if (fmt != ImageFormat.JPEG) { - throw new IOException("Only Jpeg can have alpha channel."); - } - - Dimension dimension = getImageDimension(); - if (data == null || data.length != dimension.getWidth() * dimension.getHeight()) { - throw new IOException("Data length must match the size of the image."); - } - - bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(data)); - clearCache(); - setModified(true); - } - - @Override - public ImageFormat getImageFormat() { - ImageFormat fmt = getOriginalImageFormat(); - if (fmt == ImageFormat.JPEG && bitmapAlphaData.getLength() > 0) { - fmt = ImageFormat.PNG; //transparency - } - return fmt; - } - - @Override - public ImageFormat getOriginalImageFormat() { - return ImageTag.getImageFormat(imageData); - } - - @Override - public InputStream getOriginalImageData() { - if (bitmapAlphaData.getLength() == 0) { // No alpha - return new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength()); - } - - return null; - } - - @Override - protected SerializableImage getImage() { - try { - BufferedImage image = ImageHelper.read(new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength())); - if (image == null) { - Logger.getLogger(DefineBitsJPEG4Tag.class.getName()).log(Level.SEVERE, "Failed to load image"); - return null; - } - - SerializableImage img = new SerializableImage(image); - if (bitmapAlphaData.getLength() == 0) { - return img; - } - - byte[] alphaData = getImageAlpha(); - if (alphaData.length == 0) { - return img; - } - - int width = img.getWidth(); - int height = img.getHeight(); - SerializableImage img2 = new SerializableImage(width, height, SerializableImage.TYPE_INT_ARGB_PRE); - int[] pixels = ((DataBufferInt) img.getRaster().getDataBuffer()).getData(); - int[] pixels2 = ((DataBufferInt) img2.getRaster().getDataBuffer()).getData(); - for (int i = 0; i < pixels.length; i++) { - int a = alphaData[i] & 0xff; - pixels2[i] = (pixels[i] & 0xffffff) | (a << 24); - } - - return img2; - } catch (IOException ex) { - Logger.getLogger(DefineBitsJPEG4Tag.class.getName()).log(Level.SEVERE, "Failed to get image", ex); - } - return null; - } - - @Override - public Dimension getImageDimension() { - if (cachedImage != null) { - return new Dimension(cachedImage.getWidth(), cachedImage.getHeight()); - } - - try { - ByteArrayInputStream bis = new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength()); - return ImageHelper.getDimesion(bis); - } catch (IOException ex) { - Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to get image dimension", ex); - } - - return null; - } -} +/* + * 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.tags; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.helpers.ImageHelper; +import com.jpexs.decompiler.flash.tags.base.AloneTag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.enums.ImageFormat; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.SerializableImage; +import java.awt.Dimension; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 10) +public class DefineBitsJPEG4Tag extends ImageTag implements AloneTag { + + public static final int ID = 90; + + public static final String NAME = "DefineBitsJPEG4"; + + @SWFType(BasicType.UI16) + public int deblockParam; + + @SWFType(BasicType.UI8) + public ByteArrayRange imageData; + + @SWFType(BasicType.UI8) + public ByteArrayRange bitmapAlphaData; + + /** + * Constructor + * + * @param swf + */ + public DefineBitsJPEG4Tag(SWF swf) { + super(swf, ID, NAME, null); + characterID = swf.getNextCharacterId(); + imageData = new ByteArrayRange(createEmptyImage()); + bitmapAlphaData = ByteArrayRange.EMPTY; + forceWriteAsLong = true; + } + + public DefineBitsJPEG4Tag(SWF swf, ByteArrayRange data, int characterID, byte[] imageData) throws IOException { + super(swf, ID, NAME, data); + this.characterID = characterID; + this.imageData = new ByteArrayRange(imageData); + bitmapAlphaData = ByteArrayRange.EMPTY; + forceWriteAsLong = true; + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public DefineBitsJPEG4Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + characterID = sis.readUI16("characterID"); + long alphaDataOffset = sis.readUI32("alphaDataOffset"); + deblockParam = sis.readUI16("deblockParam"); + imageData = sis.readByteRangeEx(alphaDataOffset, "imageData"); + bitmapAlphaData = sis.readByteRangeEx(sis.available(), "bitmapAlphaData", DumpInfoSpecialType.ZLIB_DATA, null); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(characterID); + sos.writeUI32(imageData.getLength()); + sos.writeUI16(deblockParam); + sos.write(imageData); + sos.write(bitmapAlphaData); + } + + private byte[] createEmptyImage() { + BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE); + ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); + ImageHelper.write(img, ImageFormat.JPEG, bitmapDataOS); + return bitmapDataOS.toByteArray(); + } + + @Override + public void setImage(byte[] data) throws IOException { + if (ImageTag.getImageFormat(data) == ImageFormat.JPEG) { + BufferedImage image = ImageHelper.read(data); + byte[] ba = new byte[image.getWidth() * image.getHeight()]; + for (int i = 0; i < ba.length; i++) { + ba[i] = (byte) 255; + } + + bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(ba)); + } else { + bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(new byte[0])); + } + + imageData = new ByteArrayRange(data); + clearCache(); + setModified(true); + } + + public byte[] getImageAlpha() throws IOException { + return SWFInputStream.uncompressByteArray(bitmapAlphaData.getRangeData()); + } + + public void setImageAlpha(byte[] data) throws IOException { + ImageFormat fmt = ImageTag.getImageFormat(imageData); + if (fmt != ImageFormat.JPEG) { + throw new IOException("Only Jpeg can have alpha channel."); + } + + Dimension dimension = getImageDimension(); + if (data == null || data.length != dimension.getWidth() * dimension.getHeight()) { + throw new IOException("Data length must match the size of the image."); + } + + bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(data)); + clearCache(); + setModified(true); + } + + @Override + public ImageFormat getImageFormat() { + ImageFormat fmt = getOriginalImageFormat(); + if (fmt == ImageFormat.JPEG && bitmapAlphaData.getLength() > 0) { + fmt = ImageFormat.PNG; //transparency + } + return fmt; + } + + @Override + public ImageFormat getOriginalImageFormat() { + return ImageTag.getImageFormat(imageData); + } + + @Override + public InputStream getOriginalImageData() { + if (bitmapAlphaData.getLength() == 0) { // No alpha + return new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength()); + } + + return null; + } + + @Override + protected SerializableImage getImage() { + try { + BufferedImage image = ImageHelper.read(new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength())); + if (image == null) { + Logger.getLogger(DefineBitsJPEG4Tag.class.getName()).log(Level.SEVERE, "Failed to load image"); + return null; + } + + SerializableImage img = new SerializableImage(image); + if (bitmapAlphaData.getLength() == 0) { + return img; + } + + byte[] alphaData = getImageAlpha(); + if (alphaData.length == 0) { + return img; + } + + int width = img.getWidth(); + int height = img.getHeight(); + SerializableImage img2 = new SerializableImage(width, height, SerializableImage.TYPE_INT_ARGB_PRE); + int[] pixels = ((DataBufferInt) img.getRaster().getDataBuffer()).getData(); + int[] pixels2 = ((DataBufferInt) img2.getRaster().getDataBuffer()).getData(); + for (int i = 0; i < pixels.length; i++) { + int a = alphaData[i] & 0xff; + pixels2[i] = (pixels[i] & 0xffffff) | (a << 24); + } + + return img2; + } catch (IOException ex) { + Logger.getLogger(DefineBitsJPEG4Tag.class.getName()).log(Level.SEVERE, "Failed to get image", ex); + } + return null; + } + + @Override + public Dimension getImageDimension() { + if (cachedImage != null) { + return new Dimension(cachedImage.getWidth(), cachedImage.getHeight()); + } + + try { + ByteArrayInputStream bis = new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength()); + return ImageHelper.getDimesion(bis); + } catch (IOException ex) { + Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to get image dimension", ex); + } + + return null; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsLossless2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsLossless2Tag.java index e4a00baca..153a7300f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsLossless2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsLossless2Tag.java @@ -1,279 +1,281 @@ -/* - * 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.tags; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.helpers.ImageHelper; -import com.jpexs.decompiler.flash.tags.base.AloneTag; -import com.jpexs.decompiler.flash.tags.base.ImageTag; -import com.jpexs.decompiler.flash.tags.enums.ImageFormat; -import com.jpexs.decompiler.flash.types.ALPHABITMAPDATA; -import com.jpexs.decompiler.flash.types.ALPHACOLORMAPDATA; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.annotations.Conditional; -import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; -import com.jpexs.decompiler.flash.types.annotations.Internal; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.SerializableImage; -import java.awt.Dimension; -import java.awt.image.DataBufferInt; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 3) -public class DefineBitsLossless2Tag extends ImageTag implements AloneTag { - - public static final int ID = 36; - - public static final String NAME = "DefineBitsLossless2"; - - @SWFType(BasicType.UI8) - public int bitmapFormat; - - @SWFType(BasicType.UI16) - public int bitmapWidth; - - @SWFType(BasicType.UI16) - public int bitmapHeight; - - @SWFType(BasicType.UI8) - @Conditional(value = "bitmapFormat", options = {FORMAT_8BIT_COLORMAPPED}) - public int bitmapColorTableSize; - - public ByteArrayRange zlibBitmapData; - - public static final int FORMAT_8BIT_COLORMAPPED = 3; - - public static final int FORMAT_32BIT_ARGB = 5; - - @HideInRawEdit - private ALPHACOLORMAPDATA colorMapData; - - @HideInRawEdit - private ALPHABITMAPDATA bitmapData; - - @Internal - private boolean decompressed = false; - - /** - * Constructor - * - * @param swf - */ - public DefineBitsLossless2Tag(SWF swf) { - this(swf, null, swf.getNextCharacterId()); - } - - public DefineBitsLossless2Tag(SWF swf, ByteArrayRange data, int characterID) { - super(swf, ID, NAME, data); - this.characterID = characterID; - bitmapFormat = DefineBitsLossless2Tag.FORMAT_32BIT_ARGB; - bitmapWidth = 1; - bitmapHeight = 1; - zlibBitmapData = new ByteArrayRange(createEmptyImage()); - forceWriteAsLong = true; - } - - public DefineBitsLossless2Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { - super(sis.getSwf(), ID, NAME, data); - readData(sis, data, 0, false, false, false); - } - - @Override - public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { - characterID = sis.readUI16("characterID"); - bitmapFormat = sis.readUI8("bitmapFormat"); - bitmapWidth = sis.readUI16("bitmapWidth"); - bitmapHeight = sis.readUI16("bitmapHeight"); - if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { - bitmapColorTableSize = sis.readUI8("bitmapColorTableSize"); - } - zlibBitmapData = sis.readByteRangeEx(sis.available(), "zlibBitmapData"); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(characterID); - sos.writeUI8(bitmapFormat); - sos.writeUI16(bitmapWidth); - sos.writeUI16(bitmapHeight); - if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { - sos.writeUI8(bitmapColorTableSize); - } - sos.write(zlibBitmapData); - } - - private byte[] createEmptyImage() { - try { - ALPHABITMAPDATA bitmapData = new ALPHABITMAPDATA(); - bitmapData.bitmapPixelData = new int[]{0xff000000}; - ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); - SWFOutputStream sos = new SWFOutputStream(bitmapDataOS, getVersion()); - sos.writeALPHABITMAPDATA(bitmapData, FORMAT_32BIT_ARGB, 1, 1); - ByteArrayOutputStream zlibOS = new ByteArrayOutputStream(); - SWFOutputStream sos2 = new SWFOutputStream(zlibOS, getVersion()); - sos2.writeBytesZlib(bitmapDataOS.toByteArray()); - return zlibOS.toByteArray(); - } catch (IOException ex) { - Logger.getLogger(DefineBitsLossless2Tag.class.getName()).log(Level.SEVERE, null, ex); - } - return null; - } - - @Override - public void setImage(byte[] data) throws IOException { - SerializableImage image = new SerializableImage(ImageHelper.read(data)); - ALPHABITMAPDATA bitmapData = new ALPHABITMAPDATA(); - int width = image.getWidth(); - int height = image.getHeight(); - bitmapData.bitmapPixelData = new int[width * height]; - int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); - for (int pos = 0; pos < pixels.length; pos++) { - int argb = pixels[pos]; - int a = (argb >> 24) & 0xff; - int r = (argb >> 16) & 0xff; - int g = (argb >> 8) & 0xff; - int b = argb & 0xff; - - r = r * a / 255; - g = g * a / 255; - b = b * a / 255; - - bitmapData.bitmapPixelData[pos] = ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF); - } - - int format = FORMAT_32BIT_ARGB; - ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); - SWFOutputStream sos = new SWFOutputStream(bitmapDataOS, getVersion()); - sos.writeALPHABITMAPDATA(bitmapData, format, width, height); - ByteArrayOutputStream zlibOS = new ByteArrayOutputStream(); - SWFOutputStream sos2 = new SWFOutputStream(zlibOS, getVersion()); - sos2.writeBytesZlib(bitmapDataOS.toByteArray()); - zlibBitmapData = new ByteArrayRange(zlibOS.toByteArray()); - bitmapFormat = format; - bitmapWidth = width; - bitmapHeight = height; - decompressed = false; - clearCache(); - setModified(true); - } - - public ALPHACOLORMAPDATA getColorMapData() { - if (!decompressed) { - uncompressData(); - } - return colorMapData; - } - - public ALPHABITMAPDATA getBitmapData() { - if (!decompressed) { - uncompressData(); - } - return bitmapData; - } - - private void uncompressData() { - try { - byte[] uncompressedData = SWFInputStream.uncompressByteArray(zlibBitmapData.getArray(), zlibBitmapData.getPos(), zlibBitmapData.getLength()); - SWFInputStream sis = new SWFInputStream(swf, uncompressedData); - if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { - colorMapData = sis.readALPHACOLORMAPDATA(bitmapColorTableSize, bitmapWidth, bitmapHeight, "colorMapData"); - } else if (bitmapFormat == FORMAT_32BIT_ARGB) { - bitmapData = sis.readALPHABITMAPDATA(bitmapFormat, bitmapWidth, bitmapHeight, "bitmapData"); - } - } catch (IOException ex) { - } - decompressed = true; - } - - @Override - public ImageFormat getImageFormat() { - return ImageFormat.PNG; - } - - @Override - public ImageFormat getOriginalImageFormat() { - return ImageFormat.PNG; - } - - @Override - public InputStream getOriginalImageData() { - return null; - } - - @Override - protected SerializableImage getImage() { - SerializableImage bi = new SerializableImage(bitmapWidth, bitmapHeight, SerializableImage.TYPE_INT_ARGB_PRE); - int[] pixels = ((DataBufferInt) bi.getRaster().getDataBuffer()).getData(); - - ALPHACOLORMAPDATA colorMapData = null; - ALPHABITMAPDATA bitmapData = null; - if (bitmapFormat == DefineBitsLossless2Tag.FORMAT_8BIT_COLORMAPPED) { - colorMapData = getColorMapData(); - } - if (bitmapFormat == DefineBitsLossless2Tag.FORMAT_32BIT_ARGB) { - bitmapData = getBitmapData(); - } - int pos32aligned = 0; - int pos = 0; - for (int y = 0; y < bitmapHeight; y++) { - for (int x = 0; x < bitmapWidth; x++) { - int c = 0; - if ((bitmapFormat == DefineBitsLossless2Tag.FORMAT_8BIT_COLORMAPPED)) { - int colorTableIndex = colorMapData.colorMapPixelData[pos32aligned] & 0xff; - if (colorTableIndex < colorMapData.colorTableRGB.length) { - c = colorMapData.colorTableRGB[colorTableIndex]; - } - } - if ((bitmapFormat == DefineBitsLossless2Tag.FORMAT_32BIT_ARGB)) { - c = bitmapData.bitmapPixelData[pos]; - } - - pixels[pos] = c; - pos32aligned++; - pos++; - } - while ((pos32aligned % 4 != 0)) { - pos32aligned++; - } - } - - return bi; - } - - @Override - public Dimension getImageDimension() { - return new Dimension(bitmapWidth, bitmapHeight); - } -} +/* + * 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.tags; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.helpers.ImageHelper; +import com.jpexs.decompiler.flash.tags.base.AloneTag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.enums.ImageFormat; +import com.jpexs.decompiler.flash.types.ALPHABITMAPDATA; +import com.jpexs.decompiler.flash.types.ALPHACOLORMAPDATA; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; +import com.jpexs.decompiler.flash.types.annotations.Internal; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.SerializableImage; +import java.awt.Dimension; +import java.awt.image.DataBufferInt; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 3) +public class DefineBitsLossless2Tag extends ImageTag implements AloneTag { + + public static final int ID = 36; + + public static final String NAME = "DefineBitsLossless2"; + + @SWFType(BasicType.UI8) + public int bitmapFormat; + + @SWFType(BasicType.UI16) + public int bitmapWidth; + + @SWFType(BasicType.UI16) + public int bitmapHeight; + + @SWFType(BasicType.UI8) + @Conditional(value = "bitmapFormat", options = {FORMAT_8BIT_COLORMAPPED}) + public int bitmapColorTableSize; + + public ByteArrayRange zlibBitmapData; + + public static final int FORMAT_8BIT_COLORMAPPED = 3; + + public static final int FORMAT_32BIT_ARGB = 5; + + @HideInRawEdit + private ALPHACOLORMAPDATA colorMapData; + + @HideInRawEdit + private ALPHABITMAPDATA bitmapData; + + @Internal + private boolean decompressed = false; + + /** + * Constructor + * + * @param swf + */ + public DefineBitsLossless2Tag(SWF swf) { + this(swf, null, swf.getNextCharacterId()); + } + + public DefineBitsLossless2Tag(SWF swf, ByteArrayRange data, int characterID) { + super(swf, ID, NAME, data); + this.characterID = characterID; + bitmapFormat = DefineBitsLossless2Tag.FORMAT_32BIT_ARGB; + bitmapWidth = 1; + bitmapHeight = 1; + zlibBitmapData = new ByteArrayRange(createEmptyImage()); + forceWriteAsLong = true; + } + + public DefineBitsLossless2Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + characterID = sis.readUI16("characterID"); + bitmapFormat = sis.readUI8("bitmapFormat"); + bitmapWidth = sis.readUI16("bitmapWidth"); + bitmapHeight = sis.readUI16("bitmapHeight"); + if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { + bitmapColorTableSize = sis.readUI8("bitmapColorTableSize"); + } + + zlibBitmapData = sis.readByteRangeEx(sis.available(), "zlibBitmapData", DumpInfoSpecialType.ZLIB_DATA, null); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(characterID); + sos.writeUI8(bitmapFormat); + sos.writeUI16(bitmapWidth); + sos.writeUI16(bitmapHeight); + if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { + sos.writeUI8(bitmapColorTableSize); + } + sos.write(zlibBitmapData); + } + + private byte[] createEmptyImage() { + try { + ALPHABITMAPDATA bitmapData = new ALPHABITMAPDATA(); + bitmapData.bitmapPixelData = new int[]{0xff000000}; + ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(bitmapDataOS, getVersion()); + sos.writeALPHABITMAPDATA(bitmapData, FORMAT_32BIT_ARGB, 1, 1); + ByteArrayOutputStream zlibOS = new ByteArrayOutputStream(); + SWFOutputStream sos2 = new SWFOutputStream(zlibOS, getVersion()); + sos2.writeBytesZlib(bitmapDataOS.toByteArray()); + return zlibOS.toByteArray(); + } catch (IOException ex) { + Logger.getLogger(DefineBitsLossless2Tag.class.getName()).log(Level.SEVERE, null, ex); + } + return null; + } + + @Override + public void setImage(byte[] data) throws IOException { + SerializableImage image = new SerializableImage(ImageHelper.read(data)); + ALPHABITMAPDATA bitmapData = new ALPHABITMAPDATA(); + int width = image.getWidth(); + int height = image.getHeight(); + bitmapData.bitmapPixelData = new int[width * height]; + int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); + for (int pos = 0; pos < pixels.length; pos++) { + int argb = pixels[pos]; + int a = (argb >> 24) & 0xff; + int r = (argb >> 16) & 0xff; + int g = (argb >> 8) & 0xff; + int b = argb & 0xff; + + r = r * a / 255; + g = g * a / 255; + b = b * a / 255; + + bitmapData.bitmapPixelData[pos] = ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF); + } + + int format = FORMAT_32BIT_ARGB; + ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(bitmapDataOS, getVersion()); + sos.writeALPHABITMAPDATA(bitmapData, format, width, height); + ByteArrayOutputStream zlibOS = new ByteArrayOutputStream(); + SWFOutputStream sos2 = new SWFOutputStream(zlibOS, getVersion()); + sos2.writeBytesZlib(bitmapDataOS.toByteArray()); + zlibBitmapData = new ByteArrayRange(zlibOS.toByteArray()); + bitmapFormat = format; + bitmapWidth = width; + bitmapHeight = height; + decompressed = false; + clearCache(); + setModified(true); + } + + public ALPHACOLORMAPDATA getColorMapData() { + if (!decompressed) { + uncompressData(); + } + return colorMapData; + } + + public ALPHABITMAPDATA getBitmapData() { + if (!decompressed) { + uncompressData(); + } + return bitmapData; + } + + private void uncompressData() { + try { + byte[] uncompressedData = SWFInputStream.uncompressByteArray(zlibBitmapData.getArray(), zlibBitmapData.getPos(), zlibBitmapData.getLength()); + SWFInputStream sis = new SWFInputStream(swf, uncompressedData); + if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { + colorMapData = sis.readALPHACOLORMAPDATA(bitmapColorTableSize, bitmapWidth, bitmapHeight, "colorMapData"); + } else if (bitmapFormat == FORMAT_32BIT_ARGB) { + bitmapData = sis.readALPHABITMAPDATA(bitmapFormat, bitmapWidth, bitmapHeight, "bitmapData"); + } + } catch (IOException ex) { + } + decompressed = true; + } + + @Override + public ImageFormat getImageFormat() { + return ImageFormat.PNG; + } + + @Override + public ImageFormat getOriginalImageFormat() { + return ImageFormat.PNG; + } + + @Override + public InputStream getOriginalImageData() { + return null; + } + + @Override + protected SerializableImage getImage() { + SerializableImage bi = new SerializableImage(bitmapWidth, bitmapHeight, SerializableImage.TYPE_INT_ARGB_PRE); + int[] pixels = ((DataBufferInt) bi.getRaster().getDataBuffer()).getData(); + + ALPHACOLORMAPDATA colorMapData = null; + ALPHABITMAPDATA bitmapData = null; + if (bitmapFormat == DefineBitsLossless2Tag.FORMAT_8BIT_COLORMAPPED) { + colorMapData = getColorMapData(); + } + if (bitmapFormat == DefineBitsLossless2Tag.FORMAT_32BIT_ARGB) { + bitmapData = getBitmapData(); + } + int pos32aligned = 0; + int pos = 0; + for (int y = 0; y < bitmapHeight; y++) { + for (int x = 0; x < bitmapWidth; x++) { + int c = 0; + if ((bitmapFormat == DefineBitsLossless2Tag.FORMAT_8BIT_COLORMAPPED)) { + int colorTableIndex = colorMapData.colorMapPixelData[pos32aligned] & 0xff; + if (colorTableIndex < colorMapData.colorTableRGB.length) { + c = colorMapData.colorTableRGB[colorTableIndex]; + } + } + if ((bitmapFormat == DefineBitsLossless2Tag.FORMAT_32BIT_ARGB)) { + c = bitmapData.bitmapPixelData[pos]; + } + + pixels[pos] = c; + pos32aligned++; + pos++; + } + while ((pos32aligned % 4 != 0)) { + pos32aligned++; + } + } + + return bi; + } + + @Override + public Dimension getImageDimension() { + return new Dimension(bitmapWidth, bitmapHeight); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsLosslessTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsLosslessTag.java index 65f0b5125..fb1abc000 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsLosslessTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsLosslessTag.java @@ -1,279 +1,281 @@ -/* - * 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.tags; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.helpers.ImageHelper; -import com.jpexs.decompiler.flash.tags.base.AloneTag; -import com.jpexs.decompiler.flash.tags.base.ImageTag; -import com.jpexs.decompiler.flash.tags.enums.ImageFormat; -import com.jpexs.decompiler.flash.types.BITMAPDATA; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.COLORMAPDATA; -import com.jpexs.decompiler.flash.types.annotations.Conditional; -import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; -import com.jpexs.decompiler.flash.types.annotations.Internal; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.SerializableImage; -import java.awt.Dimension; -import java.awt.image.DataBufferInt; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 2) -public class DefineBitsLosslessTag extends ImageTag implements AloneTag { - - public static final int ID = 20; - - public static final String NAME = "DefineBitsLossless"; - - @SWFType(BasicType.UI8) - public int bitmapFormat; - - @SWFType(BasicType.UI16) - public int bitmapWidth; - - @SWFType(BasicType.UI16) - public int bitmapHeight; - - @SWFType(BasicType.UI8) - @Conditional(value = "bitmapFormat", options = {FORMAT_8BIT_COLORMAPPED}) - public int bitmapColorTableSize; - - public ByteArrayRange zlibBitmapData; - - public static final int FORMAT_8BIT_COLORMAPPED = 3; - - public static final int FORMAT_15BIT_RGB = 4; - - public static final int FORMAT_24BIT_RGB = 5; - - @HideInRawEdit - private COLORMAPDATA colorMapData; - - @HideInRawEdit - private BITMAPDATA bitmapData; - - @Internal - private boolean decompressed = false; - - /** - * Constructor - * - * @param swf - */ - public DefineBitsLosslessTag(SWF swf) { - this(swf, null, swf.getNextCharacterId()); - } - - public DefineBitsLosslessTag(SWF swf, ByteArrayRange data, int characterID) { - super(swf, ID, NAME, data); - this.characterID = characterID; - bitmapFormat = DefineBitsLosslessTag.FORMAT_24BIT_RGB; - bitmapWidth = 1; - bitmapHeight = 1; - zlibBitmapData = new ByteArrayRange(createEmptyImage()); - forceWriteAsLong = true; - } - - public DefineBitsLosslessTag(SWFInputStream sis, ByteArrayRange data) throws IOException { - super(sis.getSwf(), ID, NAME, data); - readData(sis, data, 0, false, false, false); - } - - @Override - public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { - characterID = sis.readUI16("characterID"); - bitmapFormat = sis.readUI8("bitmapFormat"); - bitmapWidth = sis.readUI16("bitmapWidth"); - bitmapHeight = sis.readUI16("bitmapHeight"); - if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { - bitmapColorTableSize = sis.readUI8("bitmapColorTableSize"); - } - zlibBitmapData = sis.readByteRangeEx(sis.available(), "zlibBitmapData"); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(characterID); - sos.writeUI8(bitmapFormat); - sos.writeUI16(bitmapWidth); - sos.writeUI16(bitmapHeight); - if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { - sos.writeUI8(bitmapColorTableSize); - } - sos.write(zlibBitmapData); - } - - private byte[] createEmptyImage() { - try { - BITMAPDATA bitmapData = new BITMAPDATA(); - bitmapData.bitmapPixelDataPix24 = new int[]{0xff000000}; - ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); - SWFOutputStream sos = new SWFOutputStream(bitmapDataOS, getVersion()); - sos.writeBITMAPDATA(bitmapData, FORMAT_24BIT_RGB, 1, 1); - ByteArrayOutputStream zlibOS = new ByteArrayOutputStream(); - SWFOutputStream sos2 = new SWFOutputStream(zlibOS, getVersion()); - sos2.writeBytesZlib(bitmapDataOS.toByteArray()); - return zlibOS.toByteArray(); - } catch (IOException ex) { - Logger.getLogger(DefineBitsLosslessTag.class.getName()).log(Level.SEVERE, null, ex); - } - return null; - } - - @Override - public void setImage(byte[] data) throws IOException { - SerializableImage image = new SerializableImage(ImageHelper.read(data)); - int width = image.getWidth(); - int height = image.getHeight(); - bitmapData = new BITMAPDATA(); - bitmapData.bitmapPixelDataPix24 = new int[width * height]; - int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); - for (int pos = 0; pos < pixels.length; pos++) { - // set the reserved bits to 0xff, because: - // documentation says 0, but image is sometimes broken with 0, so there is 0xff, which works (maybe alpha?) - int argb = pixels[pos] | 0xff000000; - bitmapData.bitmapPixelDataPix24[pos] = argb; - } - - int format = FORMAT_24BIT_RGB; - ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); - SWFOutputStream sos = new SWFOutputStream(bitmapDataOS, getVersion()); - sos.writeBITMAPDATA(bitmapData, format, width, height); - ByteArrayOutputStream zlibOS = new ByteArrayOutputStream(); - SWFOutputStream sos2 = new SWFOutputStream(zlibOS, getVersion()); - sos2.writeBytesZlib(bitmapDataOS.toByteArray()); - zlibBitmapData = new ByteArrayRange(zlibOS.toByteArray()); - bitmapFormat = format; - bitmapWidth = width; - bitmapHeight = height; - decompressed = false; - clearCache(); - setModified(true); - } - - public COLORMAPDATA getColorMapData() { - if (!decompressed) { - uncompressData(); - } - return colorMapData; - } - - public BITMAPDATA getBitmapData() { - if (!decompressed) { - uncompressData(); - } - return bitmapData; - } - - private void uncompressData() { - try { - byte[] uncompressedData = SWFInputStream.uncompressByteArray(zlibBitmapData.getArray(), zlibBitmapData.getPos(), zlibBitmapData.getLength()); - SWFInputStream sis = new SWFInputStream(swf, uncompressedData); - if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { - colorMapData = sis.readCOLORMAPDATA(bitmapColorTableSize, bitmapWidth, bitmapHeight, "colorMapData"); - } else if ((bitmapFormat == FORMAT_15BIT_RGB) || (bitmapFormat == FORMAT_24BIT_RGB)) { - bitmapData = sis.readBITMAPDATA(bitmapFormat, bitmapWidth, bitmapHeight, "bitmapData"); - } - } catch (IOException ex) { - } - decompressed = true; - } - - @Override - public ImageFormat getImageFormat() { - return ImageFormat.PNG; - } - - @Override - public ImageFormat getOriginalImageFormat() { - return ImageFormat.PNG; - } - - @Override - public InputStream getOriginalImageData() { - return null; - } - - @Override - protected SerializableImage getImage() { - int[] pixels = new int[bitmapWidth * bitmapHeight]; - if (bitmapFormat == DefineBitsLosslessTag.FORMAT_8BIT_COLORMAPPED) { - COLORMAPDATA colorMapData = getColorMapData(); - int pos32aligned = 0; - int pos = 0; - for (int y = 0; y < bitmapHeight; y++) { - for (int x = 0; x < bitmapWidth; x++) { - int c = 0; - int colorTableIndex = colorMapData.colorMapPixelData[pos32aligned] & 0xff; - if (colorTableIndex < colorMapData.colorTableRGB.length) { - c = colorMapData.colorTableRGB[colorTableIndex]; - } - - pixels[pos++] = c; - pos32aligned++; - } - - while ((pos32aligned % 4 != 0)) { - pos32aligned++; - } - } - } else if ((bitmapFormat == DefineBitsLosslessTag.FORMAT_15BIT_RGB) || (bitmapFormat == DefineBitsLosslessTag.FORMAT_24BIT_RGB)) { - BITMAPDATA bitmapData = getBitmapData(); - int pos = 0; - int[] bitmapPixelData = null; - if (bitmapFormat == DefineBitsLosslessTag.FORMAT_15BIT_RGB) { - bitmapPixelData = bitmapData.bitmapPixelDataPix15; - } else if (bitmapFormat == DefineBitsLosslessTag.FORMAT_24BIT_RGB) { - bitmapPixelData = bitmapData.bitmapPixelDataPix24; - } - - for (int y = 0; y < bitmapHeight; y++) { - for (int x = 0; x < bitmapWidth; x++) { - int c = bitmapPixelData[pos] | 0xff000000; - pixels[pos++] = c; - } - } - } - - SerializableImage bi = new SerializableImage(bitmapWidth, bitmapHeight, SerializableImage.TYPE_INT_RGB, pixels); - return bi; - } - - @Override - public Dimension getImageDimension() { - return new Dimension(bitmapWidth, bitmapHeight); - } -} +/* + * 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.tags; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.helpers.ImageHelper; +import com.jpexs.decompiler.flash.tags.base.AloneTag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.enums.ImageFormat; +import com.jpexs.decompiler.flash.types.BITMAPDATA; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.COLORMAPDATA; +import com.jpexs.decompiler.flash.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; +import com.jpexs.decompiler.flash.types.annotations.Internal; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.SerializableImage; +import java.awt.Dimension; +import java.awt.image.DataBufferInt; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 2) +public class DefineBitsLosslessTag extends ImageTag implements AloneTag { + + public static final int ID = 20; + + public static final String NAME = "DefineBitsLossless"; + + @SWFType(BasicType.UI8) + public int bitmapFormat; + + @SWFType(BasicType.UI16) + public int bitmapWidth; + + @SWFType(BasicType.UI16) + public int bitmapHeight; + + @SWFType(BasicType.UI8) + @Conditional(value = "bitmapFormat", options = {FORMAT_8BIT_COLORMAPPED}) + public int bitmapColorTableSize; + + public ByteArrayRange zlibBitmapData; + + public static final int FORMAT_8BIT_COLORMAPPED = 3; + + public static final int FORMAT_15BIT_RGB = 4; + + public static final int FORMAT_24BIT_RGB = 5; + + @HideInRawEdit + private COLORMAPDATA colorMapData; + + @HideInRawEdit + private BITMAPDATA bitmapData; + + @Internal + private boolean decompressed = false; + + /** + * Constructor + * + * @param swf + */ + public DefineBitsLosslessTag(SWF swf) { + this(swf, null, swf.getNextCharacterId()); + } + + public DefineBitsLosslessTag(SWF swf, ByteArrayRange data, int characterID) { + super(swf, ID, NAME, data); + this.characterID = characterID; + bitmapFormat = DefineBitsLosslessTag.FORMAT_24BIT_RGB; + bitmapWidth = 1; + bitmapHeight = 1; + zlibBitmapData = new ByteArrayRange(createEmptyImage()); + forceWriteAsLong = true; + } + + public DefineBitsLosslessTag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + characterID = sis.readUI16("characterID"); + bitmapFormat = sis.readUI8("bitmapFormat"); + bitmapWidth = sis.readUI16("bitmapWidth"); + bitmapHeight = sis.readUI16("bitmapHeight"); + if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { + bitmapColorTableSize = sis.readUI8("bitmapColorTableSize"); + } + + zlibBitmapData = sis.readByteRangeEx(sis.available(), "zlibBitmapData", DumpInfoSpecialType.ZLIB_DATA, null); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(characterID); + sos.writeUI8(bitmapFormat); + sos.writeUI16(bitmapWidth); + sos.writeUI16(bitmapHeight); + if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { + sos.writeUI8(bitmapColorTableSize); + } + sos.write(zlibBitmapData); + } + + private byte[] createEmptyImage() { + try { + BITMAPDATA bitmapData = new BITMAPDATA(); + bitmapData.bitmapPixelDataPix24 = new int[]{0xff000000}; + ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(bitmapDataOS, getVersion()); + sos.writeBITMAPDATA(bitmapData, FORMAT_24BIT_RGB, 1, 1); + ByteArrayOutputStream zlibOS = new ByteArrayOutputStream(); + SWFOutputStream sos2 = new SWFOutputStream(zlibOS, getVersion()); + sos2.writeBytesZlib(bitmapDataOS.toByteArray()); + return zlibOS.toByteArray(); + } catch (IOException ex) { + Logger.getLogger(DefineBitsLosslessTag.class.getName()).log(Level.SEVERE, null, ex); + } + return null; + } + + @Override + public void setImage(byte[] data) throws IOException { + SerializableImage image = new SerializableImage(ImageHelper.read(data)); + int width = image.getWidth(); + int height = image.getHeight(); + bitmapData = new BITMAPDATA(); + bitmapData.bitmapPixelDataPix24 = new int[width * height]; + int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); + for (int pos = 0; pos < pixels.length; pos++) { + // set the reserved bits to 0xff, because: + // documentation says 0, but image is sometimes broken with 0, so there is 0xff, which works (maybe alpha?) + int argb = pixels[pos] | 0xff000000; + bitmapData.bitmapPixelDataPix24[pos] = argb; + } + + int format = FORMAT_24BIT_RGB; + ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(bitmapDataOS, getVersion()); + sos.writeBITMAPDATA(bitmapData, format, width, height); + ByteArrayOutputStream zlibOS = new ByteArrayOutputStream(); + SWFOutputStream sos2 = new SWFOutputStream(zlibOS, getVersion()); + sos2.writeBytesZlib(bitmapDataOS.toByteArray()); + zlibBitmapData = new ByteArrayRange(zlibOS.toByteArray()); + bitmapFormat = format; + bitmapWidth = width; + bitmapHeight = height; + decompressed = false; + clearCache(); + setModified(true); + } + + public COLORMAPDATA getColorMapData() { + if (!decompressed) { + uncompressData(); + } + return colorMapData; + } + + public BITMAPDATA getBitmapData() { + if (!decompressed) { + uncompressData(); + } + return bitmapData; + } + + private void uncompressData() { + try { + byte[] uncompressedData = SWFInputStream.uncompressByteArray(zlibBitmapData.getArray(), zlibBitmapData.getPos(), zlibBitmapData.getLength()); + SWFInputStream sis = new SWFInputStream(swf, uncompressedData); + if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { + colorMapData = sis.readCOLORMAPDATA(bitmapColorTableSize, bitmapWidth, bitmapHeight, "colorMapData"); + } else if ((bitmapFormat == FORMAT_15BIT_RGB) || (bitmapFormat == FORMAT_24BIT_RGB)) { + bitmapData = sis.readBITMAPDATA(bitmapFormat, bitmapWidth, bitmapHeight, "bitmapData"); + } + } catch (IOException ex) { + } + decompressed = true; + } + + @Override + public ImageFormat getImageFormat() { + return ImageFormat.PNG; + } + + @Override + public ImageFormat getOriginalImageFormat() { + return ImageFormat.PNG; + } + + @Override + public InputStream getOriginalImageData() { + return null; + } + + @Override + protected SerializableImage getImage() { + int[] pixels = new int[bitmapWidth * bitmapHeight]; + if (bitmapFormat == DefineBitsLosslessTag.FORMAT_8BIT_COLORMAPPED) { + COLORMAPDATA colorMapData = getColorMapData(); + int pos32aligned = 0; + int pos = 0; + for (int y = 0; y < bitmapHeight; y++) { + for (int x = 0; x < bitmapWidth; x++) { + int c = 0; + int colorTableIndex = colorMapData.colorMapPixelData[pos32aligned] & 0xff; + if (colorTableIndex < colorMapData.colorTableRGB.length) { + c = colorMapData.colorTableRGB[colorTableIndex]; + } + + pixels[pos++] = c; + pos32aligned++; + } + + while ((pos32aligned % 4 != 0)) { + pos32aligned++; + } + } + } else if ((bitmapFormat == DefineBitsLosslessTag.FORMAT_15BIT_RGB) || (bitmapFormat == DefineBitsLosslessTag.FORMAT_24BIT_RGB)) { + BITMAPDATA bitmapData = getBitmapData(); + int pos = 0; + int[] bitmapPixelData = null; + if (bitmapFormat == DefineBitsLosslessTag.FORMAT_15BIT_RGB) { + bitmapPixelData = bitmapData.bitmapPixelDataPix15; + } else if (bitmapFormat == DefineBitsLosslessTag.FORMAT_24BIT_RGB) { + bitmapPixelData = bitmapData.bitmapPixelDataPix24; + } + + for (int y = 0; y < bitmapHeight; y++) { + for (int x = 0; x < bitmapWidth; x++) { + int c = bitmapPixelData[pos] | 0xff000000; + pixels[pos++] = c; + } + } + } + + SerializableImage bi = new SerializableImage(bitmapWidth, bitmapHeight, SerializableImage.TYPE_INT_RGB, pixels); + return bi; + } + + @Override + public Dimension getImageDimension() { + return new Dimension(bitmapWidth, bitmapHeight); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java index 771e50995..d9c92028d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java @@ -1,290 +1,291 @@ -/* - * 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.tags; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.action.Action; -import com.jpexs.decompiler.flash.tags.base.ASMSourceContainer; -import com.jpexs.decompiler.flash.tags.base.BoundedTag; -import com.jpexs.decompiler.flash.tags.base.ButtonAction; -import com.jpexs.decompiler.flash.tags.base.ButtonTag; -import com.jpexs.decompiler.flash.tags.base.CharacterTag; -import com.jpexs.decompiler.flash.timeline.DepthState; -import com.jpexs.decompiler.flash.timeline.Frame; -import com.jpexs.decompiler.flash.timeline.Timeline; -import com.jpexs.decompiler.flash.types.BUTTONRECORD; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.ColorTransform; -import com.jpexs.decompiler.flash.types.MATRIX; -import com.jpexs.decompiler.flash.types.RECT; -import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.Cache; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; - -/** - * Defines a button character - * - * @author JPEXS - */ -@SWFVersion(from = 1) -public class DefineButtonTag extends ButtonTag implements ASMSourceContainer { - - public static final int ID = 7; - - public static final String NAME = "DefineButton"; - - /** - * ID for this character - */ - @SWFType(BasicType.UI16) - public int buttonId; - - /** - * Characters that make up the button - */ - public List characters; - - /** - * Actions to perform - */ - @HideInRawEdit - public ByteArrayRange actionBytes; - - /** - * Constructor - * - * @param swf - */ - public DefineButtonTag(SWF swf) { - super(swf, ID, NAME, null); - buttonId = swf.getNextCharacterId(); - characters = new ArrayList<>(); - actionBytes = ByteArrayRange.EMPTY; - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public DefineButtonTag(SWFInputStream sis, ByteArrayRange data) throws IOException { - super(sis.getSwf(), ID, NAME, data); - readData(sis, data, 0, false, false, false); - } - - @Override - public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { - buttonId = sis.readUI16("buttonId"); - characters = sis.readBUTTONRECORDList(false, "characters"); - actionBytes = sis.readByteRangeEx(sis.available(), "actionBytes"); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(buttonId); - sos.writeBUTTONRECORDList(characters, false); - sos.write(getActionBytes()); - } - - @Override - public int getCharacterId() { - return buttonId; - } - - @Override - public void setCharacterId(int characterId) { - this.buttonId = characterId; - } - - @Override - public List getRecords() { - return characters; - } - - @Override - public List getSubItems() { - return Arrays.asList(new ButtonAction(this)); - } - - public void setActions(List actions) { - actionBytes = Action.actionsToByteArrayRange(actions, true, swf.version); - } - - public ByteArrayRange getActionBytes() { - return actionBytes; - } - - public void setActionBytes(byte[] actionBytes) { - this.actionBytes = new ByteArrayRange(actionBytes); - } - - public void setModified() { - setModified(true); - } - - @Override - public boolean replaceCharacter(int oldCharacterId, int newCharacterId) { - boolean modified = false; - for (int i = 0; i < characters.size(); i++) { - BUTTONRECORD character = characters.get(i); - if (character.characterId == oldCharacterId) { - character.characterId = newCharacterId; - modified = true; - } - } - if (modified) { - setModified(true); - } - return modified; - } - - @Override - public boolean removeCharacter(int characterId) { - boolean modified = false; - for (int i = 0; i < characters.size(); i++) { - if (characters.get(i).characterId == characterId) { - characters.remove(i); - modified = true; - i--; - } - } - if (modified) { - setModified(true); - } - return modified; - } - - @Override - public RECT getRect(Set added) { - Cache cache = swf == null ? null : swf.getRectCache(); - RECT ret = cache == null ? null : cache.get(this); - if (ret != null) { - return ret; - } - - RECT rect = new RECT(Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE); - for (BUTTONRECORD r : characters) { - CharacterTag ch = swf.getCharacter(r.characterId); - if (ch instanceof BoundedTag) { - BoundedTag bt = (BoundedTag) ch; - if (!added.contains(bt)) { - added.add(bt); - RECT r2 = bt.getRect(added); - added.remove(bt); - MATRIX mat = r.placeMatrix; - if (mat != null) { - r2 = mat.apply(r2); - } - rect.Xmin = Math.min(r2.Xmin, rect.Xmin); - rect.Ymin = Math.min(r2.Ymin, rect.Ymin); - rect.Xmax = Math.max(r2.Xmax, rect.Xmax); - rect.Ymax = Math.max(r2.Ymax, rect.Ymax); - } - } - } - - if (cache != null) { - cache.put(this, rect); - } - - return rect; - } - - @Override - public boolean trackAsMenu() { - return false; - } - - @Override - public int getNumFrames() { - return 1; - } - - @Override - protected void initTimeline(Timeline timeline) { - DefineButtonCxformTag cxformTag = (DefineButtonCxformTag) swf.getCharacterIdTag(buttonId, DefineButtonCxformTag.ID); - ColorTransform clrTrans = cxformTag == null ? null : cxformTag.buttonColorTransform; - int maxDepth = 0; - Frame frameUp = new Frame(timeline, 0); - Frame frameDown = new Frame(timeline, 0); - Frame frameOver = new Frame(timeline, 0); - Frame frameHit = new Frame(timeline, 0); - for (BUTTONRECORD r : this.characters) { - - DepthState layer = new DepthState(swf, null); - layer.colorTransForm = clrTrans; - layer.blendMode = r.blendMode; - layer.filters = r.filterList; - layer.matrix = r.placeMatrix; - layer.characterId = r.characterId; - if (r.placeDepth > maxDepth) { - maxDepth = r.placeDepth; - } - - if (r.buttonStateUp) { - frameUp.layers.put(r.placeDepth, new DepthState(layer, frameUp, false)); - } - if (r.buttonStateDown) { - frameDown.layers.put(r.placeDepth, new DepthState(layer, frameDown, false)); - } - if (r.buttonStateOver) { - frameOver.layers.put(r.placeDepth, new DepthState(layer, frameOver, false)); - } - if (r.buttonStateHitTest) { - frameHit.layers.put(r.placeDepth, new DepthState(layer, frameHit, false)); - } - - } - - timeline.addFrame(frameUp); - - if (frameOver.layers.isEmpty()) { - frameOver = frameUp; - } - - timeline.addFrame(frameOver); - - if (frameDown.layers.isEmpty()) { - frameDown = frameOver; - } - - timeline.addFrame(frameDown); - - if (frameHit.layers.isEmpty()) { - frameHit = frameUp; - } - - timeline.addFrame(frameHit); - } -} +/* + * 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.tags; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.action.Action; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.tags.base.ASMSourceContainer; +import com.jpexs.decompiler.flash.tags.base.BoundedTag; +import com.jpexs.decompiler.flash.tags.base.ButtonAction; +import com.jpexs.decompiler.flash.tags.base.ButtonTag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.timeline.DepthState; +import com.jpexs.decompiler.flash.timeline.Frame; +import com.jpexs.decompiler.flash.timeline.Timeline; +import com.jpexs.decompiler.flash.types.BUTTONRECORD; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.MATRIX; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.Cache; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +/** + * Defines a button character + * + * @author JPEXS + */ +@SWFVersion(from = 1) +public class DefineButtonTag extends ButtonTag implements ASMSourceContainer { + + public static final int ID = 7; + + public static final String NAME = "DefineButton"; + + /** + * ID for this character + */ + @SWFType(BasicType.UI16) + public int buttonId; + + /** + * Characters that make up the button + */ + public List characters; + + /** + * Actions to perform + */ + @HideInRawEdit + public ByteArrayRange actionBytes; + + /** + * Constructor + * + * @param swf + */ + public DefineButtonTag(SWF swf) { + super(swf, ID, NAME, null); + buttonId = swf.getNextCharacterId(); + characters = new ArrayList<>(); + actionBytes = ByteArrayRange.EMPTY; + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public DefineButtonTag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + buttonId = sis.readUI16("buttonId"); + characters = sis.readBUTTONRECORDList(false, "characters"); + actionBytes = sis.readByteRangeEx(sis.available(), "actionBytes", DumpInfoSpecialType.ACTION_BYTES, sis.getPos()); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(buttonId); + sos.writeBUTTONRECORDList(characters, false); + sos.write(getActionBytes()); + } + + @Override + public int getCharacterId() { + return buttonId; + } + + @Override + public void setCharacterId(int characterId) { + this.buttonId = characterId; + } + + @Override + public List getRecords() { + return characters; + } + + @Override + public List getSubItems() { + return Arrays.asList(new ButtonAction(this)); + } + + public void setActions(List actions) { + actionBytes = Action.actionsToByteArrayRange(actions, true, swf.version); + } + + public ByteArrayRange getActionBytes() { + return actionBytes; + } + + public void setActionBytes(byte[] actionBytes) { + this.actionBytes = new ByteArrayRange(actionBytes); + } + + public void setModified() { + setModified(true); + } + + @Override + public boolean replaceCharacter(int oldCharacterId, int newCharacterId) { + boolean modified = false; + for (int i = 0; i < characters.size(); i++) { + BUTTONRECORD character = characters.get(i); + if (character.characterId == oldCharacterId) { + character.characterId = newCharacterId; + modified = true; + } + } + if (modified) { + setModified(true); + } + return modified; + } + + @Override + public boolean removeCharacter(int characterId) { + boolean modified = false; + for (int i = 0; i < characters.size(); i++) { + if (characters.get(i).characterId == characterId) { + characters.remove(i); + modified = true; + i--; + } + } + if (modified) { + setModified(true); + } + return modified; + } + + @Override + public RECT getRect(Set added) { + Cache cache = swf == null ? null : swf.getRectCache(); + RECT ret = cache == null ? null : cache.get(this); + if (ret != null) { + return ret; + } + + RECT rect = new RECT(Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE); + for (BUTTONRECORD r : characters) { + CharacterTag ch = swf.getCharacter(r.characterId); + if (ch instanceof BoundedTag) { + BoundedTag bt = (BoundedTag) ch; + if (!added.contains(bt)) { + added.add(bt); + RECT r2 = bt.getRect(added); + added.remove(bt); + MATRIX mat = r.placeMatrix; + if (mat != null) { + r2 = mat.apply(r2); + } + rect.Xmin = Math.min(r2.Xmin, rect.Xmin); + rect.Ymin = Math.min(r2.Ymin, rect.Ymin); + rect.Xmax = Math.max(r2.Xmax, rect.Xmax); + rect.Ymax = Math.max(r2.Ymax, rect.Ymax); + } + } + } + + if (cache != null) { + cache.put(this, rect); + } + + return rect; + } + + @Override + public boolean trackAsMenu() { + return false; + } + + @Override + public int getNumFrames() { + return 1; + } + + @Override + protected void initTimeline(Timeline timeline) { + DefineButtonCxformTag cxformTag = (DefineButtonCxformTag) swf.getCharacterIdTag(buttonId, DefineButtonCxformTag.ID); + ColorTransform clrTrans = cxformTag == null ? null : cxformTag.buttonColorTransform; + int maxDepth = 0; + Frame frameUp = new Frame(timeline, 0); + Frame frameDown = new Frame(timeline, 0); + Frame frameOver = new Frame(timeline, 0); + Frame frameHit = new Frame(timeline, 0); + for (BUTTONRECORD r : this.characters) { + + DepthState layer = new DepthState(swf, null); + layer.colorTransForm = clrTrans; + layer.blendMode = r.blendMode; + layer.filters = r.filterList; + layer.matrix = r.placeMatrix; + layer.characterId = r.characterId; + if (r.placeDepth > maxDepth) { + maxDepth = r.placeDepth; + } + + if (r.buttonStateUp) { + frameUp.layers.put(r.placeDepth, new DepthState(layer, frameUp, false)); + } + if (r.buttonStateDown) { + frameDown.layers.put(r.placeDepth, new DepthState(layer, frameDown, false)); + } + if (r.buttonStateOver) { + frameOver.layers.put(r.placeDepth, new DepthState(layer, frameOver, false)); + } + if (r.buttonStateHitTest) { + frameHit.layers.put(r.placeDepth, new DepthState(layer, frameHit, false)); + } + + } + + timeline.addFrame(frameUp); + + if (frameOver.layers.isEmpty()) { + frameOver = frameUp; + } + + timeline.addFrame(frameOver); + + if (frameDown.layers.isEmpty()) { + frameDown = frameOver; + } + + timeline.addFrame(frameDown); + + if (frameHit.layers.isEmpty()) { + frameHit = frameUp; + } + + timeline.addFrame(frameHit); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineScalingGridTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineScalingGridTag.java index 057874e69..713a891e7 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineScalingGridTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineScalingGridTag.java @@ -1,229 +1,229 @@ -/* - * 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.tags; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle; -import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; -import com.jpexs.decompiler.flash.exporters.commonshape.Point; -import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; -import com.jpexs.decompiler.flash.tags.base.CharacterTag; -import com.jpexs.decompiler.flash.tags.base.DrawableTag; -import com.jpexs.decompiler.flash.tags.base.RenderContext; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.RECT; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.helpers.ByteArrayRange; -import java.awt.Rectangle; -import java.awt.Shape; -import java.awt.geom.AffineTransform; -import java.awt.geom.GeneralPath; -import java.awt.geom.PathIterator; -import java.io.IOException; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 8) -public class DefineScalingGridTag extends Tag implements CharacterIdTag { - - public static final int ID = 78; - - public static final String NAME = "DefineScalingGrid"; - - @SWFType(BasicType.UI16) - public int characterId; - - public RECT splitter; - - /** - * Constructor - * - * @param swf - */ - public DefineScalingGridTag(SWF swf) { - super(swf, ID, NAME, null); - splitter = new RECT(); - } - - public DefineScalingGridTag(SWFInputStream sis, ByteArrayRange data) throws IOException { - super(sis.getSwf(), ID, NAME, data); - readData(sis, data, 0, false, false, false); - } - - @Override - public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { - characterId = sis.readUI16("characterId"); - splitter = sis.readRECT("splitter"); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(characterId); - sos.writeRECT(splitter); - } - - @Override - public int getCharacterId() { - return characterId; - } - - @Override - public void setCharacterId(int characterId) { - this.characterId = characterId; - } - - private static double roundPixels(double v) { - return v; //Math.rint(v / SWF.unitDivisor) * SWF.unitDivisor; - } - - private static double roundPixels20(double v) { - return Math.rint(v / SWF.unitDivisor) * SWF.unitDivisor; - } - - private static Matrix rectToRectMatrix(ExportRectangle fromRect, ExportRectangle toRect) { - Matrix toOrigin = Matrix.getTranslateInstance(roundPixels(-fromRect.xMin), roundPixels(-fromRect.yMin)); - Matrix scale = new Matrix(); - scale.scaleX = roundPixels(toRect.getWidth()) / roundPixels(fromRect.getWidth()); - scale.scaleY = roundPixels(toRect.getHeight()) / roundPixels(fromRect.getHeight()); - Matrix toDest = Matrix.getTranslateInstance(roundPixels(toRect.xMin), roundPixels(toRect.yMin)); - return toOrigin.preConcatenate(scale).preConcatenate(toDest); - } - - public RECT getRect() { - Shape s = getOutline(0, 0, 0, new RenderContext(), new Matrix(), new Matrix(), true); - if (s == null) { - return null; - } - Rectangle r = s.getBounds(); - return new RECT(r.x, r.x + r.width, r.y, r.y + r.height); - } - - public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, Matrix prevTransform, boolean stroked) { - CharacterTag ct = swf.getCharacter(characterId); - if (ct == null) { - return null; - } - if (!(ct instanceof DrawableTag)) { - return null; - } - double[] coords = new double[6]; - - DrawableTag dt = (DrawableTag) ct; - Shape path = dt.getOutline(frame, time, ratio, renderContext, transformation, stroked); - PathIterator iterator = path.getPathIterator(new AffineTransform()); - GeneralPath gp = new GeneralPath(GeneralPath.WIND_EVEN_ODD); - ExportRectangle boundsRect = new ExportRectangle(dt.getRect()); - ExportRectangle scalingGrid = new ExportRectangle(splitter); - - ExportRectangle[] sourceRect = new ExportRectangle[9]; - ExportRectangle[] targetRect = new ExportRectangle[9]; - Matrix[] transforms = new Matrix[9]; - - getSlices(transformation.transform(boundsRect), boundsRect, scalingGrid, sourceRect, targetRect, transforms); - - while (!iterator.isDone()) { - int type = iterator.currentSegment(coords); - for (int i = 0; i < 6; i += 2) { - double x = coords[i]; - double y = coords[i + 1]; - for (int s = 0; s < 9; s++) { - Point p = new Point(x, y); - if (sourceRect[s].contains(p)) { - p = transforms[s].transform(p); - coords[i] = p.x; - coords[i + 1] = p.y; - break; - } - } - } - switch (type) { - case PathIterator.SEG_MOVETO: - gp.moveTo(coords[0], coords[1]); - break; - case PathIterator.SEG_LINETO: - gp.lineTo(coords[0], coords[1]); - break; - case PathIterator.SEG_QUADTO: - gp.quadTo(coords[0], coords[1], coords[2], coords[3]); - break; - case PathIterator.SEG_CUBICTO: - gp.curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); - break; - case PathIterator.SEG_CLOSE: - gp.closePath(); - break; - } - iterator.next(); - } - return gp; - } - - public static void getSlices(ExportRectangle targetBounds, ExportRectangle boundsRect, ExportRectangle scalingGrid, ExportRectangle[] sourceRect, ExportRectangle[] targetRect, Matrix[] transforms) { - - double src_x[] = new double[]{boundsRect.xMin, scalingGrid.xMin, scalingGrid.xMax, boundsRect.xMax}; - double dst_x[] = new double[]{targetBounds.xMin, targetBounds.xMin + scalingGrid.xMin, targetBounds.xMax - (boundsRect.xMax - scalingGrid.xMax), targetBounds.xMax}; - - double src_y[] = new double[]{boundsRect.yMin, scalingGrid.yMin, scalingGrid.yMax, boundsRect.yMax}; - double dst_y[] = new double[]{targetBounds.yMin, targetBounds.yMin + scalingGrid.yMin, targetBounds.yMax - (boundsRect.yMax - scalingGrid.yMax), targetBounds.yMax}; - - int pos = 0; - for (int sy = 0; sy < 3; sy++) { - for (int sx = 0; sx < 3; sx++) { - sourceRect[pos] = new ExportRectangle(src_x[sx], src_y[sy], src_x[sx + 1], src_y[sy + 1]); - targetRect[pos] = new ExportRectangle(dst_x[sx], dst_y[sy], dst_x[sx + 1], dst_y[sy + 1]); - pos++; - } - } - - for (int i = 0; i < targetRect.length; i++) { - - /* sourceRect[i].xMax = roundPixels20(sourceRect[i].xMax); - sourceRect[i].yMax = roundPixels20(sourceRect[i].yMax); - sourceRect[i].xMin = roundPixels20(sourceRect[i].xMin); - sourceRect[i].yMin = roundPixels20(sourceRect[i].yMin); - */ - //System.out.println("source[" + i + "]=" + sourceRect[i]); - //System.out.println("target[" + i + "]=" + targetRect[i]); - - /*targetRect[i].xMax = roundPixels20(targetRect[i].xMax); - targetRect[i].yMax = roundPixels20(targetRect[i].yMax); - targetRect[i].xMin = roundPixels20(targetRect[i].xMin); - targetRect[i].yMin = roundPixels20(targetRect[i].yMin); - */ - transforms[i] = rectToRectMatrix(sourceRect[i], targetRect[i]); - - targetRect[i].xMax = Math.rint(targetRect[i].xMax / SWF.unitDivisor); - targetRect[i].yMax = Math.rint(targetRect[i].yMax / SWF.unitDivisor); - targetRect[i].xMin = Math.rint(targetRect[i].xMin / SWF.unitDivisor); - targetRect[i].yMin = Math.rint(targetRect[i].yMin / SWF.unitDivisor); - - //targetRect[i].xMax += maxStroke; - //Round to pixel boundary - } - } -} +/* + * 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.tags; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.commonshape.Point; +import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.DrawableTag; +import com.jpexs.decompiler.flash.tags.base.RenderContext; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.helpers.ByteArrayRange; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.GeneralPath; +import java.awt.geom.PathIterator; +import java.io.IOException; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 8) +public class DefineScalingGridTag extends Tag implements CharacterIdTag { + + public static final int ID = 78; + + public static final String NAME = "DefineScalingGrid"; + + @SWFType(BasicType.UI16) + public int characterId; + + public RECT splitter; + + /** + * Constructor + * + * @param swf + */ + public DefineScalingGridTag(SWF swf) { + super(swf, ID, NAME, null); + splitter = new RECT(); + } + + public DefineScalingGridTag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + characterId = sis.readUI16("characterId"); + splitter = sis.readRECT("splitter"); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(characterId); + sos.writeRECT(splitter); + } + + @Override + public int getCharacterId() { + return characterId; + } + + @Override + public void setCharacterId(int characterId) { + this.characterId = characterId; + } + + private static double roundPixels(double v) { + return v; //Math.rint(v / SWF.unitDivisor) * SWF.unitDivisor; + } + + private static double roundPixels20(double v) { + return Math.rint(v / SWF.unitDivisor) * SWF.unitDivisor; + } + + private static Matrix rectToRectMatrix(ExportRectangle fromRect, ExportRectangle toRect) { + Matrix toOrigin = Matrix.getTranslateInstance(roundPixels(-fromRect.xMin), roundPixels(-fromRect.yMin)); + Matrix scale = new Matrix(); + scale.scaleX = roundPixels(toRect.getWidth()) / roundPixels(fromRect.getWidth()); + scale.scaleY = roundPixels(toRect.getHeight()) / roundPixels(fromRect.getHeight()); + Matrix toDest = Matrix.getTranslateInstance(roundPixels(toRect.xMin), roundPixels(toRect.yMin)); + return toOrigin.preConcatenate(scale).preConcatenate(toDest); + } + + public RECT getRect() { + Shape s = getOutline(0, 0, 0, new RenderContext(), new Matrix(), new Matrix(), true); + if (s == null) { + return null; + } + Rectangle r = s.getBounds(); + return new RECT(r.x, r.x + r.width, r.y, r.y + r.height); + } + + public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, Matrix prevTransform, boolean stroked) { + CharacterTag ct = swf.getCharacter(characterId); + if (ct == null) { + return null; + } + if (!(ct instanceof DrawableTag)) { + return null; + } + double[] coords = new double[6]; + + DrawableTag dt = (DrawableTag) ct; + Shape path = dt.getOutline(frame, time, ratio, renderContext, transformation, stroked); + PathIterator iterator = path.getPathIterator(new AffineTransform()); + GeneralPath gp = new GeneralPath(GeneralPath.WIND_EVEN_ODD); + ExportRectangle boundsRect = new ExportRectangle(dt.getRect()); + ExportRectangle scalingGrid = new ExportRectangle(splitter); + + ExportRectangle[] sourceRect = new ExportRectangle[9]; + ExportRectangle[] targetRect = new ExportRectangle[9]; + Matrix[] transforms = new Matrix[9]; + + getSlices(transformation.transform(boundsRect), boundsRect, scalingGrid, sourceRect, targetRect, transforms); + + while (!iterator.isDone()) { + int type = iterator.currentSegment(coords); + for (int i = 0; i < 6; i += 2) { + double x = coords[i]; + double y = coords[i + 1]; + for (int s = 0; s < 9; s++) { + Point p = new Point(x, y); + if (sourceRect[s].contains(p)) { + p = transforms[s].transform(p); + coords[i] = p.x; + coords[i + 1] = p.y; + break; + } + } + } + switch (type) { + case PathIterator.SEG_MOVETO: + gp.moveTo(coords[0], coords[1]); + break; + case PathIterator.SEG_LINETO: + gp.lineTo(coords[0], coords[1]); + break; + case PathIterator.SEG_QUADTO: + gp.quadTo(coords[0], coords[1], coords[2], coords[3]); + break; + case PathIterator.SEG_CUBICTO: + gp.curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); + break; + case PathIterator.SEG_CLOSE: + gp.closePath(); + break; + } + iterator.next(); + } + return gp; + } + + public static void getSlices(ExportRectangle targetBounds, ExportRectangle boundsRect, ExportRectangle scalingGrid, ExportRectangle[] sourceRect, ExportRectangle[] targetRect, Matrix[] transforms) { + + double[] src_x = new double[]{boundsRect.xMin, scalingGrid.xMin, scalingGrid.xMax, boundsRect.xMax}; + double[] dst_x = new double[]{targetBounds.xMin, targetBounds.xMin + scalingGrid.xMin, targetBounds.xMax - (boundsRect.xMax - scalingGrid.xMax), targetBounds.xMax}; + + double[] src_y = new double[]{boundsRect.yMin, scalingGrid.yMin, scalingGrid.yMax, boundsRect.yMax}; + double[] dst_y = new double[]{targetBounds.yMin, targetBounds.yMin + scalingGrid.yMin, targetBounds.yMax - (boundsRect.yMax - scalingGrid.yMax), targetBounds.yMax}; + + int pos = 0; + for (int sy = 0; sy < 3; sy++) { + for (int sx = 0; sx < 3; sx++) { + sourceRect[pos] = new ExportRectangle(src_x[sx], src_y[sy], src_x[sx + 1], src_y[sy + 1]); + targetRect[pos] = new ExportRectangle(dst_x[sx], dst_y[sy], dst_x[sx + 1], dst_y[sy + 1]); + pos++; + } + } + + for (int i = 0; i < targetRect.length; i++) { + + /* sourceRect[i].xMax = roundPixels20(sourceRect[i].xMax); + sourceRect[i].yMax = roundPixels20(sourceRect[i].yMax); + sourceRect[i].xMin = roundPixels20(sourceRect[i].xMin); + sourceRect[i].yMin = roundPixels20(sourceRect[i].yMin); + */ + //System.out.println("source[" + i + "]=" + sourceRect[i]); + //System.out.println("target[" + i + "]=" + targetRect[i]); + + /*targetRect[i].xMax = roundPixels20(targetRect[i].xMax); + targetRect[i].yMax = roundPixels20(targetRect[i].yMax); + targetRect[i].xMin = roundPixels20(targetRect[i].xMin); + targetRect[i].yMin = roundPixels20(targetRect[i].yMin); + */ + transforms[i] = rectToRectMatrix(sourceRect[i], targetRect[i]); + + targetRect[i].xMax = Math.rint(targetRect[i].xMax / SWF.unitDivisor); + targetRect[i].yMax = Math.rint(targetRect[i].yMax / SWF.unitDivisor); + targetRect[i].xMin = Math.rint(targetRect[i].xMin / SWF.unitDivisor); + targetRect[i].yMin = Math.rint(targetRect[i].yMin / SWF.unitDivisor); + + //targetRect[i].xMax += maxStroke; + //Round to pixel boundary + } + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java index b4298e90e..d598f4246 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java @@ -1,369 +1,371 @@ -/* - * 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.tags; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.tags.base.CharacterTag; -import com.jpexs.decompiler.flash.tags.base.SoundTag; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.decompiler.flash.types.sound.MP3FRAME; -import com.jpexs.decompiler.flash.types.sound.MP3SOUNDDATA; -import com.jpexs.decompiler.flash.types.sound.SoundExportFormat; -import com.jpexs.decompiler.flash.types.sound.SoundFormat; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.Helper; -import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.sound.sampled.AudioFormat; -import javax.sound.sampled.AudioInputStream; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.UnsupportedAudioFileException; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 1) -public class DefineSoundTag extends CharacterTag implements SoundTag { - - public static final int ID = 14; - - public static final String NAME = "DefineSound"; - - @SWFType(BasicType.UI16) - public int soundId; - - @SWFType(value = BasicType.UB, count = 4) - public int soundFormat; - - @SWFType(value = BasicType.UB, count = 2) - public int soundRate; - - public boolean soundSize; - - public boolean soundType; - - @SWFType(BasicType.UI32) - public long soundSampleCount; - - public ByteArrayRange soundData; - - /** - * Constructor - * - * @param swf - */ - public DefineSoundTag(SWF swf) { - super(swf, ID, NAME, null); - soundId = swf.getNextCharacterId(); - soundData = ByteArrayRange.EMPTY; - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public DefineSoundTag(SWFInputStream sis, ByteArrayRange data) throws IOException { - super(sis.getSwf(), ID, NAME, data); - readData(sis, data, 0, false, false, false); - } - - @Override - public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { - soundId = sis.readUI16("soundId"); - soundFormat = (int) sis.readUB(4, "soundFormat"); - soundRate = (int) sis.readUB(2, "soundRate"); - soundSize = sis.readUB(1, "soundSize") == 1; - soundType = sis.readUB(1, "soundType") == 1; - soundSampleCount = sis.readUI32("soundSampleCount"); - soundData = sis.readByteRangeEx(sis.available(), "soundData"); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(soundId); - sos.writeUB(4, soundFormat); - sos.writeUB(2, soundRate); - sos.writeUB(1, soundSize ? 1 : 0); - sos.writeUB(1, soundType ? 1 : 0); - sos.writeUI32(soundSampleCount); - sos.write(soundData); - } - - @Override - public int getCharacterId() { - return soundId; - } - - @Override - public void setCharacterId(int characterId) { - this.soundId = characterId; - } - - @Override - public SoundExportFormat getExportFormat() { - if (soundFormat == SoundFormat.FORMAT_MP3) { - return SoundExportFormat.MP3; - } - if (soundFormat == SoundFormat.FORMAT_ADPCM) { - return SoundExportFormat.WAV; - } - if (soundFormat == SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN) { - return SoundExportFormat.WAV; - } - if (soundFormat == SoundFormat.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) { - return SoundExportFormat.WAV; - } - if (soundFormat == SoundFormat.FORMAT_NELLYMOSER || soundFormat == SoundFormat.FORMAT_NELLYMOSER16KHZ || soundFormat == SoundFormat.FORMAT_NELLYMOSER8KHZ) { - return SoundExportFormat.WAV; - } - return SoundExportFormat.FLV; - } - - private void loadID3v2(InputStream in) { - int size = -1; - try { - // Read ID3v2 header (10 bytes). - in.mark(10); - size = readID3v2Header(in); - } catch (IOException e) { - } finally { - try { - // Unread ID3v2 header (10 bytes). - in.reset(); - } catch (IOException e) { - } - } - // Load ID3v2 tags. - try { - if (size > 0) { - byte[] rawid3v2 = new byte[size]; - in.read(rawid3v2, 0, rawid3v2.length); - } - } catch (IOException e) { - } - } - - /** - * Parse ID3v2 tag header to find out size of ID3v2 frames. - * - * @param in MP3 InputStream - * @return size of ID3v2 frames + header - * @throws IOException - * @author JavaZOOM - */ - private int readID3v2Header(InputStream in) throws IOException { - byte[] id3header = new byte[4]; - int size = -10; - in.read(id3header, 0, 3); - // Look for ID3v2 - if ((id3header[0] == 'I') && (id3header[1] == 'D') && (id3header[2] == '3')) { - in.read(id3header, 0, 3); - int majorVersion = id3header[0]; - int revision = id3header[1]; - in.read(id3header, 0, 4); - size = (int) (id3header[0] << 21) + (id3header[1] << 14) + (id3header[2] << 7) + (id3header[3]); - } - return (size + 10); - } - - @Override - public boolean setSound(InputStream is, int newSoundFormat) { - int newSoundRate = -1; - boolean newSoundSize = false; - boolean newSoundType = false; - long newSoundSampleCount = -1; - byte[] newSoundData; - switch (newSoundFormat) { - case SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN: - try (AudioInputStream audioIs = AudioSystem.getAudioInputStream(new BufferedInputStream(is))) { - AudioFormat fmt = audioIs.getFormat(); - newSoundType = fmt.getChannels() == 2; - newSoundSize = fmt.getSampleSizeInBits() == 16; - newSoundSampleCount = audioIs.getFrameLength(); - newSoundData = Helper.readStream(audioIs); - newSoundRate = (int) Math.round(fmt.getSampleRate()); - switch (newSoundRate) { - case 5512: - newSoundRate = 0; - break; - case 11025: - newSoundRate = 1; - break; - case 22050: - newSoundRate = 2; - break; - case 44100: - newSoundRate = 3; - break; - default: - return false; - } - } catch (UnsupportedAudioFileException | IOException ex) { - return false; - } - break; - case SoundFormat.FORMAT_MP3: - BufferedInputStream bis = new BufferedInputStream(is); - loadID3v2(bis); - byte[] mp3data = Helper.readStream(bis); - - final int ID3_V1_LENTGH = 128; - final int ID3_V1_EXT_LENGTH = 227; - - if (mp3data.length > ID3_V1_LENTGH) { - //ID3v1 - if (mp3data[mp3data.length - ID3_V1_LENTGH] == 'T' && mp3data[mp3data.length - ID3_V1_LENTGH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_LENTGH + 2] == 'G') { - mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_LENTGH); - if (mp3data.length > ID3_V1_EXT_LENGTH) { - //ID3v1 extended - if (mp3data[mp3data.length - ID3_V1_EXT_LENGTH] == 'T' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 2] == 'G' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 3] == '+') { - mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_EXT_LENGTH); - } - } - } - } - try { - MP3SOUNDDATA snd = new MP3SOUNDDATA(new SWFInputStream(swf, mp3data), true); - if (!snd.frames.isEmpty()) { - MP3FRAME fr = snd.frames.get(0); - newSoundRate = fr.getSamplingRate(); - switch (newSoundRate) { - case 11025: - newSoundRate = 1; - break; - case 22050: - newSoundRate = 2; - break; - case 44100: - newSoundRate = 3; - break; - default: - return false; - } - newSoundSize = true; - newSoundType = fr.isStereo(); - int len = snd.sampleCount(); - if (fr.isStereo()) { - len = len / 2; - } - newSoundSampleCount = len; - - } - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - SWFOutputStream sos = new SWFOutputStream(baos, SWF.DEFAULT_VERSION); - sos.writeSI16(0); //Latency - how to calculate it? - sos.write(mp3data); - newSoundData = baos.toByteArray(); - } catch (IOException ex) { - return false; - } - break; - default: - return false; - } - if (newSoundData != null) { - this.soundSize = newSoundSize; - this.soundRate = newSoundRate; - this.soundSampleCount = newSoundSampleCount; - this.soundData = new ByteArrayRange(newSoundData); - this.soundType = newSoundType; - this.soundFormat = newSoundFormat; - setModified(true); - return true; - } - return false; - - } - - @Override - public boolean importSupported() { - return true; - } - - @Override - public int getSoundRate() { - return soundRate; - } - - @Override - public boolean getSoundType() { - return soundType; - } - - @Override - public List getRawSoundData() { - List ret = new ArrayList<>(); - if (soundFormat == SoundFormat.FORMAT_MP3) { - ret.add(soundData.getSubRange(2, soundData.getLength() - 2)); - return ret; - } - - ret.add(soundData); - return ret; - } - - @Override - public int getSoundFormatId() { - return soundFormat; - } - - @Override - public long getTotalSoundSampleCount() { - return soundSampleCount; - } - - @Override - public boolean getSoundSize() { - return soundSize; - } - - @Override - public SoundFormat getSoundFormat() { - final int[] rateMap = {5512, 11025, 22050, 44100}; - return new SoundFormat(getSoundFormatId(), rateMap[getSoundRate()], getSoundType()); - } - - @Override - public void getTagInfo(TagInfo tagInfo) { - super.getTagInfo(tagInfo); - SoundFormat soundFormat = getSoundFormat(); - tagInfo.addInfo("general", "codecName", soundFormat.getFormatName()); - tagInfo.addInfo("general", "exportFormat", soundFormat.getNativeExportFormat()); - tagInfo.addInfo("general", "samplingRate", soundFormat.samplingRate); - tagInfo.addInfo("general", "stereo", soundFormat.stereo); - tagInfo.addInfo("general", "sampleCount", soundSampleCount); - } -} +/* + * 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.tags; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.SoundTag; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.decompiler.flash.types.sound.MP3FRAME; +import com.jpexs.decompiler.flash.types.sound.MP3SOUNDDATA; +import com.jpexs.decompiler.flash.types.sound.SoundExportFormat; +import com.jpexs.decompiler.flash.types.sound.SoundFormat; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.Helper; +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 1) +public class DefineSoundTag extends CharacterTag implements SoundTag { + + public static final int ID = 14; + + public static final String NAME = "DefineSound"; + + @SWFType(BasicType.UI16) + public int soundId; + + @SWFType(value = BasicType.UB, count = 4) + public int soundFormat; + + @SWFType(value = BasicType.UB, count = 2) + public int soundRate; + + public boolean soundSize; + + public boolean soundType; + + @SWFType(BasicType.UI32) + public long soundSampleCount; + + public ByteArrayRange soundData; + + /** + * Constructor + * + * @param swf + */ + public DefineSoundTag(SWF swf) { + super(swf, ID, NAME, null); + soundId = swf.getNextCharacterId(); + soundData = ByteArrayRange.EMPTY; + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public DefineSoundTag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + soundId = sis.readUI16("soundId"); + soundFormat = (int) sis.readUB(4, "soundFormat"); + soundRate = (int) sis.readUB(2, "soundRate"); + soundSize = sis.readUB(1, "soundSize") == 1; + soundType = sis.readUB(1, "soundType") == 1; + soundSampleCount = sis.readUI32("soundSampleCount"); + soundData = sis.readByteRangeEx(sis.available(), "soundData"); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(soundId); + sos.writeUB(4, soundFormat); + sos.writeUB(2, soundRate); + sos.writeUB(1, soundSize ? 1 : 0); + sos.writeUB(1, soundType ? 1 : 0); + sos.writeUI32(soundSampleCount); + sos.write(soundData); + } + + @Override + public int getCharacterId() { + return soundId; + } + + @Override + public void setCharacterId(int characterId) { + this.soundId = characterId; + } + + @Override + public SoundExportFormat getExportFormat() { + if (soundFormat == SoundFormat.FORMAT_MP3) { + return SoundExportFormat.MP3; + } + if (soundFormat == SoundFormat.FORMAT_ADPCM) { + return SoundExportFormat.WAV; + } + if (soundFormat == SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN) { + return SoundExportFormat.WAV; + } + if (soundFormat == SoundFormat.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) { + return SoundExportFormat.WAV; + } + if (soundFormat == SoundFormat.FORMAT_NELLYMOSER || soundFormat == SoundFormat.FORMAT_NELLYMOSER16KHZ || soundFormat == SoundFormat.FORMAT_NELLYMOSER8KHZ) { + return SoundExportFormat.WAV; + } + return SoundExportFormat.FLV; + } + + private void loadID3v2(InputStream in) { + int size = -1; + try { + // Read ID3v2 header (10 bytes). + in.mark(10); + size = readID3v2Header(in); + } catch (IOException e) { + } finally { + try { + // Unread ID3v2 header (10 bytes). + in.reset(); + } catch (IOException e) { + } + } + // Load ID3v2 tags. + try { + if (size > 0) { + byte[] rawid3v2 = new byte[size]; + in.read(rawid3v2, 0, rawid3v2.length); + } + } catch (IOException e) { + } + } + + /** + * Parse ID3v2 tag header to find out size of ID3v2 frames. + * + * @param in MP3 InputStream + * @return size of ID3v2 frames + header + * @throws IOException + * @author JavaZOOM + */ + private int readID3v2Header(InputStream in) throws IOException { + byte[] id3header = new byte[4]; + int size = -10; + in.read(id3header, 0, 3); + // Look for ID3v2 + if ((id3header[0] == 'I') && (id3header[1] == 'D') && (id3header[2] == '3')) { + in.read(id3header, 0, 3); + int majorVersion = id3header[0]; + int revision = id3header[1]; + in.read(id3header, 0, 4); + size = (int) (id3header[0] << 21) + (id3header[1] << 14) + (id3header[2] << 7) + (id3header[3]); + } + return (size + 10); + } + + @Override + public boolean setSound(InputStream is, int newSoundFormat) { + int newSoundRate = -1; + boolean newSoundSize = false; + boolean newSoundType = false; + long newSoundSampleCount = -1; + byte[] newSoundData; + switch (newSoundFormat) { + case SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN: + try (AudioInputStream audioIs = AudioSystem.getAudioInputStream(new BufferedInputStream(is))) { + AudioFormat fmt = audioIs.getFormat(); + newSoundType = fmt.getChannels() == 2; + newSoundSize = fmt.getSampleSizeInBits() == 16; + newSoundSampleCount = audioIs.getFrameLength(); + newSoundData = Helper.readStream(audioIs); + newSoundRate = (int) Math.round(fmt.getSampleRate()); + switch (newSoundRate) { + case 5512: + newSoundRate = 0; + break; + case 11025: + newSoundRate = 1; + break; + case 22050: + newSoundRate = 2; + break; + case 44100: + newSoundRate = 3; + break; + default: + return false; + } + } catch (UnsupportedAudioFileException | IOException ex) { + return false; + } + break; + case SoundFormat.FORMAT_MP3: + BufferedInputStream bis = new BufferedInputStream(is); + loadID3v2(bis); + byte[] mp3data = Helper.readStream(bis); + + final int ID3_V1_LENTGH = 128; + final int ID3_V1_EXT_LENGTH = 227; + + if (mp3data.length > ID3_V1_LENTGH) { + //ID3v1 + if (mp3data[mp3data.length - ID3_V1_LENTGH] == 'T' && mp3data[mp3data.length - ID3_V1_LENTGH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_LENTGH + 2] == 'G') { + mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_LENTGH); + if (mp3data.length > ID3_V1_EXT_LENGTH) { + //ID3v1 extended + if (mp3data[mp3data.length - ID3_V1_EXT_LENGTH] == 'T' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 2] == 'G' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 3] == '+') { + mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_EXT_LENGTH); + } + } + } + } + try { + MP3SOUNDDATA snd = new MP3SOUNDDATA(new SWFInputStream(swf, mp3data), true); + if (!snd.frames.isEmpty()) { + MP3FRAME fr = snd.frames.get(0); + newSoundRate = fr.getSamplingRate(); + switch (newSoundRate) { + case 11025: + newSoundRate = 1; + break; + case 22050: + newSoundRate = 2; + break; + case 44100: + newSoundRate = 3; + break; + default: + return false; + } + + newSoundSize = true; + newSoundType = fr.isStereo(); + int len = snd.sampleCount(); + if (fr.isStereo()) { + len = len / 2; + } + + newSoundSampleCount = len; + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(baos, SWF.DEFAULT_VERSION); + sos.writeSI16(0); //Latency - how to calculate it? + sos.write(mp3data); + newSoundData = baos.toByteArray(); + } catch (IOException ex) { + return false; + } + break; + default: + return false; + } + if (newSoundData != null) { + this.soundSize = newSoundSize; + this.soundRate = newSoundRate; + this.soundSampleCount = newSoundSampleCount; + this.soundData = new ByteArrayRange(newSoundData); + this.soundType = newSoundType; + this.soundFormat = newSoundFormat; + setModified(true); + return true; + } + return false; + + } + + @Override + public boolean importSupported() { + return true; + } + + @Override + public int getSoundRate() { + return soundRate; + } + + @Override + public boolean getSoundType() { + return soundType; + } + + @Override + public List getRawSoundData() { + List ret = new ArrayList<>(); + if (soundFormat == SoundFormat.FORMAT_MP3) { + ret.add(soundData.getSubRange(2, soundData.getLength() - 2)); + return ret; + } + + ret.add(soundData); + return ret; + } + + @Override + public int getSoundFormatId() { + return soundFormat; + } + + @Override + public long getTotalSoundSampleCount() { + return soundSampleCount; + } + + @Override + public boolean getSoundSize() { + return soundSize; + } + + @Override + public SoundFormat getSoundFormat() { + final int[] rateMap = {5512, 11025, 22050, 44100}; + return new SoundFormat(getSoundFormatId(), rateMap[getSoundRate()], getSoundType()); + } + + @Override + public void getTagInfo(TagInfo tagInfo) { + super.getTagInfo(tagInfo); + SoundFormat soundFormat = getSoundFormat(); + tagInfo.addInfo("general", "codecName", soundFormat.getFormatName()); + tagInfo.addInfo("general", "exportFormat", soundFormat.getNativeExportFormat()); + tagInfo.addInfo("general", "samplingRate", soundFormat.samplingRate); + tagInfo.addInfo("general", "stereo", soundFormat.stereo); + tagInfo.addInfo("general", "sampleCount", soundSampleCount); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoABC2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoABC2Tag.java index 67245e787..c3bbcab53 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoABC2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoABC2Tag.java @@ -1,145 +1,146 @@ -/* - * 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.tags; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.abc.ABC; -import com.jpexs.decompiler.flash.abc.ABCInputStream; -import com.jpexs.decompiler.flash.abc.types.ScriptInfo; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; -import com.jpexs.decompiler.flash.types.annotations.SWFField; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.helpers.ByteArrayRange; -import java.io.IOException; - -/** - * Defines a series of ActionScript 3 bytecodes to be executed - * - * @author JPEXS - */ -@SWFVersion(from = 9) -public class DoABC2Tag extends Tag implements ABCContainerTag { - - public static final int ID = 82; - - public static final String NAME = "DoABC2"; - - /** - * ActionScript 3 bytecodes - */ - @HideInRawEdit - @SWFField - private ABC abc; - - /** - * A 32-bit flags value, which may contain the following bits set: - * kDoAbcLazyInitializeFlag = 1: Indicates that the ABC block should not be - * executed immediately, but only parsed. A later finddef may cause its - * scripts to execute. - */ - @SWFType(BasicType.UI32) - public long flags; - - /** - * The name assigned to the bytecode. - */ - public String name; - - /** - * Constructor - * - * @param swf - */ - public DoABC2Tag(SWF swf) { - super(swf, ID, NAME, null); - name = "New DoABC"; - abc = new ABC(this); - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public DoABC2Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { - super(sis.getSwf(), ID, NAME, data); - readData(sis, data, 0, false, false, false); - } - - @Override - public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { - flags = sis.readUI32("flags"); - name = sis.readString("name"); - - ABCInputStream ais = new ABCInputStream(sis.getBaseStream()); - - // put it to the dumpview: - sis.readByteRangeEx(sis.available(), "abcBytes"); - abc = new ABC(ais, swf, this); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI32(flags); - sos.writeString(name); - abc.saveToStream(sos); - } - - @Override - public ABC getABC() { - return abc; - } - - @Override - public String getName() { - return super.getName() + " (" + name + ")"; - } - - @Override - public int compareTo(ABCContainerTag o) { - if (o instanceof DoABC2Tag) { - DoABC2Tag n = (DoABC2Tag) o; - int lastCmp = name.compareTo(n.name); - return (lastCmp != 0 ? lastCmp - : name.compareTo(n.name)); - } - return 0; - } - - @Override - public void setModified(boolean value) { - super.setModified(value); - if (value == false && !isModified()) { - ABC abc = getABC(); - for (ScriptInfo si : abc.script_info) { - si.setModified(false); - } - } - } -} +/* + * 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.tags; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.ABCInputStream; +import com.jpexs.decompiler.flash.abc.types.ScriptInfo; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; +import com.jpexs.decompiler.flash.types.annotations.SWFField; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.helpers.ByteArrayRange; +import java.io.IOException; + +/** + * Defines a series of ActionScript 3 bytecodes to be executed + * + * @author JPEXS + */ +@SWFVersion(from = 9) +public class DoABC2Tag extends Tag implements ABCContainerTag { + + public static final int ID = 82; + + public static final String NAME = "DoABC2"; + + /** + * ActionScript 3 bytecodes + */ + @HideInRawEdit + @SWFField + private ABC abc; + + /** + * A 32-bit flags value, which may contain the following bits set: + * kDoAbcLazyInitializeFlag = 1: Indicates that the ABC block should not be + * executed immediately, but only parsed. A later finddef may cause its + * scripts to execute. + */ + @SWFType(BasicType.UI32) + public long flags; + + /** + * The name assigned to the bytecode. + */ + public String name; + + /** + * Constructor + * + * @param swf + */ + public DoABC2Tag(SWF swf) { + super(swf, ID, NAME, null); + name = "New DoABC"; + abc = new ABC(this); + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public DoABC2Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + flags = sis.readUI32("flags"); + name = sis.readString("name"); + + ABCInputStream ais = new ABCInputStream(sis.getBaseStream()); + + // put it to the dumpview: + sis.readByteRangeEx(sis.available(), "abcBytes", DumpInfoSpecialType.ABC_BYTES, null); + abc = new ABC(ais, swf, this); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI32(flags); + sos.writeString(name); + abc.saveToStream(sos); + } + + @Override + public ABC getABC() { + return abc; + } + + @Override + public String getName() { + return super.getName() + " (" + name + ")"; + } + + @Override + public int compareTo(ABCContainerTag o) { + if (o instanceof DoABC2Tag) { + DoABC2Tag n = (DoABC2Tag) o; + int lastCmp = name.compareTo(n.name); + return (lastCmp != 0 ? lastCmp + : name.compareTo(n.name)); + } + return 0; + } + + @Override + public void setModified(boolean value) { + super.setModified(value); + if (value == false && !isModified()) { + ABC abc = getABC(); + for (ScriptInfo si : abc.script_info) { + si.setModified(false); + } + } + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoABCTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoABCTag.java index cf43bddf2..2e4899c6b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoABCTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoABCTag.java @@ -22,6 +22,7 @@ import com.jpexs.decompiler.flash.SWFOutputStream; import com.jpexs.decompiler.flash.abc.ABC; import com.jpexs.decompiler.flash.abc.ABCInputStream; import com.jpexs.decompiler.flash.abc.types.ScriptInfo; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; import com.jpexs.decompiler.flash.types.annotations.SWFField; import com.jpexs.decompiler.flash.types.annotations.SWFVersion; @@ -74,7 +75,7 @@ public class DoABCTag extends Tag implements ABCContainerTag { ABCInputStream ais = new ABCInputStream(sis.getBaseStream()); // put it to the dumpview: - sis.readByteRangeEx(sis.available(), "abcBytes"); + sis.readByteRangeEx(sis.available(), "abcBytes", DumpInfoSpecialType.ABC_BYTES, null); abc = new ABC(ais, swf, this); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoActionTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoActionTag.java index ea53f2642..d1560f1b9 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoActionTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoActionTag.java @@ -23,6 +23,7 @@ import com.jpexs.decompiler.flash.SWFOutputStream; import com.jpexs.decompiler.flash.action.Action; import com.jpexs.decompiler.flash.action.ActionList; import com.jpexs.decompiler.flash.action.ConstantPoolTooBigException; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; import com.jpexs.decompiler.flash.helpers.GraphTextWriter; import com.jpexs.decompiler.flash.tags.base.ASMSource; @@ -102,7 +103,7 @@ public class DoActionTag extends Tag implements ASMSource { @Override public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { - actionBytes = sis.readByteRangeEx(sis.available(), "actionBytes"); + actionBytes = sis.readByteRangeEx(sis.available(), "actionBytes", DumpInfoSpecialType.ACTION_BYTES, sis.getPos()); } /** diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoInitActionTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoInitActionTag.java index 6c9900227..33e47dc58 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoInitActionTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoInitActionTag.java @@ -1,252 +1,253 @@ -/* - * 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.tags; - -import com.jpexs.decompiler.flash.DisassemblyListener; -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.action.Action; -import com.jpexs.decompiler.flash.action.ActionList; -import com.jpexs.decompiler.flash.action.ConstantPoolTooBigException; -import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; -import com.jpexs.decompiler.flash.helpers.GraphTextWriter; -import com.jpexs.decompiler.flash.tags.base.ASMSource; -import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; -import com.jpexs.decompiler.flash.types.annotations.Internal; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.Helper; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 6) -public class DoInitActionTag extends Tag implements CharacterIdTag, ASMSource { - - public static final int ID = 59; - - public static final String NAME = "DoInitAction"; - - /** - * Identifier of Sprite - */ - @SWFType(BasicType.UI16) - public int spriteId = 0; - - /** - * List of actions to perform - */ - @HideInRawEdit - public ByteArrayRange actionBytes; - - @Internal - private String scriptName = "-"; - - @Override - public String getScriptName() { - return scriptName; - } - - /** - * Constructor - * - * @param swf - */ - public DoInitActionTag(SWF swf) { - super(swf, ID, NAME, null); - actionBytes = ByteArrayRange.EMPTY; - } - - @Override - public void setScriptName(String scriptName) { - this.scriptName = scriptName; - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public DoInitActionTag(SWFInputStream sis, ByteArrayRange data) throws IOException { - super(sis.getSwf(), ID, NAME, data); - readData(sis, data, 0, false, false, false); - } - - @Override - public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { - spriteId = sis.readUI16("spriteId"); - actionBytes = sis.readByteRangeEx(sis.available(), "actionBytes"); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(spriteId); - sos.write(getActionBytes()); - //sos.write(Action.actionsToBytes(actions, true, version)); - } - - /** - * Whether or not this object contains ASM source - * - * @return True when contains - */ - @Override - public boolean containsSource() { - return true; - } - - /** - * Converts actions to ASM source - * - * @param exportMode PCode or hex? - * @param writer - * @param actions - * @return ASM source - * @throws java.lang.InterruptedException - */ - @Override - public GraphTextWriter getASMSource(ScriptExportMode exportMode, GraphTextWriter writer, ActionList actions) throws InterruptedException { - if (actions == null) { - actions = getActions(); - } - return Action.actionsToString(listeners, 0, actions, swf.version, exportMode, writer); - } - - @Override - public ActionList getActions() throws InterruptedException { - return SWF.getCachedActionList(this, listeners); - } - - @Override - public void setActions(List actions) { - actionBytes = Action.actionsToByteArrayRange(actions, true, swf.version); - } - - @Override - public ByteArrayRange getActionBytes() { - return actionBytes; - } - - @Override - public void setActionBytes(byte[] actionBytes) { - this.actionBytes = new ByteArrayRange(actionBytes); - SWF.uncache(this); - } - - @Override - public void setConstantPools(List> constantPools) throws ConstantPoolTooBigException { - Action.setConstantPools(this, constantPools, false); - } - - @Override - public void setModified() { - setModified(true); - } - - @Override - public GraphTextWriter getActionBytesAsHex(GraphTextWriter writer) { - return Helper.byteArrayToHexWithHeader(writer, actionBytes.getRangeData()); - } - - @Override - public int getCharacterId() { - return spriteId; - } - - @Override - public void setCharacterId(int characterId) { - this.spriteId = characterId; - } - - List listeners = new ArrayList<>(); - - @Override - public void addDisassemblyListener(DisassemblyListener listener) { - listeners.add(listener); - } - - @Override - public void removeDisassemblyListener(DisassemblyListener listener) { - listeners.remove(listener); - } - - @Override - public String getExportFileName() { - String expName = swf == null ? "" : swf.getExportName(spriteId); - if (expName == null || expName.isEmpty()) { - return super.getExportFileName(); - } - String[] pathParts = expName.contains(".") ? expName.split("\\.") : new String[]{expName}; - return pathParts[pathParts.length - 1]; - } - - @Override - public String getName() { - String expName = swf == null ? "" : swf.getExportName(spriteId); - if (expName == null || expName.isEmpty()) { - return super.getName(); - } - String[] pathParts = expName.contains(".") ? expName.split("\\.") : new String[]{expName}; - return pathParts[pathParts.length - 1]; - } - - @Override - public GraphTextWriter getActionSourcePrefix(GraphTextWriter writer) { - return writer; - } - - @Override - public GraphTextWriter getActionSourceSuffix(GraphTextWriter writer) { - return writer; - } - - @Override - public int getPrefixLineCount() { - return 0; - } - - @Override - public String removePrefixAndSuffix(String source) { - return source; - } - - @Override - public Tag getSourceTag() { - return this; - } - - @Override - public void setSourceTag(Tag t) { - //nothing - } -} +/* + * 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.tags; + +import com.jpexs.decompiler.flash.DisassemblyListener; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.action.Action; +import com.jpexs.decompiler.flash.action.ActionList; +import com.jpexs.decompiler.flash.action.ConstantPoolTooBigException; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.helpers.GraphTextWriter; +import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; +import com.jpexs.decompiler.flash.types.annotations.Internal; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.Helper; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 6) +public class DoInitActionTag extends Tag implements CharacterIdTag, ASMSource { + + public static final int ID = 59; + + public static final String NAME = "DoInitAction"; + + /** + * Identifier of Sprite + */ + @SWFType(BasicType.UI16) + public int spriteId = 0; + + /** + * List of actions to perform + */ + @HideInRawEdit + public ByteArrayRange actionBytes; + + @Internal + private String scriptName = "-"; + + @Override + public String getScriptName() { + return scriptName; + } + + /** + * Constructor + * + * @param swf + */ + public DoInitActionTag(SWF swf) { + super(swf, ID, NAME, null); + actionBytes = ByteArrayRange.EMPTY; + } + + @Override + public void setScriptName(String scriptName) { + this.scriptName = scriptName; + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public DoInitActionTag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + spriteId = sis.readUI16("spriteId"); + actionBytes = sis.readByteRangeEx(sis.available(), "actionBytes", DumpInfoSpecialType.ACTION_BYTES, sis.getPos()); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(spriteId); + sos.write(getActionBytes()); + //sos.write(Action.actionsToBytes(actions, true, version)); + } + + /** + * Whether or not this object contains ASM source + * + * @return True when contains + */ + @Override + public boolean containsSource() { + return true; + } + + /** + * Converts actions to ASM source + * + * @param exportMode PCode or hex? + * @param writer + * @param actions + * @return ASM source + * @throws java.lang.InterruptedException + */ + @Override + public GraphTextWriter getASMSource(ScriptExportMode exportMode, GraphTextWriter writer, ActionList actions) throws InterruptedException { + if (actions == null) { + actions = getActions(); + } + return Action.actionsToString(listeners, 0, actions, swf.version, exportMode, writer); + } + + @Override + public ActionList getActions() throws InterruptedException { + return SWF.getCachedActionList(this, listeners); + } + + @Override + public void setActions(List actions) { + actionBytes = Action.actionsToByteArrayRange(actions, true, swf.version); + } + + @Override + public ByteArrayRange getActionBytes() { + return actionBytes; + } + + @Override + public void setActionBytes(byte[] actionBytes) { + this.actionBytes = new ByteArrayRange(actionBytes); + SWF.uncache(this); + } + + @Override + public void setConstantPools(List> constantPools) throws ConstantPoolTooBigException { + Action.setConstantPools(this, constantPools, false); + } + + @Override + public void setModified() { + setModified(true); + } + + @Override + public GraphTextWriter getActionBytesAsHex(GraphTextWriter writer) { + return Helper.byteArrayToHexWithHeader(writer, actionBytes.getRangeData()); + } + + @Override + public int getCharacterId() { + return spriteId; + } + + @Override + public void setCharacterId(int characterId) { + this.spriteId = characterId; + } + + List listeners = new ArrayList<>(); + + @Override + public void addDisassemblyListener(DisassemblyListener listener) { + listeners.add(listener); + } + + @Override + public void removeDisassemblyListener(DisassemblyListener listener) { + listeners.remove(listener); + } + + @Override + public String getExportFileName() { + String expName = swf == null ? "" : swf.getExportName(spriteId); + if (expName == null || expName.isEmpty()) { + return super.getExportFileName(); + } + String[] pathParts = expName.contains(".") ? expName.split("\\.") : new String[]{expName}; + return pathParts[pathParts.length - 1]; + } + + @Override + public String getName() { + String expName = swf == null ? "" : swf.getExportName(spriteId); + if (expName == null || expName.isEmpty()) { + return super.getName(); + } + String[] pathParts = expName.contains(".") ? expName.split("\\.") : new String[]{expName}; + return pathParts[pathParts.length - 1]; + } + + @Override + public GraphTextWriter getActionSourcePrefix(GraphTextWriter writer) { + return writer; + } + + @Override + public GraphTextWriter getActionSourceSuffix(GraphTextWriter writer) { + return writer; + } + + @Override + public int getPrefixLineCount() { + return 0; + } + + @Override + public String removePrefixAndSuffix(String source) { + return source; + } + + @Override + public Tag getSourceTag() { + return this; + } + + @Override + public void setSourceTag(Tag t) { + //nothing + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineCompactedFont.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineCompactedFont.java index 1ff43dc49..8928a19e6 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineCompactedFont.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineCompactedFont.java @@ -420,16 +420,16 @@ public final class DefineCompactedFont extends FontTag { ret.fontFlagsWideOffsets = true; ret.fontFlagsWideCodes = true; ret.fontFlagsHasLayout = true; - ret.fontAscent = (getAscent()); - ret.fontDescent = (getDescent()); - ret.fontLeading = (getLeading()); + ret.fontAscent = resize(getAscent()); + ret.fontDescent = resize(getDescent()); + ret.fontLeading = resize(getLeading()); ret.fontAdvanceTable = new ArrayList<>(); ret.fontBoundsTable = new ArrayList<>(); ret.codeTable = new ArrayList<>(); ret.glyphShapeTable = new ArrayList<>(); List shp = getGlyphShapeTable(); for (int g = 0; g < shp.size(); g++) { - ret.fontAdvanceTable.add(resize(getGlyphAdvance(g))); + ret.fontAdvanceTable.add((int) getGlyphAdvance(g)); //already resized ret.codeTable.add((int) glyphToChar(g)); SHAPE shpX = resizeShape(shp.get(g)); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java index 9a9ff0681..5c7162cf6 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java @@ -1,1210 +1,1210 @@ -/* - * 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.timeline; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.exporters.FrameExporter; -import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle; -import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; -import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; -import com.jpexs.decompiler.flash.tags.DefineScalingGridTag; -import com.jpexs.decompiler.flash.tags.DefineSpriteTag; -import com.jpexs.decompiler.flash.tags.DoActionTag; -import com.jpexs.decompiler.flash.tags.DoInitActionTag; -import com.jpexs.decompiler.flash.tags.FrameLabelTag; -import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag; -import com.jpexs.decompiler.flash.tags.ShowFrameTag; -import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag; -import com.jpexs.decompiler.flash.tags.StartSound2Tag; -import com.jpexs.decompiler.flash.tags.StartSoundTag; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.tags.base.ASMSource; -import com.jpexs.decompiler.flash.tags.base.ASMSourceContainer; -import com.jpexs.decompiler.flash.tags.base.BoundedTag; -import com.jpexs.decompiler.flash.tags.base.ButtonTag; -import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; -import com.jpexs.decompiler.flash.tags.base.CharacterTag; -import com.jpexs.decompiler.flash.tags.base.DrawableTag; -import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; -import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; -import com.jpexs.decompiler.flash.tags.base.RemoveTag; -import com.jpexs.decompiler.flash.tags.base.RenderContext; -import com.jpexs.decompiler.flash.tags.base.ShapeTag; -import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; -import com.jpexs.decompiler.flash.tags.base.TextTag; -import com.jpexs.decompiler.flash.types.CLIPACTIONS; -import com.jpexs.decompiler.flash.types.ColorTransform; -import com.jpexs.decompiler.flash.types.MATRIX; -import com.jpexs.decompiler.flash.types.RECT; -import com.jpexs.decompiler.flash.types.filters.BlendComposite; -import com.jpexs.decompiler.flash.types.filters.FILTER; -import com.jpexs.helpers.Helper; -import com.jpexs.helpers.SerializableImage; -import java.awt.AlphaComposite; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.Shape; -import java.awt.geom.AffineTransform; -import java.awt.geom.Area; -import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Stack; -import org.w3c.dom.Element; - -/** - * - * @author JPEXS - */ -public class Timeline { - - public int id; - - public SWF swf; - - public RECT displayRect; - - public float frameRate; - - public Timelined timelined; - - public int maxDepth; - - public int fontFrameNum = -1; - - private final List frames = new ArrayList<>(); - - private final Map depthMaxFrame = new HashMap<>(); - - private final List asmSources = new ArrayList<>(); - - private final List asmSourceContainers = new ArrayList<>(); - - private final Map actionFrames = new HashMap<>(); - - private final Map> soundStramBlocks = new HashMap<>(); - - private AS2Package as2RootPackage; - - public final List otherTags = new ArrayList<>(); - - private boolean initialized = false; - - private Map labelToFrame = new HashMap<>(); - - private void ensureInitialized() { - if (!initialized) { - initialize(); - initialized = true; - } - } - - public List getFrames() { - ensureInitialized(); - return frames; - } - - public Frame getFrame(int index) { - ensureInitialized(); - if (index >= frames.size()) { - return null; - } - return frames.get(index); - } - - public void addFrame(Frame frame) { - ensureInitialized(); - frames.add(frame); - maxDepth = getMaxDepthInternal(); - calculateMaxDepthFrames(); - } - - public AS2Package getAS2RootPackage() { - ensureInitialized(); - return as2RootPackage; - } - - public Map getDepthMaxFrame() { - ensureInitialized(); - return depthMaxFrame; - } - - public List getSoundStreamBlocks(SoundStreamHeadTypeTag head) { - ensureInitialized(); - return soundStramBlocks.get(head); - } - - public Tag getParentTag() { - return timelined instanceof Tag ? (Tag) timelined : null; - } - - public void reset(SWF swf) { - reset(swf, swf, 0, swf.displayRect); - } - - public void reset(SWF swf, Timelined timelined, int id, RECT displayRect) { - initialized = false; - frames.clear(); - depthMaxFrame.clear(); - asmSources.clear(); - asmSourceContainers.clear(); - actionFrames.clear(); - soundStramBlocks.clear(); - otherTags.clear(); - this.id = id; - this.swf = swf; - this.displayRect = displayRect; - this.frameRate = swf.frameRate; - this.timelined = timelined; - as2RootPackage = new AS2Package(null, null, swf); - } - - public final int getMaxDepth() { - ensureInitialized(); - return maxDepth; - } - - private int getMaxDepthInternal() { - int max_depth = 0; - for (Frame f : frames) { - for (int depth : f.layers.keySet()) { - if (depth > max_depth) { - max_depth = depth; - } - int clipDepth = f.layers.get(depth).clipDepth; - if (clipDepth > max_depth) { - max_depth = clipDepth; - } - } - } - return max_depth; - } - - public int getFrameCount() { - ensureInitialized(); - return frames.size(); - } - - public int getRealFrameCount() { - ensureInitialized(); - - int cnt = 1; - for (int i = 1; i < frames.size(); i++) { - if (!frames.get(i).actions.isEmpty()) { - cnt++; - continue; - } - if (frames.get(i).layersChanged) { - cnt++; - } - } - - return cnt; - } - - public int getFrameForAction(ASMSource asm) { - Integer frame = actionFrames.get(asm); - if (frame == null) { - return -1; - } - - return frame; - } - - public Timeline(SWF swf) { - this(swf, swf, 0, swf.displayRect); - } - - public Timeline(SWF swf, Timelined timelined, int id, RECT displayRect) { - this.id = id; - this.swf = swf; - this.displayRect = displayRect; - this.frameRate = swf.frameRate; - this.timelined = timelined; - as2RootPackage = new AS2Package(null, null, swf); - } - - public int getFrameWithLabel(String label) { - if (labelToFrame.containsKey(label)) { - return labelToFrame.get(label); - } - return -1; - } - - private void initialize() { - int frameIdx = 0; - Frame frame = new Frame(this, frameIdx++); - frame.layersChanged = true; - boolean newFrameNeeded = false; - for (Tag t : timelined.getTags()) { - boolean isNested = ShowFrameTag.isNestedTagType(t.getId()); - if (isNested) { - newFrameNeeded = true; - frame.innerTags.add(t); - } - - if (t instanceof ASMSourceContainer) { - ASMSourceContainer asmSourceContainer = (ASMSourceContainer) t; - if (!asmSourceContainer.getSubItems().isEmpty()) { - if (isNested) { - frame.actionContainers.add(asmSourceContainer); - } else { - asmSourceContainers.add(asmSourceContainer); - } - } - } - - if (t instanceof FrameLabelTag) { - newFrameNeeded = true; - frame.label = ((FrameLabelTag) t).getLabelName(); - frame.namedAnchor = ((FrameLabelTag) t).isNamedAnchor(); - labelToFrame.put(frame.label, frames.size()); - } else if (t instanceof StartSoundTag) { - newFrameNeeded = true; - frame.sounds.add(((StartSoundTag) t).soundId); - } else if (t instanceof StartSound2Tag) { - newFrameNeeded = true; - frame.soundClasses.add(((StartSound2Tag) t).soundClassName); - } else if (t instanceof SetBackgroundColorTag) { - newFrameNeeded = true; - frame.backgroundColor = ((SetBackgroundColorTag) t).backgroundColor; - } else if (t instanceof PlaceObjectTypeTag) { - newFrameNeeded = true; - PlaceObjectTypeTag po = (PlaceObjectTypeTag) t; - int depth = po.getDepth(); - DepthState fl = frame.layers.get(depth); - if (fl == null) { - frame.layers.put(depth, fl = new DepthState(swf, frame)); - } - frame.layersChanged = true; - fl.placeObjectTag = po; - fl.minPlaceObjectNum = Math.max(fl.minPlaceObjectNum, po.getPlaceObjectNum()); - int characterId = po.getCharacterId(); - if (characterId != -1) { - fl.characterId = characterId; - } - if (po.flagMove()) { - MATRIX matrix2 = po.getMatrix(); - if (matrix2 != null) { - fl.matrix = matrix2; - } - String instanceName2 = po.getInstanceName(); - if (instanceName2 != null) { - fl.instanceName = instanceName2; - } - ColorTransform colorTransForm2 = po.getColorTransform(); - if (colorTransForm2 != null) { - fl.colorTransForm = colorTransForm2; - } - - CLIPACTIONS clipActions2 = po.getClipActions(); - if (clipActions2 != null) { - fl.clipActions = clipActions2; - } - if (po.cacheAsBitmap()) { - fl.cacheAsBitmap = true; - } - int blendMode2 = po.getBlendMode(); - if (blendMode2 > 0) { - fl.blendMode = blendMode2; - } - List filters2 = po.getFilters(); - if (filters2 != null) { - fl.filters = filters2; - } - int ratio2 = po.getRatio(); - if (ratio2 > -1) { - fl.ratio = ratio2; - } - int clipDepth2 = po.getClipDepth(); - if (clipDepth2 > -1) { - fl.clipDepth = clipDepth2; - } - } else { - fl.matrix = po.getMatrix(); - fl.instanceName = po.getInstanceName(); - fl.colorTransForm = po.getColorTransform(); - fl.cacheAsBitmap = po.cacheAsBitmap(); - fl.blendMode = po.getBlendMode(); - fl.filters = po.getFilters(); - fl.ratio = po.getRatio(); - fl.clipActions = po.getClipActions(); - fl.clipDepth = po.getClipDepth(); - } - fl.key = characterId != -1; - } else if (t instanceof RemoveTag) { - newFrameNeeded = true; - RemoveTag r = (RemoveTag) t; - int depth = r.getDepth(); - frame.layers.remove(depth); - frame.layersChanged = true; - } else if (t instanceof DoActionTag) { - newFrameNeeded = true; - frame.actions.add((DoActionTag) t); - actionFrames.put((DoActionTag) t, frame.frame); - } else if (t instanceof ShowFrameTag) { - frame.showFrameTag = (ShowFrameTag) t; - frames.add(frame); - frame = new Frame(frame, frameIdx++); - newFrameNeeded = false; - } else if (t instanceof ASMSource) { - asmSources.add((ASMSource) t); - } else { - otherTags.add(t); - } - } - if (newFrameNeeded) { - frames.add(frame); - } - - maxDepth = getMaxDepthInternal(); - - detectTweens(); - calculateMaxDepthFrames(); - - createASPackages(); - if (timelined instanceof SWF) { - // popuplate only for main timeline - populateSoundStreamBlocks(0, timelined.getTags()); - } - - initialized = true; - } - - private void detectTweens() { - for (int d = 1; d <= maxDepth; d++) { - int characterId = -1; - int len = 0; - for (int f = 0; f <= frames.size(); f++) { - DepthState ds = f >= frames.size() ? null : frames.get(f).layers.get(d); - - if (ds != null && characterId != -1 && ds.characterId == characterId) { - len++; - } else { - if (characterId != -1) { - int startPos = f - len; - List matrices = new ArrayList<>(len); - for (int k = 0; k < len; k++) { - matrices.add(frames.get(startPos + k).layers.get(d)); - } - - List ranges = TweenDetector.detectRanges(matrices); - for (TweenRange r : ranges) { - for (int t = r.startPosition; t <= r.endPosition; t++) { - DepthState layer = frames.get(startPos + t).layers.get(d); - layer.motionTween = true; - layer.key = false; - } - - frames.get(startPos + r.startPosition).layers.get(d).key = true; - } - } - - len = 1; - } - - characterId = ds == null ? -1 : ds.characterId; - } - } - } - - private void calculateMaxDepthFrames() { - depthMaxFrame.clear(); - for (int d = 1; d <= maxDepth; d++) { - for (int f = frames.size() - 1; f >= 0; f--) { - if (frames.get(f).layers.get(d) != null) { - depthMaxFrame.put(d, f); - break; - } - } - } - } - - private void createASPackages() { - for (ASMSource asm : asmSources) { - if (asm instanceof DoInitActionTag) { - DoInitActionTag initAction = (DoInitActionTag) asm; - String path = swf.getExportName(initAction.spriteId); - path = path != null ? path : "_unk_"; - if (path.isEmpty()) { - path = initAction.getExportFileName(); - } - - String[] pathParts = path.contains(".") ? path.split("\\.") : new String[]{path}; - AS2Package pkg = as2RootPackage; - for (int pos = 0; pos < pathParts.length - 1; pos++) { - String pathPart = pathParts[pos]; - AS2Package subPkg = pkg.subPackages.get(pathPart); - if (subPkg == null) { - subPkg = new AS2Package(pathPart, pkg, swf); - pkg.subPackages.put(pathPart, subPkg); - } - - pkg = subPkg; - } - - pkg.scripts.put(pathParts[pathParts.length - 1], asm); - } - } - } - - private void populateSoundStreamBlocks(int containerId, Iterable tags) { - List blocks = null; - for (Tag t : tags) { - if (t instanceof SoundStreamHeadTypeTag) { - SoundStreamHeadTypeTag head = (SoundStreamHeadTypeTag) t; - head.setVirtualCharacterId(containerId); - blocks = new ArrayList<>(); - soundStramBlocks.put(head, blocks); - continue; - } - - if (t instanceof DefineSpriteTag) { - DefineSpriteTag sprite = (DefineSpriteTag) t; - populateSoundStreamBlocks(sprite.getCharacterId(), sprite.getTags()); - } - - if (blocks == null) { - continue; - } - - if (t instanceof SoundStreamBlockTag) { - blocks.add((SoundStreamBlockTag) t); - } - } - } - - public void getNeededCharacters(Set usedCharacters) { - for (int i = 0; i < getFrameCount(); i++) { - getNeededCharacters(i, usedCharacters); - } - } - - public void getNeededCharacters(List frames, Set usedCharacters) { - for (int frame = 0; frame < getFrameCount(); frame++) { - getNeededCharacters(frame, usedCharacters); - } - } - - public void getNeededCharacters(int frame, Set usedCharacters) { - Frame frameObj = getFrame(frame); - for (int depth : frameObj.layers.keySet()) { - DepthState layer = frameObj.layers.get(depth); - if (layer.characterId != -1) { - if (!swf.getCharacters().containsKey(layer.characterId)) { - continue; - } - usedCharacters.add(layer.characterId); - swf.getCharacter(layer.characterId).getNeededCharactersDeep(usedCharacters); - } - } - } - - public boolean replaceCharacter(int oldCharacterId, int newCharacterId) { - boolean modified = false; - for (int i = 0; i < timelined.getTags().size(); i++) { - Tag t = timelined.getTags().get(i); - if (t instanceof CharacterIdTag && ((CharacterIdTag) t).getCharacterId() == oldCharacterId) { - ((CharacterIdTag) t).setCharacterId(newCharacterId); - ((Tag) t).setModified(true); - modified = true; - } - } - return modified; - } - - public boolean removeCharacter(int characterId) { - boolean modified = false; - for (int i = 0; i < timelined.getTags().size(); i++) { - Tag t = timelined.getTags().get(i); - if (t instanceof CharacterIdTag && ((CharacterIdTag) t).getCharacterId() == characterId) { - timelined.removeTag(i); - i--; - modified = true; - } - } - return modified; - } - - public double roundToPixel(double val) { - return Math.rint(val / SWF.unitDivisor) * SWF.unitDivisor; - } - - /*public void toImage(int frame, int time, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix prevTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) { - ExportRectangle scalingGrid = null; - if (timelined instanceof CharacterTag) { - DefineScalingGridTag sgt = ((CharacterTag) timelined).getScalingGridTag(); - if (sgt != null) { - scalingGrid = new ExportRectangle(sgt.splitter); - } - } - - if (scalingGrid == null || transformation.rotateSkew0 != 0 || transformation.rotateSkew1 != 0) { - toImage(frame, time, renderContext, image, isClip, transformation, absoluteTransformation, colorTransform, null); - return; - } - - //9-slice scaling using DefineScalingGrid - Matrix diffTransform = prevTransformation.inverse().preConcatenate(transformation); - transformation = diffTransform; - - Matrix prevScale = new Matrix(); - prevScale.scaleX = prevTransformation.scaleX; - prevScale.scaleY = prevTransformation.scaleY; - - ExportRectangle boundsRect = new ExportRectangle(timelined.getRect()); - - - 0 | 1 | 2 - ------------ - 3 | 4 | 5 - ------------ - 6 | 7 | 8 - - ExportRectangle targetRect[] = new ExportRectangle[9]; - ExportRectangle sourceRect[] = new ExportRectangle[9]; - Matrix transforms[] = new Matrix[9]; - - DefineScalingGridTag.getSlices(transformation, prevScale, boundsRect, scalingGrid, sourceRect, targetRect, transforms); - - for (int i = 0; i < targetRect.length; i++) { - toImage(frame, time, renderContext, image, isClip, transforms[i], absoluteTransformation, colorTransform, targetRect[i]); - } - }*/ - private void drawDrawable(Matrix strokeTransform, DepthState layer, Matrix layerMatrix, Graphics2D g, ColorTransform colorTransForm, int blendMode, List clips, Matrix transformation, boolean isClip, int clipDepth, Matrix absMat, int time, int ratio, RenderContext renderContext, SerializableImage image, DrawableTag drawable, List filters, double unzoom, ColorTransform clrTrans) { - Matrix drawMatrix = new Matrix(); - int drawableFrameCount = drawable.getNumFrames(); - if (drawableFrameCount == 0) { - drawableFrameCount = 1; - } - - RECT boundRect = drawable.getRect(); - - ExportRectangle rect = new ExportRectangle(boundRect); - Matrix mat = transformation.concatenate(layerMatrix); - rect = mat.transform(rect); - - boolean cacheAsBitmap = layer.cacheAsBitmap() && layer.placeObjectTag != null && drawable.isSingleFrame(); - /* // draw bounds - AffineTransform trans = mat.preConcatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).toTransform(); - g.setTransform(trans); - BoundedTag b = (BoundedTag) drawable; - g.setPaint(new Color(255, 255, 255, 128)); - g.setComposite(BlendComposite.Invert); - g.setStroke(new BasicStroke((int) SWF.unitDivisor)); - RECT r = b.getRect(); - g.setFont(g.getFont().deriveFont((float) (12 * SWF.unitDivisor))); - g.drawString(drawable.toString(), r.Xmin + (int) (3 * SWF.unitDivisor), r.Ymin + (int) (15 * SWF.unitDivisor)); - g.draw(new Rectangle(r.Xmin, r.Ymin, r.getWidth(), r.getHeight())); - g.drawLine(r.Xmin, r.Ymin, r.Xmax, r.Ymax); - g.drawLine(r.Xmax, r.Ymin, r.Xmin, r.Ymax); - g.setComposite(AlphaComposite.Dst);*/ - - SerializableImage img = null; - if (cacheAsBitmap && renderContext.displayObjectCache != null) { - img = renderContext.displayObjectCache.get(layer.placeObjectTag); - } - - int stateCount = renderContext.stateUnderCursor == null ? 0 : renderContext.stateUnderCursor.size(); - int dframe; - if (fontFrameNum != -1) { - dframe = fontFrameNum; - } else { - dframe = time % drawableFrameCount; - } - - if (filters != null && filters.size() > 0) { - // calculate size after applying the filters - double deltaXMax = 0; - double deltaYMax = 0; - for (FILTER filter : filters) { - double x = filter.getDeltaX(); - double y = filter.getDeltaY(); - deltaXMax = Math.max(x, deltaXMax); - deltaYMax = Math.max(y, deltaYMax); - } - rect.xMin -= deltaXMax * unzoom; - rect.xMax += deltaXMax * unzoom; - rect.yMin -= deltaYMax * unzoom; - rect.yMax += deltaYMax * unzoom; - } - - rect.xMin -= unzoom; - rect.yMin -= unzoom; - rect.xMin = Math.max(0, rect.xMin); - rect.yMin = Math.max(0, rect.yMin); - drawMatrix.translate(rect.xMin, rect.yMin); - - if (img == null) { - int newWidth = (int) (rect.getWidth() / unzoom); - int newHeight = (int) (rect.getHeight() / unzoom); - int deltaX = (int) (rect.xMin / unzoom); - int deltaY = (int) (rect.yMin / unzoom); - newWidth = Math.min(image.getWidth() - deltaX, newWidth) + 1; - newHeight = Math.min(image.getHeight() - deltaY, newHeight) + 1; - - if (newWidth <= 0 || newHeight <= 0) { - return; - } - - Matrix m = mat.preConcatenate(Matrix.getTranslateInstance(-rect.xMin, -rect.yMin)); - //strokeTransform = strokeTransform.clone(); - //strokeTransform.translate(-rect.xMin, -rect.yMin); - - if (drawable instanceof ButtonTag) { - dframe = ButtonTag.FRAME_UP; - if (renderContext.cursorPosition != null) { - Shape buttonShape = drawable.getOutline(ButtonTag.FRAME_HITTEST, time, ratio, renderContext, absMat, true); - if (buttonShape.contains(renderContext.cursorPosition)) { - renderContext.mouseOverButton = (ButtonTag) drawable; - if (renderContext.mouseButton > 0) { - dframe = ButtonTag.FRAME_DOWN; - } else { - dframe = ButtonTag.FRAME_OVER; - } - } - } - } - - img = new SerializableImage(newWidth, newHeight, SerializableImage.TYPE_INT_ARGB_PRE); - img.fillTransparent(); - - drawable.toImage(dframe, time, ratio, renderContext, img, isClip || clipDepth > -1, m, strokeTransform, absMat, clrTrans); - - if (filters != null) { - for (FILTER filter : filters) { - img = filter.apply(img); - } - } - if (blendMode > 1) { - if (colorTransForm != null) { - img = colorTransForm.apply(img); - } - } - - if (cacheAsBitmap && renderContext.displayObjectCache != null) { - renderContext.displayObjectCache.put(layer.placeObjectTag, img); - } - } - - drawMatrix.translateX /= unzoom; - drawMatrix.translateY /= unzoom; - AffineTransform trans = drawMatrix.toTransform(); - - switch (blendMode) { - case 0: - case 1: - g.setComposite(AlphaComposite.SrcOver); - break; - case 2: // Layer - g.setComposite(AlphaComposite.SrcOver); - break; - case 3: - g.setComposite(BlendComposite.Multiply); - break; - case 4: - g.setComposite(BlendComposite.Screen); - break; - case 5: - g.setComposite(BlendComposite.Lighten); - break; - case 6: - g.setComposite(BlendComposite.Darken); - break; - case 7: - g.setComposite(BlendComposite.Difference); - break; - case 8: - g.setComposite(BlendComposite.Add); - break; - case 9: - g.setComposite(BlendComposite.Subtract); - break; - case 10: - g.setComposite(BlendComposite.Invert); - break; - case 11: - g.setComposite(BlendComposite.Alpha); - break; - case 12: - g.setComposite(BlendComposite.Erase); - break; - case 13: - g.setComposite(BlendComposite.Overlay); - break; - case 14: - g.setComposite(BlendComposite.HardLight); - break; - default: // Not implemented - g.setComposite(AlphaComposite.SrcOver); - break; - } - - if (clipDepth > -1) { - BufferedImage mask = new BufferedImage(image.getWidth(), image.getHeight(), image.getType()); - Graphics2D gm = (Graphics2D) mask.getGraphics(); - gm.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - gm.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); - gm.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - gm.setComposite(AlphaComposite.Src); - gm.setColor(new Color(0, 0, 0, 0f)); - gm.fillRect(0, 0, image.getWidth(), image.getHeight()); - gm.setTransform(trans); - gm.drawImage(img.getBufferedImage(), 0, 0, null); - Clip clip = new Clip(Helper.imageToShape(mask), clipDepth); // Maybe we can get current outline instead converting from image (?) - clips.add(clip); - } else { - if (renderContext.cursorPosition != null) { - if (drawable instanceof DefineSpriteTag) { - if (renderContext.stateUnderCursor.size() > stateCount) { - renderContext.stateUnderCursor.add(layer); - } - } else if (absMat.transform(new ExportRectangle(boundRect)).contains(renderContext.cursorPosition)) { - Shape shape = drawable.getOutline(dframe, time, layer.ratio, renderContext, absMat, true); - if (shape.contains(renderContext.cursorPosition)) { - renderContext.stateUnderCursor.add(layer); - } - } - } - - if (renderContext.borderImage != null) { - Graphics2D g2 = (Graphics2D) renderContext.borderImage.getGraphics(); - g2.setPaint(Color.red); - g2.setStroke(new BasicStroke(2)); - Shape shape = drawable.getOutline(dframe, time, layer.ratio, renderContext, absMat.preConcatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)), true); - g2.draw(shape); - } - - g.setTransform(trans); - g.drawImage(img.getBufferedImage(), 0, 0, null); - } - } - - public void toImage(int frame, int time, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) { - double unzoom = SWF.unitDivisor; - if (getFrameCount() <= frame) { - return; - } - - Frame frameObj = getFrame(frame); - Graphics2D g = (Graphics2D) image.getGraphics(); - Shape prevClip = g.getClip(); - //if (targetRect != null) { - // g.setClip(new Rectangle2D.Double(targetRect.xMin, targetRect.yMin, targetRect.getWidth(), targetRect.getHeight())); - //} - - g.setPaint(frameObj.backgroundColor.toColor()); - g.fill(new Rectangle(image.getWidth(), image.getHeight())); - - g.setTransform(transformation.toTransform()); - List clips = new ArrayList<>(); - - int maxDepth = getMaxDepth(); - int clipCount = 0; - for (int i = 1; i <= maxDepth; i++) { - boolean clipChanged = clipCount != clips.size(); - for (int c = 0; c < clips.size(); c++) { - if (clips.get(c).depth < i) { - clips.remove(c); - clipChanged = true; - } - } - - if (clipChanged) { - if (clips.size() > 0) { - Area clip = null; - for (Clip clip1 : clips) { - Shape shape = clip1.shape; - if (clip == null) { - clip = new Area(shape); - } else { - clip.intersect(new Area(shape)); - } - } - - g.setTransform(new AffineTransform()); - g.setClip(clip); - - // draw clip border - //g.setPaint(Color.red); - //g.setStroke(new BasicStroke(2)); - //g.draw(clip); - } else { - g.setClip(null); - } - - clipCount = clips.size(); - } - - if (!frameObj.layers.containsKey(i)) { - continue; - } - DepthState layer = frameObj.layers.get(i); - if (!swf.getCharacters().containsKey(layer.characterId)) { - continue; - } - if (!layer.isVisible) { - continue; - } - - CharacterTag character = swf.getCharacter(layer.characterId); - Matrix layerMatrix = new Matrix(layer.matrix); - Matrix mat = transformation.concatenate(layerMatrix); - Matrix absMat = absoluteTransformation.concatenate(layerMatrix); - - ColorTransform clrTrans = colorTransform; - if (layer.colorTransForm != null && layer.blendMode <= 1) { // Normal blend mode - clrTrans = clrTrans == null ? layer.colorTransForm : colorTransform.merge(layer.colorTransForm); - } - - boolean showPlaceholder = false; - if (character instanceof DrawableTag) { - - RECT scalingRect = null; - DefineScalingGridTag sgt = character.getScalingGridTag(); - if (sgt != null) { - scalingRect = sgt.splitter; - } - - if (scalingRect != null) { - ExportRectangle sourceRect[] = new ExportRectangle[9]; - ExportRectangle targetRect[] = new ExportRectangle[9]; - Matrix transforms[] = new Matrix[9]; - //mat => image - //t => - //Matrix tx = transformation.concatenate(layerMatrix); - DrawableTag dr = (DrawableTag) character; - ExportRectangle boundsRect = new ExportRectangle(dr.getRect()); - ExportRectangle targetBoundsRect = layerMatrix.transform(boundsRect); - DefineScalingGridTag.getSlices(targetBoundsRect, boundsRect, new ExportRectangle(scalingRect), sourceRect, targetRect, transforms); - Shape c = g.getClip(); - AffineTransform origTransform = g.getTransform(); - for (int s = 0; s < 9; s++) { - g.setTransform(new AffineTransform()); - ExportRectangle p1 = transformation.transform(targetRect[s]); - g.setClip(c); - - Rectangle2D r = new Rectangle2D.Double(p1.xMin, p1.yMin, p1.getWidth(), p1.getHeight()); - g.setClip(r); - drawDrawable(strokeTransformation.preConcatenate(layerMatrix), layer, transforms[s], g, colorTransform, layer.blendMode, clips, transformation, isClip, layer.clipDepth, absMat, time, layer.ratio, renderContext, image, (DrawableTag) character, layer.filters, unzoom, clrTrans); - - } - g.setClip(c); - - /* - for (int s = 0; s < 9; s++) { - g.setTransform(new AffineTransform()); - ExportRectangle p1 = transformation.transform(targetRect[s]); - g.setClip(c); - - Rectangle2D r = new Rectangle2D.Double(p1.xMin, p1.yMin, p1.getWidth(), p1.getHeight()); - g.setColor(Color.blue); - g.draw(r); - - }*/ - g.setTransform(origTransform); - } else { - drawDrawable(strokeTransformation, layer, layerMatrix, g, colorTransform, layer.blendMode, clips, transformation, isClip, layer.clipDepth, absMat, time, layer.ratio, renderContext, image, (DrawableTag) character, layer.filters, unzoom, clrTrans); - } - } else if (character instanceof BoundedTag) { - showPlaceholder = true; - } - - if (showPlaceholder) { - AffineTransform trans = mat.preConcatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).toTransform(); - g.setTransform(trans); - BoundedTag b = (BoundedTag) character; - g.setPaint(new Color(255, 255, 255, 128)); - g.setComposite(BlendComposite.Invert); - g.setStroke(new BasicStroke((int) SWF.unitDivisor)); - RECT r = b.getRect(); - g.setFont(g.getFont().deriveFont((float) (12 * SWF.unitDivisor))); - g.drawString(character.toString(), r.Xmin + (int) (3 * SWF.unitDivisor), r.Ymin + (int) (15 * SWF.unitDivisor)); - g.draw(new Rectangle(r.Xmin, r.Ymin, r.getWidth(), r.getHeight())); - g.drawLine(r.Xmin, r.Ymin, r.Xmax, r.Ymax); - g.drawLine(r.Xmax, r.Ymin, r.Xmin, r.Ymax); - g.setComposite(AlphaComposite.Dst); - } - } - - g.setTransform(new AffineTransform()); - g.setClip(prevClip); - } - - public void toSVG(int frame, int time, DepthState stateUnderCursor, int mouseButton, SVGExporter exporter, ColorTransform colorTransform, int level) throws IOException { - if (getFrameCount() <= frame) { - return; - } - - Frame frameObj = getFrame(frame); - List clips = new ArrayList<>(); - - int maxDepth = getMaxDepth(); - int clipCount = 0; - Element clipGroup = null; - for (int i = 1; i <= maxDepth; i++) { - boolean clipChanged = clipCount != clips.size(); - for (int c = 0; c < clips.size(); c++) { - if (clips.get(c).depth < i) { - clips.remove(c); - clipChanged = true; - } - } - - if (clipChanged) { - if (clipGroup != null) { - exporter.endGroup(); - } - - if (clips.size() > 0) { - String clip = clips.get(clips.size() - 1).shape; // todo: merge clip areas - clipGroup = exporter.createSubGroup(null, null); - clipGroup.setAttribute("clip-path", "url(#" + clip + ")"); - } - - clipCount = clips.size(); - } - - if (!frameObj.layers.containsKey(i)) { - continue; - } - DepthState layer = frameObj.layers.get(i); - if (!swf.getCharacters().containsKey(layer.characterId)) { - continue; - } - if (!layer.isVisible) { - continue; - } - - CharacterTag character = swf.getCharacter(layer.characterId); - - ColorTransform clrTrans = colorTransform; - if (layer.colorTransForm != null && layer.blendMode <= 1) { // Normal blend mode - clrTrans = clrTrans == null ? layer.colorTransForm : colorTransform.merge(layer.colorTransForm); - } - - if (character instanceof DrawableTag) { - DrawableTag drawable = (DrawableTag) character; - - String assetName; - Tag drawableTag = (Tag) drawable; - RECT boundRect = drawable.getRect(); - if (exporter.exportedTags.containsKey(drawableTag)) { - assetName = exporter.exportedTags.get(drawableTag); - } else { - assetName = getTagIdPrefix(drawableTag, exporter); - exporter.exportedTags.put(drawableTag, assetName); - exporter.createDefGroup(new ExportRectangle(boundRect), assetName); - drawable.toSVG(exporter, layer.ratio, clrTrans, level + 1); - exporter.endGroup(); - } - ExportRectangle rect = new ExportRectangle(boundRect); - - // TODO: if (layer.filters != null) - // TODO: if (layer.blendMode > 1) - if (layer.clipDepth > -1) { - String clipName = exporter.getUniqueId("clipPath"); - exporter.createClipPath(new Matrix(), clipName); - SvgClip clip = new SvgClip(clipName, layer.clipDepth); - clips.add(clip); - Matrix mat = Matrix.getTranslateInstance(rect.xMin, rect.yMin).preConcatenate(new Matrix(layer.matrix)); - exporter.addUse(mat, boundRect, assetName, layer.instanceName); - exporter.endGroup(); - } else { - Matrix mat = Matrix.getTranslateInstance(rect.xMin, rect.yMin).preConcatenate(new Matrix(layer.matrix)); - exporter.addUse(mat, boundRect, assetName, layer.instanceName); - } - } - } - - if (clipGroup != null) { - exporter.endGroup(); - } - } - - private static String getTagIdPrefix(Tag tag, SVGExporter exporter) { - if (tag instanceof ShapeTag) { - return exporter.getUniqueId("shape"); - } - if (tag instanceof MorphShapeTag) { - return exporter.getUniqueId("morphshape"); - } - if (tag instanceof DefineSpriteTag) { - return exporter.getUniqueId("sprite"); - } - if (tag instanceof TextTag) { - return exporter.getUniqueId("text"); - } - if (tag instanceof ButtonTag) { - return exporter.getUniqueId("button"); - } - return exporter.getUniqueId("tag"); - } - - public void toHtmlCanvas(StringBuilder result, double unitDivisor, List frames) { - FrameExporter.framesToHtmlCanvas(result, unitDivisor, this, frames, 0, null, 0, displayRect, null, null); - } - - public void getSounds(int frame, int time, ButtonTag mouseOverButton, int mouseButton, List sounds, List soundClasses) { - Frame fr = getFrame(frame); - sounds.addAll(fr.sounds); - soundClasses.addAll(fr.soundClasses); - for (int d = maxDepth; d >= 0; d--) { - DepthState ds = fr.layers.get(d); - if (ds != null) { - CharacterTag c = swf.getCharacter(ds.characterId); - if (c instanceof Timelined) { - int frameCount = ((Timelined) c).getTimeline().frames.size(); - if (frameCount == 0) { - continue; - } - int dframe = time % frameCount; - if (c instanceof ButtonTag) { - dframe = ButtonTag.FRAME_UP; - if (mouseOverButton == c) { - if (mouseButton > 0) { - dframe = ButtonTag.FRAME_DOWN; - } else { - dframe = ButtonTag.FRAME_OVER; - } - } - } - ((Timelined) c).getTimeline().getSounds(dframe, time, mouseOverButton, mouseButton, sounds, soundClasses); - } - } - } - } - - public Shape getOutline(int frame, int time, RenderContext renderContext, Matrix transformation, boolean stroked) { - Frame fr = getFrame(frame); - Area area = new Area(); - Stack clips = new Stack<>(); - for (int d = maxDepth; d >= 0; d--) { - Clip currentClip = null; - for (int i = clips.size() - 1; i >= 0; i--) { - Clip cl = clips.get(i); - if (cl.depth <= d) { - clips.remove(i); - } - } - if (!clips.isEmpty()) { - currentClip = clips.peek(); - } - DepthState layer = fr.layers.get(d); - if (layer == null) { - continue; - } - if (!layer.isVisible) { - continue; - } - CharacterTag character = swf.getCharacter(layer.characterId); - if (character instanceof DrawableTag) { - DrawableTag drawable = (DrawableTag) character; - Matrix m = transformation.concatenate(new Matrix(layer.matrix)); - - int drawableFrameCount = drawable.getNumFrames(); - if (drawableFrameCount == 0) { - drawableFrameCount = 1; - } - - int dframe = time % drawableFrameCount; - if (character instanceof ButtonTag) { - dframe = ButtonTag.FRAME_UP; - if (renderContext.cursorPosition != null) { - ButtonTag buttonTag = (ButtonTag) character; - Shape buttonShape = buttonTag.getOutline(ButtonTag.FRAME_HITTEST, time, layer.ratio, renderContext, m, stroked); - if (buttonShape.contains(renderContext.cursorPosition)) { - if (renderContext.mouseButton > 0) { - dframe = ButtonTag.FRAME_DOWN; - } else { - dframe = ButtonTag.FRAME_OVER; - } - } - } - } - - Shape cshape = ((DrawableTag) character).getOutline(dframe, time, layer.ratio, renderContext, m, stroked); - Area addArea = new Area(cshape); - if (currentClip != null) { - Area a = new Area(new Rectangle(displayRect.Xmin, displayRect.Ymin, displayRect.getWidth(), displayRect.getHeight())); - a.subtract(new Area(currentClip.shape)); - addArea.subtract(a); - } - - if (layer.clipDepth > -1) { - Clip clip = new Clip(addArea, layer.clipDepth); - clips.push(clip); - } else { - area.add(addArea); - } - } - } - return area; - } - - public boolean isSingleFrame() { - for (int i = 0; i < getFrameCount(); i++) { - if (!isSingleFrame(i)) { - return false; - } - } - return true; - } - - public boolean isSingleFrame(int frame) { - Frame frameObj = getFrame(frame); - for (int i = 1; i <= maxDepth; i++) { - if (!frameObj.layers.containsKey(i)) { - continue; - } - DepthState layer = frameObj.layers.get(i); - if (!swf.getCharacters().containsKey(layer.characterId)) { - continue; - } - if (!layer.isVisible) { - continue; - } - CharacterTag character = swf.getCharacter(layer.characterId); - if (character instanceof DrawableTag) { - DrawableTag drawable = (DrawableTag) character; - if (!drawable.isSingleFrame()) { - return false; - } - } - } - - return true; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof Timeline) { - Timeline timelineObj = (Timeline) obj; - return timelined.equals(timelineObj.timelined); - } - - return false; - } -} +/* + * 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.timeline; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.exporters.FrameExporter; +import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; +import com.jpexs.decompiler.flash.tags.DefineScalingGridTag; +import com.jpexs.decompiler.flash.tags.DefineSpriteTag; +import com.jpexs.decompiler.flash.tags.DoActionTag; +import com.jpexs.decompiler.flash.tags.DoInitActionTag; +import com.jpexs.decompiler.flash.tags.FrameLabelTag; +import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag; +import com.jpexs.decompiler.flash.tags.ShowFrameTag; +import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag; +import com.jpexs.decompiler.flash.tags.StartSound2Tag; +import com.jpexs.decompiler.flash.tags.StartSoundTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.flash.tags.base.ASMSourceContainer; +import com.jpexs.decompiler.flash.tags.base.BoundedTag; +import com.jpexs.decompiler.flash.tags.base.ButtonTag; +import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.DrawableTag; +import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; +import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; +import com.jpexs.decompiler.flash.tags.base.RemoveTag; +import com.jpexs.decompiler.flash.tags.base.RenderContext; +import com.jpexs.decompiler.flash.tags.base.ShapeTag; +import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; +import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.types.CLIPACTIONS; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.MATRIX; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.filters.BlendComposite; +import com.jpexs.decompiler.flash.types.filters.FILTER; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.SerializableImage; +import java.awt.AlphaComposite; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Area; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; +import org.w3c.dom.Element; + +/** + * + * @author JPEXS + */ +public class Timeline { + + public int id; + + public SWF swf; + + public RECT displayRect; + + public float frameRate; + + public Timelined timelined; + + public int maxDepth; + + public int fontFrameNum = -1; + + private final List frames = new ArrayList<>(); + + private final Map depthMaxFrame = new HashMap<>(); + + private final List asmSources = new ArrayList<>(); + + private final List asmSourceContainers = new ArrayList<>(); + + private final Map actionFrames = new HashMap<>(); + + private final Map> soundStramBlocks = new HashMap<>(); + + private AS2Package as2RootPackage; + + public final List otherTags = new ArrayList<>(); + + private boolean initialized = false; + + private Map labelToFrame = new HashMap<>(); + + private void ensureInitialized() { + if (!initialized) { + initialize(); + initialized = true; + } + } + + public List getFrames() { + ensureInitialized(); + return frames; + } + + public Frame getFrame(int index) { + ensureInitialized(); + if (index >= frames.size()) { + return null; + } + return frames.get(index); + } + + public void addFrame(Frame frame) { + ensureInitialized(); + frames.add(frame); + maxDepth = getMaxDepthInternal(); + calculateMaxDepthFrames(); + } + + public AS2Package getAS2RootPackage() { + ensureInitialized(); + return as2RootPackage; + } + + public Map getDepthMaxFrame() { + ensureInitialized(); + return depthMaxFrame; + } + + public List getSoundStreamBlocks(SoundStreamHeadTypeTag head) { + ensureInitialized(); + return soundStramBlocks.get(head); + } + + public Tag getParentTag() { + return timelined instanceof Tag ? (Tag) timelined : null; + } + + public void reset(SWF swf) { + reset(swf, swf, 0, swf.displayRect); + } + + public void reset(SWF swf, Timelined timelined, int id, RECT displayRect) { + initialized = false; + frames.clear(); + depthMaxFrame.clear(); + asmSources.clear(); + asmSourceContainers.clear(); + actionFrames.clear(); + soundStramBlocks.clear(); + otherTags.clear(); + this.id = id; + this.swf = swf; + this.displayRect = displayRect; + this.frameRate = swf.frameRate; + this.timelined = timelined; + as2RootPackage = new AS2Package(null, null, swf); + } + + public final int getMaxDepth() { + ensureInitialized(); + return maxDepth; + } + + private int getMaxDepthInternal() { + int max_depth = 0; + for (Frame f : frames) { + for (int depth : f.layers.keySet()) { + if (depth > max_depth) { + max_depth = depth; + } + int clipDepth = f.layers.get(depth).clipDepth; + if (clipDepth > max_depth) { + max_depth = clipDepth; + } + } + } + return max_depth; + } + + public int getFrameCount() { + ensureInitialized(); + return frames.size(); + } + + public int getRealFrameCount() { + ensureInitialized(); + + int cnt = 1; + for (int i = 1; i < frames.size(); i++) { + if (!frames.get(i).actions.isEmpty()) { + cnt++; + continue; + } + if (frames.get(i).layersChanged) { + cnt++; + } + } + + return cnt; + } + + public int getFrameForAction(ASMSource asm) { + Integer frame = actionFrames.get(asm); + if (frame == null) { + return -1; + } + + return frame; + } + + public Timeline(SWF swf) { + this(swf, swf, 0, swf.displayRect); + } + + public Timeline(SWF swf, Timelined timelined, int id, RECT displayRect) { + this.id = id; + this.swf = swf; + this.displayRect = displayRect; + this.frameRate = swf.frameRate; + this.timelined = timelined; + as2RootPackage = new AS2Package(null, null, swf); + } + + public int getFrameWithLabel(String label) { + if (labelToFrame.containsKey(label)) { + return labelToFrame.get(label); + } + return -1; + } + + private void initialize() { + int frameIdx = 0; + Frame frame = new Frame(this, frameIdx++); + frame.layersChanged = true; + boolean newFrameNeeded = false; + for (Tag t : timelined.getTags()) { + boolean isNested = ShowFrameTag.isNestedTagType(t.getId()); + if (isNested) { + newFrameNeeded = true; + frame.innerTags.add(t); + } + + if (t instanceof ASMSourceContainer) { + ASMSourceContainer asmSourceContainer = (ASMSourceContainer) t; + if (!asmSourceContainer.getSubItems().isEmpty()) { + if (isNested) { + frame.actionContainers.add(asmSourceContainer); + } else { + asmSourceContainers.add(asmSourceContainer); + } + } + } + + if (t instanceof FrameLabelTag) { + newFrameNeeded = true; + frame.label = ((FrameLabelTag) t).getLabelName(); + frame.namedAnchor = ((FrameLabelTag) t).isNamedAnchor(); + labelToFrame.put(frame.label, frames.size()); + } else if (t instanceof StartSoundTag) { + newFrameNeeded = true; + frame.sounds.add(((StartSoundTag) t).soundId); + } else if (t instanceof StartSound2Tag) { + newFrameNeeded = true; + frame.soundClasses.add(((StartSound2Tag) t).soundClassName); + } else if (t instanceof SetBackgroundColorTag) { + newFrameNeeded = true; + frame.backgroundColor = ((SetBackgroundColorTag) t).backgroundColor; + } else if (t instanceof PlaceObjectTypeTag) { + newFrameNeeded = true; + PlaceObjectTypeTag po = (PlaceObjectTypeTag) t; + int depth = po.getDepth(); + DepthState fl = frame.layers.get(depth); + if (fl == null) { + frame.layers.put(depth, fl = new DepthState(swf, frame)); + } + frame.layersChanged = true; + fl.placeObjectTag = po; + fl.minPlaceObjectNum = Math.max(fl.minPlaceObjectNum, po.getPlaceObjectNum()); + int characterId = po.getCharacterId(); + if (characterId != -1) { + fl.characterId = characterId; + } + if (po.flagMove()) { + MATRIX matrix2 = po.getMatrix(); + if (matrix2 != null) { + fl.matrix = matrix2; + } + String instanceName2 = po.getInstanceName(); + if (instanceName2 != null) { + fl.instanceName = instanceName2; + } + ColorTransform colorTransForm2 = po.getColorTransform(); + if (colorTransForm2 != null) { + fl.colorTransForm = colorTransForm2; + } + + CLIPACTIONS clipActions2 = po.getClipActions(); + if (clipActions2 != null) { + fl.clipActions = clipActions2; + } + if (po.cacheAsBitmap()) { + fl.cacheAsBitmap = true; + } + int blendMode2 = po.getBlendMode(); + if (blendMode2 > 0) { + fl.blendMode = blendMode2; + } + List filters2 = po.getFilters(); + if (filters2 != null) { + fl.filters = filters2; + } + int ratio2 = po.getRatio(); + if (ratio2 > -1) { + fl.ratio = ratio2; + } + int clipDepth2 = po.getClipDepth(); + if (clipDepth2 > -1) { + fl.clipDepth = clipDepth2; + } + } else { + fl.matrix = po.getMatrix(); + fl.instanceName = po.getInstanceName(); + fl.colorTransForm = po.getColorTransform(); + fl.cacheAsBitmap = po.cacheAsBitmap(); + fl.blendMode = po.getBlendMode(); + fl.filters = po.getFilters(); + fl.ratio = po.getRatio(); + fl.clipActions = po.getClipActions(); + fl.clipDepth = po.getClipDepth(); + } + fl.key = characterId != -1; + } else if (t instanceof RemoveTag) { + newFrameNeeded = true; + RemoveTag r = (RemoveTag) t; + int depth = r.getDepth(); + frame.layers.remove(depth); + frame.layersChanged = true; + } else if (t instanceof DoActionTag) { + newFrameNeeded = true; + frame.actions.add((DoActionTag) t); + actionFrames.put((DoActionTag) t, frame.frame); + } else if (t instanceof ShowFrameTag) { + frame.showFrameTag = (ShowFrameTag) t; + frames.add(frame); + frame = new Frame(frame, frameIdx++); + newFrameNeeded = false; + } else if (t instanceof ASMSource) { + asmSources.add((ASMSource) t); + } else { + otherTags.add(t); + } + } + if (newFrameNeeded) { + frames.add(frame); + } + + maxDepth = getMaxDepthInternal(); + + detectTweens(); + calculateMaxDepthFrames(); + + createASPackages(); + if (timelined instanceof SWF) { + // popuplate only for main timeline + populateSoundStreamBlocks(0, timelined.getTags()); + } + + initialized = true; + } + + private void detectTweens() { + for (int d = 1; d <= maxDepth; d++) { + int characterId = -1; + int len = 0; + for (int f = 0; f <= frames.size(); f++) { + DepthState ds = f >= frames.size() ? null : frames.get(f).layers.get(d); + + if (ds != null && characterId != -1 && ds.characterId == characterId) { + len++; + } else { + if (characterId != -1) { + int startPos = f - len; + List matrices = new ArrayList<>(len); + for (int k = 0; k < len; k++) { + matrices.add(frames.get(startPos + k).layers.get(d)); + } + + List ranges = TweenDetector.detectRanges(matrices); + for (TweenRange r : ranges) { + for (int t = r.startPosition; t <= r.endPosition; t++) { + DepthState layer = frames.get(startPos + t).layers.get(d); + layer.motionTween = true; + layer.key = false; + } + + frames.get(startPos + r.startPosition).layers.get(d).key = true; + } + } + + len = 1; + } + + characterId = ds == null ? -1 : ds.characterId; + } + } + } + + private void calculateMaxDepthFrames() { + depthMaxFrame.clear(); + for (int d = 1; d <= maxDepth; d++) { + for (int f = frames.size() - 1; f >= 0; f--) { + if (frames.get(f).layers.get(d) != null) { + depthMaxFrame.put(d, f); + break; + } + } + } + } + + private void createASPackages() { + for (ASMSource asm : asmSources) { + if (asm instanceof DoInitActionTag) { + DoInitActionTag initAction = (DoInitActionTag) asm; + String path = swf.getExportName(initAction.spriteId); + path = path != null ? path : "_unk_"; + if (path.isEmpty()) { + path = initAction.getExportFileName(); + } + + String[] pathParts = path.contains(".") ? path.split("\\.") : new String[]{path}; + AS2Package pkg = as2RootPackage; + for (int pos = 0; pos < pathParts.length - 1; pos++) { + String pathPart = pathParts[pos]; + AS2Package subPkg = pkg.subPackages.get(pathPart); + if (subPkg == null) { + subPkg = new AS2Package(pathPart, pkg, swf); + pkg.subPackages.put(pathPart, subPkg); + } + + pkg = subPkg; + } + + pkg.scripts.put(pathParts[pathParts.length - 1], asm); + } + } + } + + private void populateSoundStreamBlocks(int containerId, Iterable tags) { + List blocks = null; + for (Tag t : tags) { + if (t instanceof SoundStreamHeadTypeTag) { + SoundStreamHeadTypeTag head = (SoundStreamHeadTypeTag) t; + head.setVirtualCharacterId(containerId); + blocks = new ArrayList<>(); + soundStramBlocks.put(head, blocks); + continue; + } + + if (t instanceof DefineSpriteTag) { + DefineSpriteTag sprite = (DefineSpriteTag) t; + populateSoundStreamBlocks(sprite.getCharacterId(), sprite.getTags()); + } + + if (blocks == null) { + continue; + } + + if (t instanceof SoundStreamBlockTag) { + blocks.add((SoundStreamBlockTag) t); + } + } + } + + public void getNeededCharacters(Set usedCharacters) { + for (int i = 0; i < getFrameCount(); i++) { + getNeededCharacters(i, usedCharacters); + } + } + + public void getNeededCharacters(List frames, Set usedCharacters) { + for (int frame = 0; frame < getFrameCount(); frame++) { + getNeededCharacters(frame, usedCharacters); + } + } + + public void getNeededCharacters(int frame, Set usedCharacters) { + Frame frameObj = getFrame(frame); + for (int depth : frameObj.layers.keySet()) { + DepthState layer = frameObj.layers.get(depth); + if (layer.characterId != -1) { + if (!swf.getCharacters().containsKey(layer.characterId)) { + continue; + } + usedCharacters.add(layer.characterId); + swf.getCharacter(layer.characterId).getNeededCharactersDeep(usedCharacters); + } + } + } + + public boolean replaceCharacter(int oldCharacterId, int newCharacterId) { + boolean modified = false; + for (int i = 0; i < timelined.getTags().size(); i++) { + Tag t = timelined.getTags().get(i); + if (t instanceof CharacterIdTag && ((CharacterIdTag) t).getCharacterId() == oldCharacterId) { + ((CharacterIdTag) t).setCharacterId(newCharacterId); + ((Tag) t).setModified(true); + modified = true; + } + } + return modified; + } + + public boolean removeCharacter(int characterId) { + boolean modified = false; + for (int i = 0; i < timelined.getTags().size(); i++) { + Tag t = timelined.getTags().get(i); + if (t instanceof CharacterIdTag && ((CharacterIdTag) t).getCharacterId() == characterId) { + timelined.removeTag(i); + i--; + modified = true; + } + } + return modified; + } + + public double roundToPixel(double val) { + return Math.rint(val / SWF.unitDivisor) * SWF.unitDivisor; + } + + /*public void toImage(int frame, int time, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix prevTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) { + ExportRectangle scalingGrid = null; + if (timelined instanceof CharacterTag) { + DefineScalingGridTag sgt = ((CharacterTag) timelined).getScalingGridTag(); + if (sgt != null) { + scalingGrid = new ExportRectangle(sgt.splitter); + } + } + + if (scalingGrid == null || transformation.rotateSkew0 != 0 || transformation.rotateSkew1 != 0) { + toImage(frame, time, renderContext, image, isClip, transformation, absoluteTransformation, colorTransform, null); + return; + } + + //9-slice scaling using DefineScalingGrid + Matrix diffTransform = prevTransformation.inverse().preConcatenate(transformation); + transformation = diffTransform; + + Matrix prevScale = new Matrix(); + prevScale.scaleX = prevTransformation.scaleX; + prevScale.scaleY = prevTransformation.scaleY; + + ExportRectangle boundsRect = new ExportRectangle(timelined.getRect()); + + + 0 | 1 | 2 + ------------ + 3 | 4 | 5 + ------------ + 6 | 7 | 8 + + ExportRectangle[] targetRect = new ExportRectangle[9]; + ExportRectangle[] sourceRect = new ExportRectangle[9]; + Matrix[] transforms = new Matrix[9]; + + DefineScalingGridTag.getSlices(transformation, prevScale, boundsRect, scalingGrid, sourceRect, targetRect, transforms); + + for (int i = 0; i < targetRect.length; i++) { + toImage(frame, time, renderContext, image, isClip, transforms[i], absoluteTransformation, colorTransform, targetRect[i]); + } + }*/ + private void drawDrawable(Matrix strokeTransform, DepthState layer, Matrix layerMatrix, Graphics2D g, ColorTransform colorTransForm, int blendMode, List clips, Matrix transformation, boolean isClip, int clipDepth, Matrix absMat, int time, int ratio, RenderContext renderContext, SerializableImage image, DrawableTag drawable, List filters, double unzoom, ColorTransform clrTrans) { + Matrix drawMatrix = new Matrix(); + int drawableFrameCount = drawable.getNumFrames(); + if (drawableFrameCount == 0) { + drawableFrameCount = 1; + } + + RECT boundRect = drawable.getRect(); + + ExportRectangle rect = new ExportRectangle(boundRect); + Matrix mat = transformation.concatenate(layerMatrix); + rect = mat.transform(rect); + + boolean cacheAsBitmap = layer.cacheAsBitmap() && layer.placeObjectTag != null && drawable.isSingleFrame(); + /* // draw bounds + AffineTransform trans = mat.preConcatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).toTransform(); + g.setTransform(trans); + BoundedTag b = (BoundedTag) drawable; + g.setPaint(new Color(255, 255, 255, 128)); + g.setComposite(BlendComposite.Invert); + g.setStroke(new BasicStroke((int) SWF.unitDivisor)); + RECT r = b.getRect(); + g.setFont(g.getFont().deriveFont((float) (12 * SWF.unitDivisor))); + g.drawString(drawable.toString(), r.Xmin + (int) (3 * SWF.unitDivisor), r.Ymin + (int) (15 * SWF.unitDivisor)); + g.draw(new Rectangle(r.Xmin, r.Ymin, r.getWidth(), r.getHeight())); + g.drawLine(r.Xmin, r.Ymin, r.Xmax, r.Ymax); + g.drawLine(r.Xmax, r.Ymin, r.Xmin, r.Ymax); + g.setComposite(AlphaComposite.Dst);*/ + + SerializableImage img = null; + if (cacheAsBitmap && renderContext.displayObjectCache != null) { + img = renderContext.displayObjectCache.get(layer.placeObjectTag); + } + + int stateCount = renderContext.stateUnderCursor == null ? 0 : renderContext.stateUnderCursor.size(); + int dframe; + if (fontFrameNum != -1) { + dframe = fontFrameNum; + } else { + dframe = time % drawableFrameCount; + } + + if (filters != null && filters.size() > 0) { + // calculate size after applying the filters + double deltaXMax = 0; + double deltaYMax = 0; + for (FILTER filter : filters) { + double x = filter.getDeltaX(); + double y = filter.getDeltaY(); + deltaXMax = Math.max(x, deltaXMax); + deltaYMax = Math.max(y, deltaYMax); + } + rect.xMin -= deltaXMax * unzoom; + rect.xMax += deltaXMax * unzoom; + rect.yMin -= deltaYMax * unzoom; + rect.yMax += deltaYMax * unzoom; + } + + rect.xMin -= unzoom; + rect.yMin -= unzoom; + rect.xMin = Math.max(0, rect.xMin); + rect.yMin = Math.max(0, rect.yMin); + drawMatrix.translate(rect.xMin, rect.yMin); + + if (img == null) { + int newWidth = (int) (rect.getWidth() / unzoom); + int newHeight = (int) (rect.getHeight() / unzoom); + int deltaX = (int) (rect.xMin / unzoom); + int deltaY = (int) (rect.yMin / unzoom); + newWidth = Math.min(image.getWidth() - deltaX, newWidth) + 1; + newHeight = Math.min(image.getHeight() - deltaY, newHeight) + 1; + + if (newWidth <= 0 || newHeight <= 0) { + return; + } + + Matrix m = mat.preConcatenate(Matrix.getTranslateInstance(-rect.xMin, -rect.yMin)); + //strokeTransform = strokeTransform.clone(); + //strokeTransform.translate(-rect.xMin, -rect.yMin); + + if (drawable instanceof ButtonTag) { + dframe = ButtonTag.FRAME_UP; + if (renderContext.cursorPosition != null) { + Shape buttonShape = drawable.getOutline(ButtonTag.FRAME_HITTEST, time, ratio, renderContext, absMat, true); + if (buttonShape.contains(renderContext.cursorPosition)) { + renderContext.mouseOverButton = (ButtonTag) drawable; + if (renderContext.mouseButton > 0) { + dframe = ButtonTag.FRAME_DOWN; + } else { + dframe = ButtonTag.FRAME_OVER; + } + } + } + } + + img = new SerializableImage(newWidth, newHeight, SerializableImage.TYPE_INT_ARGB_PRE); + img.fillTransparent(); + + drawable.toImage(dframe, time, ratio, renderContext, img, isClip || clipDepth > -1, m, strokeTransform, absMat, clrTrans); + + if (filters != null) { + for (FILTER filter : filters) { + img = filter.apply(img); + } + } + if (blendMode > 1) { + if (colorTransForm != null) { + img = colorTransForm.apply(img); + } + } + + if (cacheAsBitmap && renderContext.displayObjectCache != null) { + renderContext.displayObjectCache.put(layer.placeObjectTag, img); + } + } + + drawMatrix.translateX /= unzoom; + drawMatrix.translateY /= unzoom; + AffineTransform trans = drawMatrix.toTransform(); + + switch (blendMode) { + case 0: + case 1: + g.setComposite(AlphaComposite.SrcOver); + break; + case 2: // Layer + g.setComposite(AlphaComposite.SrcOver); + break; + case 3: + g.setComposite(BlendComposite.Multiply); + break; + case 4: + g.setComposite(BlendComposite.Screen); + break; + case 5: + g.setComposite(BlendComposite.Lighten); + break; + case 6: + g.setComposite(BlendComposite.Darken); + break; + case 7: + g.setComposite(BlendComposite.Difference); + break; + case 8: + g.setComposite(BlendComposite.Add); + break; + case 9: + g.setComposite(BlendComposite.Subtract); + break; + case 10: + g.setComposite(BlendComposite.Invert); + break; + case 11: + g.setComposite(BlendComposite.Alpha); + break; + case 12: + g.setComposite(BlendComposite.Erase); + break; + case 13: + g.setComposite(BlendComposite.Overlay); + break; + case 14: + g.setComposite(BlendComposite.HardLight); + break; + default: // Not implemented + g.setComposite(AlphaComposite.SrcOver); + break; + } + + if (clipDepth > -1) { + BufferedImage mask = new BufferedImage(image.getWidth(), image.getHeight(), image.getType()); + Graphics2D gm = (Graphics2D) mask.getGraphics(); + gm.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + gm.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + gm.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + gm.setComposite(AlphaComposite.Src); + gm.setColor(new Color(0, 0, 0, 0f)); + gm.fillRect(0, 0, image.getWidth(), image.getHeight()); + gm.setTransform(trans); + gm.drawImage(img.getBufferedImage(), 0, 0, null); + Clip clip = new Clip(Helper.imageToShape(mask), clipDepth); // Maybe we can get current outline instead converting from image (?) + clips.add(clip); + } else { + if (renderContext.cursorPosition != null) { + if (drawable instanceof DefineSpriteTag) { + if (renderContext.stateUnderCursor.size() > stateCount) { + renderContext.stateUnderCursor.add(layer); + } + } else if (absMat.transform(new ExportRectangle(boundRect)).contains(renderContext.cursorPosition)) { + Shape shape = drawable.getOutline(dframe, time, layer.ratio, renderContext, absMat, true); + if (shape.contains(renderContext.cursorPosition)) { + renderContext.stateUnderCursor.add(layer); + } + } + } + + if (renderContext.borderImage != null) { + Graphics2D g2 = (Graphics2D) renderContext.borderImage.getGraphics(); + g2.setPaint(Color.red); + g2.setStroke(new BasicStroke(2)); + Shape shape = drawable.getOutline(dframe, time, layer.ratio, renderContext, absMat.preConcatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)), true); + g2.draw(shape); + } + + g.setTransform(trans); + g.drawImage(img.getBufferedImage(), 0, 0, null); + } + } + + public void toImage(int frame, int time, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) { + double unzoom = SWF.unitDivisor; + if (getFrameCount() <= frame) { + return; + } + + Frame frameObj = getFrame(frame); + Graphics2D g = (Graphics2D) image.getGraphics(); + Shape prevClip = g.getClip(); + //if (targetRect != null) { + // g.setClip(new Rectangle2D.Double(targetRect.xMin, targetRect.yMin, targetRect.getWidth(), targetRect.getHeight())); + //} + + g.setPaint(frameObj.backgroundColor.toColor()); + g.fill(new Rectangle(image.getWidth(), image.getHeight())); + + g.setTransform(transformation.toTransform()); + List clips = new ArrayList<>(); + + int maxDepth = getMaxDepth(); + int clipCount = 0; + for (int i = 1; i <= maxDepth; i++) { + boolean clipChanged = clipCount != clips.size(); + for (int c = 0; c < clips.size(); c++) { + if (clips.get(c).depth < i) { + clips.remove(c); + clipChanged = true; + } + } + + if (clipChanged) { + if (clips.size() > 0) { + Area clip = null; + for (Clip clip1 : clips) { + Shape shape = clip1.shape; + if (clip == null) { + clip = new Area(shape); + } else { + clip.intersect(new Area(shape)); + } + } + + g.setTransform(new AffineTransform()); + g.setClip(clip); + + // draw clip border + //g.setPaint(Color.red); + //g.setStroke(new BasicStroke(2)); + //g.draw(clip); + } else { + g.setClip(null); + } + + clipCount = clips.size(); + } + + if (!frameObj.layers.containsKey(i)) { + continue; + } + DepthState layer = frameObj.layers.get(i); + if (!swf.getCharacters().containsKey(layer.characterId)) { + continue; + } + if (!layer.isVisible) { + continue; + } + + CharacterTag character = swf.getCharacter(layer.characterId); + Matrix layerMatrix = new Matrix(layer.matrix); + Matrix mat = transformation.concatenate(layerMatrix); + Matrix absMat = absoluteTransformation.concatenate(layerMatrix); + + ColorTransform clrTrans = colorTransform; + if (layer.colorTransForm != null && layer.blendMode <= 1) { // Normal blend mode + clrTrans = clrTrans == null ? layer.colorTransForm : colorTransform.merge(layer.colorTransForm); + } + + boolean showPlaceholder = false; + if (character instanceof DrawableTag) { + + RECT scalingRect = null; + DefineScalingGridTag sgt = character.getScalingGridTag(); + if (sgt != null) { + scalingRect = sgt.splitter; + } + + if (scalingRect != null) { + ExportRectangle[] sourceRect = new ExportRectangle[9]; + ExportRectangle[] targetRect = new ExportRectangle[9]; + Matrix[] transforms = new Matrix[9]; + //mat => image + //t => + //Matrix tx = transformation.concatenate(layerMatrix); + DrawableTag dr = (DrawableTag) character; + ExportRectangle boundsRect = new ExportRectangle(dr.getRect()); + ExportRectangle targetBoundsRect = layerMatrix.transform(boundsRect); + DefineScalingGridTag.getSlices(targetBoundsRect, boundsRect, new ExportRectangle(scalingRect), sourceRect, targetRect, transforms); + Shape c = g.getClip(); + AffineTransform origTransform = g.getTransform(); + for (int s = 0; s < 9; s++) { + g.setTransform(new AffineTransform()); + ExportRectangle p1 = transformation.transform(targetRect[s]); + g.setClip(c); + + Rectangle2D r = new Rectangle2D.Double(p1.xMin, p1.yMin, p1.getWidth(), p1.getHeight()); + g.setClip(r); + drawDrawable(strokeTransformation.preConcatenate(layerMatrix), layer, transforms[s], g, colorTransform, layer.blendMode, clips, transformation, isClip, layer.clipDepth, absMat, time, layer.ratio, renderContext, image, (DrawableTag) character, layer.filters, unzoom, clrTrans); + + } + g.setClip(c); + + /* + for (int s = 0; s < 9; s++) { + g.setTransform(new AffineTransform()); + ExportRectangle p1 = transformation.transform(targetRect[s]); + g.setClip(c); + + Rectangle2D r = new Rectangle2D.Double(p1.xMin, p1.yMin, p1.getWidth(), p1.getHeight()); + g.setColor(Color.blue); + g.draw(r); + + }*/ + g.setTransform(origTransform); + } else { + drawDrawable(strokeTransformation, layer, layerMatrix, g, colorTransform, layer.blendMode, clips, transformation, isClip, layer.clipDepth, absMat, time, layer.ratio, renderContext, image, (DrawableTag) character, layer.filters, unzoom, clrTrans); + } + } else if (character instanceof BoundedTag) { + showPlaceholder = true; + } + + if (showPlaceholder) { + AffineTransform trans = mat.preConcatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).toTransform(); + g.setTransform(trans); + BoundedTag b = (BoundedTag) character; + g.setPaint(new Color(255, 255, 255, 128)); + g.setComposite(BlendComposite.Invert); + g.setStroke(new BasicStroke((int) SWF.unitDivisor)); + RECT r = b.getRect(); + g.setFont(g.getFont().deriveFont((float) (12 * SWF.unitDivisor))); + g.drawString(character.toString(), r.Xmin + (int) (3 * SWF.unitDivisor), r.Ymin + (int) (15 * SWF.unitDivisor)); + g.draw(new Rectangle(r.Xmin, r.Ymin, r.getWidth(), r.getHeight())); + g.drawLine(r.Xmin, r.Ymin, r.Xmax, r.Ymax); + g.drawLine(r.Xmax, r.Ymin, r.Xmin, r.Ymax); + g.setComposite(AlphaComposite.Dst); + } + } + + g.setTransform(new AffineTransform()); + g.setClip(prevClip); + } + + public void toSVG(int frame, int time, DepthState stateUnderCursor, int mouseButton, SVGExporter exporter, ColorTransform colorTransform, int level) throws IOException { + if (getFrameCount() <= frame) { + return; + } + + Frame frameObj = getFrame(frame); + List clips = new ArrayList<>(); + + int maxDepth = getMaxDepth(); + int clipCount = 0; + Element clipGroup = null; + for (int i = 1; i <= maxDepth; i++) { + boolean clipChanged = clipCount != clips.size(); + for (int c = 0; c < clips.size(); c++) { + if (clips.get(c).depth < i) { + clips.remove(c); + clipChanged = true; + } + } + + if (clipChanged) { + if (clipGroup != null) { + exporter.endGroup(); + } + + if (clips.size() > 0) { + String clip = clips.get(clips.size() - 1).shape; // todo: merge clip areas + clipGroup = exporter.createSubGroup(null, null); + clipGroup.setAttribute("clip-path", "url(#" + clip + ")"); + } + + clipCount = clips.size(); + } + + if (!frameObj.layers.containsKey(i)) { + continue; + } + DepthState layer = frameObj.layers.get(i); + if (!swf.getCharacters().containsKey(layer.characterId)) { + continue; + } + if (!layer.isVisible) { + continue; + } + + CharacterTag character = swf.getCharacter(layer.characterId); + + ColorTransform clrTrans = colorTransform; + if (layer.colorTransForm != null && layer.blendMode <= 1) { // Normal blend mode + clrTrans = clrTrans == null ? layer.colorTransForm : colorTransform.merge(layer.colorTransForm); + } + + if (character instanceof DrawableTag) { + DrawableTag drawable = (DrawableTag) character; + + String assetName; + Tag drawableTag = (Tag) drawable; + RECT boundRect = drawable.getRect(); + if (exporter.exportedTags.containsKey(drawableTag)) { + assetName = exporter.exportedTags.get(drawableTag); + } else { + assetName = getTagIdPrefix(drawableTag, exporter); + exporter.exportedTags.put(drawableTag, assetName); + exporter.createDefGroup(new ExportRectangle(boundRect), assetName); + drawable.toSVG(exporter, layer.ratio, clrTrans, level + 1); + exporter.endGroup(); + } + ExportRectangle rect = new ExportRectangle(boundRect); + + // TODO: if (layer.filters != null) + // TODO: if (layer.blendMode > 1) + if (layer.clipDepth > -1) { + String clipName = exporter.getUniqueId("clipPath"); + exporter.createClipPath(new Matrix(), clipName); + SvgClip clip = new SvgClip(clipName, layer.clipDepth); + clips.add(clip); + Matrix mat = Matrix.getTranslateInstance(rect.xMin, rect.yMin).preConcatenate(new Matrix(layer.matrix)); + exporter.addUse(mat, boundRect, assetName, layer.instanceName); + exporter.endGroup(); + } else { + Matrix mat = Matrix.getTranslateInstance(rect.xMin, rect.yMin).preConcatenate(new Matrix(layer.matrix)); + exporter.addUse(mat, boundRect, assetName, layer.instanceName); + } + } + } + + if (clipGroup != null) { + exporter.endGroup(); + } + } + + private static String getTagIdPrefix(Tag tag, SVGExporter exporter) { + if (tag instanceof ShapeTag) { + return exporter.getUniqueId("shape"); + } + if (tag instanceof MorphShapeTag) { + return exporter.getUniqueId("morphshape"); + } + if (tag instanceof DefineSpriteTag) { + return exporter.getUniqueId("sprite"); + } + if (tag instanceof TextTag) { + return exporter.getUniqueId("text"); + } + if (tag instanceof ButtonTag) { + return exporter.getUniqueId("button"); + } + return exporter.getUniqueId("tag"); + } + + public void toHtmlCanvas(StringBuilder result, double unitDivisor, List frames) { + FrameExporter.framesToHtmlCanvas(result, unitDivisor, this, frames, 0, null, 0, displayRect, null, null); + } + + public void getSounds(int frame, int time, ButtonTag mouseOverButton, int mouseButton, List sounds, List soundClasses) { + Frame fr = getFrame(frame); + sounds.addAll(fr.sounds); + soundClasses.addAll(fr.soundClasses); + for (int d = maxDepth; d >= 0; d--) { + DepthState ds = fr.layers.get(d); + if (ds != null) { + CharacterTag c = swf.getCharacter(ds.characterId); + if (c instanceof Timelined) { + int frameCount = ((Timelined) c).getTimeline().frames.size(); + if (frameCount == 0) { + continue; + } + int dframe = time % frameCount; + if (c instanceof ButtonTag) { + dframe = ButtonTag.FRAME_UP; + if (mouseOverButton == c) { + if (mouseButton > 0) { + dframe = ButtonTag.FRAME_DOWN; + } else { + dframe = ButtonTag.FRAME_OVER; + } + } + } + ((Timelined) c).getTimeline().getSounds(dframe, time, mouseOverButton, mouseButton, sounds, soundClasses); + } + } + } + } + + public Shape getOutline(int frame, int time, RenderContext renderContext, Matrix transformation, boolean stroked) { + Frame fr = getFrame(frame); + Area area = new Area(); + Stack clips = new Stack<>(); + for (int d = maxDepth; d >= 0; d--) { + Clip currentClip = null; + for (int i = clips.size() - 1; i >= 0; i--) { + Clip cl = clips.get(i); + if (cl.depth <= d) { + clips.remove(i); + } + } + if (!clips.isEmpty()) { + currentClip = clips.peek(); + } + DepthState layer = fr.layers.get(d); + if (layer == null) { + continue; + } + if (!layer.isVisible) { + continue; + } + CharacterTag character = swf.getCharacter(layer.characterId); + if (character instanceof DrawableTag) { + DrawableTag drawable = (DrawableTag) character; + Matrix m = transformation.concatenate(new Matrix(layer.matrix)); + + int drawableFrameCount = drawable.getNumFrames(); + if (drawableFrameCount == 0) { + drawableFrameCount = 1; + } + + int dframe = time % drawableFrameCount; + if (character instanceof ButtonTag) { + dframe = ButtonTag.FRAME_UP; + if (renderContext.cursorPosition != null) { + ButtonTag buttonTag = (ButtonTag) character; + Shape buttonShape = buttonTag.getOutline(ButtonTag.FRAME_HITTEST, time, layer.ratio, renderContext, m, stroked); + if (buttonShape.contains(renderContext.cursorPosition)) { + if (renderContext.mouseButton > 0) { + dframe = ButtonTag.FRAME_DOWN; + } else { + dframe = ButtonTag.FRAME_OVER; + } + } + } + } + + Shape cshape = ((DrawableTag) character).getOutline(dframe, time, layer.ratio, renderContext, m, stroked); + Area addArea = new Area(cshape); + if (currentClip != null) { + Area a = new Area(new Rectangle(displayRect.Xmin, displayRect.Ymin, displayRect.getWidth(), displayRect.getHeight())); + a.subtract(new Area(currentClip.shape)); + addArea.subtract(a); + } + + if (layer.clipDepth > -1) { + Clip clip = new Clip(addArea, layer.clipDepth); + clips.push(clip); + } else { + area.add(addArea); + } + } + } + return area; + } + + public boolean isSingleFrame() { + for (int i = 0; i < getFrameCount(); i++) { + if (!isSingleFrame(i)) { + return false; + } + } + return true; + } + + public boolean isSingleFrame(int frame) { + Frame frameObj = getFrame(frame); + for (int i = 1; i <= maxDepth; i++) { + if (!frameObj.layers.containsKey(i)) { + continue; + } + DepthState layer = frameObj.layers.get(i); + if (!swf.getCharacters().containsKey(layer.characterId)) { + continue; + } + if (!layer.isVisible) { + continue; + } + CharacterTag character = swf.getCharacter(layer.characterId); + if (character instanceof DrawableTag) { + DrawableTag drawable = (DrawableTag) character; + if (!drawable.isSingleFrame()) { + return false; + } + } + } + + return true; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Timeline) { + Timeline timelineObj = (Timeline) obj; + return timelined.equals(timelineObj.timelined); + } + + return false; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/BUTTONCONDACTION.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/BUTTONCONDACTION.java index e29c628f9..8b0f06182 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/BUTTONCONDACTION.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/BUTTONCONDACTION.java @@ -1,344 +1,345 @@ -/* - * 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.types; - -import com.jpexs.decompiler.flash.DisassemblyListener; -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.action.Action; -import com.jpexs.decompiler.flash.action.ActionList; -import com.jpexs.decompiler.flash.action.ConstantPoolTooBigException; -import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; -import com.jpexs.decompiler.flash.helpers.GraphTextWriter; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.tags.base.ASMSource; -import com.jpexs.decompiler.flash.types.annotations.Conditional; -import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; -import com.jpexs.decompiler.flash.types.annotations.Internal; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.Helper; -import java.io.IOException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -/** - * Actions to execute at particular button events - * - * @author JPEXS - */ -public class BUTTONCONDACTION implements ASMSource, Serializable { - - private SWF swf; - - private Tag tag; - - private String scriptName = "-"; - - @Override - public String getScriptName() { - return scriptName; - } - - // Constructor for Generic tag editor. - public BUTTONCONDACTION() { - swf = null; - tag = null; - actionBytes = new ByteArrayRange(SWFInputStream.BYTE_ARRAY_EMPTY); - } - - @Override - public void setScriptName(String scriptName) { - this.scriptName = scriptName; - } - - public BUTTONCONDACTION(SWF swf, SWFInputStream sis, Tag tag) throws IOException { - this.swf = swf; - this.tag = tag; - int condActionSize = sis.readUI16("condActionSize"); - isLast = condActionSize <= 0; - condIdleToOverDown = sis.readUB(1, "condIdleToOverDown") == 1; - condOutDownToIdle = sis.readUB(1, "condOutDownToIdle") == 1; - condOutDownToOverDown = sis.readUB(1, "condOutDownToOverDown") == 1; - condOverDownToOutDown = sis.readUB(1, "condOverDownToOutDown") == 1; - condOverDownToOverUp = sis.readUB(1, "condOverDownToOverUp") == 1; - condOverUpToOverDown = sis.readUB(1, "condOverUpToOverDown") == 1; - condOverUpToIddle = sis.readUB(1, "condOverUpToIddle") == 1; - condIdleToOverUp = sis.readUB(1, "condIdleToOverUp") == 1; - condKeyPress = (int) sis.readUB(7, "condKeyPress"); - condOverDownToIdle = sis.readUB(1, "condOverDownToIdle") == 1; - actionBytes = sis.readByteRangeEx(condActionSize <= 0 ? sis.available() : condActionSize - 4, "actionBytes"); - } - - @Override - public SWF getSwf() { - return swf; - } - - /** - * Is this BUTTONCONDACTION last in the list? - */ - @Internal - public boolean isLast; - - /** - * Idle to OverDown - */ - public boolean condIdleToOverDown; - - /** - * OutDown to Idle - */ - public boolean condOutDownToIdle; - - /** - * OutDown to OverDown - */ - public boolean condOutDownToOverDown; - - /** - * OverDown to OutDown - */ - public boolean condOverDownToOutDown; - - /** - * OverDown to OverUp - */ - public boolean condOverDownToOverUp; - - /** - * OverUp to OverDown - */ - public boolean condOverUpToOverDown; - - /** - * OverUp to Idle - */ - public boolean condOverUpToIddle; - - /** - * Idle to OverUp - */ - public boolean condIdleToOverUp; - - /** - * @since SWF 4 key code - */ - @SWFType(value = BasicType.UB, count = 7) - @Conditional(minSwfVersion = 4) - public int condKeyPress; - - /** - * OverDown to Idle - */ - public boolean condOverDownToIdle; - - /** - * Actions to perform in byte array - */ - @HideInRawEdit - public ByteArrayRange actionBytes; - - /** - * Sets actions associated with this object - * - * @param actions Action list - */ - /*public void setActions(List actions) { - this.actions = actions; - }*/ - /** - * Returns a string representation of the object - * - * @return a string representation of the object. - */ - @Override - public String toString() { - return "BUTTONCONDACTION"; - } - - /** - * Converts actions to ASM source - * - * @param exportMode PCode or hex? - * @param writer - * @param actions - * @return ASM source - * @throws java.lang.InterruptedException - */ - @Override - public GraphTextWriter getASMSource(ScriptExportMode exportMode, GraphTextWriter writer, ActionList actions) throws InterruptedException { - if (actions == null) { - actions = getActions(); - } - return Action.actionsToString(listeners, 0, actions, swf.version, exportMode, writer); - } - - /** - * Whether or not this object contains ASM source - * - * @return True when contains - */ - @Override - public boolean containsSource() { - return true; - } - - /** - * Returns actions associated with this object - * - * @return List of actions - * @throws java.lang.InterruptedException - */ - @Override - public ActionList getActions() throws InterruptedException { - return SWF.getCachedActionList(this, listeners); - } - - @Override - public void setActions(List actions) { - actionBytes = Action.actionsToByteArrayRange(actions, true, swf.version); - } - - @Override - public ByteArrayRange getActionBytes() { - return actionBytes; - } - - @Override - public void setActionBytes(byte[] actionBytes) { - this.actionBytes = new ByteArrayRange(actionBytes); - SWF.uncache(this); - } - - @Override - public void setConstantPools(List> constantPools) throws ConstantPoolTooBigException { - Action.setConstantPools(this, constantPools, false); - } - - @Override - public void setModified() { - if (tag != null) { - tag.setModified(true); - } - } - - @Override - public boolean isModified() { - if (tag != null) { - return tag.isModified(); - } - return false; - } - - @Override - public GraphTextWriter getActionBytesAsHex(GraphTextWriter writer) { - return Helper.byteArrayToHexWithHeader(writer, actionBytes.getRangeData()); - } - - List listeners = new ArrayList<>(); - - @Override - public void addDisassemblyListener(DisassemblyListener listener) { - listeners.add(listener); - } - - @Override - public void removeDisassemblyListener(DisassemblyListener listener) { - listeners.remove(listener); - } - - private String getHeader(boolean asFilename) { - List events = new ArrayList<>(); - if (condOverUpToOverDown) { - events.add("press"); - } - if (condOverDownToOverUp) { - events.add("release"); - } - if (condOutDownToIdle) { - events.add("releaseOutside"); - } - if (condIdleToOverUp) { - events.add("rollOver"); - } - if (condOverUpToIddle) { - events.add("rollOut"); - } - if (condOverDownToOutDown) { - events.add("dragOut"); - } - if (condOutDownToOverDown) { - events.add("dragOver"); - } - if (condKeyPress > 0) { - if (asFilename) { - events.add("keyPress " + Helper.makeFileName(CLIPACTIONRECORD.keyToString(condKeyPress).replace("<", "").replace(">", "")) + ""); - } else { - events.add("keyPress \"" + CLIPACTIONRECORD.keyToString(condKeyPress) + "\""); - } - } - String onStr = ""; - for (int i = 0; i < events.size(); i++) { - if (i > 0) { - onStr += ", "; - } - onStr += events.get(i); - } - return "on(" + onStr + ")"; - } - - @Override - public GraphTextWriter getActionSourcePrefix(GraphTextWriter writer) { - writer.appendNoHilight(getHeader(false)); - writer.appendNoHilight("{").newLine(); - return writer.indent(); - } - - @Override - public GraphTextWriter getActionSourceSuffix(GraphTextWriter writer) { - writer.unindent(); - return writer.appendNoHilight("}").newLine(); - } - - @Override - public int getPrefixLineCount() { - return 1; - } - - @Override - public String removePrefixAndSuffix(String source) { - return Helper.unindentRows(1, 1, source); - } - - @Override - public String getExportFileName() { - return getHeader(true); - } - - @Override - public Tag getSourceTag() { - return tag; - } - - @Override - public void setSourceTag(Tag t) { - this.tag = t; - this.swf = t.getSwf(); - } -} +/* + * 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.types; + +import com.jpexs.decompiler.flash.DisassemblyListener; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.action.Action; +import com.jpexs.decompiler.flash.action.ActionList; +import com.jpexs.decompiler.flash.action.ConstantPoolTooBigException; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.helpers.GraphTextWriter; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.flash.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; +import com.jpexs.decompiler.flash.types.annotations.Internal; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.Helper; +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Actions to execute at particular button events + * + * @author JPEXS + */ +public class BUTTONCONDACTION implements ASMSource, Serializable { + + private SWF swf; + + private Tag tag; + + private String scriptName = "-"; + + @Override + public String getScriptName() { + return scriptName; + } + + // Constructor for Generic tag editor. + public BUTTONCONDACTION() { + swf = null; + tag = null; + actionBytes = new ByteArrayRange(SWFInputStream.BYTE_ARRAY_EMPTY); + } + + @Override + public void setScriptName(String scriptName) { + this.scriptName = scriptName; + } + + public BUTTONCONDACTION(SWF swf, SWFInputStream sis, Tag tag) throws IOException { + this.swf = swf; + this.tag = tag; + int condActionSize = sis.readUI16("condActionSize"); + isLast = condActionSize <= 0; + condIdleToOverDown = sis.readUB(1, "condIdleToOverDown") == 1; + condOutDownToIdle = sis.readUB(1, "condOutDownToIdle") == 1; + condOutDownToOverDown = sis.readUB(1, "condOutDownToOverDown") == 1; + condOverDownToOutDown = sis.readUB(1, "condOverDownToOutDown") == 1; + condOverDownToOverUp = sis.readUB(1, "condOverDownToOverUp") == 1; + condOverUpToOverDown = sis.readUB(1, "condOverUpToOverDown") == 1; + condOverUpToIddle = sis.readUB(1, "condOverUpToIddle") == 1; + condIdleToOverUp = sis.readUB(1, "condIdleToOverUp") == 1; + condKeyPress = (int) sis.readUB(7, "condKeyPress"); + condOverDownToIdle = sis.readUB(1, "condOverDownToIdle") == 1; + actionBytes = sis.readByteRangeEx(condActionSize <= 0 ? sis.available() : condActionSize - 4, "actionBytes", DumpInfoSpecialType.ACTION_BYTES, sis.getPos()); + } + + @Override + public SWF getSwf() { + return swf; + } + + /** + * Is this BUTTONCONDACTION last in the list? + */ + @Internal + public boolean isLast; + + /** + * Idle to OverDown + */ + public boolean condIdleToOverDown; + + /** + * OutDown to Idle + */ + public boolean condOutDownToIdle; + + /** + * OutDown to OverDown + */ + public boolean condOutDownToOverDown; + + /** + * OverDown to OutDown + */ + public boolean condOverDownToOutDown; + + /** + * OverDown to OverUp + */ + public boolean condOverDownToOverUp; + + /** + * OverUp to OverDown + */ + public boolean condOverUpToOverDown; + + /** + * OverUp to Idle + */ + public boolean condOverUpToIddle; + + /** + * Idle to OverUp + */ + public boolean condIdleToOverUp; + + /** + * @since SWF 4 key code + */ + @SWFType(value = BasicType.UB, count = 7) + @Conditional(minSwfVersion = 4) + public int condKeyPress; + + /** + * OverDown to Idle + */ + public boolean condOverDownToIdle; + + /** + * Actions to perform in byte array + */ + @HideInRawEdit + public ByteArrayRange actionBytes; + + /** + * Sets actions associated with this object + * + * @param actions Action list + */ + /*public void setActions(List actions) { + this.actions = actions; + }*/ + /** + * Returns a string representation of the object + * + * @return a string representation of the object. + */ + @Override + public String toString() { + return "BUTTONCONDACTION"; + } + + /** + * Converts actions to ASM source + * + * @param exportMode PCode or hex? + * @param writer + * @param actions + * @return ASM source + * @throws java.lang.InterruptedException + */ + @Override + public GraphTextWriter getASMSource(ScriptExportMode exportMode, GraphTextWriter writer, ActionList actions) throws InterruptedException { + if (actions == null) { + actions = getActions(); + } + return Action.actionsToString(listeners, 0, actions, swf.version, exportMode, writer); + } + + /** + * Whether or not this object contains ASM source + * + * @return True when contains + */ + @Override + public boolean containsSource() { + return true; + } + + /** + * Returns actions associated with this object + * + * @return List of actions + * @throws java.lang.InterruptedException + */ + @Override + public ActionList getActions() throws InterruptedException { + return SWF.getCachedActionList(this, listeners); + } + + @Override + public void setActions(List actions) { + actionBytes = Action.actionsToByteArrayRange(actions, true, swf.version); + } + + @Override + public ByteArrayRange getActionBytes() { + return actionBytes; + } + + @Override + public void setActionBytes(byte[] actionBytes) { + this.actionBytes = new ByteArrayRange(actionBytes); + SWF.uncache(this); + } + + @Override + public void setConstantPools(List> constantPools) throws ConstantPoolTooBigException { + Action.setConstantPools(this, constantPools, false); + } + + @Override + public void setModified() { + if (tag != null) { + tag.setModified(true); + } + } + + @Override + public boolean isModified() { + if (tag != null) { + return tag.isModified(); + } + return false; + } + + @Override + public GraphTextWriter getActionBytesAsHex(GraphTextWriter writer) { + return Helper.byteArrayToHexWithHeader(writer, actionBytes.getRangeData()); + } + + List listeners = new ArrayList<>(); + + @Override + public void addDisassemblyListener(DisassemblyListener listener) { + listeners.add(listener); + } + + @Override + public void removeDisassemblyListener(DisassemblyListener listener) { + listeners.remove(listener); + } + + private String getHeader(boolean asFilename) { + List events = new ArrayList<>(); + if (condOverUpToOverDown) { + events.add("press"); + } + if (condOverDownToOverUp) { + events.add("release"); + } + if (condOutDownToIdle) { + events.add("releaseOutside"); + } + if (condIdleToOverUp) { + events.add("rollOver"); + } + if (condOverUpToIddle) { + events.add("rollOut"); + } + if (condOverDownToOutDown) { + events.add("dragOut"); + } + if (condOutDownToOverDown) { + events.add("dragOver"); + } + if (condKeyPress > 0) { + if (asFilename) { + events.add("keyPress " + Helper.makeFileName(CLIPACTIONRECORD.keyToString(condKeyPress).replace("<", "").replace(">", "")) + ""); + } else { + events.add("keyPress \"" + CLIPACTIONRECORD.keyToString(condKeyPress) + "\""); + } + } + String onStr = ""; + for (int i = 0; i < events.size(); i++) { + if (i > 0) { + onStr += ", "; + } + onStr += events.get(i); + } + return "on(" + onStr + ")"; + } + + @Override + public GraphTextWriter getActionSourcePrefix(GraphTextWriter writer) { + writer.appendNoHilight(getHeader(false)); + writer.appendNoHilight("{").newLine(); + return writer.indent(); + } + + @Override + public GraphTextWriter getActionSourceSuffix(GraphTextWriter writer) { + writer.unindent(); + return writer.appendNoHilight("}").newLine(); + } + + @Override + public int getPrefixLineCount() { + return 1; + } + + @Override + public String removePrefixAndSuffix(String source) { + return Helper.unindentRows(1, 1, source); + } + + @Override + public String getExportFileName() { + return getHeader(true); + } + + @Override + public Tag getSourceTag() { + return tag; + } + + @Override + public void setSourceTag(Tag t) { + this.tag = t; + this.swf = t.getSwf(); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/CLIPACTIONRECORD.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/CLIPACTIONRECORD.java index f7c518f01..cc57c20ef 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/CLIPACTIONRECORD.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/CLIPACTIONRECORD.java @@ -1,300 +1,301 @@ -/* - * 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.types; - -import com.jpexs.decompiler.flash.DisassemblyListener; -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.action.Action; -import com.jpexs.decompiler.flash.action.ActionList; -import com.jpexs.decompiler.flash.action.ConstantPoolTooBigException; -import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; -import com.jpexs.decompiler.flash.helpers.GraphTextWriter; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.tags.base.ASMSource; -import com.jpexs.decompiler.flash.types.annotations.Conditional; -import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; -import com.jpexs.decompiler.flash.types.annotations.Internal; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.Helper; -import java.io.IOException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -/** - * Event handler - * - * @author JPEXS - */ -public class CLIPACTIONRECORD implements ASMSource, Serializable { - - private String scriptName = "-"; - - @Override - public String getScriptName() { - return scriptName; - } - - public static String keyToString(int key) { - if ((key < CLIPACTIONRECORD.KEYNAMES.length) && (key > 0) && (CLIPACTIONRECORD.KEYNAMES[key] != null)) { - return CLIPACTIONRECORD.KEYNAMES[key]; - } else { - return "" + (char) key; - } - } - - public static final String[] KEYNAMES = { - null, - "", - "", - "", - "", - "", - "", - null, - "", - null, - null, - null, - null, - "", - "", - "", - "", - "", - "", - "", - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - "" - }; - - @Internal - private SWF swf; - - @Internal - private Tag tag; - - // Constructor for Generic tag editor. TODO:Handle this somehow better - public CLIPACTIONRECORD() { - swf = null; - tag = null; - eventFlags = new CLIPEVENTFLAGS(); - actionBytes = ByteArrayRange.EMPTY; - } - - @Override - public void setScriptName(String scriptName) { - this.scriptName = scriptName; - } - - public CLIPACTIONRECORD(SWF swf, SWFInputStream sis, Tag tag) throws IOException { - this.swf = swf; - this.tag = tag; - eventFlags = sis.readCLIPEVENTFLAGS("eventFlags"); - if (eventFlags.isClear()) { - return; - } - long actionRecordSize = sis.readUI32("actionRecordSize"); - if (eventFlags.clipEventKeyPress) { - keyCode = sis.readUI8("keyCode"); - actionRecordSize--; - } - actionBytes = sis.readByteRangeEx(actionRecordSize, "actionBytes"); - } - - @Override - public SWF getSwf() { - return swf; - } - - /** - * Events to which this handler applies - */ - public CLIPEVENTFLAGS eventFlags; - - /** - * If EventFlags contain ClipEventKeyPress: Key code to trap - */ - @Conditional("eventFlags.clipEventKeyPress") - public int keyCode; - - /** - * Actions to perform - */ - @HideInRawEdit - public ByteArrayRange actionBytes; - - /** - * Returns a string representation of the object - * - * @return a string representation of the object. - */ - @Override - public String toString() { - return eventFlags.getHeader(keyCode, false); - } - - /** - * Returns header with events converted to string - * - * @return String representation of events - */ - public String getHeader() { - String ret; - ret = eventFlags.toString(); - if (eventFlags.clipEventKeyPress) { - ret = ret.replace("keyPress", "keyPress<" + keyCode + ">"); - } - return ret; - } - - /** - * Converts actions to ASM source - * - * @param exportMode PCode or hex? - * @param writer - * @param actions - * @return ASM source - * @throws java.lang.InterruptedException - */ - @Override - public GraphTextWriter getASMSource(ScriptExportMode exportMode, GraphTextWriter writer, ActionList actions) throws InterruptedException { - if (actions == null) { - actions = getActions(); - } - return Action.actionsToString(listeners, 0, actions, swf.version, exportMode, writer); - } - - /** - * Whether or not this object contains ASM source - * - * @return True when contains - */ - @Override - public boolean containsSource() { - return true; - } - - @Override - public ActionList getActions() throws InterruptedException { - return SWF.getCachedActionList(this, listeners); - } - - @Override - public void setActions(List actions) { - actionBytes = Action.actionsToByteArrayRange(actions, true, swf.version); - } - - @Override - public ByteArrayRange getActionBytes() { - return actionBytes; - } - - @Override - public void setActionBytes(byte[] actionBytes) { - this.actionBytes = new ByteArrayRange(actionBytes); - SWF.uncache(this); - } - - @Override - public void setConstantPools(List> constantPools) throws ConstantPoolTooBigException { - Action.setConstantPools(this, constantPools, false); - } - - @Override - public void setModified() { - if (tag != null) { - tag.setModified(true); - } - } - - @Override - public boolean isModified() { - if (tag != null) { - return tag.isModified(); - } - return false; - } - - @Override - public GraphTextWriter getActionBytesAsHex(GraphTextWriter writer) { - return Helper.byteArrayToHexWithHeader(writer, actionBytes.getRangeData()); - } - - List listeners = new ArrayList<>(); - - @Override - public void addDisassemblyListener(DisassemblyListener listener) { - listeners.add(listener); - } - - @Override - public void removeDisassemblyListener(DisassemblyListener listener) { - listeners.remove(listener); - } - - @Override - public GraphTextWriter getActionSourcePrefix(GraphTextWriter writer) { - writer.appendNoHilight(eventFlags.getHeader(keyCode, false)); - writer.appendNoHilight("{").newLine(); - return writer.indent(); - } - - @Override - public GraphTextWriter getActionSourceSuffix(GraphTextWriter writer) { - writer.unindent(); - return writer.appendNoHilight("}").newLine(); - } - - @Override - public int getPrefixLineCount() { - return 1; - } - - @Override - public String removePrefixAndSuffix(String source) { - return Helper.unindentRows(1, 1, source); - } - - @Override - public String getExportFileName() { - return eventFlags.getHeader(keyCode, true); - } - - @Override - public Tag getSourceTag() { - return tag; - } - - @Override - public void setSourceTag(Tag t) { - this.tag = t; - this.swf = t.getSwf(); - } -} +/* + * 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.types; + +import com.jpexs.decompiler.flash.DisassemblyListener; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.action.Action; +import com.jpexs.decompiler.flash.action.ActionList; +import com.jpexs.decompiler.flash.action.ConstantPoolTooBigException; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.helpers.GraphTextWriter; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.flash.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; +import com.jpexs.decompiler.flash.types.annotations.Internal; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.Helper; +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Event handler + * + * @author JPEXS + */ +public class CLIPACTIONRECORD implements ASMSource, Serializable { + + private String scriptName = "-"; + + @Override + public String getScriptName() { + return scriptName; + } + + public static String keyToString(int key) { + if ((key < CLIPACTIONRECORD.KEYNAMES.length) && (key > 0) && (CLIPACTIONRECORD.KEYNAMES[key] != null)) { + return CLIPACTIONRECORD.KEYNAMES[key]; + } else { + return "" + (char) key; + } + } + + public static final String[] KEYNAMES = { + null, + "", + "", + "", + "", + "", + "", + null, + "", + null, + null, + null, + null, + "", + "", + "", + "", + "", + "", + "", + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "" + }; + + @Internal + private SWF swf; + + @Internal + private Tag tag; + + // Constructor for Generic tag editor. TODO:Handle this somehow better + public CLIPACTIONRECORD() { + swf = null; + tag = null; + eventFlags = new CLIPEVENTFLAGS(); + actionBytes = ByteArrayRange.EMPTY; + } + + @Override + public void setScriptName(String scriptName) { + this.scriptName = scriptName; + } + + public CLIPACTIONRECORD(SWF swf, SWFInputStream sis, Tag tag) throws IOException { + this.swf = swf; + this.tag = tag; + eventFlags = sis.readCLIPEVENTFLAGS("eventFlags"); + if (eventFlags.isClear()) { + return; + } + long actionRecordSize = sis.readUI32("actionRecordSize"); + if (eventFlags.clipEventKeyPress) { + keyCode = sis.readUI8("keyCode"); + actionRecordSize--; + } + actionBytes = sis.readByteRangeEx(actionRecordSize, "actionBytes", DumpInfoSpecialType.ACTION_BYTES, sis.getPos()); + } + + @Override + public SWF getSwf() { + return swf; + } + + /** + * Events to which this handler applies + */ + public CLIPEVENTFLAGS eventFlags; + + /** + * If EventFlags contain ClipEventKeyPress: Key code to trap + */ + @Conditional("eventFlags.clipEventKeyPress") + public int keyCode; + + /** + * Actions to perform + */ + @HideInRawEdit + public ByteArrayRange actionBytes; + + /** + * Returns a string representation of the object + * + * @return a string representation of the object. + */ + @Override + public String toString() { + return eventFlags.getHeader(keyCode, false); + } + + /** + * Returns header with events converted to string + * + * @return String representation of events + */ + public String getHeader() { + String ret; + ret = eventFlags.toString(); + if (eventFlags.clipEventKeyPress) { + ret = ret.replace("keyPress", "keyPress<" + keyCode + ">"); + } + return ret; + } + + /** + * Converts actions to ASM source + * + * @param exportMode PCode or hex? + * @param writer + * @param actions + * @return ASM source + * @throws java.lang.InterruptedException + */ + @Override + public GraphTextWriter getASMSource(ScriptExportMode exportMode, GraphTextWriter writer, ActionList actions) throws InterruptedException { + if (actions == null) { + actions = getActions(); + } + return Action.actionsToString(listeners, 0, actions, swf.version, exportMode, writer); + } + + /** + * Whether or not this object contains ASM source + * + * @return True when contains + */ + @Override + public boolean containsSource() { + return true; + } + + @Override + public ActionList getActions() throws InterruptedException { + return SWF.getCachedActionList(this, listeners); + } + + @Override + public void setActions(List actions) { + actionBytes = Action.actionsToByteArrayRange(actions, true, swf.version); + } + + @Override + public ByteArrayRange getActionBytes() { + return actionBytes; + } + + @Override + public void setActionBytes(byte[] actionBytes) { + this.actionBytes = new ByteArrayRange(actionBytes); + SWF.uncache(this); + } + + @Override + public void setConstantPools(List> constantPools) throws ConstantPoolTooBigException { + Action.setConstantPools(this, constantPools, false); + } + + @Override + public void setModified() { + if (tag != null) { + tag.setModified(true); + } + } + + @Override + public boolean isModified() { + if (tag != null) { + return tag.isModified(); + } + return false; + } + + @Override + public GraphTextWriter getActionBytesAsHex(GraphTextWriter writer) { + return Helper.byteArrayToHexWithHeader(writer, actionBytes.getRangeData()); + } + + List listeners = new ArrayList<>(); + + @Override + public void addDisassemblyListener(DisassemblyListener listener) { + listeners.add(listener); + } + + @Override + public void removeDisassemblyListener(DisassemblyListener listener) { + listeners.remove(listener); + } + + @Override + public GraphTextWriter getActionSourcePrefix(GraphTextWriter writer) { + writer.appendNoHilight(eventFlags.getHeader(keyCode, false)); + writer.appendNoHilight("{").newLine(); + return writer.indent(); + } + + @Override + public GraphTextWriter getActionSourceSuffix(GraphTextWriter writer) { + writer.unindent(); + return writer.appendNoHilight("}").newLine(); + } + + @Override + public int getPrefixLineCount() { + return 1; + } + + @Override + public String removePrefixAndSuffix(String source) { + return Helper.unindentRows(1, 1, source); + } + + @Override + public String getExportFileName() { + return eventFlags.getHeader(keyCode, true); + } + + @Override + public Tag getSourceTag() { + return tag; + } + + @Override + public void setSourceTag(Tag t) { + this.tag = t; + this.swf = t.getSwf(); + } +} 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 70693b210..c8072c922 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 @@ -1393,7 +1393,7 @@ public class XFLConverter { } /* 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. + These datatypes can be in the FLA file but are not exported to SWF with _EMBED_SWF_ publish format. */ } @@ -1691,7 +1691,7 @@ public class XFLConverter { } } - byte imageBytes[] = Helper.readStream(imageTag.getImageData()); + byte[] imageBytes = Helper.readStream(imageTag.getImageData()); SerializableImage image = imageTag.getImageCached(); ImageFormat format = imageTag.getImageFormat(); String symbolFile = "bitmap" + symbol.getCharacterId() + imageTag.getImageFormat().getExtension(); @@ -1807,52 +1807,53 @@ public class XFLConverter { } format += 4; //quality best try { - MP3SOUNDDATA s = new MP3SOUNDDATA(new SWFInputStream(swf, soundData), false); - //sis.readSI16(); - //MP3FRAME frame = new MP3FRAME(sis); - MP3FRAME frame = s.frames.get(0); - int bitRate = frame.getBitRate(); + SWFInputStream sis = new SWFInputStream(swf, soundData); + MP3SOUNDDATA s = new MP3SOUNDDATA(sis, false); + if (!s.frames.isEmpty()) { + MP3FRAME frame = s.frames.get(0); + int bitRate = frame.getBitRate(); - switch (bitRate) { - case 8: - bits = 6; - break; - case 16: - bits = 7; - break; - case 20: - bits = 8; - break; - case 24: - bits = 9; - break; - case 32: - bits = 10; - break; - case 48: - bits = 11; - break; - case 56: - bits = 12; - break; - case 64: - bits = 13; - break; - case 80: - bits = 14; - break; - case 112: - bits = 15; - break; - case 128: - bits = 16; - break; - case 160: - bits = 17; - break; + switch (bitRate) { + case 8: + bits = 6; + break; + case 16: + bits = 7; + break; + case 20: + bits = 8; + break; + case 24: + bits = 9; + break; + case 32: + bits = 10; + break; + case 48: + bits = 11; + break; + case 56: + bits = 12; + break; + case 64: + bits = 13; + break; + case 80: + bits = 14; + break; + case 112: + bits = 15; + break; + case 128: + bits = 16; + break; + case 160: + bits = 17; + break; + } } - } catch (IOException | ArrayIndexOutOfBoundsException ex) { + } catch (IOException | IndexOutOfBoundsException ex) { logger.log(Level.SEVERE, null, ex); } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java index dc60af652..ccb379054 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java @@ -1,2480 +1,2480 @@ -/* - * 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.graph; - -import com.jpexs.decompiler.flash.BaseLocalData; -import com.jpexs.decompiler.flash.FinalProcessLocalData; -import com.jpexs.decompiler.flash.action.Action; -import com.jpexs.decompiler.flash.action.model.FunctionActionItem; -import com.jpexs.decompiler.flash.helpers.GraphTextWriter; -import com.jpexs.decompiler.graph.model.AndItem; -import com.jpexs.decompiler.graph.model.BreakItem; -import com.jpexs.decompiler.graph.model.ContinueItem; -import com.jpexs.decompiler.graph.model.DefaultItem; -import com.jpexs.decompiler.graph.model.DoWhileItem; -import com.jpexs.decompiler.graph.model.DuplicateItem; -import com.jpexs.decompiler.graph.model.ExitItem; -import com.jpexs.decompiler.graph.model.FalseItem; -import com.jpexs.decompiler.graph.model.ForItem; -import com.jpexs.decompiler.graph.model.GotoItem; -import com.jpexs.decompiler.graph.model.IfItem; -import com.jpexs.decompiler.graph.model.IntegerValueItem; -import com.jpexs.decompiler.graph.model.IntegerValueTypeItem; -import com.jpexs.decompiler.graph.model.LabelItem; -import com.jpexs.decompiler.graph.model.LocalData; -import com.jpexs.decompiler.graph.model.LogicalOpItem; -import com.jpexs.decompiler.graph.model.LoopItem; -import com.jpexs.decompiler.graph.model.NotItem; -import com.jpexs.decompiler.graph.model.OrItem; -import com.jpexs.decompiler.graph.model.PopItem; -import com.jpexs.decompiler.graph.model.PushItem; -import com.jpexs.decompiler.graph.model.ScriptEndItem; -import com.jpexs.decompiler.graph.model.SwitchItem; -import com.jpexs.decompiler.graph.model.TernarOpItem; -import com.jpexs.decompiler.graph.model.TrueItem; -import com.jpexs.decompiler.graph.model.UniversalLoopItem; -import com.jpexs.decompiler.graph.model.WhileItem; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Stack; - -/** - * - * @author JPEXS - */ -public class Graph { - - public List heads; - - protected GraphSource code; - - private final List alternateEntries; - - public static final int SOP_USE_STATIC = 0; - - public static final int SOP_SKIP_STATIC = 1; - - public static final int SOP_REMOVE_STATIC = 2; - - /** - * Identify loop exits - * - * @param localData - * @param allParts All nodes - * @return - */ - public Map> identifyLoopBreaks(BaseLocalData localData, Set allParts) { - Map> lb = new HashMap<>(); - - for (GraphPart b0 : allParts) { - List np = new ArrayList<>(b0.nextParts); - np.addAll(b0.throwParts); - for (GraphPart b : np) { - GraphPart hdr = (b0.type == GraphPart.TYPE_LOOP_HEADER || b0.type == GraphPart.TYPE_REENTRY) ? b0 : b0.iloop_header; - - if (hdr != null && b.iloop_header != hdr && b.iloop_header == hdr.iloop_header && b != hdr) { - if (!lb.containsKey(hdr)) { - lb.put(hdr, new ArrayList<>()); - } - if (!lb.get(hdr).contains(b)) { - lb.get(hdr).add(b); - } - } - } - } - - return lb; - } - - /** - * Identifying loops. Based on http://lenx.100871.net/papers/loop-SAS.pdf - * - * @param localData - * @param loopContinues Result - list of loop headers - * @param heads Entries - * @param appParts All Nodes - */ - public void identifyLoops(BaseLocalData localData, List loopContinues, List heads, Set appParts) { - for (GraphPart b : appParts) { - b.traversed = false; - b.DFSP_pos = 0; - b.irreducible = false; - b.type = GraphPart.TYPE_NONE; - //initialize b - } - for (GraphPart h0 : heads) { - trav_loops_DFS(localData, loopContinues, h0, 1); - } - } - - /** - * Tag h as loop headr of b. - * - * @param b Block - * @param h Loop header - */ - protected void tag_lhead(GraphPart b, GraphPart h) { - if (b == h || h == null) { - return; - } - GraphPart cur1 = b; - GraphPart cur2 = h; - while (cur1.iloop_header != null) { - GraphPart ih = cur1.iloop_header; - if (ih == cur2) { - return; - } - if (ih.DFSP_pos < cur2.DFSP_pos) { - cur1.iloop_header = cur2; - cur1 = cur2; - cur2 = ih; - } else { - cur1 = h; - } - } - if (cur1 == cur2) { - return; - } - - cur1.iloop_header = cur2; - } - - protected List filter(List list) { - return new ArrayList<>(list); - } - - /** - * Traverse loops deep first search - * - * @param localData - * @param loopHeaders Resulting loop headers - * @param b0 Current node - * @param DFSP_pos Position in DFSP - * @return innermost loop header of b0 - */ - protected GraphPart trav_loops_DFS(BaseLocalData localData, List loopHeaders, GraphPart b0, int DFSP_pos) { - - List folParts = new ArrayList<>(b0.nextParts); - folParts.addAll(b0.throwParts); - - b0.traversed = true; - b0.DFSP_pos = DFSP_pos; //Mark b0’s position in DFSP - for (GraphPart b : folParts) { - b = checkPart(null, localData, b, null); - if (b == null) { - continue; - } - if (!b.traversed) { - //case (A), new - GraphPart nh = trav_loops_DFS(localData, loopHeaders, b, DFSP_pos + 1); - tag_lhead(b0, nh); - } else if (b.DFSP_pos > 0) { // b in DFSP(b0) - //case (B) - if (b.type != GraphPart.TYPE_LOOP_HEADER) { - b.type = GraphPart.TYPE_LOOP_HEADER; - loopHeaders.add(b); - } - tag_lhead(b0, b); - } else if (b.iloop_header == null) { - //case (C), do nothing - } else { - GraphPart h = b.iloop_header; - if (h.DFSP_pos > 0) { // h in DFSP(b0) - //case (D) - tag_lhead(b0, h); - } else { // h not in DFSP(b0) - //case (E), reentry - b.type = GraphPart.TYPE_REENTRY; //TODO:and b0,b ? - h.irreducible = true; - while (h.iloop_header != null) { - h = h.iloop_header; - if (h.DFSP_pos > 0) { //h in DFSP(b0) - tag_lhead(b0, h); - break; - } - h.irreducible = true; - } - } - } - } - b0.DFSP_pos = 0; // clear b0’s DFSP position - return b0.iloop_header; - } - - public Graph(GraphSource code, List alternateEntries) { - this.code = code; - this.alternateEntries = alternateEntries; - - } - - public void init(BaseLocalData localData) throws InterruptedException { - if (heads != null) { - return; - } - heads = makeGraph(code, new ArrayList<>(), alternateEntries); - int time = 1; - List ordered = new ArrayList<>(); - List visited = new ArrayList<>(); - for (GraphPart head : heads) { - time = head.setTime(time, ordered, visited); - } - } - - protected static void populateParts(GraphPart part, Set allParts) { - if (allParts.contains(part)) { - return; - } - allParts.add(part); - for (GraphPart p : part.nextParts) { - populateParts(p, allParts); - } - } - - public GraphPart deepCopy(GraphPart part) { - return deepCopy(part, new HashMap<>()); - } - - private GraphPart deepCopy(GraphPart part, Map copies) { - GraphPart copy = copies.get(part); - if (copy != null) { - return copy; - } - - copy = new GraphPart(part.start, part.end); - copy.path = part.path; - copies.put(part, copy); - copy.nextParts = new ArrayList<>(); - for (int i = 0; i < part.nextParts.size(); i++) { - copy.nextParts.add(deepCopy(part.nextParts.get(i), copies)); - } - - for (int i = 0; i < part.refs.size(); i++) { - copy.refs.add(deepCopy(part.refs.get(i), copies)); - } - - return copy; - } - - public void resetGraph(GraphPart part, Set visited) { - if (visited.contains(part)) { - return; - } - - visited.add(part); - int pos = 0; - for (GraphPart p : part.nextParts) { - if (!visited.contains(p)) { - p.path = part.path.sub(pos, p.end); - } - - resetGraph(p, visited); - pos++; - } - } - - private void getReachableParts(GraphPart part, LinkedHashSet ret, List loops) { - // use LinkedHashSet to preserve order - getReachableParts(part, ret, loops, true); - } - - private void getReachableParts(GraphPart part, LinkedHashSet ret, List loops, boolean first) { - - // todo: honfika: why call with first = true parameter always? - Stack stack = new Stack<>(); - GraphPartQueue queue = new GraphPartQueue(); - queue.add(part); - stack.add(queue); - stacknext: - while (!stack.isEmpty()) { - - queue = stack.peek(); - if (!queue.isEmpty()) { - part = queue.remove(); - } else if (queue.currentLoop != null) { - Loop cLoop = queue.currentLoop; - part = cLoop.loopBreak; - queue.currentLoop = null; - if (ret.contains(part)) { - continue; - } - - ret.add(part); - cLoop.reachableMark = 2; - } else { - stack.pop(); - continue; - } - - for (Loop l : loops) { - l.reachableMark = 0; - } - - Loop currentLoop = null; - for (Loop l : loops) { - if ((l.phase == 1) || (l.reachableMark == 1)) { - if (l.loopContinue == part) { - continue stacknext; - } - if (l.loopBreak == part) { - continue stacknext; - } - if (l.loopPreContinue == part) { - continue stacknext; - } - } - if (l.reachableMark == 0) { - if (l.loopContinue == part) { - l.reachableMark = 1; - currentLoop = l; - } - } - } - - GraphPartQueue newParts = new GraphPartQueue(); - loopnext: - for (GraphPart next : part.nextParts) { - for (Loop l : loops) { - if ((l.phase == 1) || (l.reachableMark == 1)) { - if (l.loopContinue == next) { - continue loopnext; - } - if (l.loopBreak == next) { - continue loopnext; - } - if (l.loopPreContinue == next) { - continue loopnext; - } - } - - } - if (!ret.contains(next)) { - newParts.add(next); - } - } - - ret.addAll(newParts); - if (currentLoop != null && currentLoop.loopBreak != null) { - newParts.currentLoop = currentLoop; - } - - if (!newParts.isEmpty() || newParts.currentLoop != null) { - stack.add(newParts); - } - } - } - - public GraphPart getNextCommonPart(BaseLocalData localData, GraphPart part, List loops) throws InterruptedException { - return getCommonPart(localData, part.nextParts, loops); - } - - //TODO: Make this faster! - public GraphPart getCommonPart(BaseLocalData localData, List parts, List loops) throws InterruptedException { - if (parts.isEmpty()) { - return null; - } - - List loopContinues = new ArrayList<>();//getLoopsContinues(loops); - for (Loop l : loops) { - if (l.phase == 1) { - loopContinues.add(l.loopContinue); - } - } - - for (GraphPart p : parts) { - if (loopContinues.contains(p)) { - break; - } - boolean common = true; - for (GraphPart q : parts) { - if (q == p) { - continue; - } - if (!q.leadsTo(localData, this, code, p, loops)) { - common = false; - break; - } - } - if (common) { - return p; - } - } - List> reachable = new ArrayList<>(); - for (GraphPart p : parts) { - LinkedHashSet r1 = new LinkedHashSet<>(); - getReachableParts(p, r1, loops); - r1.add(p); - reachable.add(r1); - } - Set first = reachable.get(0); - for (GraphPart p : first) { - /*if (ignored.contains(p)) { - continue; - }*/ - p = checkPart(null, localData, p, null); - if (p == null) { - continue; - } - boolean common = true; - for (Set r : reachable) { - if (!r.contains(p)) { - common = false; - break; - } - } - if (common) { - return p; - } - } - return null; - } - - public GraphPart getMostCommonPart(BaseLocalData localData, List parts, List loops) throws InterruptedException { - if (parts.isEmpty()) { - return null; - } - - Set s = new HashSet<>(parts); //unique - parts = new ArrayList<>(s); //make local copy - - List loopContinues = new ArrayList<>();//getLoopsContinues(loops); - for (Loop l : loops) { - if (l.phase == 1) { - loopContinues.add(l.loopContinue); - loopContinues.add(l.loopPreContinue); - } - } - - for (GraphPart p : parts) { - if (loopContinues.contains(p)) { - break; - } - boolean common = true; - for (GraphPart q : parts) { - if (q == p) { - continue; - } - if (!q.leadsTo(localData, this, code, p, loops)) { - common = false; - break; - } - } - if (common) { - return p; - } - } - - loopi: - for (int i = 0; i < parts.size(); i++) { - for (int j = 0; j < parts.size(); j++) { - if (j == i) { - continue; - } - if (parts.get(i).leadsTo(localData, this, code, parts.get(j), loops)) { - parts.remove(i); - i--; - continue loopi; - } - } - } - List> reachable = new ArrayList<>(); - for (GraphPart p : parts) { - LinkedHashSet r1 = new LinkedHashSet<>(); - getReachableParts(p, r1, loops); - Set r2 = new LinkedHashSet<>(); - r2.add(p); - r2.addAll(r1); - reachable.add(r2); - } - ///List first = reachable.get(0); - int commonLevel; - Map levelMap = new HashMap<>(); - for (Set first : reachable) { - int maxclevel = 0; - Set visited = new HashSet<>(); - for (GraphPart p : first) { - if (loopContinues.contains(p)) { - break; - } - if (visited.contains(p)) { - continue; - } - visited.add(p); - boolean common = true; - commonLevel = 1; - for (Set r : reachable) { - if (r == first) { - continue; - } - if (r.contains(p)) { - commonLevel++; - } - } - if (commonLevel <= maxclevel) { - continue; - } - maxclevel = commonLevel; - if (levelMap.containsKey(p)) { - if (levelMap.get(p) > commonLevel) { - commonLevel = levelMap.get(p); - } - } - levelMap.put(p, commonLevel); - if (common) { - //return p; - } - } - } - for (int i = reachable.size() - 1; i >= 2; i--) { - for (GraphPart p : levelMap.keySet()) { - if (levelMap.get(p) == i) { - return p; - } - } - } - for (GraphPart p : levelMap.keySet()) { - if (levelMap.get(p) == parts.size()) { - return p; - } - } - return null; - } - - public GraphPart getNextNoJump(GraphPart part, BaseLocalData localData) { - while (code.get(part.start).isJump()) { - part = part.getSubParts().get(0).nextParts.get(0); - } - /*localData = prepareBranchLocalData(localData); - TranslateStack st = new TranslateStack(); - List output=new ArrayList<>(); - GraphPart startPart = part; - for (int i = part.start; i <= part.end; i++) { - GraphSourceItem src = code.get(i); - if (src.isJump()) { - part = part.nextParts.get(0); - if(st.isEmpty()){ - startPart = part; - } - i = part.start - 1; - continue; - } - try{ - src.translate(localData, st, output, SOP_USE_STATIC, ""); - }catch(Exception ex){ - return startPart; - } - if(!output.isEmpty()){ - return startPart; - } - }*/ - return part; - } - - public static List translateViaGraph(BaseLocalData localData, String path, GraphSource code, List alternateEntries, int staticOperation) throws InterruptedException { - Graph g = new Graph(code, alternateEntries); - g.init(localData); - return g.translate(localData, staticOperation, path); - } - - public List translate(BaseLocalData localData, int staticOperation, String path) throws InterruptedException { - Set allParts = new HashSet<>(); - for (GraphPart head : heads) { - populateParts(head, allParts); - } - TranslateStack stack = new TranslateStack(path); - List loops = new ArrayList<>(); - - //TODO: Make this working. :-( - final boolean newLoopDetection = false; - - if (!newLoopDetection) { - getLoops(localData, heads.get(0), loops, null); - } else { - List loopHeads = new ArrayList<>(); - identifyLoops(localData, loopHeads, heads, allParts); - Map> loopBreaks = identifyLoopBreaks(localData, allParts); - - List loops2 = new ArrayList<>(); - for (int i = 0; i < loopHeads.size(); i++) { - loops2.add(new Loop(loops2.size(), loopHeads.get(i), null)); - } - for (int i = 0; i < loopHeads.size(); i++) { - if (loopBreaks.containsKey(loopHeads.get(i))) { - loops2.get(i).loopBreak = loopBreaks.get(loopHeads.get(i)).get(0);//getMostCommonPart(localData, loopBreaks.get(loopHeads.get(i)), loops2); - } else { - loops2.get(i).loopBreak = null; - } - } - - loops = loops2; - } - - /* - System.err.println(""); - for (Loop el : loops) { - System.err.println(el); - } - System.err.println(""); - */ - //TODO: Make getPrecontinues faster - getPrecontinues(path, localData, null, heads.get(0), allParts, loops, null); - - /*System.err.println(""); - for (Loop el : loops) { - System.err.println(el); - } - System.err.println("");//*/ - List ret = printGraph(new HashMap<>(), new HashMap<>(), localData, stack, allParts, null, heads.get(0), null, loops, staticOperation, path); - processIfs(ret); - finalProcessStack(stack, ret); - finalProcessAll(ret, 0, new FinalProcessLocalData()); - return ret; - } - - public void finalProcessStack(TranslateStack stack, List output) { - } - - private void finalProcessAll(List list, int level, FinalProcessLocalData localData) throws InterruptedException { - finalProcess(list, level, localData); - for (GraphTargetItem item : list) { - if (item instanceof Block) { - List> subs = ((Block) item).getSubs(); - for (List sub : subs) { - finalProcessAll(sub, level + 1, localData); - } - } - } - finalProcessAfter(list, level, localData); - } - - private boolean processSubBlk(Block b, GraphTargetItem replacement) { - boolean allSubPush = true; - boolean atleastOne = false; - for (List sub : b.getSubs()) { - if (!sub.isEmpty()) { - int lastPos = sub.size() - 1; - - GraphTargetItem last = sub.get(sub.size() - 1); - GraphTargetItem br = null; - - if ((last instanceof BreakItem) && (sub.size() >= 2)) { - br = last; - lastPos--; - last = sub.get(lastPos); - } - if (last instanceof Block) { - if (!processSubBlk((Block) last, replacement)) { - allSubPush = false; - } else { - atleastOne = true; - } - } else if (last instanceof PushItem) { - if (replacement != null) { - GraphTargetItem e2 = (((GraphTargetItem) replacement).clone()); - e2.value = last.value; - sub.set(lastPos, e2); - if (br != null) { - sub.remove(sub.size() - 1); - } - } - atleastOne = true; - } else if (!(last instanceof ExitItem)) { - allSubPush = false; - } - } - } - return allSubPush && atleastOne; - } - - protected void finalProcessAfter(List list, int level, FinalProcessLocalData localData) { - if (list.size() >= 2) { - if (list.get(list.size() - 1) instanceof ExitItem) { - ExitItem e = (ExitItem) list.get(list.size() - 1); - if (list.get(list.size() - 1).value instanceof PopItem) { - if (list.get(list.size() - 2) instanceof Block) { - Block b = (Block) list.get(list.size() - 2); - if (processSubBlk(b, (GraphTargetItem) e)) { - list.remove(list.size() - 1); - } - } - } - } - } - } - - protected void finalProcess(List list, int level, FinalProcessLocalData localData) throws InterruptedException { - - //For detection based on debug line information - boolean toDelete[] = new boolean[list.size()]; - for (int i = 0; i < list.size(); i++) { - if (Thread.currentThread().isInterrupted()) { - throw new InterruptedException(); - } - - GraphTargetItem itemI = list.get(i); - if (itemI instanceof ForItem) { - ForItem fori = (ForItem) itemI; - int exprLine = fori.getLine(); - if (exprLine > 0) { - List forFirstCommands = new ArrayList<>(); - for (int j = i - 1; j >= 0; j--) { - if (list.get(j).getLine() == exprLine && !(list.get(j) instanceof LoopItem /*to avoid recursion and StackOverflow*/)) { - forFirstCommands.add(0, list.get(j)); - toDelete[j] = true; - } else { - break; - } - } - fori.firstCommands.addAll(0, forFirstCommands); - } - } - - if (itemI instanceof WhileItem) { - WhileItem whi = (WhileItem) itemI; - int whileExprLine = whi.getLine(); - if (whileExprLine > 0) { - List forFirstCommands = new ArrayList<>(); - List forFinalCommands = new ArrayList<>(); - - for (int j = i - 1; j >= 0; j--) { - GraphTargetItem itemJ = list.get(j); - if (itemJ.getLine() == whileExprLine && !(itemJ instanceof LoopItem /*to avoid recursion and StackOverflow*/)) { - forFirstCommands.add(0, itemJ); - toDelete[j] = true; - } else { - break; - } - } - for (int j = whi.commands.size() - 1; j >= 0; j--) { - if (whi.commands.get(j).getLine() == whileExprLine && !(whi.commands.get(j) instanceof LoopItem /*to avoid recursion and StackOverflow*/)) { - forFinalCommands.add(0, whi.commands.remove(j)); - } else { - break; - } - } - if (!forFirstCommands.isEmpty() || !forFinalCommands.isEmpty()) { - //Do not allow more than 2 first/final commands, since it can be obfuscated - if (forFirstCommands.size() > 2 || forFinalCommands.size() > 2) { - //put it back - for (int k = 0; k < forFirstCommands.size(); k++) { - toDelete[i - 1 - k] = false; - } - whi.commands.addAll(forFinalCommands); //put it back - } else if (whi.commands.isEmpty() && forFirstCommands.isEmpty()) { - //it would be for(;expr;commands) {} which looks better as while(expr){commands} - whi.commands.addAll(forFinalCommands); //put it back - } else { - GraphTargetItem lastExpr = whi.expression.remove(whi.expression.size() - 1); - forFirstCommands.addAll(whi.expression); - list.set(i, new ForItem(whi.getSrc(), whi.getLineStartItem(), whi.loop, forFirstCommands, lastExpr, forFinalCommands, whi.commands)); - } - } - } - } - } - - for (int i = toDelete.length - 1; i >= 0; i--) { - if (toDelete[i]) { - list.remove(i); - } - } - } - - private void processIfs(List list) { - - for (int i = 0; i < list.size(); i++) { - GraphTargetItem item = list.get(i); - if ((item instanceof LoopItem) && (item instanceof Block)) { - List> subs = ((Block) item).getSubs(); - for (List sub : subs) { - processIfs(sub); - checkContinueAtTheEnd(sub, ((LoopItem) item).loop); - } - } else if (item instanceof Block) { - List> subs = ((Block) item).getSubs(); - for (List sub : subs) { - processIfs(sub); - } - } - if (item instanceof IfItem) { - IfItem ifi = (IfItem) item; - List onTrue = ifi.onTrue; - List onFalse = ifi.onFalse; - if ((!onTrue.isEmpty()) && (!onFalse.isEmpty())) { - if (onTrue.get(onTrue.size() - 1) instanceof ContinueItem) { - if (onFalse.get(onFalse.size() - 1) instanceof ContinueItem) { - if (((ContinueItem) onTrue.get(onTrue.size() - 1)).loopId == ((ContinueItem) onFalse.get(onFalse.size() - 1)).loopId) { - onTrue.remove(onTrue.size() - 1); - list.add(i + 1, onFalse.remove(onFalse.size() - 1)); - } - } - } - } - - if ((!onTrue.isEmpty()) && (!onFalse.isEmpty())) { - GraphTargetItem last = onTrue.get(onTrue.size() - 1); - if ((last instanceof ExitItem) || (last instanceof ContinueItem) || (last instanceof BreakItem)) { - list.addAll(i + 1, onFalse); - onFalse.clear(); - } - } - - if ((!onTrue.isEmpty()) && (!onFalse.isEmpty())) { - if (onFalse.get(onFalse.size() - 1) instanceof ExitItem) { - if (onTrue.get(onTrue.size() - 1) instanceof ContinueItem) { - list.add(i + 1, onTrue.remove(onTrue.size() - 1)); - } - } - } - } - } - - //Same continues in onTrue and onFalse gets continue on parent level - } - - protected List getLoopsContinuesPreAndBreaks(List loops) { - List ret = new ArrayList<>(); - for (Loop l : loops) { - if (l.loopContinue != null) { - ret.add(l.loopContinue); - } - if (l.loopPreContinue != null) { - ret.add(l.loopPreContinue); - } - if (l.loopBreak != null) { - ret.add(l.loopBreak); - } - } - return ret; - } - - protected List getLoopsContinuesAndPre(List loops) { - List ret = new ArrayList<>(); - for (Loop l : loops) { - if (l.loopContinue != null) { - ret.add(l.loopContinue); - } - if (l.loopPreContinue != null) { - ret.add(l.loopPreContinue); - } - } - return ret; - } - - protected List getLoopsContinues(List loops) { - List ret = new ArrayList<>(); - for (Loop l : loops) { - if (l.loopContinue != null) { - ret.add(l.loopContinue); - } - /*if (l.loopPreContinue != null) { - ret.add(l.loopPreContinue); - }*/ - } - return ret; - } - - protected GraphTargetItem checkLoop(GraphPart part, List stopPart, List loops) { - if (stopPart.contains(part)) { - return null; - } - - GraphSourceItem firstIns = null; - if (part != null) { - if (part.start >= 0 && part.start < code.size()) { - firstIns = code.get(part.start); - } - } - - for (Loop l : loops) { - if (l.loopContinue == part) { - return (new ContinueItem(null, firstIns, l.id)); - } - if (l.loopBreak == part) { - return (new BreakItem(null, firstIns, l.id)); - } - } - return null; - } - - private void checkContinueAtTheEnd(List commands, Loop loop) { - if (!commands.isEmpty()) { - int i = commands.size() - 1; - for (; i >= 0; i--) { - if (commands.get(i) instanceof ContinueItem) { - continue; - } - if (commands.get(i) instanceof BreakItem) { - continue; - } - break; - } - if (i < commands.size() - 1) { - for (int k = i + 2; k < commands.size(); k++) { - commands.remove(k); - } - } - if (commands.get(commands.size() - 1) instanceof ContinueItem) { - if (((ContinueItem) commands.get(commands.size() - 1)).loopId == loop.id) { - commands.remove(commands.size() - 1); - } - } - } - } - - protected boolean isEmpty(List output) { - if (output.isEmpty()) { - return true; - } - if (output.size() == 1) { - if (output.get(0) instanceof MarkItem) { - return true; - } - } - return false; - } - - protected List check(Map> partCodes, Map partCodePos, GraphSource code, BaseLocalData localData, Set allParts, TranslateStack stack, GraphPart parent, GraphPart part, List stopPart, List loops, List output, Loop currentLoop, int staticOperation, String path) throws InterruptedException { - return null; - } - - protected GraphPart checkPart(TranslateStack stack, BaseLocalData localData, GraphPart part, Set allParts) { - return part; - } - - //@SuppressWarnings("unchecked") - protected GraphTargetItem translatePartGetStack(BaseLocalData localData, GraphPart part, TranslateStack stack, int staticOperation) throws InterruptedException { - stack = (TranslateStack) stack.clone(); - translatePart(localData, part, stack, staticOperation, null); - return stack.pop(); - } - - protected List translatePart(BaseLocalData localData, GraphPart part, TranslateStack stack, int staticOperation, String path) throws InterruptedException { - List sub = part.getSubParts(); - List ret = new ArrayList<>(); - int end; - for (GraphPart p : sub) { - if (p.end == -1) { - p.end = code.size() - 1; - } - if (p.start == code.size()) { - continue; - } else if (p.end == code.size()) { - p.end--; - } - end = p.end; - int start = p.start; - ret.addAll(code.translatePart(part, localData, stack, start, end, staticOperation, path)); - } - return ret; - } - - private void markBranchEnd(List items) { - if (!items.isEmpty()) { - if (items.get(items.size() - 1) instanceof BreakItem) { - return; - } - if (items.get(items.size() - 1) instanceof ContinueItem) { - return; - } - if (items.get(items.size() - 1) instanceof ExitItem) { - return; - } - } - items.add(new MarkItem("finish")); - } - - private static GraphTargetItem getLastNoEnd(List list) { - if (list.isEmpty()) { - return null; - } - if (list.get(list.size() - 1) instanceof ScriptEndItem) { - if (list.size() >= 2) { - return list.get(list.size() - 2); - } - return list.get(list.size() - 1); - } - return list.get(list.size() - 1); - } - - private static void removeLastNoEnd(List list) { - if (list.isEmpty()) { - return; - } - if (list.get(list.size() - 1) instanceof ScriptEndItem) { - if (list.size() >= 2) { - list.remove(list.size() - 2); - } - return; - } - list.remove(list.size() - 1); - } - - protected List printGraph(Map> partCodes, Map partCodePos, BaseLocalData localData, TranslateStack stack, Set allParts, GraphPart parent, GraphPart part, List stopPart, List loops, int staticOperation, String path) throws InterruptedException { - return printGraph(partCodes, partCodePos, new HashSet<>(), localData, stack, allParts, parent, part, stopPart, loops, null, staticOperation, path, 0); - } - - protected GraphTargetItem checkLoop(LoopItem loopItem, BaseLocalData localData, List loops) { - return loopItem; - } - - //TODO: Make this faster!!! - private void getPrecontinues(String path, BaseLocalData localData, GraphPart parent, GraphPart part, Set allParts, List loops, List stopPart) throws InterruptedException { - try { - markLevels(path, localData, part, allParts, loops); - } catch (ThreadDeath | InterruptedException iex) { - throw iex; - } catch (Throwable ex) { - //It is unusual code so markLevels failed, nevermind, it can still work - } - //Note: this also marks part as precontinue when there is if - /* - while(k<10){ - if(k==7){ - trace(a); - }else{ - trace(b); - } - //precontinue - k++; - } - - */ - looploops: - for (Loop l : loops) { - if (l.loopContinue != null) { - Set uniqueRefs = new HashSet<>(); - uniqueRefs.addAll(l.loopContinue.refs); - if (uniqueRefs.size() == 2) { //only one path - from precontinue - List uniqueRefsList = new ArrayList<>(uniqueRefs); - if (uniqueRefsList.get(0).discoveredTime > uniqueRefsList.get(1).discoveredTime) { //latch node is discovered later - part = uniqueRefsList.get(0); - } else { - part = uniqueRefsList.get(1); - } - if (part == l.loopContinue) { - continue looploops; - } - - while (part.refs.size() == 1) { - if (part.refs.get(0).nextParts.size() != 1) { - continue looploops; - } - - part = part.refs.get(0); - if (part == l.loopContinue) { - break; - } - } - if (part.level == 0 && part != l.loopContinue) { - l.loopPreContinue = part; - } - } - } - } - /*clearLoops(loops); - getPrecontinues(parent, part, loops, stopPart, 0, new ArrayList()); - clearLoops(loops);*/ - } - - private void markLevels(String path, BaseLocalData localData, GraphPart part, Set allParts, List loops) throws InterruptedException { - clearLoops(loops); - markLevels(path, localData, part, allParts, loops, new ArrayList<>(), 1, new HashSet<>(), 0); - clearLoops(loops); - } - - private void markLevels(String path, BaseLocalData localData, GraphPart part, Set allParts, List loops, List stopPart, int level, Set visited, int recursionLevel) throws InterruptedException { - boolean debugMode = false; - if (stopPart == null) { - stopPart = new ArrayList<>(); - } - if (recursionLevel > allParts.size() + 1) { - throw new RuntimeException(path + ": markLevels max recursion level reached"); - } - - if (debugMode) { - System.err.println("markLevels " + part); - } - if (stopPart.contains(part)) { - return; - } - for (Loop el : loops) { - if ((el.phase == 2) && (el.loopContinue == part)) { - return; - } - if (el.phase != 1) { - if (debugMode) { - //System.err.println("ignoring "+el); - } - continue; - } - if (el.loopContinue == part) { - return; - } - if (el.loopPreContinue == part) { - return; - } - if (el.loopBreak == part) { - return; - } - } - - if (visited.contains(part)) { - part.level = 0; - } else { - visited.add(part); - part.level = level; - } - - boolean isLoop = false; - Loop currentLoop = null; - for (Loop el : loops) { - if ((el.phase == 0) && (el.loopContinue == part)) { - isLoop = true; - currentLoop = el; - el.phase = 1; - break; - } - } - - List nextParts = checkPrecoNextParts(part); - if (nextParts == null) { - nextParts = part.nextParts; - } - - if (nextParts.size() == 2) { - GraphPart next = getCommonPart(localData, nextParts, loops);//part.getNextPartPath(new ArrayList()); - List stopParts2 = new ArrayList<>(); //stopPart); - if (next != null) { - stopParts2.add(next); - } else if (!stopPart.isEmpty()) { - stopParts2.add(stopPart.get(stopPart.size() - 1)); - } - if (next != nextParts.get(0)) { - markLevels(path, localData, nextParts.get(0), allParts, loops, next == null ? stopPart : stopParts2, level + 1, visited, recursionLevel + 1); - } - if (next != nextParts.get(1)) { - markLevels(path, localData, nextParts.get(1), allParts, loops, next == null ? stopPart : stopParts2, level + 1, visited, recursionLevel + 1); - } - if (next != null) { - markLevels(path, localData, next, allParts, loops, stopPart, level, visited, recursionLevel + 1); - } - } - - if (nextParts.size() > 2) { - GraphPart next = getMostCommonPart(localData, nextParts, loops); - List vis = new ArrayList<>(); - for (GraphPart p : nextParts) { - if (vis.contains(p)) { - continue; - } - List stopPart2 = new ArrayList<>(); //(stopPart); - if (next != null) { - stopPart2.add(next); - } else if (!stopPart.isEmpty()) { - stopPart2.add(stopPart.get(stopPart.size() - 1)); - } - for (GraphPart p2 : nextParts) { - if (p2 == p) { - continue; - } - if (!stopPart2.contains(p2)) { - stopPart2.add(p2); - } - } - if (next != p) { - markLevels(path, localData, p, allParts, loops, stopPart2, level + 1, visited, recursionLevel + 1); - vis.add(p); - } - } - if (next != null) { - markLevels(path, localData, next, allParts, loops, stopPart, level, visited, recursionLevel + 1); - } - } - - if (nextParts.size() == 1) { - markLevels(path, localData, nextParts.get(0), allParts, loops, stopPart, level, visited, recursionLevel + 1); - } - - for (GraphPart t : part.throwParts) { - if (!visited.contains(t)) { - List stopPart2 = new ArrayList<>(); - List cmn = new ArrayList<>(); - cmn.add(part); - cmn.add(t); - GraphPart next = getCommonPart(localData, cmn, loops); - if (next != null) { - stopPart2.add(next); - } else { - stopPart2 = stopPart; - } - - markLevels(path, localData, t, allParts, loops, stopPart2, level, visited, recursionLevel + 1); - } - } - - if (isLoop) { - if (currentLoop != null && currentLoop.loopBreak != null) { - currentLoop.phase = 2; - markLevels(path, localData, currentLoop.loopBreak, allParts, loops, stopPart, level, visited, recursionLevel + 1); - } - } - } - - private void clearLoops(List loops) { - for (Loop l : loops) { - l.phase = 0; - } - } - - private void getLoops(BaseLocalData localData, GraphPart part, List loops, List stopPart) throws InterruptedException { - clearLoops(loops); - getLoops(localData, part, loops, stopPart, true, 1, new ArrayList<>()); - clearLoops(loops); - } - - private void getLoops(BaseLocalData localData, GraphPart part, List loops, List stopPart, boolean first, int level, List visited) throws InterruptedException { - boolean debugMode = false; - - if (part == null) { - return; - } - - part = checkPart(null, localData, part, null); - if (part == null) { - return; - } - if (!visited.contains(part)) { - visited.add(part); - } - - if (debugMode) { - System.err.println("getloops: " + part); - } - //List loopContinues = getLoopsContinues(loops); - Loop lastP1 = null; - for (Loop el : loops) { - if ((el.phase == 1) && el.loopBreak == null) { //break not found yet - if (el.loopContinue != part) { - lastP1 = el; - - } else { - lastP1 = null; - } - - } - } - if (lastP1 != null) { - if (lastP1.breakCandidates.contains(part)) { - lastP1.breakCandidates.add(part); - lastP1.breakCandidatesLevels.add(level); - return; - } else { - //List loopContinues2 = new ArrayList<>(loopContinues); - //loopContinues2.remove(lastP1.loopContinue); - List loops2 = new ArrayList<>(loops); - loops2.remove(lastP1); - if (!part.leadsTo(localData, this, code, lastP1.loopContinue, loops2)) { - if (lastP1.breakCandidatesLocked == 0) { - if (debugMode) { - System.err.println("added breakCandidate " + part + " to " + lastP1); - } - - lastP1.breakCandidates.add(part); - lastP1.breakCandidatesLevels.add(level); - return; - } - } - } - } - - for (Loop el : loops) { - if (el.loopContinue == part) { - return; - } - } - - if (stopPart != null && stopPart.contains(part)) { - return; - } - part.level = level; - - boolean isLoop = part.leadsTo(localData, this, code, part, loops); - Loop currentLoop = null; - if (isLoop) { - currentLoop = new Loop(loops.size(), part, null); - currentLoop.phase = 1; - loops.add(currentLoop); - //loopContinues.add(part); - } - - if (part.nextParts.size() == 2) { - - List nps;/* = new ArrayList<>(part.nextParts); - for(int i=0;i stopPart2 = stopPart == null ? new ArrayList<>() : new ArrayList<>(stopPart); - if (next != null) { - stopPart2.add(next); - } - if (next != nps.get(0)) { - getLoops(localData, nps.get(0), loops, stopPart2, false, level + 1, visited); - } - if (next != nps.get(1)) { - getLoops(localData, nps.get(1), loops, stopPart2, false, level + 1, visited); - } - if (next != null) { - getLoops(localData, next, loops, stopPart, false, level, visited); - } - } else if (part.nextParts.size() > 2) { - GraphPart next = getNextCommonPart(localData, part, loops); - - for (GraphPart p : part.nextParts) { - List stopPart2 = stopPart == null ? new ArrayList<>() : new ArrayList<>(stopPart); - if (next != null) { - stopPart2.add(next); - } - for (GraphPart p2 : part.nextParts) { - if (p2 == p) { - continue; - } - if (!stopPart2.contains(p2)) { - stopPart2.add(p2); - } - } - if (next != p) { - getLoops(localData, p, loops, stopPart2, false, level + 1, visited); - } - } - if (next != null) { - getLoops(localData, next, loops, stopPart, false, level, visited); - } - } else if (part.nextParts.size() == 1) { - getLoops(localData, part.nextParts.get(0), loops, stopPart, false, level, visited); - } - - List loops2 = new ArrayList<>(loops); - for (Loop l : loops2) { - l.breakCandidatesLocked++; - } - for (GraphPart t : part.throwParts) { - if (!visited.contains(t)) { - getLoops(localData, t, loops, stopPart, false, level, visited); - } - } - for (Loop l : loops2) { - l.breakCandidatesLocked--; - } - - if (isLoop && currentLoop != null) { - GraphPart found; - Map removed = new HashMap<>(); - do { - found = null; - for (int i = 0; i < currentLoop.breakCandidates.size(); i++) { - GraphPart ch = checkPart(null, localData, currentLoop.breakCandidates.get(i), null); - if (ch == null) { - currentLoop.breakCandidates.remove(i); - i--; - } - } - loopcand: - for (GraphPart cand : currentLoop.breakCandidates) { - for (GraphPart cand2 : currentLoop.breakCandidates) { - if (cand == cand2) { - continue; - } - if (cand.leadsTo(localData, this, code, cand2, loops)) { - int lev1 = Integer.MAX_VALUE; - int lev2 = Integer.MAX_VALUE; - for (int i = 0; i < currentLoop.breakCandidates.size(); i++) { - if (currentLoop.breakCandidates.get(i) == cand) { - if (currentLoop.breakCandidatesLevels.get(i) < lev1) { - lev1 = currentLoop.breakCandidatesLevels.get(i); - } - } - if (currentLoop.breakCandidates.get(i) == cand2) { - if (currentLoop.breakCandidatesLevels.get(i) < lev2) { - lev2 = currentLoop.breakCandidatesLevels.get(i); - } - } - } - if (lev1 <= lev2) { - found = cand2; - } else { - found = cand; - } - break loopcand; - } - } - } - if (found != null) { - int maxlevel = 0; - while (currentLoop.breakCandidates.contains(found)) { - int ind = currentLoop.breakCandidates.indexOf(found); - currentLoop.breakCandidates.remove(ind); - int lev = currentLoop.breakCandidatesLevels.remove(ind); - if (lev > maxlevel) { - maxlevel = lev; - } - } - if (removed.containsKey(found)) { - if (removed.get(found) > maxlevel) { - maxlevel = removed.get(found); - } - } - removed.put(found, maxlevel); - } - } while ((found != null) && (currentLoop.breakCandidates.size() > 1)); - - Map count = new HashMap<>(); - GraphPart winner = null; - int winnerCount = 0; - for (GraphPart cand : currentLoop.breakCandidates) { - - if (!count.containsKey(cand)) { - count.put(cand, 0); - } - count.put(cand, count.get(cand) + 1); - boolean otherBreakCandidate = false; - for (Loop el : loops) { - if (el == currentLoop) { - continue; - } - if (el.breakCandidates.contains(cand)) { - otherBreakCandidate = true; - break; - } - } - if (otherBreakCandidate) { - } else if (count.get(cand) > winnerCount) { - winnerCount = count.get(cand); - winner = cand; - } else if (count.get(cand) == winnerCount && winner != null) { - if (cand.path.length() < winner.path.length()) { - winner = cand; - } - } - } - for (int i = 0; i < currentLoop.breakCandidates.size(); i++) { - GraphPart cand = currentLoop.breakCandidates.get(i); - if (cand != winner) { - int lev = currentLoop.breakCandidatesLevels.get(i); - if (removed.containsKey(cand)) { - if (removed.get(cand) > lev) { - lev = removed.get(cand); - } - } - removed.put(cand, lev); - } - } - currentLoop.loopBreak = winner; - currentLoop.phase = 2; - boolean start = false; - for (int l = 0; l < loops.size(); l++) { - Loop el = loops.get(l); - if (start) { - el.phase = 1; - } - if (el == currentLoop) { - start = true; - } - } - List removedVisited = new ArrayList<>(); - for (GraphPart r : removed.keySet()) { - if (removedVisited.contains(r)) { - continue; - } - getLoops(localData, r, loops, stopPart, false, removed.get(r), visited); - removedVisited.add(r); - } - start = false; - for (int l = 0; l < loops.size(); l++) { - Loop el = loops.get(l); - if (el == currentLoop) { - start = true; - } - if (start) { - el.phase = 2; - } - } - getLoops(localData, currentLoop.loopBreak, loops, stopPart, false, level, visited); - } - } - - protected List printGraph(Map> partCodes, Map partCodePos, Set visited, BaseLocalData localData, TranslateStack stack, Set allParts, GraphPart parent, GraphPart part, List stopPart, List loops, List ret, int staticOperation, String path, int recursionLevel) throws InterruptedException { - if (Thread.currentThread().isInterrupted()) { - throw new InterruptedException(); - } - if (stopPart == null) { - stopPart = new ArrayList<>(); - } - if (recursionLevel > allParts.size() + 1) { - throw new TranslateException("printGraph max recursion level reached."); - } - - if (ret == null) { - ret = new ArrayList<>(); - } - //try { - boolean debugMode = false; - - if (debugMode) { - System.err.println("PART " + part + " nextsize:" + part.nextParts.size()); - } - - /*while (((part != null) && (part.getHeight() == 1)) && (code.size() > part.start) && (code.get(part.start).isJump())) { //Parts with only jump in it gets ignored - - if (part == stopPart) { - return ret; - } - GraphTargetItem lop = checkLoop(part.nextParts.get(0), stopPart, loops); - if (lop == null) { - part = part.nextParts.get(0); - } else { - break; - } - }*/ - if (part == null) { - return ret; - } - part = checkPart(stack, localData, part, allParts); - if (part == null) { - return ret; - } - - if (part.ignored) { - return ret; - } - - //List loopContinues = getLoopsContinues(loops); - boolean isLoop = false; - Loop currentLoop = null; - for (Loop el : loops) { - if ((el.loopContinue == part) && (el.phase == 0)) { - currentLoop = el; - currentLoop.phase = 1; - isLoop = true; - break; - } - } - - if (debugMode) { - System.err.println("loopsize:" + loops.size()); - } - for (int l = loops.size() - 1; l >= 0; l--) { - Loop el = loops.get(l); - if (el == currentLoop) { - if (debugMode) { - System.err.println("ignoring current loop " + el); - } - continue; - } - if (el.phase != 1) { - if (debugMode) { - System.err.println("ignoring loop " + el); - } - continue; - } - if (el.loopBreak == part) { - if (currentLoop != null) { - currentLoop.phase = 0; - } - if (debugMode) { - System.err.println("Adding break"); - } - ret.add(new BreakItem(null, localData.lineStartInstruction, el.id)); - return ret; - } - if (el.loopPreContinue == part) { - if (currentLoop != null) { - currentLoop.phase = 0; - } - if (debugMode) { - System.err.println("Adding precontinue"); - } - ret.add(new ContinueItem(null, localData.lineStartInstruction, el.id)); - return ret; - } - if (el.loopContinue == part) { - if (currentLoop != null) { - currentLoop.phase = 0; - } - if (debugMode) { - System.err.println("Adding continue"); - } - ret.add(new ContinueItem(null, localData.lineStartInstruction, el.id)); - return ret; - } - } - - if (stopPart.contains(part)) { - if (currentLoop != null) { - currentLoop.phase = 0; - } - if (debugMode) { - System.err.println("Stopped on part " + part); - } - return ret; - } - - if (code.size() <= part.start) { - ret.add(new ScriptEndItem()); - return ret; - } - - if (visited.contains(part)) { - String labelName = "addr" + part.start; - List firstCode = partCodes.get(part); - int firstCodePos = partCodePos.get(part); - if (firstCodePos > firstCode.size()) { - firstCodePos = firstCode.size(); - } - if (firstCode.size() > firstCodePos && (firstCode.get(firstCodePos) instanceof LabelItem)) { - labelName = ((LabelItem) firstCode.get(firstCodePos)).labelName; - } else { - firstCode.add(firstCodePos, new LabelItem(null, localData.lineStartInstruction, labelName)); - } - ret.add(new GotoItem(null, localData.lineStartInstruction, labelName)); - return ret; - } else { - visited.add(part); - partCodes.put(part, ret); - partCodePos.put(part, ret.size()); - } - List currentRet = ret; - UniversalLoopItem loopItem = null; - TranslateStack sPreLoop = stack; - if (isLoop) { - //makeAllCommands(currentRet, stack); - stack = (TranslateStack) stack.clone(); - stack.clear(); - loopItem = new UniversalLoopItem(null, localData.lineStartInstruction, currentLoop); - //loopItem.commands=printGraph(visited, localData, stack, allParts, parent, part, stopPart, loops); - currentRet.add(loopItem); - loopItem.commands = new ArrayList<>(); - currentRet = loopItem.commands; - //return ret; - } - - boolean parseNext = true; - - //****************************DECOMPILING PART************* - List output = new ArrayList<>(); - - List parts = new ArrayList<>(); - if (part instanceof GraphPartMulti) { - parts = ((GraphPartMulti) part).parts; - } else { - parts.add(part); - } - for (GraphPart p : parts) { - int end = p.end; - int start = p.start; - - output.addAll(code.translatePart(p, localData, stack, start, end, staticOperation, path)); - if ((end >= code.size() - 1) && p.nextParts.isEmpty()) { - output.add(new ScriptEndItem()); - } - } - - if (parseNext) { - List retCheck = check(partCodes, partCodePos, code, localData, allParts, stack, parent, part, stopPart, loops, output, currentLoop, staticOperation, path); - if (retCheck != null) { - if (!retCheck.isEmpty()) { - currentRet.addAll(retCheck); - } - parseNext = false; - //return ret; - } else { - currentRet.addAll(output); - } - } -//********************************END PART DECOMPILING - if (parseNext) { - - if (part.nextParts.size() > 2) { - GraphPart next = getMostCommonPart(localData, part.nextParts, loops); - List vis = new ArrayList<>(); - GraphTargetItem switchedItem = stack.pop(); - makeAllCommands(currentRet, stack); - - List caseValues = new ArrayList<>(); - List> caseCommands = new ArrayList<>(); - List valueMappings = new ArrayList<>(); - Loop swLoop = new Loop(loops.size(), null, next); - swLoop.phase = 1; - loops.add(swLoop); - boolean first = false; - int pos; - - Map caseExpressions = new HashMap<>(); - Map caseExpressionLeftSides = new HashMap<>(); - Map caseExpressionRightSides = new HashMap<>(); - GraphTargetItem it = switchedItem; - int defaultBranch = 0; - boolean hasExpr = false; - - while (it instanceof TernarOpItem) { - TernarOpItem to = (TernarOpItem) it; - if (to.expression instanceof EqualsTypeItem) { - if (to.onTrue instanceof IntegerValueTypeItem) { - int cpos = ((IntegerValueTypeItem) to.onTrue).intValue(); - caseExpressionLeftSides.put(cpos, ((EqualsTypeItem) to.expression).getLeftSide()); - caseExpressionRightSides.put(cpos, ((EqualsTypeItem) to.expression).getRightSide()); - it = to.onFalse; - } else { - break; - } - } else if (to.expression instanceof FalseItem) { - it = to.onFalse; - } else if (to.expression instanceof TrueItem) { - it = to.onTrue; - } else { - break; - } - } - //int ignoredBranch = -1; - if (it instanceof IntegerValueTypeItem) { - defaultBranch = ((IntegerValueTypeItem) it).intValue(); - } - - if (!caseExpressionRightSides.isEmpty()) { - GraphTargetItem firstItem; - firstItem = (GraphTargetItem) caseExpressionRightSides.values().toArray()[0]; - boolean sameRight = true; - for (GraphTargetItem cit : caseExpressionRightSides.values()) { - if (!cit.equals(firstItem)) { - sameRight = false; - break; - } - } - - if (sameRight) { - caseExpressions = caseExpressionLeftSides; - switchedItem = firstItem; - hasExpr = true; - } else { - firstItem = (GraphTargetItem) caseExpressionLeftSides.values().toArray()[0]; - - boolean sameLeft = true; - for (GraphTargetItem cit : caseExpressionLeftSides.values()) { - if (!cit.equals(firstItem)) { - sameLeft = false; - break; - } - } - if (sameLeft) { - caseExpressions = caseExpressionRightSides; - switchedItem = firstItem; - hasExpr = true; - } - } - } - - first = true; - pos = 0; - //This is tied to AS3 switch implementation which has nextparts switched from index 1. TODO: Make more universal - - GraphPart defaultPart = hasExpr ? part.nextParts.get(1 + defaultBranch) : part.nextParts.get(0); - //int defaultNum = hasExpr ? 1 + defaultBranch : 0; - - for (int i = 1; i < part.nextParts.size(); i++) { - if (caseExpressions.containsKey(pos)) { - caseValues.add(caseExpressions.get(pos)); - } else if (part.nextParts.get(i) == defaultPart) { - caseValues.add(new DefaultItem()); - } else { - caseValues.add(new IntegerValueItem(null, localData.lineStartInstruction, pos)); - } - pos++; - } - - first = true; - pos = 0; - List nextCommands = new ArrayList<>(); - for (int i = 1; i < part.nextParts.size(); i++) { - GraphPart p = part.nextParts.get(i); - /*if (pos == ignoredBranch) { - pos++; - continue; - }*/ - //if (p != defaultPart) - { - if (vis.contains(p)) { - valueMappings.add(caseCommands.size() - 1); - continue; - } - valueMappings.add(caseCommands.size()); - } - List stopPart2 = new ArrayList<>(); - if (next != null) { - stopPart2.add(next); - } else if (!stopPart.isEmpty()) { - stopPart2.add(stopPart.get(stopPart.size() - 1)); - } - for (GraphPart p2 : part.nextParts) { - if (p2 == p) { - continue; - } - if (!stopPart2.contains(p2)) { - stopPart2.add(p2); - } - } - if (next != p) { - //if (p == defaultPart && !defaultCommands.isEmpty()) { - //ignore - //} else - { - TranslateStack s2 = (TranslateStack) stack.clone(); - s2.clear(); - nextCommands = printGraph(partCodes, partCodePos, visited, prepareBranchLocalData(localData), s2, allParts, part, p, stopPart2, loops, null, staticOperation, path, recursionLevel + 1); - makeAllCommands(nextCommands, s2); - caseCommands.add(nextCommands); - vis.add(p); - } - } else { - caseCommands.add(nextCommands); - } - first = false; - pos++; - } - - //If the lastone is default empty and alone, remove it - if (!caseCommands.isEmpty()) { - List lastc = caseCommands.get(caseCommands.size() - 1); - if (!lastc.isEmpty() && (lastc.get(lastc.size() - 1) instanceof BreakItem)) { - BreakItem bi = (BreakItem) lastc.get(lastc.size() - 1); - if (bi.loopId == swLoop.id) { - lastc.remove(lastc.size() - 1); - } - } - if (lastc.isEmpty()) { - int cnt = 0; - if (caseValues.get(caseValues.size() - 1) instanceof DefaultItem) { - for (int i = valueMappings.size() - 1; i >= 0; i--) { - if (valueMappings.get(i) == caseCommands.size() - 1) { - cnt++; - } - } - if (cnt == 1) { - caseValues.remove(caseValues.size() - 1); - valueMappings.remove(valueMappings.size() - 1); - caseCommands.remove(lastc); - } - } - } - } - //remove last break from last section - if (!caseCommands.isEmpty()) { - List lastc = caseCommands.get(caseCommands.size() - 1); - if (!lastc.isEmpty() && (lastc.get(lastc.size() - 1) instanceof BreakItem)) { - BreakItem bi = (BreakItem) lastc.get(lastc.size() - 1); - if (bi.loopId == swLoop.id) { - lastc.remove(lastc.size() - 1); - } - } - } - SwitchItem sw = new SwitchItem(null, localData.lineStartInstruction, swLoop, switchedItem, caseValues, caseCommands, valueMappings); - currentRet.add(sw); - swLoop.phase = 2; - if (next != null) { - currentRet.addAll(printGraph(partCodes, partCodePos, visited, localData, stack, allParts, part, next, stopPart, loops, null, staticOperation, path, recursionLevel + 1)); - } - pos++; - } //else - GraphPart nextOnePart = null; - if (part.nextParts.size() == 2) { - GraphTargetItem expr = stack.pop(); - /*if (expr instanceof LogicalOpItem) { - expr = ((LogicalOpItem) expr).invert(); - } else { - expr = new NotItem(null, expr); - }*/ - if (nextOnePart == null) { - - List nps; - nps = part.nextParts; - boolean isEmpty = nps.get(0) == nps.get(1); - - GraphPart next = getCommonPart(localData, nps, loops); - TranslateStack trueStack = (TranslateStack) stack.clone(); - TranslateStack falseStack = (TranslateStack) stack.clone(); - trueStack.clear(); - falseStack.clear(); - - if (isEmpty) { - next = nps.get(0); - } - boolean hasOntrue = nps.get(1) != next; - boolean hasOnFalse = nps.get(0) != next; - - List stopPart2 = new ArrayList<>(stopPart); - - if ((!isEmpty) && (next != null)) { - stopPart2.add(next); - } - - List onTrue = new ArrayList<>(); - if (!isEmpty && hasOntrue) { - onTrue = printGraph(partCodes, partCodePos, visited, prepareBranchLocalData(localData), trueStack, allParts, part, nps.get(1), stopPart2, loops, null, staticOperation, path, recursionLevel + 1); - } - List onFalse = new ArrayList<>(); - - if (!isEmpty && hasOnFalse) { - onFalse = printGraph(partCodes, partCodePos, visited, prepareBranchLocalData(localData), falseStack, allParts, part, nps.get(0), stopPart2, loops, null, staticOperation, path, recursionLevel + 1); - } - //List out2 = new ArrayList<>(); - //makeAllCommands(out2, stack); - makeAllCommands(onTrue, trueStack); - makeAllCommands(onFalse, falseStack); - - List filteredOnTrue = filter(onTrue); - List filteredOnFalse = filter(onFalse); - - if (!isEmpty(filteredOnTrue) && !isEmpty(filteredOnFalse) && filteredOnTrue.size() == 1 && filteredOnFalse.size() == 1 && (filteredOnTrue.get(0) instanceof PushItem) && (filteredOnFalse.get(0) instanceof PushItem)) { - stack.push(new TernarOpItem(null, localData.lineStartInstruction, expr.invert(null), ((PushItem) filteredOnTrue.get(0)).value, ((PushItem) filteredOnFalse.get(0)).value)); - } else { - boolean isIf = true; - //If the ontrue is empty, switch ontrue and onfalse - if (filteredOnTrue.isEmpty() && !filteredOnFalse.isEmpty()) { - expr = expr.invert(null); - List tmp = onTrue; - onTrue = onFalse; - onFalse = tmp; - //tmp = filteredOnTrue; - filteredOnTrue = filteredOnFalse; - //filteredOnFalse = tmp; - } - if (!stack.isEmpty() && ((filteredOnTrue.size() == 1 && (filteredOnTrue.get(0) instanceof PopItem)) || ((filteredOnTrue.size() >= 2) && (filteredOnTrue.get(0) instanceof PopItem) && (filteredOnTrue.get(filteredOnTrue.size() - 1) instanceof PushItem)))) { - if (filteredOnTrue.size() > 1) { - GraphTargetItem rightSide = ((PushItem) filteredOnTrue.get(filteredOnTrue.size() - 1)).value; - GraphTargetItem prevExpr = stack.pop(); - GraphTargetItem leftSide = expr.getNotCoercedNoDup(); - - if (leftSide instanceof DuplicateItem) { - isIf = false; - stack.push(new OrItem(null, localData.lineStartInstruction, prevExpr, rightSide)); - } else if (leftSide.invert(null).getNotCoercedNoDup() instanceof DuplicateItem) { - isIf = false; - stack.push(new AndItem(null, localData.lineStartInstruction, prevExpr, rightSide)); - } else if (prevExpr instanceof FalseItem) { - isIf = false; - leftSide = leftSide.invert(null); - stack.push(new AndItem(null, localData.lineStartInstruction, leftSide, rightSide)); - } else if (prevExpr instanceof TrueItem) { - isIf = false; - stack.push(new OrItem(null, localData.lineStartInstruction, leftSide, rightSide)); - } else { - //:-( - } - } else { - isIf = false; - } - } - - if (isIf) { - makeAllCommands(currentRet, stack); - IfItem b = new IfItem(null, localData.lineStartInstruction, expr.invert(null), onTrue, onFalse); - currentRet.add(b); - if (processSubBlk(b, null)) { - stack.push(new PopItem(null, localData.lineStartInstruction)); - } - } - } - //currentRet.addAll(out2); - if (next != null) { - printGraph(partCodes, partCodePos, visited, localData, stack, allParts, part, next, stopPart, loops, currentRet, staticOperation, path, recursionLevel + 1); - //currentRet.addAll(); - } - } - } //else - if (part.nextParts.size() == 1) { - nextOnePart = part.nextParts.get(0); - } - if (nextOnePart != null) { - printGraph(partCodes, partCodePos, visited, localData, stack, allParts, part, part.nextParts.get(0), stopPart, loops, currentRet, staticOperation, path, recursionLevel + 1); - } - - } - if (isLoop && loopItem != null && currentLoop != null) { - - LoopItem li = loopItem; - boolean loopTypeFound = false; - - boolean hasContinue = false; - processIfs(loopItem.commands); - checkContinueAtTheEnd(loopItem.commands, currentLoop); - List continues = loopItem.getContinues(); - for (ContinueItem c : continues) { - if (c.loopId == currentLoop.id) { - hasContinue = true; - break; - } - } - if (!hasContinue) { - if (currentLoop.loopPreContinue != null) { - List stopContPart = new ArrayList<>(); - stopContPart.add(currentLoop.loopContinue); - GraphPart precoBackup = currentLoop.loopPreContinue; - currentLoop.loopPreContinue = null; - loopItem.commands.addAll(printGraph(partCodes, partCodePos, visited, localData, new TranslateStack(path), allParts, null, precoBackup, stopContPart, loops, null, staticOperation, path, recursionLevel + 1)); - } - } - - //Loop with condition at the beginning (While) - if (!loopTypeFound && (!loopItem.commands.isEmpty())) { - if (loopItem.commands.get(0) instanceof IfItem) { - IfItem ifi = (IfItem) loopItem.commands.get(0); - - List bodyBranch = null; - boolean inverted = false; - boolean breakpos2 = false; - if ((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem)) { - BreakItem bi = (BreakItem) ifi.onTrue.get(0); - if (bi.loopId == currentLoop.id) { - bodyBranch = ifi.onFalse; - inverted = true; - } - } else if ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof BreakItem)) { - BreakItem bi = (BreakItem) ifi.onFalse.get(0); - if (bi.loopId == currentLoop.id) { - bodyBranch = ifi.onTrue; - } - } else if (loopItem.commands.size() == 2 && (loopItem.commands.get(1) instanceof BreakItem)) { - BreakItem bi = (BreakItem) loopItem.commands.get(1); - if (bi.loopId == currentLoop.id) { - if (ifi.onTrue.isEmpty()) { - inverted = true; - } - bodyBranch = inverted ? ifi.onFalse : ifi.onTrue; - breakpos2 = true; - } - } - if (bodyBranch != null) { - int index = ret.indexOf(loopItem); - ret.remove(index); - List exprList = new ArrayList<>(); - GraphTargetItem expr = ifi.expression; - if (inverted) { - if (expr instanceof LogicalOpItem) { - expr = ((LogicalOpItem) expr).invert(null); - } else { - expr = new NotItem(null, expr.getLineStartItem(), expr); - } - } - exprList.add(expr); - List commands = new ArrayList<>(); - commands.addAll(bodyBranch); - loopItem.commands.remove(0); - if (breakpos2) { - loopItem.commands.remove(0); //remove that break too - } - commands.addAll(loopItem.commands); - checkContinueAtTheEnd(commands, currentLoop); - List finalComm = new ArrayList<>(); - if (currentLoop.loopPreContinue != null) { - GraphPart backup = currentLoop.loopPreContinue; - currentLoop.loopPreContinue = null; - List stopPart2 = new ArrayList<>(stopPart); - stopPart2.add(currentLoop.loopContinue); - finalComm = printGraph(partCodes, partCodePos, visited, localData, new TranslateStack(path), allParts, null, backup, stopPart2, loops, null, staticOperation, path, recursionLevel + 1); - currentLoop.loopPreContinue = backup; - checkContinueAtTheEnd(finalComm, currentLoop); - } - if (!finalComm.isEmpty()) { - ret.add(index, li = new ForItem(expr.getSrc(), expr.getLineStartItem(), currentLoop, new ArrayList<>(), exprList.get(exprList.size() - 1), finalComm, commands)); - } else { - ret.add(index, li = new WhileItem(expr.getSrc(), expr.getLineStartItem(), currentLoop, exprList, commands)); - } - - loopTypeFound = true; - } - } - } - - //Loop with condition at the end (Do..While) - if (!loopTypeFound && (!loopItem.commands.isEmpty())) { - if (loopItem.commands.get(loopItem.commands.size() - 1) instanceof IfItem) { - IfItem ifi = (IfItem) loopItem.commands.get(loopItem.commands.size() - 1); - List bodyBranch = null; - boolean inverted = false; - if ((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem)) { - BreakItem bi = (BreakItem) ifi.onTrue.get(0); - if (bi.loopId == currentLoop.id) { - bodyBranch = ifi.onFalse; - inverted = true; - } - } else if ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof BreakItem)) { - BreakItem bi = (BreakItem) ifi.onFalse.get(0); - if (bi.loopId == currentLoop.id) { - bodyBranch = ifi.onTrue; - } - } - if (bodyBranch != null) { - //Condition at the beginning - int index = ret.indexOf(loopItem); - ret.remove(index); - List exprList = new ArrayList<>(); - GraphTargetItem expr = ifi.expression; - if (inverted) { - expr = expr.invert(null); - } - - checkContinueAtTheEnd(bodyBranch, currentLoop); - - List commands = new ArrayList<>(); - - if (!bodyBranch.isEmpty()) { - ret.add(index, loopItem); - /* - loopItem.commands.remove(loopItem.commands.size() - 1); - exprList.addAll(loopItem.commands); - commands.addAll(bodyBranch); - exprList.add(expr); - checkContinueAtTheEnd(commands, currentLoop); - ret.add(index, li = new WhileItem(null, currentLoop, exprList, commands));*/ - } else { - loopItem.commands.remove(loopItem.commands.size() - 1); - commands.addAll(loopItem.commands); - commands.addAll(bodyBranch); - exprList.add(expr); - checkContinueAtTheEnd(commands, currentLoop); - ret.add(index, li = new DoWhileItem(null, exprList.get(0).getLineStartItem(), currentLoop, commands, exprList)); - } - - loopTypeFound = true; - } - } - } - - if (!loopTypeFound) { - if (currentLoop.loopPreContinue != null) { - loopTypeFound = true; - GraphPart backup = currentLoop.loopPreContinue; - currentLoop.loopPreContinue = null; - List stopPart2 = new ArrayList<>(stopPart); - stopPart2.add(currentLoop.loopContinue); - List finalComm = printGraph(partCodes, partCodePos, visited, localData, new TranslateStack(path), allParts, null, backup, stopPart2, loops, null, staticOperation, path, recursionLevel + 1); - currentLoop.loopPreContinue = backup; - checkContinueAtTheEnd(finalComm, currentLoop); - - if (!finalComm.isEmpty()) { - if (finalComm.get(finalComm.size() - 1) instanceof IfItem) { - IfItem ifi = (IfItem) finalComm.get(finalComm.size() - 1); - boolean ok = false; - boolean invert = false; - if (((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem) && (((BreakItem) ifi.onTrue.get(0)).loopId == currentLoop.id)) - && ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof ContinueItem) && (((ContinueItem) ifi.onFalse.get(0)).loopId == currentLoop.id))) { - ok = true; - invert = true; - } - if (((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof ContinueItem) && (((ContinueItem) ifi.onTrue.get(0)).loopId == currentLoop.id)) - && ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof BreakItem) && (((BreakItem) ifi.onFalse.get(0)).loopId == currentLoop.id))) { - ok = true; - } - if (ok) { - finalComm.remove(finalComm.size() - 1); - int index = ret.indexOf(loopItem); - ret.remove(index); - List exprList = new ArrayList<>(finalComm); - GraphTargetItem expr = ifi.expression; - if (invert) { - expr = expr.invert(null); - } - exprList.add(expr); - ret.add(index, li = new DoWhileItem(null, expr.getLineStartItem(), currentLoop, loopItem.commands, exprList)); - } - } - } - } - } - - if (!loopTypeFound) { - checkContinueAtTheEnd(loopItem.commands, currentLoop); - } - currentLoop.phase = 2; - - GraphTargetItem replaced = checkLoop(li, localData, loops); - if (replaced != li) { - int index = ret.indexOf(li); - ret.remove(index); - if (replaced != null) { - ret.add(index, replaced); - } - } - - if (currentLoop.loopBreak != null) { - ret.addAll(printGraph(partCodes, partCodePos, visited, localData, sPreLoop, allParts, part, currentLoop.loopBreak, stopPart, loops, null, staticOperation, path, recursionLevel + 1)); - } - } - - return ret; - } - - protected void checkGraph(List allBlocks) { - } - - private List makeGraph(GraphSource code, List allBlocks, List alternateEntries) throws InterruptedException { - HashMap> refs = code.visitCode(alternateEntries); - List ret = new ArrayList<>(); - boolean[] visited = new boolean[code.size()]; - ret.add(makeGraph(null, new GraphPath(), code, 0, 0, allBlocks, refs, visited)); - for (int pos : alternateEntries) { - GraphPart e1 = new GraphPart(-1, -1); - e1.path = new GraphPath("e"); - ret.add(makeGraph(e1, new GraphPath("e"), code, pos, pos, allBlocks, refs, visited)); - } - checkGraph(allBlocks); - return ret; - } - - protected int checkIp(int ip) { - return ip; - } - - private GraphPart makeGraph(GraphPart parent, GraphPath path, GraphSource code, int startip, int lastIp, List allBlocks, HashMap> refs, boolean[] visited2) throws InterruptedException { - if (Thread.currentThread().isInterrupted()) { - throw new InterruptedException(); - } - - int ip = startip; - for (GraphPart p : allBlocks) { - if (p.start == ip) { - p.refs.add(parent); - return p; - } - } - GraphPart g; - GraphPart ret = new GraphPart(ip, -1); - ret.path = path; - GraphPart part = ret; - while (ip < code.size()) { - if (visited2[ip] || ((ip != startip) && (refs.get(ip).size() > 1))) { - part.end = lastIp; - GraphPart found = null; - for (GraphPart p : allBlocks) { - if (p.start == ip) { - found = p; - break; - } - } - - allBlocks.add(part); - - if (found != null) { - part.nextParts.add(found); - found.refs.add(part); - break; - } else { - GraphPart gp = new GraphPart(ip, -1); - gp.path = path; - part.nextParts.add(gp); - gp.refs.add(part); - part = gp; - } - } - - ip = checkIp(ip); - lastIp = ip; - GraphSourceItem ins = code.get(ip); - if (ins.isIgnored()) { - ip++; - continue; - } - if (ins instanceof GraphSourceItemContainer) { - GraphSourceItemContainer cnt = (GraphSourceItemContainer) ins; - if (ins instanceof Action) { //TODO: Remove dependency of AVM1 - long endAddr = ((Action) ins).getAddress() + cnt.getHeaderSize(); - for (long size : cnt.getContainerSizes()) { - endAddr += size; - } - ip = code.adr2pos(endAddr); - } - continue; - } else if (ins.isExit()) { - part.end = ip; - allBlocks.add(part); - break; - } else if (ins.isJump()) { - part.end = ip; - allBlocks.add(part); - ip = ins.getBranches(code).get(0); - part.nextParts.add(g = makeGraph(part, path, code, ip, lastIp, allBlocks, refs, visited2)); - g.refs.add(part); - break; - } else if (ins.isBranch()) { - part.end = ip; - - allBlocks.add(part); - List branches = ins.getBranches(code); - for (int i = 0; i < branches.size(); i++) { - part.nextParts.add(g = makeGraph(part, path.sub(i, ip), code, branches.get(i), ip, allBlocks, refs, visited2)); - g.refs.add(part); - } - break; - } - ip++; - } - if ((part.end == -1) && (ip >= code.size())) { - if (part.start == code.size()) { - part.end = code.size(); - allBlocks.add(part); - } else { - part.end = ip - 1; - for (GraphPart p : allBlocks) { - if (p.start == ip) { - p.refs.add(part); - part.nextParts.add(p); - allBlocks.add(part); - return ret; - } - } - GraphPart gp = new GraphPart(ip, ip); - allBlocks.add(gp); - gp.refs.add(part); - part.nextParts.add(gp); - allBlocks.add(part); - } - } - return ret; - } - - /** - * String used to indent line when converting to string - */ - public static final String INDENTOPEN = "INDENTOPEN"; - - /** - * String used to unindent line when converting to string - */ - public static final String INDENTCLOSE = "INDENTCLOSE"; - - /** - * Converts list of TreeItems to string - * - * @param tree List of TreeItem - * @param writer - * @param localData - * @return String - * @throws java.lang.InterruptedException - */ - public static GraphTextWriter graphToString(List tree, GraphTextWriter writer, LocalData localData) throws InterruptedException { - for (GraphTargetItem ti : tree) { - if (!ti.isEmpty()) { - ti.toStringSemicoloned(writer, localData).newLine(); - } - } - return writer; - } - - public BaseLocalData prepareBranchLocalData(BaseLocalData localData) { - return localData; - } - - protected List checkPrecoNextParts(GraphPart part) { - return null; - } - - protected GraphPart makeMultiPart(GraphPart part) { - List parts = new ArrayList<>(); - do { - parts.add(part); - if (part.nextParts.size() == 1 && part.nextParts.get(0).refs.size() == 1) { - part = part.nextParts.get(0); - } else { - part = null; - } - } while (part != null); - if (parts.size() > 1) { - GraphPartMulti ret = new GraphPartMulti(parts); - ret.refs.addAll(parts.get(0).refs); - ret.nextParts.addAll(parts.get(parts.size() - 1).nextParts); - return ret; - } else { - return parts.get(0); - } - } - - protected List getPartItems(GraphPart part) { - List ret = new ArrayList<>(); - do { - for (int i = 0; i < part.getHeight(); i++) { - if (part.getPosAt(i) < code.size()) { - if (part.getPosAt(i) < 0) { - continue; - } - GraphSourceItem s = code.get(part.getPosAt(i)); - if (!s.isJump()) { - ret.add(s); - } - } - } - if (part.nextParts.size() == 1 && part.nextParts.get(0).refs.size() == 1) { - part = part.nextParts.get(0); - } else { - part = null; - } - } while (part != null); - return ret; - } - - protected static void makeAllStack(List commands, TranslateStack stack) { - int pcnt = 0; - for (int i = commands.size() - 1; i >= 0; i--) { - if (commands.get(i) instanceof PushItem) { - pcnt++; - } else { - break; - } - } - for (int i = commands.size() - pcnt; i < commands.size(); i++) { - stack.push(commands.remove(i).value); - i--; - } - } - - protected static void makeAllCommands(List commands, TranslateStack stack) { - int clen = commands.size(); - if (!commands.isEmpty()) { - if (commands.get(commands.size() - 1) instanceof BreakItem) { - clen--; - } - } - while (stack.size() > 0) { - GraphTargetItem p = stack.pop(); - if (!(p instanceof PopItem)) { - if (p instanceof FunctionActionItem) { - commands.add(clen, p); - } else { - commands.add(clen, new PushItem(p)); - } - } - } - } -} +/* + * 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.graph; + +import com.jpexs.decompiler.flash.BaseLocalData; +import com.jpexs.decompiler.flash.FinalProcessLocalData; +import com.jpexs.decompiler.flash.action.Action; +import com.jpexs.decompiler.flash.action.model.FunctionActionItem; +import com.jpexs.decompiler.flash.helpers.GraphTextWriter; +import com.jpexs.decompiler.graph.model.AndItem; +import com.jpexs.decompiler.graph.model.BreakItem; +import com.jpexs.decompiler.graph.model.ContinueItem; +import com.jpexs.decompiler.graph.model.DefaultItem; +import com.jpexs.decompiler.graph.model.DoWhileItem; +import com.jpexs.decompiler.graph.model.DuplicateItem; +import com.jpexs.decompiler.graph.model.ExitItem; +import com.jpexs.decompiler.graph.model.FalseItem; +import com.jpexs.decompiler.graph.model.ForItem; +import com.jpexs.decompiler.graph.model.GotoItem; +import com.jpexs.decompiler.graph.model.IfItem; +import com.jpexs.decompiler.graph.model.IntegerValueItem; +import com.jpexs.decompiler.graph.model.IntegerValueTypeItem; +import com.jpexs.decompiler.graph.model.LabelItem; +import com.jpexs.decompiler.graph.model.LocalData; +import com.jpexs.decompiler.graph.model.LogicalOpItem; +import com.jpexs.decompiler.graph.model.LoopItem; +import com.jpexs.decompiler.graph.model.NotItem; +import com.jpexs.decompiler.graph.model.OrItem; +import com.jpexs.decompiler.graph.model.PopItem; +import com.jpexs.decompiler.graph.model.PushItem; +import com.jpexs.decompiler.graph.model.ScriptEndItem; +import com.jpexs.decompiler.graph.model.SwitchItem; +import com.jpexs.decompiler.graph.model.TernarOpItem; +import com.jpexs.decompiler.graph.model.TrueItem; +import com.jpexs.decompiler.graph.model.UniversalLoopItem; +import com.jpexs.decompiler.graph.model.WhileItem; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; + +/** + * + * @author JPEXS + */ +public class Graph { + + public List heads; + + protected GraphSource code; + + private final List alternateEntries; + + public static final int SOP_USE_STATIC = 0; + + public static final int SOP_SKIP_STATIC = 1; + + public static final int SOP_REMOVE_STATIC = 2; + + /** + * Identify loop exits + * + * @param localData + * @param allParts All nodes + * @return + */ + public Map> identifyLoopBreaks(BaseLocalData localData, Set allParts) { + Map> lb = new HashMap<>(); + + for (GraphPart b0 : allParts) { + List np = new ArrayList<>(b0.nextParts); + np.addAll(b0.throwParts); + for (GraphPart b : np) { + GraphPart hdr = (b0.type == GraphPart.TYPE_LOOP_HEADER || b0.type == GraphPart.TYPE_REENTRY) ? b0 : b0.iloop_header; + + if (hdr != null && b.iloop_header != hdr && b.iloop_header == hdr.iloop_header && b != hdr) { + if (!lb.containsKey(hdr)) { + lb.put(hdr, new ArrayList<>()); + } + if (!lb.get(hdr).contains(b)) { + lb.get(hdr).add(b); + } + } + } + } + + return lb; + } + + /** + * Identifying loops. Based on http://lenx.100871.net/papers/loop-SAS.pdf + * + * @param localData + * @param loopContinues Result - list of loop headers + * @param heads Entries + * @param appParts All Nodes + */ + public void identifyLoops(BaseLocalData localData, List loopContinues, List heads, Set appParts) { + for (GraphPart b : appParts) { + b.traversed = false; + b.DFSP_pos = 0; + b.irreducible = false; + b.type = GraphPart.TYPE_NONE; + //initialize b + } + for (GraphPart h0 : heads) { + trav_loops_DFS(localData, loopContinues, h0, 1); + } + } + + /** + * Tag h as loop headr of b. + * + * @param b Block + * @param h Loop header + */ + protected void tag_lhead(GraphPart b, GraphPart h) { + if (b == h || h == null) { + return; + } + GraphPart cur1 = b; + GraphPart cur2 = h; + while (cur1.iloop_header != null) { + GraphPart ih = cur1.iloop_header; + if (ih == cur2) { + return; + } + if (ih.DFSP_pos < cur2.DFSP_pos) { + cur1.iloop_header = cur2; + cur1 = cur2; + cur2 = ih; + } else { + cur1 = h; + } + } + if (cur1 == cur2) { + return; + } + + cur1.iloop_header = cur2; + } + + protected List filter(List list) { + return new ArrayList<>(list); + } + + /** + * Traverse loops deep first search + * + * @param localData + * @param loopHeaders Resulting loop headers + * @param b0 Current node + * @param DFSP_pos Position in DFSP + * @return innermost loop header of b0 + */ + protected GraphPart trav_loops_DFS(BaseLocalData localData, List loopHeaders, GraphPart b0, int DFSP_pos) { + + List folParts = new ArrayList<>(b0.nextParts); + folParts.addAll(b0.throwParts); + + b0.traversed = true; + b0.DFSP_pos = DFSP_pos; //Mark b0’s position in DFSP + for (GraphPart b : folParts) { + b = checkPart(null, localData, b, null); + if (b == null) { + continue; + } + if (!b.traversed) { + //case (A), new + GraphPart nh = trav_loops_DFS(localData, loopHeaders, b, DFSP_pos + 1); + tag_lhead(b0, nh); + } else if (b.DFSP_pos > 0) { // b in DFSP(b0) + //case (B) + if (b.type != GraphPart.TYPE_LOOP_HEADER) { + b.type = GraphPart.TYPE_LOOP_HEADER; + loopHeaders.add(b); + } + tag_lhead(b0, b); + } else if (b.iloop_header == null) { + //case (C), do nothing + } else { + GraphPart h = b.iloop_header; + if (h.DFSP_pos > 0) { // h in DFSP(b0) + //case (D) + tag_lhead(b0, h); + } else { // h not in DFSP(b0) + //case (E), reentry + b.type = GraphPart.TYPE_REENTRY; //TODO:and b0,b ? + h.irreducible = true; + while (h.iloop_header != null) { + h = h.iloop_header; + if (h.DFSP_pos > 0) { //h in DFSP(b0) + tag_lhead(b0, h); + break; + } + h.irreducible = true; + } + } + } + } + b0.DFSP_pos = 0; // clear b0’s DFSP position + return b0.iloop_header; + } + + public Graph(GraphSource code, List alternateEntries) { + this.code = code; + this.alternateEntries = alternateEntries; + + } + + public void init(BaseLocalData localData) throws InterruptedException { + if (heads != null) { + return; + } + heads = makeGraph(code, new ArrayList<>(), alternateEntries); + int time = 1; + List ordered = new ArrayList<>(); + List visited = new ArrayList<>(); + for (GraphPart head : heads) { + time = head.setTime(time, ordered, visited); + } + } + + protected static void populateParts(GraphPart part, Set allParts) { + if (allParts.contains(part)) { + return; + } + allParts.add(part); + for (GraphPart p : part.nextParts) { + populateParts(p, allParts); + } + } + + public GraphPart deepCopy(GraphPart part) { + return deepCopy(part, new HashMap<>()); + } + + private GraphPart deepCopy(GraphPart part, Map copies) { + GraphPart copy = copies.get(part); + if (copy != null) { + return copy; + } + + copy = new GraphPart(part.start, part.end); + copy.path = part.path; + copies.put(part, copy); + copy.nextParts = new ArrayList<>(); + for (int i = 0; i < part.nextParts.size(); i++) { + copy.nextParts.add(deepCopy(part.nextParts.get(i), copies)); + } + + for (int i = 0; i < part.refs.size(); i++) { + copy.refs.add(deepCopy(part.refs.get(i), copies)); + } + + return copy; + } + + public void resetGraph(GraphPart part, Set visited) { + if (visited.contains(part)) { + return; + } + + visited.add(part); + int pos = 0; + for (GraphPart p : part.nextParts) { + if (!visited.contains(p)) { + p.path = part.path.sub(pos, p.end); + } + + resetGraph(p, visited); + pos++; + } + } + + private void getReachableParts(GraphPart part, LinkedHashSet ret, List loops) { + // use LinkedHashSet to preserve order + getReachableParts(part, ret, loops, true); + } + + private void getReachableParts(GraphPart part, LinkedHashSet ret, List loops, boolean first) { + + // todo: honfika: why call with first = true parameter always? + Stack stack = new Stack<>(); + GraphPartQueue queue = new GraphPartQueue(); + queue.add(part); + stack.add(queue); + stacknext: + while (!stack.isEmpty()) { + + queue = stack.peek(); + if (!queue.isEmpty()) { + part = queue.remove(); + } else if (queue.currentLoop != null) { + Loop cLoop = queue.currentLoop; + part = cLoop.loopBreak; + queue.currentLoop = null; + if (ret.contains(part)) { + continue; + } + + ret.add(part); + cLoop.reachableMark = 2; + } else { + stack.pop(); + continue; + } + + for (Loop l : loops) { + l.reachableMark = 0; + } + + Loop currentLoop = null; + for (Loop l : loops) { + if ((l.phase == 1) || (l.reachableMark == 1)) { + if (l.loopContinue == part) { + continue stacknext; + } + if (l.loopBreak == part) { + continue stacknext; + } + if (l.loopPreContinue == part) { + continue stacknext; + } + } + if (l.reachableMark == 0) { + if (l.loopContinue == part) { + l.reachableMark = 1; + currentLoop = l; + } + } + } + + GraphPartQueue newParts = new GraphPartQueue(); + loopnext: + for (GraphPart next : part.nextParts) { + for (Loop l : loops) { + if ((l.phase == 1) || (l.reachableMark == 1)) { + if (l.loopContinue == next) { + continue loopnext; + } + if (l.loopBreak == next) { + continue loopnext; + } + if (l.loopPreContinue == next) { + continue loopnext; + } + } + + } + if (!ret.contains(next)) { + newParts.add(next); + } + } + + ret.addAll(newParts); + if (currentLoop != null && currentLoop.loopBreak != null) { + newParts.currentLoop = currentLoop; + } + + if (!newParts.isEmpty() || newParts.currentLoop != null) { + stack.add(newParts); + } + } + } + + public GraphPart getNextCommonPart(BaseLocalData localData, GraphPart part, List loops) throws InterruptedException { + return getCommonPart(localData, part.nextParts, loops); + } + + //TODO: Make this faster! + public GraphPart getCommonPart(BaseLocalData localData, List parts, List loops) throws InterruptedException { + if (parts.isEmpty()) { + return null; + } + + List loopContinues = new ArrayList<>();//getLoopsContinues(loops); + for (Loop l : loops) { + if (l.phase == 1) { + loopContinues.add(l.loopContinue); + } + } + + for (GraphPart p : parts) { + if (loopContinues.contains(p)) { + break; + } + boolean common = true; + for (GraphPart q : parts) { + if (q == p) { + continue; + } + if (!q.leadsTo(localData, this, code, p, loops)) { + common = false; + break; + } + } + if (common) { + return p; + } + } + List> reachable = new ArrayList<>(); + for (GraphPart p : parts) { + LinkedHashSet r1 = new LinkedHashSet<>(); + getReachableParts(p, r1, loops); + r1.add(p); + reachable.add(r1); + } + Set first = reachable.get(0); + for (GraphPart p : first) { + /*if (ignored.contains(p)) { + continue; + }*/ + p = checkPart(null, localData, p, null); + if (p == null) { + continue; + } + boolean common = true; + for (Set r : reachable) { + if (!r.contains(p)) { + common = false; + break; + } + } + if (common) { + return p; + } + } + return null; + } + + public GraphPart getMostCommonPart(BaseLocalData localData, List parts, List loops) throws InterruptedException { + if (parts.isEmpty()) { + return null; + } + + Set s = new HashSet<>(parts); //unique + parts = new ArrayList<>(s); //make local copy + + List loopContinues = new ArrayList<>();//getLoopsContinues(loops); + for (Loop l : loops) { + if (l.phase == 1) { + loopContinues.add(l.loopContinue); + loopContinues.add(l.loopPreContinue); + } + } + + for (GraphPart p : parts) { + if (loopContinues.contains(p)) { + break; + } + boolean common = true; + for (GraphPart q : parts) { + if (q == p) { + continue; + } + if (!q.leadsTo(localData, this, code, p, loops)) { + common = false; + break; + } + } + if (common) { + return p; + } + } + + loopi: + for (int i = 0; i < parts.size(); i++) { + for (int j = 0; j < parts.size(); j++) { + if (j == i) { + continue; + } + if (parts.get(i).leadsTo(localData, this, code, parts.get(j), loops)) { + parts.remove(i); + i--; + continue loopi; + } + } + } + List> reachable = new ArrayList<>(); + for (GraphPart p : parts) { + LinkedHashSet r1 = new LinkedHashSet<>(); + getReachableParts(p, r1, loops); + Set r2 = new LinkedHashSet<>(); + r2.add(p); + r2.addAll(r1); + reachable.add(r2); + } + ///List first = reachable.get(0); + int commonLevel; + Map levelMap = new HashMap<>(); + for (Set first : reachable) { + int maxclevel = 0; + Set visited = new HashSet<>(); + for (GraphPart p : first) { + if (loopContinues.contains(p)) { + break; + } + if (visited.contains(p)) { + continue; + } + visited.add(p); + boolean common = true; + commonLevel = 1; + for (Set r : reachable) { + if (r == first) { + continue; + } + if (r.contains(p)) { + commonLevel++; + } + } + if (commonLevel <= maxclevel) { + continue; + } + maxclevel = commonLevel; + if (levelMap.containsKey(p)) { + if (levelMap.get(p) > commonLevel) { + commonLevel = levelMap.get(p); + } + } + levelMap.put(p, commonLevel); + if (common) { + //return p; + } + } + } + for (int i = reachable.size() - 1; i >= 2; i--) { + for (GraphPart p : levelMap.keySet()) { + if (levelMap.get(p) == i) { + return p; + } + } + } + for (GraphPart p : levelMap.keySet()) { + if (levelMap.get(p) == parts.size()) { + return p; + } + } + return null; + } + + public GraphPart getNextNoJump(GraphPart part, BaseLocalData localData) { + while (code.get(part.start).isJump()) { + part = part.getSubParts().get(0).nextParts.get(0); + } + /*localData = prepareBranchLocalData(localData); + TranslateStack st = new TranslateStack(); + List output=new ArrayList<>(); + GraphPart startPart = part; + for (int i = part.start; i <= part.end; i++) { + GraphSourceItem src = code.get(i); + if (src.isJump()) { + part = part.nextParts.get(0); + if(st.isEmpty()){ + startPart = part; + } + i = part.start - 1; + continue; + } + try{ + src.translate(localData, st, output, SOP_USE_STATIC, ""); + }catch(Exception ex){ + return startPart; + } + if(!output.isEmpty()){ + return startPart; + } + }*/ + return part; + } + + public static List translateViaGraph(BaseLocalData localData, String path, GraphSource code, List alternateEntries, int staticOperation) throws InterruptedException { + Graph g = new Graph(code, alternateEntries); + g.init(localData); + return g.translate(localData, staticOperation, path); + } + + public List translate(BaseLocalData localData, int staticOperation, String path) throws InterruptedException { + Set allParts = new HashSet<>(); + for (GraphPart head : heads) { + populateParts(head, allParts); + } + TranslateStack stack = new TranslateStack(path); + List loops = new ArrayList<>(); + + //TODO: Make this working. :-( + final boolean newLoopDetection = false; + + if (!newLoopDetection) { + getLoops(localData, heads.get(0), loops, null); + } else { + List loopHeads = new ArrayList<>(); + identifyLoops(localData, loopHeads, heads, allParts); + Map> loopBreaks = identifyLoopBreaks(localData, allParts); + + List loops2 = new ArrayList<>(); + for (int i = 0; i < loopHeads.size(); i++) { + loops2.add(new Loop(loops2.size(), loopHeads.get(i), null)); + } + for (int i = 0; i < loopHeads.size(); i++) { + if (loopBreaks.containsKey(loopHeads.get(i))) { + loops2.get(i).loopBreak = loopBreaks.get(loopHeads.get(i)).get(0);//getMostCommonPart(localData, loopBreaks.get(loopHeads.get(i)), loops2); + } else { + loops2.get(i).loopBreak = null; + } + } + + loops = loops2; + } + + /* + System.err.println(""); + for (Loop el : loops) { + System.err.println(el); + } + System.err.println(""); + */ + //TODO: Make getPrecontinues faster + getPrecontinues(path, localData, null, heads.get(0), allParts, loops, null); + + /*System.err.println(""); + for (Loop el : loops) { + System.err.println(el); + } + System.err.println("");//*/ + List ret = printGraph(new HashMap<>(), new HashMap<>(), localData, stack, allParts, null, heads.get(0), null, loops, staticOperation, path); + processIfs(ret); + finalProcessStack(stack, ret); + finalProcessAll(ret, 0, new FinalProcessLocalData()); + return ret; + } + + public void finalProcessStack(TranslateStack stack, List output) { + } + + private void finalProcessAll(List list, int level, FinalProcessLocalData localData) throws InterruptedException { + finalProcess(list, level, localData); + for (GraphTargetItem item : list) { + if (item instanceof Block) { + List> subs = ((Block) item).getSubs(); + for (List sub : subs) { + finalProcessAll(sub, level + 1, localData); + } + } + } + finalProcessAfter(list, level, localData); + } + + private boolean processSubBlk(Block b, GraphTargetItem replacement) { + boolean allSubPush = true; + boolean atleastOne = false; + for (List sub : b.getSubs()) { + if (!sub.isEmpty()) { + int lastPos = sub.size() - 1; + + GraphTargetItem last = sub.get(sub.size() - 1); + GraphTargetItem br = null; + + if ((last instanceof BreakItem) && (sub.size() >= 2)) { + br = last; + lastPos--; + last = sub.get(lastPos); + } + if (last instanceof Block) { + if (!processSubBlk((Block) last, replacement)) { + allSubPush = false; + } else { + atleastOne = true; + } + } else if (last instanceof PushItem) { + if (replacement != null) { + GraphTargetItem e2 = (((GraphTargetItem) replacement).clone()); + e2.value = last.value; + sub.set(lastPos, e2); + if (br != null) { + sub.remove(sub.size() - 1); + } + } + atleastOne = true; + } else if (!(last instanceof ExitItem)) { + allSubPush = false; + } + } + } + return allSubPush && atleastOne; + } + + protected void finalProcessAfter(List list, int level, FinalProcessLocalData localData) { + if (list.size() >= 2) { + if (list.get(list.size() - 1) instanceof ExitItem) { + ExitItem e = (ExitItem) list.get(list.size() - 1); + if (list.get(list.size() - 1).value instanceof PopItem) { + if (list.get(list.size() - 2) instanceof Block) { + Block b = (Block) list.get(list.size() - 2); + if (processSubBlk(b, (GraphTargetItem) e)) { + list.remove(list.size() - 1); + } + } + } + } + } + } + + protected void finalProcess(List list, int level, FinalProcessLocalData localData) throws InterruptedException { + + //For detection based on debug line information + boolean[] toDelete = new boolean[list.size()]; + for (int i = 0; i < list.size(); i++) { + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); + } + + GraphTargetItem itemI = list.get(i); + if (itemI instanceof ForItem) { + ForItem fori = (ForItem) itemI; + int exprLine = fori.getLine(); + if (exprLine > 0) { + List forFirstCommands = new ArrayList<>(); + for (int j = i - 1; j >= 0; j--) { + if (list.get(j).getLine() == exprLine && !(list.get(j) instanceof LoopItem /*to avoid recursion and StackOverflow*/)) { + forFirstCommands.add(0, list.get(j)); + toDelete[j] = true; + } else { + break; + } + } + fori.firstCommands.addAll(0, forFirstCommands); + } + } + + if (itemI instanceof WhileItem) { + WhileItem whi = (WhileItem) itemI; + int whileExprLine = whi.getLine(); + if (whileExprLine > 0) { + List forFirstCommands = new ArrayList<>(); + List forFinalCommands = new ArrayList<>(); + + for (int j = i - 1; j >= 0; j--) { + GraphTargetItem itemJ = list.get(j); + if (itemJ.getLine() == whileExprLine && !(itemJ instanceof LoopItem /*to avoid recursion and StackOverflow*/)) { + forFirstCommands.add(0, itemJ); + toDelete[j] = true; + } else { + break; + } + } + for (int j = whi.commands.size() - 1; j >= 0; j--) { + if (whi.commands.get(j).getLine() == whileExprLine && !(whi.commands.get(j) instanceof LoopItem /*to avoid recursion and StackOverflow*/)) { + forFinalCommands.add(0, whi.commands.remove(j)); + } else { + break; + } + } + if (!forFirstCommands.isEmpty() || !forFinalCommands.isEmpty()) { + //Do not allow more than 2 first/final commands, since it can be obfuscated + if (forFirstCommands.size() > 2 || forFinalCommands.size() > 2) { + //put it back + for (int k = 0; k < forFirstCommands.size(); k++) { + toDelete[i - 1 - k] = false; + } + whi.commands.addAll(forFinalCommands); //put it back + } else if (whi.commands.isEmpty() && forFirstCommands.isEmpty()) { + //it would be for(;expr;commands) {} which looks better as while(expr){commands} + whi.commands.addAll(forFinalCommands); //put it back + } else { + GraphTargetItem lastExpr = whi.expression.remove(whi.expression.size() - 1); + forFirstCommands.addAll(whi.expression); + list.set(i, new ForItem(whi.getSrc(), whi.getLineStartItem(), whi.loop, forFirstCommands, lastExpr, forFinalCommands, whi.commands)); + } + } + } + } + } + + for (int i = toDelete.length - 1; i >= 0; i--) { + if (toDelete[i]) { + list.remove(i); + } + } + } + + private void processIfs(List list) { + + for (int i = 0; i < list.size(); i++) { + GraphTargetItem item = list.get(i); + if ((item instanceof LoopItem) && (item instanceof Block)) { + List> subs = ((Block) item).getSubs(); + for (List sub : subs) { + processIfs(sub); + checkContinueAtTheEnd(sub, ((LoopItem) item).loop); + } + } else if (item instanceof Block) { + List> subs = ((Block) item).getSubs(); + for (List sub : subs) { + processIfs(sub); + } + } + if (item instanceof IfItem) { + IfItem ifi = (IfItem) item; + List onTrue = ifi.onTrue; + List onFalse = ifi.onFalse; + if ((!onTrue.isEmpty()) && (!onFalse.isEmpty())) { + if (onTrue.get(onTrue.size() - 1) instanceof ContinueItem) { + if (onFalse.get(onFalse.size() - 1) instanceof ContinueItem) { + if (((ContinueItem) onTrue.get(onTrue.size() - 1)).loopId == ((ContinueItem) onFalse.get(onFalse.size() - 1)).loopId) { + onTrue.remove(onTrue.size() - 1); + list.add(i + 1, onFalse.remove(onFalse.size() - 1)); + } + } + } + } + + if ((!onTrue.isEmpty()) && (!onFalse.isEmpty())) { + GraphTargetItem last = onTrue.get(onTrue.size() - 1); + if ((last instanceof ExitItem) || (last instanceof ContinueItem) || (last instanceof BreakItem)) { + list.addAll(i + 1, onFalse); + onFalse.clear(); + } + } + + if ((!onTrue.isEmpty()) && (!onFalse.isEmpty())) { + if (onFalse.get(onFalse.size() - 1) instanceof ExitItem) { + if (onTrue.get(onTrue.size() - 1) instanceof ContinueItem) { + list.add(i + 1, onTrue.remove(onTrue.size() - 1)); + } + } + } + } + } + + //Same continues in onTrue and onFalse gets continue on parent level + } + + protected List getLoopsContinuesPreAndBreaks(List loops) { + List ret = new ArrayList<>(); + for (Loop l : loops) { + if (l.loopContinue != null) { + ret.add(l.loopContinue); + } + if (l.loopPreContinue != null) { + ret.add(l.loopPreContinue); + } + if (l.loopBreak != null) { + ret.add(l.loopBreak); + } + } + return ret; + } + + protected List getLoopsContinuesAndPre(List loops) { + List ret = new ArrayList<>(); + for (Loop l : loops) { + if (l.loopContinue != null) { + ret.add(l.loopContinue); + } + if (l.loopPreContinue != null) { + ret.add(l.loopPreContinue); + } + } + return ret; + } + + protected List getLoopsContinues(List loops) { + List ret = new ArrayList<>(); + for (Loop l : loops) { + if (l.loopContinue != null) { + ret.add(l.loopContinue); + } + /*if (l.loopPreContinue != null) { + ret.add(l.loopPreContinue); + }*/ + } + return ret; + } + + protected GraphTargetItem checkLoop(GraphPart part, List stopPart, List loops) { + if (stopPart.contains(part)) { + return null; + } + + GraphSourceItem firstIns = null; + if (part != null) { + if (part.start >= 0 && part.start < code.size()) { + firstIns = code.get(part.start); + } + } + + for (Loop l : loops) { + if (l.loopContinue == part) { + return (new ContinueItem(null, firstIns, l.id)); + } + if (l.loopBreak == part) { + return (new BreakItem(null, firstIns, l.id)); + } + } + return null; + } + + private void checkContinueAtTheEnd(List commands, Loop loop) { + if (!commands.isEmpty()) { + int i = commands.size() - 1; + for (; i >= 0; i--) { + if (commands.get(i) instanceof ContinueItem) { + continue; + } + if (commands.get(i) instanceof BreakItem) { + continue; + } + break; + } + if (i < commands.size() - 1) { + for (int k = i + 2; k < commands.size(); k++) { + commands.remove(k); + } + } + if (commands.get(commands.size() - 1) instanceof ContinueItem) { + if (((ContinueItem) commands.get(commands.size() - 1)).loopId == loop.id) { + commands.remove(commands.size() - 1); + } + } + } + } + + protected boolean isEmpty(List output) { + if (output.isEmpty()) { + return true; + } + if (output.size() == 1) { + if (output.get(0) instanceof MarkItem) { + return true; + } + } + return false; + } + + protected List check(Map> partCodes, Map partCodePos, GraphSource code, BaseLocalData localData, Set allParts, TranslateStack stack, GraphPart parent, GraphPart part, List stopPart, List loops, List output, Loop currentLoop, int staticOperation, String path) throws InterruptedException { + return null; + } + + protected GraphPart checkPart(TranslateStack stack, BaseLocalData localData, GraphPart part, Set allParts) { + return part; + } + + //@SuppressWarnings("unchecked") + protected GraphTargetItem translatePartGetStack(BaseLocalData localData, GraphPart part, TranslateStack stack, int staticOperation) throws InterruptedException { + stack = (TranslateStack) stack.clone(); + translatePart(localData, part, stack, staticOperation, null); + return stack.pop(); + } + + protected List translatePart(BaseLocalData localData, GraphPart part, TranslateStack stack, int staticOperation, String path) throws InterruptedException { + List sub = part.getSubParts(); + List ret = new ArrayList<>(); + int end; + for (GraphPart p : sub) { + if (p.end == -1) { + p.end = code.size() - 1; + } + if (p.start == code.size()) { + continue; + } else if (p.end == code.size()) { + p.end--; + } + end = p.end; + int start = p.start; + ret.addAll(code.translatePart(part, localData, stack, start, end, staticOperation, path)); + } + return ret; + } + + private void markBranchEnd(List items) { + if (!items.isEmpty()) { + if (items.get(items.size() - 1) instanceof BreakItem) { + return; + } + if (items.get(items.size() - 1) instanceof ContinueItem) { + return; + } + if (items.get(items.size() - 1) instanceof ExitItem) { + return; + } + } + items.add(new MarkItem("finish")); + } + + private static GraphTargetItem getLastNoEnd(List list) { + if (list.isEmpty()) { + return null; + } + if (list.get(list.size() - 1) instanceof ScriptEndItem) { + if (list.size() >= 2) { + return list.get(list.size() - 2); + } + return list.get(list.size() - 1); + } + return list.get(list.size() - 1); + } + + private static void removeLastNoEnd(List list) { + if (list.isEmpty()) { + return; + } + if (list.get(list.size() - 1) instanceof ScriptEndItem) { + if (list.size() >= 2) { + list.remove(list.size() - 2); + } + return; + } + list.remove(list.size() - 1); + } + + protected List printGraph(Map> partCodes, Map partCodePos, BaseLocalData localData, TranslateStack stack, Set allParts, GraphPart parent, GraphPart part, List stopPart, List loops, int staticOperation, String path) throws InterruptedException { + return printGraph(partCodes, partCodePos, new HashSet<>(), localData, stack, allParts, parent, part, stopPart, loops, null, staticOperation, path, 0); + } + + protected GraphTargetItem checkLoop(LoopItem loopItem, BaseLocalData localData, List loops) { + return loopItem; + } + + //TODO: Make this faster!!! + private void getPrecontinues(String path, BaseLocalData localData, GraphPart parent, GraphPart part, Set allParts, List loops, List stopPart) throws InterruptedException { + try { + markLevels(path, localData, part, allParts, loops); + } catch (ThreadDeath | InterruptedException iex) { + throw iex; + } catch (Throwable ex) { + //It is unusual code so markLevels failed, nevermind, it can still work + } + //Note: this also marks part as precontinue when there is if + /* + while(k<10){ + if(k==7){ + trace(a); + }else{ + trace(b); + } + //precontinue + k++; + } + + */ + looploops: + for (Loop l : loops) { + if (l.loopContinue != null) { + Set uniqueRefs = new HashSet<>(); + uniqueRefs.addAll(l.loopContinue.refs); + if (uniqueRefs.size() == 2) { //only one path - from precontinue + List uniqueRefsList = new ArrayList<>(uniqueRefs); + if (uniqueRefsList.get(0).discoveredTime > uniqueRefsList.get(1).discoveredTime) { //latch node is discovered later + part = uniqueRefsList.get(0); + } else { + part = uniqueRefsList.get(1); + } + if (part == l.loopContinue) { + continue looploops; + } + + while (part.refs.size() == 1) { + if (part.refs.get(0).nextParts.size() != 1) { + continue looploops; + } + + part = part.refs.get(0); + if (part == l.loopContinue) { + break; + } + } + if (part.level == 0 && part != l.loopContinue) { + l.loopPreContinue = part; + } + } + } + } + /*clearLoops(loops); + getPrecontinues(parent, part, loops, stopPart, 0, new ArrayList()); + clearLoops(loops);*/ + } + + private void markLevels(String path, BaseLocalData localData, GraphPart part, Set allParts, List loops) throws InterruptedException { + clearLoops(loops); + markLevels(path, localData, part, allParts, loops, new ArrayList<>(), 1, new HashSet<>(), 0); + clearLoops(loops); + } + + private void markLevels(String path, BaseLocalData localData, GraphPart part, Set allParts, List loops, List stopPart, int level, Set visited, int recursionLevel) throws InterruptedException { + boolean debugMode = false; + if (stopPart == null) { + stopPart = new ArrayList<>(); + } + if (recursionLevel > allParts.size() + 1) { + throw new RuntimeException(path + ": markLevels max recursion level reached"); + } + + if (debugMode) { + System.err.println("markLevels " + part); + } + if (stopPart.contains(part)) { + return; + } + for (Loop el : loops) { + if ((el.phase == 2) && (el.loopContinue == part)) { + return; + } + if (el.phase != 1) { + if (debugMode) { + //System.err.println("ignoring "+el); + } + continue; + } + if (el.loopContinue == part) { + return; + } + if (el.loopPreContinue == part) { + return; + } + if (el.loopBreak == part) { + return; + } + } + + if (visited.contains(part)) { + part.level = 0; + } else { + visited.add(part); + part.level = level; + } + + boolean isLoop = false; + Loop currentLoop = null; + for (Loop el : loops) { + if ((el.phase == 0) && (el.loopContinue == part)) { + isLoop = true; + currentLoop = el; + el.phase = 1; + break; + } + } + + List nextParts = checkPrecoNextParts(part); + if (nextParts == null) { + nextParts = part.nextParts; + } + + if (nextParts.size() == 2) { + GraphPart next = getCommonPart(localData, nextParts, loops);//part.getNextPartPath(new ArrayList()); + List stopParts2 = new ArrayList<>(); //stopPart); + if (next != null) { + stopParts2.add(next); + } else if (!stopPart.isEmpty()) { + stopParts2.add(stopPart.get(stopPart.size() - 1)); + } + if (next != nextParts.get(0)) { + markLevels(path, localData, nextParts.get(0), allParts, loops, next == null ? stopPart : stopParts2, level + 1, visited, recursionLevel + 1); + } + if (next != nextParts.get(1)) { + markLevels(path, localData, nextParts.get(1), allParts, loops, next == null ? stopPart : stopParts2, level + 1, visited, recursionLevel + 1); + } + if (next != null) { + markLevels(path, localData, next, allParts, loops, stopPart, level, visited, recursionLevel + 1); + } + } + + if (nextParts.size() > 2) { + GraphPart next = getMostCommonPart(localData, nextParts, loops); + List vis = new ArrayList<>(); + for (GraphPart p : nextParts) { + if (vis.contains(p)) { + continue; + } + List stopPart2 = new ArrayList<>(); //(stopPart); + if (next != null) { + stopPart2.add(next); + } else if (!stopPart.isEmpty()) { + stopPart2.add(stopPart.get(stopPart.size() - 1)); + } + for (GraphPart p2 : nextParts) { + if (p2 == p) { + continue; + } + if (!stopPart2.contains(p2)) { + stopPart2.add(p2); + } + } + if (next != p) { + markLevels(path, localData, p, allParts, loops, stopPart2, level + 1, visited, recursionLevel + 1); + vis.add(p); + } + } + if (next != null) { + markLevels(path, localData, next, allParts, loops, stopPart, level, visited, recursionLevel + 1); + } + } + + if (nextParts.size() == 1) { + markLevels(path, localData, nextParts.get(0), allParts, loops, stopPart, level, visited, recursionLevel + 1); + } + + for (GraphPart t : part.throwParts) { + if (!visited.contains(t)) { + List stopPart2 = new ArrayList<>(); + List cmn = new ArrayList<>(); + cmn.add(part); + cmn.add(t); + GraphPart next = getCommonPart(localData, cmn, loops); + if (next != null) { + stopPart2.add(next); + } else { + stopPart2 = stopPart; + } + + markLevels(path, localData, t, allParts, loops, stopPart2, level, visited, recursionLevel + 1); + } + } + + if (isLoop) { + if (currentLoop != null && currentLoop.loopBreak != null) { + currentLoop.phase = 2; + markLevels(path, localData, currentLoop.loopBreak, allParts, loops, stopPart, level, visited, recursionLevel + 1); + } + } + } + + private void clearLoops(List loops) { + for (Loop l : loops) { + l.phase = 0; + } + } + + private void getLoops(BaseLocalData localData, GraphPart part, List loops, List stopPart) throws InterruptedException { + clearLoops(loops); + getLoops(localData, part, loops, stopPart, true, 1, new ArrayList<>()); + clearLoops(loops); + } + + private void getLoops(BaseLocalData localData, GraphPart part, List loops, List stopPart, boolean first, int level, List visited) throws InterruptedException { + boolean debugMode = false; + + if (part == null) { + return; + } + + part = checkPart(null, localData, part, null); + if (part == null) { + return; + } + if (!visited.contains(part)) { + visited.add(part); + } + + if (debugMode) { + System.err.println("getloops: " + part); + } + //List loopContinues = getLoopsContinues(loops); + Loop lastP1 = null; + for (Loop el : loops) { + if ((el.phase == 1) && el.loopBreak == null) { //break not found yet + if (el.loopContinue != part) { + lastP1 = el; + + } else { + lastP1 = null; + } + + } + } + if (lastP1 != null) { + if (lastP1.breakCandidates.contains(part)) { + lastP1.breakCandidates.add(part); + lastP1.breakCandidatesLevels.add(level); + return; + } else { + //List loopContinues2 = new ArrayList<>(loopContinues); + //loopContinues2.remove(lastP1.loopContinue); + List loops2 = new ArrayList<>(loops); + loops2.remove(lastP1); + if (!part.leadsTo(localData, this, code, lastP1.loopContinue, loops2)) { + if (lastP1.breakCandidatesLocked == 0) { + if (debugMode) { + System.err.println("added breakCandidate " + part + " to " + lastP1); + } + + lastP1.breakCandidates.add(part); + lastP1.breakCandidatesLevels.add(level); + return; + } + } + } + } + + for (Loop el : loops) { + if (el.loopContinue == part) { + return; + } + } + + if (stopPart != null && stopPart.contains(part)) { + return; + } + part.level = level; + + boolean isLoop = part.leadsTo(localData, this, code, part, loops); + Loop currentLoop = null; + if (isLoop) { + currentLoop = new Loop(loops.size(), part, null); + currentLoop.phase = 1; + loops.add(currentLoop); + //loopContinues.add(part); + } + + if (part.nextParts.size() == 2) { + + List nps;/* = new ArrayList<>(part.nextParts); + for(int i=0;i stopPart2 = stopPart == null ? new ArrayList<>() : new ArrayList<>(stopPart); + if (next != null) { + stopPart2.add(next); + } + if (next != nps.get(0)) { + getLoops(localData, nps.get(0), loops, stopPart2, false, level + 1, visited); + } + if (next != nps.get(1)) { + getLoops(localData, nps.get(1), loops, stopPart2, false, level + 1, visited); + } + if (next != null) { + getLoops(localData, next, loops, stopPart, false, level, visited); + } + } else if (part.nextParts.size() > 2) { + GraphPart next = getNextCommonPart(localData, part, loops); + + for (GraphPart p : part.nextParts) { + List stopPart2 = stopPart == null ? new ArrayList<>() : new ArrayList<>(stopPart); + if (next != null) { + stopPart2.add(next); + } + for (GraphPart p2 : part.nextParts) { + if (p2 == p) { + continue; + } + if (!stopPart2.contains(p2)) { + stopPart2.add(p2); + } + } + if (next != p) { + getLoops(localData, p, loops, stopPart2, false, level + 1, visited); + } + } + if (next != null) { + getLoops(localData, next, loops, stopPart, false, level, visited); + } + } else if (part.nextParts.size() == 1) { + getLoops(localData, part.nextParts.get(0), loops, stopPart, false, level, visited); + } + + List loops2 = new ArrayList<>(loops); + for (Loop l : loops2) { + l.breakCandidatesLocked++; + } + for (GraphPart t : part.throwParts) { + if (!visited.contains(t)) { + getLoops(localData, t, loops, stopPart, false, level, visited); + } + } + for (Loop l : loops2) { + l.breakCandidatesLocked--; + } + + if (isLoop && currentLoop != null) { + GraphPart found; + Map removed = new HashMap<>(); + do { + found = null; + for (int i = 0; i < currentLoop.breakCandidates.size(); i++) { + GraphPart ch = checkPart(null, localData, currentLoop.breakCandidates.get(i), null); + if (ch == null) { + currentLoop.breakCandidates.remove(i); + i--; + } + } + loopcand: + for (GraphPart cand : currentLoop.breakCandidates) { + for (GraphPart cand2 : currentLoop.breakCandidates) { + if (cand == cand2) { + continue; + } + if (cand.leadsTo(localData, this, code, cand2, loops)) { + int lev1 = Integer.MAX_VALUE; + int lev2 = Integer.MAX_VALUE; + for (int i = 0; i < currentLoop.breakCandidates.size(); i++) { + if (currentLoop.breakCandidates.get(i) == cand) { + if (currentLoop.breakCandidatesLevels.get(i) < lev1) { + lev1 = currentLoop.breakCandidatesLevels.get(i); + } + } + if (currentLoop.breakCandidates.get(i) == cand2) { + if (currentLoop.breakCandidatesLevels.get(i) < lev2) { + lev2 = currentLoop.breakCandidatesLevels.get(i); + } + } + } + if (lev1 <= lev2) { + found = cand2; + } else { + found = cand; + } + break loopcand; + } + } + } + if (found != null) { + int maxlevel = 0; + while (currentLoop.breakCandidates.contains(found)) { + int ind = currentLoop.breakCandidates.indexOf(found); + currentLoop.breakCandidates.remove(ind); + int lev = currentLoop.breakCandidatesLevels.remove(ind); + if (lev > maxlevel) { + maxlevel = lev; + } + } + if (removed.containsKey(found)) { + if (removed.get(found) > maxlevel) { + maxlevel = removed.get(found); + } + } + removed.put(found, maxlevel); + } + } while ((found != null) && (currentLoop.breakCandidates.size() > 1)); + + Map count = new HashMap<>(); + GraphPart winner = null; + int winnerCount = 0; + for (GraphPart cand : currentLoop.breakCandidates) { + + if (!count.containsKey(cand)) { + count.put(cand, 0); + } + count.put(cand, count.get(cand) + 1); + boolean otherBreakCandidate = false; + for (Loop el : loops) { + if (el == currentLoop) { + continue; + } + if (el.breakCandidates.contains(cand)) { + otherBreakCandidate = true; + break; + } + } + if (otherBreakCandidate) { + } else if (count.get(cand) > winnerCount) { + winnerCount = count.get(cand); + winner = cand; + } else if (count.get(cand) == winnerCount && winner != null) { + if (cand.path.length() < winner.path.length()) { + winner = cand; + } + } + } + for (int i = 0; i < currentLoop.breakCandidates.size(); i++) { + GraphPart cand = currentLoop.breakCandidates.get(i); + if (cand != winner) { + int lev = currentLoop.breakCandidatesLevels.get(i); + if (removed.containsKey(cand)) { + if (removed.get(cand) > lev) { + lev = removed.get(cand); + } + } + removed.put(cand, lev); + } + } + currentLoop.loopBreak = winner; + currentLoop.phase = 2; + boolean start = false; + for (int l = 0; l < loops.size(); l++) { + Loop el = loops.get(l); + if (start) { + el.phase = 1; + } + if (el == currentLoop) { + start = true; + } + } + List removedVisited = new ArrayList<>(); + for (GraphPart r : removed.keySet()) { + if (removedVisited.contains(r)) { + continue; + } + getLoops(localData, r, loops, stopPart, false, removed.get(r), visited); + removedVisited.add(r); + } + start = false; + for (int l = 0; l < loops.size(); l++) { + Loop el = loops.get(l); + if (el == currentLoop) { + start = true; + } + if (start) { + el.phase = 2; + } + } + getLoops(localData, currentLoop.loopBreak, loops, stopPart, false, level, visited); + } + } + + protected List printGraph(Map> partCodes, Map partCodePos, Set visited, BaseLocalData localData, TranslateStack stack, Set allParts, GraphPart parent, GraphPart part, List stopPart, List loops, List ret, int staticOperation, String path, int recursionLevel) throws InterruptedException { + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); + } + if (stopPart == null) { + stopPart = new ArrayList<>(); + } + if (recursionLevel > allParts.size() + 1) { + throw new TranslateException("printGraph max recursion level reached."); + } + + if (ret == null) { + ret = new ArrayList<>(); + } + //try { + boolean debugMode = false; + + if (debugMode) { + System.err.println("PART " + part + " nextsize:" + part.nextParts.size()); + } + + /*while (((part != null) && (part.getHeight() == 1)) && (code.size() > part.start) && (code.get(part.start).isJump())) { //Parts with only jump in it gets ignored + + if (part == stopPart) { + return ret; + } + GraphTargetItem lop = checkLoop(part.nextParts.get(0), stopPart, loops); + if (lop == null) { + part = part.nextParts.get(0); + } else { + break; + } + }*/ + if (part == null) { + return ret; + } + part = checkPart(stack, localData, part, allParts); + if (part == null) { + return ret; + } + + if (part.ignored) { + return ret; + } + + //List loopContinues = getLoopsContinues(loops); + boolean isLoop = false; + Loop currentLoop = null; + for (Loop el : loops) { + if ((el.loopContinue == part) && (el.phase == 0)) { + currentLoop = el; + currentLoop.phase = 1; + isLoop = true; + break; + } + } + + if (debugMode) { + System.err.println("loopsize:" + loops.size()); + } + for (int l = loops.size() - 1; l >= 0; l--) { + Loop el = loops.get(l); + if (el == currentLoop) { + if (debugMode) { + System.err.println("ignoring current loop " + el); + } + continue; + } + if (el.phase != 1) { + if (debugMode) { + System.err.println("ignoring loop " + el); + } + continue; + } + if (el.loopBreak == part) { + if (currentLoop != null) { + currentLoop.phase = 0; + } + if (debugMode) { + System.err.println("Adding break"); + } + ret.add(new BreakItem(null, localData.lineStartInstruction, el.id)); + return ret; + } + if (el.loopPreContinue == part) { + if (currentLoop != null) { + currentLoop.phase = 0; + } + if (debugMode) { + System.err.println("Adding precontinue"); + } + ret.add(new ContinueItem(null, localData.lineStartInstruction, el.id)); + return ret; + } + if (el.loopContinue == part) { + if (currentLoop != null) { + currentLoop.phase = 0; + } + if (debugMode) { + System.err.println("Adding continue"); + } + ret.add(new ContinueItem(null, localData.lineStartInstruction, el.id)); + return ret; + } + } + + if (stopPart.contains(part)) { + if (currentLoop != null) { + currentLoop.phase = 0; + } + if (debugMode) { + System.err.println("Stopped on part " + part); + } + return ret; + } + + if (code.size() <= part.start) { + ret.add(new ScriptEndItem()); + return ret; + } + + if (visited.contains(part)) { + String labelName = "addr" + part.start; + List firstCode = partCodes.get(part); + int firstCodePos = partCodePos.get(part); + if (firstCodePos > firstCode.size()) { + firstCodePos = firstCode.size(); + } + if (firstCode.size() > firstCodePos && (firstCode.get(firstCodePos) instanceof LabelItem)) { + labelName = ((LabelItem) firstCode.get(firstCodePos)).labelName; + } else { + firstCode.add(firstCodePos, new LabelItem(null, localData.lineStartInstruction, labelName)); + } + ret.add(new GotoItem(null, localData.lineStartInstruction, labelName)); + return ret; + } else { + visited.add(part); + partCodes.put(part, ret); + partCodePos.put(part, ret.size()); + } + List currentRet = ret; + UniversalLoopItem loopItem = null; + TranslateStack sPreLoop = stack; + if (isLoop) { + //makeAllCommands(currentRet, stack); + stack = (TranslateStack) stack.clone(); + stack.clear(); + loopItem = new UniversalLoopItem(null, localData.lineStartInstruction, currentLoop); + //loopItem.commands=printGraph(visited, localData, stack, allParts, parent, part, stopPart, loops); + currentRet.add(loopItem); + loopItem.commands = new ArrayList<>(); + currentRet = loopItem.commands; + //return ret; + } + + boolean parseNext = true; + + //****************************DECOMPILING PART************* + List output = new ArrayList<>(); + + List parts = new ArrayList<>(); + if (part instanceof GraphPartMulti) { + parts = ((GraphPartMulti) part).parts; + } else { + parts.add(part); + } + for (GraphPart p : parts) { + int end = p.end; + int start = p.start; + + output.addAll(code.translatePart(p, localData, stack, start, end, staticOperation, path)); + if ((end >= code.size() - 1) && p.nextParts.isEmpty()) { + output.add(new ScriptEndItem()); + } + } + + if (parseNext) { + List retCheck = check(partCodes, partCodePos, code, localData, allParts, stack, parent, part, stopPart, loops, output, currentLoop, staticOperation, path); + if (retCheck != null) { + if (!retCheck.isEmpty()) { + currentRet.addAll(retCheck); + } + parseNext = false; + //return ret; + } else { + currentRet.addAll(output); + } + } +//********************************END PART DECOMPILING + if (parseNext) { + + if (part.nextParts.size() > 2) { + GraphPart next = getMostCommonPart(localData, part.nextParts, loops); + List vis = new ArrayList<>(); + GraphTargetItem switchedItem = stack.pop(); + makeAllCommands(currentRet, stack); + + List caseValues = new ArrayList<>(); + List> caseCommands = new ArrayList<>(); + List valueMappings = new ArrayList<>(); + Loop swLoop = new Loop(loops.size(), null, next); + swLoop.phase = 1; + loops.add(swLoop); + boolean first = false; + int pos; + + Map caseExpressions = new HashMap<>(); + Map caseExpressionLeftSides = new HashMap<>(); + Map caseExpressionRightSides = new HashMap<>(); + GraphTargetItem it = switchedItem; + int defaultBranch = 0; + boolean hasExpr = false; + + while (it instanceof TernarOpItem) { + TernarOpItem to = (TernarOpItem) it; + if (to.expression instanceof EqualsTypeItem) { + if (to.onTrue instanceof IntegerValueTypeItem) { + int cpos = ((IntegerValueTypeItem) to.onTrue).intValue(); + caseExpressionLeftSides.put(cpos, ((EqualsTypeItem) to.expression).getLeftSide()); + caseExpressionRightSides.put(cpos, ((EqualsTypeItem) to.expression).getRightSide()); + it = to.onFalse; + } else { + break; + } + } else if (to.expression instanceof FalseItem) { + it = to.onFalse; + } else if (to.expression instanceof TrueItem) { + it = to.onTrue; + } else { + break; + } + } + //int ignoredBranch = -1; + if (it instanceof IntegerValueTypeItem) { + defaultBranch = ((IntegerValueTypeItem) it).intValue(); + } + + if (!caseExpressionRightSides.isEmpty()) { + GraphTargetItem firstItem; + firstItem = (GraphTargetItem) caseExpressionRightSides.values().toArray()[0]; + boolean sameRight = true; + for (GraphTargetItem cit : caseExpressionRightSides.values()) { + if (!cit.equals(firstItem)) { + sameRight = false; + break; + } + } + + if (sameRight) { + caseExpressions = caseExpressionLeftSides; + switchedItem = firstItem; + hasExpr = true; + } else { + firstItem = (GraphTargetItem) caseExpressionLeftSides.values().toArray()[0]; + + boolean sameLeft = true; + for (GraphTargetItem cit : caseExpressionLeftSides.values()) { + if (!cit.equals(firstItem)) { + sameLeft = false; + break; + } + } + if (sameLeft) { + caseExpressions = caseExpressionRightSides; + switchedItem = firstItem; + hasExpr = true; + } + } + } + + first = true; + pos = 0; + //This is tied to AS3 switch implementation which has nextparts switched from index 1. TODO: Make more universal + + GraphPart defaultPart = hasExpr ? part.nextParts.get(1 + defaultBranch) : part.nextParts.get(0); + //int defaultNum = hasExpr ? 1 + defaultBranch : 0; + + for (int i = 1; i < part.nextParts.size(); i++) { + if (caseExpressions.containsKey(pos)) { + caseValues.add(caseExpressions.get(pos)); + } else if (part.nextParts.get(i) == defaultPart) { + caseValues.add(new DefaultItem()); + } else { + caseValues.add(new IntegerValueItem(null, localData.lineStartInstruction, pos)); + } + pos++; + } + + first = true; + pos = 0; + List nextCommands = new ArrayList<>(); + for (int i = 1; i < part.nextParts.size(); i++) { + GraphPart p = part.nextParts.get(i); + /*if (pos == ignoredBranch) { + pos++; + continue; + }*/ + //if (p != defaultPart) + { + if (vis.contains(p)) { + valueMappings.add(caseCommands.size() - 1); + continue; + } + valueMappings.add(caseCommands.size()); + } + List stopPart2 = new ArrayList<>(); + if (next != null) { + stopPart2.add(next); + } else if (!stopPart.isEmpty()) { + stopPart2.add(stopPart.get(stopPart.size() - 1)); + } + for (GraphPart p2 : part.nextParts) { + if (p2 == p) { + continue; + } + if (!stopPart2.contains(p2)) { + stopPart2.add(p2); + } + } + if (next != p) { + //if (p == defaultPart && !defaultCommands.isEmpty()) { + //ignore + //} else + { + TranslateStack s2 = (TranslateStack) stack.clone(); + s2.clear(); + nextCommands = printGraph(partCodes, partCodePos, visited, prepareBranchLocalData(localData), s2, allParts, part, p, stopPart2, loops, null, staticOperation, path, recursionLevel + 1); + makeAllCommands(nextCommands, s2); + caseCommands.add(nextCommands); + vis.add(p); + } + } else { + caseCommands.add(nextCommands); + } + first = false; + pos++; + } + + //If the lastone is default empty and alone, remove it + if (!caseCommands.isEmpty()) { + List lastc = caseCommands.get(caseCommands.size() - 1); + if (!lastc.isEmpty() && (lastc.get(lastc.size() - 1) instanceof BreakItem)) { + BreakItem bi = (BreakItem) lastc.get(lastc.size() - 1); + if (bi.loopId == swLoop.id) { + lastc.remove(lastc.size() - 1); + } + } + if (lastc.isEmpty()) { + int cnt = 0; + if (caseValues.get(caseValues.size() - 1) instanceof DefaultItem) { + for (int i = valueMappings.size() - 1; i >= 0; i--) { + if (valueMappings.get(i) == caseCommands.size() - 1) { + cnt++; + } + } + if (cnt == 1) { + caseValues.remove(caseValues.size() - 1); + valueMappings.remove(valueMappings.size() - 1); + caseCommands.remove(lastc); + } + } + } + } + //remove last break from last section + if (!caseCommands.isEmpty()) { + List lastc = caseCommands.get(caseCommands.size() - 1); + if (!lastc.isEmpty() && (lastc.get(lastc.size() - 1) instanceof BreakItem)) { + BreakItem bi = (BreakItem) lastc.get(lastc.size() - 1); + if (bi.loopId == swLoop.id) { + lastc.remove(lastc.size() - 1); + } + } + } + SwitchItem sw = new SwitchItem(null, localData.lineStartInstruction, swLoop, switchedItem, caseValues, caseCommands, valueMappings); + currentRet.add(sw); + swLoop.phase = 2; + if (next != null) { + currentRet.addAll(printGraph(partCodes, partCodePos, visited, localData, stack, allParts, part, next, stopPart, loops, null, staticOperation, path, recursionLevel + 1)); + } + pos++; + } //else + GraphPart nextOnePart = null; + if (part.nextParts.size() == 2) { + GraphTargetItem expr = stack.pop(); + /*if (expr instanceof LogicalOpItem) { + expr = ((LogicalOpItem) expr).invert(); + } else { + expr = new NotItem(null, expr); + }*/ + if (nextOnePart == null) { + + List nps; + nps = part.nextParts; + boolean isEmpty = nps.get(0) == nps.get(1); + + GraphPart next = getCommonPart(localData, nps, loops); + TranslateStack trueStack = (TranslateStack) stack.clone(); + TranslateStack falseStack = (TranslateStack) stack.clone(); + trueStack.clear(); + falseStack.clear(); + + if (isEmpty) { + next = nps.get(0); + } + boolean hasOntrue = nps.get(1) != next; + boolean hasOnFalse = nps.get(0) != next; + + List stopPart2 = new ArrayList<>(stopPart); + + if ((!isEmpty) && (next != null)) { + stopPart2.add(next); + } + + List onTrue = new ArrayList<>(); + if (!isEmpty && hasOntrue) { + onTrue = printGraph(partCodes, partCodePos, visited, prepareBranchLocalData(localData), trueStack, allParts, part, nps.get(1), stopPart2, loops, null, staticOperation, path, recursionLevel + 1); + } + List onFalse = new ArrayList<>(); + + if (!isEmpty && hasOnFalse) { + onFalse = printGraph(partCodes, partCodePos, visited, prepareBranchLocalData(localData), falseStack, allParts, part, nps.get(0), stopPart2, loops, null, staticOperation, path, recursionLevel + 1); + } + //List out2 = new ArrayList<>(); + //makeAllCommands(out2, stack); + makeAllCommands(onTrue, trueStack); + makeAllCommands(onFalse, falseStack); + + List filteredOnTrue = filter(onTrue); + List filteredOnFalse = filter(onFalse); + + if (!isEmpty(filteredOnTrue) && !isEmpty(filteredOnFalse) && filteredOnTrue.size() == 1 && filteredOnFalse.size() == 1 && (filteredOnTrue.get(0) instanceof PushItem) && (filteredOnFalse.get(0) instanceof PushItem)) { + stack.push(new TernarOpItem(null, localData.lineStartInstruction, expr.invert(null), ((PushItem) filteredOnTrue.get(0)).value, ((PushItem) filteredOnFalse.get(0)).value)); + } else { + boolean isIf = true; + //If the ontrue is empty, switch ontrue and onfalse + if (filteredOnTrue.isEmpty() && !filteredOnFalse.isEmpty()) { + expr = expr.invert(null); + List tmp = onTrue; + onTrue = onFalse; + onFalse = tmp; + //tmp = filteredOnTrue; + filteredOnTrue = filteredOnFalse; + //filteredOnFalse = tmp; + } + if (!stack.isEmpty() && ((filteredOnTrue.size() == 1 && (filteredOnTrue.get(0) instanceof PopItem)) || ((filteredOnTrue.size() >= 2) && (filteredOnTrue.get(0) instanceof PopItem) && (filteredOnTrue.get(filteredOnTrue.size() - 1) instanceof PushItem)))) { + if (filteredOnTrue.size() > 1) { + GraphTargetItem rightSide = ((PushItem) filteredOnTrue.get(filteredOnTrue.size() - 1)).value; + GraphTargetItem prevExpr = stack.pop(); + GraphTargetItem leftSide = expr.getNotCoercedNoDup(); + + if (leftSide instanceof DuplicateItem) { + isIf = false; + stack.push(new OrItem(null, localData.lineStartInstruction, prevExpr, rightSide)); + } else if (leftSide.invert(null).getNotCoercedNoDup() instanceof DuplicateItem) { + isIf = false; + stack.push(new AndItem(null, localData.lineStartInstruction, prevExpr, rightSide)); + } else if (prevExpr instanceof FalseItem) { + isIf = false; + leftSide = leftSide.invert(null); + stack.push(new AndItem(null, localData.lineStartInstruction, leftSide, rightSide)); + } else if (prevExpr instanceof TrueItem) { + isIf = false; + stack.push(new OrItem(null, localData.lineStartInstruction, leftSide, rightSide)); + } else { + //:-( + } + } else { + isIf = false; + } + } + + if (isIf) { + makeAllCommands(currentRet, stack); + IfItem b = new IfItem(null, localData.lineStartInstruction, expr.invert(null), onTrue, onFalse); + currentRet.add(b); + if (processSubBlk(b, null)) { + stack.push(new PopItem(null, localData.lineStartInstruction)); + } + } + } + //currentRet.addAll(out2); + if (next != null) { + printGraph(partCodes, partCodePos, visited, localData, stack, allParts, part, next, stopPart, loops, currentRet, staticOperation, path, recursionLevel + 1); + //currentRet.addAll(); + } + } + } //else + if (part.nextParts.size() == 1) { + nextOnePart = part.nextParts.get(0); + } + if (nextOnePart != null) { + printGraph(partCodes, partCodePos, visited, localData, stack, allParts, part, part.nextParts.get(0), stopPart, loops, currentRet, staticOperation, path, recursionLevel + 1); + } + + } + if (isLoop && loopItem != null && currentLoop != null) { + + LoopItem li = loopItem; + boolean loopTypeFound = false; + + boolean hasContinue = false; + processIfs(loopItem.commands); + checkContinueAtTheEnd(loopItem.commands, currentLoop); + List continues = loopItem.getContinues(); + for (ContinueItem c : continues) { + if (c.loopId == currentLoop.id) { + hasContinue = true; + break; + } + } + if (!hasContinue) { + if (currentLoop.loopPreContinue != null) { + List stopContPart = new ArrayList<>(); + stopContPart.add(currentLoop.loopContinue); + GraphPart precoBackup = currentLoop.loopPreContinue; + currentLoop.loopPreContinue = null; + loopItem.commands.addAll(printGraph(partCodes, partCodePos, visited, localData, new TranslateStack(path), allParts, null, precoBackup, stopContPart, loops, null, staticOperation, path, recursionLevel + 1)); + } + } + + //Loop with condition at the beginning (While) + if (!loopTypeFound && (!loopItem.commands.isEmpty())) { + if (loopItem.commands.get(0) instanceof IfItem) { + IfItem ifi = (IfItem) loopItem.commands.get(0); + + List bodyBranch = null; + boolean inverted = false; + boolean breakpos2 = false; + if ((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem)) { + BreakItem bi = (BreakItem) ifi.onTrue.get(0); + if (bi.loopId == currentLoop.id) { + bodyBranch = ifi.onFalse; + inverted = true; + } + } else if ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof BreakItem)) { + BreakItem bi = (BreakItem) ifi.onFalse.get(0); + if (bi.loopId == currentLoop.id) { + bodyBranch = ifi.onTrue; + } + } else if (loopItem.commands.size() == 2 && (loopItem.commands.get(1) instanceof BreakItem)) { + BreakItem bi = (BreakItem) loopItem.commands.get(1); + if (bi.loopId == currentLoop.id) { + if (ifi.onTrue.isEmpty()) { + inverted = true; + } + bodyBranch = inverted ? ifi.onFalse : ifi.onTrue; + breakpos2 = true; + } + } + if (bodyBranch != null) { + int index = ret.indexOf(loopItem); + ret.remove(index); + List exprList = new ArrayList<>(); + GraphTargetItem expr = ifi.expression; + if (inverted) { + if (expr instanceof LogicalOpItem) { + expr = ((LogicalOpItem) expr).invert(null); + } else { + expr = new NotItem(null, expr.getLineStartItem(), expr); + } + } + exprList.add(expr); + List commands = new ArrayList<>(); + commands.addAll(bodyBranch); + loopItem.commands.remove(0); + if (breakpos2) { + loopItem.commands.remove(0); //remove that break too + } + commands.addAll(loopItem.commands); + checkContinueAtTheEnd(commands, currentLoop); + List finalComm = new ArrayList<>(); + if (currentLoop.loopPreContinue != null) { + GraphPart backup = currentLoop.loopPreContinue; + currentLoop.loopPreContinue = null; + List stopPart2 = new ArrayList<>(stopPart); + stopPart2.add(currentLoop.loopContinue); + finalComm = printGraph(partCodes, partCodePos, visited, localData, new TranslateStack(path), allParts, null, backup, stopPart2, loops, null, staticOperation, path, recursionLevel + 1); + currentLoop.loopPreContinue = backup; + checkContinueAtTheEnd(finalComm, currentLoop); + } + if (!finalComm.isEmpty()) { + ret.add(index, li = new ForItem(expr.getSrc(), expr.getLineStartItem(), currentLoop, new ArrayList<>(), exprList.get(exprList.size() - 1), finalComm, commands)); + } else { + ret.add(index, li = new WhileItem(expr.getSrc(), expr.getLineStartItem(), currentLoop, exprList, commands)); + } + + loopTypeFound = true; + } + } + } + + //Loop with condition at the end (Do..While) + if (!loopTypeFound && (!loopItem.commands.isEmpty())) { + if (loopItem.commands.get(loopItem.commands.size() - 1) instanceof IfItem) { + IfItem ifi = (IfItem) loopItem.commands.get(loopItem.commands.size() - 1); + List bodyBranch = null; + boolean inverted = false; + if ((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem)) { + BreakItem bi = (BreakItem) ifi.onTrue.get(0); + if (bi.loopId == currentLoop.id) { + bodyBranch = ifi.onFalse; + inverted = true; + } + } else if ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof BreakItem)) { + BreakItem bi = (BreakItem) ifi.onFalse.get(0); + if (bi.loopId == currentLoop.id) { + bodyBranch = ifi.onTrue; + } + } + if (bodyBranch != null) { + //Condition at the beginning + int index = ret.indexOf(loopItem); + ret.remove(index); + List exprList = new ArrayList<>(); + GraphTargetItem expr = ifi.expression; + if (inverted) { + expr = expr.invert(null); + } + + checkContinueAtTheEnd(bodyBranch, currentLoop); + + List commands = new ArrayList<>(); + + if (!bodyBranch.isEmpty()) { + ret.add(index, loopItem); + /* + loopItem.commands.remove(loopItem.commands.size() - 1); + exprList.addAll(loopItem.commands); + commands.addAll(bodyBranch); + exprList.add(expr); + checkContinueAtTheEnd(commands, currentLoop); + ret.add(index, li = new WhileItem(null, currentLoop, exprList, commands));*/ + } else { + loopItem.commands.remove(loopItem.commands.size() - 1); + commands.addAll(loopItem.commands); + commands.addAll(bodyBranch); + exprList.add(expr); + checkContinueAtTheEnd(commands, currentLoop); + ret.add(index, li = new DoWhileItem(null, exprList.get(0).getLineStartItem(), currentLoop, commands, exprList)); + } + + loopTypeFound = true; + } + } + } + + if (!loopTypeFound) { + if (currentLoop.loopPreContinue != null) { + loopTypeFound = true; + GraphPart backup = currentLoop.loopPreContinue; + currentLoop.loopPreContinue = null; + List stopPart2 = new ArrayList<>(stopPart); + stopPart2.add(currentLoop.loopContinue); + List finalComm = printGraph(partCodes, partCodePos, visited, localData, new TranslateStack(path), allParts, null, backup, stopPart2, loops, null, staticOperation, path, recursionLevel + 1); + currentLoop.loopPreContinue = backup; + checkContinueAtTheEnd(finalComm, currentLoop); + + if (!finalComm.isEmpty()) { + if (finalComm.get(finalComm.size() - 1) instanceof IfItem) { + IfItem ifi = (IfItem) finalComm.get(finalComm.size() - 1); + boolean ok = false; + boolean invert = false; + if (((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem) && (((BreakItem) ifi.onTrue.get(0)).loopId == currentLoop.id)) + && ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof ContinueItem) && (((ContinueItem) ifi.onFalse.get(0)).loopId == currentLoop.id))) { + ok = true; + invert = true; + } + if (((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof ContinueItem) && (((ContinueItem) ifi.onTrue.get(0)).loopId == currentLoop.id)) + && ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof BreakItem) && (((BreakItem) ifi.onFalse.get(0)).loopId == currentLoop.id))) { + ok = true; + } + if (ok) { + finalComm.remove(finalComm.size() - 1); + int index = ret.indexOf(loopItem); + ret.remove(index); + List exprList = new ArrayList<>(finalComm); + GraphTargetItem expr = ifi.expression; + if (invert) { + expr = expr.invert(null); + } + exprList.add(expr); + ret.add(index, li = new DoWhileItem(null, expr.getLineStartItem(), currentLoop, loopItem.commands, exprList)); + } + } + } + } + } + + if (!loopTypeFound) { + checkContinueAtTheEnd(loopItem.commands, currentLoop); + } + currentLoop.phase = 2; + + GraphTargetItem replaced = checkLoop(li, localData, loops); + if (replaced != li) { + int index = ret.indexOf(li); + ret.remove(index); + if (replaced != null) { + ret.add(index, replaced); + } + } + + if (currentLoop.loopBreak != null) { + ret.addAll(printGraph(partCodes, partCodePos, visited, localData, sPreLoop, allParts, part, currentLoop.loopBreak, stopPart, loops, null, staticOperation, path, recursionLevel + 1)); + } + } + + return ret; + } + + protected void checkGraph(List allBlocks) { + } + + private List makeGraph(GraphSource code, List allBlocks, List alternateEntries) throws InterruptedException { + HashMap> refs = code.visitCode(alternateEntries); + List ret = new ArrayList<>(); + boolean[] visited = new boolean[code.size()]; + ret.add(makeGraph(null, new GraphPath(), code, 0, 0, allBlocks, refs, visited)); + for (int pos : alternateEntries) { + GraphPart e1 = new GraphPart(-1, -1); + e1.path = new GraphPath("e"); + ret.add(makeGraph(e1, new GraphPath("e"), code, pos, pos, allBlocks, refs, visited)); + } + checkGraph(allBlocks); + return ret; + } + + protected int checkIp(int ip) { + return ip; + } + + private GraphPart makeGraph(GraphPart parent, GraphPath path, GraphSource code, int startip, int lastIp, List allBlocks, HashMap> refs, boolean[] visited2) throws InterruptedException { + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); + } + + int ip = startip; + for (GraphPart p : allBlocks) { + if (p.start == ip) { + p.refs.add(parent); + return p; + } + } + GraphPart g; + GraphPart ret = new GraphPart(ip, -1); + ret.path = path; + GraphPart part = ret; + while (ip < code.size()) { + if (visited2[ip] || ((ip != startip) && (refs.get(ip).size() > 1))) { + part.end = lastIp; + GraphPart found = null; + for (GraphPart p : allBlocks) { + if (p.start == ip) { + found = p; + break; + } + } + + allBlocks.add(part); + + if (found != null) { + part.nextParts.add(found); + found.refs.add(part); + break; + } else { + GraphPart gp = new GraphPart(ip, -1); + gp.path = path; + part.nextParts.add(gp); + gp.refs.add(part); + part = gp; + } + } + + ip = checkIp(ip); + lastIp = ip; + GraphSourceItem ins = code.get(ip); + if (ins.isIgnored()) { + ip++; + continue; + } + if (ins instanceof GraphSourceItemContainer) { + GraphSourceItemContainer cnt = (GraphSourceItemContainer) ins; + if (ins instanceof Action) { //TODO: Remove dependency of AVM1 + long endAddr = ((Action) ins).getAddress() + cnt.getHeaderSize(); + for (long size : cnt.getContainerSizes()) { + endAddr += size; + } + ip = code.adr2pos(endAddr); + } + continue; + } else if (ins.isExit()) { + part.end = ip; + allBlocks.add(part); + break; + } else if (ins.isJump()) { + part.end = ip; + allBlocks.add(part); + ip = ins.getBranches(code).get(0); + part.nextParts.add(g = makeGraph(part, path, code, ip, lastIp, allBlocks, refs, visited2)); + g.refs.add(part); + break; + } else if (ins.isBranch()) { + part.end = ip; + + allBlocks.add(part); + List branches = ins.getBranches(code); + for (int i = 0; i < branches.size(); i++) { + part.nextParts.add(g = makeGraph(part, path.sub(i, ip), code, branches.get(i), ip, allBlocks, refs, visited2)); + g.refs.add(part); + } + break; + } + ip++; + } + if ((part.end == -1) && (ip >= code.size())) { + if (part.start == code.size()) { + part.end = code.size(); + allBlocks.add(part); + } else { + part.end = ip - 1; + for (GraphPart p : allBlocks) { + if (p.start == ip) { + p.refs.add(part); + part.nextParts.add(p); + allBlocks.add(part); + return ret; + } + } + GraphPart gp = new GraphPart(ip, ip); + allBlocks.add(gp); + gp.refs.add(part); + part.nextParts.add(gp); + allBlocks.add(part); + } + } + return ret; + } + + /** + * String used to indent line when converting to string + */ + public static final String INDENTOPEN = "INDENTOPEN"; + + /** + * String used to unindent line when converting to string + */ + public static final String INDENTCLOSE = "INDENTCLOSE"; + + /** + * Converts list of TreeItems to string + * + * @param tree List of TreeItem + * @param writer + * @param localData + * @return String + * @throws java.lang.InterruptedException + */ + public static GraphTextWriter graphToString(List tree, GraphTextWriter writer, LocalData localData) throws InterruptedException { + for (GraphTargetItem ti : tree) { + if (!ti.isEmpty()) { + ti.toStringSemicoloned(writer, localData).newLine(); + } + } + return writer; + } + + public BaseLocalData prepareBranchLocalData(BaseLocalData localData) { + return localData; + } + + protected List checkPrecoNextParts(GraphPart part) { + return null; + } + + protected GraphPart makeMultiPart(GraphPart part) { + List parts = new ArrayList<>(); + do { + parts.add(part); + if (part.nextParts.size() == 1 && part.nextParts.get(0).refs.size() == 1) { + part = part.nextParts.get(0); + } else { + part = null; + } + } while (part != null); + if (parts.size() > 1) { + GraphPartMulti ret = new GraphPartMulti(parts); + ret.refs.addAll(parts.get(0).refs); + ret.nextParts.addAll(parts.get(parts.size() - 1).nextParts); + return ret; + } else { + return parts.get(0); + } + } + + protected List getPartItems(GraphPart part) { + List ret = new ArrayList<>(); + do { + for (int i = 0; i < part.getHeight(); i++) { + if (part.getPosAt(i) < code.size()) { + if (part.getPosAt(i) < 0) { + continue; + } + GraphSourceItem s = code.get(part.getPosAt(i)); + if (!s.isJump()) { + ret.add(s); + } + } + } + if (part.nextParts.size() == 1 && part.nextParts.get(0).refs.size() == 1) { + part = part.nextParts.get(0); + } else { + part = null; + } + } while (part != null); + return ret; + } + + protected static void makeAllStack(List commands, TranslateStack stack) { + int pcnt = 0; + for (int i = commands.size() - 1; i >= 0; i--) { + if (commands.get(i) instanceof PushItem) { + pcnt++; + } else { + break; + } + } + for (int i = commands.size() - pcnt; i < commands.size(); i++) { + stack.push(commands.remove(i).value); + i--; + } + } + + protected static void makeAllCommands(List commands, TranslateStack stack) { + int clen = commands.size(); + if (!commands.isEmpty()) { + if (commands.get(commands.size() - 1) instanceof BreakItem) { + clen--; + } + } + while (stack.size() > 0) { + GraphTargetItem p = stack.pop(); + if (!(p instanceof PopItem)) { + if (p instanceof FunctionActionItem) { + commands.add(clen, p); + } else { + commands.add(clen, new PushItem(p)); + } + } + } + } +} diff --git a/nsis_locales/Hungarian.nsh b/nsis_locales/Hungarian.nsh index f8ab8d8be..c67255b8c 100644 --- a/nsis_locales/Hungarian.nsh +++ b/nsis_locales/Hungarian.nsh @@ -47,7 +47,7 @@ !insertmacro LANG_STRING STRING_JRE_DETECTCOMPLETE "JRE Verzió detektálás kész - eredmény = " !insertmacro LANG_STRING STRING_JRE_DETECTCOMPLETE_OLD "Régi JRE található" !insertmacro LANG_STRING STRING_JRE_DETECTCOMPLETE_NO "JRE nem található" -!insertmacro LANG_STRING STRING_JRE_DETECTCOMPARE_1 "" +!insertmacro LANG_STRING STRING_JRE_DETECTCOMPARE_1 "A " !insertmacro LANG_STRING STRING_JRE_DETECTCOMPARE_2 " verzió összehasonlítása a " !insertmacro LANG_STRING STRING_JRE_DETECTCOMPARE_3 " verzióval. Eredmények: " !insertmacro LANG_STRING STRING_JRE_WILLDOWNLOAD "JRE letöltése innen: " diff --git a/src/com/jpexs/browsers/cache/CacheEntry.java b/src/com/jpexs/browsers/cache/CacheEntry.java index 9da50045d..e8c19e4df 100644 --- a/src/com/jpexs/browsers/cache/CacheEntry.java +++ b/src/com/jpexs/browsers/cache/CacheEntry.java @@ -88,7 +88,7 @@ public abstract class CacheEntry { if (st == null) { return 0; } - String parts[] = st.split(" "); + String[] parts = st.split(" "); try { return Integer.parseInt(parts[1]); } catch (NumberFormatException nfe) { diff --git a/src/com/jpexs/browsers/cache/chrome/BlockFileHeader.java b/src/com/jpexs/browsers/cache/chrome/BlockFileHeader.java index a5734a150..28c719ce7 100644 --- a/src/com/jpexs/browsers/cache/chrome/BlockFileHeader.java +++ b/src/com/jpexs/browsers/cache/chrome/BlockFileHeader.java @@ -43,13 +43,13 @@ public class BlockFileHeader { private final int max_entries; - private int empty[] = new int[4]; + private int[] empty = new int[4]; - private int hints[] = new int[4]; + private int[] hints = new int[4]; private final int updating; - private int user[] = new int[5]; + private int[] user = new int[5]; private final long allocation_map[]; diff --git a/src/com/jpexs/browsers/cache/chrome/CacheAddr.java b/src/com/jpexs/browsers/cache/chrome/CacheAddr.java index 6d09613be..8178eb7ee 100644 --- a/src/com/jpexs/browsers/cache/chrome/CacheAddr.java +++ b/src/com/jpexs/browsers/cache/chrome/CacheAddr.java @@ -45,9 +45,9 @@ public class CacheAddr { private static final int BLOCK_EVICTED = 7; - private static final String blockNames[] = new String[]{"EXTERNAL", "RANKINGS", "BLOCK_256", "BLOCK_1K", "BLOCK_4K", "BLOCK_FILES", "BLOCK_ENTRIES", "BLOCK_EVICTED"}; + private static final String[] blockNames = new String[]{"EXTERNAL", "RANKINGS", "BLOCK_256", "BLOCK_1K", "BLOCK_4K", "BLOCK_FILES", "BLOCK_ENTRIES", "BLOCK_EVICTED"}; - private static final int blockSizes[] = new int[]{0, 36, 256, 1024, 4096, 8, 104, 48}; + private static final int[] blockSizes = new int[]{0, 36, 256, 1024, 4096, 8, 104, 48}; private static final long kInitializedMask = 0x80000000L; diff --git a/src/com/jpexs/browsers/cache/chrome/EntryStore.java b/src/com/jpexs/browsers/cache/chrome/EntryStore.java index d3114c596..d08c60ada 100644 --- a/src/com/jpexs/browsers/cache/chrome/EntryStore.java +++ b/src/com/jpexs/browsers/cache/chrome/EntryStore.java @@ -61,17 +61,17 @@ public class EntryStore extends CacheEntry { public CacheAddr long_key; // Optional address of a long key. - public int data_size[] = new int[4]; // We can store up to 4 data streams for each + public int[] data_size = new int[4]; // We can store up to 4 data streams for each - public CacheAddr data_addr[] = new CacheAddr[4]; // entry. + public CacheAddr[] data_addr = new CacheAddr[4]; // entry. public long flags; // Any combination of EntryFlags. - public int pad[] = new int[4]; + public int[] pad = new int[4]; public long self_hash; // The hash of EntryStore up to this point. - public byte key[] = new byte[256 - 24 * 4]; // null terminated + public byte[] key = new byte[256 - 24 * 4]; // null terminated public EntryStore(InputStream is, File rootDir, Map dataFiles, File externalFilesDir) throws IOException { IndexInputStream iis = new IndexInputStream(is); @@ -154,7 +154,7 @@ public class EntryStore extends CacheEntry { for (int h = 1; h < headers.size(); h++) { String hs = headers.get(h); if (hs.contains(":")) { - String hp[] = hs.split(":"); + String[] hp = hs.split(":"); ret.put(hp[0].trim(), hp[1].trim()); } } diff --git a/src/com/jpexs/browsers/cache/chrome/HttpResponseInfo.java b/src/com/jpexs/browsers/cache/chrome/HttpResponseInfo.java index 74e3c79c4..e5b43dde6 100644 --- a/src/com/jpexs/browsers/cache/chrome/HttpResponseInfo.java +++ b/src/com/jpexs/browsers/cache/chrome/HttpResponseInfo.java @@ -92,7 +92,7 @@ public class HttpResponseInfo { public String getHeaderValue(String header) { for (String h : headers) { if (h.contains(":")) { - String keyval[] = h.split(":"); + String[] keyval = h.split(":"); String key = keyval[0].trim().toLowerCase(); String val = keyval[1].trim(); if (header.toLowerCase().equals(key)) { diff --git a/src/com/jpexs/browsers/cache/chrome/IndexHeader.java b/src/com/jpexs/browsers/cache/chrome/IndexHeader.java index 1981e9aae..d2ea84376 100644 --- a/src/com/jpexs/browsers/cache/chrome/IndexHeader.java +++ b/src/com/jpexs/browsers/cache/chrome/IndexHeader.java @@ -50,7 +50,7 @@ public class IndexHeader { long create_time; - int pad[] = new int[52]; + int[] pad = new int[52]; LruData lru; diff --git a/src/com/jpexs/browsers/cache/chrome/IndexInputStream.java b/src/com/jpexs/browsers/cache/chrome/IndexInputStream.java index 24ceaa9ee..e10be3cbc 100644 --- a/src/com/jpexs/browsers/cache/chrome/IndexInputStream.java +++ b/src/com/jpexs/browsers/cache/chrome/IndexInputStream.java @@ -73,7 +73,7 @@ public class IndexInputStream extends InputStream { public String readString() throws IOException { int len = (int) readInt(); - byte data[] = new byte[len]; + byte[] data = new byte[len]; if (read(data) != len) { throw new IOException(); } diff --git a/src/com/jpexs/browsers/cache/chrome/LruData.java b/src/com/jpexs/browsers/cache/chrome/LruData.java index 53cd725de..529cc8d5e 100644 --- a/src/com/jpexs/browsers/cache/chrome/LruData.java +++ b/src/com/jpexs/browsers/cache/chrome/LruData.java @@ -28,15 +28,15 @@ import java.util.Map; */ public class LruData { - int pad1[] = new int[2]; + int[] pad1 = new int[2]; int filled; - int sizes[] = new int[5]; + int[] sizes = new int[5]; - CacheAddr heads[] = new CacheAddr[5]; + CacheAddr[] heads = new CacheAddr[5]; - CacheAddr tails[] = new CacheAddr[5]; + CacheAddr[] tails = new CacheAddr[5]; CacheAddr transaction; @@ -44,7 +44,7 @@ public class LruData { int operation_list; - int pad2[] = new int[7]; + int[] pad2 = new int[7]; public LruData(InputStream is, File rootDir, Map dataFiles, File externalFilesDir) throws IOException { IndexInputStream iis = new IndexInputStream(is); diff --git a/src/com/jpexs/browsers/cache/firefox/FirefoxCache.java b/src/com/jpexs/browsers/cache/firefox/FirefoxCache.java index 6f3eb4658..3fe5c5567 100644 --- a/src/com/jpexs/browsers/cache/firefox/FirefoxCache.java +++ b/src/com/jpexs/browsers/cache/firefox/FirefoxCache.java @@ -111,7 +111,7 @@ public class FirefoxCache implements CacheImplementation { if (profilesDir == null) { return null; } - File profiles[] = profilesDir.listFiles(); + File[] profiles = profilesDir.listFiles(); File profileDir = null; for (File f : profiles) { if (f.isDirectory()) { diff --git a/src/com/jpexs/browsers/cache/firefox/MapBucket.java b/src/com/jpexs/browsers/cache/firefox/MapBucket.java index 2538eb733..156023669 100644 --- a/src/com/jpexs/browsers/cache/firefox/MapBucket.java +++ b/src/com/jpexs/browsers/cache/firefox/MapBucket.java @@ -92,12 +92,12 @@ public class MapBucket extends CacheEntry { if (responseHead == null) { return null; } - String headers[] = responseHead.split("\r\n"); + String[] headers = responseHead.split("\r\n"); Map ret = new HashMap<>(); for (int h = 1; h < headers.length; h++) { String hs = headers[h]; if (hs.contains(":")) { - String hp[] = hs.split(":"); + String[] hp = hs.split(":"); ret.put(hp[0].trim(), hp[1].trim()); } } @@ -111,7 +111,7 @@ public class MapBucket extends CacheEntry { return null; } String responseHead = m.response.get("response-head"); - String headers[] = responseHead.split("\r\n"); + String[] headers = responseHead.split("\r\n"); return headers[0]; } diff --git a/src/com/jpexs/browsers/cache/firefox/MetaData.java b/src/com/jpexs/browsers/cache/firefox/MetaData.java index 9c0fd0f96..78bdd1376 100644 --- a/src/com/jpexs/browsers/cache/firefox/MetaData.java +++ b/src/com/jpexs/browsers/cache/firefox/MetaData.java @@ -66,13 +66,13 @@ public class MetaData { dataSize = cis.readInt32(); requestSize = cis.readInt32(); infoSize = cis.readInt32(); - byte req[] = new byte[(int) requestSize]; + byte[] req = new byte[(int) requestSize]; if (cis.read(req) != req.length) { throw new IOException(); } request = new String(req, 0, (int) requestSize - 1/*Ends with char 0*/); - byte res[] = new byte[(int) infoSize]; + byte[] res = new byte[(int) infoSize]; cis.read(res); String responseStr = new String(res); int nulpos; diff --git a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java index d0c5261e4..ef8c1134b 100644 --- a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java +++ b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java @@ -1610,7 +1610,7 @@ public class CommandLineArgumentParser { Integer min = null; Integer max = null; if (r.contains("-")) { - String ps[] = r.split("\\-"); + String[] ps = r.split("\\-"); if (ps.length != 2) { System.err.println("invalid range"); badArguments("select"); diff --git a/src/com/jpexs/decompiler/flash/console/ContextMenuTools.java b/src/com/jpexs/decompiler/flash/console/ContextMenuTools.java index 90c306e7d..42ec4cc6e 100644 --- a/src/com/jpexs/decompiler/flash/console/ContextMenuTools.java +++ b/src/com/jpexs/decompiler/flash/console/ContextMenuTools.java @@ -82,7 +82,7 @@ public class ContextMenuTools { } private static boolean addToContextMenu(boolean add, boolean fromCommandLine, String exeName) { - final String extensions[] = new String[]{"swf", "gfx"}; + final String[] extensions = new String[]{"swf", "gfx"}; final WinReg.HKEY REG_CLASSES_HKEY = WinReg.HKEY_LOCAL_MACHINE; final String REG_CLASSES_PATH = "Software\\Classes\\"; diff --git a/src/com/jpexs/decompiler/flash/gui/AdvancedSettingsDialog.java b/src/com/jpexs/decompiler/flash/gui/AdvancedSettingsDialog.java index d639549cc..dfe518f9a 100644 --- a/src/com/jpexs/decompiler/flash/gui/AdvancedSettingsDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/AdvancedSettingsDialog.java @@ -256,7 +256,7 @@ public class AdvancedSettingsDialog extends AppDialog { Map tabs = new HashMap<>(); getCategories(componentsMap, tabs, skinComboBox, getResourceBundle()); - String catOrder[] = new String[]{"ui", "display", "decompilation", "script", "format", "export", "import", "paths", "limit", "update", "debug", "other"}; + String[] catOrder = new String[]{"ui", "display", "decompilation", "script", "format", "export", "import", "paths", "limit", "update", "debug", "other"}; for (String cat : catOrder) { if (!tabs.containsKey(cat)) { diff --git a/src/com/jpexs/decompiler/flash/gui/DebugPanel.java b/src/com/jpexs/decompiler/flash/gui/DebugPanel.java index 8660bf7f2..d36a02279 100644 --- a/src/com/jpexs/decompiler/flash/gui/DebugPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/DebugPanel.java @@ -93,9 +93,9 @@ public class DebugPanel extends JPanel { private void safeSetTreeModel(MyTreeTable tt, MyTreeTableModel tmodel) { List> expanded = View.getExpandedNodes(tt.getTree()); - int selRows[] = tt.getSelectedRows(); + int[] selRows = tt.getSelectedRows(); - TreePath selPaths[] = new TreePath[selRows.length]; + TreePath[] selPaths = new TreePath[selRows.length]; for (int i = 0; i < selRows.length; i++) { selPaths[i] = tt.getTree().getPathForRow(selRows[i]); } diff --git a/src/com/jpexs/decompiler/flash/gui/ExportDialog.java b/src/com/jpexs/decompiler/flash/gui/ExportDialog.java index b09134c99..1e99e32a5 100644 --- a/src/com/jpexs/decompiler/flash/gui/ExportDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/ExportDialog.java @@ -33,6 +33,7 @@ import com.jpexs.decompiler.flash.exporters.modes.SymbolClassExportMode; import com.jpexs.decompiler.flash.exporters.modes.TextExportMode; import com.jpexs.decompiler.flash.gui.tagtree.TagTreeModel; import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; +import com.jpexs.decompiler.flash.tags.DefineSpriteTag; import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; import com.jpexs.decompiler.flash.tags.base.ASMSource; import com.jpexs.decompiler.flash.tags.base.ButtonTag; @@ -136,7 +137,7 @@ public class ExportDialog extends AppDialog { public E getValue(Class option) { for (int i = 0; i < optionClasses.length; i++) { if (option == optionClasses[i]) { - E values[] = option.getEnumConstants(); + E[] values = option.getEnumConstants(); return values[combos[i].getSelectedIndex()]; } } @@ -163,7 +164,7 @@ public class ExportDialog extends AppDialog { for (int i = 0; i < optionNames.length; i++) { int selIndex = combos[i].getSelectedIndex(); Class c = optionClasses[i]; - Object vals[] = c.getEnumConstants(); + Object[] vals = c.getEnumConstants(); String key = optionNames[i] + "." + vals[selIndex].toString().toLowerCase(); if (i > 0) { cfg.append(","); @@ -175,6 +176,25 @@ public class ExportDialog extends AppDialog { Configuration.lastSelectedExportFormats.set(cfg.toString()); } + private boolean optionCanHandle(int optionIndex, Object e) { + for (int i = 0; i < objClasses[optionIndex].length; i++) { + Class c = objClasses[optionIndex][i]; + if (c.isInstance(e)) { + if (c == Frame.class) { //Frame class can be SWF frame or Sprite frame + Frame f = (Frame) e; + boolean isSprite = (f.timeline.timelined instanceof DefineSpriteTag); + boolean spritesWanted = optionClasses[optionIndex] == SpriteExportMode.class; + if (spritesWanted == isSprite) { //both true or both false + return true; + } + } else { + return true; + } + } + } + return false; + } + public ExportDialog(List exportables) { setTitle(translate("dialog.title")); setResizable(false); @@ -190,10 +210,8 @@ public class ExportDialog extends AppDialog { exportableExists = true; } else { for (TreeItem e : exportables) { - for (int j = 0; j < objClasses[i].length; j++) { - if (objClasses[i][j].isInstance(e)) { - exportableExists = true; - } + if (optionCanHandle(i, e)) { + exportableExists = true; } } } @@ -215,7 +233,7 @@ public class ExportDialog extends AppDialog { exportFormatsStr = null; } - String exportFormatsArr[] = new String[0]; + String[] exportFormatsArr = new String[0]; if (exportFormatsStr != null) { if (exportFormatsStr.contains(",")) { exportFormatsArr = exportFormatsStr.split(","); @@ -250,8 +268,8 @@ public class ExportDialog extends AppDialog { boolean zoomable = false; for (int i = 0; i < optionNames.length; i++) { Class c = optionClasses[i]; - Object vals[] = c.getEnumConstants(); - String names[] = new String[vals.length]; + Object[] vals = c.getEnumConstants(); + String[] names = new String[vals.length]; int itemIndex = -1; for (int j = 0; j < vals.length; j++) { diff --git a/src/com/jpexs/decompiler/flash/gui/FontEmbedDialog.java b/src/com/jpexs/decompiler/flash/gui/FontEmbedDialog.java index 42698e0f9..2579206b0 100644 --- a/src/com/jpexs/decompiler/flash/gui/FontEmbedDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/FontEmbedDialog.java @@ -91,7 +91,7 @@ public class FontEmbedDialog extends AppDialog { Font f = getSelectedFont(); if (allCheckbox.isSelected()) { for (int i = 0; i < rangeCheckboxes.length; i++) { - int codes[] = CharacterRanges.rangeCodes(i); + int[] codes = CharacterRanges.rangeCodes(i); for (int c : codes) { if (f.canDisplay(c)) { chars.add(c); @@ -101,7 +101,7 @@ public class FontEmbedDialog extends AppDialog { } else { for (int i = 0; i < rangeCheckboxes.length; i++) { if (rangeCheckboxes[i].isSelected()) { - int codes[] = CharacterRanges.rangeCodes(i); + int[] codes = CharacterRanges.rangeCodes(i); for (int c : codes) { if (f.canDisplay(c)) { chars.add(c); @@ -309,7 +309,7 @@ public class FontEmbedDialog extends AppDialog { Set allChars = new HashSet<>(); for (int i = 0; i < rc; i++) { rangeNames[i] = CharacterRanges.rangeName(i); - int codes[] = CharacterRanges.rangeCodes(i); + int[] codes = CharacterRanges.rangeCodes(i); int avail = 0; StringBuilder sample = new StringBuilder(); for (int c = 0; c < codes.length; c++) { diff --git a/src/com/jpexs/decompiler/flash/gui/FontPanel.java b/src/com/jpexs/decompiler/flash/gui/FontPanel.java index 03e762a58..8391f62c1 100644 --- a/src/com/jpexs/decompiler/flash/gui/FontPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/FontPanel.java @@ -642,7 +642,7 @@ public class FontPanel extends JPanel { Set selChars = new HashSet<>(); try { Font f = Font.createFont(Font.TRUETYPE_FONT, selfile); - int required[] = new int[]{0x0001, 0x0000, 0x000D, 0x0020}; + int[] required = new int[]{0x0001, 0x0000, 0x000D, 0x0020}; loopi: for (char i = 0; i < Character.MAX_VALUE; i++) { for (int r : required) { diff --git a/src/com/jpexs/decompiler/flash/gui/GenericTagPanel.java b/src/com/jpexs/decompiler/flash/gui/GenericTagPanel.java index b0c47d8ec..d3ed4c0f0 100644 --- a/src/com/jpexs/decompiler/flash/gui/GenericTagPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/GenericTagPanel.java @@ -290,7 +290,7 @@ public class GenericTagPanel extends JPanel implements ChangeListener { final int val = sb.getValue(); //save scroll top SWFType swfType = field.getAnnotation(SWFType.class); if (swfType != null && !swfType.countField().isEmpty()) { //Fields with same countField must be removed from too - Field fields[] = obj.getClass().getDeclaredFields(); + Field[] fields = obj.getClass().getDeclaredFields(); for (int f = 0; f < fields.length; f++) { SWFType fieldSwfType = fields[f].getAnnotation(SWFType.class); if (fieldSwfType != null && fieldSwfType.countField().equals(swfType.countField())) { @@ -338,7 +338,7 @@ public class GenericTagPanel extends JPanel implements ChangeListener { final int val = sb.getValue(); //save scroll top SWFType swfType = field.getAnnotation(SWFType.class); if (swfType != null && !swfType.countField().isEmpty()) { //Fields with same countField must be enlarged too - Field fields[] = obj.getClass().getDeclaredFields(); + Field[] fields = obj.getClass().getDeclaredFields(); for (int f = 0; f < fields.length; f++) { SWFType fieldSwfType = fields[f].getAnnotation(SWFType.class); if (fieldSwfType != null && fieldSwfType.countField().equals(swfType.countField())) { diff --git a/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java b/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java index 60dee4471..558b5e0f5 100644 --- a/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java @@ -1068,7 +1068,7 @@ public class GenericTagTreePanel extends GenericTagPanel { List ret = fieldCache.get(cls); if (ret == null) { ret = new ArrayList<>(); - Field fields[] = cls.getFields(); + Field[] fields = cls.getFields(); for (Field f : fields) { if (Modifier.isStatic(f.getModifiers())) { continue; @@ -1092,7 +1092,7 @@ public class GenericTagTreePanel extends GenericTagPanel { private void addItem(Object obj, Field field, int index, Class cls) { SWFArray swfArray = field.getAnnotation(SWFArray.class); if (swfArray != null && !swfArray.countField().isEmpty()) { //Fields with same countField must be enlarged too - Field fields[] = obj.getClass().getDeclaredFields(); + Field[] fields = obj.getClass().getDeclaredFields(); List sameFlds = new ArrayList<>(); for (int f = 0; f < fields.length; f++) { SWFArray fieldSwfArray = fields[f].getAnnotation(SWFArray.class); @@ -1154,7 +1154,7 @@ public class GenericTagTreePanel extends GenericTagPanel { private void removeItem(Object obj, Field field, int index) { SWFArray swfArray = field.getAnnotation(SWFArray.class); if (swfArray != null && !swfArray.countField().isEmpty()) { //Fields with same countField must be removed from too - Field fields[] = obj.getClass().getDeclaredFields(); + Field[] fields = obj.getClass().getDeclaredFields(); for (int f = 0; f < fields.length; f++) { SWFArray fieldSwfArray = fields[f].getAnnotation(SWFArray.class); if (fieldSwfArray != null && fieldSwfArray.countField().equals(swfArray.countField())) { diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java index b17e9570f..4299dbb96 100644 --- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -47,6 +47,7 @@ import com.jpexs.decompiler.flash.types.SOUNDINFO; import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.Cache; import com.jpexs.helpers.SerializableImage; +import com.jpexs.helpers.Stopwatch; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.BorderLayout; @@ -206,6 +207,9 @@ public final class ImagePanel extends JPanel implements MediaDisplay { if (img == null) { return; } + if (renderImage == null) { + return; + } Graphics2D g2 = null; do { @@ -1034,7 +1038,13 @@ public final class ImagePanel extends JPanel implements MediaDisplay { img = null; if (display) { + Stopwatch sw = Stopwatch.startNew(); img = getFrame(swf, frame, time, timelined, renderContext, selectedDepth, zoomDouble); + sw.stop(); + if (sw.getElapsedMilliseconds() > 100) { + logger.log(Level.WARNING, "Slow rendering. {0}. frame, time={1}, {2}ms", new Object[]{frame, time, sw.getElapsedMilliseconds()}); + } + if (renderContext.borderImage != null) { img = renderContext.borderImage; } diff --git a/src/com/jpexs/decompiler/flash/gui/Main.java b/src/com/jpexs/decompiler/flash/gui/Main.java index 94bd445cb..c554c43b1 100644 --- a/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/src/com/jpexs/decompiler/flash/gui/Main.java @@ -221,7 +221,6 @@ public class Main { final String ffile = file; CancellableWorker runWorker = new CancellableWorker() { - @Override protected Object doInBackground() throws Exception { Process proc; @@ -280,7 +279,6 @@ public class Main { freeRun(); mainFrame.getMenu().updateComponents(); } - }; mainFrame.getMenu().updateComponents(); @@ -458,7 +456,6 @@ public class Main { final File fTempFile = tempFile; final List tempFiles = new ArrayList<>(); CancellableWorker instrumentWorker = new CancellableWorker() { - @Override protected Object doInBackground() throws Exception { @@ -486,7 +483,6 @@ public class Main { Main.startDebugger(); runPlayer(AppStrings.translate("work.debugging.wait"), playerLocation, fTempFile.getAbsolutePath(), flashVars); } - }; Main.startWork(AppStrings.translate("work.debugging.instrumenting"), instrumentWorker); @@ -712,7 +708,6 @@ public class Main { final String[] yesno = new String[]{AppStrings.translate("button.yes"), AppStrings.translate("button.no"), AppStrings.translate("button.yes.all"), AppStrings.translate("button.no.all")}; CancellableWorker worker = new CancellableWorker() { - private boolean yestoall = false; private boolean notoall = false; @@ -848,7 +843,6 @@ public class Main { } @Override - public SWF doInBackground() throws Exception { return open(fInputStream, sourceInfo.getFile(), sourceInfo.getFileTitle()); } @@ -1047,7 +1041,7 @@ public class Main { public OpenFileWorker(SWFSourceInfo[] sourceInfos, Runnable executeAfterOpen, int[] reloadIndices) { this.sourceInfos = sourceInfos; this.executeAfterOpen = executeAfterOpen; - int indices[] = new int[sourceInfos.length]; + int[] indices = new int[sourceInfos.length]; for (int i = 0; i < indices.length; i++) { indices[i] = -1; } @@ -1574,7 +1568,6 @@ public class Main { loadingDialog = new LoadingDialog(); DebuggerTools.initDebugger().addMessageListener(new DebugListener() { - @Override public void onMessage(String clientId, String msg) { } @@ -1620,7 +1613,6 @@ public class Main { flashDebugger = new Debugger(); debugHandler = new DebuggerHandler(); debugHandler.addBreakListener(new DebuggerHandler.BreakListener() { - @Override public void doContinue() { mainFrame.getPanel().clearDebuggerColors(); @@ -1629,7 +1621,6 @@ public class Main { @Override public void breakAt(String scriptName, int line, final int classIndex, final int traitIndex, final int methodIndex) { View.execInEventDispatch(new Runnable() { - @Override public void run() { mainFrame.getPanel().gotoScriptLine(getMainFrame().getPanel().getCurrentSwf(), scriptName, line, classIndex, traitIndex, methodIndex); @@ -1638,7 +1629,6 @@ public class Main { } }); debugHandler.addConnectionListener(new DebuggerHandler.ConnectionListener() { - @Override public void connected() { Main.mainFrame.getMenu().updateComponents(); @@ -1801,7 +1791,7 @@ public class Main { if (Advapi32Util.registryValueExists(WinReg.HKEY_LOCAL_MACHINE, uninstKey, "NSIS: Language")) { String installedLoc = Advapi32Util.registryGetStringValue(WinReg.HKEY_LOCAL_MACHINE, uninstKey, "NSIS: Language"); int lcid = Integer.parseInt(installedLoc); - char buf[] = new char[9]; + char[] buf = new char[9]; int cnt = Kernel32.INSTANCE.GetLocaleInfo(lcid, Kernel32.LOCALE_SISO639LANGNAME, buf, 9); String langCode = new String(buf, 0, cnt).trim().toLowerCase(); @@ -1848,8 +1838,7 @@ public class Main { if (!tempDir.exists()) { return; } - File delFiles[] = tempDir.listFiles(new FilenameFilter() { - + File[] delFiles = tempDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.matches("ffdec_cache.*\\.tmp") || name.matches("javactivex_.*\\.exe") || name.matches("temp[0-9]+\\.swf") || name.matches("ffdec_view_.*\\.swf"); @@ -1930,7 +1919,7 @@ public class Main { List exfiles = new ArrayList<>(); List extitles = new ArrayList<>(); String lastSessionTitles = Configuration.lastSessionFileTitles.get(); - String fileTitles[] = new String[0]; + String[] fileTitles = new String[0]; if (lastSessionTitles != null && !lastSessionTitles.isEmpty()) { fileTitles = lastSessionTitles.split(File.pathSeparator, -1); } @@ -2294,7 +2283,6 @@ public class Main { } Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override public void uncaughtException(Thread t, Throwable e) { logger.log(Level.SEVERE, "Uncaught exception in thread: " + t.getName(), e); diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java index 93efd3cff..0b687bf5b 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java @@ -437,7 +437,6 @@ public abstract class MainFrameMenu implements MenuBuilder { CheckResources.checkResources(stream, null); final String str = new String(os.toByteArray(), Utf8Helper.charset); JDialog dialog = new JDialog() { - @Override public void setVisible(boolean bln) { setSize(new Dimension(800, 600)); @@ -1072,6 +1071,10 @@ public abstract class MainFrameMenu implements MenuBuilder { finishMenu(""); } + public void showResourcesView() { + viewResourcesActionPerformed(null); + } + private void viewResourcesActionPerformed(ActionEvent evt) { Configuration.dumpView.set(false); mainFrame.getPanel().showView(MainPanel.VIEW_RESOURCES); diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java index 08ca06ffe..44f7441cc 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java @@ -207,7 +207,7 @@ public class MainFrameRibbonMenu extends MainFrameMenu { throw new IllegalArgumentException("Not a menu: " + path); } - String parts[] = path.contains("/") ? path.split("\\/") : new String[]{""}; + String[] parts = path.contains("/") ? path.split("\\/") : new String[]{""}; List subs = menuSubs.get(path); boolean onlyCheckboxes = true; @@ -422,7 +422,7 @@ public class MainFrameRibbonMenu extends MainFrameMenu { bandCount++; } } - AbstractRibbonBand bands[] = new AbstractRibbonBand[bandCount]; + AbstractRibbonBand[] bands = new AbstractRibbonBand[bandCount]; int b = 0; for (String sub : subs) { if (sub.equals("-")) { diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 79265b496..bfd69ac4b 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -463,7 +463,6 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se tagTree = new TagTree(null, this); tagTree.addTreeSelectionListener(this); tagTree.setSelectionModel(new DefaultTreeSelectionModel() { - private boolean isModified() { if (abcPanel != null && abcPanel.isEditing()) { abcPanel.tryAutoSave(); @@ -2097,7 +2096,6 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se String selFile = Helper.fixDialogFile(chooser.getSelectedFile()).getAbsolutePath(); File textsFile = new File(Path.combine(selFile, TextExportSettings.EXPORT_FOLDER_NAME, TextExporter.TEXT_EXPORT_FILENAME_FORMATTED)); TextImporter textImporter = new TextImporter(getMissingCharacterHandler(), new TextImportErrorHandler() { - // "configuration items" for the current replace only private final ConfigurationItem showAgainImportError = new ConfigurationItem<>("showAgainImportError", true, true); @@ -2528,7 +2526,6 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se private MissingCharacterHandler getMissingCharacterHandler() { return new MissingCharacterHandler() { - // "configuration items" for the current replace only private final ConfigurationItem showAgainIgnoreMissingCharacters = new ConfigurationItem<>("showAgainIgnoreMissingCharacters", true, true); @@ -2645,6 +2642,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se boolean ok = false; try { ok = ds.setSound(new FileInputStream(selfile), soundFormat); + ds.getSwf().clearSoundCache(); } catch (IOException ex) { //ignore } @@ -2840,7 +2838,6 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } } FileFilter ff = new FileFilter() { - @Override public boolean accept(File f) { if (f.isDirectory()) { @@ -3054,12 +3051,10 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se treePanelMode = TreePanelMode.TAG_TREE; treePanel.addComponentListener(new ComponentAdapter() { - @Override public void componentResized(ComponentEvent e) { tagTree.scrollPathToVisible(tagTree.getSelectionPath()); } - }); reload(true); @@ -3109,7 +3104,6 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se Main.loadingDialog.setVisible(true); new CancellableWorker() { - @Override protected Void doInBackground() throws Exception { try { @@ -3117,7 +3111,6 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se try { InputStream is = new ByteArrayInputStream(binaryDataTag.binaryData.getRangeData()); SWF bswf = new SWF(is, null, "(SWF Data)", new ProgressListener() { - @Override public void progress(int p) { Main.loadingDialog.setPercent(p); @@ -3289,7 +3282,6 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } if (!Main.isInited() || !Main.isWorking() || Main.isDebugging()) { CancellableWorker worker = new CancellableWorker() { - @Override protected Void doInBackground() throws Exception { ABCPanel abcPanel = getABCPanel(); @@ -3495,7 +3487,6 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se public static Timelined makeTimelined(final Tag tag, final int fontFrameNum) { return new Timelined() { - private Timeline tim; @Override diff --git a/src/com/jpexs/decompiler/flash/gui/MenuBuilder.java b/src/com/jpexs/decompiler/flash/gui/MenuBuilder.java index 51ddd2dd3..29da062ab 100644 --- a/src/com/jpexs/decompiler/flash/gui/MenuBuilder.java +++ b/src/com/jpexs/decompiler/flash/gui/MenuBuilder.java @@ -105,7 +105,7 @@ public interface MenuBuilder { } public HotKey(String h) { - String parts[] = h.contains("+") ? h.split("\\+") : new String[]{h}; + String[] parts = h.contains("+") ? h.split("\\+") : new String[]{h}; for (String s : parts) { switch (s) { case "SHIFT": diff --git a/src/com/jpexs/decompiler/flash/gui/View.java b/src/com/jpexs/decompiler/flash/gui/View.java index 2f182c3eb..add04b510 100644 --- a/src/com/jpexs/decompiler/flash/gui/View.java +++ b/src/com/jpexs/decompiler/flash/gui/View.java @@ -308,7 +308,7 @@ public class View { List images = new ArrayList<>(); MyResizableIcon[] icons = MyRibbonApplicationMenuButtonUI.getIcons(); MyResizableIcon icon = icons[1]; - int sizes[] = new int[]{256, 128, 64, 42, 40, 32, 20, 16}; + int[] sizes = new int[]{256, 128, 64, 42, 40, 32, 20, 16}; for (int size : sizes) { icon.setIconSize(size, size); BufferedImage bi = new BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR); @@ -462,7 +462,7 @@ public class View { } public static int showConfirmDialog(final Component parentComponent, final Object message, final String title, final int optionType, final int messageTyp) { - final int ret[] = new int[1]; + final int[] ret = new int[1]; execInEventDispatch(() -> { ret[0] = JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageTyp); }); @@ -489,7 +489,7 @@ public class View { warPanel.add(donotShowAgainCheckBox, BorderLayout.SOUTH); } - final int ret[] = new int[1]; + final int[] ret = new int[1]; final Object messageObj = warPanel == null ? message : warPanel; execInEventDispatch(() -> { ret[0] = JOptionPane.showConfirmDialog(parentComponent, messageObj, title, optionType, messageType); diff --git a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java index 2c24e0deb..083b1238f 100644 --- a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java @@ -577,7 +577,7 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener 0 && (cnt = is.read(data, off, len)) > 0) { @@ -147,7 +147,7 @@ public class Debugger { } len = (len << 8) + len2; - byte buf[] = new byte[len]; + byte[] buf = new byte[len]; for (int i = 0; i < len; i++) { int rd = is.read(); if (rd == -1) { @@ -180,7 +180,7 @@ public class Debugger { } } else { if (!ret.isEmpty()) { - String param[] = (ret.contains(";") ? ret.split(";") : new String[]{ret}); + String[] param = (ret.contains(";") ? ret.split(";") : new String[]{ret}); for (String p : param) { if (p.contains("=")) { String key = p.substring(0, p.indexOf('=')); @@ -215,7 +215,7 @@ public class Debugger { } break; case MSG_LOADER_BYTES: - byte retB[] = readBytes(is); + byte[] retB = readBytes(is); for (DebugListener l : listeners) { l.onLoaderBytes(clientName, retB); } diff --git a/src/com/jpexs/decompiler/flash/gui/debugger/DebuggerTools.java b/src/com/jpexs/decompiler/flash/gui/debugger/DebuggerTools.java index dadf683a8..e06f83a3d 100644 --- a/src/com/jpexs/decompiler/flash/gui/debugger/DebuggerTools.java +++ b/src/com/jpexs/decompiler/flash/gui/debugger/DebuggerTools.java @@ -170,7 +170,7 @@ public class DebuggerTools { } } else { Random rnd = new Random(); - byte rb[] = new byte[16]; + byte[] rb = new byte[16]; rnd.nextBytes(rb); String rhex = Helper.byteArrayToHex(rb); try { diff --git a/src/com/jpexs/decompiler/flash/gui/dumpview/DumpTree.java b/src/com/jpexs/decompiler/flash/gui/dumpview/DumpTree.java index 18322c0ac..3adbdbbc4 100644 --- a/src/com/jpexs/decompiler/flash/gui/dumpview/DumpTree.java +++ b/src/com/jpexs/decompiler/flash/gui/dumpview/DumpTree.java @@ -25,6 +25,8 @@ import com.jpexs.decompiler.flash.action.Action; import com.jpexs.decompiler.flash.action.ActionListReader; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.dumpview.DumpInfo; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecial; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; import com.jpexs.decompiler.flash.dumpview.DumpInfoSwfNode; import com.jpexs.decompiler.flash.gui.Main; import com.jpexs.decompiler.flash.gui.MainPanel; @@ -72,6 +74,7 @@ import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag; import com.jpexs.decompiler.flash.tags.ShowFrameTag; import com.jpexs.decompiler.flash.tags.SoundStreamHead2Tag; import com.jpexs.decompiler.flash.tags.SoundStreamHeadTag; +import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.gfx.DefineCompactedFont; import com.jpexs.helpers.Helper; import com.jpexs.helpers.MemoryInputStream; @@ -270,6 +273,18 @@ public class DumpTree extends JTree { parseInstructionsMenuItem.addActionListener(this::parseInstructionsButtonActionPerformed); contextPopupMenu.add(parseInstructionsMenuItem); + final JMenuItem gotoTagMenuItem = new JMenuItem(mainPanel.translate("contextmenu.showInResources")); + gotoTagMenuItem.addActionListener(this::gotoTagButtonActionPerformed); + contextPopupMenu.add(gotoTagMenuItem); + + final JMenuItem gotoActionListMenuItem = new JMenuItem(mainPanel.translate("contextmenu.showInResources")); + gotoActionListMenuItem.addActionListener(this::gotoActionListButtonActionPerformed); + contextPopupMenu.add(gotoActionListMenuItem); + + final JMenuItem gotoMethodMenuItem = new JMenuItem(mainPanel.translate("contextmenu.showInResources")); + gotoMethodMenuItem.addActionListener(this::gotoMethodButtonActionPerformed); + contextPopupMenu.add(gotoMethodMenuItem); + addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { @@ -293,9 +308,13 @@ public class DumpTree extends JTree { parseActionsMenuItem.setVisible(false); parseAbcMenuItem.setVisible(false); parseInstructionsMenuItem.setVisible(false); + gotoTagMenuItem.setVisible(false); + gotoActionListMenuItem.setVisible(false); + gotoMethodMenuItem.setVisible(false); if (paths.length == 1) { DumpInfo treeNode = (DumpInfo) paths[0].getLastPathComponent(); + DumpInfoSpecialType specialType = getSpecialType(treeNode); if (treeNode instanceof DumpInfoSwfNode) { closeSelectionMenuItem.setVisible(true); @@ -303,22 +322,38 @@ public class DumpTree extends JTree { if (treeNode.getEndByte() - treeNode.startByte > 3) { saveToFileMenuItem.setVisible(true); - if (treeNode.name.equals("bitmapAlphaData") || treeNode.name.equals("zlibBitmapData")) { + if (specialType == DumpInfoSpecialType.ZLIB_DATA) { saveUncompressedToFileMenuItem.setVisible(true); } } - // todo honfika: do not use string names, because it has conflicts e.g with DefineFont.code - if (treeNode.name.equals("actionBytes") && treeNode.getChildCount() == 0) { - parseActionsMenuItem.setVisible(true); + boolean noChild = treeNode.getChildCount() == 0; + + if (noChild) { + switch (specialType) { + case ACTION_BYTES: + parseActionsMenuItem.setVisible(true); + break; + case ABC_BYTES: + parseAbcMenuItem.setVisible(true); + break; + case ABC_CODE: + parseInstructionsMenuItem.setVisible(true); + break; + } } - if (treeNode.name.equals("abcBytes") && treeNode.getChildCount() == 0) { - parseAbcMenuItem.setVisible(true); - } - - if (treeNode.name.equals("code") && treeNode.parent.name.equals("method_body") && treeNode.getChildCount() == 0) { - parseInstructionsMenuItem.setVisible(true); + switch (specialType) { + case TAG: + gotoTagMenuItem.setVisible(true); + break; + case ACTION_BYTES: + gotoActionListMenuItem.setVisible(true); + break; + case ABC_CODE: + case ABC_METHOD_BODY: + gotoMethodMenuItem.setVisible(true); + break; } TreeModel model = getModel(); @@ -331,6 +366,13 @@ public class DumpTree extends JTree { }); } + private DumpInfoSpecialType getSpecialType(DumpInfo dumpInfo) { + DumpInfoSpecialType specialType = dumpInfo instanceof DumpInfoSpecial + ? ((DumpInfoSpecial) dumpInfo).specialType + : DumpInfoSpecialType.NONE; + return specialType; + } + private void expandRecursiveButtonActionPerformed(ActionEvent evt) { TreePath path = getSelectionPath(); if (path == null) { @@ -358,11 +400,10 @@ public class DumpTree extends JTree { if (fc.showSaveDialog(f) == JFileChooser.APPROVE_OPTION) { File sf = Helper.fixDialogFile(fc.getSelectedFile()); try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(sf))) { + byte[] data = DumpInfoSwfNode.getSwfNode(dumpInfo).getSwf().originalUncompressedData; if (decompress) { - byte[] data = DumpInfoSwfNode.getSwfNode(dumpInfo).getSwf().originalUncompressedData; fos.write(SWFInputStream.uncompressByteArray(data, (int) dumpInfo.startByte, (int) (dumpInfo.getEndByte() - dumpInfo.startByte + 1))); } else { - byte[] data = DumpInfoSwfNode.getSwfNode(dumpInfo).getSwf().originalUncompressedData; fos.write(data, (int) dumpInfo.startByte, (int) (dumpInfo.getEndByte() - dumpInfo.startByte + 1)); } } catch (IOException ex) { @@ -431,6 +472,48 @@ public class DumpTree extends JTree { repaint(); } + private void gotoTagButtonActionPerformed(ActionEvent evt) { + TreePath[] paths = getSelectionPaths(); + DumpInfoSpecial dumpInfo = (DumpInfoSpecial) paths[0].getLastPathComponent(); + + SWF swf = DumpInfoSwfNode.getSwfNode(dumpInfo).getSwf(); + long address = (long) (Long) dumpInfo.specialValue; + Tag foundTag = null; + for (Tag tag : swf.getTags()) { + if (tag.getOriginalRange().getPos() == address) { + foundTag = tag; + break; + } + } + + if (foundTag != null) { + mainPanel.getMainFrame().getMenu().showResourcesView(); + mainPanel.setTagTreeSelectedNode(foundTag); + } + } + + private void gotoActionListButtonActionPerformed(ActionEvent evt) { + TreePath[] paths = getSelectionPaths(); + DumpInfoSpecial dumpInfo = (DumpInfoSpecial) paths[0].getLastPathComponent(); + + SWF swf = DumpInfoSwfNode.getSwfNode(dumpInfo).getSwf(); + long address = (long) (Long) dumpInfo.specialValue; + mainPanel.getMainFrame().getMenu().showResourcesView(); + //mainPanel.setTagTreeSelectedNode(asm); + } + + private void gotoMethodButtonActionPerformed(ActionEvent evt) { + TreePath[] paths = getSelectionPaths(); + DumpInfoSpecial dumpInfo = (DumpInfoSpecial) paths[0].getLastPathComponent(); + if (dumpInfo.specialType == DumpInfoSpecialType.ABC_CODE) { + dumpInfo = (DumpInfoSpecial) dumpInfo.parent; // method_body + } + + SWF swf = DumpInfoSwfNode.getSwfNode(dumpInfo).getSwf(); + int method_info = (int) dumpInfo.specialValue; + // todo + } + private void closeSwfButtonActionPerformed(ActionEvent evt) { Main.closeFile(mainPanel.getCurrentSwfList()); } diff --git a/src/com/jpexs/decompiler/flash/gui/dumpview/DumpViewPanel.java b/src/com/jpexs/decompiler/flash/gui/dumpview/DumpViewPanel.java index 09fdb08b4..4610f45a6 100644 --- a/src/com/jpexs/decompiler/flash/gui/dumpview/DumpViewPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/dumpview/DumpViewPanel.java @@ -18,16 +18,27 @@ package com.jpexs.decompiler.flash.gui.dumpview; import com.jpexs.decompiler.flash.dumpview.DumpInfo; import com.jpexs.decompiler.flash.dumpview.DumpInfoSwfNode; +import com.jpexs.decompiler.flash.gui.MyTextField; +import com.jpexs.decompiler.flash.gui.View; import com.jpexs.decompiler.flash.gui.hexview.HexView; import com.jpexs.decompiler.flash.gui.hexview.HexViewListener; import com.jpexs.helpers.Helper; +import com.jpexs.helpers.utf8.Utf8Helper; import java.awt.BorderLayout; +import java.awt.Color; import java.awt.Dimension; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.List; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; @@ -43,6 +54,10 @@ public class DumpViewPanel extends JPanel { private final HexView dumpViewHexTable; + private JTextField filterField = new MyTextField(""); + + private JPanel searchPanel; + private final DumpTree dumpTree; private DumpInfo selectedDumpInfo; @@ -68,7 +83,6 @@ public class DumpViewPanel extends JPanel { dumpViewHexTable = new HexView(); dumpViewHexTable.addListener(new HexViewListener() { - private int lastAddressUnderCursor = -1; @Override @@ -136,7 +150,158 @@ public class DumpViewPanel extends JPanel { } }); - add(new JScrollPane(dumpViewHexTable), BorderLayout.CENTER); + searchPanel = new JPanel(); + searchPanel.setLayout(new BorderLayout()); + searchPanel.add(filterField, BorderLayout.CENTER); + searchPanel.add(new JLabel(View.getIcon("search16")), BorderLayout.WEST); + JLabel closeSearchButton = new JLabel(View.getIcon("cancel16")); + closeSearchButton.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + closeDumpViewSearch(); + } + }); + searchPanel.add(closeSearchButton, BorderLayout.EAST); + searchPanel.setVisible(false); + + dumpViewHexTable.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if ((e.getKeyCode() == 'F') && (e.isControlDown())) { + searchPanel.setVisible(true); + filterField.requestFocusInWindow(); + } + } + }); + + filterField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void changedUpdate(DocumentEvent e) { + warn(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + warn(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + warn(); + } + + public void warn() { + doSearch(); + } + }); + + JPanel hexPanel = new JPanel(new BorderLayout()); + hexPanel.add(new JScrollPane(dumpViewHexTable), BorderLayout.CENTER); + hexPanel.add(searchPanel, BorderLayout.SOUTH); + add(hexPanel, BorderLayout.CENTER); + } + + public void closeDumpViewSearch() { + filterField.setText(""); + doSearch(); + searchPanel.setVisible(false); + } + + private void doSearch() { + filterField.setBackground(Color.white); + + String text = filterField.getText(); + if (text.length() == 0) { + dumpViewHexTable.clearSelectedBytes(); + return; + } + + byte[] data = dumpViewHexTable.getData(); + + byte[] textBytes = Utf8Helper.getBytes(text); + byte[] hex = getAsHex(text); + byte[] foundArray = textBytes; + + int pos = textBytes == null ? -1 : findHex(data, textBytes, 0, data.length); + int hexPos = hex == null ? -1 : findHex(data, hex, 0, data.length); + + if (pos == -1 || (hexPos != -1 && hexPos < pos)) { + pos = hexPos; + foundArray = hex; + } + + if (pos != -1) { + dumpViewHexTable.selectBytes(pos, foundArray.length); + } else { + dumpViewHexTable.clearSelectedBytes(); + filterField.setBackground(Color.red); + } + } + + private int findHex(byte[] data, byte[] searchData, int from, int to) { + for (int i = from; i < to; i++) { + if (isMatch(data, searchData, i)) { + return i; + } + } + + return -1; + } + + private boolean isMatch(byte[] data, byte[] searchData, int pos) { + if (pos + searchData.length > data.length) { + return false; + } + + for (int i = 0; i < searchData.length; i++) { + if (data[pos + i] != searchData[i]) { + return false; + } + } + + return true; + } + + private byte[] getAsHex(String text) { + int charCount = 0; + for (int i = 0; i < text.length(); i++) { + char ch = Character.toUpperCase(text.charAt(i)); + boolean whiteSpace = Character.isWhitespace(ch); + if (!((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || whiteSpace)) { + return null; + } + + if (!whiteSpace) { + charCount++; + } + } + + if (charCount % 2 == 1) { + // hex character count should be even + return null; + } + + byte[] result = new byte[charCount / 2]; + int cnt = 0; + int v0 = 0; + for (int i = 0; i < text.length(); i++) { + char ch = Character.toUpperCase(text.charAt(i)); + if (Character.isWhitespace(ch)) { + continue; + } + + int v = Integer.parseInt(Character.toString(ch), 16); + + if (cnt % 2 == 1) { + result[cnt / 2] = (byte) (v0 * 16 + v); + } else { + v0 = v; + } + + cnt++; + } + + return result; } public void clear() { diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/fileoffset16.png b/src/com/jpexs/decompiler/flash/gui/graphics/fileoffset16.png index 5deb5c2bf..7548fefef 100644 Binary files a/src/com/jpexs/decompiler/flash/gui/graphics/fileoffset16.png and b/src/com/jpexs/decompiler/flash/gui/graphics/fileoffset16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/originalbytes16.png b/src/com/jpexs/decompiler/flash/gui/graphics/originalbytes16.png index 5deb5c2bf..5aeb6fe25 100644 Binary files a/src/com/jpexs/decompiler/flash/gui/graphics/originalbytes16.png and b/src/com/jpexs/decompiler/flash/gui/graphics/originalbytes16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/hexview/HexView.java b/src/com/jpexs/decompiler/flash/gui/hexview/HexView.java index 704ef3d30..8484df926 100644 --- a/src/com/jpexs/decompiler/flash/gui/hexview/HexView.java +++ b/src/com/jpexs/decompiler/flash/gui/hexview/HexView.java @@ -57,6 +57,10 @@ public class HexView extends JTable { private int mouseOverIdx = -1; + private int selectionStart = -1; + + private int selectionEnd = -1; + private HexViewListener listener; private class HighlightCellRenderer extends DefaultTableCellRenderer { @@ -90,10 +94,12 @@ public class HexView extends JTable { background = row % 2 == 0 ? bgColor : bgColorAlternate; } - if (idx != -1 && idx == mouseOverIdx) { + if (idx != -1 && (idx == mouseOverIdx + || (idx >= selectionStart && idx <= selectionEnd))) { foreground = new Color(255 - foreground.getRed(), 255 - foreground.getGreen(), 255 - foreground.getBlue()); background = new Color(255 - background.getRed(), 255 - background.getGreen(), 255 - background.getBlue()); } + l.setForeground(foreground); l.setBackground(background); @@ -225,6 +231,25 @@ public class HexView extends JTable { return getModel().getData(); } + public void selectByte(long byteNum) { + scrollToByte(byteNum); + listener.byteValueChanged((int) byteNum, getData()[(int) byteNum]); + } + + public void selectBytes(long byteNum, int length) { + selectionStart = (int) byteNum; + selectionEnd = (int) (byteNum + length - 1); + scrollToByte(new long[]{byteNum}, new long[]{byteNum + length - 1}); + listener.byteValueChanged((int) byteNum, getData()[(int) byteNum]); + getModel().fireTableDataChanged(); + } + + public void clearSelectedBytes() { + selectionStart = -1; + selectionEnd = -1; + getModel().fireTableDataChanged(); + } + public void scrollToByte(long byteNum) { int row = (int) (byteNum / bytesInRow); diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 126e31f6f..1cd00fb83 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -734,5 +734,5 @@ generic.editor.amf3.help = AMF3 value syntax:\n\ \ * Nonscalar datatypes can be referenced by previously declared "id" attributes with # syntax:\n\ %reference_sample%\n\ \ * Keys in Dictionary entries can be any type\n - -message.flexpath.notset = Flex SDK not found. Please configure its path in Advanced Settings / Paths (4). \ No newline at end of file +contextmenu.showInResources = Show in Resources +message.flexpath.notset = Flex SDK not found. Please configure its path in Advanced Settings / Paths (4). diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties index 5b3edf76e..28d000a0a 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -688,3 +688,44 @@ debug.break.reason.stopRequest = (Zastaveno) debug.break.reason.step = (Step) debug.break.reason.halt = (Halt) debug.break.reason.scriptLoaded = (Skript na\u010dten) + +menu.file.start.debugpcode = Lad\u011bn\u00ed P-k\u00f3du + +#after 7.1.2 +button.replaceNoFill = Nahradit - aktualizovat hranice... +message.warning.svgImportExperimental = Ne v\u0161echny SVG vlastnosti jsou podporov\u00e1ny. Pros\u00edm zkontrolujte log po importu. + +message.imported.swf = SWF soubor pou\u017e\u00edv\u00e1 assety z importovan\u00e9ho SWF souboru:\n%url%\nChcete tyto assety na\u010d\u00edst z dan\u00e9ho URL? +message.imported.swf.manually = Nelze na\u010d\u00edst importovan\u00e9 SWF\n%url%\nSoubor nebo URL neexistuje.\nChcete vybrat m\u00edstn\u00ed soubor? + +message.warning.hexViewNotUpToDate = Hexa pohled nen\u00ed aktu\u00e1ln\u00ed. Pros\u00edm ulo\u017ete a znovuna\u010dt\u011bte soubor pro aktualizaci hexa pohledu. +message.font.replace.updateTexts = N\u011bkter\u00e9 znaky byly nahrazeny. Chcete aktualizovat existuj\u00edc\u00ed texty? + +menu.settings.simplifyExpressions = Zjednodu\u0161it v\u00fdrazy + +#after 8.0.1 +menu.recentFiles.empty = Seznam ned\u00e1vno otev\u0159en\u00fdch je pr\u00e1zdn\u00fd +message.warning.outOfMemory32BitJre = Nastala chyba z nedostatku pam\u011bti. M\u00e1te spu\u0161t\u011bnou 32bitovou Javu na 64bitov\u00e9m syst\u00e9mu. Pros\u00edm pou\u017eijte 64bitovou Java. + +menu.file.reloadAll = Znovu na\u010d\u00edst v\u0161e +message.confirm.reloadAll = Tato akce stornuje v\u0161echny neulo\u017een\u00e9 zm\u011bny ve v\u0161ech SWF souborech a znovu na\u010dte celou aplikaci.\nChcete pokra\u010dovat? +export.script.singleFilePallelModeWarning = Export skript\u016f do jednoho souboru nen\u00ed dostupn\u00fd se zapnut\u00fdm paralleln\u00edm zrychlen\u00edm + +button.showOriginalBytesInPcodeHex = Zobrazit origin\u00e1ln\u00ed bajty +button.remove = Odstranit +button.showFileOffsetInPcodeHex = Zobrazit offset v souboru + +generic.editor.amf3.title = AMF3 editor +generic.editor.amf3.help = Syntaxe AMF3 hodnoty:\n\ + ------------------\n\ + skal\u00e1rn\u00ed typy:\n\ + %scalar_samples%\ + ostatn\u00ed typy:\n\ + %nonscalar_samples%\ + \n\ + Pozn\u00e1mky:\n\ + \ * Neskal\u00e1rn\u00ed datov\u00e9 typy mohou mohou b\u00fdt odkazov\u00e1ny p\u0159edem deklarovan\u00fdmi "id" atributy s pomoc\u00ed # syntaxe:\n\ + %reference_sample%\n\ + \ * Kl\u00ed\u010de v z\u00e1znamech Dictionary mohou b\u00fdt jak\u00e9hokoli typu\n +contextmenu.showInResources = Zobrazit ve Zdroj\u00edch + \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_fr.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_fr.properties index cb3e3eb26..10419f9e0 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_fr.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_fr.properties @@ -725,16 +725,15 @@ button.remove = Enlever generic.editor.amf3.title = \u00c9diteur AMF3 -#TODO: translate rest of the multiline text! : generic.editor.amf3.help = Syntaxe de la valeur AMF3 :\n\ ------------------\n\ - scalar types:\n\ + types de scalaire:\n\ %scalar_samples%\ - other types:\n\ + autres types:\n\ %nonscalar_samples%\ \n\ Notes:\n\ - \ * Nonscalar datatypes can be referenced by previously declared "id" attributes with # syntax:\n\ + \ * les types de donn\u00e9es non scalaires peuvent faire r\u00e9f\u00e9rence \u00e0 des "id" pr\u00e9c\u00e9demment d\u00e9clar\u00e9s avec les attributs # syntaxe :\n\ %reference_sample%\n\ - \ * Keys in Dictionary entries can be any type\n - \ No newline at end of file + \ * les entr\u00e9es du dictionnaire peuvent \u00eatre de tous types\n +contextmenu.showInResources = Afficher dans les resources \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_zh.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_zh.properties index 546ab5dad..c12a98dd4 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_zh.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_zh.properties @@ -76,7 +76,7 @@ node.sprites = \u7cbe\u7075 node.shapes = \u56fe\u5f62 node.morphshapes = morphshapes node.buttons = \u6309\u94ae -node.frames = \u6846\u67b6 +node.frames = \u5e27 node.scripts = \u811a\u672c message.warning = \u8b66\u544a diff --git a/src/com/jpexs/decompiler/flash/gui/pipes/PipeInputStream.java b/src/com/jpexs/decompiler/flash/gui/pipes/PipeInputStream.java index 7f195773b..8226d0b85 100644 --- a/src/com/jpexs/decompiler/flash/gui/pipes/PipeInputStream.java +++ b/src/com/jpexs/decompiler/flash/gui/pipes/PipeInputStream.java @@ -72,7 +72,7 @@ public class PipeInputStream extends InputStream { @Override public synchronized int read() throws IOException { - byte d[] = new byte[1]; + byte[] d = new byte[1]; if (readPipe(d) == 0) { return -1; } diff --git a/src/com/jpexs/decompiler/flash/gui/pipes/PipeOutputStream.java b/src/com/jpexs/decompiler/flash/gui/pipes/PipeOutputStream.java index 5ca2bd6f2..9f996cad1 100644 --- a/src/com/jpexs/decompiler/flash/gui/pipes/PipeOutputStream.java +++ b/src/com/jpexs/decompiler/flash/gui/pipes/PipeOutputStream.java @@ -72,7 +72,7 @@ public class PipeOutputStream extends OutputStream { @Override public synchronized void write(int b) throws IOException { - byte data[] = new byte[]{(byte) b}; + byte[] data = new byte[]{(byte) b}; IntByReference ibr = new IntByReference(); boolean result = Kernel32.INSTANCE.WriteFile(pipe, data, data.length, ibr, null); if (!result) { diff --git a/src/com/jpexs/decompiler/flash/gui/proxy/ProxyFrame.java b/src/com/jpexs/decompiler/flash/gui/proxy/ProxyFrame.java index 97b56d89f..77a86e3ee 100644 --- a/src/com/jpexs/decompiler/flash/gui/proxy/ProxyFrame.java +++ b/src/com/jpexs/decompiler/flash/gui/proxy/ProxyFrame.java @@ -215,7 +215,7 @@ public class ProxyFrame extends AppFrame implements CatchedListener, MouseListen loadReplacements(); - Object data[][] = new Object[replacements.size()][3]; + Object[][] data = new Object[replacements.size()][3]; for (int i = 0; i < replacements.size(); i++) { Replacement r = replacements.get(i); @@ -228,7 +228,7 @@ public class ProxyFrame extends AppFrame implements CatchedListener, MouseListen @Override public Class getColumnClass(int columnIndex) { - Class classes[] = new Class[]{String.class, SizeItem.class, String.class}; + Class[] classes = new Class[]{String.class, SizeItem.class, String.class}; return classes[columnIndex]; } @@ -370,7 +370,7 @@ public class ProxyFrame extends AppFrame implements CatchedListener, MouseListen } private int[] getSelectedRows() { - int sel[] = replacementsTable.getSelectedRows(); + int[] sel = replacementsTable.getSelectedRows(); for (int i = 0; i < sel.length; i++) { sel[i] = replacementsTable.getRowSorter().convertRowIndexToModel(sel[i]); }